commit 51f2eb8e1d0e5dfaa6dfbaaf79d567968eb09b3d Author: bramval Date: Fri Aug 21 13:44:15 2026 +0200 Initial commit: OpenMaps project diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..03b4abe --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +.venv/ +ENV/ +env.bak/ +venv.bak/ + +# Flask +instance/ +.webassets-cache +.well-known/cache + +# Git +.git/ +.gitignore +.gitattributes + +# Docker +Dockerfile +.dockerignore +docker-compose.yml +docker-compose.yaml + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Tests +tests/ +pytest.ini \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e4b682f --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +env/ +ENV/ +.venv/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Docker +.docker/ + +# Environment variables +.env +.env.local + +# Logs +*.log +logs/ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..125ce09 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +FROM python:3.12-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PORT=5000 + +# Set working directory +WORKDIR /app + +# Install system dependencies (if any needed) +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better layer caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY app.py . +COPY templates/ templates/ + +# Create a non-root user to run the app +RUN useradd -m appuser +USER appuser + +# Expose the port +EXPOSE 5000 + +# Run the app with gunicorn +CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT} --workers 2 --threads 4 app:app"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..a3b8907 --- /dev/null +++ b/app.py @@ -0,0 +1,219 @@ +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": 8, + "addressdetails": 1, + "extratags": 1, + "namedetails": 1, + "dedupe": 1, + } + + 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 + ) \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eb80432 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + openmaps: + build: . + container_name: openmaps + ports: + - "5000:5000" + environment: + - PORT=5000 + restart: unless-stopped \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1de67ec --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask==3.0.3 +requests==2.32.3 +gunicorn==22.0.0 \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..8dfae32 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,2375 @@ + + + + + + + LibreMaps + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ Zoom in closer to select a location +
+ + + + + + + + + + + + + + \ No newline at end of file