Skip to content

Commit 1887398

Browse files
bgoniasapre-commit-ci[bot]giswqs
authored
feat: CartoCiudad (IGN España) geocoding provider (#1712)
* feat(geocoding): add CartoCiudad (IGN España) provider Adds CartoCiudad as a geocoding provider using the public IGN/CNIG REST API: - Forward geocoding via /geocoder/api/geocoder/find?q= - Reverse geocoding via /geocoder/api/geocoder/reverseGeocode?lat=&lon= - No API key required (public service) - Parses rich Spanish address data: tip_via, portalNumber, postalCode, refCatastral, provincia, comunidadAutónoma, etc. - 9 unit tests covering URL building, forward/reverse parsing, edge cases * style: auto-format (ruff + oxfmt) [pre-commit.ci] * fix(geocoding): address code review feedback for CartoCiudad provider - Allow cartociudad.es in Tauri desktop HTTP capability configuration - Guard against null/empty lat/lng in parseForward/parseReverse - Validate coordinate bounds and finiteness in geocodeReverse - Document CartoCiudad in data-integrations.md guide - Add tests for invalid coordinates and null/empty field parsing * Address review feedback Verified the open questions against the live CartoCiudad API before fixing. - geocodeReverse now wraps longitude into [-180, 180] instead of rejecting it. MapLibre reports lngLat.lng unwrapped for clicks on repeated world copies, so the new bounds check was silently dropping legitimate reverse-geocode clicks for every provider, not just CartoCiudad. - Handle HTTP 204 / empty response bodies as "no results" in geocodeForward and geocodeReverse. CartoCiudad answers an unmatched query with 204 and an empty body (verified against find and reverseGeocode), which response.json() would reject on, surfacing a nonsense query as "Search failed" rather than no match. - Validate CartoCiudad coordinates through one shared helper used by both parseForward and parseReverse: rejects null/blank/non-numeric values ("NaN", "invalid") and anything outside WGS84 bounds. Replaces the duplicated blocks. - Reject an array response explicitly in both parsers. find answers with a single object even for an ambiguous query ("calle mayor" verified), so an array is an unrecognized shape rather than a candidate list. - Drop the unused state field from CartoCiudadResult and document why there is no in-body success flag to check: state is 0 on every observed match and no-match is signalled by 204, not by a failure code with bogus coordinates. - Stop forwarding limit in buildForwardUrl; find has no result-count parameter, so it was a silent no-op. Commented like the Google provider does. - Skip a blank portalNumber instead of appending a trailing space to the street. - Tests for each of the above, including the world-copy wrap and the 204 path. * Address Claude review feedback - Return an in-range longitude untouched from wrapLongitude instead of running it through the modulo round-trip, which is not exact in floating point: every ordinary longitude drifted (-3.7 became -3.7000000000000455, 2.35 became 2.3500000000000227). Also keeps 180 as 180 rather than flipping it to -180. - Extend the world-copy test to assert an in-range longitude reaches the provider unperturbed. * Address Claude review feedback Checked the two open questions against IGN's official service documentation (github.com/IDEESpain/Cartociudad) rather than inference. - Trim province before interpolating it into the display name, so a padded value renders as "(Madrid)" like the other segments rather than "( Madrid )". - Replace the empirical "state is 0 on every match observed" note with the documented reason: IGN records state/stateMsg as suppressed in the current Elasticsearch-backed geocoder, which cannot rank candidates by degree of match, so state is permanently 0 and there is no confidence signal to map onto score. - Document why CartoCiudad is deliberately excluded from shouldThrottle: the 1 req/sec pace and 1000-row cap are Nominatim's published policy, IGN documents no rate limit, and its own bulk geocoder processes 60,000 records per run. * Address Claude review feedback - Pace the public CartoCiudad host at ~5 req/sec instead of leaving it unbounded. The earlier rationale conflated two things: IGN's 60,000-record figure describes their purpose-built bulk geocoder, not the single-lookup find/reverseGeocode endpoints this provider calls, so unbounded bursting was an unverified assumption rather than a sanctioned one. Nominatim's 1 req/sec is still not borrowed: CARTOCIUDAD_MIN_INTERVAL_MS is our own courtesy default, and CartoCiudad stays out of shouldThrottle so it keeps no row cap (IGN documents none). Split the hostname lookup into a helper so the pacing and policy gates no longer have to share one boolean. - Fall back to muni when poblacion is absent in the display name. Verified against the live API: a road kilometre point ("A-6 km 120") returns muni "Espinosa de los Caballeros" with a null poblacion, so the municipality was being dropped from a name the raw response carried. - Document both in the geocoding usage-policy section of the user guide. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Qiusheng Wu <giswqs@gmail.com>
1 parent f879b67 commit 1887398

5 files changed

Lines changed: 513 additions & 24 deletions

File tree

apps/geolibre-desktop/src-tauri/capabilities/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
{ "url": "https://geocode.arcgis.com/*" },
3838
{ "url": "https://api.mapbox.com/*" },
3939
{ "url": "https://maps.googleapis.com/*" },
40+
{ "url": "https://www.cartociudad.es/*" },
4041
{ "url": "https://share.geolibre.app/*" },
4142
{ "url": "https://datasets.geolibre.app/*" }
4243
]

docs/user-guide/data-integrations.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Choose a backend in **Settings → Geocoding**. The selection, per-provider API
6868
| Provider | API key | Notes |
6969
| --- | --- | --- |
7070
| **Nominatim (OpenStreetMap)** | No | Default. Public endpoint is paced and row-capped (see below); point it at a self-hosted instance to relax both. |
71+
| **CartoCiudad (IGN España)** | No | Official Spanish national geocoder (IGN/CNIG). Free public API, no key required. |
7172
| **Pelias** | Optional | Hosted [geocode.earth](https://geocode.earth/) needs a key; a self-hosted Pelias does not. |
7273
| **ArcGIS World Geocoder** | Yes | Esri token / API key. |
7374
| **Mapbox** | Yes | Mapbox access token (`pk.…`). |
@@ -77,15 +78,19 @@ API keys are stored in plain text in the `.geolibre.json` project file, so avoid
7778

7879
### Usage policy and limits
7980

80-
Requests to the public Nominatim endpoint are paced to one per second and a single batch run is capped at 1000 rows, in line with the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/). Browsers cannot set a `User-Agent`, so the app identifies itself through the page `Referer` and the optional `email` parameter. Self-hosted Nominatim and the keyed providers (Mapbox, ArcGIS, Google, hosted Pelias) are not paced or capped by GeoLibre; their own quotas apply.
81+
Requests to the public Nominatim endpoint are paced to one per second and a single batch run is capped at 1000 rows, in line with the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/). Browsers cannot set a `User-Agent`, so the app identifies itself through the page `Referer` and the optional `email` parameter.
82+
83+
The public CartoCiudad endpoint is paced to roughly five requests per second and is not row-capped. IGN publishes no rate limit, so this is a courtesy default rather than their policy: it keeps a large batch from bursting at a free public service without borrowing Nominatim's much stricter numbers.
84+
85+
Self-hosted Nominatim and the keyed providers (Mapbox, ArcGIS, Google, hosted Pelias) are not paced or capped by GeoLibre; their own quotas apply.
8186

8287
### Configuring with environment variables
8388

8489
The Geocoding settings panel is the easiest way to configure a provider, but the same values can also be set as runtime environment variables (the same `VITE_`-prefixed mechanism used for [imagery credentials](../getting-started.md#optional-imagery-credentials)). Explicit environment variables override the Settings panel.
8590

8691
| Variable | Default | Purpose |
8792
| --- | --- | --- |
88-
| `VITE_GEOCODER_PROVIDER` | `nominatim` | Provider id: `nominatim`, `pelias`, `arcgis`, `mapbox`, or `google`. |
93+
| `VITE_GEOCODER_PROVIDER` | `nominatim` | Provider id: `nominatim`, `cartociudad`, `pelias`, `arcgis`, `mapbox`, or `google`. |
8994
| `VITE_GEOCODER_API_KEY` | unset | API key / access token for the selected provider. |
9095
| `VITE_GEOCODER_ENDPOINT` | provider default | Forward (address to point) search endpoint override. |
9196
| `VITE_GEOCODER_REVERSE_ENDPOINT` | provider default | Reverse (point to address) endpoint override. |

0 commit comments

Comments
 (0)