Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
2ef3539
fix: enable route button when text is typed, geocode on submit
GeiserX Mar 15, 2026
0486f63
fix: move banner accent line and text higher to avoid overlap with ro…
GeiserX Mar 15, 2026
f59a242
docs: add spacing before horizontal rule in README
GeiserX Mar 15, 2026
9d0b589
docs: update AGENTS.md with Phase 1 details, comprehensive self-hosti…
GeiserX Mar 15, 2026
738764c
docs: update Photon to use country-specific Spain dump
GeiserX Mar 16, 2026
8f494c2
fix: add latest tag for main branch Docker builds
GeiserX Mar 16, 2026
2c596d3
fix: disable station clustering to show individual points at all zoom…
GeiserX Mar 16, 2026
76167ec
fix: re-enable clustering only at low zoom levels (maxZoom 7)
GeiserX Mar 16, 2026
2afce3f
fix: render route line below station points for better visibility
GeiserX Mar 16, 2026
ed1b73c
feat: clustering behind PROPEL_CLUSTER_STATIONS env var, fix dropdown…
GeiserX Mar 16, 2026
978a2f2
feat: automatic scraper via PROPEL_SCRAPE_INTERVAL_HOURS env var
GeiserX Mar 16, 2026
f8ecc38
feat: auto-geolocate on load, default scraper interval to 24h
GeiserX Mar 16, 2026
4619b13
fix: auto-geolocate on map load, search panel expanded by default
GeiserX Mar 16, 2026
d79da96
fix: always fetch stations on load, don't block on geolocation
GeiserX Mar 16, 2026
7c555f4
fix: remove min zoom gate and reduce debounce for snappier station lo…
GeiserX Mar 16, 2026
fb5e634
fix: use Permissions API to avoid double station fetch on load
GeiserX Mar 16, 2026
7cc8764
docs: update AGENTS.md with env vars, design decisions, and infrastru…
GeiserX Mar 16, 2026
0d8e6d4
feat: redesign search panel to Google Maps-style UX
GeiserX Mar 16, 2026
bde7822
fix: select first autocomplete result on Enter when dropdown is open
GeiserX Mar 16, 2026
9ae9ae6
feat: unified search card with connected origin/destination fields
GeiserX Mar 16, 2026
b98226c
fix: use merged ref so parent can call flyTo/fitBounds on the map
GeiserX Mar 16, 2026
61c0cf1
fix: destination autocomplete dropdown clipped + softer icon
GeiserX Mar 16, 2026
9323266
fix: always show all bbox stations, even when route is active
GeiserX Mar 16, 2026
a21d83b
fix: revert to object ref for Map, restore corridor filtering, add logs
GeiserX Mar 16, 2026
8a0eb58
feat: add server-side logging to all API endpoints
GeiserX Mar 16, 2026
19e6dce
fix: always fetch stations on map load, don't skip for granted geoloc…
GeiserX Mar 16, 2026
d61eab1
fix: separate cluster/non-cluster render paths, handle null prices in…
GeiserX Mar 16, 2026
952b89a
fix: render route layer below all station layers (including clusters)
GeiserX Mar 16, 2026
03c5d6a
fix: force dynamic rendering so runtime env vars are respected
GeiserX Mar 16, 2026
a9982c3
feat: complete Phase 1 — alternative routes, waypoints, swap, station…
GeiserX Mar 16, 2026
cfb7f2b
fix: cast match expression through unknown for ExpressionSpecification
GeiserX Mar 16, 2026
e5bc640
docs: update ROADMAP with completed Phase 1 items
GeiserX Mar 16, 2026
93d3a18
fix: replace amber alt route color with pink for road contrast
GeiserX Mar 16, 2026
31435f4
feat: open station popup from list click, filter list by max price
GeiserX Mar 16, 2026
2e8b65b
feat: add PROPEL_CORRIDOR_KM env var for station search radius
GeiserX Mar 16, 2026
cbffa0c
feat: detour estimation with slider filter and best deal highlight
GeiserX Mar 16, 2026
faa03c4
docs: update ROADMAP with Phase 2 detour progress
GeiserX Mar 16, 2026
9f74473
fix: keep slider visible when filter empties list, add 3 distinct badges
GeiserX Mar 16, 2026
2704f24
feat: i18n language selector and translated UI strings
GeiserX Mar 16, 2026
373e841
feat: add scrapers for France, Portugal, Italy, Austria
GeiserX Mar 16, 2026
29fd3fc
fix(austria): query by 117 districts instead of 9 states for 10x more…
GeiserX Mar 16, 2026
e460bfe
docs: update ROADMAP to reflect Phase 3 multi-country progress
GeiserX Mar 16, 2026
76a8470
feat: optimize France scraper (bulk export) + add Germany scraper
GeiserX Mar 16, 2026
6183735
feat: per-country scrape intervals with sensible defaults
GeiserX Mar 16, 2026
c1aee1a
docs: update AGENTS.md and ROADMAP.md for multi-country state
GeiserX Mar 16, 2026
9252182
docs: add detailed EV charging research to ROADMAP Phase 5
GeiserX Mar 16, 2026
6b50a50
docs: add comprehensive EU fuel price API research to ROADMAP
GeiserX Mar 16, 2026
ff152e7
docs: fix Photon approach — use per-country dumps, not planet dump
GeiserX Mar 16, 2026
148c39d
fix(austria): exclude closed stations that have no prices
GeiserX Mar 16, 2026
3cd4fd2
docs: update Valhalla RAM requirement to 24GB (OOMs at 16GB with 5 PBFs)
GeiserX Mar 16, 2026
c1f12fa
feat: add UK (CMA) and Slovenia (goriva.si) scrapers
GeiserX Mar 17, 2026
b03310d
fix: convert UK prices from pence to pounds and add currency-aware di…
GeiserX Mar 17, 2026
a8918b3
docs: update for 8-country deployment (68K stations)
GeiserX Mar 17, 2026
9381786
feat: add Netherlands, Belgium, and Luxembourg scrapers (ANWB API)
GeiserX Mar 17, 2026
5e91890
feat: add Romania scraper + fix EURO98 fuel type mapping
GeiserX Mar 17, 2026
cc2c9ef
feat: add Greece scraper (FuelGR API, grid-based)
GeiserX Mar 17, 2026
3676772
feat: add Ireland scraper (Pick A Pump API, 14th country)
GeiserX Mar 17, 2026
89a91bb
feat: add Croatia scraper (MZOE government API, 15th country)
GeiserX Mar 17, 2026
757321c
fix: use comprehensive currency symbol map in popup and station list
GeiserX Mar 17, 2026
51be8e5
fix: transaction timeout + ANWB source conflict + Austria grid scraper
GeiserX Mar 17, 2026
145f5cf
fix: add 8 missing countries to config + stats flags, update ROADMAP
GeiserX Mar 17, 2026
e04a5fc
feat: auto-detect browser locale, savings comparison, i18n fixes
GeiserX Mar 17, 2026
f18a815
feat: internationalize popup and price filter strings
GeiserX Mar 17, 2026
14fe5c4
feat: internationalize geolocate button and fuel category labels
GeiserX Mar 17, 2026
4cbb5d8
fix: mobile-responsive search panel and geolocate button
GeiserX Mar 17, 2026
b62f62c
docs: update ROADMAP with completed i18n and savings items
GeiserX Mar 17, 2026
638f29b
feat: add "Navigate here" button to station popup
GeiserX Mar 17, 2026
c3e6fc9
docs: update .env.example with all 15 countries and corridor setting
GeiserX Mar 17, 2026
d293b59
docs: update README for 15-country, 84K+ station coverage
GeiserX Mar 17, 2026
50dc33d
feat: currency conversion with 90+ world currencies and ECB daily rates
GeiserX Mar 17, 2026
757bce1
fix: limit currency dropdown to 31 ECB-supported currencies only
GeiserX Mar 18, 2026
bb5fbdd
fix: derive display currency from station data, not global selection
GeiserX Mar 18, 2026
5943d5d
fix: filter out stations with no prices and reject bad price data
GeiserX Mar 18, 2026
1057c92
feat: make price validation bounds configurable via env vars
GeiserX Mar 18, 2026
371ea2d
fix: only show stations that have the selected fuel type
GeiserX Mar 18, 2026
c61d9ac
feat: fixed route colors, station sort options, 30min detour slider
GeiserX Mar 18, 2026
b0d8f52
feat: add 11 new country scrapers (CH, PL, CZ, HU, BG, SK, DK, SE, NO…
GeiserX Mar 18, 2026
475b043
fix: fix HTML parsing for Finland, Serbia, and Norway scrapers
GeiserX Mar 18, 2026
04f71a2
fix: Norway prices iteration guard + widen non-EUR price filter to 250x
GeiserX Mar 18, 2026
ccb4b5d
fix: add Array.isArray guard for DrivstoffAppen prices in DK and SE s…
GeiserX Mar 18, 2026
d7863be
feat: price-colored clusters + disable clustering during route view
GeiserX Mar 18, 2026
ff93da5
fix: restrict min zoom to 5, fix cluster color null division, cap sta…
GeiserX Mar 18, 2026
9449305
feat: blue dot for user location + responsive mobile navbar
GeiserX Mar 18, 2026
bb157cd
fix: remove lang param from Photon geocoding for public API compat
GeiserX Mar 18, 2026
5c7cc76
fix: force source remount when switching cluster mode on/off
GeiserX Mar 18, 2026
a5766d1
style: widen primary route line (outline 10px, fill 6px)
GeiserX Mar 18, 2026
695b75f
feat: dynamic corridor distance slider (1-25km) in route view
GeiserX Mar 18, 2026
65a1fce
refactor: remove PROPEL_CORRIDOR_KM env var, hardcode default 5km
GeiserX Mar 18, 2026
081a4f2
perf: debounce corridor slider to avoid fetch on every tick
GeiserX Mar 18, 2026
3df4544
feat: add 5 new country scrapers (EE, LV, LT, BA, MK)
GeiserX Mar 18, 2026
da88cef
feat: move stats to top-right map overlay, collapsible search on mobile
GeiserX Mar 18, 2026
45d58d3
feat: add dark/light mode toggle with dynamic map style
GeiserX Mar 18, 2026
ef5c632
fix: reset price filter on fuel change, auto-geolocate, stats dark mode
GeiserX Mar 18, 2026
d2f5198
refactor: move geolocate, theme, stats buttons to navbar
GeiserX Mar 18, 2026
2111e50
fix: reset price filter slider on currency change
GeiserX Mar 19, 2026
385ef31
feat: add Turkey, Moldova, Australia (WA) scrapers
GeiserX Mar 19, 2026
27cbbe8
fix: Safari mobile viewport - safe areas, theme color, dvh
GeiserX Mar 19, 2026
a921199
fix: Moldova scraper duplicate external IDs
GeiserX Mar 19, 2026
83e9b00
feat: add Argentina and Mexico scrapers, widen price column
GeiserX Mar 19, 2026
7742bb3
feat: add ARS and MDL currencies for Argentina and Moldova
GeiserX Mar 19, 2026
ff1021c
feat: add TR, MD, AU, AR, MX to country config
GeiserX Mar 19, 2026
5d6d8dd
fix: parse per-price currency from Fuelo, add NSW scraper, world zoom
GeiserX Mar 19, 2026
c238b14
Fix 4 mobile UI issues: iOS zoom, navbar overflow, collapse visibilit…
GeiserX Mar 19, 2026
8db85ea
build(deps): bump next in the npm_and_yarn group across 1 directory
dependabot[bot] Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,37 @@ DATABASE_URL=postgresql://propel:propel@localhost:5433/propel
# ---------------------------------------------------------------------------

# ISO 3166-1 alpha-2 code for initial map view (default: ES)
# Supported: ES, FR, DE, IT, GB, AT, PT
# Supported: ES, FR, DE, IT, GB, AT, PT, SI, NL, BE, LU, RO, GR, IE, HR
PROPEL_DEFAULT_COUNTRY=ES

# Comma-separated ISO codes to enable (default: all countries with scrapers)
# Example: ES,FR,DE — limits the app to only those countries
PROPEL_ENABLED_COUNTRIES=ES
# PROPEL_ENABLED_COUNTRIES=ES,FR,PT,IT,AT,DE,GB,SI,NL,BE,LU,RO,GR,IE,HR

# Override default fuel type (default: per-country, e.g. B7 for Spain)
# See src/types/fuel.ts for valid codes
# PROPEL_DEFAULT_FUEL=B7

# Enable station clustering at low zoom levels (default: true)
# Set to false to always show individual station points
# PROPEL_CLUSTER_STATIONS=true

# Station search corridor width in km for route planning (default: 5, range: 0.5-50)
# PROPEL_CORRIDOR_KM=5

# Price sanity bounds for scraper validation (EUR/GBP/CHF per litre)
# Prices outside this range are rejected as bad data. Other currencies use 25x max.
# PROPEL_PRICE_MIN=0.30
# PROPEL_PRICE_MAX=4.00

# ---------------------------------------------------------------------------
# Scraper Schedule
# ---------------------------------------------------------------------------

# Automatic scraping interval in hours (default: 24)
# Runs once on startup + repeats on this interval. Set to 0 to disable.
# PROPEL_SCRAPE_INTERVAL_HOURS=24

# ---------------------------------------------------------------------------
# External Services (optional — only needed when those features are enabled)
# ---------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ jobs:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
security-events: write
contents: read

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
with:
images: ${{ env.DOCKER_IMAGE }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=semver,pattern={{version}}
type=semver,pattern={{raw}}
type=raw,value=${{ github.event.inputs.tag }},enable=${{ github.event.inputs.tag != '' }}
Expand Down
151 changes: 118 additions & 33 deletions AGENTS.md

Large diffs are not rendered by default.

129 changes: 109 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,144 @@
<a href="https://hub.docker.com/r/drumsergio/propel"><img src="https://img.shields.io/docker/pulls/drumsergio/propel?style=flat-square&logo=docker&label=Docker%20Pulls" alt="Docker Pulls"></a>
</p>

<br>

---

**No app in the world combines route planning with real-time energy prices, detour time calculation, and smart refueling recommendations. Propel does.**

## Features

- Real-time fuel prices from government open data APIs (Spain, France, Germany, Italy, UK, Austria, Portugal)
- Interactive map with 65,000+ stations across Europe
- Route planning with energy station filtering along the route
- "Cheapest within N minutes detour" — calculates if a detour is worth it
- Smart refuel/recharge advisor based on your remaining range
- Real-time fuel prices from government open data APIs across 15 European countries
- Interactive map with 84,000+ stations across Europe
- Route planning with geocoding autocomplete, alternative routes, and corridor station filtering
- "Cheapest within N minutes detour" — filters stations by detour time with price comparison
- Smart refuel/recharge advisor based on your remaining range (planned)
- EV charging integration (planned)
- Multi-language, multi-currency, multi-unit support
- Self-hostable with Docker Compose
- Fully self-hostable with Docker Compose
- 100% open source (GPL-3.0)

## Quick Start
## Self-Hosting

### Requirements

| Resource | First-time build | Steady state |
|---|---|---|
| **RAM** | 24 GB (Valhalla 15-country tile build) | 6-8 GB |
| **Disk** | ~250 GB (15-country Photon + Valhalla) | ~80 GB |
| **CPU** | Multi-core recommended | Any |

> **Important**: On first start, Valhalla builds routing tiles from OpenStreetMap data (3-6 hours for 15 countries) and Photon imports geocoding data (20+ hours for 15 countries). These are one-time operations — data persists across restarts.

### Quick Start

1. Clone and configure:

```bash
git clone https://github.com/GeiserX/propel.git
cd propel
docker compose -f docker/docker-compose.yml up -d
cp .env.example .env
# Edit .env — set DATABASE_URL and optionally PROPEL_DEFAULT_COUNTRY
```

2. Start the stack:

```bash
docker compose up -d
```

3. Wait for the first-time setup to complete:

```bash
# Watch Valhalla build routing tiles (~20 min)
docker logs -f propel-valhalla

# Watch Photon download geocoding data (~30 min)
docker logs -f propel-photon
```

4. Seed station data:

```bash
npm install
npx prisma generate
npm run scraper:run -- --country=ES --once
npm run dev
```

Open http://localhost:3000
5. Open http://localhost:3000

## Self-Hosting
### What Happens on First Start

| Service | First start | Subsequent starts |
|---|---|---|
| **Valhalla** | Builds routing tiles from 15-country merged PBF (~20 GB). Uses 8 threads, peaks at ~15 GB RAM. Takes 3-6 hours. | Loads pre-built tiles instantly (~2 GB RAM) |
| **Photon** | Downloads Photon 1.0.1 JAR (~92 MB) + 15 country geocoding dumps. Imports 200M+ documents (20+ hours). | Starts with existing index (~3 GB RAM) |
| **PostGIS** | Creates database schema | Ready immediately |
| **App** | Ready immediately | Ready immediately |

### Docker Compose Services

| Service | Image | Steady-state RAM | Internal Port |
|---|---|---|---|
| app | `drumsergio/propel` | 512 MB | 3000 (exposed as 3200) |
| db | `postgis/postgis:17-3.4` | 2 GB | 5432 |
| valhalla | `ghcr.io/gis-ops/docker-valhalla/valhalla:3.5.1` | 512 MB | 8002 |
| photon | `eclipse-temurin:21-jre` + Photon 1.0.1 JAR | 1 GB | 2322 |

### Disk Usage (15 countries)

| Directory | Size | Contents |
|---|---|---|
| `valhalla/` | ~30 GB | 15-country merged PBF + routing tiles |
| `photon/` | ~250 GB | Combined JSONL + OpenSearch index |
| `pgdata/` | ~2 GB | PostGIS database (84K+ stations) |
| **Total** | **~280 GB** | |

### Configuration

| Variable | Description | Default |
|---|---|---|
| `DATABASE_URL` | PostGIS connection string | Required |
| `PROPEL_DEFAULT_COUNTRY` | ISO code for initial map view | `ES` |
| `PROPEL_ENABLED_COUNTRIES` | Comma-separated ISO codes | All |
| `PROPEL_DEFAULT_FUEL` | Override default fuel type | Per-country |
| `PROPEL_CLUSTER_STATIONS` | Enable map marker clustering | `true` |
| `PROPEL_CORRIDOR_KM` | Route corridor width in km (0.5–50) | `5` |
| `VALHALLA_URL` | Valhalla routing endpoint | `http://propel-valhalla:8002` |
| `PHOTON_URL` | Photon geocoding endpoint | `http://propel-photon:2322` |

### Using a Different Country

Valhalla and Photon can be configured for any country:

See [docker/docker-compose.yml](docker/docker-compose.yml) for the full stack. Total requirements: ~6-10 GB RAM, ~80 GB disk for Europe.
1. **Valhalla**: Change `tile_urls` to the country PBF from [Geofabrik](https://download.geofabrik.de/)
2. **Photon**: Change `-country-codes es` to your country code in the docker-compose entrypoint
3. **App**: Set `PROPEL_DEFAULT_COUNTRY` and `PROPEL_ENABLED_COUNTRIES`

## Data Sources

| Country | Source | Stations | Update |
|---|---|---|---|
| Spain | MITECO | ~12,000 | Daily |
| France | prix-carburants.gouv.fr | ~9,800 | 10 min |
| Germany | Tankerkoenig (MTS-K) | ~14,000 | 4 min |
| Italy | MIMIT | ~22,000 | Daily |
| UK | CMA Open Data | ~8,000 | Varies |
| Austria | E-Control | All | Real-time |
| Portugal | DGEG | ~3,500 | Daily |
| Italy | MIMIT | ~23,600 | Daily |
| Germany | Tankerkoenig (MTS-K) | ~14,300 | Hourly |
| Spain | MITECO | ~12,200 | Daily |
| France | prix-carburants.gouv.fr | ~9,900 | Daily |
| Netherlands | ANWB | ~3,900 | Daily |
| UK | CMA Open Data | ~3,500 | Daily |
| Portugal | DGEG | ~3,200 | Daily |
| Belgium | ANWB | ~3,200 | Daily |
| Greece | FuelGR | ~3,100 | Daily |
| Austria | E-Control | ~2,700 | Real-time |
| Romania | Peco Online | ~1,400 | Daily |
| Ireland | Pick A Pump | ~1,300 | Daily |
| Croatia | MZOE | ~900 | Daily |
| Slovenia | goriva.si | ~550 | Daily |
| Luxembourg | ANWB | ~240 | Daily |

## Tech Stack

Next.js 15, React 19, MapLibre GL JS, Tailwind CSS, PostGIS, Valhalla, OpenFreeMap, Photon, next-intl.
Next.js 15, React 19, MapLibre GL JS, Tailwind CSS, PostGIS 17, Valhalla 3.5.1, Photon 1.0.1, OpenFreeMap.

## Roadmap

Expand Down
Loading
Loading