Mapbox: Find POIs with defined radius - mapbox

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.

Related

Leaflet marker.cluster Popup on the geojson data loaded by leaflet ajax

I want to create a leaflet map to display site locations. The site data was loaded by leaflet ajax in the geojson format.
Then I use Leaflet.markercluster to create a cluster view and it works fine. But it seems the popup only shows the last site, no matter which icon I click on.
Here is my original code
function map_viewer(map, options){
var my_data = new L.GeoJSON.AJAX("http://127.0.0.1:8000/my_data/",{
onEachFeature: function(feature,layer){
layer.bindPopup(feature.properties.siteid);
clusters.on('click', function (e) {
this.bindPopup(feature.properties.siteid);
});
}
});
var clusters = L.markerClusterGroup();
my_data.on('data:loaded', function()
{
clusters.addLayer(my_data);
map.addLayer(clusters);
});
var groupedOverlays = {
"Layers": {
"cluster view": clusters
}
};
L.control.groupedLayers(groupedOverlays).addTo(map);
}
Updated on 2021-02-01:
I realise there are a few similar cases after go searching on google. However, when I refine my code based on the suggestion, the popup window never show up anymore:
function map_viewer(map, options){
var clusters = L.markerClusterGroup();
var my_data = new L.GeoJSON.AJAX("http://127.0.0.1:8000/my_data/",{
onEachFeature: function(feature,layer){
layer.bindPopup(feature.properties.siteid);
}
});
my_data.on('data:loaded', function()
{
clusters.addLayer(my_data);
map.addLayer(clusters);
});
}
Also, please refer to below a small part of the importing geojson dataset.
{"type": "FeatureCollection", "crs":
{"type": "name", "properties": {"name": "EPSG:4326"}}, "features": [
{"type": "Feature", "properties": {"siteid": 1, "latitude": -28.004959, "longitude": 153.428117, "pk": "1"}, "geometry": {"type": "MultiPoint", "coordinates": [[153.428117, -28.004959]]}},
{"type": "Feature", "properties": {"siteid": 2, "latitude": -33.870436, "longitude": 151.225013, "pk": "2"}, "geometry": {"type": "MultiPoint", "coordinates": [[151.225013, -33.870436]]}},
{"type": "Feature", "properties": {"siteid": 3, "latitude": -33.92677, "longitude": 151.21356, "pk": "3"}, "geometry": {"type": "MultiPoint", "coordinates": [[151.21356, -33.92677]]}},
{"type": "Feature", "properties": {"siteid": 4, "latitude": -33.854711, "longitude": 150.987657, "pk": "4"}, "geometry": {"type": "MultiPoint", "coordinates": [[150.987657, -33.854711]]}},
Conclusion:
I have fixed the issue just by converting the geometry type in my geojson dataset from "Multipoint" to "Point". It seems this plugin Leaflet.markercluster can only cluster view for Multipoints, but not able to display the bindPopup of each layer.
Fo me it is clear why the popup has the last id, because in the onEachFeature function you bind/overwrite every time the popup to the clusters with the new siteid and so the last one is applied.
But when you add a popup to the the complete markercluster all layers have the same popup.
So change your code to:
var my_data = new L.GeoJSON.AJAX("http://127.0.0.1:8000/my_data/",{
onEachFeature: function(feature,layer){
layer.bindPopup(feature.properties.siteid);
}
});
Alright, I delved into this case for 2 day, eventually, I find the bug and fix it.
The geometry type in my geojson dataset is "Multipoint". It seems this plugin Leaflet.markercluster can only cluster view for Multipoints, but not able to display the bindPopup of each layer.
To fix this, I just need convert "Multipoint" to "Point.
Anyway, thanks heaps #Falke Design for your kind hints and the great example.

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

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"]
}

How to add Polygon coordinates to an existing geoJSON file?

Trying to place the boundaries of 42 countries to the map with Choropleth Leaflet. Apparently, the data that I have only had lat and long for each country and not the list of coordinates. How would I add polygon coordinates to my existing data? Or is there an API I can use for that? Research brought me nowhere.
my data
"type": "Feature",
"properties": {
"country": "Austria",
"points": 89.19089810712848,
"price": 31.192106322996377
},
"geometry": {
"type": "Polygon",
"coordinates": [
13.199959,
47.2000338
]
}```
If you need rough country border coordinates, you can search for "GeoJSON world", there are a few convenient sources available.
For example https://geojson-maps.ash.ms/, https://github.com/simonepri/geo-maps or https://github.com/johan/world.geo.json

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 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