diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index 1dd7710ede..56ed081aaa 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -1,6 +1,7 @@ // This is a port of the test suite: // hungry/js/test/parse_geo_point_test.js +const rp = require('request-promise'); var TestObject = Parse.Object.extend('TestObject'); describe('Parse.GeoPoint testing', () => { @@ -355,4 +356,158 @@ describe('Parse.GeoPoint testing', () => { } }); }); + + it('supports withinPolygon', (done) => { + const point1 = new Parse.GeoPoint(1.5, 1.5); + const point2 = new Parse.GeoPoint(2, 8); + const point3 = new Parse.GeoPoint(20, 20); + const obj1 = new Parse.Object('Polygon', {location: point1}); + const obj2 = new Parse.Object('Polygon', {location: point2}); + const obj3 = new Parse.Object('Polygon', {location: point3}); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const where = { + location: { + $geoWithin: { + $polygon: [ + { __type: 'GeoPoint', latitude: 0, longitude: 0 }, + { __type: 'GeoPoint', latitude: 0, longitude: 10 }, + { __type: 'GeoPoint', latitude: 10, longitude: 10 }, + { __type: 'GeoPoint', latitude: 10, longitude: 0 } + ] + } + } + }; + 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 input withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $polygon: 1234 + } + } + }; + 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('invalid geoPoint withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $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('invalid latitude withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $polygon: [ + { __type: 'GeoPoint', latitude: 0, longitude: 0 }, + { __type: 'GeoPoint', latitude: 181, longitude: 0 } + ] + } + } + }; + 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 longitude withinPolygon', (done) => { + const point = new Parse.GeoPoint(1.5, 1.5); + const obj = new Parse.Object('Polygon', {location: point}); + obj.save().then(() => { + const where = { + location: { + $geoWithin: { + $polygon: [ + { __type: 'GeoPoint', latitude: 0, longitude: 0 }, + { __type: 'GeoPoint', latitude: 0, longitude: 181 } + ] + } + } + }; + 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(); + }); + }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index 39f313240c..c6e5dfff58 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -618,6 +618,24 @@ function transformConstraint(constraint, inArray) { }; break; + case '$geoWithin': { + const polygon = constraint[key]['$polygon']; + if (!(polygon instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + } + const points = polygon.map((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 + }; + break; + } default: if (key.match(/^\$+/)) { throw new Parse.Error( diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 1d0ff7397f..7e847c08e7 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -344,6 +344,25 @@ const buildWhereClause = ({ schema, query, index }) => { index += 2; } + if (fieldValue.$geoWithin && fieldValue.$geoWithin.$polygon) { + const polygon = fieldValue.$geoWithin.$polygon; + if (!(polygon instanceof Array)) { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + } + const points = polygon.map((point) => { + if (typeof point !== 'object' || point.__type !== 'GeoPoint') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + } else { + Parse.GeoPoint._validate(point.latitude, point.longitude); + } + return `(${point.longitude}, ${point.latitude})`; + }).join(', '); + + patterns.push(`$${index}:name::point <@ $${index + 1}::polygon`); + values.push(fieldName, `(${points})`); + index += 2; + } + if (fieldValue.$regex) { let regex = fieldValue.$regex; let operator = '~';