UNPKG

digipin

Version:

An npm package for getting the digipin value from lat and long

146 lines (145 loc) 6.42 kB
"use strict"; /** * 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;