diff --git a/spec/ParseGeoPoint.spec.js b/spec/ParseGeoPoint.spec.js index 109939c5..f2f8201e 100644 --- a/spec/ParseGeoPoint.spec.js +++ b/spec/ParseGeoPoint.spec.js @@ -5,6 +5,7 @@ const rp = require('request-promise'); var TestObject = Parse.Object.extend('TestObject'); describe('Parse.GeoPoint testing', () => { + it('geo point roundtrip', (done) => { var point = new Parse.GeoPoint(44.0, -11.0); var obj = new TestObject(); @@ -331,18 +332,20 @@ describe('Parse.GeoPoint testing', () => { }); it('works with geobox queries', (done) => { - var inSF = new Parse.GeoPoint(37.75, -122.4); - var southwestOfSF = new Parse.GeoPoint(37.708813, -122.526398); - var northeastOfSF = new Parse.GeoPoint(37.822802, -122.373962); - - var object = new TestObject(); - object.set('point', inSF); - object.save().then(() => { - var query = new Parse.Query(TestObject); - query.withinGeoBox('point', southwestOfSF, northeastOfSF); + 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}); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const sw = new Parse.GeoPoint(0, 0); + const ne = new Parse.GeoPoint(10, 10); + const query = new Parse.Query(TestObject); + query.withinGeoBox('location', sw, ne); return query.find(); }).then((results) => { - equal(results.length, 1); + equal(results.length, 2); done(); }); }); @@ -407,13 +410,13 @@ 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}); + it('supports withinPolygon open path', (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}); Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { const where = { location: { @@ -441,6 +444,41 @@ describe('Parse.GeoPoint testing', () => { }, done.fail); }); + it('supports withinPolygon closed path', (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}); + 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 }, + { __type: 'GeoPoint', latitude: 0, 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}); @@ -508,7 +546,8 @@ describe('Parse.GeoPoint testing', () => { $geoWithin: { $polygon: [ { __type: 'GeoPoint', latitude: 0, longitude: 0 }, - { __type: 'GeoPoint', latitude: 181, longitude: 0 } + { __type: 'GeoPoint', latitude: 181, longitude: 0 }, + { __type: 'GeoPoint', latitude: 0, longitude: 0 } ] } } @@ -539,7 +578,8 @@ describe('Parse.GeoPoint testing', () => { $geoWithin: { $polygon: [ { __type: 'GeoPoint', latitude: 0, longitude: 0 }, - { __type: 'GeoPoint', latitude: 0, longitude: 181 } + { __type: 'GeoPoint', latitude: 0, longitude: 181 }, + { __type: 'GeoPoint', latitude: 0, longitude: 0 } ] } } @@ -560,4 +600,32 @@ describe('Parse.GeoPoint testing', () => { done(); }); }); + + it('minimum 3 points 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(107); + done(); + }); + }); }); diff --git a/src/Adapters/Storage/Mongo/MongoTransform.js b/src/Adapters/Storage/Mongo/MongoTransform.js index c6e5dfff..a9bdda3d 100644 --- a/src/Adapters/Storage/Mongo/MongoTransform.js +++ b/src/Adapters/Storage/Mongo/MongoTransform.js @@ -621,7 +621,16 @@ function transformConstraint(constraint, inArray) { case '$geoWithin': { const polygon = constraint[key]['$polygon']; if (!(polygon instanceof Array)) { - throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value'); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' + ); + } + 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) => { if (!GeoPointCoder.isValidJSON(point)) { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 29ee67c0..cd82ade4 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -348,7 +348,16 @@ const buildWhereClause = ({ schema, query, index }) => { 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'); + throw new Parse.Error( + Parse.Error.INVALID_JSON, + 'bad $geoWithin value; $polygon should contain at least 3 GeoPoints' + ); + } + 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) => { if (typeof point !== 'object' || point.__type !== 'GeoPoint') {