MongoDB: GeoJSON object type index, Can't extract geo keys - mongodb

I have made many the below GeoJSON Point objects in MongoDB Compass as per the docs
{
"_id": {
"$oid": "5e86d275a3d7fd05e4f022a8"
},
"location": {
"type": "point",
"coordinates": ["-110.85458435", "39.68476146"]
},
"website": "carbonmedicalservice.com",
"address": "125 S Main St",
"state": "UT",
"zip": "84526",
"name": "Carbon Medical"
}
I want to be able te search the collection to return all records within an reactangle, I believe I need to add a Geospatial Indexe to the location but I get the error shown below...
This is how I entered the data:

In a GeoJSON type, coordinates should be float/double not string.
Also, the type should be Point, not point in your case. So, the GeoJSON in your case should be:
{
"type": "Point",
"coordinates": [-110.85458435, 39.68476146]
}
instead of
{
"type": "point",
"coordinates": ["-110.85458435", "39.68476146"]
}

Related

Mongodb aggregate on two collections based on geopoint

I have two collections
person
{
"name": "Mike",
"age": 42,
"address": {
"location": {"type": "Point", "coordinates": [12.3456, 78.9101]}
}
}
restaurant
{
"name": "Bistro",
"type": ["Maxican", "Italian"],
"location": {"type": "Point", "coordinates": [12.3555, 78.9333]}
}
I've created spherical indexes on both collections
db.person.createIndex({"address.location": "2dsphere"})
db.restaurant.createIndex({"location": "2dsphere"})
Now I want to find all the restaurants near to a person using lat/long in both collections.
for e.g find all the restaurants within 10KM of a person. result will look like below
{
"name": "Mike",
"age": 42,
"address": {
"location": {"type": "Point", "coordinates": [12.3456, 78.9101]}
},
"nearby_restaurants": [{"name": "Bistro", "distance": 5.3}] // this means bistro is 5.3 KM away from this person
}
Notice that, I'm only using name field from restaurant collection and an additional field distance which will be calculated after the query.
I want to do this for all person. so basically for each person, i have to scan the whole restaurant collection every time.
To achieve this I thought of using lookup with aggregate, something like below
db.person.aggregate([
{
$lookup: {
from: "restaurant",
let: {"personPoint": "$address.location"},
as: "nearby_restaurants",
pipeline: [
{
$geoNear: {
near: "$$personPoint",
spherical: true,
distanceField: "distance",
maxDistance: 10 * 1000, // within 10 KM
distanceMultiplier: 0.001 // get the result in KM
}
},
{
$unwind: "$location"
},
]
}
},
{
$unwind: {
path: "$nearby_restaurants",
preserveNullAndEmptyArrays: true
}
}
])
This doesnt work at all. I get this error: $geoNear requires a 'near' option as an Array"
I tried to look at different sources but couldnt understand and fix the error.
PS: If currently, it's not possible through lookups/aggregate, how can we achieve the same through looping over each person's document?

Mapbox: Find POIs with defined radius

The task I need is:
input: geolocation coordinates from gps, radius
ouput: list of pois (just names) to user can choose
just need analogue for google nearby search (since their cost is too high for a production)
questions:
1) what API is more preferable for this? examples are appreciated
2) do I need own data for pois or there is build in date in mapbox for these purposes?
The Mapbox Tilequery API lets you do exactly this. Here is a step-by-step tutorial explaining how to work with this API, and this API playground lets you experiment with the API. The data queried by the API is determined by the tileset passed as a parameter to your API request. As noted in the linked documentation, tutorial, and playground, you can either use existing Mapbox tilesets are create your own tileset with custom data, depending on your use case.
Note that, depending on the structure of the underlying data in the tileset you use, you might need to do a little extra work to convert a feature returned by the Tilequery API into a name of a POI. For example, consider the response body for below API request which makes use of the default mapbox.mapbox-streets-v8 tileset:
https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/tilequery/-93.1204,44.9472.json?radius=25&limit=5&dedupe&access_token=YOUR_MAPBOX_ACCESS_TOKEN
One particular feature returned by the response body is:
{
"type": "Feature",
"id": 4,
"geometry": {
"type": "Point",
"coordinates": [
-93.12041537130386,
44.947199821761615
]
},
"properties": {
"extrude": "true",
"height": 3,
"min_height": 0,
"type": "house",
"underground": "false",
"tilequery": {
"distance": 1.2132887872688276,
"geometry": "polygon",
"layer": "building"
}
}
}
Although there is no POI name here, you could use the returned coordinates in conjunction with the Mapbox reverse geocoding API endpoint to retrieve names and other relevant POI properties for the POI located at said coordinate.
conversation with Mapbox support
Hi Artemii​,
It's Alex from Mapbox Support, happy to help!
You will want to use the Mapbox Tilequery API. The option you will want to utilize is radius​. Please be advised that queries will use tiles from the maximum zoom of the tileset, and will only include the intersecting tile plus eight surrounding tiles when searching for nearby features. That means that if your tileset has a maximum extent of z20, the maximum radius that you can search is only a few meters. Here is an API playground that you can test the API out with.
I hope this was helpful!
Hi Alex,
thanks for the quick reply, one more question:
request:
https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/tilequery/55.9414,54.7295.json?radius=25&limit=50&dedupe&geometry=point&access_token=YOUR_MAPBOX_ACCESS_TOKEN
response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 8,
"geometry": {
"type": "Point",
"coordinates": [
55.94845533370972,
54.72732387401962
]
},
"properties": {
"house_num": "32",
"tilequery": {
"distance": 8.949637333832088,
"geometry": "point",
"layer": "housenum_label"
}
}
},
{
"type": "Feature",
"id": 23629792230,
"geometry": {
"type": "Point",
"coordinates": [
55.948566645383835,
54.72761119224691
]
},
"properties": {
"class": "general",
"filterrank": 4,
"maki": "marker",
"name": "Башинформсвязь",
"name_script": "Cyrillic",
"sizerank": 16,
"type": "Telecommunication",
"tilequery": {
"distance": 23.898768437893523,
"geometry": "point",
"layer": "poi_label"
}
}
}
]
}
question: I understand that I can find info about places I got in the response using reverse geocoding API, but is there another approach to do this? Ideally, I would like to get poi's names from one tilquery request, because in case of using two API's (tilquery + geocoding) I will have to query 4-5 queries instead of only 1 (just worrying because it impacts on cost).
Hi Artemii​,
There is a parameter in the Tilequery API called layers that you can use to target a specific layer of your style.
https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/tilequery/55.9414,54.7295.json?radius=25&limit=50&dedupe&geometry=point&layers=poi_label&access_token=
Which gets this response:
{ "type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 32316157590,
"geometry": {
"type": "Point",
"coordinates": [
55.94142526388168,
54.7295828683082
]
},
"properties": {
"category_en": "Supermarket",
"category_zh-Hans": "超市",
"class": "food_and_drink_stores",
"filterrank": 1,
"maki": "grocery",
"name": "Магнит",
"name_script": "Cyrillic",
"sizerank": 16,
"type": "Supermarket",
"tilequery": {
"distance": 9.367370433680872,
"geometry": "point",
"layer": "poi_label"
}
}
}
]
}
You can take this response object and return all the information from the POI. The "name" property is the name of the POI. Was there other properties that you were looking for from the reverse geocoding that are not being returned by the tilequery? It would be helpful for you to share a full example of a workflow using both tilequery and reverse geocoding, and to hear more about your exact use case, and how this tilequery/reverse geocoding operation fits into your larger application workflow.
Hi Alex,
request (without poi label since it doesn't find few bars I know. But if you add this label you only see one result) : https://api.mapbox.com/v4/mapbox.mapbox-streets-v8/tilequery/55.9485,54.7275.json?radius=14&limit=50&dedupe&access_token=YOUR_MAPBOX_ACCESS_TOKEN request
response:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": 5,
"geometry": {
"type": "Point",
"coordinates": [
55.9485,
54.7275
]
},
"properties": {
"extrude": "true",
"height": 15,
"min_height": 0,
"type": "building",
"underground": "false",
"tilequery": {
"distance": 0,
"geometry": "polygon",
"layer": "building"
}
}
},
{
"type": "Feature",
"id": 1297495121,
"geometry": {
"type": "Point",
"coordinates": [
55.94833781213748,
54.727526546045794
]
},
"properties": {
"class": "path",
"iso_3166_1": "RU",
"iso_3166_2": "RU-BA",
"len": 4450,
"oneway": "false",
"structure": "none",
"surface": "paved",
"type": "footway",
"tilequery": {
"distance": 10.859473551200084,
"geometry": "linestring",
"layer": "road"
}
}
},
{
"type": "Feature",
"id": 23629792230,
"geometry": {
"type": "Point",
"coordinates": [
55.948566645383835,
54.72761119224691
]
},
"properties": {
"class": "general",
"filterrank": 4,
"maki": "marker",
"name": "Башинформсвязь",
"name_script": "Cyrillic",
"sizerank": 16,
"type": "Telecommunication",
"tilequery": {
"distance": 13.10152056398561,
"geometry": "point",
"layer": "poi_label"
}
}
},
{
"type": "Feature",
"id": 7,
"geometry": {
"type": "Point",
"coordinates": [
55.94869895433047,
54.7274698331467
]
},
"properties": {
"extrude": "true",
"height": 3,
"min_height": 0,
"type": "building",
"underground": "false",
"tilequery": {
"distance": 13.251093067012334,
"geometry": "polygon",
"layer": "building"
}
}
}
]
}
If we decode the first pair of coordinates(55.9485, 54.7275) using reverse geocoding (https://docs.mapbox.com/search-playground), we get Smoky People, ул. Ленина, 32, Уфа, Республика Башкирия 450077, Russia and if we decode all pairs, we will be able to find more cafes and bars (poi's).
Hey Artemii,
The reason for the varied results are the sources of the queries.
The Tilequery API searches for things that are on our Mapbox Streets v8 tileset (which our Mapbox Streets v11 style uses). The data in this tileset majorly comes from OpenStreetMap. If there are missing or outdated places on the map, this is the perfect opportunity to help us improve our map! If you do want to add or edit anything, head to openstreetmap.org, create an account, and make improvements directly. You'll have the option to go through an interactive tutorial to get you started. There are more details for advanced editing here as well: https://labs.mapbox.com/mapping. The Mapbox Streets tileset is updated regularly as features are edited or added to the map, which means that if you edit OpenStreetMap, you will eventually see your changes reflected on your Mapbox map.
The Geocoding API contains data sources from governments, open data projects, and private companies. In some cases, results from the Geocoding API may differ from Mapbox Streets or OpenStreetMap data. Check out this documentation on how geocoding works at Mapbox.
I think the best way to reduce the amount of API calls you make would be to pick one API, either reverse geocoding or Tilequery, and stick with it. With OpenStreetMaps, as tedious as it sounds, you do have the ability to add any POIs you know are missing, or cross reference other sources to add POIs to the map, which will make the place visible on the map as well as appear in your tilequery. With the Geocoding API, due to the nature of some sources and licensing, not all the places that you can search are visible on the map.

MongoDB: how to store GPS track including timestamps

I'm interested in your input on what's the most efficient (or most intelligent) way to store a GPS track consisting of GPS coordinates (Longitude & Latitude) and timestamps.
I'm thinking of those two ideas:
gpsTrack = {
"gps_track" : {
"type": "FeatureCollection",
"features": [
{ "type": "Feature",
"geometry": {"type": "Point", "coordinates": [104.0, 0.3]},
"properties": {
"absolute_timestamp" : "2011-12-31T23:50:59Z"
}
},
{ "type": "Feature",
"geometry": {"type": "Point", "coordinates": [128.0, 0.5]},
"properties": {
"absolute_timestamp" : "2011-12-31T23:59:59Z"
}
}
] }
}
Or using the GeoJSON MultiPoint and the timestamps as an array.
gpsTrack = {
"geometry": {"type": "MultiPoint", "coordinates": [[104.0, 0.3], [128.0, 0.5]]},
"properties": {
"timestamps" : ["2011-12-31T23:50:59Z", "2011-12-31T23:59:59Z"]
}
The total amount of way points is assumed to be 4.000 to 10.000 but for sure below 100.000. For the first idea I'm afraid that the collection will become quite long since every points and its timestamp is stored separately. While the second idea is more compact and utilizing the MultiPoint GeoJSON type.
Thanks for your input.
May be you can use coordinates array for timestamp too. Most likely mongodb will just interested in first 2 objects of coordinates. You can put timestamp as third object.
gpsTrack = {
"geometry": {
"type": "MultiPoint",
"coordinates": [
[104.0, 0.3, "2011-12-31T23:50:59Z"],
[128.0, 0.5, "2011-12-31T23:59:59Z"]]
}
}
like I said, I didn't try if this violates mongodb to make geo query over coordinates.

How to make a GeometryCollection in GeoJSON with a single point + polygon?

How do you add a point to a polygon as a single feature? According to the GeoJson specs, this is known as a "GeometryCollection".
Example of a 'GeometryCollection':
{ "type": "GeometryCollection",
"geometries": [
{ "type": "Point",
"coordinates": [100.0, 0.0]
},
{ "type": "LineString",
"coordinates": [ [101.0, 0.0], [102.0, 1.0] ]
}
]
}
I tried adding a point to a polygon feature, but I couldn't get it to show on my mapbox map because I guess it is invalid GeoJson.
Anyone know what the proper way of doing this is? There are not many examples to follow on the web.
My take: [jsfilddle]
var myRegions = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometries": [
{
"type": "Point",
"coordinates": [
61.34765625,
48.63290858589535
]
},
{
"type": "Polygon",
"coordinates": [
[
[
59.94140624999999,
50.65294336725709
],
[
54.931640625,
50.90303283111257
],
[
51.943359375,
51.04139389812637
],
[
50.9765625,
48.19538740833338
],
[
52.55859375,
46.46813299215554
],
[
52.998046875,
43.8028187190472
],
[
54.4921875,
42.391008609205045
],
[
57.041015625,
43.29320031385282
],
[
59.8974609375,
45.398449976304086
],
[
62.5341796875,
44.08758502824516
],
[
65.6982421875,
45.73685954736049
],
[
68.37890625,
48.3416461723746
],
[
65.8740234375,
49.18170338770663
],
[
63.720703125,
49.97948776108648
],
[
63.80859374999999,
52.348763181988076
],
[
61.4794921875,
52.32191088594773
],
[
59.9853515625,
51.86292391360244
],
[
61.9189453125,
51.09662294502995
],
[
60.5126953125,
50.51342652633956
],
[
59.94140624999999,
50.65294336725709
]
]
]
}
]
}
]
};
As said in GeoJSON spec, a Feature object has exactly one geometry member, which is a Geometry object (or null).
A feature object must have a member with the name "geometry". The value of the geometry member is a geometry object as defined above or a JSON null value.
Among the possible geometry's you can indeed use a GeometryCollection, which must have a member geometries. The latter is an array of other geometries, i.e. your point, polygon, etc., or even another GeometryCollection.
A geometry collection must have a member with the name "geometries". The value corresponding to "geometries" is an array. Each element in this array is a GeoJSON geometry object.
So in your case you could simply do something like:
var myRegions = {
"type": "FeatureCollection",
"features": [{
"type": "Feature", // single feature
"properties": {},
"geometry": { // unique geometry member
"type": "GeometryCollection", // the geometry can be a GeometryCollection
"geometries": [ // unique geometries member
{ // each array item is a geometry object
"type": "Point",
"coordinates": [
61.34765625,
48.63290858589535
]
},
{
"type": "Polygon",
"coordinates": [
[
[
59.94140624999999,
50.65294336725709
],
// more points…
[
59.94140624999999,
50.65294336725709
]
]
]
}
]
}
}]
};
Updated jsfiddle: http://jsfiddle.net/rh8ok5t8/18/
I'm unsure to what you're actually trying to accomplish because you say you want to create a geometrycollection but in your example you're creating a featurecollection which is not the same by far.
A featurecollection is a collection of features:
A GeoJSON object with the type "FeatureCollection" is a feature collection object. An object of type "FeatureCollection" must have a member with the name "features". The value corresponding to "features" is an array.
http://geojson.org/geojson-spec.html#feature-collection-objects
Here's an example of a featurecollection:
{
type: "FeatureCollection",
features: [{
"type": "Feature",
"properties": {
"value": "foo"
},
"geometry": {
"type": "Point",
"coordinates": [0,0]
}
}, {
"type": "Feature",
"properties": {
"value": "bar"
},
"geometry": {
"type": "Polygon",
"coordinates": [[[45, 45], [45, -45], [-45, -45], [-45, 45], [45,45]]]
}
}]
}
A geometrycollection is a single feature (which you could contain in a featurecollection):
A GeoJSON object with the type "Feature" is a feature object. A feature object must have a member with the name "geometry". The value of the geometry member is a geometry object as defined above or a JSON null value. A feature object must have a member with the name "properties". The value of the properties member is an object (any JSON object or a JSON null value). If a feature has a commonly used identifier, that identifier should be included as a member of the feature object with the name "id".
http://geojson.org/geojson-spec.html#feature-objects
with multiple geometries:
A GeoJSON object with type "GeometryCollection" is a geometry object which represents a collection of geometry objects. A geometry collection must have a member with the name "geometries". The value corresponding to "geometries" is an array. Each element in this array is a GeoJSON geometry object.
http://geojson.org/geojson-spec.html#geometry-collection
And here's an example of a geometrycollection feature:
{
"type": "GeometryCollection",
"properties": {
"value": "foo"
},
"geometries": [{
"type": "Point",
"coordinates": [0, 0]
}, {
"type": "Polygon",
"coordinates": [[[45, 45], [45, -45], [-45, -45], [-45, 45], [45,45]]]
}]
}

How to add a GeoJSON layer in GraphHopper?

I have a GeoJSON file containing POIs that I'd like to be able to display within a separate GraphHopper layer. After several tries and search over internet, I just can't manage to get a way to do it.
This is a sample of the GeoJSON file (I checked the whole file with JSON validator and it was OK).
{"type": "Feature",
"properties": {
"fee": "no",
"bicycle_parking": "anchors",
"ref": "PVNAN23",
"address": "Rue Gabriel Goudy 44200 Nantes",
"name": "Pirmil P+R",
"capacity": "24",
"park_ride": "yes",
"amenity": "bicycle_parking",
"covered": "yes"
},
"geometry": {"type": "Point", "coordinates": [-1.5406709, 47.1960031]}},
{"type": "Feature",
"properties": {
"bicycle_parking": "stands",
"addr:postcode": "44000",
"addr:country": "FR",
"name": "Madeleine",
"capacity": "6",
"amenity": "bicycle_parking",
"addr:street": "chaussée de la Madeleine",
"note": "vérifié",
"addr:city": "Nantes",
"covered": "no",
"addr:housenumber": "35"
},
"geometry": {"type": "Point", "coordinates": [-1.55076671448, 47.21000114109]}}
]}
I tried what is explained in How to load external geojson file into leaflet map but I cannot get it working.
If your JSON is valid that doesn't mean you're working with a valid GeoJSON object. For instance: {"foo": "bar"} is perfectly valid JSON but in no way a valid GeoJSON object. L.GeoJSON, leaflet's GeoJSON layer expects a FeatureCollection or an array containing Features.
A valid FeatureCollection:
{
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"id": 1
},
"geometry": {
"type": "Point",
"coordinates": [0,0]
}
},{
"type": "Feature",
"properties": {
"id": 2
},
"geometry": {
"type": "Point",
"coordinates": [1,1]
}
}]
}
Or just the array with features:
[{
"type": "Feature",
"properties": {
"id": 1
},
"geometry": {
"type": "Point",
"coordinates": [0,0]
}
},{
"type": "Feature",
"properties": {
"id": 2
},
"geometry": {
"type": "Point",
"coordinates": [1,1]
}
}]
(Note that just an array of features isn't a valid GeoJSON object but Leaflet will handle it without problems)
To load these into a L.GeoJson layer you'll need to make them available in your script. You could simple declare the object before you create the layer. For example:
// Declare GeoJSON object
var geojson = {
type: "FeatureCollection",
features: [
// Features here
]
}
// Create a new GeoJSON layer with geojson object
// And add to map (assuming your map instance is assigned to "map")
var layer = new L.GeoJSON(geojson).addTo(map);
But that will become quite a mess when you've got lots of features and it's always better to keep your logic and data separated so you should put your data object in a separate file. So let's say you've got the object stored in a file called "geo.json", then you can load the file with XHR/AJAX solution of your choice. I'm using jQuery in the following example:
// Fetch geo.json file and assign the data to geojson variable
$.getJSON('geo.json', function (geojson) {
// Create a new GeoJSON layer with GeoJSON object
// And add to map (assuming your map instance is assigned to "map")
var layer = new L.GeoJSON(geojson).addTo(map);
});
Here a working example on Plunker: http://plnkr.co/edit/Mh8p4F?p=preview