--- title: Prix des Carburants France emoji: ⛽ colorFrom: green colorTo: red sdk: docker app_port: 7860 pinned: false license: apache-2.0 --- # ⛽ Petrol Map — live French fuel prices (PWA) An installable, mobile-friendly map of every petrol station in France, colour-coded by price and refreshed twice a day from the official open-data feed. **Data source:** [`prix_des_carburants_j_7`](https://public.opendatasoft.com/explore/dataset/prix_des_carburants_j_7) on opendatasoft (the last 7 days of prices reported by ~9 800 stations). ## Features - **Installable PWA** — add it to your phone's home screen and launch it like a native app; works offline (shows the last cached prices) via a service worker. - **Responsive** — full-screen map with touch-friendly controls and safe-area handling for notched phones. - **One layer per fuel** — Gazole, SP95, SP98, E10, E85, GPLc — via the selector. - **Price colour scale** — green = cheaper, red = pricier, scaled to each fuel's national 5th–95th percentile; the legend updates per fuel. - **Search by postal code / city** — type e.g. `75011` or `Lyon` to filter the map to that area and zoom to it. - **Itinerary maker** — enter a *ville de départ* and *ville d'arrivée* (with city autocomplete via the Base Adresse Nationale); the route is drawn (OSRM) and the map shows only the stations along the way, flagging the cheapest for the fuel. - **Real vehicle picker + journey cost** — choose an actual model (≈550 from the ADEME database, with their exact WLTP L/100 km, hybrids included) to estimate the trip's fuel cost from the cheapest along-route price. - **Electric vehicles** — pick an EV (WLTP range), see the number of recharge stops needed and energy used, with stops placed at real chargers along the route. - **EV charging stations** — toggle the national IRVE layer (≈224 000 points, loaded live by viewport), colour-coded by power (fast ≥ 50 kW). - **Marker clustering** keeps ~10 000 stations smooth; zoom in to split clusters. - **Click a station** for all prices, address, last-reported date, and a one-click *Directions* link, plus hover tooltips. Data sources: fuel prices [`prix_des_carburants_j_7`](https://public.opendatasoft.com/explore/dataset/prix_des_carburants_j_7), vehicle consumption [ADEME Car Labelling](https://data.ademe.fr/datasets/ademe-car-labelling), chargers [IRVE via ODRE](https://odre.opendatasoft.com/explore/dataset/bornes-irve), geocoding [BAN](https://adresse.data.gouv.fr/), routing [OSRM](https://project-osrm.org/), basemap [IGN Plan v2](https://geoservices.ign.fr/). ## Run locally ```bash pip install -r requirements.txt python app.py # builds, then serves http://localhost:7860 ``` Open — on a phone (same network or behind HTTPS) you can then "Add to Home Screen" to install it. Other entrypoints: ```bash python build_map.py # build output/index.html once, no server python scheduler.py # rebuild twice daily without serving (for static hosting) python scheduler.py --once ``` > **PWA note:** install/offline features need a secure context — i.e. `https://` > or `http://localhost`. Opening the file directly (`file://`) shows the map but > not the installable app. ## Deploy to Hugging Face Spaces (Docker) 1. Create a new Space → **SDK: Docker**. 2. Push these files to the Space repo (the YAML header above configures it): ```bash git init && git add . && git commit -m "Petrol map PWA" git remote add origin https://huggingface.co/spaces// git push origin main ``` 3. The Space builds the `Dockerfile`, serves on port `7860`, and rebuilds the prices twice a day. The public Space URL is HTTPS, so the PWA is installable straight from your phone. Run the same container anywhere else: ```bash docker build -t petrol-map . docker run -p 7860:7860 petrol-map # http://localhost:7860 ``` ## Files **Backend (Python, build/serve):** | File | Purpose | |------|---------| | `fuel_data.py` | Fetch + normalise fuel prices (drops implausible values) | | `car_data.py` | Fetch real vehicle models + WLTP L/100 km from ADEME | | `build_map.py` | Inject the dataset into the HTML shell, copy static assets | | `app.py` | Build + serve on `$PORT` + twice-daily refresh (container entrypoint) | | `scheduler.py` | Twice-daily rebuild without serving | | `generate_icons.py` | One-off PWA icon generator (needs Pillow; not used at runtime) | | `Dockerfile` | Container image for Hugging Face Spaces / anywhere | **Frontend (served from `static/`, separated for clean architecture):** | File | Purpose | |------|---------| | `map_template.html` | Slim HTML shell; injects the build's data as `window.APP_DATA` | | `static/app.js` | All map logic (one IIFE, sectioned by concern) | | `static/styles.css` | All styles | | `static/sw.js` | Service worker (offline + installable) | | `static/manifest.webmanifest`, icons | PWA metadata | | `output/` | Generated site (`index.html` + copied static assets + `stations.json`) | ## How the refresh works The upstream dataset is updated continuously by the French administration. `app.py` re-fetches the full feed and regenerates the page twice daily (07:00 and 19:00, container local time — set `TZ`), so the map always reflects a recent snapshot. Change `REFRESH_TIMES` to refresh on a different cadence. ## License Licensed under the [Apache License, Version 2.0](LICENSE) — see the `LICENSE` file for the full text. © 2026 Samuel Adone. Open data used by this project remains under its own licences (Licence Ouverte / Etalab for the French government feeds).