Files
Europa/Nuvolari
Alby96andClaude Opus 5 b4b8d089e6 Add municipality and coordinate search, and a map target selector
Puts the controls that change what the map is looking at over the map itself,
where they act, and gives them something to search.

Search is one field, not two. A string either parses as coordinates or it does
not, and the answer is obvious from the text, so making the user declare up
front which kind of thing they are looking for would be asking them to do the
program's job. Coordinates accept decimal and degrees-minutes-seconds, comma or
space separated, with hemisphere letters — including the Italian O for ovest,
because someone reading an Italian map will type it and silently reading it as
east would put them the wrong side of Greenwich.

The 1180 Piedmont municipalities are bundled rather than geocoded online. The
app is region-scoped, so a list of one region's towns is small enough to ship
(100 KB) and beats a geocoder on every axis that matters: instant, offline, no
API key, no rate limit, and it cannot return a result somewhere the app has no
radar for. Matching folds accents, so "aglie" finds "Agliè", and prefix matches
outrank substring ones — typing "tor" should surface Torino, not the first
alphabetical name that happens to contain those letters.

tool/generate_places.py derives the list from Istat boundary shapefiles
(CC BY 4.0). Two properties of that file cost time and are now written down:
the geometry is UTM 32N rather than degrees, and the DBF is UTF-8 despite one
bilingual Friulian record that makes strict cp1252 fail. A terminal renders
utf-8 and latin-1 output identically, so the encoding cannot be settled by
looking at printed text — it took dumping codepoints. A guard in the generator
and a test against the shipped asset both check for mojibake now, and the guard
caught a real mistake the moment it was written.

The target selector switches between following the device, a saved place, and
the whole region, and the selection doubles as what the app reopens on: "the
place I marked" and "what I see when I open the app" are one idea to the person
using it. Dragging the map while it is following stops the camera chasing them
but leaves that preference alone, because looking somewhere else now is not the
same as changing their mind about next launch.

The position marker and the following are MapLibre's own, driven by
onCameraTrackingDismissed, so there is no second location stream to keep in step
with the map.

A test asserts that all 1180 municipalities fall inside the region bounds, which
is what actually validates the UTM-to-degrees conversion end to end.

Verified on the emulator: the dropdown lists follow, region and both saved
places; "aglie" finds Agliè; "44.3841 7.5426" offers the coordinate jump and
lands on Cuneo at town-reading zoom; selecting follow moves the map to the
device position with the blue dot on it and changes the recentre button to
match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 21:47:20 +02:00
..

Nuvolari

Precipitation radar for Piedmont, Italy. Android first, iOS later, one Flutter codebase.

Radar imagery comes from the public Radar-DPC platform and weather alerts from the ARPA Piemonte XML-CAP bulletin, drawn over an OpenStreetMap base map. The app is free, with no advertising and no accounts.

Scope: radar, official alerts, rain notifications. No forecasts, no lightning, no home-screen widget. See CLAUDE.md.

Nuvolari is an independent app. It is not affiliated with, endorsed by, or operated by ARPA Piemonte or the Dipartimento della Protezione Civile. For civil-protection purposes the official channels always prevail.

Layout

app/       Flutter application (Dart package "nuvolari")
backend/   Python worker: fetches DPC rasters, crops, reprojects, renders PNG frames
docs/      architecture, data sources, licenses, stack decisions, roadmap, privacy
tool/      verification scripts

Start with docs/architecture.md, then docs/roadmap.md for what is built and what is next.

Toolchain

Tool Version
Flutter 3.47.3 stable (Dart 3.13.3)
Android SDK platform 36, build-tools 36.0.0, platform-tools
JDK 21
Python 3.12+ (3.14 works; rasterio ships wheels for it)
Android NDK 28.2.13676358 — pinned by maplibre_gl, not by this project

A full toolchain plus a warm Gradle cache needs roughly 14 GB of disk: Flutter 3 GB, Android SDK 2.5 GB (2 GB of which is the NDK the map plugin pins), the Gradle cache 5 GB, and build output around 2 GB. flutter clean reclaims the last of those.

Configuration

Secrets never enter the repository. Copy the template and fill it in:

cp env.example.json env.json     # env.json is git-ignored
Key Purpose
MAP_STYLE_URL MapLibre style URL. Empty uses the free OpenFreeMap style; offline forces the bundled no-network style.
RADAR_MANIFEST_URL Base URL of the published manifest.json.
RADAR_SOURCE mock (offline demo), dpc (live), arpa (disabled stub).
REGION_ID Which region config to load. Defaults to piemonte.

Running

cd app
flutter run --dart-define-from-file=../env.json

With no env.json the app starts in demo mode: mock radar frames from assets on the free OpenFreeMap base map. No credentials are needed for anything — OpenFreeMap has no API key and no signup.

The radar frames work with no network at all. The base map needs one; run with --dart-define=MAP_STYLE_URL=offline to drop it too and work entirely offline.

Debugging in VS Code

Open the Nuvolari folder as the workspace root — the configurations in .vscode/ are relative to it. Then pick one in Run and Debug and press F5:

Configuration Use it for
Nuvolari — debug (emulatore) Everyday work. Demo mode, no env.json needed, hot reload on save.
Nuvolari — profile Judging animation smoothness. Debug builds run the Dart VM unoptimised, so the radar timeline always looks worse than it is — never assess it in debug.
Nuvolari — debug con env.json Pointing the app at a live radar manifest or a different base map style.
Nuvolari — debug su Windows Fast UI iteration with no emulator. The native map does not render here.
Test — tutti / Test — file corrente Debugging tests with breakpoints.

Every emulator configuration runs tool/start_emulator.ps1 first, so F5 works from a cold machine. The script exits immediately when a device is already attached, so pressing F5 twice does not start two emulators.

Ctrl+Shift+B runs the full verification. Other tasks live under Terminal → Run Task: quick verification, regenerating the demo radar frames, and shutting the emulator down.

Verifying

.\tool\verify.ps1              # format, analyze, test, build, lint, pytest
.\tool\verify.ps1 -SkipBuild   # fast inner loop

Stages whose target does not exist yet are skipped, so this runs from day one.

Attribution

Radar-DPC (CC BY-SA) · Arpa Piemonte · © OpenStreetMap contributors (ODbL) · © OpenMapTiles · OpenFreeMap.

See docs/licenses.md for the obligations these carry — in particular, the rendered radar frames are a derived product of CC BY-SA data and inherit share-alike, and the OpenFreeMap style ships no attribution field, so the app has to render the map credits itself.