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 260c466dcb
commit 1e29d0299b
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) {
// 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);
}

View File

@@ -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) {
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 should be Polygon object or Array of Parse.GeoPoint\'s'
);
}
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');
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 {
Parse.GeoPoint._validate(point.latitude, point.longitude);
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint\'s'
);
}
return [point.longitude, point.latitude];
});
answer[key] = {
'$polygon': points
};
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');
}
// 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;
}
case '$geoIntersects': {

View File

@@ -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;
@@ -1986,4 +2010,13 @@ function literalizeRegexPart(s: string) {
);
}
var GeoPointCoder = {
isValidJSON(value) {
return (typeof value === 'object' &&
value !== null &&
value.__type === 'GeoPoint'
);
}
};
export default PostgresStorageAdapter;