Skip to content

Commit

Permalink
feat(ParseQuery): Added 'withinPolygon' support for GeoPoints (#3866)
Browse files Browse the repository at this point in the history
* Added 'withinPolygon' to query

* Unit test for withinPolygon

* More Unit Test

* withinPolygon fix for Postgres

* Fix nit

nit?
  • Loading branch information
dplewis authored and flovilmart committed May 28, 2017
1 parent a380fcf commit c99fdea
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 0 deletions.
155 changes: 155 additions & 0 deletions spec/ParseGeoPoint.spec.js
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -374,4 +375,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();
});
});
});
18 changes: 18 additions & 0 deletions src/Adapters/Storage/Mongo/MongoTransform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
19 changes: 19 additions & 0 deletions src/Adapters/Storage/Postgres/PostgresStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '~';
Expand Down

0 comments on commit c99fdea

Please sign in to comment.