Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for geoWithin.centerSphere queries via withJSON #4825

Merged
merged 6 commits into from
Jun 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions spec/ParseQuery.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3985,4 +3985,106 @@ describe('Parse.Query testing', () => {
})
});

it('withJSON supports geoWithin.centerSphere', (done) => {
const inbound = new Parse.GeoPoint(1.5, 1.5);
const onbound = new Parse.GeoPoint(10, 10);
const outbound = new Parse.GeoPoint(20, 20);
const obj1 = new Parse.Object('TestObject', {location: inbound});
const obj2 = new Parse.Object('TestObject', {location: onbound});
const obj3 = new Parse.Object('TestObject', {location: outbound});
const center = new Parse.GeoPoint(0, 0);
const distanceInKilometers = 1569 + 1; // 1569km is the approximate distance between {0, 0} and {10, 10}.
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
center,
distanceInKilometers / 6371.0
]
}
};
q.withJSON(jsonQ);
return q.find();
}).then(results => {
equal(results.length, 2);
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
[0, 0],
distanceInKilometers / 6371.0
]
}
};
q.withJSON(jsonQ);
return q.find();
}).then(results => {
equal(results.length, 2);
done();
}).catch(error => {
fail(error);
done();
});
});

it('withJSON with geoWithin.centerSphere fails without parameters', (done) => {
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
]
}
};
q.withJSON(jsonQ);
q.find(expectError(Parse.Error.INVALID_JSON, done));
});

it('withJSON with geoWithin.centerSphere fails with invalid distance', (done) => {
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
[0, 0],
'invalid_distance'
]
}
};
q.withJSON(jsonQ);
q.find(expectError(Parse.Error.INVALID_JSON, done));
});

it('withJSON with geoWithin.centerSphere fails with invalid coordinate', (done) => {
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
[-190,-190],
1
]
}
};
q.withJSON(jsonQ);
q.find(expectError(undefined, done));
});

it('withJSON with geoWithin.centerSphere fails with invalid geo point', (done) => {
const q = new Parse.Query(TestObject);
const jsonQ = q.toJSON();
jsonQ.where.location = {
'$geoWithin': {
'$centerSphere': [
{'longitude': 0, 'dummytude': 0},
1
]
}
};
q.withJSON(jsonQ);
q.find(expectError(undefined, done));
});
});
10 changes: 6 additions & 4 deletions spec/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,13 @@ function expectError(errorCode, callback) {
error: function(obj, e) {
// Some methods provide 2 parameters.
e = e || obj;
if (!e) {
fail('expected a specific error but got a blank error');
return;
if (errorCode !== undefined) {
if (!e) {
fail('expected a specific error but got a blank error');
return;
}
expect(e.code).toEqual(errorCode, e.message);
}
expect(e.code).toEqual(errorCode, e.message);
if (callback) {
callback(e);
}
Expand Down
86 changes: 56 additions & 30 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -905,44 +905,70 @@ function transformConstraint(constraint, field) {

case '$geoWithin': {
const polygon = constraint[key]['$polygon'];
let points;
if (typeof polygon === 'object' && polygon.__type === 'Polygon') {
if (!polygon.coordinates || polygon.coordinates.length < 3) {
const centerSphere = constraint[key]['$centerSphere'];
if (polygon !== undefined) {
let points;
if (typeof polygon === 'object' && polygon.__type === 'Polygon') {
if (!polygon.coordinates || polygon.coordinates.length < 3) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; Polygon.coordinates should contain at least 3 lon/lat pairs'
);
}
points = polygon.coordinates;
} else if (polygon instanceof Array) {
if (polygon.length < 3) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; $polygon should contain at least 3 GeoPoints'
);
}
points = polygon;
} else {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; Polygon.coordinates should contain at least 3 lon/lat pairs'
'bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint\'s'
);
}
points = polygon.coordinates;
} else if (polygon instanceof Array) {
if (polygon.length < 3) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; $polygon should contain at least 3 GeoPoints'
);
points = points.map((point) => {
if (point instanceof Array && point.length === 2) {
Parse.GeoPoint._validate(point[1], point[0]);
return point;
}
if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
} else {
Parse.GeoPoint._validate(point.latitude, point.longitude);
}
return [point.longitude, point.latitude];
});
answer[key] = {
'$polygon': points
};
} else if (centerSphere !== undefined) {
if (!(centerSphere instanceof Array) || centerSphere.length < 2) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere should be an array of Parse.GeoPoint and distance');
}
points = polygon;
} else {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint\'s'
);
}
points = points.map((point) => {
// Get point, convert to geo point if necessary and validate
let point = centerSphere[0];
if (point instanceof Array && point.length === 2) {
Parse.GeoPoint._validate(point[1], point[0]);
return point;
point = new Parse.GeoPoint(point[1], point[0]);
} else if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere geo point invalid');
}
if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
} else {
Parse.GeoPoint._validate(point.latitude, point.longitude);
Parse.GeoPoint._validate(point.latitude, point.longitude);
// Get distance and validate
const distance = centerSphere[1];
if(isNaN(distance) || distance < 0) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere distance invalid');
}
return [point.longitude, point.latitude];
});
answer[key] = {
'$polygon': points
};
answer[key] = {
'$centerSphere': [
[point.longitude, point.latitude],
distance
]
};
}
break;
}
case '$geoIntersects': {
Expand Down
33 changes: 33 additions & 0 deletions src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,30 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
index += 2;
}

if (fieldValue.$geoWithin && fieldValue.$geoWithin.$centerSphere) {
const centerSphere = fieldValue.$geoWithin.$centerSphere;
if (!(centerSphere instanceof Array) || centerSphere.length < 2) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere should be an array of Parse.GeoPoint and distance');
}
// Get point, convert to geo point if necessary and validate
let point = centerSphere[0];
if (point instanceof Array && point.length === 2) {
point = new Parse.GeoPoint(point[1], point[0]);
} else if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere geo point invalid');
}
Parse.GeoPoint._validate(point.latitude, point.longitude);
// Get distance and validate
const distance = centerSphere[1];
if(isNaN(distance) || distance < 0) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value; $centerSphere distance invalid');
}
const distanceInKM = distance * 6371 * 1000;
patterns.push(`ST_distance_sphere($${index}:name::geometry, POINT($${index + 1}, $${index + 2})::geometry) <= $${index + 3}`);
values.push(fieldName, point.longitude, point.latitude, distanceInKM);
index += 4;
}

if (fieldValue.$geoWithin && fieldValue.$geoWithin.$polygon) {
const polygon = fieldValue.$geoWithin.$polygon;
let points;
Expand Down Expand Up @@ -1986,4 +2010,13 @@ function literalizeRegexPart(s: string) {
);
}

var GeoPointCoder = {
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'GeoPoint'
);
}
};

export default PostgresStorageAdapter;