Sub-millisecond reverse geocoding for India, fully offline.
No API calls. No network latency. No rate limits.
Built for backend services, cron jobs, and analytics workloads that need deterministic geocode lookups.
- 📍 converts
lat, lontocity,state, optionaldistrictandpincode - 🔢 supports direct H3 index lookup via
geocodeH3() - ↩️ parent-cell fallback (
resolution 5 → 4) when exact cell has no data - ⚡ data loaded once per process — all subsequent lookups are in-memory map reads
- 🐛 optional debug mode traces load time and per-lookup timing
- 🔷 TypeScript-first — full type definitions included
| Feature | @aialok/lakhua |
Hosted API |
|---|---|---|
| Works offline | yes | no |
| API key required | no | usually yes |
| Network dependency | no | yes |
| Per-request cost | no | usually yes |
| Latency variability | low, in-process | network-dependent |
npm install @aialok/lakhua
# or
bun add @aialok/lakhuaimport { geocode } from "@aialok/lakhua";
const result = geocode(28.6139, 77.2090);
if (result) {
console.log(result.city, result.state);
}geocode(lat: number, lon: number, options?: GeocodeOptions): GeocodeResult | null
geocodeH3(h3Index: string, options?: GeocodeOptions): GeocodeResult | nullThese use the internal singleton geocoder — no class instantiation needed.
interface GeocodeOptions {
resolution?: number; // H3 resolution for geocode(lat, lon). Default: 5
fallback?: boolean; // Walk up to parent resolution on miss. Default: true
debug?: boolean; // Print load and lookup timings. Default: false
}interface GeocodeResult {
city: string;
state: string;
district?: string;
pincode?: string;
matched_h3: string; // H3 cell that matched (may be parent)
matched_resolution: number; // Resolution of the matched cell
}Returns null for invalid input or when no data exists for the given location.
import { ReverseGeocoder, ReverseGeoDataLoader } from "@aialok/lakhua";
ReverseGeocoder.getInstance()
ReverseGeoDataLoader.getInstance()Use these only when you need explicit control — e.g. testing or custom singleton lifecycle.
import { geocode } from "@aialok/lakhua";
const result = geocode(12.9716, 77.5946); // Bengaluru
if (result) {
console.log(result.city, result.state, result.pincode);
}import { geocodeH3 } from "@aialok/lakhua";
const result = geocodeH3("8560145bfffffff");
if (result) {
console.log(result.city);
}import { geocode } from "@aialok/lakhua";
const result = geocode(19.076, 72.8777, { debug: true });
// prints load + lookup timings to consoleimport { geocode } from "@aialok/lakhua";
const result = geocode(28.6139, 77.2090, { fallback: false });
// only checks resolution 5, no parent lookup- Convert
lat, lonto H3 at resolution 5 (or configured resolution). - Lookup the H3 key in in-memory store.
- If not found and fallback is enabled, check parent resolution 4.
- Return a result object, or
nullif no match exists.
lat, lon -> h3(res5) -> in-memory map -> optional parent(res4) -> result/null
- Indexing system: Uber H3
- Geographic source data: OpenStreetMap data by OpenStreetMap contributors
- Distribution model: precomputed JSON stores bundled with the package
- India-focused dataset
- H3 resolutions: 4 and 5
- Fields:
city,state - Optional fields:
district,pincode(when available)
- Node.js
- Bun
@aialok/lakhua reads packaged JSON files from local filesystem at runtime.
- Data is loaded into memory once on first call.
- Each lookup is a single map read — typically < 1ms.
- With fallback enabled, up to 2 map reads (resolution 5, then 4).
Use test/performance.test.ts to benchmark in your own environment.
When publishing numbers, include machine type, runtime version, and warm/cold process context.
- You need rooftop-level precision
- You need global coverage
- You need live POI or continuously updated location feeds
bun install
bun run lint
bun run format
bun run typecheck
bun testMIT