Add Polygon Type To Schema / PolygonContain to Query (#3944)
* Added type polygon to schema * refactoring and more tests * fix tests * update test and transform * add support for polygonContains * fix transform.mongoObjectToParseObject test * add indexes for polygon * index test * postgres test fix * remove invalid loop test * add invalid loop test * nit
This commit is contained in:
committed by
Florent Vilmart
parent
0571c6f95e
commit
e6cc8204b3
@@ -145,13 +145,25 @@ describe('parseObjectToMongoObjectForCreate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('geopoint', (done) => {
|
it('geopoint', (done) => {
|
||||||
var input = {location: [180, -180]};
|
var input = {location: [45, -45]};
|
||||||
var output = transform.mongoObjectToParseObject(null, input, {
|
var output = transform.mongoObjectToParseObject(null, input, {
|
||||||
fields: { location: { type: 'GeoPoint' }},
|
fields: { location: { type: 'GeoPoint' }},
|
||||||
});
|
});
|
||||||
expect(typeof output.location).toEqual('object');
|
expect(typeof output.location).toEqual('object');
|
||||||
expect(output.location).toEqual(
|
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();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ describe('Parse.Object testing', () => {
|
|||||||
|
|
||||||
it("invalid __type", function(done) {
|
it("invalid __type", function(done) {
|
||||||
var item = new Parse.Object("Item");
|
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 tests = types.map(type => {
|
||||||
var test = new Parse.Object("Item");
|
var test = new Parse.Object("Item");
|
||||||
test.set('foo', {
|
test.set('foo', {
|
||||||
|
|||||||
262
spec/ParsePolygon.spec.js
Normal file
262
spec/ParsePolygon.spec.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -524,6 +524,7 @@ describe('SchemaController', () => {
|
|||||||
aPointer: {type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet'},
|
aPointer: {type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet'},
|
||||||
aRelation: {type: 'Relation', targetClass: 'NewClass'},
|
aRelation: {type: 'Relation', targetClass: 'NewClass'},
|
||||||
aBytes: {type: 'Bytes'},
|
aBytes: {type: 'Bytes'},
|
||||||
|
aPolygon: {type: 'Polygon'},
|
||||||
}))
|
}))
|
||||||
.then(actualSchema => {
|
.then(actualSchema => {
|
||||||
const expectedSchema = {
|
const expectedSchema = {
|
||||||
@@ -544,6 +545,7 @@ describe('SchemaController', () => {
|
|||||||
aPointer: { type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet' },
|
aPointer: { type: 'Pointer', targetClass: 'ThisClassDoesNotExistYet' },
|
||||||
aRelation: { type: 'Relation', targetClass: 'NewClass' },
|
aRelation: { type: 'Relation', targetClass: 'NewClass' },
|
||||||
aBytes: {type: 'Bytes'},
|
aBytes: {type: 'Bytes'},
|
||||||
|
aPolygon: {type: 'Polygon'},
|
||||||
},
|
},
|
||||||
classLevelPermissions: {
|
classLevelPermissions: {
|
||||||
find: { '*': true },
|
find: { '*': true },
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ function mongoFieldToParseSchemaField(type) {
|
|||||||
case 'geopoint': return {type: 'GeoPoint'};
|
case 'geopoint': return {type: 'GeoPoint'};
|
||||||
case 'file': return {type: 'File'};
|
case 'file': return {type: 'File'};
|
||||||
case 'bytes': return {type: 'Bytes'};
|
case 'bytes': return {type: 'Bytes'};
|
||||||
|
case 'polygon': return {type: 'Polygon'};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ function parseFieldTypeToMongoFieldType({ type, targetClass }) {
|
|||||||
case 'GeoPoint': return 'geopoint';
|
case 'GeoPoint': return 'geopoint';
|
||||||
case 'File': return 'file';
|
case 'File': return 'file';
|
||||||
case 'Bytes': return 'bytes';
|
case 'Bytes': return 'bytes';
|
||||||
|
case 'Polygon': return 'polygon';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,8 @@ export class MongoStorageAdapter {
|
|||||||
|
|
||||||
addFieldIfNotExists(className, fieldName, type) {
|
addFieldIfNotExists(className, fieldName, type) {
|
||||||
return this._schemaCollection()
|
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.)
|
// Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
|
||||||
@@ -429,6 +430,21 @@ export class MongoStorageAdapter {
|
|||||||
return this._adaptiveCollection(className)
|
return this._adaptiveCollection(className)
|
||||||
.then(collection => collection._mongoCollection.createIndex(index));
|
.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;
|
export default MongoStorageAdapter;
|
||||||
|
|||||||
@@ -495,6 +495,9 @@ function transformTopLevelAtom(atom) {
|
|||||||
if (GeoPointCoder.isValidJSON(atom)) {
|
if (GeoPointCoder.isValidJSON(atom)) {
|
||||||
return GeoPointCoder.JSONToDatabase(atom);
|
return GeoPointCoder.JSONToDatabase(atom);
|
||||||
}
|
}
|
||||||
|
if (PolygonCoder.isValidJSON(atom)) {
|
||||||
|
return PolygonCoder.JSONToDatabase(atom);
|
||||||
|
}
|
||||||
if (FileCoder.isValidJSON(atom)) {
|
if (FileCoder.isValidJSON(atom)) {
|
||||||
return FileCoder.JSONToDatabase(atom);
|
return FileCoder.JSONToDatabase(atom);
|
||||||
}
|
}
|
||||||
@@ -692,6 +695,24 @@ function transformConstraint(constraint, inArray) {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case '$geoIntersects': {
|
||||||
|
const point = constraint[key]['$point'];
|
||||||
|
if (!GeoPointCoder.isValidJSON(point)) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
'bad $geoIntersect value; $point should be GeoPoint'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Parse.GeoPoint._validate(point.latitude, point.longitude);
|
||||||
|
}
|
||||||
|
answer[key] = {
|
||||||
|
$geometry: {
|
||||||
|
type: 'Point',
|
||||||
|
coordinates: [point.longitude, point.latitude]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if (key.match(/^\$+/)) {
|
if (key.match(/^\$+/)) {
|
||||||
throw new Parse.Error(
|
throw new Parse.Error(
|
||||||
@@ -940,6 +961,10 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
|
|||||||
restObject[key] = GeoPointCoder.databaseToJSON(value);
|
restObject[key] = GeoPointCoder.databaseToJSON(value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (schema.fields[key] && schema.fields[key].type === 'Polygon' && PolygonCoder.isValidDatabaseObject(value)) {
|
||||||
|
restObject[key] = PolygonCoder.databaseToJSON(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (schema.fields[key] && schema.fields[key].type === 'Bytes' && BytesCoder.isValidDatabaseObject(value)) {
|
if (schema.fields[key] && schema.fields[key].type === 'Bytes' && BytesCoder.isValidDatabaseObject(value)) {
|
||||||
restObject[key] = BytesCoder.databaseToJSON(value);
|
restObject[key] = BytesCoder.databaseToJSON(value);
|
||||||
break;
|
break;
|
||||||
@@ -1043,6 +1068,64 @@ var GeoPointCoder = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var PolygonCoder = {
|
||||||
|
databaseToJSON(object) {
|
||||||
|
return {
|
||||||
|
__type: 'Polygon',
|
||||||
|
coordinates: object['coordinates'][0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidDatabaseObject(object) {
|
||||||
|
const coords = object.coordinates[0];
|
||||||
|
if (object.type !== 'Polygon' || !(coords instanceof Array)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < coords.length; i++) {
|
||||||
|
const point = coords[i];
|
||||||
|
if (!GeoPointCoder.isValidDatabaseObject(point)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0]));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
JSONToDatabase(json) {
|
||||||
|
const coords = json.coordinates;
|
||||||
|
if (coords[0][0] !== coords[coords.length - 1][0] ||
|
||||||
|
coords[0][1] !== coords[coords.length - 1][1]) {
|
||||||
|
coords.push(coords[0]);
|
||||||
|
}
|
||||||
|
const unique = coords.filter((item, index, ar) => {
|
||||||
|
let foundIndex = -1;
|
||||||
|
for (let i = 0; i < ar.length; i += 1) {
|
||||||
|
const pt = ar[i];
|
||||||
|
if (pt[0] === item[0] &&
|
||||||
|
pt[1] === item[1]) {
|
||||||
|
foundIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundIndex === index;
|
||||||
|
});
|
||||||
|
if (unique.length < 3) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INTERNAL_SERVER_ERROR,
|
||||||
|
'GeoJSON: Loop must have at least 3 different vertices'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { type: 'Polygon', coordinates: [coords] };
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidJSON(value) {
|
||||||
|
return (typeof value === 'object' &&
|
||||||
|
value !== null &&
|
||||||
|
value.__type === 'Polygon'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var FileCoder = {
|
var FileCoder = {
|
||||||
databaseToJSON(object) {
|
databaseToJSON(object) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const parseTypeToPostgresType = type => {
|
|||||||
case 'Number': return 'double precision';
|
case 'Number': return 'double precision';
|
||||||
case 'GeoPoint': return 'point';
|
case 'GeoPoint': return 'point';
|
||||||
case 'Bytes': return 'jsonb';
|
case 'Bytes': return 'jsonb';
|
||||||
|
case 'Polygon': return 'polygon';
|
||||||
case 'Array':
|
case 'Array':
|
||||||
if (type.contents && type.contents.type === 'String') {
|
if (type.contents && type.contents.type === 'String') {
|
||||||
return 'text[]';
|
return 'text[]';
|
||||||
@@ -435,6 +436,20 @@ const buildWhereClause = ({ schema, query, index }) => {
|
|||||||
values.push(fieldName, `(${points})`);
|
values.push(fieldName, `(${points})`);
|
||||||
index += 2;
|
index += 2;
|
||||||
}
|
}
|
||||||
|
if (fieldValue.$geoIntersects && fieldValue.$geoIntersects.$point) {
|
||||||
|
const point = fieldValue.$geoIntersects.$point;
|
||||||
|
if (typeof point !== 'object' || point.__type !== 'GeoPoint') {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
'bad $geoIntersect value; $point should be GeoPoint'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Parse.GeoPoint._validate(point.latitude, point.longitude);
|
||||||
|
}
|
||||||
|
patterns.push(`$${index}:name::polygon @> $${index + 1}::point`);
|
||||||
|
values.push(fieldName, `(${point.longitude}, ${point.latitude})`);
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
|
||||||
if (fieldValue.$regex) {
|
if (fieldValue.$regex) {
|
||||||
let regex = fieldValue.$regex;
|
let regex = fieldValue.$regex;
|
||||||
@@ -480,6 +495,13 @@ const buildWhereClause = ({ schema, query, index }) => {
|
|||||||
index += 3;
|
index += 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fieldValue.__type === 'Polygon') {
|
||||||
|
const value = convertPolygonToSQL(fieldValue.coordinates);
|
||||||
|
patterns.push(`$${index}:name ~= $${index + 1}::polygon`);
|
||||||
|
values.push(fieldName, value);
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(ParseToPosgresComparator).forEach(cmp => {
|
Object.keys(ParseToPosgresComparator).forEach(cmp => {
|
||||||
if (fieldValue[cmp]) {
|
if (fieldValue[cmp]) {
|
||||||
const pgComparator = ParseToPosgresComparator[cmp];
|
const pgComparator = ParseToPosgresComparator[cmp];
|
||||||
@@ -844,6 +866,11 @@ export class PostgresStorageAdapter {
|
|||||||
case 'File':
|
case 'File':
|
||||||
valuesArray.push(object[fieldName].name);
|
valuesArray.push(object[fieldName].name);
|
||||||
break;
|
break;
|
||||||
|
case 'Polygon': {
|
||||||
|
const value = convertPolygonToSQL(object[fieldName].coordinates);
|
||||||
|
valuesArray.push(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'GeoPoint':
|
case 'GeoPoint':
|
||||||
// pop the point and process later
|
// pop the point and process later
|
||||||
geoPoints[fieldName] = object[fieldName];
|
geoPoints[fieldName] = object[fieldName];
|
||||||
@@ -1024,6 +1051,11 @@ export class PostgresStorageAdapter {
|
|||||||
updatePatterns.push(`$${index}:name = POINT($${index + 1}, $${index + 2})`);
|
updatePatterns.push(`$${index}:name = POINT($${index + 1}, $${index + 2})`);
|
||||||
values.push(fieldName, fieldValue.longitude, fieldValue.latitude);
|
values.push(fieldName, fieldValue.longitude, fieldValue.latitude);
|
||||||
index += 3;
|
index += 3;
|
||||||
|
} else if (fieldValue.__type === 'Polygon') {
|
||||||
|
const value = convertPolygonToSQL(fieldValue.coordinates);
|
||||||
|
updatePatterns.push(`$${index}:name = $${index + 1}::polygon`);
|
||||||
|
values.push(fieldName, value);
|
||||||
|
index += 2;
|
||||||
} else if (fieldValue.__type === 'Relation') {
|
} else if (fieldValue.__type === 'Relation') {
|
||||||
// noop
|
// noop
|
||||||
} else if (typeof fieldValue === 'number') {
|
} else if (typeof fieldValue === 'number') {
|
||||||
@@ -1186,6 +1218,20 @@ export class PostgresStorageAdapter {
|
|||||||
longitude: object[fieldName].x
|
longitude: object[fieldName].x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') {
|
||||||
|
let coords = object[fieldName];
|
||||||
|
coords = coords.substr(2, coords.length - 4).split('),(');
|
||||||
|
coords = coords.map((point) => {
|
||||||
|
return [
|
||||||
|
parseFloat(point.split(',')[1]),
|
||||||
|
parseFloat(point.split(',')[0])
|
||||||
|
];
|
||||||
|
});
|
||||||
|
object[fieldName] = {
|
||||||
|
__type: "Polygon",
|
||||||
|
coordinates: coords
|
||||||
|
}
|
||||||
|
}
|
||||||
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
if (object[fieldName] && schema.fields[fieldName].type === 'File') {
|
||||||
object[fieldName] = {
|
object[fieldName] = {
|
||||||
__type: 'File',
|
__type: 'File',
|
||||||
@@ -1303,6 +1349,42 @@ export class PostgresStorageAdapter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertPolygonToSQL(polygon) {
|
||||||
|
if (polygon.length < 3) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INVALID_JSON,
|
||||||
|
`Polygon must have at least 3 values`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (polygon[0][0] !== polygon[polygon.length - 1][0] ||
|
||||||
|
polygon[0][1] !== polygon[polygon.length - 1][1]) {
|
||||||
|
polygon.push(polygon[0]);
|
||||||
|
}
|
||||||
|
const unique = polygon.filter((item, index, ar) => {
|
||||||
|
let foundIndex = -1;
|
||||||
|
for (let i = 0; i < ar.length; i += 1) {
|
||||||
|
const pt = ar[i];
|
||||||
|
if (pt[0] === item[0] &&
|
||||||
|
pt[1] === item[1]) {
|
||||||
|
foundIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundIndex === index;
|
||||||
|
});
|
||||||
|
if (unique.length < 3) {
|
||||||
|
throw new Parse.Error(
|
||||||
|
Parse.Error.INTERNAL_SERVER_ERROR,
|
||||||
|
'GeoJSON: Loop must have at least 3 different vertices'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const points = polygon.map((point) => {
|
||||||
|
Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0]));
|
||||||
|
return `(${point[1]}, ${point[0]})`;
|
||||||
|
}).join(', ');
|
||||||
|
return `(${points})`;
|
||||||
|
}
|
||||||
|
|
||||||
function removeWhiteSpace(regex) {
|
function removeWhiteSpace(regex) {
|
||||||
if (!regex.endsWith('\n')){
|
if (!regex.endsWith('\n')){
|
||||||
regex += '\n';
|
regex += '\n';
|
||||||
|
|||||||
@@ -231,7 +231,8 @@ const validNonRelationOrPointerTypes = [
|
|||||||
'Array',
|
'Array',
|
||||||
'GeoPoint',
|
'GeoPoint',
|
||||||
'File',
|
'File',
|
||||||
'Bytes'
|
'Bytes',
|
||||||
|
'Polygon'
|
||||||
];
|
];
|
||||||
// Returns an error suitable for throwing if the type is invalid
|
// Returns an error suitable for throwing if the type is invalid
|
||||||
const fieldTypeIsInvalid = ({ type, targetClass }) => {
|
const fieldTypeIsInvalid = ({ type, targetClass }) => {
|
||||||
@@ -995,6 +996,11 @@ function getObjectType(obj) {
|
|||||||
return 'Bytes';
|
return 'Bytes';
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'Polygon' :
|
||||||
|
if(obj.coordinates) {
|
||||||
|
return 'Polygon';
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, "This is not a valid " + obj.__type);
|
throw new Parse.Error(Parse.Error.INCORRECT_TYPE, "This is not a valid " + obj.__type);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user