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 )