Skip to content

Instantly share code, notes, and snippets.

@answerquest
Created June 8, 2025 08:50
Show Gist options
  • Save answerquest/7cc8da95ecb36c327df7be48f4484cb7 to your computer and use it in GitHub Desktop.
Save answerquest/7cc8da95ecb36c327df7be48f4484cb7 to your computer and use it in GitHub Desktop.
Python implementation of India's DigiPIN geocoding address system
# adapted from https://github.com/CEPT-VZG/digipin/blob/main/src/digipin.js
import re
import math
REGEX_PATTERN_DIGIPIN = re.compile(r'^[2-9CFJKLMPT]{3}-[2-9CFJKLMPT]{3}-[2-9CFJKLMPT]{4}$')
REGEX_PATTERN_DIGIPIN_SEARCH = re.compile(r'[2-9CFJKLMPT]{3}-[2-9CFJKLMPT]{3}-[2-9CFJKLMPT]{4}')
DIGIPIN_GRID = [
['F', 'C', '9', '8'],
['J', '3', '2', '7'],
['K', '4', '5', '6'],
['L', 'M', 'P', 'T']
]
DIGIPIN_BOUNDS = {
'minLat': 2.5,
'maxLat': 38.5,
'minLon': 63.5,
'maxLon': 99.5
}
def validate_digipin(d):
"""Validates if a given string is a valid digipin. Should be in format like: 4FP-994-M7C6"""
global REGEX_PATTERN_DIGIPIN
return bool(REGEX_PATTERN_DIGIPIN.match(d))
def extract_digipin(d):
"""Finds first digipin in a larger string and extracts it."""
global REGEX_PATTERN_DIGIPIN_SEARCH
match = REGEX_PATTERN_DIGIPIN_SEARCH.search(d)
if match:
return match.group(0)
return None
def get_digipin(lat, lon):
"""analogous to `function getDigiPin` in https://github.com/CEPT-VZG/digipin/blob/main/src/digipin.js"""
if lat < DIGIPIN_BOUNDS['minLat'] or lat > DIGIPIN_BOUNDS['maxLat']:
raise ValueError('Latitude out of range')
if lon < DIGIPIN_BOUNDS['minLon'] or lon > DIGIPIN_BOUNDS['maxLon']:
raise ValueError('Longitude out of range')
min_lat = DIGIPIN_BOUNDS['minLat']
max_lat = DIGIPIN_BOUNDS['maxLat']
min_lon = DIGIPIN_BOUNDS['minLon']
max_lon = DIGIPIN_BOUNDS['maxLon']
digipin = ''
for level in range(1, 11):
lat_div = (max_lat - min_lat) / 4
lon_div = (max_lon - min_lon) / 4
# REVERSED row logic (to match original)
row = 3 - math.floor((lat - min_lat) / lat_div)
col = math.floor((lon - min_lon) / lon_div)
row = max(0, min(row, 3))
col = max(0, min(col, 3))
digipin += DIGIPIN_GRID[row][col]
if level == 3 or level == 6:
digipin += '-'
# Update bounds (reverse logic for row)
max_lat = min_lat + lat_div * (4 - row)
min_lat = min_lat + lat_div * (3 - row)
min_lon = min_lon + lon_div * col
max_lon = min_lon + lon_div
return digipin
def get_latlng_from_digipin(digipin):
"""analogous to `function getLatLngFromDigiPin` in https://github.com/CEPT-VZG/digipin/blob/main/src/digipin.js,
But slight difference in output, this one gives simpler 2 values lat, lon in response instead of making a json array.
"""
if not validate_digipin(digipin):
raise ValueError('Invalid DIGIPIN')
pin = digipin.replace('-', '')
if len(pin) != 10:
raise ValueError('Invalid DIGIPIN')
min_lat = DIGIPIN_BOUNDS['minLat']
max_lat = DIGIPIN_BOUNDS['maxLat']
min_lon = DIGIPIN_BOUNDS['minLon']
max_lon = DIGIPIN_BOUNDS['maxLon']
for i in range(10):
char = pin[i]
found = False
ri = -1
ci = -1
# Locate character in DIGIPIN grid
for r in range(4):
for c in range(4):
if DIGIPIN_GRID[r][c] == char:
ri = r
ci = c
found = True
break
if found:
break
if not found:
raise ValueError('Invalid character in DIGIPIN')
lat_div = (max_lat - min_lat) / 4
lon_div = (max_lon - min_lon) / 4
lat1 = max_lat - lat_div * (ri + 1)
lat2 = max_lat - lat_div * ri
lon1 = min_lon + lon_div * ci
lon2 = min_lon + lon_div * (ci + 1)
# Update bounding box for next level
min_lat = lat1
max_lat = lat2
min_lon = lon1
max_lon = lon2
center_lat = round((min_lat + max_lat) / 2, 6)
center_lon = round((min_lon + max_lon) / 2, 6)
return center_lat, center_lon
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment