I Built a Cheapest Fuel Station Map from Spanish Open Data
Spain publishes the price of every public fuel station as open data. The feed is refreshed every 30 minutes, contains roughly 11,500 stations and needs no API key. That sounded like a good small project: download the data, find the cheapest station near a location, and put the result on a map that works on a phone. The result is FuelSpot , a mobile site for comparing stations by fuel and…
Spain makes every public fuel station’s price available as open data. The data is updated every 30 minutes and contains details on around 11,500 stations. It requires no API key and can be accessed through a simple URL. This proved perfect for a small project: downloading the data, locating the cheapest station near a location, and displaying the result on a mobile-friendly map.
The project, named FuelSpot, was built in just one day atop a personal data-web engine. The challenging aspect wasn’t drawing markers. Instead, it was making the government's public feed work consistently. The Spanish Ministry’s REST API is straightforward with a REST endpoint: GET https://sedeaplicaciones.minetur.gob.es/ServiciosRESTCarburantes/PreciosCarburantes/EstacionesTerrestres/
There are a few technical hurdles. The API can return either JSON or XML depending on the request. Initially, the API supplied XML, causing parsing issues. The solution was to explicitly state the request should return JSON using: const response = await fetch ( url , { headers : { Accept : application/json } } ) ; const stations = await response . json ( ) ;
Another issue was the data format. Coordinates are given as strings with decimal commas, e.g., 39,211417, instead of decimal points. Prices are also strings, with an empty string indicating no fuel sale. Some field names include spaces, dots, or parentheses, like "C.P." and "Longitud (WGS84)". These inconsistencies were dealt with by normalising all fields at the data ingestion stage rather than letting the original formatting carry over into the application.
Naming was another challenge. Province names like CORUÑA (A), PALMAS (LAS), VALENCIA / VALÈNCIA, and ARABA/ÁLAVA were mapped to more readable labels and clean slugs. For instance, CORUÑA became a-coruna, PALMAS became las-palmas, and ARABA/ÁLAVA became alava. Similarly, some municipalities like Realejos (Los) are better displayed as Los Realejos. While seemingly minor, proper names are crucial for URLs, breadcrumbs, filters, and titles.
The “near me” search function runs on the server. Users input a centre point, a fuel type, and a radius of 5, 10, or 25 kilometres. The algorithm first filters out stations outside the specified radius using a latitude/longitude bounding box. Then, it calculates the haversine distance for the remaining stations. The haversine distance determines which stations are within the chosen radius and sorts the results by price, returning the top 20 options.
For example, searching for unleaded 95 around León yielded 48 stations. The cheapest was 1.469 €/l 1.8 km away, while the average price was 1.617 €/l.
Adding a year of historical data was another feature. The API offers a second endpoint for historical data: .../EstacionesTerrestresHist/{dd-mm-yyyy}. This returns the same data type as the current feed but only for a specified past date. The API rejects today’s date, returning an HTTP 400 error message. When building the backfill, the application only processes dates up to yesterday to avoid errors.
Data was collected for 364 days, cached daily averages per fuel (both nationally and by province), and kept for a set period to manage storage.
The implementation uses MapLibre GL JS with OpenFreeMap vector tiles, which do not require an API key. Initially, each marker displayed a separate HTML price pill. This caused overcrowding at country zoom levels. The solution was to render labels with a symbol layer using GeoJSON data. MapLibre’s built-in collision detection hides labels that don’t fit, and the symbol-sort-key is set to price to prioritize cheaper stations.
For dense areas with many points, clustering is used, creating a small circle layer underneath to keep stations visible even if their labels are hidden. This approach ensures the map remains readable even when displaying numerous station options.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.