duckdb-geosupportGitHub ↗

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:

bblbinlatitudelongitudegrcgrc2
1001680032100183140.715669-74.0023520000

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;
idbblbinlatitudelongitudegrcgrc2
11001680032100183140.715669-74.0023520000
21008350041101586240.748452-73.9857120000
34002770001400353340.744071-73.9360770000
4NULLNULLNULLNULLEEEE

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';
idgrcgrc2messagemessage2
4EEEE'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

SignaturePurpose
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
CodeAliases
1—
1A—
1BADDRESS
1E—
1NGET_STREET_CODE, STREET_NAME_TO_STREET_CODE
2INTERSECTION
2WINTERSECTIONS, INTERSECTION_WIDE
3STREET_SEGMENT
3CBLOCKFACE
3SSTREET_STRETCH
APADDRESS_POINT
BBBROWSE_BACK
BFBROWSE_FORWARD
BLBBL, LOT, TAX_LOT
BNBIN, BUILDING
DGET_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

FieldTypeMeaning
bbl, binVARCHARTax lot and building identifiers.
latitude, longitudeDOUBLECoordinates, when available.
grc, grc2VARCHARPrimary and secondary return codes.
message, message2VARCHARCorresponding engine messages.
releaseVARCHARQuarterly release label, e.g. 26c.
engine_versionVARCHAREngine version, e.g. 26.3.
bundle_idVARCHARImmutable engine/data identity.
rawVARCHARFull parsed JSON output.

Unavailable result fields are NULL. Not every function returns coordinates or both return codes.

Release management

SQLPurpose
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
VariableDefault / purpose
GEOSUPPORT_HOME~/.cache/duckdb-geosupport; bundle store.
GEOSUPPORT_CATALOGOptional local catalog file.
GEOSUPPORT_RUNTIMElocal or docker.
GEOSUPPORT_DOCKERDocker executable; default docker.
GEOSUPPORT_DOCKER_IMAGEDefault 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:

FieldValue
grcEE
message'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'?
grc2EE
message2'NOT A REAL STREET' NOT RECOGNIZED. IS IT 'NOTRE DAME SCHOOL'?
bblNULL
binNULL
latitudeNULL
longitudeNULL

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.