Skip to main content

Simple PostGIS Reverse Geocoder

HOT

Given a geopoint, find the nearest city using PostGIS (reverse geocode).

Publish Docs Publish Test Package version Downloads Pre-Commit License


📖 Documentation: https://hotosm.github.io/pg-nearest-city/

🖥️ Source Code: https://github.com/hotosm/pg-nearest-city


Why do we need this?

This package was developed primarily as a basic reverse geocoder for use within web frameworks (APIs) that have an existing PostGIS connection to utilise.

Simple alternatives:

  • The reverse geocoding package in Python is probably the original and canonincal implementation using K-D tree.
    • However, it's a bit outdated now, with numerous unattended pull requests and uses an unfavourable multiprocessing-based approach.
    • It leaves a large memory footprint of approximately 260MB to load the K-D tree in memory (see benchmarks), which remains there: an unacceptable compromise for a web server for such a small amount of functionality.
  • This package is an excellent revamp of the package above, and possibly the best choice in many scenarios, particularly if PostGIS is not available.

The pg-nearest-city approach:

  • Is approximately ~450x more performant (45ms --> 0.1ms).
  • Has a small ~8MB memory footprint, compared to ~260MB.
    • Depends on the selected baseline dataset and compression algorithm: GADM when compressed is ~25MB.
  • However it has a one-time initialisation penalty of approximately 30s-4m to load the data into the database (which could be handled at web server startup).
    • As with the memory footprint, this depends on the baseline dataset and compression algorithm selected.

Priorities

  • Lightweight package size.
  • Minimal memory footprint.
  • High performance.
  • Keeping package size as small as possible.

How This Package Works

  • Ingest GeoNames cities500 data (cities with population > 500).
  • Ingest country boundary polygons from either GADM or Natural Earth.
  • Apply GeoBoundaries corrections for inaccurate upstream geometry (e.g. disputed territories, overseas regions).
  • Clean and normalise country data (spelling fixes, ISO 3166-1 alignment, etc.).
  • Simplify geometry with PostGIS ST_Subdivide to shrink size and speed up queries.
  • Query: use ST_Covers to identify the covering country polygon, then a KNN lateral join to find the nearest city within that country.

Usage

Install

Distributed as a pip package on PyPi:

pip install pg-nearest-city
# or use your dependency manager of choice

Run The Code

Async

from pg_nearest_city import AsyncNearestCity

# Existing code to get db connection, say from API endpoint
db = await get_db_connection()

async with AsyncNearestCity(db) as geocoder:
    location = await geocoder.query(-74.0060, 40.7128)

print(location.city)
# "New York City"
print(location.country)
# "USA"

Sync

from pg_nearest_city import NearestCity

# Existing code to get db connection, say from API endpoint
db = get_db_connection()

with NearestCity(db) as geocoder:
    location = geocoder.query(-74.0060, 40.7128)

print(location.city)
# "New York City"
print(location.country)
# "USA"

Create A New DB Connection

  • If your app upstream already has a psycopg connection, this can be passed through.
  • If you require a new database connection, the connection parameters can be defined as DbConfig object variables:
from pg_nearest_city import DbConfig, AsyncNearestCity

db_config = DbConfig(
    dbname="db1",
    user="user1",
    password="pass1",
    host="localhost",
    port="5432",
)

async with AsyncNearestCity(db_config) as geocoder:
    location = await geocoder.query(-74.0060, 40.7128)
  • Or alternatively as variables from your system environment:
PGNEAREST_DB_NAME=cities
PGNEAREST_DB_USER=cities
PGNEAREST_DB_PASSWORD=somepassword
PGNEAREST_DB_HOST=localhost
PGNEAREST_DB_PORT=5432

then

from pg_nearest_city import AsyncNearestCity

async with AsyncNearestCity() as geocoder:
    location = await geocoder.query(-74.0060, 40.7128)

Testing

Via Docker:

docker compose run --rm code pytest

Or locally (requires a running PostgreSQL instance with PostGIS and loaded data):

PGNEAREST_DB_USER=myuser PGNEAREST_DB_NAME=postgres uv run pytest tests/ -v

Data Pipeline

The pgnearest-load CLI command runs a multi-step pipeline that downloads source data, imports it into PostGIS, applies corrections, simplifies geometry, and exports compressed CSV files for distribution.

Running the Pipeline

# Using Natural Earth boundaries
uv run pgnearest-load --boundary-source naturalearth \
    --db-name postgres --db-user myuser --output-dir ./output

# Using GADM boundaries
uv run pgnearest-load --boundary-source gadm \
    --db-name postgres --db-user myuser --output-dir ./output

# Full rebuild (drops all tables first)
uv run pgnearest-load ... --clean

Key Flags

Flag Description
--boundary-source {gadm,naturalearth} Which boundary dataset to use
--compression {auto,gzip,bz2,xz,zstd} Compression for exported files
--no-cache Download to temp dir, no persist
--clean Drop all project tables first
--skip-steps / --only-steps Step prefixes to skip or isolate
--list-steps Print pipeline steps and exit
--country Filter to a country (e.g. IT)

Output Files

The pipeline exports three compressed CSV files:

  • country.csv.<ext> — subdivided country polygons (alpha2, alpha3, name, WKB geometry).
  • geocoding.csv.<ext> — city-to-country mapping (city, country, lat, lon).
  • cities_500_simple.txt.<ext> — simplified city data.

When to Regenerate

Manual regeneration is only necessary when:

  • New GeoNames data becomes available and you want to update.
  • Upstream boundary datasets have been corrected.
  • You need to filter for a specific geographic region.

Metadata

Release files for pg-nearest-city 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pg-nearest-city 2.0.0
File Size Uploaded Provenance
pg_nearest_city-2.0.0.tar.gz 6.6 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pg-nearest-city 2.0.0
File Interpreter ABI Platform
pg_nearest_city-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 13.2 MB

Release files / pg_nearest_city-2.0.0.tar.gz

Download URL pg_nearest_city-2.0.0.tar.gz
Size 6.6 MB
Tags Source
SHA-256 checksum
How to use checksums
43b9a729ab1e230073734f2034d151593ae3aa075128852fd33755c2f01999f8
BLAKE2b-256 checksum
How to use checksums
aa43e01d67392300de0650a305b553f6cb2bb842fb89996d214a4709d982e704
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.9.3 CPython/3.12.3

Release files / pg_nearest_city-2.0.0-py3-none-any.whl

Download URL pg_nearest_city-2.0.0-py3-none-any.whl
Size 6.5 MB
Tags Python 3
SHA-256 checksum
How to use checksums
51df615e5ffe080e0fce2ec2b251b852e9e08855852c2e29796a65aa0346b21a
BLAKE2b-256 checksum
How to use checksums
c48488f907e44be18048d77080ed1a67fb678edcf24fe93cefa094bd2cce974e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.9.3 CPython/3.12.3

Security for pg-nearest-city 2.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page