# LibreMaps A lightweight, self-hosted map viewer built with **Flask** and **Leaflet**, powered entirely by free open data from **OpenStreetMap** (via the Nominatim geocoding API) and **MapTiler** street tiles. ## Features - **Interactive map** — pan/zoom over MapTiler "Streets" tiles, centered on Amsterdam by default. - **Search** — type a place or POI to search. Results are first restricted to the area currently visible on the map; if nothing is found locally, it automatically falls back to searching the entire world. - **Category shortcuts** — quick chips for common places (Supermarkt, Café, Kroeg, Camping, Park, Restaurant, Vegan, Hotel, Parkeerplek). - **Typed pins** — each result is drawn as a colored circular badge whose emoji and color reflect the POI type (e.g. 🍴 orange for food, 🛏 purple for hotels, 🚆 blue for transport, 💊 red for pharmacies, 🌳 green for parks). A small permanent label shows the location name above each pin. - **Place details card** — click a result or a pin to see the name, type, address, opening hours, phone, website, email, and extra tags (cuisine, brand, wheelchair access, payment methods, etc.). - **Actions** — get directions (Google Maps), web search (Qwant), call, visit website, or email directly from the detail card. - **Reverse geocoding** — click anywhere on the map (when zoomed in enough) to look up what's at that spot. - **"My location"** button using browser geolocation. - **Responsive layout** — adapts to mobile screens. ## Tech Stack | Layer | Technology | |-----------|-------------------------------------| | Backend | Python 3, Flask | | Frontend | Vanilla JS, Leaflet 1.9.4 | | Geocoding | OpenStreetMap Nominatim | | Tiles | MapTiler Streets v4 | ## Project Structure ``` openmaps/ ├── app.py # Flask app: /, /search, /geocode routes ├── templates/ │ └── index.html # Single-page UI (map + all client logic) ├── sync.py # Sync helper script ├── requirements.txt # Python dependencies ├── Dockerfile # Container image definition ├── docker-compose.yml # Local container orchestration ├── .dockerignore └── .gitignore ``` ## How It Works ### Backend (`app.py`) Three endpoints proxy requests to Nominatim while applying a simple rate limit (minimum 1 second between calls) and a descriptive `User-Agent`: - `GET /` — serves the single-page UI. - `GET /search?q=...&viewbox=left,top,right,bottom` — forwards a forward-geocoding query. When a valid viewbox is supplied, results are bounded to that region (`bounded=1`). Returns normalized JSON (coordinates, address, extratags, namedetails, importance, bounding box). - `GET /geocode?lat=...&lon=...` — reverse-geocodes a coordinate into a place record. ### Frontend (`templates/index.html`) - Initializes a Leaflet map with MapTiler tiles. - Builds a viewbox from the current map bounds and sends it with each search so results stay local. - Renders results in a scrollable card and drops typed pins on the map. - Falls back to a worldwide search when the local search returns nothing. - Shows a detail card with contact info, opening hours, and action buttons. ## Getting Started ### Prerequisites - Python 3.x - (Optional) Docker / Docker Compose ### Run locally ```bash pip install -r requirements.txt python app.py ``` Then open . ### Run with Docker ```bash docker compose up --build ``` ## Notes - The MapTiler tile layer uses an embedded API key in `templates/index.html`; replace it with your own key for production use. - Nominatim has usage policies — keep the built-in rate limiting in place and identify your application via the `User-Agent`.