3.8 KiB
3.8 KiB
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
pip install -r requirements.txt
python app.py
Then open http://localhost:5000.
Run with Docker
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.