add support for geoWithin.centerSphere queries via withJSON (#4825)

* add support for geoWithin.centerSphere queries via withJSON

* added test for passing array of lat, lng instead of Parse.GeoPoint

* added postgres support

* added more tests

* improved tests and validation

* added more tests
This commit is contained in:
Manuel
2018-06-12 18:41:02 +02:00
committed by Florent Vilmart
parent f2f92858f1
commit cddb924703
4 changed files with 203 additions and 40 deletions

View File

@@ -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));
});
}); });

View File

@@ -294,11 +294,13 @@ function expectError(errorCode, callback) {
error: function(obj, e) { error: function(obj, e) {
// Some methods provide 2 parameters. // Some methods provide 2 parameters.
e = e || obj; e = e || obj;
if (errorCode !== undefined) {
if (!e) { if (!e) {
fail('expected a specific error but got a blank error'); fail('expected a specific error but got a blank error');
return; return;
} }
expect(e.code).toEqual(errorCode, e.message); expect(e.code).toEqual(errorCode, e.message);
}
if (callback) { if (callback) {
callback(e); callback(e);
} }

View File

@@ -905,6 +905,8 @@ function transformConstraint(constraint, field) {
case '$geoWithin': { case '$geoWithin': {
const polygon = constraint[key]['$polygon']; const polygon = constraint[key]['$polygon'];
const centerSphere = constraint[key]['$centerSphere'];
if (polygon !== undefined) {
let points; let points;
if (typeof polygon === 'object' && polygon.__type === 'Polygon') { if (typeof polygon === 'object' && polygon.__type === 'Polygon') {
if (!polygon.coordinates || polygon.coordinates.length < 3) { if (!polygon.coordinates || polygon.coordinates.length < 3) {
@@ -943,6 +945,30 @@ function transformConstraint(constraint, field) {
answer[key] = { answer[key] = {
'$polygon': points '$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');
}
// 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');
}
answer[key] = {
'$centerSphere': [
[point.longitude, point.latitude],
distance
]
};
}
break; break;
} }
case '$geoIntersects': { case '$geoIntersects': {

View File

@@ -535,6 +535,30 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
index += 2; 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) { if (fieldValue.$geoWithin && fieldValue.$geoWithin.$polygon) {
const polygon = fieldValue.$geoWithin.$polygon; const polygon = fieldValue.$geoWithin.$polygon;
let points; let points;
@@ -1986,4 +2010,13 @@ function literalizeRegexPart(s: string) {
); );
} }
var GeoPointCoder = {
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'GeoPoint'
);
}
};
export default PostgresStorageAdapter; export default PostgresStorageAdapter;