digipin
Version:
An npm package for getting the digipin value from lat and long
146 lines (145 loc) • 6.42 kB
JavaScript
;
/**
* DIGIPIN encoder / decoder (2025 grid) – sentinel-string flavour
* ===============================================================
* • `getDIGIPINFromLatLon()` → DIGIPIN | 'Out of Bound'
* • `getLatLonFromDIGIPIN()` → { latitude, longitude } | 'Invalid DIGIPIN'
* • `getBoundsFromDIGIPIN()` → { minLat, maxLat, minLon, maxLon } | 'Invalid DIGIPIN'
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBoundsFromDIGIPIN = exports.getLatLonFromDIGIPIN = exports.getDIGIPINFromLatLon = void 0;
/* eslint-disable @typescript-eslint/ban-ts-comment */
// ───────────────────────────────────────────────────────────
// Constants
// ───────────────────────────────────────────────────────────
const DIGIPIN_GRID = [
['F', 'C', '9', '8'],
['J', '3', '2', '7'],
['K', '4', '5', '6'],
['L', 'M', 'P', 'T'],
];
// Create a lookup map for faster character-to-position lookups
const CHAR_POSITION_MAP = new Map();
for (let r = 0; r < DIGIPIN_GRID.length; r++) {
for (let c = 0; c < DIGIPIN_GRID[r].length; c++) {
CHAR_POSITION_MAP.set(DIGIPIN_GRID[r][c], [r, c]);
}
}
const BOUNDS = Object.freeze({
minLat: 2.5,
maxLat: 38.5,
minLon: 63.5,
maxLon: 99.5,
});
// ───────────────────────────────────────────────────────────
// Encode
// ───────────────────────────────────────────────────────────
/**
* Encode a latitude / longitude into a 10-character DIGIPIN.
* Returns `'Out of Bound'` if the input lies outside the supported area
* or is not a finite number.
*/
function getDIGIPINFromLatLon(lat, lon) {
if (!Number.isFinite(lat) || !Number.isFinite(lon))
return 'Out of Bound';
if (lat < BOUNDS.minLat || lat > BOUNDS.maxLat)
return 'Out of Bound';
if (lon < BOUNDS.minLon || lon > BOUNDS.maxLon)
return 'Out of Bound';
// Round to maximum precision for consistency
lat = Number(lat.toFixed(6));
lon = Number(lon.toFixed(6));
let minLat = BOUNDS.minLat;
let maxLat = BOUNDS.maxLat;
let minLon = BOUNDS.minLon;
let maxLon = BOUNDS.maxLon;
let pin = '';
for (let level = 1; level <= 10; level++) {
const latDiv = (maxLat - minLat) / 4;
const lonDiv = (maxLon - minLon) / 4;
// Row logic reversed (south→north index)
const row = 3 - Math.floor((lat - minLat) / latDiv);
const col = Math.floor((lon - minLon) / lonDiv);
const r = Math.min(Math.max(row, 0), 3);
const c = Math.min(Math.max(col, 0), 3);
pin += DIGIPIN_GRID[r][c];
if (level === 3 || level === 6)
pin += '-';
// Narrow bounds
maxLat = minLat + latDiv * (4 - r);
minLat = minLat + latDiv * (3 - r);
minLon = minLon + lonDiv * c;
maxLon = minLon + lonDiv;
}
return pin;
}
exports.getDIGIPINFromLatLon = getDIGIPINFromLatLon;
// ───────────────────────────────────────────────────────────
// Internal decode helper
// ───────────────────────────────────────────────────────────
function decodeInternal(pin) {
const clean = pin.replace(/-/g, '');
if (clean.length !== 10)
return 'Invalid DIGIPIN';
let minLat = BOUNDS.minLat;
let maxLat = BOUNDS.maxLat;
let minLon = BOUNDS.minLon;
let maxLon = BOUNDS.maxLon;
for (const ch of clean) {
// O(1) lookup using the map
const position = CHAR_POSITION_MAP.get(ch);
if (!position)
return 'Invalid DIGIPIN';
const [r, c] = position;
const latDiv = (maxLat - minLat) / 4;
const lonDiv = (maxLon - minLon) / 4;
const lat1 = maxLat - latDiv * (r + 1);
const lat2 = maxLat - latDiv * r;
const lon1 = minLon + lonDiv * c;
const lon2 = minLon + lonDiv * (c + 1);
minLat = lat1;
maxLat = lat2;
minLon = lon1;
maxLon = lon2;
}
return {
centre: {
latitude: Number(((minLat + maxLat) / 2).toFixed(6)),
longitude: Number(((minLon + maxLon) / 2).toFixed(6)),
},
minLat, maxLat, minLon, maxLon,
};
}
// ───────────────────────────────────────────────────────────
// Public decode functions
// ───────────────────────────────────────────────────────────
/**
* Decode a DIGIPIN and return its centre point.
* Returns `'Invalid DIGIPIN'` on error.
*/
function getLatLonFromDIGIPIN(pin) {
const result = decodeInternal(pin);
return result === 'Invalid DIGIPIN' ? result : result.centre;
}
exports.getLatLonFromDIGIPIN = getLatLonFromDIGIPIN;
/**
* Decode a DIGIPIN and return its bounding box.
* Returns `'Invalid DIGIPIN'` on error.
*/
function getBoundsFromDIGIPIN(pin) {
const result = decodeInternal(pin);
if (result === 'Invalid DIGIPIN')
return result;
const { minLat, maxLat, minLon, maxLon } = result;
return { minLat, maxLat, minLon, maxLon };
}
exports.getBoundsFromDIGIPIN = getBoundsFromDIGIPIN;
// ───────────────────────────────────────────────────────────
// Default export bundle
// ───────────────────────────────────────────────────────────
const digipin = {
getDIGIPINFromLatLon,
getLatLonFromDIGIPIN,
getBoundsFromDIGIPIN,
};
exports.default = digipin;