84 lines
3.8 KiB
Markdown
84 lines
3.8 KiB
Markdown
# 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 <http://localhost:5000>.
|
|
|
|
### 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`. |