Skip to content

@studnicky/geo-layer-postal

US ZCTA postal-code lookup by coordinate, backed by a geohash-indexed cell table with adaptive-depth overrides.

This package resolves a (lat, lon) coordinate to the US ZCTA (ZIP Code Tabulation Area) code containing it, or null outside every mapped ZCTA. Cell classification, adaptive-depth override descent, and artifact encoding are delegated to @studnicky/geo-boundary-layer-kit; this package supplies only the ZCTA-specific NamedFeatureResolver oracle (properties.ZCTA5 extraction from TIGERweb GeoJSON) that the kit builds and looks up against.

Install

bash
pnpm add @studnicky/geo-layer-postal

Requires @studnicky:registry=https://npm.pkg.github.com in .npmrc.

Basic lookup

PostalLookup.default() loads the bundled synthetic fixture artifact, and exposes coverage and provenance metadata:

ts
import { PostalLookup } from '../src/index.js';

function logPlaceholderWarning(message: string): void {
  console.log('placeholder warning:', message);
}

function demonstrateBasicLookup(): { 'insideResult': string | null; 'outsideResult': string | null; 'synthetic': boolean } {
  // the layer's coverage — this layer only has boundary data for the US
  console.log('coverage:', PostalLookup.coverage);

  // PostalLookup.default() loads the bundled synthetic fixture artifact
  const lookup = PostalLookup.default({ 'onPlaceholderWarning': logPlaceholderWarning });

  // provenance of the loaded artifact
  console.log('metadata:', lookup.metadata);

  // a point inside one of the fixture's 4 synthetic ZCTA squares (near Philadelphia, PA)
  const insideResult = lookup.lookup(40.025, -75.075);
  console.log('lookup(40.025, -75.075):', insideResult);

  // a point outside every mapped ZCTA resolves to null, never throws
  const outsideResult = lookup.lookup(0, 0);
  console.log('lookup(0, 0):', outsideResult);

  return { 'insideResult': insideResult, 'outsideResult': outsideResult, 'synthetic': lookup.metadata.synthetic };
}

Building a custom artifact

PostalBuilder.build() builds a real artifact against live (or faked) US Census TIGERweb data, then PostalLookup.fromArtifact() loads it:

ts
import { PostalBuilder, PostalLookup } from '../src/index.js';

async function demonstrateBuildCustomArtifact(): Promise<{ 'cellCount': number; 'insideResult': string | null; 'outsideResult': string | null; 'sourceId': string; 'tupleCount': number }> {
  // a single synthetic ZCTA square near Philadelphia, PA — stands in for a real TIGERweb response
  const fakeFeatureCollection = {
    'features': [
      {
        'geometry': {
          'coordinates': [[
            [-75.10, 40.00],
            [-75.00, 40.00],
            [-75.00, 40.10],
            [-75.10, 40.10],
            [-75.10, 40.00]
          ]],
          'type': 'Polygon' as const
        },
        'properties': { 'ZCTA5': 'ZDEMO01' },
        'type': 'Feature' as const
      }
    ],
    'type': 'FeatureCollection' as const
  };

  const buildBbox: [number, number, number, number] = [-75.15, 39.95, -74.95, 40.15];

  const result = await PostalBuilder.build({
    'adapter': USCensusTigerAdapter.create(() => {
      const response = new Response(JSON.stringify(fakeFeatureCollection), { 'status': 200 });
      return Promise.resolve(response);
    }),
    'bbox':            buildBbox,
    'maxDescentDepth': 4
  });
  console.log('tupleCount:', result.tupleCount);
  console.log('cellCount:', result.cellCount);

  // build a lookup directly from the freshly-built artifact
  const lookup = PostalLookup.fromArtifact(result.artifact);

  // a point inside the synthetic square resolves to its ZCTA5 code
  const insideResult = lookup.lookup(40.05, -75.05);
  console.log('lookup(40.05, -75.05):', insideResult);

  // a point outside the build bbox resolves to null
  const outsideResult = lookup.lookup(0, 0);
  console.log('lookup(0, 0):', outsideResult);

  return {
    'cellCount':     result.cellCount,
    'insideResult':  insideResult,
    'outsideResult': outsideResult,
    'sourceId':      result.artifact.metadata.sourceId,
    'tupleCount':    result.tupleCount
  };
}

Source on GitHub