Skip to content

Commit c99fdea

Browse files
dplewisflovilmart
authored andcommitted
feat(ParseQuery): Added 'withinPolygon' support for GeoPoints (#3866)
* Added 'withinPolygon' to query * Unit test for withinPolygon * More Unit Test * withinPolygon fix for Postgres * Fix nit nit?
1 parent a380fcf commit c99fdea

File tree

3 files changed

+192
-0
lines changed

3 files changed

+192
-0
lines changed

spec/ParseGeoPoint.spec.js

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This is a port of the test suite:
22
// hungry/js/test/parse_geo_point_test.js
33

4+
const rp = require('request-promise');
45
var TestObject = Parse.Object.extend('TestObject');
56

67
describe('Parse.GeoPoint testing', () => {
@@ -374,4 +375,158 @@ describe('Parse.GeoPoint testing', () => {
374375
}
375376
});
376377
});
378+
379+
it('supports withinPolygon', (done) => {
380+
const point1 = new Parse.GeoPoint(1.5, 1.5);
381+
const point2 = new Parse.GeoPoint(2, 8);
382+
const point3 = new Parse.GeoPoint(20, 20);
383+
const obj1 = new Parse.Object('Polygon', {location: point1});
384+
const obj2 = new Parse.Object('Polygon', {location: point2});
385+
const obj3 = new Parse.Object('Polygon', {location: point3});
386+
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
387+
const where = {
388+
location: {
389+
$geoWithin: {
390+
$polygon: [
391+
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
392+
{ __type: 'GeoPoint', latitude: 0, longitude: 10 },
393+
{ __type: 'GeoPoint', latitude: 10, longitude: 10 },
394+
{ __type: 'GeoPoint', latitude: 10, longitude: 0 }
395+
]
396+
}
397+
}
398+
};
399+
return rp.post({
400+
url: Parse.serverURL + '/classes/Polygon',
401+
json: { where, '_method': 'GET' },
402+
headers: {
403+
'X-Parse-Application-Id': Parse.applicationId,
404+
'X-Parse-Javascript-Key': Parse.javaScriptKey
405+
}
406+
});
407+
}).then((resp) => {
408+
expect(resp.results.length).toBe(2);
409+
done();
410+
}, done.fail);
411+
});
412+
413+
it('invalid input withinPolygon', (done) => {
414+
const point = new Parse.GeoPoint(1.5, 1.5);
415+
const obj = new Parse.Object('Polygon', {location: point});
416+
obj.save().then(() => {
417+
const where = {
418+
location: {
419+
$geoWithin: {
420+
$polygon: 1234
421+
}
422+
}
423+
};
424+
return rp.post({
425+
url: Parse.serverURL + '/classes/Polygon',
426+
json: { where, '_method': 'GET' },
427+
headers: {
428+
'X-Parse-Application-Id': Parse.applicationId,
429+
'X-Parse-Javascript-Key': Parse.javaScriptKey
430+
}
431+
});
432+
}).then((resp) => {
433+
fail(`no request should succeed: ${JSON.stringify(resp)}`);
434+
done();
435+
}).catch((err) => {
436+
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
437+
done();
438+
});
439+
});
440+
441+
it('invalid geoPoint withinPolygon', (done) => {
442+
const point = new Parse.GeoPoint(1.5, 1.5);
443+
const obj = new Parse.Object('Polygon', {location: point});
444+
obj.save().then(() => {
445+
const where = {
446+
location: {
447+
$geoWithin: {
448+
$polygon: [
449+
{}
450+
]
451+
}
452+
}
453+
};
454+
return rp.post({
455+
url: Parse.serverURL + '/classes/Polygon',
456+
json: { where, '_method': 'GET' },
457+
headers: {
458+
'X-Parse-Application-Id': Parse.applicationId,
459+
'X-Parse-Javascript-Key': Parse.javaScriptKey
460+
}
461+
});
462+
}).then((resp) => {
463+
fail(`no request should succeed: ${JSON.stringify(resp)}`);
464+
done();
465+
}).catch((err) => {
466+
expect(err.error.code).toEqual(Parse.Error.INVALID_JSON);
467+
done();
468+
});
469+
});
470+
471+
it('invalid latitude withinPolygon', (done) => {
472+
const point = new Parse.GeoPoint(1.5, 1.5);
473+
const obj = new Parse.Object('Polygon', {location: point});
474+
obj.save().then(() => {
475+
const where = {
476+
location: {
477+
$geoWithin: {
478+
$polygon: [
479+
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
480+
{ __type: 'GeoPoint', latitude: 181, longitude: 0 }
481+
]
482+
}
483+
}
484+
};
485+
return rp.post({
486+
url: Parse.serverURL + '/classes/Polygon',
487+
json: { where, '_method': 'GET' },
488+
headers: {
489+
'X-Parse-Application-Id': Parse.applicationId,
490+
'X-Parse-Javascript-Key': Parse.javaScriptKey
491+
}
492+
});
493+
}).then((resp) => {
494+
fail(`no request should succeed: ${JSON.stringify(resp)}`);
495+
done();
496+
}).catch((err) => {
497+
expect(err.error.code).toEqual(1);
498+
done();
499+
});
500+
});
501+
502+
it('invalid longitude withinPolygon', (done) => {
503+
const point = new Parse.GeoPoint(1.5, 1.5);
504+
const obj = new Parse.Object('Polygon', {location: point});
505+
obj.save().then(() => {
506+
const where = {
507+
location: {
508+
$geoWithin: {
509+
$polygon: [
510+
{ __type: 'GeoPoint', latitude: 0, longitude: 0 },
511+
{ __type: 'GeoPoint', latitude: 0, longitude: 181 }
512+
]
513+
}
514+
}
515+
};
516+
return rp.post({
517+
url: Parse.serverURL + '/classes/Polygon',
518+
json: { where, '_method': 'GET' },
519+
headers: {
520+
'X-Parse-Application-Id': Parse.applicationId,
521+
'X-Parse-Javascript-Key': Parse.javaScriptKey
522+
}
523+
});
524+
}).then((resp) => {
525+
fail(`no request should succeed: ${JSON.stringify(resp)}`);
526+
done();
527+
}).catch((err) => {
528+
expect(err.error.code).toEqual(1);
529+
done();
530+
});
531+
});
377532
});

src/Adapters/Storage/Mongo/MongoTransform.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,24 @@ function transformConstraint(constraint, inArray) {
618618
};
619619
break;
620620

621+
case '$geoWithin': {
622+
const polygon = constraint[key]['$polygon'];
623+
if (!(polygon instanceof Array)) {
624+
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
625+
}
626+
const points = polygon.map((point) => {
627+
if (!GeoPointCoder.isValidJSON(point)) {
628+
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
629+
} else {
630+
Parse.GeoPoint._validate(point.latitude, point.longitude);
631+
}
632+
return [point.longitude, point.latitude];
633+
});
634+
answer[key] = {
635+
'$polygon': points
636+
};
637+
break;
638+
}
621639
default:
622640
if (key.match(/^\$+/)) {
623641
throw new Parse.Error(

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,25 @@ const buildWhereClause = ({ schema, query, index }) => {
344344
index += 2;
345345
}
346346

347+
if (fieldValue.$geoWithin && fieldValue.$geoWithin.$polygon) {
348+
const polygon = fieldValue.$geoWithin.$polygon;
349+
if (!(polygon instanceof Array)) {
350+
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
351+
}
352+
const points = polygon.map((point) => {
353+
if (typeof point !== 'object' || point.__type !== 'GeoPoint') {
354+
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
355+
} else {
356+
Parse.GeoPoint._validate(point.latitude, point.longitude);
357+
}
358+
return `(${point.longitude}, ${point.latitude})`;
359+
}).join(', ');
360+
361+
patterns.push(`$${index}:name::point <@ $${index + 1}::polygon`);
362+
values.push(fieldName, `(${points})`);
363+
index += 2;
364+
}
365+
347366
if (fieldValue.$regex) {
348367
let regex = fieldValue.$regex;
349368
let operator = '~';

0 commit comments

Comments
 (0)