Files
LibMap/app.py
bramval 439d389a44 Add viewbox-bounded search and enable Dutch quick-search chips
- 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).
2026-08-21 14:10:30 +02:00

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
)