Amended. As originally filed, this issue claimed forward_geocode_regions could resolve a county-NAME column ("Allegheny County") to a county FIPS. That is wrong, and the correction changes what this issue is actually asking for. The original mechanism is sound — but for a city-name column, not a county-name one. Details below.
Summary
--geojson auto (#4397, PR #4418) resolves boundaries from region codes (county FIPS, ZCTA, tract, place GEOID). Extend it to region columns that hold names rather than codes, by resolving them to FIPS through the Geonames engine qsv geocode already ships.
Why this is a real gap
REGION_CODE_LEAVES (src/cmd/viz.rs) already includes "county" as distinct from "county_fips"/"fips", and match_region_code already case-folds alphabetic region codes — so name-keyed region columns are a supported shape today whenever the user supplies their own GeoJSON. They are exactly the columns auto cannot serve.
What the geocode engine can and cannot do (the correction)
forward_geocode_regions (src/cmd/geocode.rs) calls:
engine.suggest::<String>(name, 1, None, None)
That searches the cities index — the Geonames citiesNNNNN gazetteer. It matches populated places.
admin2Codes.txt (DEFAULT_ADMIN2_CODES_URL) is loaded only to decorate a matched city record with cityrecord.admin2_division. Counties are not independently searchable, and the --admin1 option is a filter applied to city results, not a name search over administrative divisions.
So:
| column holds |
resolvable today? |
how |
a city name (Pittsburgh) |
yes |
suggest → city record → admin2_division → us_fips_from_codes → 42003 |
a county name (Allegheny County) |
no |
suggest fuzzy-matches it against city names; it does not reach a county record |
The original issue conflated these. A literal "Allegheny County" value does not resolve through this path.
Two separable pieces of work
A. City-name columns → county FIPS (the mechanism that works)
suggest → matched city → its admin2_division → county FIPS → the existing county boundary path from #4397. This is a lookup, not a spatial approximation, so the nearest-populated-place caveat below does not apply — the answer is "what county is this city in", which Geonames states directly.
Needs GeoRegion to expose the FIPS pair: it currently carries admin1_code / us_state_code but not admin2 or the FIPS codes, even though us_fips_from_codes(admin1_code, admin2_code) already exists as a pure function and us_fips_strings shows the admin2 access pattern. GeoRegion's own doc comment says the struct is "a complete region-identifier surface … populated for reuse by future callers" — this is that caller. ~5 lines.
Geonames index resolution is the binding constraint here. The default is cities15000 (~26k cities worldwide, ~800 US), which omits most US small towns, so a city-name column will under-resolve badly. cities1000 (~140k) or cities500 (~200k) largely fixes it.
But do not change the global default to fix this: it is a cost imposed on every geocode user (13mb → 56mb index, ~26k → ~200k records, slower lookups, more memory) and the help text explicitly warns that "search results will be different". The prebuilt index published from the qsv repo is English-only cities15000, so changing the default also means shipping a much larger prebuilt or pushing users to build locally.
Instead, detect and report — the same pattern #4397 uses for a vintage mismatch, which turns a dead end into an instruction:
--geojson auto: resolved 40 of 300 place names. The default cities15000 Geonames index
omits places under 15,000 population; rebuild with `qsv geocode index-update --cities-url 1000`.
B. County-name columns (what this issue originally asked for)
Needs a mechanism that does not exist yet. Options, roughly in order of appeal:
- Make admin2 records searchable — build a lookup over
admin2Codes.txt (name → US.PA.003), which is already downloaded. Cheap, offline, and exactly keyed to the question. Ambiguity (Washington County exists ~30 times) is resolved by a state column, or reported.
- Match TIGERweb's
NAME/BASENAME — the county layer carries both (Beaver County / Beaver). Tempting, but bare names are ambiguous across states, so it needs a state from somewhere regardless.
- Require the user to supply a state column alongside, and disambiguate with it.
Whichever is chosen, the honesty check is the same as the code path: resolution rate over distinct names, with ambiguous bare names reported rather than silently resolved to an arbitrary state.
Important: do NOT extend either of these to lat/lon reverse geocoding
Investigated and rejected during #4397. reverse_geocode_regions returns the nearest populated place's county, not the containing county. A point in rural Fayette County PA whose nearest city record sits in Westmoreland yields the wrong county — and nothing downstream detects it, because every derived FIPS is a real county, so any resolution-rate check reads 100%.
A denser index does not rescue this. It makes the error rarer without making it detectable, which is a worse failure mode than the common one: it survives testing, so you stop looking for it. The correct mechanism for bare lat/lon boundaries is point-in-polygon against fetched polygons, which #4397 also defers.
Reverse geocoding remains safe for deriving a state set (a nearest-city match essentially never crosses a state line, and when it does the set merely widens, which is harmless).
Acceptance
Summary
--geojson auto(#4397, PR #4418) resolves boundaries from region codes (county FIPS, ZCTA, tract, place GEOID). Extend it to region columns that hold names rather than codes, by resolving them to FIPS through the Geonames engineqsv geocodealready ships.Why this is a real gap
REGION_CODE_LEAVES(src/cmd/viz.rs) already includes"county"as distinct from"county_fips"/"fips", andmatch_region_codealready case-folds alphabetic region codes — so name-keyed region columns are a supported shape today whenever the user supplies their own GeoJSON. They are exactly the columnsautocannot serve.What the geocode engine can and cannot do (the correction)
forward_geocode_regions(src/cmd/geocode.rs) calls:That searches the cities index — the Geonames
citiesNNNNNgazetteer. It matches populated places.admin2Codes.txt(DEFAULT_ADMIN2_CODES_URL) is loaded only to decorate a matched city record withcityrecord.admin2_division. Counties are not independently searchable, and the--admin1option is a filter applied to city results, not a name search over administrative divisions.So:
Pittsburgh)suggest→ city record →admin2_division→us_fips_from_codes→42003Allegheny County)suggestfuzzy-matches it against city names; it does not reach a county recordThe original issue conflated these. A literal
"Allegheny County"value does not resolve through this path.Two separable pieces of work
A. City-name columns → county FIPS (the mechanism that works)
suggest→ matched city → itsadmin2_division→ county FIPS → the existing county boundary path from #4397. This is a lookup, not a spatial approximation, so the nearest-populated-place caveat below does not apply — the answer is "what county is this city in", which Geonames states directly.Needs
GeoRegionto expose the FIPS pair: it currently carriesadmin1_code/us_state_codebut not admin2 or the FIPS codes, even thoughus_fips_from_codes(admin1_code, admin2_code)already exists as a pure function andus_fips_stringsshows the admin2 access pattern.GeoRegion's own doc comment says the struct is "a complete region-identifier surface … populated for reuse by future callers" — this is that caller. ~5 lines.Geonames index resolution is the binding constraint here. The default is
cities15000(~26k cities worldwide, ~800 US), which omits most US small towns, so a city-name column will under-resolve badly.cities1000(~140k) orcities500(~200k) largely fixes it.But do not change the global default to fix this: it is a cost imposed on every
geocodeuser (13mb → 56mb index, ~26k → ~200k records, slower lookups, more memory) and the help text explicitly warns that "search results will be different". The prebuilt index published from the qsv repo is English-onlycities15000, so changing the default also means shipping a much larger prebuilt or pushing users to build locally.Instead, detect and report — the same pattern #4397 uses for a vintage mismatch, which turns a dead end into an instruction:
B. County-name columns (what this issue originally asked for)
Needs a mechanism that does not exist yet. Options, roughly in order of appeal:
admin2Codes.txt(name →US.PA.003), which is already downloaded. Cheap, offline, and exactly keyed to the question. Ambiguity (Washington Countyexists ~30 times) is resolved by a state column, or reported.NAME/BASENAME— the county layer carries both (Beaver County/Beaver). Tempting, but bare names are ambiguous across states, so it needs a state from somewhere regardless.Whichever is chosen, the honesty check is the same as the code path: resolution rate over distinct names, with ambiguous bare names reported rather than silently resolved to an arbitrary state.
Important: do NOT extend either of these to lat/lon reverse geocoding
Investigated and rejected during #4397.
reverse_geocode_regionsreturns the nearest populated place's county, not the containing county. A point in rural Fayette County PA whose nearest city record sits in Westmoreland yields the wrong county — and nothing downstream detects it, because every derived FIPS is a real county, so any resolution-rate check reads 100%.A denser index does not rescue this. It makes the error rarer without making it detectable, which is a worse failure mode than the common one: it survives testing, so you stop looking for it. The correct mechanism for bare lat/lon boundaries is point-in-polygon against fetched polygons, which #4397 also defers.
Reverse geocoding remains safe for deriving a state set (a nearest-city match essentially never crosses a state line, and when it does the set merely widens, which is harmless).
Acceptance