Skip to content

Add nearsphere convenience methods #582

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
155 changes: 155 additions & 0 deletions integration/test/ParseGeoPointTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,161 @@ describe('Geo Point', () => {
});
});

it('can query within large distances unsorted', (done) => {
let objects = [];
for (let i = 0; i < 3; i++) {
let obj = new TestObject();
let point = new Parse.GeoPoint(0, i * 45);
obj.set('location', point);
obj.set('construct', 'large_dist')
obj.set('index', i);
objects.push(obj);
}
Parse.Object.saveAll(objects).then(() => {
let query = new Parse.Query(TestObject);
let point = new Parse.GeoPoint(1, -1);
query.equalTo('construct', 'large_dist');
query.withinRadiansUnsorted('location', point, 3.14);
return query.find();
}).then((results) => {
assert.equal(results.length, 3);
done();
});
});

it('can query within medium distances unsorted', (done) => {
let objects = [];
for (let i = 0; i < 3; i++) {
let obj = new TestObject();
let point = new Parse.GeoPoint(0, i * 45);
obj.set('location', point);
obj.set('construct', 'medium_dist')
obj.set('index', i);
objects.push(obj);
}
Parse.Object.saveAll(objects).then(() => {
let query = new Parse.Query(TestObject);
let point = new Parse.GeoPoint(1, -1);
query.equalTo('construct', 'medium_dist');
query.withinRadiansUnsorted('location', point, 3.14 * 0.5);
return query.find();
}).then((results) => {
assert.equal(results.length, 2);
assert.equal(results[0].get('index'), 0);
assert.equal(results[1].get('index'), 1);
done();
});
});

it('can query within small distances unsorted', (done) => {
let objects = [];
for (let i = 0; i < 3; i++) {
let obj = new TestObject();
let point = new Parse.GeoPoint(0, i * 45);
obj.set('location', point);
obj.set('construct', 'small_dist')
obj.set('index', i);
objects.push(obj);
}
Parse.Object.saveAll(objects).then(() => {
let query = new Parse.Query(TestObject);
let point = new Parse.GeoPoint(1, -1);
query.equalTo('construct', 'small_dist');
query.withinRadiansUnsorted('location', point, 3.14 * 0.25);
return query.find();
}).then((results) => {
assert.equal(results.length, 1);
assert.equal(results[0].get('index'), 0);
done();
});
});

it('can measure distance within km unsorted - everywhere', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinKilometersUnsorted('location', sfo, 4000.0);
query.find().then((results) => {
assert.equal(results.length, 3);
done();
});
});

it('can measure distance within km unsorted - california', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinKilometersUnsorted('location', sfo, 3700.0);
query.find().then((results) => {
assert.equal(results.length, 2);
assert.equal(results[0].get('name'), 'San Francisco');
assert.equal(results[1].get('name'), 'Sacramento');
done();
});
});

it('can measure distance within km unsorted - bay area', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinKilometersUnsorted('location', sfo, 100.0);
query.find().then((results) => {
assert.equal(results.length, 1);
assert.equal(results[0].get('name'), 'San Francisco');
done();
});
});

it('can measure distance within km unsorted - mid peninsula', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinKilometersUnsorted('location', sfo, 10.0);
query.find().then((results) => {
assert.equal(results.length, 0);
done();
});
});

it('can measure distance within miles unsorted - everywhere', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinMilesUnsorted('location', sfo, 2500.0);
query.find().then((results) => {
assert.equal(results.length, 3);
done();
});
});

it('can measure distance within miles unsorted - california', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinMilesUnsorted('location', sfo, 2200.0);
query.find().then((results) => {
assert.equal(results.length, 2);
assert.equal(results[0].get('name'), 'San Francisco');
assert.equal(results[1].get('name'), 'Sacramento');
done();
});
});

it('can measure distance within miles unsorted - bay area', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinMilesUnsorted('location', sfo, 75.0);
query.find().then((results) => {
assert.equal(results.length, 1);
assert.equal(results[0].get('name'), 'San Francisco');
done();
});
});

it('can measure distance within km unsorted - mid peninsula', (done) => {
let sfo = new Parse.GeoPoint(37.6189722, -122.3748889);
let query = new Parse.Query(TestPoint);
query.withinMilesUnsorted('location', sfo, 10.0);
query.find().then((results) => {
assert.equal(results.length, 0);
done();
});
});

it('supports withinPolygon open path', (done) => {
const points = [
new Parse.GeoPoint(37.85, -122.33),
Expand Down
71 changes: 71 additions & 0 deletions src/ParseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -1135,6 +1135,77 @@ class ParseQuery {
return this.withinRadians(key, point, distance / 6371.0);
}

/**
* Adds a proximity based constraint for finding objects with key point
* values near the point given and within the maximum distance given.
* Results are unsorted for better performance compared to `withinRadians'.
* @param {String} key The key that the Parse.GeoPoint is stored in.
* @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
* @param {Number} maxDistance Maximum distance (in radians) of results to
* return.
* @return {Parse.Query} Returns the query, so you can chain this call.
*/
withinRadiansUnsorted(key: string, point: ParseGeoPoint, distance: number): ParseQuery {

if (!(point instanceof ParseGeoPoint)) {
// Try to cast it as a GeoPoint
point = new ParseGeoPoint(point);
}
return this._addCondition(key, '$geoWithin', { '$centerSphere': [point, distance] });
}

/**
* Adds a proximity based constraint for finding objects with key point
* values near the point given and within the maximum distance given.
* Radius of earth used is 3958.8 miles.
* Results are unsorted for better performance compared to `withinMiles'.
* @param {String} key The key that the Parse.GeoPoint is stored in.
* @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
* @param {Number} maxDistance Maximum distance (in miles) of results to
* return.
* @return {Parse.Query} Returns the query, so you can chain this call.
*/
withinMilesUnsorted(key: string, point: ParseGeoPoint, distance: number): ParseQuery {
return this.withinRadiansUnsorted(key, point, distance / 3958.8);
}

/**
* Adds a proximity based constraint for finding objects with key point
* values near the point given and within the maximum distance given.
* Radius of earth used is 6371.0 kilometers.
* Results are unsorted for better performance compared to `withinKilometers'.
* @param {String} key The key that the Parse.GeoPoint is stored in.
* @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
* @param {Number} maxDistance Maximum distance (in kilometers) of results
* to return.
* @return {Parse.Query} Returns the query, so you can chain this call.
*/
withinKilometersUnsorted(key: string, point: ParseGeoPoint, distance: number): ParseQuery {
return this.withinRadiansUnsorted(key, point, distance / 6371.0);
}

/**
* Adds a constraint to the query that requires a particular key's
* coordinates be contained within a given rectangular geographic bounding
* box.
* @param {String} key The key to be constrained.
* @param {Parse.GeoPoint} southwest
* The lower-left inclusive corner of the box.
* @param {Parse.GeoPoint} northeast
* The upper-right inclusive corner of the box.
* @return {Parse.Query} Returns the query, so you can chain this call.
*/
withinGeoBox(key: string, southwest: ParseGeoPoint, northeast: ParseGeoPoint): ParseQuery {
if (!(southwest instanceof ParseGeoPoint)) {
southwest = new ParseGeoPoint(southwest);
}
if (!(northeast instanceof ParseGeoPoint)) {
northeast = new ParseGeoPoint(northeast);
}
this._addCondition(key, '$within', { '$box': [ southwest, northeast ] });
return this;
}

/**
* Adds a constraint to the query that requires a particular key's
* coordinates be contained within a given rectangular geographic bounding
Expand Down
57 changes: 57 additions & 0 deletions src/__tests__/ParseQuery-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,63 @@ describe('ParseQuery', () => {
});
});

fit('can generate near-geopoint queries without sorting', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, needs to replace by ‘it’

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

var q = new ParseQuery('Shipment');
q.withinRadiansUnsorted('shippedTo', [20, 40], 2);
expect(q.toJSON()).toEqual({
where: {
shippedTo: {
$geoWithin: {
$centerSphere: [
{
__type: 'GeoPoint',
latitude: 20,
longitude: 40
},
2
]
}
}
}
});

q.withinMilesUnsorted('shippedTo', [20, 30], 3958.8);
expect(q.toJSON()).toEqual({
where: {
shippedTo: {
$geoWithin: {
$centerSphere: [
{
__type: 'GeoPoint',
latitude: 20,
longitude: 30
},
1
]
}
}
}
});

q.withinKilometersUnsorted('shippedTo', [30, 30], 6371.0);
expect(q.toJSON()).toEqual({
where: {
shippedTo: {
$geoWithin: {
$centerSphere: [
{
__type: 'GeoPoint',
latitude: 30,
longitude: 30
},
1
]
}
}
}
});
});

it('can generate geobox queries', () => {
var q = new ParseQuery('Shipment');
q.withinGeoBox('shippedTo', [20, 20], [10, 30]);
Expand Down