Den nächsten POI finden

Den nächsten POI finden#

Aus Nutzer-Perspektive ist das ein ganz klassisches Problem:

Wo ist das nächste Café? Wo ist das nächste Klo? Wo ist die nächste Bahn-Haltestelle?

Aus einem geoinformatischen Blickwinkel stellt sich das ganze etwas komplizierter dar. Zunächst wollen wir die Frage in der Sprache der Geoinformatik formulieren:

Gegeben eine Koordinate und eine Menge an POIs einer bestimmten Kategorie, welcher ist fußläufig der nächste?

Die Koordinate ist unser Standort, also N 51° 57' 49.068 E 007° 36' 46.512". Die POIs einer bestimmten Kategorie, hier Cafés kann uns der openpoiservice liefern. Dieser wird allerdings einen Bereich um unseren Standort benötigen, um uns alle POIs, die in diesem Bereich liegen, geben zu können.

Angenommen dass wir maximal einen Kilometer, also ca. 15 Minuten, laufen wollen, können wir diesen Bereich einfach mit einem Kreis mit passendem Radius approximieren.

Wir können aber auch zunächst den tatsächlich erreichbaren Bereich mithilfe des openrouteservice bestimmen:

Wir beginnen mit ein bisschen notwendigem Setup, das für den Rest des Beispiels gebraucht wird.

Hide code cell source
import folium
import requests

# The key in here is not working anymore.
headers = {
    'Accept': 'application/json, application/geo+json, application/gpx+xml, img/png; charset=utf-8',
    'Authorization': '5b3ce3597851110001cf6248e2553546494b4efba514744ff48c2e59',
    'Content-Type': 'application/json; charset=utf-8'
}

Als nächstes kann der /isochrones-Endpunkt genutzt werden, um den Bereich zu finden, der fußläufig innerhalb von 15 Minuten (900 Sekunden) von unserem Standort aus erreichbar ist. Wir nutzen folium, um das Ergebnis zu visualisieren.

# openrouteservice uses [lon, lat]
startpoint = [7.61282,51.96363] # [lon, lat]
startpoint_reverse = startpoint[::-1] # [lat, lon] for folium

body = {"locations":[startpoint],"range":[900]}

# We use the foot-walking profile here.
isochrone_response = requests.post('https://api.openrouteservice.org/v2/isochrones/foot-walking', json=body, headers=headers)
isochrone_geojson = isochrone_response.json()

# folium uses [lat, lon]
m = folium.Map(startpoint_reverse, zoom_start=14)

folium.GeoJson(isochrone_geojson).add_to(m)

m
Make this Notebook Trusted to load map: File -> Trust Notebook

Innerhalb dieses Bereichs können nun POIs der gewünschten Kategorie gesucht werden. Dafür wird der /pois-Endpunkt verwendet. Die entsprechende Kategorie findet sich in der Backend-Dokumentation des openrouteservice

# The POI endpoint needs a "plain" polygon, not a GeoJSON "Feature".
geojson = isochrone_geojson["features"][0]["geometry"]

body = {"request":"pois","geometry":{"geojson":geojson},"filters":{"category_ids":[564]},"sortby":"category"}

poi_response = requests.post('https://api.openrouteservice.org/pois', json=body, headers=headers)
poi_geojson = poi_response.json()

m = folium.Map(startpoint_reverse, zoom_start=14)
folium.GeoJson(poi_geojson).add_to(m)

m
Make this Notebook Trusted to load map: File -> Trust Notebook

Als Nächstes muss hieraus der nächste POI extrahiert werden. Dafür wird der /matrix-Endpunkt genutzt. Hierbei werden die POI-Koordinaten als destination, der Startpunkt als source übergeben. Dadurch ist das Ergebnis einfach verarbeitbar.

pois = []
pois_info = []

for feature in poi_geojson["features"]:
    pois.append(feature["geometry"]["coordinates"])
    pois_info.append(feature)

# The final list of locations will include all POI locations and have the startpoint attached to the end.
destinations = list(range(len(pois)))
sources = [len(pois)]

pois.append(startpoint)

# now we have the POIs and can make a neat matrix call

body = {"locations": pois, "sources": sources, "destinations": destinations }
print(body)

matrix_response = requests.post('https://api.openrouteservice.org/v2/matrix/foot-walking', json=body, headers=headers)
print(matrix_response.status_code, matrix_response.reason)
print(matrix_response.text)
matrix_json = matrix_response.json()

print(matrix_json)

durations = matrix_json["durations"][0]
nearest_duration = min(durations)
nearest_index = durations.index(nearest_duration)

# The last element in the list is the startpoint.
nearest_poi = pois[nearest_index]
nearest_poi_reverse = nearest_poi[::-1]
nearest_poi_info = pois_info[nearest_index]
m = folium.Map(startpoint_reverse, zoom_start=14)
folium.map.Marker(location=nearest_poi_reverse).add_to(m)
m
{'locations': [[7.610429, 51.967839], [7.627296, 51.960045], [7.61323, 51.968324], [7.620167, 51.963336], [7.628404, 51.963373], [7.62639, 51.960486], [7.618128, 51.966363], [7.61678, 51.955436], [7.610157, 51.964569], [7.626256, 51.95945], [7.626413, 51.95943], [7.622595, 51.970044], [7.626235, 51.959383], [7.622607, 51.960341], [7.615033, 51.968788], [7.627175, 51.964004], [7.618026, 51.963168], [7.628389, 51.959613], [7.628547, 51.961154], [7.626372, 51.960522], [7.627238, 51.959161], [7.603077, 51.959505], [7.597829, 51.961154], [7.61055, 51.972014], [7.612293, 51.952544], [7.618595, 51.963004], [7.626887, 51.965795], [7.623465, 51.964594], [7.628791, 51.967291], [7.628029, 51.96306], [7.603274, 51.966042], [7.626422, 51.960006], [7.613973, 51.971225], [7.626536, 51.960224], [7.620144, 51.963368], [7.62603, 51.958297], [7.625461, 51.961987], [7.624915, 51.960543], [7.625832, 51.958746], [7.624067, 51.96627], [7.61812, 51.96232], [7.620895, 51.970133], [7.622075, 51.958793], [7.625878, 51.961425], [7.626765, 51.965017], [7.620929, 51.962295], [7.610729, 51.953073], [7.629119, 51.960431], [7.599961, 51.965219], [7.621319, 51.96368], [7.619153, 51.965286], [7.622336, 51.972149], [7.627463, 51.961908], [7.6257, 51.961969], [7.623672, 51.960466], [7.622219, 51.968612], [7.627632, 51.960853], [7.61282, 51.96363]], 'sources': [57], 'destinations': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56]}
200 OK
{"durations":[[694.68,898.18,576.89,437.54,932.15,841.57,480.02,895.72,165.85,918.12,925.99,905.94,917.62,854.84,617.94,868.43,353.34,978.13,984.38,838.67,986.11,766.48,1152.27,931.03,1207.65,390.23,910.78,662.47,965.32,952.92,775.47,883.76,826.47,863.05,437.03,1006.77,813.98,771.23,970.04,707.08,381.26,834.63,775.9,873.99,876.51,548.47,1208.87,1026.43,840.25,499.47,429.89,1061.13,912.27,825.86,727.15,750.22,927.44]],"destinations":[{"location":[7.610283,51.967753],"snapped_distance":13.85},{"location":[7.627326,51.960136],"snapped_distance":10.3},{"location":[7.613287,51.968282],"snapped_distance":6.08},{"location":[7.620129,51.963434],"snapped_distance":11.16},{"location":[7.628171,51.963316],"snapped_distance":17.14},{"location":[7.626464,51.960516],"snapped_distance":6.06},{"location":[7.618201,51.966395],"snapped_distance":6.12},{"location":[7.616714,51.955482],"snapped_distance":6.84},{"location":[7.610224,51.964556],"snapped_distance":4.81},{"location":[7.626263,51.959492],"snapped_distance":4.67},{"location":[7.626421,51.959482],"snapped_distance":5.82},{"location":[7.622588,51.970132],"snapped_distance":9.78},{"location":[7.626253,51.959492],"snapped_distance":12.23},{"location":[7.622557,51.960358],"snapped_distance":3.93},{"location":[7.615162,51.968708],"snapped_distance":12.51},{"location":[7.627213,51.963953],"snapped_distance":6.22},{"location":[7.61796,51.96317],"snapped_distance":4.51},{"location":[7.628276,51.959628],"snapped_distance":7.9},{"location":[7.628573,51.961148],"snapped_distance":1.89},{"location":[7.626432,51.960546],"snapped_distance":4.89},{"location":[7.627333,51.959153],"snapped_distance":6.58},{"location":[7.602917,51.959452],"snapped_distance":12.46},{"location":[7.598043,51.961508],"snapped_distance":41.96},{"location":[7.61064,51.972053],"snapped_distance":7.52},{"location":[7.612331,51.952518],"snapped_distance":3.94},{"location":[7.618565,51.962958],"snapped_distance":5.48},{"location":[7.627067,51.96577],"snapped_distance":12.64},{"location":[7.623369,51.964555],"snapped_distance":7.86},{"location":[7.628906,51.967312],"snapped_distance":8.21},{"location":[7.628019,51.963092],"snapped_distance":3.59},{"location":[7.603247,51.965996],"snapped_distance":5.47},{"location":[7.626293,51.959883],"snapped_distance":16.33},{"location":[7.613742,51.971188],"snapped_distance":16.32},{"location":[7.626703,51.960292],"snapped_distance":13.72},{"location":[7.620119,51.963432],"snapped_distance":7.33},{"location":[7.626151,51.958321],"snapped_distance":8.73},{"location":[7.625486,51.962106],"snapped_distance":13.36},{"location":[7.625054,51.960546],"snapped_distance":9.54},{"location":[7.625973,51.958766],"snapped_distance":9.88},{"location":[7.624015,51.9664],"snapped_distance":14.93},{"location":[7.61809,51.962237],"snapped_distance":9.47},{"location":[7.621015,51.970142],"snapped_distance":8.25},{"location":[7.621908,51.958871],"snapped_distance":14.36},{"location":[7.625971,51.96142],"snapped_distance":6.43},{"location":[7.626652,51.964937],"snapped_distance":11.77},{"location":[7.62085,51.962166],"snapped_distance":15.38},{"location":[7.610676,51.953119],"snapped_distance":6.27},{"location":[7.629164,51.960427],"snapped_distance":3.14},{"location":[7.599961,51.965167],"snapped_distance":5.82},{"location":[7.621265,51.963751],"snapped_distance":8.72},{"location":[7.619134,51.96517],"snapped_distance":12.93},{"location":[7.622479,51.972225],"snapped_distance":12.94},{"location":[7.627424,51.961838],"snapped_distance":8.22},{"location":[7.625725,51.962087],"snapped_distance":13.22},{"location":[7.623635,51.960549],"snapped_distance":9.55},{"location":[7.622043,51.96862],"snapped_distance":12.06},{"location":[7.627634,51.960853],"snapped_distance":0.16}],"sources":[{"location":[7.612824,51.963635],"snapped_distance":0.64}],"metadata":{"attribution":"openrouteservice.org | OpenStreetMap contributors","service":"matrix","timestamp":1743078653774,"query":{"locations":[[7.610429,51.967839],[7.627296,51.960045],[7.61323,51.968324],[7.620167,51.963336],[7.628404,51.963373],[7.62639,51.960486],[7.618128,51.966363],[7.61678,51.955436],[7.610157,51.964569],[7.626256,51.95945],[7.626413,51.95943],[7.622595,51.970044],[7.626235,51.959383],[7.622607,51.960341],[7.615033,51.968788],[7.627175,51.964004],[7.618026,51.963168],[7.628389,51.959613],[7.628547,51.961154],[7.626372,51.960522],[7.627238,51.959161],[7.603077,51.959505],[7.597829,51.961154],[7.61055,51.972014],[7.612293,51.952544],[7.618595,51.963004],[7.626887,51.965795],[7.623465,51.964594],[7.628791,51.967291],[7.628029,51.96306],[7.603274,51.966042],[7.626422,51.960006],[7.613973,51.971225],[7.626536,51.960224],[7.620144,51.963368],[7.62603,51.958297],[7.625461,51.961987],[7.624915,51.960543],[7.625832,51.958746],[7.624067,51.96627],[7.61812,51.96232],[7.620895,51.970133],[7.622075,51.958793],[7.625878,51.961425],[7.626765,51.965017],[7.620929,51.962295],[7.610729,51.953073],[7.629119,51.960431],[7.599961,51.965219],[7.621319,51.96368],[7.619153,51.965286],[7.622336,51.972149],[7.627463,51.961908],[7.6257,51.961969],[7.623672,51.960466],[7.622219,51.968612],[7.627632,51.960853],[7.61282,51.96363]],"profile":"foot-walking","profileName":"foot-walking","responseType":"json","sources":["57"],"destinations":["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56"]},"engine":{"version":"9.1.1","build_date":"2025-03-14T11:07:03Z","graph_date":"2025-03-19T23:54:28Z"}}}
{'durations': [[694.68, 898.18, 576.89, 437.54, 932.15, 841.57, 480.02, 895.72, 165.85, 918.12, 925.99, 905.94, 917.62, 854.84, 617.94, 868.43, 353.34, 978.13, 984.38, 838.67, 986.11, 766.48, 1152.27, 931.03, 1207.65, 390.23, 910.78, 662.47, 965.32, 952.92, 775.47, 883.76, 826.47, 863.05, 437.03, 1006.77, 813.98, 771.23, 970.04, 707.08, 381.26, 834.63, 775.9, 873.99, 876.51, 548.47, 1208.87, 1026.43, 840.25, 499.47, 429.89, 1061.13, 912.27, 825.86, 727.15, 750.22, 927.44]], 'destinations': [{'location': [7.610283, 51.967753], 'snapped_distance': 13.85}, {'location': [7.627326, 51.960136], 'snapped_distance': 10.3}, {'location': [7.613287, 51.968282], 'snapped_distance': 6.08}, {'location': [7.620129, 51.963434], 'snapped_distance': 11.16}, {'location': [7.628171, 51.963316], 'snapped_distance': 17.14}, {'location': [7.626464, 51.960516], 'snapped_distance': 6.06}, {'location': [7.618201, 51.966395], 'snapped_distance': 6.12}, {'location': [7.616714, 51.955482], 'snapped_distance': 6.84}, {'location': [7.610224, 51.964556], 'snapped_distance': 4.81}, {'location': [7.626263, 51.959492], 'snapped_distance': 4.67}, {'location': [7.626421, 51.959482], 'snapped_distance': 5.82}, {'location': [7.622588, 51.970132], 'snapped_distance': 9.78}, {'location': [7.626253, 51.959492], 'snapped_distance': 12.23}, {'location': [7.622557, 51.960358], 'snapped_distance': 3.93}, {'location': [7.615162, 51.968708], 'snapped_distance': 12.51}, {'location': [7.627213, 51.963953], 'snapped_distance': 6.22}, {'location': [7.61796, 51.96317], 'snapped_distance': 4.51}, {'location': [7.628276, 51.959628], 'snapped_distance': 7.9}, {'location': [7.628573, 51.961148], 'snapped_distance': 1.89}, {'location': [7.626432, 51.960546], 'snapped_distance': 4.89}, {'location': [7.627333, 51.959153], 'snapped_distance': 6.58}, {'location': [7.602917, 51.959452], 'snapped_distance': 12.46}, {'location': [7.598043, 51.961508], 'snapped_distance': 41.96}, {'location': [7.61064, 51.972053], 'snapped_distance': 7.52}, {'location': [7.612331, 51.952518], 'snapped_distance': 3.94}, {'location': [7.618565, 51.962958], 'snapped_distance': 5.48}, {'location': [7.627067, 51.96577], 'snapped_distance': 12.64}, {'location': [7.623369, 51.964555], 'snapped_distance': 7.86}, {'location': [7.628906, 51.967312], 'snapped_distance': 8.21}, {'location': [7.628019, 51.963092], 'snapped_distance': 3.59}, {'location': [7.603247, 51.965996], 'snapped_distance': 5.47}, {'location': [7.626293, 51.959883], 'snapped_distance': 16.33}, {'location': [7.613742, 51.971188], 'snapped_distance': 16.32}, {'location': [7.626703, 51.960292], 'snapped_distance': 13.72}, {'location': [7.620119, 51.963432], 'snapped_distance': 7.33}, {'location': [7.626151, 51.958321], 'snapped_distance': 8.73}, {'location': [7.625486, 51.962106], 'snapped_distance': 13.36}, {'location': [7.625054, 51.960546], 'snapped_distance': 9.54}, {'location': [7.625973, 51.958766], 'snapped_distance': 9.88}, {'location': [7.624015, 51.9664], 'snapped_distance': 14.93}, {'location': [7.61809, 51.962237], 'snapped_distance': 9.47}, {'location': [7.621015, 51.970142], 'snapped_distance': 8.25}, {'location': [7.621908, 51.958871], 'snapped_distance': 14.36}, {'location': [7.625971, 51.96142], 'snapped_distance': 6.43}, {'location': [7.626652, 51.964937], 'snapped_distance': 11.77}, {'location': [7.62085, 51.962166], 'snapped_distance': 15.38}, {'location': [7.610676, 51.953119], 'snapped_distance': 6.27}, {'location': [7.629164, 51.960427], 'snapped_distance': 3.14}, {'location': [7.599961, 51.965167], 'snapped_distance': 5.82}, {'location': [7.621265, 51.963751], 'snapped_distance': 8.72}, {'location': [7.619134, 51.96517], 'snapped_distance': 12.93}, {'location': [7.622479, 51.972225], 'snapped_distance': 12.94}, {'location': [7.627424, 51.961838], 'snapped_distance': 8.22}, {'location': [7.625725, 51.962087], 'snapped_distance': 13.22}, {'location': [7.623635, 51.960549], 'snapped_distance': 9.55}, {'location': [7.622043, 51.96862], 'snapped_distance': 12.06}, {'location': [7.627634, 51.960853], 'snapped_distance': 0.16}], 'sources': [{'location': [7.612824, 51.963635], 'snapped_distance': 0.64}], 'metadata': {'attribution': 'openrouteservice.org | OpenStreetMap contributors', 'service': 'matrix', 'timestamp': 1743078653774, 'query': {'locations': [[7.610429, 51.967839], [7.627296, 51.960045], [7.61323, 51.968324], [7.620167, 51.963336], [7.628404, 51.963373], [7.62639, 51.960486], [7.618128, 51.966363], [7.61678, 51.955436], [7.610157, 51.964569], [7.626256, 51.95945], [7.626413, 51.95943], [7.622595, 51.970044], [7.626235, 51.959383], [7.622607, 51.960341], [7.615033, 51.968788], [7.627175, 51.964004], [7.618026, 51.963168], [7.628389, 51.959613], [7.628547, 51.961154], [7.626372, 51.960522], [7.627238, 51.959161], [7.603077, 51.959505], [7.597829, 51.961154], [7.61055, 51.972014], [7.612293, 51.952544], [7.618595, 51.963004], [7.626887, 51.965795], [7.623465, 51.964594], [7.628791, 51.967291], [7.628029, 51.96306], [7.603274, 51.966042], [7.626422, 51.960006], [7.613973, 51.971225], [7.626536, 51.960224], [7.620144, 51.963368], [7.62603, 51.958297], [7.625461, 51.961987], [7.624915, 51.960543], [7.625832, 51.958746], [7.624067, 51.96627], [7.61812, 51.96232], [7.620895, 51.970133], [7.622075, 51.958793], [7.625878, 51.961425], [7.626765, 51.965017], [7.620929, 51.962295], [7.610729, 51.953073], [7.629119, 51.960431], [7.599961, 51.965219], [7.621319, 51.96368], [7.619153, 51.965286], [7.622336, 51.972149], [7.627463, 51.961908], [7.6257, 51.961969], [7.623672, 51.960466], [7.622219, 51.968612], [7.627632, 51.960853], [7.61282, 51.96363]], 'profile': 'foot-walking', 'profileName': 'foot-walking', 'responseType': 'json', 'sources': ['57'], 'destinations': ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56']}, 'engine': {'version': '9.1.1', 'build_date': '2025-03-14T11:07:03Z', 'graph_date': '2025-03-19T23:54:28Z'}}}
Make this Notebook Trusted to load map: File -> Trust Notebook

Jetzt kann als letzter Schritt eine Route zum nächsten POI berechnet werden.

body = {"coordinates":[startpoint,nearest_poi]}

directions_response = requests.post('https://api.openrouteservice.org/v2/directions/foot-walking/geojson', json=body, headers=headers)
directions_geojson = directions_response.json()
print("startpoint", startpoint)
print("nearest_poi", nearest_poi)
print("Directions Response", directions_geojson)
m = folium.Map(startpoint_reverse, zoom_start=14)
folium.map.Marker(location=nearest_poi_reverse).add_to(m)
folium.map.Marker(location=startpoint_reverse).add_to(m)

folium.GeoJson(directions_geojson).add_to(m)
m
startpoint [7.61282, 51.96363]
nearest_poi [7.610157, 51.964569]
Directions Response {'type': 'FeatureCollection', 'features': [{'bbox': [7.610224, 51.963635, 7.612824, 51.964735], 'type': 'Feature', 'properties': {'segments': [{'distance': 230.3, 'duration': 165.8, 'steps': [{'distance': 206.5, 'duration': 148.7, 'type': 11, 'instruction': 'Head northwest', 'name': '-', 'way_points': [0, 8]}, {'distance': 23.9, 'duration': 17.2, 'type': 0, 'instruction': 'Turn left', 'name': '-', 'way_points': [8, 9]}, {'distance': 0.0, 'duration': 0.0, 'type': 10, 'instruction': 'Arrive at your destination, on the right', 'name': '-', 'way_points': [9, 9]}]}], 'summary': {'distance': 230.3, 'duration': 165.8}, 'way_points': [0, 9]}, 'geometry': {'coordinates': [[7.612824, 51.963635], [7.612749, 51.963658], [7.612655, 51.963689], [7.611862, 51.963971], [7.61162, 51.964069], [7.61135, 51.964221], [7.611049, 51.96438], [7.610985, 51.964418], [7.610416, 51.964735], [7.610224, 51.964556]], 'type': 'LineString'}}], 'bbox': [7.610224, 51.963635, 7.612824, 51.964735], 'metadata': {'attribution': 'openrouteservice.org | OpenStreetMap contributors', 'service': 'routing', 'timestamp': 1743078653942, 'query': {'coordinates': [[7.61282, 51.96363], [7.610157, 51.964569]], 'profile': 'foot-walking', 'profileName': 'foot-walking', 'format': 'geojson'}, 'engine': {'version': '9.1.1', 'build_date': '2025-03-14T11:07:03Z', 'graph_date': '2025-03-19T23:54:28Z'}}}
Make this Notebook Trusted to load map: File -> Trust Notebook