@smarthivelabs-devs/geocore-core
v1.6.6
Published
Geocore universal SDK — geocoding, routing, places for browser and Node.js
Readme
@smarthivelabs-devs/geocore-core
Universal geospatial SDK for browser and Node.js. One API across 8 providers, 14 geo-operations, weather, and the SmartHive Pricing Engine — with automatic fallback, circuit breaker, rate limiting, and caching built in.
Installation
npm install @smarthivelabs-devs/geocore-core
# or
pnpm add @smarthivelabs-devs/geocore-coreMap rendering libs (mapbox-gl, @googlemaps/js-api-loader, @googlemaps/markerclusterer) are bundled as dependencies and auto-install.
Choosing a Mode
The SDK has two completely separate clients — pick the right one first:
| | Geocore (direct) | BackendClient (backend proxy) |
|---|---|---|
| API keys location | In your app | On your Geocore server |
| Fallback | Client-side, automatic | Server-side |
| Circuit breaker | Per-provider, in-app | Server handles it |
| Provider pinning | { provider: 'mapbox' } | { provider: 'mapbox' } |
| Weather / Pricing | ✗ | ✓ |
| Use when | Prototyping, CLI tools, SSR | Production apps, mobile, multi-tenant |
Two Modes
Direct Mode — API keys in your app
Provider keys live in the client. Best for prototyping and tools with no backend.
import { Geocore } from '@smarthivelabs-devs/geocore-core';
const geocore = new Geocore({
apiKeys: {
mapbox: { secretToken: 'sk.eyJ1...', publicToken: 'pk.eyJ1...' },
google: 'AIza...',
tomtom: 'abc123',
},
provider: {
preferredOrder: ['mapbox', 'google', 'tomtom'],
fallbackEnabled: true,
timeout: 8000,
},
});Backend Mode — Geocore API proxy
API keys stay on your server. The SDK talks to your Geocore backend, which handles provider fallback. Weather and pricing only work in this mode.
const geocore = new Geocore({
apiKey: 'your-geocore-api-key', // apiUrl optional — defaults to production
});Normalized Address Components
Every geocode and reverse-geocode result now returns normalized components regardless of which provider answered. No more guessing whether the city lives in city, locality, place, or municipality.
import type { AddressComponents } from '@smarthivelabs-devs/geocore-core';
// components shape — same keys from every provider:
interface AddressComponents {
streetNumber?: string; // house / building number
street?: string; // road / street name
area?: string; // neighborhood, suburb, or quarter
city?: string; // city, town, or village
county?: string; // county or local district
state?: string; // state, region, or province
country?: string; // full country name
countryCode?: string; // ISO 3166-1 alpha-2 (e.g. "GH")
postalCode?: string;
poiName?: string; // point-of-interest or establishment name
}getDisplayAddress(components, fallback?)
Build a clean, human-readable display string from any AddressComponents object. Automatically orders parts from specific → general and removes duplicates.
import { getDisplayAddress } from '@smarthivelabs-devs/geocore-core';
const rev = await geocore.reverse(6.6885, -1.6244);
// rev.data.components → { area: 'Adum', city: 'Kumasi', state: 'Ashanti Region', country: 'Ghana' }
const label = getDisplayAddress(rev.data.components);
// → "Adum, Kumasi, Ashanti Region, Ghana"
const label = getDisplayAddress(rev.data.components, 'Unknown location');
// → fallback used when components are emptyGeo Operations (14)
Geocoding
// Address → coordinates
const result = await geocore.geocode('1600 Amphitheatre Parkway, Mountain View, CA');
console.log(result.data.coordinates); // { lat: 37.4224, lng: -122.0840 }
console.log(result.data.components?.city); // 'Mountain View'
console.log(result.data.components?.state); // 'California'
console.log(result.provider); // 'mapbox'
// Coordinates → address (normalized components, all providers)
const rev = await geocore.reverse(37.4224, -122.0840);
console.log(rev.data.formattedAddress);
console.log(rev.data.components?.city); // 'Mountain View'
console.log(rev.data.components?.area); // neighborhood if available
console.log(getDisplayAddress(rev.data.components));Directions
const route = await geocore.directions(
{ lat: 51.5074, lng: -0.1278 }, // from
{ lat: 48.8566, lng: 2.3522 }, // to
{ mode: 'driving' }
);
console.log(route.data.distance.text); // '341 km'
console.log(route.data.duration.text); // '3 hrs 15 min'
console.log(route.data.steps);Places Search
const places = await geocore.places('coffee', { lat: 51.5074, lng: -0.1278 }, { radius: 1000 });
const nearby = await geocore.nearbySearch('restaurant', { lat: 51.5074, lng: -0.1278 });
const suggest = await geocore.autocomplete('Hyde Park Lo');Elevation, Timezone, Traffic
const elev = await geocore.elevation(51.5074, -0.1278);
console.log(elev.data.elevation); // metres above sea level
const tz = await geocore.timezone(51.5074, -0.1278);
console.log(tz.data.timeZoneId); // 'Europe/London'
const traffic = await geocore.traffic(51.5074, -0.1278);
console.log(traffic.data.congestionLevel); // 'light'Isochrone, Distance Matrix, Snap to Road, Route Optimize
// Reachability polygon (15 & 30 min drive)
const iso = await geocore.isochrone(51.5074, -0.1278, [15, 30], { mode: 'driving' });
// Distance/duration grid
const matrix = await geocore.distanceMatrix(
['London, UK', 'Paris, France'],
['Berlin, Germany', 'Madrid, Spain'],
);
// Snap GPS points to road network
const snapped = await geocore.snapToRoad([
{ lat: 51.501, lng: -0.124 },
{ lat: 51.505, lng: -0.120 },
]);
// Optimise waypoint order (TSP)
const optimised = await geocore.routeOptimize([
'London, UK', 'Paris, France', 'Berlin, Germany',
]);
// Static map image URL
const map = await geocore.staticMap(51.5074, -0.1278, { zoom: 14, width: 800, height: 600 });Weather (backend mode required)
const geocore = new Geocore({ apiKey: 'your-key' });
// Current conditions
const now = await geocore.weather(51.5074, -0.1278);
console.log(now.data.temperature); // °C
console.log(now.data.description); // 'Partly cloudy'
console.log(now.data.humidity); // 72
// 7-day forecast (max 14 days)
const forecast = await geocore.forecast(51.5074, -0.1278, 7);
forecast.data.days.forEach(day => {
console.log(`${day.date}: ${day.tempMin}–${day.tempMax}°C, ${day.description}`);
});Weather providers: Open-Meteo (free, no key) → OpenWeatherMap (free tier fallback).
SmartHive Pricing Engine (backend mode required)
The Pricing Engine converts a base price into the user's local currency using stored exchange rates — no per-request API calls. Rates are refreshed automatically on the 1st and 15th of each month.
Location-based pricing
const geocore = new Geocore({ apiKey: 'your-key' });
// User in Nigeria — Hivedemia Pro priced at 100 GHS
const price = await geocore.localizePrice(100, 'GHS', 6.5244, 3.3792);
console.log(price.data);
// {
// amount: 4500,
// currency: 'NGN',
// symbol: '₦',
// country: 'NG',
// originalAmount: 100,
// originalCurrency: 'GHS',
// rate: 45.0,
// }Manual country override (country picker UI)
// User manually selects United States
const price = await geocore.localizePriceByCountry(100, 'GHS', 'US');
// { amount: 9.99, currency: 'USD', symbol: '$', country: 'US' }Direct FX lookup
const rate = await geocore.fxRate('USD', 'EUR');
console.log(rate.data.rate); // 0.92
const rates = await geocore.fxRates('USD');
console.log(rates.data.rates); // { EUR: 0.92, GBP: 0.79, NGN: 1580, ... }Provider Capability Table
| Provider | Geocode | Reverse | Autocomplete | Places | Nearby | Directions | Distance Matrix | Elevation | Isochrone | Traffic | Timezone | Snap to Road | Route Optimize | Static Map | Weather | |----------|:-------:|:-------:|:------------:|:------:|:------:|:----------:|:---------------:|:---------:|:---------:|:-------:|:--------:|:------------:|:--------------:|:----------:|:-------:| | Mapbox | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | | Google Maps | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ | — | ✓ | — | | TomTom | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | ✓ | — | — | — | ✓ | — | | HERE | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | ✓ | ✓ | — | — | — | — | | Azure Maps | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | ✓ | ✓ | — | — | ✓ | — | | LocationIQ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — | — | — | ✓ | — | | OpenRouteService | ✓ | ✓ | — | — | — | ✓ | ✓ | ✓ | ✓ | — | — | ✓ | ✓ | — | — | | Geoapify | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — | — | — | ✓ | — | | Open-Meteo | — | — | — | — | — | — | — | — | — | — | — | — | — | — | ✓ | | OpenWeatherMap | — | — | — | — | — | — | — | — | — | — | — | — | — | — | ✓ |
Provider Selection
Per-call provider pinning — force a specific provider for one call, bypassing the fallback chain:
// Direct mode — uses Mapbox even if Google is first in preferredOrder
const result = await geocore.geocode('Accra Mall, Ghana', { provider: 'mapbox' });
const rev = await geocore.reverse(5.636, -0.174, { provider: 'google' });
// Backend mode — passes ?provider=mapbox to the Geocore API
const result = await client.geocode('Accra Mall', { provider: 'here' });
const rev = await client.reverse(5.636, -0.174, { provider: 'locationiq' });If the requested provider is not configured or its key is invalid, it throws a GeocoreError with code PROVIDER_ERROR — it will not silently fall back to another provider when you explicitly pinned one.
Fallback Mechanism
Direct mode — fully automatic, client-side:
const geocore = new Geocore({
apiKeys: {
mapbox: { secretToken: 'sk...' },
google: 'AIza...',
here: 'your-key',
},
providers: {
// Tried in this order — next is used only if current fails
preferredOrder: ['mapbox', 'google', 'here'],
fallbackEnabled: true, // default: true
timeout: 8000,
},
});
const result = await geocore.geocode('Kumasi, Ghana');
console.log(result.provider); // which provider actually answered
console.log(result.fallbackUsed); // true if primary failed and fallback was usedFallback triggers on: network errors, timeouts, rate limits, zero results, any exception.
To disable fallback (fail fast, use only the primary provider):
providers: { preferredOrder: ['mapbox'], fallbackEnabled: false }Backend mode — server-side. The client always calls one endpoint. The Geocore server handles the fallback chain internally (configured via its env vars). Each response still includes provider so you know who answered.
Fallback Chain (default order)
mapbox → google → tomtom → here → azure → locationiq → openrouteservice → geoapifyOnly providers with valid API keys are actually registered — unconfigured providers are skipped transparently.
Provider Environment Variables (backend / server-side)
| Provider | Env Var |
|----------|---------|
| Mapbox | MAPBOX_PUBLIC_TOKEN, MAPBOX_SECRET_TOKEN |
| Google Maps | GOOGLE_API_KEY |
| TomTom | TOMTOM_API_KEY |
| HERE | HERE_API_KEY |
| Azure Maps | AZURE_API_KEY |
| LocationIQ | LOCATIONIQ_API_KEY |
| OpenRouteService | OPENROUTESERVICE_API_KEY |
| Geoapify | GEOAPIFY_API_KEY |
| OpenWeatherMap | OPENWEATHER_API_KEY (optional — Open-Meteo is the free fallback) |
Error Handling
import { GeocoreError } from '@smarthivelabs-devs/geocore-core';
try {
const result = await geocore.geocode('123 Fake Street');
} catch (err) {
if (err instanceof GeocoreError) {
console.log(err.code); // 'PROVIDER_ERROR' | 'TIMEOUT' | 'NO_RESULTS' | ...
console.log(err.provider); // which provider failed
console.log(err.statusCode); // HTTP status if applicable
}
}Error codes: PROVIDER_ERROR · TIMEOUT · RATE_LIMIT_EXCEEDED · CIRCUIT_BREAKER_OPEN · NO_RESULTS · INVALID_COORDINATES · NETWORK_ERROR · VALIDATION_ERROR
BackendClient (direct backend integration)
If you only need the HTTP layer without the full Geocore class (e.g. in a server component or a thin wrapper):
import { BackendClient } from '@smarthivelabs-devs/geocore-core';
const client = new BackendClient({
apiKey: 'your-geocore-api-key',
// apiUrl defaults to https://geocore.smarthivelabs.dev/api
timeout: 10000,
logger: { enabled: true, level: 'debug' },
});
const result = await client.geocode('Accra, Ghana');
const weather = await client.weather(5.6037, -0.1870);
const price = await client.localizePrice(100, 'GHS', { lat: 6.5244, lng: 3.3792 });
// Provider registry (no auth)
const providers = await client.getProviders();
const supported = await client.getProvidersForOperation('directions');Cache & Resilience
const geocore = new Geocore({
apiKeys: { mapbox: { secretToken: '...' } },
resilience: {
cache: {
enabled: true,
ttl: {
geocode: 86400, // 24 hours
reverse: 86400,
directions: 3600, // 1 hour
places: 1800, // 30 min
traffic: 30, // 30 seconds
},
},
circuitBreaker: {
failureThreshold: 5, // open after 5 consecutive failures
resetTimeout: 30000, // retry after 30 seconds
},
rateLimit: {
requestsPerSecond: 10,
},
retry: {
maxAttempts: 3,
initialDelayMs: 1000,
},
},
});
// Toggle cache at runtime
geocore.setCache(false); // disable
geocore.setCache(true, { traffic: 60 }); // enable with custom TTL
// Inspect provider + circuit breaker health
const status = geocore.getStatus();
console.log(status.providers); // [{ name, status, failures }]
console.log(status.cache); // { enabled, size }Interactive Map (browser)
const map = await geocore.createMap({
container: 'map', // DOM element ID
center: [51.5074, -0.1278],
zoom: 12,
});
// Add markers
map.addMarker({ lat: 51.5074, lng: -0.1278, title: 'London' });
// Draw route polyline
map.addPolyline({ coordinates: route.data.polyline, color: '#3B82F6' });TypeScript
All methods are fully typed. Key exports:
import type {
GeocodingResult, ReverseGeocodingResult,
DirectionsResult, RouteOptimizeResult, SnapToRoadResult,
PlacesResult, NearbySearchResult, AutocompleteResult,
ElevationResult, IsochroneResult, TimezoneResult,
DistanceMatrixResult, TrafficResult, StaticMapResult,
WeatherCurrentResult, WeatherForecastResult,
PricingResult, FxRateResult, FxRatesResult,
GeocodingOptions, DirectionsOptions, PlacesOptions,
WeatherOptions, PricingOptions,
AddressComponents, // normalized address fields
Coordinates, SDKResponse, GeocoreConfig,
} from '@smarthivelabs-devs/geocore-core';
// Value export (not type-only)
import { getDisplayAddress } from '@smarthivelabs-devs/geocore-core';Changelog
1.6.0
- Normalized
components— all providers now return the same standard keys (streetNumber,street,area,city,county,state,country,countryCode,postalCode,poiName). Previously each provider used different field names. - Fixed Mapbox reverse geocoding — was storing full place names (e.g.
"Kumasi, Ashanti Region, Ghana") in each component key instead of just the specific level name (e.g."Kumasi"). - Added
getDisplayAddress(components, fallback?)— utility that builds a clean, deduplicated display string from anyAddressComponentsobject.
License
MIT — SmartHive Labs
