duckdb-geosupport
NYC address, tax lot, and building lookups in DuckDB.
Version 0.2.0 · Tested with DuckDB 1.4.4 · Unsigned development extension
Try it in Google Colab
Open the step-by-step Colab notebook ↗ to download and load the prebuilt extension, install 26C, geocode a table, review Geosupport return codes and messages, and export CSV/Parquet results with provenance.
Choose a Python 3 CPU runtime and run the cells in order. Initial setup takes several minutes and several GiB of disk, including the approximately 195 MiB download and 1.9 GiB installed 26C bundle. The notebook downloads a checksum-verified release binary and installs runtime libraries only if missing; no compiler, Make, Git, Docker, or GitHub token is needed. Optional cells let you upload your own CSV and compare quarters.
Download your results before the temporary Colab runtime ends. Download the notebook · View on GitHub
1. Download and set up
Linux x86-64: download the extension
Download the v0.2.0 Linux x86-64 archive and compare its SHA-256 with the release checksums. Extract the archive to get geosupport.duckdb_extension. A GitHub account is not required.
Use DuckDB 1.4.4 for Linux x86-64. Unzip it and open the DuckDB interface from your terminal:
./duckdb -unsigned
In DuckDB, load the downloaded extension using its absolute path:
LOAD '/absolute/path/to/geosupport.duckdb_extension';
This binary was built on Ubuntu 22.04 and tested on Ubuntu 22.04 and 24.04 with DuckDB 1.4.4. It requires libcurl, OpenSSL 3, and the C++ runtime (libcurl.so.4, libcrypto.so.3, and libstdc++.so.6). Compilers and development headers are not needed. See the release notes and build provenance.
Mac with Apple Silicon: run the published container
Start Docker Desktop, then run the published 26c image to open an interactive DuckDB session:
docker run --rm -it --platform linux/amd64 \
ghcr.io/sptkl/duckdb-geosupport:26c
Docker downloads the image on first use. It includes DuckDB, the loaded extension, and the installed 26C engine and data, with 26C selected as the default. Continue directly to the query below.
2. Run a query
If you downloaded the Linux extension, first run this setup in DuckDB. The install downloads approximately 195 MiB and uses 1.9 GiB on disk. Skip this setup when using the 26c container.
CALL gs_install('26c');
SET VARIABLE geosupport_release =
(SELECT bundle_id FROM gs_load('26c'));
Run this query in either DuckDB session:
CREATE TEMP TABLE result AS
SELECT gs_address('125', 'Worth St', 'Mn') AS geo;
SELECT geo.bbl, geo.bin, geo.latitude, geo.longitude,
geo.grc, geo.grc2
FROM result;
Sample output, captured from the 26C engine:
| bbl | bin | latitude | longitude | grc | grc2 |
|---|---|---|---|---|---|
| 1001680032 | 1001831 | 40.715669 | -74.002352 | 00 | 00 |
To select a release for an individual lookup, pass release := '26c'. Installed engine data is stored separately from your DuckDB database; the Linux download setup uses ~/.cache/duckdb-geosupport by default.
3. Examples
Geocode a table
CREATE TEMP TABLE addresses AS
SELECT * FROM (VALUES
(1, '125', 'Worth St', 'Mn'),
(2, '350', '5 Ave', 'Mn'),
(3, '30-30', 'Thomson Ave', 'QN'),
(4, '999999', 'NOT A REAL STREET', 'Mn')
) AS t(id, house_number, street_name, borough);
CREATE TEMP TABLE geocoded AS
SELECT id, gs_address(house_number, street_name, borough,
release := '26c') AS geo
FROM addresses;
SELECT id, geo.bbl, geo.bin, geo.latitude, geo.longitude,
geo.grc, geo.grc2
FROM geocoded ORDER BY id;
| id | bbl | bin | latitude | longitude | grc | grc2 |
|---|---|---|---|---|---|---|
| 1 | 1001680032 | 1001831 | 40.715669 | -74.002352 | 00 | 00 |
| 2 | 1008350041 | 1015862 | 40.748452 | -73.985712 | 00 | 00 |
| 3 | 4002770001 | 4003533 | 40.744071 | -73.936077 | 00 | 00 |
| 4 | NULL | NULL | NULL | NULL | EE | EE |
Keep house numbers and identifiers as strings. Rejected addresses remain rows with return codes and messages. Inspect both grc and grc2 for an address lookup.
SELECT id, geo.grc, geo.grc2, geo.message, geo.message2
FROM geocoded
WHERE geo.grc IS DISTINCT FROM '00'
OR geo.grc2 IS DISTINCT FROM '00';
| id | grc | grc2 | message | message2 |
|---|---|---|---|---|
| 4 | EE | EE | 'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'? | 'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'? |
This filter flags anything other than two exact 00 codes for review; your acceptance policy may allow other codes.
Look up a tax lot or building
SELECT (gs_bbl('1001680032', release := '26c')).bbl AS bbl;
-- 1001680032
SELECT (gs_bin('1001831', release := '26c', mode := 'tpad')).bbl AS bbl;
-- 1001680032
Call a Geosupport function directly
SELECT gs_call('1E', to_json({
'house_number': '125',
'street_name': 'Worth St',
'borough_code': 'Mn'
}), release := '26c', mode := 'extended') AS geo;
Every result includes raw, the full parsed output as JSON text. Use geo.raw::JSON to inspect function-specific fields.
Use another quarter
CALL gs_install('26b');
SELECT (gs_address('125', 'Worth St', 'Mn', release := '26b')).bbl AS bbl;
-- 1001680032
Save release, engine_version, and bundle_id with your results. An explicit release overrides the connection variable. Prepared statements capture that variable when prepared.
Examples use real captured engine output. Download the full sample results · Capture provenance
4. Function reference
Lookups
| Signature | Purpose |
|---|---|
gs_address(house_number, street_name, borough, release := NULL) | Address lookup using function 1B, regular mode. |
gs_bbl(bbl, release := NULL, mode := 'regular') | Tax lot lookup using BL. |
gs_bin(bin, release := NULL, mode := 'regular') | Building lookup using BN. |
gs_call(function_code, inputs, release := NULL, mode := 'regular') | Generic call with a JSON object of input fields. |
Accepted modes: regular, extended, long, tpad, long+tpad, extended+tpad. Output depends on the function and mode. gs_address does not implement a separate 1A/1E fallback.
Inputs must fit their fixed field widths and contain ASCII without NUL. Generic field values may be strings, integers, booleans, or NULL. Individual NULL fields become blanks. A NULL generic function, entire input value, or mode returns NULL when a release is selected.
All supported Geosupport function codes and aliases
| Code | Aliases |
|---|---|
| 1 | — |
| 1A | — |
| 1B | ADDRESS |
| 1E | — |
| 1N | GET_STREET_CODE, STREET_NAME_TO_STREET_CODE |
| 2 | INTERSECTION |
| 2W | INTERSECTIONS, INTERSECTION_WIDE |
| 3 | STREET_SEGMENT |
| 3C | BLOCKFACE |
| 3S | STREET_STRETCH |
| AP | ADDRESS_POINT |
| BB | BROWSE_BACK |
| BF | BROWSE_FORWARD |
| BL | BBL, LOT, TAX_LOT |
| BN | BIN, BUILDING |
| D | GET_STREET_NAME |
| DG | — |
| DN | — |
| N* | NORMALIZE_STREET_NAME |
See the layout definitions for input field names, widths, and function-specific layouts. _gs_call is an internal implementation function; use the public helpers above.
Returned fields
| Field | Type | Meaning |
|---|---|---|
bbl, bin | VARCHAR | Tax lot and building identifiers. |
latitude, longitude | DOUBLE | Coordinates, when available. |
grc, grc2 | VARCHAR | Primary and secondary return codes. |
message, message2 | VARCHAR | Corresponding engine messages. |
release | VARCHAR | Quarterly release label, e.g. 26c. |
engine_version | VARCHAR | Engine version, e.g. 26.3. |
bundle_id | VARCHAR | Immutable engine/data identity. |
raw | VARCHAR | Full parsed JSON output. |
Unavailable result fields are NULL. Not every function returns coordinates or both return codes.
Release management
| SQL | Purpose |
|---|---|
SELECT * FROM gs_available_releases(); | List catalog releases (26B and 26C). |
SELECT * FROM gs_installed_releases(); | List installed bundles. |
SELECT * FROM gs_runtime_info(); | List active workers. |
CALL gs_install('26c'); | Download, verify, and install a release. Repeated calls verify and reuse it. |
CALL gs_import('26c', '/path/linux_geo26c_26.3.zip'); | Import a matching base archive offline. |
CALL gs_verify('26c'); | Verify bundle integrity. |
CALL gs_load('26c'); | Start a worker and return the bundle identity. |
CALL gs_unload('26c'); | Stop a worker; keep installed data. |
These functions return release, bundle_id, engine_version, platform, and details (JSON text). Geocoding never installs missing data automatically.
Environment variables and runtime limits
| Variable | Default / purpose |
|---|---|
GEOSUPPORT_HOME | ~/.cache/duckdb-geosupport; bundle store. |
GEOSUPPORT_CATALOG | Optional local catalog file. |
GEOSUPPORT_RUNTIME | local or docker. |
GEOSUPPORT_DOCKER | Docker executable; default docker. |
GEOSUPPORT_DOCKER_IMAGE | Default duckdb-geosupport-runtime:0.2.0. |
Store/catalog settings are captured on first use; runtime settings apply at worker startup. Calls are serialized, with up to eight workers and a bounded per-worker cache. Lifecycle changes are outside SQL transactions. Cancellation and external-access policy integration remain unfinished; use trusted analytical sessions.
5. Geosupport errors and return messages
Geosupport returns rejected addresses as result rows. Inspect grc and message, plus grc2 and message2 for address lookups. These fields preserve the NYC engine's return codes and messages.
Example: an unrecognized street
This query submits an address with an unrecognized street name and captures the returned error fields:
WITH rejected AS (
SELECT gs_address('999999', 'NOT A REAL STREET', 'Mn',
release := '26c') AS geo
)
SELECT geo.grc, geo.message, geo.grc2, geo.message2,
geo.bbl, geo.bin, geo.latitude, geo.longitude
FROM rejected;
Captured 26C result, displayed by field:
| Field | Value |
|---|---|
| grc | EE |
| message | 'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'? |
| grc2 | EE |
| message2 | 'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'? |
| bbl | NULL |
| bin | NULL |
| latitude | NULL |
| longitude | NULL |
Here, both return codes are EE, and both messages report the unrecognized street and suggest a possible name. The BBL, BIN, and coordinates are NULL. The query completes and returns this row, so you can retain it for review alongside successful lookups.
For address results, check both return codes. The table example above filters rows where either code differs from 00 for review. Message text and suggestions depend on the input and quarterly release; keep the returned codes and messages with your results.