digipinindia
Version:
JS/TS NPM Package to encode and decode lattitude/longitude pair to digipin and vice a versa.
149 lines • 5.82 kB
JavaScript
/**
* DIGIPIN Encoder and Decoder Library
* Developed by India Post, Department of Posts
* Released under an open-source license for public use
*
* This module contains two main functions:
* - getDigiPin(lat, lon): Encodes latitude & longitude into a 10-digit alphanumeric DIGIPIN
* - getLatLngFromDigiPin(digiPin): Decodes a DIGIPIN back into its central latitude & longitude
*/
// Pre-compute character lookup map for O(1) character finding
const CHAR_TO_POSITION = new Map();
const DIGIPIN_GRID = [
['F', 'C', '9', '8'],
['J', '3', '2', '7'],
['K', '4', '5', '6'],
['L', 'M', 'P', 'T']
];
// Initialize character lookup map
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
CHAR_TO_POSITION.set(DIGIPIN_GRID[r][c], { row: r, col: c });
}
}
const BOUNDS = {
minLat: 2.5,
maxLat: 38.5,
minLon: 63.5,
maxLon: 99.5,
latRange: 36.0, // Pre-computed: maxLat - minLat
lonRange: 36.0 // Pre-computed: maxLon - minLon
};
const DIGIPIN_LENGTH = 10;
const GRID_SIZE = 4;
/**
* Encodes latitude and longitude into a DIGIPIN
* @param {number} lat - Latitude (2.5 to 38.5)
* @param {number} lon - Longitude (63.5 to 99.5)
* @returns {string} 10-digit DIGIPIN with hyphens
* @throws {Error} If coordinates are out of bounds
*/
export function getDigiPin(lat, lon) {
// Input validation with early return
if (lat < BOUNDS.minLat || lat > BOUNDS.maxLat) {
throw new Error(`Latitude ${lat} out of range [${BOUNDS.minLat}, ${BOUNDS.maxLat}]`);
}
if (lon < BOUNDS.minLon || lon > BOUNDS.maxLon) {
throw new Error(`Longitude ${lon} out of range [${BOUNDS.minLon}, ${BOUNDS.maxLon}]`);
}
let minLat = BOUNDS.minLat;
let maxLat = BOUNDS.maxLat;
let minLon = BOUNDS.minLon;
let maxLon = BOUNDS.maxLon;
// Pre-allocate string with exact size (10 chars + 2 hyphens = 12)
let result = '';
result += reserveCapacity(13); // Hint to JS engine for string capacity
for (let level = 0; level < DIGIPIN_LENGTH; level++) {
// Use bit shifting for division by 4 (faster than regular division)
const latRange = maxLat - minLat;
const lonRange = maxLon - minLon;
const latDiv = latRange * 0.25; // Multiply by 0.25 instead of divide by 4
const lonDiv = lonRange * 0.25;
// Calculate grid position with bit operations where possible
const latOffset = lat - minLat;
const lonOffset = lon - minLon;
// Avoid Math.min/Math.max by using conditional logic
let row = Math.floor(latOffset / latDiv);
row = row > 3 ? 3 : row;
row = 3 - row; // Reverse row logic
row = row < 0 ? 0 : row;
let col = Math.floor(lonOffset / lonDiv);
col = col > 3 ? 3 : col;
col = col < 0 ? 0 : col;
result += DIGIPIN_GRID[row][col];
// Add hyphen after 3rd and 6th characters (unrolled condition)
if (level === 2 || level === 5) {
result += '-';
}
// Update bounds for next level - use pre-calculated divisions
const newMinLat = minLat + latDiv * (3 - row);
maxLat = newMinLat + latDiv;
minLat = newMinLat;
const newMinLon = minLon + lonDiv * col;
maxLon = newMinLon + lonDiv;
minLon = newMinLon;
}
return result;
}
// Helper function to hint string capacity (modern JS engines optimize this)
function reserveCapacity(size) {
return '';
}
/**
* Decodes a DIGIPIN back to its central coordinates
* @param {string} digiPin - DIGIPIN string (with or without hyphens)
* @returns {Object} Object with latitude and longitude as numbers
* @throws {Error} If DIGIPIN is invalid
*/
export function getLatLngFromDigiPin(digiPin) {
// Remove hyphens and validate length - avoid regex for better performance
let pin = '';
for (let i = 0; i < digiPin.length; i++) {
const char = digiPin[i];
if (char !== '-') {
pin += char;
}
}
if (pin.length !== DIGIPIN_LENGTH) {
throw new Error(`Invalid DIGIPIN length: ${pin.length}, expected ${DIGIPIN_LENGTH}`);
}
let minLat = BOUNDS.minLat;
let maxLat = BOUNDS.maxLat;
let minLon = BOUNDS.minLon;
let maxLon = BOUNDS.maxLon;
// Pre-calculate commonly used values
const gridSizeReciprocal = 0.25; // 1/4
for (let i = 0; i < DIGIPIN_LENGTH; i++) {
const char = pin[i];
const position = CHAR_TO_POSITION.get(char);
if (!position) {
throw new Error(`Invalid character '${char}' at position ${i} in DIGIPIN`);
}
const { row, col } = position;
// Use multiplication instead of division for better performance
const latRange = maxLat - minLat;
const lonRange = maxLon - minLon;
const latDiv = latRange * gridSizeReciprocal;
const lonDiv = lonRange * gridSizeReciprocal;
// Calculate new bounds (reverse logic for latitude maintained)
// Use direct arithmetic instead of multiplying by (row + 1)
const newMinLat = maxLat - latDiv * row - latDiv;
const newMaxLat = maxLat - latDiv * row;
const newMinLon = minLon + lonDiv * col;
const newMaxLon = newMinLon + lonDiv;
// Update bounds for next iteration
minLat = newMinLat;
maxLat = newMaxLat;
minLon = newMinLon;
maxLon = newMaxLon;
}
// Calculate center coordinates using bit shift for division by 2
const centerLat = (minLat + maxLat) * 0.5;
const centerLon = (minLon + maxLon) * 0.5;
// Use more efficient rounding - avoid parseFloat(toFixed())
return {
latitude: Math.round(centerLat * 1000000) / 1000000,
longitude: Math.round(centerLon * 1000000) / 1000000
};
}
//# sourceMappingURL=index.js.map