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:
@@ -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));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user