I have some polygons stored in GeoJSON in my mongoDB.
The client sends a list of boxes to load (the boxes are cells of a regular grid).
The normal way to retrieve them is to make a GeoJSON query with each box, but it is slow when I have many boxes.
I do not want to retrieve duplicates (polygons which lie on two cells are returned twice), so I make a list of retrieved polygons pks to ignore them in the next queries.
Given:
box = [ [ [ 0, 0 ], [ 1, 0 ], [ 1, 1 ], [ 0, 1 ], [ 0, 0 ] ] ] // the box to load
pks = [ ObjectId("54cf535cfe022e01ab4932f5"), ObjectId("54cf535cfe022e01ab4932f6") ] // the list of polygons already retrieved
With mongoDB I would have something like this:
for box in boxes:
db.places.find( { points:
{ $geoIntersects: { $geometry: { type: "Polygon" , coordinates: box } } },
{ _id: { $nin: pks } }
} )
I use MongoEngine, so I have the following:
pks = []
for box in boxes:
p = Polygon.objects(points__geo_intersects=box, pk__nin=pks)
if len(p)>0:
pks += p.scalar("id")
I have three questions:
1. Is there a more efficient way to query the polygons with this method?
2. Would it be faster to use a Cell object containing the list of reference of polygons which lie on the cell?
In MongoEngine I would have the following model:
class Cell(Document):
x = DecimalField()
y = DecimalField()
polygons = ListField(ReferenceField('Polygon'))
meta = {
'indexes': [[ ("x", 1), ("y", 1) ]]
}
The list of boxes to load would be the coordinates corresponding to the cells to load.
This would give with MongoEngine:
polygons = {}
for b in boxes:
cell = Cell.objects.get(x=b['x'], y=b['y'])
for polygon in cell.polygons:
if not polygons.has_key(polygon.pk):
polygons[polygon.pk] = polygon.to_json()
3. Is there a more efficient way to query the polygons with this method? (I think I should use select_related(), and maybe it is possible to filter the polygons directly in the mongoDB query to avoid retrieving duplicates)
After making a few benchmarks, I can answer my own questions: it is not noticeably faster to use an intermediate object such as Cell.
However, using a list of primary keys in the query, like this:
pks = []
for box in boxes:
p = Polygon.objects(points__geo_intersects=box, pk__nin=pks)
if len(p)>0:
pks += p.scalar("id")
is much slower than using a dictionary to ignore duplicates, like this:
polygons = {}
for box in boxes:
p = Polygon.objects(points__geo_intersects=box)
for polygon in p:
if not polygons.has_key(polygon.pk):
polygons[polygon.pk] = polygon.to_json()
Related
I try to find a polygon intersecting a "Big" polygon inside a MongoDB document.
From the documentation I know that
For $geoIntersects, if you specify a single-ringed polygon that has an area greater than a single hemisphere, ... , $geoIntersects queries for the complementary geometry.
If the "Big" polygon is used in the query - you can specify a custom MongoDB CRS to avoid that:
$geometry: {
type: "Polygon",
coordinates: [ <coordinates> ],
crs: {
type: "name",
properties: { name: "urn:x-mongodb:crs:strictwinding:EPSG:4326"
}
}
But how can I handle it, if it's the other way around, the "Big" polygon is stored in the MongoDB ? Simple use case for the described scenario: Find all countries a polygon intersects
I am trying find a solution on how to display polygons that are only within a specific range, a circle with radius using leaflet.
Polygons screenshots
Before, I have ask for help regarding on the display of points within a specific range but this time, since a polygon have many nodes/coordinates, i don't have any idea of how it can be done for polygons; a foreach statement?
Any solution? Thanks for the help!
Similar problem solved for displaying points within a specific range
Since you're using MongoDB, the best solution here is (if that's possible), to handle this in the database. Put 2dsphere indexes on your document's loc field and use a $geoWithin query in combination with $centerSphere:
The following example queries grid coordinates and returns all documents within a 10 mile radius of longitude 88 W and latitude 30 N. The query converts the distance to radians by dividing by the approximate radius of the earth, 3959 miles:
db.places.find( {
loc: { $geoWithin: { $centerSphere: [ [ -88, 30 ], 10/3959 ] } }
} )
2dsphere reference: http://docs.mongodb.org/manual/core/2dsphere/
$geoWithin reference: http://docs.mongodb.org/manual/reference/operator/query/geoWithin/
$centerSphere reference: http://docs.mongodb.org/manual/reference/operator/query/centerSphere/
If you really want to do this clientside (which i absolutely wouldn't recommend) and you don't want to build your on solution (which is possible) you could take a look at GeoScript.
GeoScript's geom.Geometry() class has a contains method:
Tests if this geometry contains the other geometry (without boundaries touching).
Geom.geometry reference: http://geoscript.org/js/api/geom/geometry.html
EDIT: Here's the pure JS/Leaflet solution as requested in the comments, this is quick-n-dirty, but it should work. Here the containsPolygon method returns true when all of the polygon's points are within the circle:
L.Circle.include({
'containsPoint': function (latLng) {
return this.getLatLng().distanceTo(latLng) < this.getRadius();
},
'containsPolygon': function (polygon) {
var results = [];
polygon.getLatLngs().forEach(function (latLng) {
results.push(this.containsPoint(latLng));
}, this);
return (results.indexOf(false) === -1);
}
});
Here's a working example: http://plnkr.co/edit/JlFToy?p=preview
If you want to return true if one or more of the polygon's points are within the circle than you must change the return statement to this:
return (results.indexOf(true) !== -1);
Is there a way to find out what polygons (specifically circles) a specific Point lies in?
In this case I would have stored a documents containing circles, like below, I would pass in a latitude and longitude for a point, and would like to get back all documents where the point is within the given circle.
{
"_id" : ObjectId("53e3e85ce4b0c2e8227a1dad"),
"name" : "Menlo College",
"location" : [-122.1928, 37.45632],
"radius" : NumberLong(215),
},
{
"_id" : ObjectId("53e43d19e4b0aeabcb3d3f9d"),
"name" : "West Valley College",
"location" : [-122.01021194458008, 37.263226547586207],
"radius" : NumberLong(604),
}
If this is not possible, then is it at least possible with other GeoJSON shapes? Everything I've found so far indicates that the inverse is possible (find all points which like inside a circle), but nothing for this scenario.
Thanks
It is possible using MongoDB's $geoIntersects Geospatial query operator.
So, if you have a collection of GeoJson polygons and you want to find out all the polygons that intersect with your given point, then you need to run the following:
db.places.find( { <locationFieldOfYourDocuments> :
{ $geoIntersects :
{ $geometry :
{ type : "Point" ,
coordinates: [long, lat]
} } } } )
In the command above, loc is that attribute of each document that contains the coordinates for GeoJson polygon. Also, make sure that you have 2dsphere index over <locationFieldOfYourDocuments>.
Now, to get your original problem solved I will use a little bit of javascript. There may be better solutions but not in my knowledge.
Let's say all your circles are stored in Circles collection. I would query that collection and fetch each circle one by one and then perform an intersect with another collection that would contain a single point which would be the one you wanted to query if it intersects with the circles or not. So let the point be stored in SinglePoint collection.
The script would look like...
db.Intersections.remove({}); // emptying the output collection
var circleCursor = db.Circles.find();
while (circleCursor.hasNext()) {
var circle = circleCursor.next();
var coord = circle.location;
var radiusInRadians = circle.radius * conversionFactorForRadius;
var intersect = db.SinglePoint.find({loc :
{ $geoWithin :
{$centerSphere : [coord], radiusInRadians}
}});
if (intersect.hasNext()) {db.Intersections.add(circle)} // this will add all intersecting circles to Intersections collection
}
All you have to do is save this script in a file (myScript.js) and make a call:
mongo DBName pathTomyScript.js
This will store all the circles that intersect with your input point in the Intersects collection. All the above collections should be in DBName database.
I have a documents contains list of location "boxes" (square area). Each box is represented by 2 points (bottom-left or south-west, top-right or north-east).
Document, for example:
{
locations: [
[[bottom,left],[top,right]],
[[bottom,left],[top,right]],
[[bottom,left],[top,right]]
]
}
I'm using 2d index for those boundaries points.
My input is a specific location point [x,y] and I want to fetch all documents that have at list one box that this point is located in it.
Is there any geospatial operator I can use to do that?
How do I write this query?
You can use the box operator, see:
http://docs.mongodb.org/manual/reference/operator/query/box/#op._S_box with the following example taken directly from that page:
db.places.find( { loc : { $geoWithin : { $box :
[ [ 0 , 0 ] ,
[ 100 , 100 ] ] } } } )
It is worth noting that the 2d index is considered legacy. If you can convert to using GeoJSON and a 2dsphere index, then you can use the $geoWithin operator: see
http://docs.mongodb.org/manual/reference/operator/query/geoWithin/#op._S_geoWithin
GeoJSON has a number of other benefits, not least of which, is that it is easily transmitted and digested by web mapping apps such as OpenLayers or Leaflet.
I have figured out how to make the query for the intersection, but can't figure out how to define the boxes in the database so that it returns me all boxes that intersect with my query parameter.
How do I accomplish this?
I have found this website to be most helpful. Just use their examples and everything should be clear. If not, consider turning that complex polygon into a square (which is also a polygin).
You can use these commands for a database called 'test' and collection called 'geo':
test.geo.insert( {_id: "Poly1", shape: {type: "Polygon", coordinates: [[ [1,2], [1,4], [4,4], [4,2], [1,2] ]] } });
test.geo.ensureIndex( {shape:"2dsphere"} );
geo.find( {shape: {$geoIntersects: {$geometry: {type: "Polygon", coordinates: [[ [2,4], [2,8], [3,8], [3,4], [2,4] ]] }}}} )
This particular example has the polygons intersecting on column 4. I have not yet tested all possible combinations but from the ones I tested, it does work.