Initial commit: OpenMaps project

This commit is contained in:
2026-08-21 13:44:15 +02:00
commit 51f2eb8e1d
7 changed files with 2739 additions and 0 deletions

46
.dockerignore Normal file
View File

@@ -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

54
.gitignore vendored Normal file
View File

@@ -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/

33
Dockerfile Normal file
View File

@@ -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"]

219
app.py Normal file
View File

@@ -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
)

9
docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
openmaps:
build: .
container_name: openmaps
ports:
- "5000:5000"
environment:
- PORT=5000
restart: unless-stopped

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
Flask==3.0.3
requests==2.32.3
gunicorn==22.0.0

2375
templates/index.html Normal file
View File

File diff suppressed because it is too large Load Diff