- Pass the currently visible map bounds as a Nominatim viewbox so results are restricted to what the user can see (bounded=1). - Increase default result limit from 8 to 20. - Enable the quick-search chip row with localized Dutch labels (Supermarkt, Café, Kroeg, Camping, Park, Restaurant, Vegan, Hotel, Parkeerplek).
240 lines
5.0 KiB
Python
240 lines
5.0 KiB
Python
from flask import Flask, render_template, request, jsonify
|
|
import requests
|
|
import time
|
|
|
|
app = Flask(__name__)
|
|
|
|
NOMINATIM_SEARCH_URL = "https://nominatim.openstreetmap.org/search"
|
|
NOMINATIM_REVERSE_URL = "https://nominatim.openstreetmap.org/reverse"
|
|
|
|
# Nominatim asks clients to identify themselves.
|
|
HEADERS = {
|
|
"User-Agent": "LibreMapViewer/1.0 (local Flask application)"
|
|
}
|
|
|
|
# Very simple rate limiting so we don't hammer Nominatim.
|
|
last_request_time = 0
|
|
MIN_REQUEST_INTERVAL = 1.0
|
|
|
|
|
|
def nominatim_request(url, params):
|
|
"""
|
|
Make a request to Nominatim while respecting a small
|
|
delay between requests.
|
|
"""
|
|
global last_request_time
|
|
|
|
elapsed = time.time() - last_request_time
|
|
|
|
if elapsed < MIN_REQUEST_INTERVAL:
|
|
time.sleep(MIN_REQUEST_INTERVAL - elapsed)
|
|
|
|
response = requests.get(
|
|
url,
|
|
params=params,
|
|
headers=HEADERS,
|
|
timeout=15
|
|
)
|
|
|
|
last_request_time = time.time()
|
|
|
|
response.raise_for_status()
|
|
|
|
return response.json()
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
return render_template("index.html")
|
|
|
|
|
|
@app.route("/search")
|
|
def search():
|
|
query = request.args.get("q", "").strip()
|
|
|
|
if not query:
|
|
return jsonify({
|
|
"error": "Please enter a location"
|
|
}), 400
|
|
|
|
params = {
|
|
"q": query,
|
|
"format": "jsonv2",
|
|
"limit": 20,
|
|
"addressdetails": 1,
|
|
"extratags": 1,
|
|
"namedetails": 1,
|
|
"dedupe": 1,
|
|
}
|
|
|
|
# Restrict results to the area currently visible on the map.
|
|
# Expected format: "left,top,right,bottom" (lon,lat pairs).
|
|
viewbox = request.args.get("viewbox", "").strip()
|
|
|
|
if viewbox:
|
|
parts = [p.strip() for p in viewbox.split(",")]
|
|
|
|
if len(parts) == 4:
|
|
try:
|
|
left, top, right, bottom = (
|
|
float(p) for p in parts
|
|
)
|
|
|
|
params["viewbox"] = "{},{},{},{}".format(
|
|
left, top, right, bottom
|
|
)
|
|
params["bounded"] = 1
|
|
|
|
except ValueError:
|
|
pass
|
|
|
|
try:
|
|
results = nominatim_request(
|
|
NOMINATIM_SEARCH_URL,
|
|
params
|
|
)
|
|
|
|
except requests.RequestException as e:
|
|
return jsonify({
|
|
"error": f"Search failed: {e}"
|
|
}), 500
|
|
|
|
normalized = []
|
|
|
|
for item in results:
|
|
|
|
normalized.append({
|
|
"place_id": item.get("place_id"),
|
|
|
|
"osm_type": item.get("osm_type"),
|
|
"osm_id": item.get("osm_id"),
|
|
|
|
"display_name":
|
|
item.get("display_name", ""),
|
|
|
|
"lat":
|
|
float(item["lat"]),
|
|
|
|
"lon":
|
|
float(item["lon"]),
|
|
|
|
"type":
|
|
item.get("type"),
|
|
|
|
"category":
|
|
item.get("category"),
|
|
|
|
"address":
|
|
item.get("address", {}),
|
|
|
|
"extratags":
|
|
item.get("extratags", {}),
|
|
|
|
"namedetails":
|
|
item.get("namedetails", {}),
|
|
|
|
"importance":
|
|
item.get("importance", 0),
|
|
|
|
"boundingbox":
|
|
item.get("boundingbox"),
|
|
})
|
|
|
|
return jsonify(normalized)
|
|
|
|
|
|
@app.route("/geocode")
|
|
def geocode():
|
|
|
|
lat = request.args.get("lat")
|
|
lon = request.args.get("lon")
|
|
|
|
if not lat or not lon:
|
|
return jsonify({
|
|
"error": "Missing latitude or longitude"
|
|
}), 400
|
|
|
|
try:
|
|
lat = float(lat)
|
|
lon = float(lon)
|
|
except ValueError:
|
|
return jsonify({
|
|
"error": "Invalid latitude or longitude"
|
|
}), 400
|
|
|
|
params = {
|
|
"lat": lat,
|
|
"lon": lon,
|
|
|
|
"format": "jsonv2",
|
|
|
|
"zoom": 18,
|
|
|
|
"addressdetails": 1,
|
|
|
|
"extratags": 1,
|
|
|
|
"namedetails": 1,
|
|
}
|
|
|
|
try:
|
|
|
|
result = nominatim_request(
|
|
NOMINATIM_REVERSE_URL,
|
|
params
|
|
)
|
|
|
|
except requests.RequestException as e:
|
|
|
|
return jsonify({
|
|
"error": f"Reverse geocoding failed: {e}"
|
|
}), 500
|
|
|
|
if not result:
|
|
|
|
return jsonify({
|
|
"display_name": None
|
|
})
|
|
|
|
return jsonify({
|
|
|
|
"place_id":
|
|
result.get("place_id"),
|
|
|
|
"osm_type":
|
|
result.get("osm_type"),
|
|
|
|
"osm_id":
|
|
result.get("osm_id"),
|
|
|
|
"display_name":
|
|
result.get("display_name", ""),
|
|
|
|
"lat":
|
|
float(result.get("lat", lat)),
|
|
|
|
"lon":
|
|
float(result.get("lon", lon)),
|
|
|
|
"type":
|
|
result.get("type"),
|
|
|
|
"category":
|
|
result.get("category"),
|
|
|
|
"address":
|
|
result.get("address", {}),
|
|
|
|
"extratags":
|
|
result.get("extratags", {}),
|
|
|
|
"namedetails":
|
|
result.get("namedetails", {}),
|
|
})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(
|
|
port=5000,
|
|
debug=True
|
|
) |