GIS & Geospatial Data (QGIS, GeoPandas, PostGIS) Module 5 – Python: Shapely and GeoPandas
Geocoding, Indian addresses and DIGIPIN
Forward and reverse geocoding, how to judge a match, public geocoders within their rules, PIN codes as delivery areas, and DIGIPIN from its specification.
What you will learn
- Explain forward and reverse geocoding, and judge an answer by its rank and its distance
- Use a public geocoder within its usage policy, with a cache, a name and its credit
- Implement DIGIPIN encoding and decoding from the Department of Posts' specification
- Check the post-office positions of a PIN code directory before mapping them
Before you start
What you’ll learn · Before you start
What you will learn
- Explain forward and reverse geocoding, and judge an answer by its rank and its distance
- Use a public geocoder within its usage policy, with a cache, a name and its credit
- Implement DIGIPIN encoding and decoding from the Department of Posts' specification
- Check the post-office positions of a PIN code directory before mapping them
Before you start
On this page
A survey typed “Station Road” for a house, and the geocoder answered with the middle of the street, 1,257 m from the door, without a warning. Every geocoding answer has a precision, and you must read it before you trust the point.
Here you judge answers by what they matched and where they landed, keep a public geocoder’s rules, and see why a PIN code names an area. Then you take DIGIPIN, ten symbols for every square of about 4 m across India, from its specification to an encoder.
Geocoding turns an address into a point, and every answer has a precision
geocoding: Turning text, such as an address or a place name, into coordinates. Reverse geocoding turns coordinates back into the nearest address or place. turns text, an address or a place name, into coordinates; reverse geocoding turns
coordinates back into an address. The answer is the geocoder’s best match: for a reverse query Nominatim,
OpenStreetMap’s geocoder, finds the closest suitable object and returns its address, not one computed for the exact
point (Nominatim, reverse geocoding). It reports what each
answer matched as its place_rank: 30 for a house or a point of interest, 28 for an interpolated address, 26 and 27
for streets, 20 for a neighbourhood, 17 and 18 for a town and 13 to 16 for a city
(Nominatim, place ranking). Here are six made-up
answers in Nominatim’s format, beside doors a survey measured:
"""Six made-up first answers in the shape of a Nominatim jsonv2 search result, one for each address of a survey in the
track's town, beside the door of each address as the survey measured it (WGS 84 degrees). No service produced them."""
TOWN_BOX = (18.0887, 75.0, 18.1430, 75.0757) # south, west, north, east of the town
LICENCE = "Made-up sample for this lesson; a real answer carries its service's licence here"
SURVEY = [
# (the address as typed, the surveyed door, the geocoder's first answer)
("H0001, Hill Street", (18.1261627, 75.0196281),
{"lat": "18.1261139", "lon": "75.0196621", "place_rank": 30, "addresstype": "house", "display_name": "H0001, Hill Street"}),
("45 Station Road", (18.1367008, 75.0323310),
{"lat": "18.1360229", "lon": "75.0327658", "place_rank": 28, "addresstype": "house", "display_name": "45, Station Road"}),
("Station Road", (18.0991005, 75.0327022),
{"lat": "18.1103986", "lon": "75.0330824", "place_rank": 26, "addresstype": "road", "display_name": "Station Road"}),
("Ward W07", (18.1185301, 75.0482080),
{"lat": "18.1158188", "lon": "75.0472620", "place_rank": 20, "addresstype": "neighbourhood", "display_name": "W07"}),
("The town", (18.1040638, 75.0642720),
{"lat": "18.1158209", "lon": "75.0378096", "place_rank": 18, "addresstype": "town", "display_name": "The town"}),
("12 Hill Stret", (18.1250428, 75.0202292),
{"lat": "18.3442330", "lon": "74.6829325", "place_rank": 30, "addresstype": "house", "display_name": "12, Hill Street, another town"}),
] """Judge six geocoder answers the way a survey can: what each answer is, from its place rank, how far it lies from the
door the survey measured, and whether it lies in the town at all."""
import math
from answers import SURVEY, TOWN_BOX
# The lowest search rank of each kind of place, as Nominatim's ranking documentation groups them.
KINDS = [(30, "house or place"), (28, "address range"), (26, "street"), (21, "dwelling or block"),
(20, "neighbourhood"), (19, "village or suburb"), (17, "town"), (13, "city"), (0, "region or country")]
def kind(rank):
return next(name for low, name in KINDS if rank >= low)
def metres(a, b):
"""Great-circle distance on a sphere of the Earth's mean radius, 6,371,008.8 m: close enough to rate a match."""
(la1, lo1), (la2, lo2) = (map(math.radians, p) for p in (a, b))
h = math.sin((la2 - la1) / 2) ** 2 + math.cos(la1) * math.cos(la2) * math.sin((lo2 - lo1) / 2) ** 2
return 2 * 6_371_008.8 * math.asin(math.sqrt(h))
def in_town(p):
s, w, n, e = TOWN_BOX
return s <= p[0] <= n and w <= p[1] <= e
print(f"{'address':<20}{'answer':<20}{'rank':>4} {'from the door':>13} in the town")
good = 0
for address, door, answer in SURVEY:
at = (float(answer["lat"]), float(answer["lon"]))
off = metres(door, at)
usable = answer["place_rank"] >= 28 and in_town(at)
good += usable
print(f"{address:<20}{kind(answer['place_rank']):<20}{answer['place_rank']:>4} {off:>11,.0f} m {'yes' if in_town(at) else 'NO'}")
print(f"\nanswers at address level and inside the town: {good} of {len(SURVEY)}") Output
address answer rank from the door in the town H0001, Hill Street house or place 30 7 m yes 45 Station Road address range 28 88 m yes Station Road street 26 1,257 m yes Ward W07 neighbourhood 20 318 m yes The town town 18 3,087 m yes 12 Hill Stret house or place 30 43,162 m NO answers at address level and inside the town: 2 of 6
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 match_quality.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Only 2 of the 6 answers can stand for a house. The rank says what was matched, not how far it lies: ward W07’s rank-20 answer landed 318 m from the door, the rank-26 street 1,257 m. The misspelt “12 Hill Stret” came back at rank 30, 43 km away in another town: a confident wrong answer that only the town’s box caught.
Trusting an answer because its coordinates look exact
A town-level answer has as many decimals as a house. Keep the rank beside every geocoded point, and test each point against the area it should fall in.
Checkpoint
Check: Geocoding and the precision of an answer
A public geocoder sets rules: one request a second, a name, a cache and credit
OpenStreetMap’s public Nominatim service is shared, and its usage policy sets the terms:
| Rule | What it means for a program |
|---|---|
| At most 1 request a second | for the whole application; 4 a minute for a script that runs longer than a day |
| Name the application | its own User-Agent or Referer: “stock User-Agents as set by http libraries will not do” |
| Cache and credit | answers kept on your side, shown with the attribution, under the ODbL’s share-alike terms |
| Bulk discouraged, no autocomplete | a small one-off bulk job at most, from one thread on one machine; no suggestions as you type in the browser |
The policy may change without notice, so read it before each new use. The programs of this lesson never call the service: they use made-up answers.
Checkpoint
Check: The rules of a public geocoder
A PIN code names a delivery area, and a DIGIPIN names a square of about 4 m
Indian addresses take many forms, and the six-digit PIN code (Postal Index Number): India's six-digit postal code: its digits give the zone, the sub-zone and sorting district, the route and the delivery post office, so a code names the area one office delivers to. is the part the post sorts by: its digits give the zone, the sub-zone and sorting district, the route, and the delivery post office (India Post, Tamil Nadu Circle). A PIN names the area one office delivers to. The directory copy in MySmartCoPilot’s PIN code tool lists 165,601 post offices under 19,586 PIN codes, about 8.5 to a code, so a point for a PIN is at best one office’s point.
PIN Code Finder (India Post) Look up a PIN code: every post office it covers, with the district and state.DIGIPIN: The Department of Posts' code for any point in a box around India: ten symbols, each naming one of 4 by 4 cells at one level, down to a cell of about 4 m., from the Department of Posts with IIT Hyderabad and NRSC, ISRO, names places by their coordinates instead (DIGIPIN). Its box runs from 63.5° to 99.5° E and from 2.5° to 38.5° N, in WGS 84. It is cut into 4 by 4 cells ten times over, and each cell takes one of 16 symbols from the same grid at every level (technical document):
The 16 symbols of a DIGIPIN level and where each one sits
Text description of the diagram
The grid has four rows from north to south and four columns from west to east. The first row reads F, C, 9, 8; the second J, 3, 2, 7; the third K, 4, 5, 6; the fourth L, M, P, T. The same grid labels the 16 squares at every one of the ten levels.
A dashed line joins the symbols in the order 2, 3, 4, 5, 6, 7, 8, 9, C, F, J, K, L, M, P, T. It starts at 2 in the second row, turns anticlockwise around the middle and spirals outwards to T in the south-east corner, so two symbols that follow each other always name squares that touch. The square of symbol 3, second row and second column, is shaded: Dak Bhawan falls in it at the first level.
Ten symbols name a cell of about 3.8 m at the equator. Follow the document’s own example, Dak Bhawan, down the ten levels:
The drawing shows the whole scene; its 10 steps are listed below.
Every step as text
- The whole box. The box runs from 63.5° to 99.5° E and from 2.5° to 38.5° N, 36° each way, cut into 4 by 4 squares of 9°. Dak Bhawan, at 28.62° N and 77.21° E, is in the second row from the north and the second column from the west: 3.
- Level 2. Square 3, from 20.5° to 29.5° N and 72.5° to 81.5° E, is cut the same way into squares of 2.25°. Dak Bhawan is in the first row and the third column: 9.
- Level 3. Inside 39, from 27.25° to 29.5° N and 77° to 79.25° E, it falls in the second row and the first column: J. The first three symbols, 39J, are written as one group.
- Level 4. The square is now about 15.6 km wide. Third row, second column: 4.
- Level 5. First row, third column: 9. The square is about 3.9 km across, the size of a few neighbourhoods.
- Level 6. Fourth row, first column: L. Six symbols, 39J-49L, already name a square of about 1 km.
- Level 7. Fourth row, first column again: L. The same symbol can come twice in a row, because every level uses the same grid.
- Level 8. First row, fourth column: 8. The square is about 60 m across, the size of a large building.
- Level 9. Fourth row, fourth column: T. About 15 m across.
- Level 10. Third row, second column: 4. The finished code, 39J-49L-L8T4, names a cell of about 3.8 m at the equator, and the technical document gives this same code for Dak Bhawan.
A DIGIPIN adds a location to an address; it does not replace one. Indian addresses can carry both: the PIN code for the post, the DIGIPIN for the door. Three things decide whether such codes and points can be trusted:
- The grid-line rules give the code of a point that lies exactly between two cells.
- Measured precision turns a cell’s degrees, and a phone’s error, into metres.
- A position audit finds directory points that are missing, swapped, copied or wrong.
Checkpoint
Check: PIN codes and the DIGIPIN grid
DIGIPIN to the letter: an encoder, a decoder and their edge cases Pro: opens with a Pro, Premium or Ultimate pass
A short program turns the document’s rules into codes, and the document’s own example checks it. This part writes it, tests it on grid lines and edges, and measures what its cells mean on the ground.
In this part:
- The encoder and decoder, checked against the document
- A code is only as precise as the position it came from
2 runnable examples (Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
A geocoder client that keeps the usage rules, run against a stand-in Pro: opens with a Pro, Premium or Ultimate pass
The rules of a public geocoder are easiest to keep when the client enforces them. This part builds such a client and runs it against a stand-in for the service, so that the run sends nothing and can be repeated exactly.
In this part:
- Rate, name, cache and credit, enforced by the client
- What to store with every geocoded row
- Bulk work needs another source
1 runnable example (Python) · about 3 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Auditing PIN code positions before you map them, reproduced Pro: opens with a Pro, Premium or Ultimate pass
Every row of a directory looks equally sure of itself. This part runs six checks over the positions of eleven post offices and sorts each into what can be mapped and what must be fixed first.
In this part:
- Six checks that sort a directory’s positions
- What to do with each verdict
2 runnable examples (Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Practise: DIGIPIN from both ends
Encode a point, then decode a code back to the centre of its cell:
Code lab · 2 steps
DIGIPIN from both ends
Encode a point in WGS 84 degrees as a DIGIPIN, then decode a DIGIPIN to the centre of its cell.
Exercise · Medium · Python
Encode a point as a DIGIPIN
Write encode(lat, lon) in encode.py. It takes a point in WGS 84 degrees and returns its DIGIPIN, the ten symbols written in three groups, XXX-XXX-XXXX.
Start with the box from 2.5° to 38.5° N and from 63.5° to 99.5° E. Ten times over, cut the current box into 4 by 4 equal cells, find the cell the point is in, add that cell's symbol from GRID (rows from north to south, columns from west to east) and make that cell the new box. A point on a line between two cells goes to the cell to its north or its east; a point on the outer north or east edge of the whole box stays in the cells along that edge. Raise ValueError for a point outside the box.
The sample tests use the technical document's example, Dak Bhawan, the example of the official code's README, a point on two grid lines, the corners of the box and a point outside it.
Starter code · encode.py
GRID = ["FC98", "J327", "K456", "LMPT"] # rows from north to south, columns from west to east
SOUTH, NORTH, WEST, EAST = 2.5, 38.5, 63.5, 99.5
def encode(lat, lon):
"""The DIGIPIN of a point in WGS 84 degrees, written XXX-XXX-XXXX."""
# Replace this line: cut the box into 4 x 4 cells ten times and add the symbol of the cell the point is in.
return "" The sample tests · test_encode.py
from encode import encode
def test_dak_bhawan():
"""gives Dak Bhawan the code of the technical document, 39J-49L-L8T4"""
assert encode(28.622788, 77.213033) == "39J-49L-L8T4"
def test_the_official_readme_example():
"""encodes the example of the official code's README"""
assert encode(13.11179621, 80.20264269) == "4T3-96F-42L7"
def test_a_point_on_two_grid_lines():
"""sends a point on two lines of the first level to the cell to its north-east"""
assert encode(29.5, 72.5) == "CLL-LLL-LLLL"
def test_the_corners_of_the_box():
"""keeps the corners of the box inside it, the north-east one included"""
assert encode(2.5, 63.5) == "LLL-LLL-LLLL"
assert encode(38.5, 99.5) == "888-888-8888"
assert encode(38.5, 63.5) == "FFF-FFF-FFFF"
def test_a_point_outside_the_box():
"""refuses a point west of 63.5° E"""
try:
encode(28.6, 60.0)
except ValueError:
return
raise AssertionError("encode(28.6, 60.0) should raise ValueError") A hint
Count rows from the south: up = int((lat - south) / height), where height is the cell height at this level. A point on a line then lands in the cell above it. Cap up at 3 for the north edge, and use row 3 - up of GRID, whose first row is the northern one. Columns work the same way from the west.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Exercise · Medium · Python
Decode a DIGIPIN to the centre of its cell
Write decode(code) in decode.py. It takes a DIGIPIN, with or without its hyphens and in upper or lower case, and returns (lat, lon), the centre of the cell it names, in degrees rounded to 6 decimals. Walk down the same grid as the encoder: for each symbol, find its row and column in GRID, cut the current box into 4 by 4 cells and keep that cell. Raise ValueError for a code that does not have ten symbols, or that uses a symbol the grid does not have.
The sample tests use the technical document's example, the decoding example of the official code's README, a code written in lower case without hyphens, and two codes that must be refused.
Starter code · decode.py
GRID = ["FC98", "J327", "K456", "LMPT"] # rows from north to south, columns from west to east
SOUTH, NORTH, WEST, EAST = 2.5, 38.5, 63.5, 99.5
def decode(code):
"""The centre (lat, lon) of the cell a DIGIPIN names, in degrees rounded to 6 decimals."""
# Replace this line: find each symbol's row and column in GRID and keep that cell of the current box.
return (0.0, 0.0) The sample tests · test_decode.py
from decode import decode
def test_dak_bhawan():
"""decodes 39J-49L-L8T4 to the centre of Dak Bhawan's cell"""
assert decode("39J-49L-L8T4") == (28.622793, 77.213049)
def test_the_official_readme_example():
"""decodes the example of the official code's README"""
assert decode("4P3JK852C9") == (12.971601, 77.594584)
def test_lower_case_without_hyphens():
"""reads a code in lower case, written without its hyphens"""
assert decode("39j49ll8t4") == (28.622793, 77.213049)
def test_codes_that_are_not_digipins():
"""refuses a code with a symbol outside the grid, and one with nine symbols"""
for bad in ("39J-49L-L8TG", "39J-49L-L8T"):
try:
decode(bad)
except ValueError:
continue
raise AssertionError(f"decode({bad!r}) should raise ValueError") A hint
Keep the box as its south-west corner and its size. For a symbol in row r (counted from the north) and column c, the new south edge is south + (3 - r) * height and the new west edge west + c * width, after dividing height and width by 4. The centre is the south-west corner plus half the final size.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Practice that adapts to you
Practise this lesson
9 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): Why can a PIN code not stand in for a household’s location?
A PIN code names the area one delivery post office serves, and the directory lists about 8.5 offices for each code: an area of many streets, not a point. A point stored for a PIN is at best one office’s. Use the PIN to join an address to its district; for a location, geocode the address, or record coordinates or a DIGIPIN at the door.
Three longer geocoding questions live in the module’s question bank, each with its first step:
-
Fresher to mid
A health department geocodes 3,000 clinic addresses for a coverage map, and 412 clinics land on exactly the same point in the middle of the district town. Nothing failed. What happened, how do you find every affected clinic, and what do you do about them?
-
Mid to senior
A delivery app stores the DIGIPIN of each customer's door, made from the phone's position at sign-up. Riders report that many parcels go to the neighbour's gate. Explain why, and redesign what the app stores and how it uses it.
-
Mid to senior
You must geocode 200,000 addresses once, for a public dashboard. A colleague proposes a script that calls the public Nominatim service from ten threads, to finish overnight. What do you tell them, and what plan do you propose instead?
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
What are forward and reverse geocoding, and what does a reverse answer actually return?
Forward geocoding turns an address or a place name into coordinates; reverse geocoding turns coordinates into an address. Nominatim's reverse answer is the address of the closest suitable object, not an address computed for the exact point.
-
What does Nominatim's place_rank tell you about an answer?
A number for what was matched: houses and points of interest are 30, streets 26 or 27, neighbourhoods 20, towns 17 or 18 and cities 13 to 16. It says nothing about distance: a rank-26 street landed 1,257 m from the door.
-
How can a geocoding answer of rank 30 still be wrong?
A misspelt address, "12 Hill Stret", was matched at rank 30 to a place in another town 43 km away. Keep the rank beside every point, and test each point against the area it should fall in.
-
What does the public Nominatim service's usage policy ask of a program?
At most 1 request a second for the whole application, its own User-Agent or Referer, answers cached and shown with the credit under the ODbL, only small one-off bulk jobs from one thread, and no autocomplete as users type. Read the policy again before each new use.
-
Why can a PIN code not stand in for a household's location?
It names the area one delivery post office serves, and the directory lists 165,601 offices under 19,586 codes, about 8.5 to a code, so a point for a PIN is at best one office's point. For the door, geocode the address, or record coordinates or a DIGIPIN there.
-
How does DIGIPIN name a place?
From coordinates alone. Its box in WGS 84 is divided ten times into a 4 × 4 grid, each cell named by one of 16 symbols, so ten symbols name a cell about 3.8 m across at the equator. A DIGIPIN adds a location to an address rather than replacing it.
Key takeaways
- A geocoder answers with its best match: keep each answer’s rank, and test it against the area it should fall in.
- A public geocoder’s rules are part of using it: one request a second, your application’s name, a cache and the credit.
- A PIN code names a delivery area; a DIGIPIN names a cell of about 4 m from coordinates alone.
Terms in this lesson
- DIGIPIN
- The Department of Posts' code for any point in a box around India: ten symbols, each naming one of 4 by 4 cells at one level, down to a cell of about 4 m.
- geocoding
- Turning text, such as an address or a place name, into coordinates. Reverse geocoding turns coordinates back into the nearest address or place.
- PIN code (Postal Index Number)
- India's six-digit postal code: its digits give the zone, the sub-zone and sorting district, the route and the delivery post office, so a code names the area one office delivers to.
References
- DIGIPIN: National Level Addressing Grid, technical document (final version) (Department of Posts, Ministry of Communications)
- DIGIPIN (Department of Posts, Ministry of Communications)
- DIGIPIN: the official encoding and decoding code (Apache-2.0) (Department of Posts)
- Nominatim usage policy (OpenStreetMap Foundation)
- Nominatim manual: search queries (Nominatim project (OpenStreetMap))
- Nominatim manual: place ranking (Nominatim project (OpenStreetMap))
- Nominatim manual: reverse geocoding (Nominatim project (OpenStreetMap))
- GPS accuracy (GPS.gov, National Coordination Office for Space-Based Positioning, Navigation, and Timing)
- Attribution guidelines (OpenStreetMap Foundation)
- Pincode (Tamil Nadu Postal Circle page, archived copy) (India Post, Tamil Nadu Postal Circle)
- All India Pincode Directory till last month (Department of Posts, on the Open Government Data Platform India)
- Government Open Data License - India (Gazette of India notification) (Ministry of Electronics and Information Technology)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress