pg-nearest-city


Namepg-nearest-city JSON
Version 0.2.1 PyPI version JSON
download
home_pageNone
SummaryGiven a geopoint, find the nearest city using PostGIS (reverse geocode).
upload_time2025-02-17 23:32:33
maintainerNone
docs_urlNone
authorEmir Fabio Cognigni
requires_python>=3.10
licenseGPL-3.0-only
keywords geocode nearest postgis reverse reverse-geocode
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Simple PostGIS Reverse Geocoder

<!-- markdownlint-disable -->
<p align="center">
  <img src="https://raw.githubusercontent.com/hotosm/pg-nearest-city/refs/heads/main/docs/images/hot_logo.png" style="width: 200px;" alt="HOT"></a>
</p>
<p align="center">
  <em>Given a geopoint, find the nearest city using PostGIS (reverse geocode).</em>
</p>
<p align="center">
  <a href="https://github.com/hotosm/pg-nearest-city/actions/workflows/docs.yml" target="_blank">
      <img src="https://github.com/hotosm/pg-nearest-city/actions/workflows/docs.yml/badge.svg" alt="Publish Docs">
  </a>
  <a href="https://github.com/hotosm/pg-nearest-city/actions/workflows/publish.yml" target="_blank">
      <img src="https://github.com/hotosm/pg-nearest-city/actions/workflows/publish.yml/badge.svg" alt="Publish">
  </a>
  <a href="https://github.com/hotosm/pg-nearest-city/actions/workflows/pytest.yml" target="_blank">
      <img src="https://github.com/hotosm/pg-nearest-city/actions/workflows/pytest.yml/badge.svg?branch=main" alt="Test">
  </a>
  <a href="https://pypi.org/project/pg-nearest-city" target="_blank">
      <img src="https://img.shields.io/pypi/v/pg-nearest-city?color=%2334D058&label=pypi%20package" alt="Package version">
  </a>
  <a href="https://pypistats.org/packages/pg-nearest-city" target="_blank">
      <img src="https://img.shields.io/pypi/dm/pg-nearest-city.svg" alt="Downloads">
  </a>
  <a href="https://github.com/hotosm/pg-nearest-city/blob/main/LICENSE.md" target="_blank">
      <img src="https://img.shields.io/github/license/hotosm/pg-nearest-city.svg" alt="License">
  </a>
</p>

---

📖 **Documentation**: <a href="https://hotosm.github.io/pg-nearest-city/" target="_blank">https://hotosm.github.io/pg-nearest-city/</a>

🖥️ **Source Code**: <a href="https://github.com/hotosm/pg-nearest-city" target="_blank">https://github.com/hotosm/pg-nearest-city</a>

---

<!-- markdownlint-enable -->

## 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 [here](https://github.com/thampiman/reverse-geocoder)
  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](./benchmark-results.md)), which
    remains there: an unacceptable compromise for a web server for such a
    small amount of functionality.
- The package [here](https://github.com/richardpenman/reverse_geocode) 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 ~20x more performant (45ms --> 2ms).
- Has a small ~8MB memory footprint, compared to ~260MB.
- However it has a one-time initialisation penalty of approximately 16s
  to load the data into the database (which could be handled at
  web server startup).

See [benchmarks](./benchmark-results.md) for more details.

> [!NOTE]
> We don't discuss web based geocoding services here, such as Nominatim, as simple
> offline reverse-geocoding has two purposes:
>
> - Reduced latency, when very precise locations are not required.
> - Reduced load on free services such as Nominatim (particularly when running
> in automated tests frequently).

### Priorities

- Lightweight package size.
- Minimal memory footprint.
- High performance.

### How This Package Works

- Ingest geonames.org data for cities over 1000 population.
- Create voronoi polygons based on city geopoints.
- Bundle the voronoi data with this package and load into Postgis.
- Query the loaded voronoi data with a given geopoint, returning the city.

The diagram below should give a good indication for how this works:

![voronoi_italy](./voronoi_italy.jpg)

## Usage

### Install

Distributed as a pip package on PyPi:

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

### Run The Code

#### Async

```python
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(40.7128, -74.0060)

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

#### Sync

```python
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(40.7128, -74.0060)

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:

```python
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(40.7128, -74.0060)
```

- Or alternatively as variables from your system environment:

```dotenv
PGNEAREST_DB_NAME=cities
PGNEAREST_DB_USER=cities
PGNEAREST_DB_PASSWORD=somepassword
PGNEAREST_DB_HOST=localhost
PGNEAREST_DB_PORT=5432
```

then

```python
from pg_nearest_city import AsyncNearestCity

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

## Testing

Run the tests with:

```bash
docker compose run --rm code pytest
```

## Benchmarks

Run the benchmarks with:

```bash
docker compose run --rm benchmark
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "pg-nearest-city",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "geocode, nearest, postgis, reverse, reverse-geocode",
    "author": "Emir Fabio Cognigni",
    "author_email": "Sam Woodcock <sam.woodcock@hotosm.org>",
    "download_url": "https://files.pythonhosted.org/packages/62/8b/7a40b079cbd478d5e76a11eb1ecab6d1b2a491992faf6d455a6e5b18fff2/pg_nearest_city-0.2.1.tar.gz",
    "platform": null,
    "description": "# Simple PostGIS Reverse Geocoder\n\n<!-- markdownlint-disable -->\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/hotosm/pg-nearest-city/refs/heads/main/docs/images/hot_logo.png\" style=\"width: 200px;\" alt=\"HOT\"></a>\n</p>\n<p align=\"center\">\n  <em>Given a geopoint, find the nearest city using PostGIS (reverse geocode).</em>\n</p>\n<p align=\"center\">\n  <a href=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/docs.yml\" target=\"_blank\">\n      <img src=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/docs.yml/badge.svg\" alt=\"Publish Docs\">\n  </a>\n  <a href=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/publish.yml\" target=\"_blank\">\n      <img src=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/publish.yml/badge.svg\" alt=\"Publish\">\n  </a>\n  <a href=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/pytest.yml\" target=\"_blank\">\n      <img src=\"https://github.com/hotosm/pg-nearest-city/actions/workflows/pytest.yml/badge.svg?branch=main\" alt=\"Test\">\n  </a>\n  <a href=\"https://pypi.org/project/pg-nearest-city\" target=\"_blank\">\n      <img src=\"https://img.shields.io/pypi/v/pg-nearest-city?color=%2334D058&label=pypi%20package\" alt=\"Package version\">\n  </a>\n  <a href=\"https://pypistats.org/packages/pg-nearest-city\" target=\"_blank\">\n      <img src=\"https://img.shields.io/pypi/dm/pg-nearest-city.svg\" alt=\"Downloads\">\n  </a>\n  <a href=\"https://github.com/hotosm/pg-nearest-city/blob/main/LICENSE.md\" target=\"_blank\">\n      <img src=\"https://img.shields.io/github/license/hotosm/pg-nearest-city.svg\" alt=\"License\">\n  </a>\n</p>\n\n---\n\n\ud83d\udcd6 **Documentation**: <a href=\"https://hotosm.github.io/pg-nearest-city/\" target=\"_blank\">https://hotosm.github.io/pg-nearest-city/</a>\n\n\ud83d\udda5\ufe0f **Source Code**: <a href=\"https://github.com/hotosm/pg-nearest-city\" target=\"_blank\">https://github.com/hotosm/pg-nearest-city</a>\n\n---\n\n<!-- markdownlint-enable -->\n\n## Why do we need this?\n\nThis package was developed primarily as a **basic** reverse geocoder for use within\nweb frameworks (APIs) that **have an existing PostGIS connection to utilise**.\n\nSimple alternatives:\n\n- The reverse geocoding package in Python [here](https://github.com/thampiman/reverse-geocoder)\n  is probably the original and canonincal implementation using K-D tree.\n  - However, it's a bit outdated now, with numerous unattended pull\n    requests and uses an unfavourable multiprocessing-based approach.\n  - It leaves a large memory footprint of approximately 260MB to load the\n    K-D tree in memory (see [benchmarks](./benchmark-results.md)), which\n    remains there: an unacceptable compromise for a web server for such a\n    small amount of functionality.\n- The package [here](https://github.com/richardpenman/reverse_geocode) is an excellent\n  revamp of the package above, and possibly the best choice in many scenarios,\n  particularly if PostGIS is not available.\n\nThe pg-nearest-city approach:\n\n- Is approximately ~20x more performant (45ms --> 2ms).\n- Has a small ~8MB memory footprint, compared to ~260MB.\n- However it has a one-time initialisation penalty of approximately 16s\n  to load the data into the database (which could be handled at\n  web server startup).\n\nSee [benchmarks](./benchmark-results.md) for more details.\n\n> [!NOTE]\n> We don't discuss web based geocoding services here, such as Nominatim, as simple\n> offline reverse-geocoding has two purposes:\n>\n> - Reduced latency, when very precise locations are not required.\n> - Reduced load on free services such as Nominatim (particularly when running\n> in automated tests frequently).\n\n### Priorities\n\n- Lightweight package size.\n- Minimal memory footprint.\n- High performance.\n\n### How This Package Works\n\n- Ingest geonames.org data for cities over 1000 population.\n- Create voronoi polygons based on city geopoints.\n- Bundle the voronoi data with this package and load into Postgis.\n- Query the loaded voronoi data with a given geopoint, returning the city.\n\nThe diagram below should give a good indication for how this works:\n\n![voronoi_italy](./voronoi_italy.jpg)\n\n## Usage\n\n### Install\n\nDistributed as a pip package on PyPi:\n\n```bash\npip install pg-nearest-city\n# or use your dependency manager of choice\n```\n\n### Run The Code\n\n#### Async\n\n```python\nfrom pg_nearest_city import AsyncNearestCity\n\n# Existing code to get db connection, say from API endpoint\ndb = await get_db_connection()\n\nasync with AsyncNearestCity(db) as geocoder:\n    location = await geocoder.query(40.7128, -74.0060)\n\nprint(location.city)\n# \"New York City\"\nprint(location.country)\n# \"USA\"\n```\n\n#### Sync\n\n```python\nfrom pg_nearest_city import NearestCity\n\n# Existing code to get db connection, say from API endpoint\ndb = get_db_connection()\n\nwith NearestCity(db) as geocoder:\n    location = geocoder.query(40.7128, -74.0060)\n\nprint(location.city)\n# \"New York City\"\nprint(location.country)\n# \"USA\"\n```\n\n#### Create A New DB Connection\n\n- If your app upstream already has a psycopg connection, this can be\n  passed through.\n- If you require a new database connection, the connection parameters\n  can be defined as DbConfig object variables:\n\n```python\nfrom pg_nearest_city import DbConfig, AsyncNearestCity\n\ndb_config = DbConfig(\n    dbname=\"db1\",\n    user=\"user1\",\n    password=\"pass1\",\n    host=\"localhost\",\n    port=\"5432\",\n)\n\nasync with AsyncNearestCity(db_config) as geocoder:\n    location = await geocoder.query(40.7128, -74.0060)\n```\n\n- Or alternatively as variables from your system environment:\n\n```dotenv\nPGNEAREST_DB_NAME=cities\nPGNEAREST_DB_USER=cities\nPGNEAREST_DB_PASSWORD=somepassword\nPGNEAREST_DB_HOST=localhost\nPGNEAREST_DB_PORT=5432\n```\n\nthen\n\n```python\nfrom pg_nearest_city import AsyncNearestCity\n\nasync with AsyncNearestCity() as geocoder:\n    location = await geocoder.query(40.7128, -74.0060)\n```\n\n## Testing\n\nRun the tests with:\n\n```bash\ndocker compose run --rm code pytest\n```\n\n## Benchmarks\n\nRun the benchmarks with:\n\n```bash\ndocker compose run --rm benchmark\n```\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-only",
    "summary": "Given a geopoint, find the nearest city using PostGIS (reverse geocode).",
    "version": "0.2.1",
    "project_urls": {
        "documentation": "https://hotosm.github.io/pg-nearest-city",
        "homepage": "https://github.com/hotosm/pg-nearest-city",
        "repository": "https://github.com/hotosm/pg-nearest-city"
    },
    "split_keywords": [
        "geocode",
        " nearest",
        " postgis",
        " reverse",
        " reverse-geocode"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c28ee66698a7bf16304aaa4caa20b590b85d5c7123d8b824b46941979cdd0e17",
                "md5": "10f32069fec9b66a07bff0965e852275",
                "sha256": "631f0115105ad6e1e65758ab5cedcf56ed2a5b048ca13ecc152caabb748cb177"
            },
            "downloads": -1,
            "filename": "pg_nearest_city-0.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "10f32069fec9b66a07bff0965e852275",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 13147516,
            "upload_time": "2025-02-17T23:32:29",
            "upload_time_iso_8601": "2025-02-17T23:32:29.709111Z",
            "url": "https://files.pythonhosted.org/packages/c2/8e/e66698a7bf16304aaa4caa20b590b85d5c7123d8b824b46941979cdd0e17/pg_nearest_city-0.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "628b7a40b079cbd478d5e76a11eb1ecab6d1b2a491992faf6d455a6e5b18fff2",
                "md5": "420af91102588f3684f3e5626c6d0906",
                "sha256": "a026d166624db748d8bc44e4e5c0433df125c523d5b626d2a96e8e1cafe8313a"
            },
            "downloads": -1,
            "filename": "pg_nearest_city-0.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "420af91102588f3684f3e5626c6d0906",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 13324955,
            "upload_time": "2025-02-17T23:32:33",
            "upload_time_iso_8601": "2025-02-17T23:32:33.733847Z",
            "url": "https://files.pythonhosted.org/packages/62/8b/7a40b079cbd478d5e76a11eb1ecab6d1b2a491992faf6d455a6e5b18fff2/pg_nearest_city-0.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-02-17 23:32:33",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "hotosm",
    "github_project": "pg-nearest-city",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pg-nearest-city"
}
        
Elapsed time: 1.27632s