ThomLive is a production-ready Progressive Web App for ranking nearby administrative municipalities by perceived heat discomfort. It combines OpenStreetMap/Overpass municipality boundaries, live Open-Meteo temperature and humidity data, the Thom Index, an offline-capable PWA shell, semantic UI colors, maps, and a multilingual interface.
The app is designed to answer a simple question: where does heat feel heavier nearby, and where does discomfort ease off?
This version intentionally excludes hamlets, districts, neighborhoods, suburbs, quarters, and minor localities. It uses OpenStreetMap administrative boundaries with boundary=administrative and admin_level=8, which usually correspond to municipalities in Italy.
- Search for a place with autocomplete.
- Rank administrative municipalities within a fixed radius.
- Fetch live temperature and relative humidity from Open-Meteo.
- Calculate the Thom Index for each municipality.
- Highlight discomfort levels with a clear semantic palette.
- Show a mobile-first interactive map with heat/discomfort layers.
- Show the coolest nearby municipalities separately.
- Save the latest result for offline reopening.
- Provide a PWA manifest, service worker, favicon set, and installable app icons.
- Support multiple UI languages through JSON locale files.
- Automatically prune stale backend cache files.
- PHP 7.4+ or PHP 8+ hosting.
- PHP cURL extension enabled, or
allow_url_fopenavailable. - A writable
api/cachedirectory. - HTTPS recommended for PWA installation and browser geolocation.
- Internet access to Leaflet and leaflet.heat CDN assets for the interactive map.
- No Open-Meteo API key is required for public/non-commercial usage.
- Upload all project files to your PHP hosting space.
- Make sure
api/cacheis writable by the web server. - Open the app domain in a browser.
When upgrading from an older release that included minor localities, you can safely clear api/cache. This version uses municipality-specific cache keys, but clearing old cache files keeps the installation tidy.
thomlive-pwa/
├─ index.html
├─ styles.css
├─ app.js
├─ manifest.json
├─ sw.js
├─ locales/
│ ├─ it.json
│ └─ en.json
├─ assets/
│ ├─ app-icon.png
│ ├─ favicon.svg
│ └─ icons/
│ ├─ favicon-16.png
│ ├─ favicon-32.png
│ ├─ apple-touch-icon.png
│ ├─ icon-192.png
│ └─ icon-512.png
└─ api/
├─ config.php
├─ config.sample.php
├─ _bootstrap.php
├─ geocode.php
├─ ranking.php
└─ cache/GET api/geocode.php?q=Rimini&limit=5Uses Open-Meteo Geocoding and returns candidate coordinates.
GET api/ranking.php?lat=44.0678&lon=12.5695&label=Rimini&radiusKm=70The endpoint performs the following steps:
- Queries Overpass for OpenStreetMap municipalities within the selected radius.
- Also retrieves the municipality containing the search center through
is_in(). - Calls the Open-Meteo Forecast API in batches for current temperature and humidity.
- Calculates the Thom Index.
- Sorts municipalities by descending discomfort.
- Returns the data used by the ranking cards and map UI.
The UI includes a Discomfort and coolest municipalities section below the summary. The map uses:
- Leaflet for the interactive mobile-friendly map.
- OpenStreetMap tiles.
- leaflet.heat for heatmap rendering.
- An automatic marker fallback if the heatmap plugin cannot be loaded.
Available map modes:
- Scenario: default mode. Shows the heatmap where Thom is at least 24 and overlays clear markers for municipalities below 24.
- Discomfort: highlights only municipalities with a Thom Index of at least 24.
- Points: shows every municipality as a tappable marker with a popup containing name, distance, temperature, humidity, and Thom Index.
A dedicated Where discomfort drops list is sorted by ascending Thom Index.
The release includes the optimized main visual icon at assets/app-icon.png in 512×512, PNG favicons, apple-touch-icon.png, and PWA icons at 192×192 and 512×512.
The previous 1024×1024 icon variant was removed because it is not required by the manifest and was too heavy for a lightweight PWA.
The icon displayed in the hero is rendered inside a square container with aspect-ratio: 1 / 1 and object-fit: contain, so it does not stretch when the hero layout changes size.
Cache values can be configured in api/config.php:
define('PLACES_CACHE_TTL', 60 * 60 * 24 * 14); // 14 days
define('GEOCODE_CACHE_TTL', 60 * 60 * 24); // 1 day
define('WEATHER_CACHE_TTL', 60 * 10); // 10 minutes
define('CACHE_PRUNE_INTERVAL', 60 * 60); // prune at most once per hour
define('CACHE_PRUNE_MAX_AGE', PLACES_CACHE_TTL + 60 * 60 * 24); // 15 days
define('MAX_WEATHER_PLACES', 250); // weather municipality limitThe backend exposes cache_prune() and calls it through cache_maybe_prune() from the API endpoints. It removes .json cache files older than CACHE_PRUNE_MAX_AGE and uses api/cache/.last-prune to avoid scanning the cache directory on every request.
If pruning fails, the endpoints keep responding normally.
The app includes an explanatory section at the bottom of the UI describing the Thom Index and the legend used across cards, badges, rankings, and maps.
Thom = T - (0.55 - 0.0055 × RH) × (T - 14.5)Where:
T= air temperature in °C.RH= relative humidity in percent.
Scale used by the backend, ranking, badges, and map:
| Thom | Label | UI Color |
|---|---|---|
< 21 |
Comfort | Blue/cyan |
21–23.9 |
Slight discomfort | Green |
24–26.9 |
Moderate discomfort | Yellow/amber |
27–28.9 |
Strong discomfort | Orange |
≥ 29 |
Very strong discomfort | Red |
The summary card displays the Thom value of the selected municipality when that municipality is present in the ranking.
If the search point comes from GPS coordinates or cannot be safely matched to an administrative municipality, the badge is explicitly labeled as an area peak instead. The selected municipality is also highlighted in the ranking to avoid confusion between the local value and the maximum value in the surrounding area.
The UI is ready for community localization without changing the main application logic.
Current locale files:
locales/it.json
locales/en.jsonThe language selector is available in the top bar and stores the selected language in localStorage.
To add a new language:
- Copy
locales/it.jsonorlocales/en.jsonto a new file, for examplelocales/fr.json. - Translate the values, keeping the keys unchanged.
- Keep placeholders exactly as they are, for example
{count},{place},{score},{radius}, and{time}. - Register the new locale code in
SUPPORTED_LOCALESinsideapp.js. - Add the locale file to the service worker pre-cache list in
sw.js. - Update the service worker cache name in
sw.jsto force clients to refresh the PWA shell.
- UI colors, map colors, and visual tone: edit CSS variables in
styles.cssandTHOM_LEVELSinapp.js. - Search radius: edit
RADIUS_KMinapp.jsandDEFAULT_RADIUS_KMinapi/config.php. - Discomfort thresholds: edit
thom_level()inapi/_bootstrap.php. - Administrative entity type: edit the Overpass query in
api/ranking.php. - Cache duration and pruning behavior: edit constants in
api/config.php.
- The Overpass query uses administrative boundaries with
admin_level=8. This is appropriate for Italian municipalities, but administrative levels may differ in other countries. - Open-Meteo is called server-side through PHP.
- Dense areas are limited by
MAX_WEATHER_PLACESto protect response times. - The PWA works offline for the app shell and can reopen the last saved ranking from the browser. Live weather data, map tiles, and CDN libraries still require a connection.