Skip to content

Add Polygon Type To Schema / PolygonContain to Query #3944

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 12, 2017
16 changes: 14 additions & 2 deletions spec/MongoTransform.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
});

it('geopoint', (done) => {
var input = {location: [180, -180]};
var input = {location: [45, -45]};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'GeoPoint' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'GeoPoint', longitude: 180, latitude: -180}
{__type: 'GeoPoint', longitude: 45, latitude: -45}
);
done();
});

it('polygon', (done) => {
var input = {location: { type: 'Polygon', coordinates: [[[45, -45],[45, -45]]]}};
var output = transform.mongoObjectToParseObject(null, input, {
fields: { location: { type: 'Polygon' }},
});
expect(typeof output.location).toEqual('object');
expect(output.location).toEqual(
{__type: 'Polygon', coordinates: [[45, -45],[45, -45]]}
);
done();
});
Expand Down
2 changes: 1 addition & 1 deletion spec/ParseObject.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ describe('Parse.Object testing', () => {

it("invalid __type", function(done) {
var item = new Parse.Object("Item");
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes'];
var types = ['Pointer', 'File', 'Date', 'GeoPoint', 'Bytes', 'Polygon'];
var tests = types.map(type => {
var test = new Parse.Object("Item");
test.set('foo', {
Expand Down
262 changes: 262 additions & 0 deletions spec/ParsePolygon.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
const TestObject = Parse.Object.extend('TestObject');
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
const rp = require('request-promise');
const defaultHeaders = {
'X-Parse-Application-Id': 'test',
'X-Parse-Rest-API-Key': 'rest'
}

describe('Parse.Polygon testing', () => {
it('polygon save open path', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0]];
const closed = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, closed);
done();
}, done.fail);
});

it('polygon save closed path', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, coords);
done();
}, done.fail);
});

it('polygon equalTo (open/closed) path', (done) => {
const openPoints = [[0,0],[0,1],[1,1],[1,0]];
const closedPoints = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const openPolygon = {__type: 'Polygon', coordinates: openPoints};
const closedPolygon = {__type: 'Polygon', coordinates: closedPoints};
const obj = new TestObject();
obj.set('polygon', openPolygon);
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
query.equalTo('polygon', openPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, closedPoints);
const query = new Parse.Query(TestObject);
query.equalTo('polygon', closedPolygon);
return query.find();
}).then((results) => {
const polygon = results[0].get('polygon');
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, closedPoints);
done();
}, done.fail);
});

it('polygon update', (done) => {
const oldCoords = [[0,0],[0,1],[1,1],[1,0]];
const oldPolygon = {__type: 'Polygon', coordinates: oldCoords};
const newCoords = [[2,2],[2,3],[3,3],[3,2]];
const newPolygon = {__type: 'Polygon', coordinates: newCoords};
const obj = new TestObject();
obj.set('polygon', oldPolygon);
return obj.save().then(() => {
obj.set('polygon', newPolygon);
return obj.save();
}).then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
newCoords.push(newCoords[0]);
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, newCoords);
done();
}, done.fail);
});

it('polygon invalid value', (done) => {
const coords = [['foo','bar'],[0,1],[1,0],[1,1],[0,0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
return obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then(done.fail, done);
});

it('polygon three points minimum', (done) => {
const coords = [[0,0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(done.fail, done);
});

it('polygon three different points minimum', (done) => {
const coords = [[0,0],[0,1],[0,0]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(done.fail, done);
});

it('polygon counterclockwise', (done) => {
const coords = [[1,1],[0,1],[0,0],[1,0]];
const closed = [[1,1],[0,1],[0,0],[1,0],[1,1]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(() => {
const query = new Parse.Query(TestObject);
return query.get(obj.id);
}).then((result) => {
const polygon = result.get('polygon');
equal(polygon.__type, 'Polygon');
equal(polygon.coordinates, closed);
done();
}, done.fail);
});

it('polygonContain query', (done) => {
const points1 = [[0,0],[0,1],[1,1],[1,0]];
const points2 = [[0,0],[0,2],[2,2],[2,0]];
const points3 = [[10,10],[10,15],[15,15],[15,10],[10,10]];
const polygon1 = {__type: 'Polygon', coordinates: points1};
const polygon2 = {__type: 'Polygon', coordinates: points2};
const polygon3 = {__type: 'Polygon', coordinates: points3};
const obj1 = new TestObject({location: polygon1});
const obj2 = new TestObject({location: polygon2});
const obj3 = new TestObject({location: polygon3});
Parse.Object.saveAll([obj1, obj2, obj3]).then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 0.5, longitude: 0.5 }
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
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('polygonContain invalid input', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = {__type: 'Polygon', coordinates: points};
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: { __type: 'GeoPoint', latitude: 181, longitude: 181 }
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then(done.fail, done);
});

it('polygonContain invalid geoPoint', (done) => {
const points = [[0,0],[0,1],[1,1],[1,0]];
const polygon = {__type: 'Polygon', coordinates: points};
const obj = new TestObject({location: polygon});
obj.save().then(() => {
const where = {
location: {
$geoIntersects: {
$point: []
}
}
};
return rp.post({
url: Parse.serverURL + '/classes/TestObject',
json: { where, '_method': 'GET' },
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Javascript-Key': Parse.javaScriptKey
}
});
}).then(done.fail, done);
});
});

describe_only_db('mongo')('Parse.Polygon testing', () => {
it('support 2d and 2dsphere', (done) => {
const coords = [[0,0],[0,1],[1,1],[1,0],[0,0]];
const polygon = {__type: 'Polygon', coordinates: coords};
const location = {__type: 'GeoPoint', latitude:10, longitude:10};
const databaseAdapter = new MongoStorageAdapter({ uri: mongoURI });
return reconfigureServer({
appId: 'test',
restAPIKey: 'rest',
publicServerURL: 'http://localhost:8378/1',
databaseAdapter
}).then(() => {
return databaseAdapter.createIndex('TestObject', {location: '2d'});
}).then(() => {
return databaseAdapter.createIndex('TestObject', {polygon: '2dsphere'});
}).then(() => {
return rp.post({
url: 'http://localhost:8378/1/classes/TestObject',
json: {
'_method': 'POST',
location,
polygon,
polygon2: polygon
},
headers: defaultHeaders
});
}).then((resp) => {
return rp.post({
url: `http://localhost:8378/1/classes/TestObject/${resp.objectId}`,
json: {'_method': 'GET'},
headers: defaultHeaders
});
}).then((resp) => {
equal(resp.location, location);
equal(resp.polygon, polygon);
equal(resp.polygon2, polygon);
return databaseAdapter.getIndexes('TestObject');
}).then((indexes) => {
equal(indexes.length, 4);
equal(indexes[0].key, {_id: 1});
equal(indexes[1].key, {location: '2d'});
equal(indexes[2].key, {polygon: '2dsphere'});
equal(indexes[3].key, {polygon2: '2dsphere'});
done();
}, done.fail);
});

it('polygon loop is not valid', (done) => {
const coords = [[0,0],[0,1],[1,0],[1,1]];
const obj = new TestObject();
obj.set('polygon', {__type: 'Polygon', coordinates: coords});
obj.save().then(done.fail, done);
});
});
2 changes: 2 additions & 0 deletions spec/Schema.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ describe('SchemaController', () => {
aPointer: {type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet'},
aRelation: {type: 'Relation', targetClass: 'NewClass'},
aBytes: {type: 'Bytes'},
aPolygon: {type: 'Polygon'},
}))
.then(actualSchema => {
const expectedSchema = {
Expand All @@ -544,6 +545,7 @@ describe('SchemaController', () => {
aPointer: { type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet' },
aRelation: { type: 'Relation', targetClass: 'NewClass' },
aBytes: {type: 'Bytes'},
aPolygon: {type: 'Polygon'},
},
classLevelPermissions: {
find: { '*': true },
Expand Down
2 changes: 2 additions & 0 deletions src/Adapters/Storage/Mongo/MongoSchemaCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function mongoFieldToParseSchemaField(type) {
case 'geopoint': return {type: 'GeoPoint'};
case 'file': return {type: 'File'};
case 'bytes': return {type: 'Bytes'};
case 'polygon': return {type: 'Polygon'};
}
}

Expand Down Expand Up @@ -98,6 +99,7 @@ function parseFieldTypeToMongoFieldType({ type, targetClass }) {
case 'GeoPoint': return 'geopoint';
case 'File': return 'file';
case 'Bytes': return 'bytes';
case 'Polygon': return 'polygon';
}
}

Expand Down
18 changes: 17 additions & 1 deletion src/Adapters/Storage/Mongo/MongoStorageAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ export class MongoStorageAdapter {

addFieldIfNotExists(className, fieldName, type) {
return this._schemaCollection()
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type));
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type))
.then(() => this.createIndexesIfNeeded(className, fieldName, type));
}

// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
Expand Down Expand Up @@ -429,6 +430,21 @@ export class MongoStorageAdapter {
return this._adaptiveCollection(className)
.then(collection => collection._mongoCollection.createIndex(index));
}

createIndexesIfNeeded(className, fieldName, type) {
if (type && type.type === 'Polygon') {
const index = {
[fieldName]: '2dsphere'
};
return this.createIndex(className, index);
}
return Promise.resolve();
}

getIndexes(className) {
return this._adaptiveCollection(className)
.then(collection => collection._mongoCollection.indexes());
}
}

export default MongoStorageAdapter;
Expand Down
Loading