offlineRevGeocoder 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- offlinerevgeocoder-0.1.0/LICENSE +26 -0
- offlinerevgeocoder-0.1.0/MANIFEST.in +3 -0
- offlinerevgeocoder-0.1.0/PKG-INFO +131 -0
- offlinerevgeocoder-0.1.0/README.md +97 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder/__init__.py +3 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder/data/__init__.py +0 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder/data/reverse_geocoder_cache.pkl +0 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder/reverseGeocoder.py +150 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder.egg-info/PKG-INFO +131 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder.egg-info/SOURCES.txt +12 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder.egg-info/dependency_links.txt +1 -0
- offlinerevgeocoder-0.1.0/offlineRevGeocoder.egg-info/top_level.txt +1 -0
- offlinerevgeocoder-0.1.0/setup.cfg +4 -0
- offlinerevgeocoder-0.1.0/setup.py +39 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AminJavadi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
Natural Earth data notice:
|
|
24
|
+
The bundled geographic cache is derived from Natural Earth data. Natural Earth
|
|
25
|
+
map data is public domain and may be used, modified, and redistributed. See:
|
|
26
|
+
https://www.naturalearthdata.com/about/terms-of-use/
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: offlineRevGeocoder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast offline reverse geocoding using a prepared Natural Earth cache
|
|
5
|
+
Home-page: https://github.com/aminjavadi02/reverseGeocoder-py
|
|
6
|
+
Author: AminJavadi
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Source, https://github.com/aminjavadi02/reverseGeocoder-py
|
|
9
|
+
Project-URL: Natural Earth, https://www.naturalearthdata.com/
|
|
10
|
+
Keywords: geocoder,geocoding,gis,natural-earth,reverse-geocoding
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: classifier
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: keywords
|
|
29
|
+
Dynamic: license
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: project-url
|
|
32
|
+
Dynamic: requires-python
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
# Reverse Geocoder
|
|
36
|
+
|
|
37
|
+
Tiny offline reverse geocoding for Python.
|
|
38
|
+
|
|
39
|
+
`offlineRevGeocoder` takes a latitude and longitude and returns a small `GeoResult`
|
|
40
|
+
object with the matching country, continent, ocean, and nearest city when one is
|
|
41
|
+
close enough. It is intentionally simple: the geographic source data is prepared
|
|
42
|
+
ahead of time, cached as a pickle file, and loaded locally at runtime. No API
|
|
43
|
+
keys, no network requests, no external service latency.
|
|
44
|
+
|
|
45
|
+
The bundled lookup data is derived from [Natural Earth](https://www.naturalearthdata.com/),
|
|
46
|
+
a public domain map dataset. The prepared cache is included so callers can use
|
|
47
|
+
the package immediately.
|
|
48
|
+
|
|
49
|
+
## Project Info
|
|
50
|
+
|
|
51
|
+
- PyPI package: `offlineRevGeocoder`
|
|
52
|
+
- Import package: `offlineRevGeocoder`
|
|
53
|
+
- Source code: [github.com/aminjavadi02/reverseGeocoder-py](https://github.com/aminjavadi02/reverseGeocoder-py)
|
|
54
|
+
- License: MIT
|
|
55
|
+
- Author: AminJavadi
|
|
56
|
+
|
|
57
|
+
This is an open-source package. Bug reports, fixes, data improvements, and
|
|
58
|
+
documentation updates are welcome; please open an issue or send a pull request
|
|
59
|
+
on GitHub.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install offlineRevGeocoder
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from offlineRevGeocoder import get
|
|
71
|
+
|
|
72
|
+
result = get(35.6892, 51.3890)
|
|
73
|
+
|
|
74
|
+
print(result.country) # Iran
|
|
75
|
+
print(result.country_iso2) # IR
|
|
76
|
+
print(result.city) # Tehran
|
|
77
|
+
print(result.precision) # city
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Common Use Case
|
|
81
|
+
|
|
82
|
+
This package is most useful when you only need a small reverse geocoding answer,
|
|
83
|
+
for example checking whether a user's latitude and longitude are in or near a
|
|
84
|
+
specific city. In that kind of flow, you often do not need to pay for a full
|
|
85
|
+
reverse geocoding API subscription.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from offlineRevGeocoder import get
|
|
89
|
+
|
|
90
|
+
place = get(35.7000, 51.4000, city_radius_km=25)
|
|
91
|
+
|
|
92
|
+
print(place.city) # Tehran
|
|
93
|
+
print(place.precision) # city
|
|
94
|
+
print(place.city == "Tehran") # True
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For a nearby-city check, use `city_radius_km` to control how strict the match
|
|
98
|
+
should be for your product.
|
|
99
|
+
|
|
100
|
+
## Return Type
|
|
101
|
+
|
|
102
|
+
`get(lat, lon, city_radius_km=50)` returns:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
GeoResult(
|
|
106
|
+
continent: str | None,
|
|
107
|
+
country: str | None,
|
|
108
|
+
country_iso2: str | None,
|
|
109
|
+
country_iso3: str | None,
|
|
110
|
+
ocean: str | None,
|
|
111
|
+
city: str | None,
|
|
112
|
+
city_distance_km: float | None,
|
|
113
|
+
precision: str,
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`precision` is one of:
|
|
118
|
+
|
|
119
|
+
- `city`: a nearby city was found.
|
|
120
|
+
- `country`: the point matched a country, but no nearby city was found.
|
|
121
|
+
- `ocean`: the point matched an ocean or marine area.
|
|
122
|
+
- `none`: no matching land or ocean feature was found.
|
|
123
|
+
|
|
124
|
+
## Data
|
|
125
|
+
|
|
126
|
+
The package uses Natural Earth datasets that were transformed into a compact
|
|
127
|
+
pickle cache at `offlineRevGeocoder/data/reverse_geocoder_cache.pkl`. Natural Earth data is
|
|
128
|
+
public domain, so it can be redistributed with this MIT-licensed package.
|
|
129
|
+
|
|
130
|
+
The code is MIT licensed. Natural Earth requests attribution where possible; this
|
|
131
|
+
README keeps that attribution with the package.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Reverse Geocoder
|
|
2
|
+
|
|
3
|
+
Tiny offline reverse geocoding for Python.
|
|
4
|
+
|
|
5
|
+
`offlineRevGeocoder` takes a latitude and longitude and returns a small `GeoResult`
|
|
6
|
+
object with the matching country, continent, ocean, and nearest city when one is
|
|
7
|
+
close enough. It is intentionally simple: the geographic source data is prepared
|
|
8
|
+
ahead of time, cached as a pickle file, and loaded locally at runtime. No API
|
|
9
|
+
keys, no network requests, no external service latency.
|
|
10
|
+
|
|
11
|
+
The bundled lookup data is derived from [Natural Earth](https://www.naturalearthdata.com/),
|
|
12
|
+
a public domain map dataset. The prepared cache is included so callers can use
|
|
13
|
+
the package immediately.
|
|
14
|
+
|
|
15
|
+
## Project Info
|
|
16
|
+
|
|
17
|
+
- PyPI package: `offlineRevGeocoder`
|
|
18
|
+
- Import package: `offlineRevGeocoder`
|
|
19
|
+
- Source code: [github.com/aminjavadi02/reverseGeocoder-py](https://github.com/aminjavadi02/reverseGeocoder-py)
|
|
20
|
+
- License: MIT
|
|
21
|
+
- Author: AminJavadi
|
|
22
|
+
|
|
23
|
+
This is an open-source package. Bug reports, fixes, data improvements, and
|
|
24
|
+
documentation updates are welcome; please open an issue or send a pull request
|
|
25
|
+
on GitHub.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install offlineRevGeocoder
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from offlineRevGeocoder import get
|
|
37
|
+
|
|
38
|
+
result = get(35.6892, 51.3890)
|
|
39
|
+
|
|
40
|
+
print(result.country) # Iran
|
|
41
|
+
print(result.country_iso2) # IR
|
|
42
|
+
print(result.city) # Tehran
|
|
43
|
+
print(result.precision) # city
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Common Use Case
|
|
47
|
+
|
|
48
|
+
This package is most useful when you only need a small reverse geocoding answer,
|
|
49
|
+
for example checking whether a user's latitude and longitude are in or near a
|
|
50
|
+
specific city. In that kind of flow, you often do not need to pay for a full
|
|
51
|
+
reverse geocoding API subscription.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from offlineRevGeocoder import get
|
|
55
|
+
|
|
56
|
+
place = get(35.7000, 51.4000, city_radius_km=25)
|
|
57
|
+
|
|
58
|
+
print(place.city) # Tehran
|
|
59
|
+
print(place.precision) # city
|
|
60
|
+
print(place.city == "Tehran") # True
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
For a nearby-city check, use `city_radius_km` to control how strict the match
|
|
64
|
+
should be for your product.
|
|
65
|
+
|
|
66
|
+
## Return Type
|
|
67
|
+
|
|
68
|
+
`get(lat, lon, city_radius_km=50)` returns:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
GeoResult(
|
|
72
|
+
continent: str | None,
|
|
73
|
+
country: str | None,
|
|
74
|
+
country_iso2: str | None,
|
|
75
|
+
country_iso3: str | None,
|
|
76
|
+
ocean: str | None,
|
|
77
|
+
city: str | None,
|
|
78
|
+
city_distance_km: float | None,
|
|
79
|
+
precision: str,
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`precision` is one of:
|
|
84
|
+
|
|
85
|
+
- `city`: a nearby city was found.
|
|
86
|
+
- `country`: the point matched a country, but no nearby city was found.
|
|
87
|
+
- `ocean`: the point matched an ocean or marine area.
|
|
88
|
+
- `none`: no matching land or ocean feature was found.
|
|
89
|
+
|
|
90
|
+
## Data
|
|
91
|
+
|
|
92
|
+
The package uses Natural Earth datasets that were transformed into a compact
|
|
93
|
+
pickle cache at `offlineRevGeocoder/data/reverse_geocoder_cache.pkl`. Natural Earth data is
|
|
94
|
+
public domain, so it can be redistributed with this MIT-licensed package.
|
|
95
|
+
|
|
96
|
+
The code is MIT licensed. Natural Earth requests attribution where possible; this
|
|
97
|
+
README keeps that attribution with the package.
|
|
File without changes
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import pickle
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
CACHE = Path(__file__).resolve().parent / "data" / "reverse_geocoder_cache.pkl"
|
|
11
|
+
CACHE_VERSION = 3
|
|
12
|
+
EARTH_RADIUS_KM = 6371.0088
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class GeoResult:
|
|
17
|
+
continent: str | None
|
|
18
|
+
country: str | None
|
|
19
|
+
country_iso2: str | None
|
|
20
|
+
country_iso3: str | None
|
|
21
|
+
ocean: str | None
|
|
22
|
+
city: str | None
|
|
23
|
+
city_distance_km: float | None
|
|
24
|
+
precision: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
C_CONTINENT, C_COUNTRY, C_ISO2, C_ISO3, C_BBOX, C_RINGS = range(6)
|
|
28
|
+
CITY_NAME, CITY_ISO2, CITY_LAT, CITY_LON = range(4)
|
|
29
|
+
M_NAME, M_BBOX, M_RINGS = range(3)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get(lat: float, lon: float, *, city_radius_km: float = 50) -> GeoResult:
|
|
33
|
+
_validate(lat, lon)
|
|
34
|
+
data = _load()
|
|
35
|
+
country = _find_country(data, lat, lon)
|
|
36
|
+
ocean = None if country else _find_ocean(data, lat, lon)
|
|
37
|
+
city = _find_city(data, lat, lon, city_radius_km, country)
|
|
38
|
+
return GeoResult(
|
|
39
|
+
continent=country[C_CONTINENT] if country else None,
|
|
40
|
+
country=country[C_COUNTRY] if country else None,
|
|
41
|
+
country_iso2=country[C_ISO2] if country else None,
|
|
42
|
+
country_iso3=country[C_ISO3] if country else None,
|
|
43
|
+
ocean=ocean[M_NAME] if ocean else None,
|
|
44
|
+
city=city[0][CITY_NAME] if city else None,
|
|
45
|
+
city_distance_km=round(city[1], 3) if city else None,
|
|
46
|
+
precision="city" if city else "country" if country else "ocean" if ocean else "none",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@lru_cache(maxsize=1)
|
|
51
|
+
def _load() -> dict[str, Any]:
|
|
52
|
+
if not CACHE.exists():
|
|
53
|
+
raise RuntimeError("missing reverse geocoder cache; run python3 prepare.py")
|
|
54
|
+
with CACHE.open("rb") as f:
|
|
55
|
+
data = pickle.load(f)
|
|
56
|
+
if data.get("version") != CACHE_VERSION:
|
|
57
|
+
raise RuntimeError("stale reverse geocoder cache; run python3 prepare.py")
|
|
58
|
+
return data
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _find_country(data: dict[str, Any], lat: float, lon: float) -> tuple[Any, ...] | None:
|
|
62
|
+
cell = _cell_id(*_cell(lat, lon, data["country_cell"]), data["country_grid_width"])
|
|
63
|
+
for i in data["country_grid"].get(cell, []):
|
|
64
|
+
country = data["countries"][i]
|
|
65
|
+
if _in_bbox(lat, lon, country[C_BBOX]) and _in_polygon(lat, lon, country[C_RINGS]):
|
|
66
|
+
return country
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _find_ocean(data: dict[str, Any], lat: float, lon: float) -> tuple[Any, ...] | None:
|
|
71
|
+
cell = _cell_id(*_cell(lat, lon, data["marine_cell"]), data["marine_grid_width"])
|
|
72
|
+
for i in data["marine_grid"].get(cell, []):
|
|
73
|
+
ocean = data["marine"][i]
|
|
74
|
+
if _in_bbox(lat, lon, ocean[M_BBOX]) and _in_polygon(lat, lon, ocean[M_RINGS]):
|
|
75
|
+
return ocean
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _find_city(data: dict[str, Any], lat: float, lon: float, radius_km: float, country: tuple[Any, ...] | None) -> tuple[tuple[Any, ...], float] | None:
|
|
80
|
+
size = data["city_cell"]
|
|
81
|
+
radius_cells = max(1, math.ceil((radius_km / 111.0) / size))
|
|
82
|
+
width = data["city_grid_width"]
|
|
83
|
+
cx, cy = _cell(lat, lon, size)
|
|
84
|
+
best_city = None
|
|
85
|
+
best_distance = radius_km
|
|
86
|
+
country_iso2 = country[C_ISO2] if country else None
|
|
87
|
+
for x in range(cx - radius_cells, cx + radius_cells + 1):
|
|
88
|
+
for y in range(cy - radius_cells, cy + radius_cells + 1):
|
|
89
|
+
for i in data["city_grid"].get(_cell_id(x, y, width), []):
|
|
90
|
+
city = data["cities"][i]
|
|
91
|
+
if country_iso2 and city[CITY_ISO2] and city[CITY_ISO2] != country_iso2:
|
|
92
|
+
continue
|
|
93
|
+
distance = _haversine(lat, lon, city[CITY_LAT], city[CITY_LON])
|
|
94
|
+
if distance <= best_distance:
|
|
95
|
+
best_city = city
|
|
96
|
+
best_distance = distance
|
|
97
|
+
return (best_city, best_distance) if best_city else None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _in_polygon(lat: float, lon: float, rings: list[tuple]) -> bool:
|
|
101
|
+
inside = False
|
|
102
|
+
for bbox, points in rings:
|
|
103
|
+
if _in_bbox(lat, lon, bbox) and _point_in_ring(lat, lon, points):
|
|
104
|
+
inside = not inside
|
|
105
|
+
return inside
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _point_in_ring(lat: float, lon: float, ring: list[tuple[float, float]]) -> bool:
|
|
109
|
+
inside = False
|
|
110
|
+
j = len(ring) - 1
|
|
111
|
+
for i, (xi, yi) in enumerate(ring):
|
|
112
|
+
xj, yj = ring[j]
|
|
113
|
+
if ((xi <= lon <= xj) or (xj <= lon <= xi)) and ((yi <= lat <= yj) or (yj <= lat <= yi)):
|
|
114
|
+
cross = (lat - yi) * (xj - xi) - (lon - xi) * (yj - yi)
|
|
115
|
+
if abs(cross) <= 1e-10:
|
|
116
|
+
return True
|
|
117
|
+
if (yi > lat) != (yj > lat):
|
|
118
|
+
x_at_lat = (xj - xi) * (lat - yi) / (yj - yi) + xi
|
|
119
|
+
if lon < x_at_lat:
|
|
120
|
+
inside = not inside
|
|
121
|
+
j = i
|
|
122
|
+
return inside
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _in_bbox(lat: float, lon: float, bbox: tuple[float, float, float, float]) -> bool:
|
|
126
|
+
minx, miny, maxx, maxy = bbox
|
|
127
|
+
return minx <= lon <= maxx and miny <= lat <= maxy
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
|
131
|
+
p1, p2 = math.radians(lat1), math.radians(lat2)
|
|
132
|
+
dp = math.radians(lat2 - lat1)
|
|
133
|
+
dl = math.radians(lon2 - lon1)
|
|
134
|
+
a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
|
|
135
|
+
return 2 * EARTH_RADIUS_KM * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _cell(lat: float, lon: float, size: float) -> tuple[int, int]:
|
|
139
|
+
return math.floor((lon + 180.0) / size), math.floor((lat + 90.0) / size)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _cell_id(x: int, y: int, width: int) -> int:
|
|
143
|
+
return y * width + x
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _validate(lat: float, lon: float) -> None:
|
|
147
|
+
if not -90 <= lat <= 90:
|
|
148
|
+
raise ValueError("lat must be between -90 and 90")
|
|
149
|
+
if not -180 <= lon <= 180:
|
|
150
|
+
raise ValueError("lon must be between -180 and 180")
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: offlineRevGeocoder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Fast offline reverse geocoding using a prepared Natural Earth cache
|
|
5
|
+
Home-page: https://github.com/aminjavadi02/reverseGeocoder-py
|
|
6
|
+
Author: AminJavadi
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Source, https://github.com/aminjavadi02/reverseGeocoder-py
|
|
9
|
+
Project-URL: Natural Earth, https://www.naturalearthdata.com/
|
|
10
|
+
Keywords: geocoder,geocoding,gis,natural-earth,reverse-geocoding
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Dynamic: author
|
|
24
|
+
Dynamic: classifier
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: keywords
|
|
29
|
+
Dynamic: license
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: project-url
|
|
32
|
+
Dynamic: requires-python
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
# Reverse Geocoder
|
|
36
|
+
|
|
37
|
+
Tiny offline reverse geocoding for Python.
|
|
38
|
+
|
|
39
|
+
`offlineRevGeocoder` takes a latitude and longitude and returns a small `GeoResult`
|
|
40
|
+
object with the matching country, continent, ocean, and nearest city when one is
|
|
41
|
+
close enough. It is intentionally simple: the geographic source data is prepared
|
|
42
|
+
ahead of time, cached as a pickle file, and loaded locally at runtime. No API
|
|
43
|
+
keys, no network requests, no external service latency.
|
|
44
|
+
|
|
45
|
+
The bundled lookup data is derived from [Natural Earth](https://www.naturalearthdata.com/),
|
|
46
|
+
a public domain map dataset. The prepared cache is included so callers can use
|
|
47
|
+
the package immediately.
|
|
48
|
+
|
|
49
|
+
## Project Info
|
|
50
|
+
|
|
51
|
+
- PyPI package: `offlineRevGeocoder`
|
|
52
|
+
- Import package: `offlineRevGeocoder`
|
|
53
|
+
- Source code: [github.com/aminjavadi02/reverseGeocoder-py](https://github.com/aminjavadi02/reverseGeocoder-py)
|
|
54
|
+
- License: MIT
|
|
55
|
+
- Author: AminJavadi
|
|
56
|
+
|
|
57
|
+
This is an open-source package. Bug reports, fixes, data improvements, and
|
|
58
|
+
documentation updates are welcome; please open an issue or send a pull request
|
|
59
|
+
on GitHub.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install offlineRevGeocoder
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from offlineRevGeocoder import get
|
|
71
|
+
|
|
72
|
+
result = get(35.6892, 51.3890)
|
|
73
|
+
|
|
74
|
+
print(result.country) # Iran
|
|
75
|
+
print(result.country_iso2) # IR
|
|
76
|
+
print(result.city) # Tehran
|
|
77
|
+
print(result.precision) # city
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Common Use Case
|
|
81
|
+
|
|
82
|
+
This package is most useful when you only need a small reverse geocoding answer,
|
|
83
|
+
for example checking whether a user's latitude and longitude are in or near a
|
|
84
|
+
specific city. In that kind of flow, you often do not need to pay for a full
|
|
85
|
+
reverse geocoding API subscription.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from offlineRevGeocoder import get
|
|
89
|
+
|
|
90
|
+
place = get(35.7000, 51.4000, city_radius_km=25)
|
|
91
|
+
|
|
92
|
+
print(place.city) # Tehran
|
|
93
|
+
print(place.precision) # city
|
|
94
|
+
print(place.city == "Tehran") # True
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For a nearby-city check, use `city_radius_km` to control how strict the match
|
|
98
|
+
should be for your product.
|
|
99
|
+
|
|
100
|
+
## Return Type
|
|
101
|
+
|
|
102
|
+
`get(lat, lon, city_radius_km=50)` returns:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
GeoResult(
|
|
106
|
+
continent: str | None,
|
|
107
|
+
country: str | None,
|
|
108
|
+
country_iso2: str | None,
|
|
109
|
+
country_iso3: str | None,
|
|
110
|
+
ocean: str | None,
|
|
111
|
+
city: str | None,
|
|
112
|
+
city_distance_km: float | None,
|
|
113
|
+
precision: str,
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`precision` is one of:
|
|
118
|
+
|
|
119
|
+
- `city`: a nearby city was found.
|
|
120
|
+
- `country`: the point matched a country, but no nearby city was found.
|
|
121
|
+
- `ocean`: the point matched an ocean or marine area.
|
|
122
|
+
- `none`: no matching land or ocean feature was found.
|
|
123
|
+
|
|
124
|
+
## Data
|
|
125
|
+
|
|
126
|
+
The package uses Natural Earth datasets that were transformed into a compact
|
|
127
|
+
pickle cache at `offlineRevGeocoder/data/reverse_geocoder_cache.pkl`. Natural Earth data is
|
|
128
|
+
public domain, so it can be redistributed with this MIT-licensed package.
|
|
129
|
+
|
|
130
|
+
The code is MIT licensed. Natural Earth requests attribution where possible; this
|
|
131
|
+
README keeps that attribution with the package.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
offlineRevGeocoder/__init__.py
|
|
6
|
+
offlineRevGeocoder/reverseGeocoder.py
|
|
7
|
+
offlineRevGeocoder.egg-info/PKG-INFO
|
|
8
|
+
offlineRevGeocoder.egg-info/SOURCES.txt
|
|
9
|
+
offlineRevGeocoder.egg-info/dependency_links.txt
|
|
10
|
+
offlineRevGeocoder.egg-info/top_level.txt
|
|
11
|
+
offlineRevGeocoder/data/__init__.py
|
|
12
|
+
offlineRevGeocoder/data/reverse_geocoder_cache.pkl
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
offlineRevGeocoder
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
|
|
5
|
+
ROOT = Path(__file__).parent
|
|
6
|
+
README = (ROOT / "README.md").read_text(encoding="utf-8")
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="offlineRevGeocoder",
|
|
10
|
+
version="0.1.0",
|
|
11
|
+
description="Fast offline reverse geocoding using a prepared Natural Earth cache",
|
|
12
|
+
long_description=README,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
author="AminJavadi",
|
|
15
|
+
url="https://github.com/aminjavadi02/reverseGeocoder-py",
|
|
16
|
+
project_urls={
|
|
17
|
+
"Source": "https://github.com/aminjavadi02/reverseGeocoder-py",
|
|
18
|
+
"Natural Earth": "https://www.naturalearthdata.com/",
|
|
19
|
+
},
|
|
20
|
+
packages=["offlineRevGeocoder", "offlineRevGeocoder.data"],
|
|
21
|
+
include_package_data=True,
|
|
22
|
+
package_data={"offlineRevGeocoder": ["data/*.pkl"]},
|
|
23
|
+
python_requires=">=3.10",
|
|
24
|
+
install_requires=[],
|
|
25
|
+
license="MIT",
|
|
26
|
+
license_files=["LICENSE"],
|
|
27
|
+
classifiers=[
|
|
28
|
+
"Development Status :: 3 - Alpha",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Topic :: Scientific/Engineering :: GIS",
|
|
37
|
+
],
|
|
38
|
+
keywords=["geocoder", "geocoding", "gis", "natural-earth", "reverse-geocoding"],
|
|
39
|
+
)
|