Skip to content

@studnicky/geo-resolver

Multi-modal geo resolution: address, IP, coordinate, and phone-based location lookup with country/timezone/locale detail.

Resolves a location signal — a postal address, an IP, a WGS-84 coordinate, or an E.164 phone number — to a fused location with country, region, locality, timezone, locale, continent, and privacy jurisdiction. GeoResolver fuses several signals gathered for the same subject into one higher-confidence result, weighting each modality and back-filling empty fields from lower-weight candidates. Coordinate, phone, and offline reverse-geocoding paths run entirely offline; address and IP lookups call out to Nominatim and freeipapi.com respectively.

Install

bash
pnpm add @studnicky/geo-resolver

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

Usage

Geo is the discoverable static facade for offline lookups: country-code normalization/validation, centroid/continent/locale derivation, and calling-code resolution:

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

import { Geo } from '../src/index.js';

function demonstrateGeoStaticLookups(): { 'berlin': GeoLookupOutcomeType; 'normalized': string | null } {
  // normalize a country name or alpha-3 code to ISO 3166-1 alpha-2
  const normalized = Geo.normalizeCountryCode('Germany');
  console.log('normalized country:', normalized);

  // validate an ISO 3166-1 alpha-2 code
  console.log('is valid code (DE):', Geo.isValidCountryCode('DE'));
  console.log('is valid code (ZZ):', Geo.isValidCountryCode('ZZ'));

  // resolve a country's centroid, continent, and primary locale
  const centroid = Geo.centroidForCountry('DE');
  console.log('DE centroid:', centroid);
  console.log('DE continent:', Geo.continentForCountry('DE'));
  console.log('DE locale:', Geo.localeForCountry('DE'));

  // resolve a locale tag's primary IANA timezone
  console.log('en-US timezone:', Geo.timezoneForLocale('en-US'));

  // reverse-geocode a coordinate offline (no network, no API key)
  const berlin = Geo.byCoordinatesOffline(52.52, 13.405);
  console.log('berlin candidate:', berlin.candidate.country, berlin.candidate.countryName);

  // resolve a phone number's calling code to a country, offline
  console.log('calling code for +1:', Geo.countryForCallingCode('+1 415 555 0100'));

  return { 'berlin': berlin, 'normalized': normalized };
}

Geohash and timezone lookup

Encode/decode WGS-84 coordinates to geohash strings and resolve a geohash to its timezone via the bundled GeohashTzMap:

ts
import { Geo, GeohashTzMap } from '../src/index.js';

function demonstrateGeohashTimezoneLookup(): {
  'box': { 'maxLat': number; 'maxLon': number; 'minLat': number; 'minLon': number } | null;
  'invalid': { 'country': string; 'locale': string; 'timezone': string; 'waterBody': string };
  'tokyo': { 'country': string; 'locale': string; 'timezone': string; 'waterBody': string };
} {
  // encode a WGS-84 coordinate to a geohash string
  const geohash = Geo.encodeGeohash(35.6812, 139.7671, 7);
  console.log('Tokyo geohash:', geohash);

  // decode a geohash back to its WGS-84 bounding box
  const box = Geo.decodeGeohash(geohash);
  console.log('decoded bounding box:', box);

  // resolve a coordinate's IANA timezone + country via the bundled geohash table
  const geohashTzMap = GeohashTzMap.default();
  const tokyo = Geo.byCoordinates(geohashTzMap, 35.6812, 139.7671);
  console.log('Tokyo timezone:', tokyo.timezone);
  console.log('Tokyo country:', tokyo.country);

  // non-finite input short-circuits to the empty shape, no geohash encode spent
  const invalid = Geo.byCoordinates(geohashTzMap, NaN, 139.7671);
  console.log('invalid coordinate result:', invalid);

  return { 'box': box, 'invalid': invalid, 'tokyo': tokyo };
}

Resolving offline signals

GeoResolver accepts coordinate, phone, or reverse-geocoded signals and fuses them into a single higher-confidence result — entirely offline, no network calls. Try it live — edit the code and press Execute:

Fusing offline location signals
/** resolveOfflineSignals — demonstrates GeoResolver.resolve/resolveAll over offline signals. Run: npx tsx examples/resolveOfflineSignals.ts */

import assert from 'node:assert/strict';

// #region usage
import type { GeoResolveResultType } from '../src/index.js';

import { GeoResolver } from '../src/index.js';

async function demonstrateResolveOfflineSignals(): Promise<{
  'byCoords': GeoResolveResultType;
  'byPhone': GeoResolveResultType;
  'fused': GeoResolveResultType;
}> {
  const resolver = GeoResolver.create();

  // resolve a single GPS coordinate via the offline geohash tables
  const byCoords = await resolver.resolve({ 'lat': 40.7128, 'lon': -74.006 });
  console.log('coords -> country:', byCoords.resolvedGeo.country);
  console.log('coords -> timezone:', byCoords.geoContext.timezone);

  // resolve a single phone number via the offline calling-code table
  const byPhone = await resolver.resolve({ 'phone': '+1 415 555 0100' });
  console.log('phone -> country:', byPhone.resolvedGeo.country);

  // resolveAll fuses several signals for the same subject into one result,
  // picking the highest-weight resolved candidate as the winner
  const fused = await resolver.resolveAll([{ 'lat': 51.5074, 'lon': -0.1278 }, { 'phone': '+44 20 7946 0958' }]);
  console.log('fused -> country:', fused.resolvedGeo.country);
  console.log('fused -> provenance:', fused.resolvedGeo.provenance);

  return { 'byCoords': byCoords, 'byPhone': byPhone, 'fused': fused };
}
// #endregion usage

const result = await demonstrateResolveOfflineSignals();

assert.equal(result.byCoords.resolvedGeo.country, 'US');
assert.equal(result.byPhone.resolvedGeo.country, 'US');
assert.equal(result.fused.resolvedGeo.country, 'GB');

console.log('resolveOfflineSignals: all assertions passed');
Output
Press Execute to run this example against the real library.

Source on GitHub