@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
pnpm add @studnicky/geo-resolverRequires @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:
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:
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:
/** 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');