From 57da2def1d37997e578ab16aaa9258a05b28b519 Mon Sep 17 00:00:00 2001 From: Mads Bjerre Date: Tue, 22 May 2018 18:06:43 +0200 Subject: [PATCH] Adds withinPolygon support for Polygon object (#4067) * Whitespace * Add Polygon type to $polygon query * Add tests Polygon object in $polygon query $geoIntersects queries * Refactor * Postgres support * More tests * Remove duplicate test * Missing semicolon * fix tests --- spec/ParseGeoPoint.spec.js | 110 ++++++++++++++++++ src/Adapters/Storage/Mongo/MongoTransform.js | 33 ++++-- .../Postgres/PostgresStorageAdapter.js | 33 ++++-- 3 files changed, 158 insertions(+), 18 deletions(-) diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index 66915c7f..ec6ec792 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -496,6 +496,116 @@ describe('Parse.GeoPoint testing', () => { }, done.fail); }); + it('supports withinPolygon Polygon object', (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('Polygon', {location: inbound}); + const obj2 = new Parse.Object('Polygon', {location: onbound}); + const obj3 = new Parse.Object('Polygon', {location: outbound}); + const polygon = { + __type: 'Polygon', + coordinates: [ + [0, 0], + [10, 0], + [10, 10], + [0, 10], + [0, 0] + ] + } + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const where = { + location: { + $geoWithin: { + $polygon: polygon + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/Polygon', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then((resp) => { + expect(resp.results.length).toBe(2); + done(); + }, done.fail); + }); + + it('invalid Polygon object withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + const polygon = { + __type: 'Polygon', + coordinates: [ + [0, 0], + [10, 0], + ] + } + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $polygon: polygon + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/Polygon', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then((resp) => { + fail(`no request should succeed: ${JSON.stringify(resp)}`); + done(); + }).catch((err) => { + expect(err.error.code).toEqual(Parse.Error.INVALID_JSON); + done(); + }); + }); + + it('out of bounds Polygon object withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + const polygon = { + __type: 'Polygon', + coordinates: [ + [0, 0], + [181, 0], + [0, 10] + ] + } + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $polygon: polygon + } + } + }; + return rp.post({ + url: Parse.serverURL + '/classes/Polygon', + json: { where, '_method': 'GET' }, + headers: { + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Javascript-Key': Parse.javaScriptKey + } + }); + }).then((resp) => { + fail(`no request should succeed: ${JSON.stringify(resp)}`); + done(); + }).catch((err) => { + expect(err.error.code).toEqual(1); + done(); + }); + }); + it('invalid input withinPolygon', (done) => { const point = new Parse.GeoPoint(1.5, 1.5); const obj = new Parse.Object('Polygon', {location: point}); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index c60db85d..78578865 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -905,19 +905,34 @@ function transformConstraint(constraint, field) { case '$geoWithin': { const polygon = constraint[key]['$polygon']; - if (!(polygon instanceof Array)) { + 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 contain at least 3 GeoPoints' + 'bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint\'s' ); } - if (polygon.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, - 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' - ); - } - const points = polygon.map((point) => { + 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 { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index cd8759b9..540516d3 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -537,19 +537,34 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => { if (fieldValue.$geoWithin && fieldValue.$geoWithin.$polygon) { const polygon = fieldValue.$geoWithin.$polygon; - if (!(polygon instanceof Array)) { + 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 contain at least 3 GeoPoints' + 'bad $geoWithin value; $polygon should be Polygon object or Array of Parse.GeoPoint\'s' ); } - if (polygon.length < 3) { - throw new Parse.Error( - Parse.Error.INVALID_JSON, - 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' - ); - } - const points = polygon.map((point) => { + points = points.map((point) => { + if (point instanceof Array && point.length === 2) { + Parse.GeoPoint._validate(point[1], point[0]); + return `(${point[0]}, ${point[1]})`; + } if (typeof point !== 'object' || point.__type !== 'GeoPoint') { throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); } else {