Skip to content
Joel Natividad edited this page May 21, 2026 · 4 revisions

Geospatial

Tier: Intermediate Commands covered: geocode, geoconvert

Per-command flag reference lives in /docs/help/. This page is the workflow layer — when to reach for each command and how they compose.

Two commands, big footprint. geocode handles forward geocoding, reverse geocoding, city suggestions, IP geolocation, and country info — all against a local Geonames index plus an optional MaxMind GeoLite2 database. No network round-trip per row, 360,000 records / sec with caching and multithreading. It can also geocode online via the OpenCage API (the opencage / opencagenow subcommands) when you need true street-address geocoding that the local city index can't do.

geoconvert flips between CSV and spatial formats (GeoJSON, SHP, KML, GPX, …).

Quick decision table

If you want to… Use Notes
Find lat/lon from a city name geocode suggest Jaro-Winkler fuzzy matching
Find the nearest city to a lat/lon geocode reverse Local index; no API rate limits
Find country code from an IP geocode iplookup Needs MaxMind GeoLite2-City.mmdb
Enrich with US state/county FIPS codes geocode suggest / reverse with FIPS dyncols Census data prep
Geocode a full street address (online) geocode opencage OpenCage API; needs an API key
Reverse-geocode a coordinate to a full address (online) geocode opencage Same subcommand; mode auto-detected
Manage / prune the OpenCage result cache geocode cache-* cache-clear, cache-prune, cache-info
CSV → GeoJSON for QGIS / web maps geoconvert Specify --geometry for WKT or lat/lon
GeoJSON / SHP → CSV geoconvert Drops to a flat tabular form

geocode

Twelve subcommands; the big four are suggest, suggestnow, reverse, reversenow. Plus iplookup / iplookupnow, countryinfo / countryinfonow, the online opencage / opencagenow subcommands, the geocode index-* subcommands for managing the local Geonames cities index, and the geocode cache-* subcommands for managing the on-disk OpenCage result cache.

Setting up the index

# Downloads the prebuilt Geonames cities15000 index (~26k cities, pop > 15k)
qsv geocode suggest --help    # first run triggers the download

# Manage the local index
qsv geocode index-check                    # show index metadata; check for updates
qsv geocode index-update                   # rebuild from the latest Geonames data
qsv geocode index-load my_index.rkyv       # load a custom-built .rkyv index file
qsv geocode index-reset                    # restore the default prebuilt cities15000 index

For IP-based lookups, manually download GeoLite2-City.mmdb from your MaxMind account (free) and copy it to ~/.qsv-cache/ (or set QSV_GEOIP2_FILENAME).

Forward geocoding (suggest)

Example: enrich a list of city names with coordinates

# input has a "city" column with values like "Brooklyn", "san jose", "PARIS"
qsv geocode suggest city \
  --new-column 'lat,lon' \
  --formatstr "%latitude,%longitude" \
  cities.csv > cities_with_coords.csv

Example: US-only suggestion that also pulls FIPS codes (for Census joins)

qsv geocode suggest city_col --country US -f \
  "%dyncols: {geocoded_city:name},{state:admin1},{county:admin2},\
{state_fips:us_state_fips_code},{county_fips:us_county_fips_code}" \
  voters.csv -o voters_with_fips.csv

The %dyncols: directive expands to multiple new columns named on the left of each : pair.

Reverse geocoding (reverse)

Example: reverse-geocode NYC 311 lat/lon to Borough / NTA

qsv geocode reverse 'Location' \
  --new-column 'nearest_city,admin1,admin2' \
  --formatstr "%name,%admin1,%admin2" \
  --country US \
  NYC_311_SR_2010-2020-sample-1M.csv > nyc311_reversed.csv

Location is the source column in "lat, lon" or "(lat, lon)" format.

Example: reverse-geocode FIPS codes for downstream Census tract joins

qsv geocode reverse coordinate_col --country US -f \
  "%dyncols: {city:name},{state:admin1},{county:admin2},\
{state_fips:us_state_fips_code},{county_fips:us_county_fips_code}" \
  events.csv -o events_with_fips.csv

One-shot variants (suggestnow, reversenow, countryinfonow)

For ad-hoc one-off lookups from the command line (not a CSV):

qsv geocode suggestnow "Brooklyn"
qsv geocode reversenow "40.6782, -73.9442"
qsv geocode countryinfonow US

IP lookup (iplookup)

Needs the MaxMind GeoLite2-City.mmdb.

qsv geocode iplookup --new-column country ip_col logs.csv > logs_with_country.csv

Country info (countryinfo)

qsv geocode countryinfo country_code visitors.csv > with_country_info.csv
# Adds columns: name, capital, area, population, languages, currency, ...

Online geocoding with OpenCage (opencage, opencagenow)

suggest / reverse are city-level and offline — fast, but they can't resolve a full street address. When you need real address geocoding, opencage calls the online OpenCage API. One subcommand does forward (address → coordinates) and reverse (coordinates → address) geocoding — the mode is auto-detected per row (pass --reverse to force reverse).

Requires an OpenCage API key — set it with --api-key or the QSV_OPENCAGE_API_KEY environment variable. A free key allows 2,500 requests/day: https://opencagedata.com/users/sign_up.

# forward-geocode a column of street addresses to coordinates
qsv geocode opencage address --new-column coordinates -f %location addresses.csv

# reverse-geocode "(lat, lon)" coordinates to a full formatted address (the default format)
qsv geocode opencage Location --reverse --new-column address events.csv

# pull individual OpenCage components with dotted dynamic formatting
qsv geocode opencage address -f '{components.city}, {components.country}' addresses.csv

# add multiple columns in one pass with the %dyncols: directive
qsv geocode opencage address \
  -f '%dyncols: {city:components.city}, {postcode:components.postcode}, {tz:annotations.timezone.name}' \
  addresses.csv

# one-off lookups from the shell
qsv geocode opencagenow "1600 Pennsylvania Ave NW, Washington DC"
qsv geocode opencagenow "40.71427, -74.00597"

OpenCage's terms of service permit caching, so results are kept in a persistent on-disk cache (separate from the Geonames index, in {cache-dir}/geocode-opencage_v1) — re-runs and duplicate queries never re-hit the API (--cache-ttl sets the TTL; --no-cache disables it). Rows are processed sequentially behind a rate limiter (--rate-limit, default 1 request/sec — the free-tier limit), so reach for opencage for enrichment quality and for the offline subcommands for raw throughput. Inspect and clean up that cache with the cache-* subcommands.

OpenCage --formatstr options: %formatted (default — the full formatted address), %lat-long, %location, %city, %state, %county, %country, %country_name, %postcode, %confidence, %json, %pretty-json; plus dotted dynamic keys {components.<name>} and {annotations.<dotted.path>}. The %dyncols: directive works here too — %dyncols: {city:components.city}, {tz:annotations.timezone.name} adds several columns to the output CSV in one pass (it cannot be combined with --new-column).

Managing the OpenCage cache (cache-*)

The opencage / opencagenow subcommands keep their results in a persistent on-disk cache that lives in {cache-dir}/geocode-opencage_v1 (default cache dir ~/.qsv-cache/). It is only populated by the OpenCage subcommands — the Geonames cities index is managed separately via index-*. Three subcommands manage this cache:

# wipe the entire OpenCage cache
qsv geocode cache-clear

# delete entries older than a relative age (s/m/h/d/w suffix) or an absolute date
qsv geocode cache-prune --older-than 30d
qsv geocode cache-prune --older-than 2025-01-01

# report the cache dir, entry count, on-disk size and oldest/newest timestamps (JSON)
qsv geocode cache-info

cache-prune is the tool for honoring data-retention policies without discarding the whole cache; cache-info emits a JSON summary you can pipe into other commands.

See also: /docs/help/geocode.md, Geonames, MaxMind GeoLite2, OpenCage API, Recipe: Geographic Enrichment, Lookup Tables — for fallback when geocode misses.

geoconvert

Convert between CSV/SVG and spatial formats. Available formats include geojson, shp (Shapefile), kml, gpx, geojsonl, and more — check qsv geoconvert --help for the full list in your build.

Example: GeoJSON → CSV (for SQL analysis)

qsv geoconvert nta_boundaries.geojson geojson csv > nta.csv
qsv sqlp nta.csv "SELECT borough, COUNT(*) FROM nta GROUP BY borough"

Example: CSV with WKT geometry → GeoJSON for a web map

qsv geoconvert parcels.csv csv geojson --geometry geometry > parcels.geojson

Example: SHP → CSV (drops to tabular form)

qsv geoconvert nyc_boroughs.shp shp csv > nyc_boroughs.csv

Example: pick a file via dialog, then convert

qsv prompt -m 'Choose a GeoJSON file' -F geojson \
  | qsv geoconvert - geojson csv > result.csv

Example: combine geocode + geoconvert to enrich and map

# 1. Reverse-geocode NYC 311 to get the borough column
qsv geocode reverse 'Location' --new-column 'borough' \
  --formatstr "%admin2" --country US nyc311.csv > step1.csv

# 2. Bulk-convert lat/lon pairs into WKT, then to GeoJSON
qsv apply operations wkt_point Latitude Longitude \
  --new-column geometry step1.csv > step2.csv
qsv geoconvert step2.csv csv geojson --geometry geometry > nyc311.geojson

See also: /docs/help/geoconvert.md, geocode, Conversion & I/O.

See also

Clone this wiki locally