giso 1.0.0__py3-none-any.whl
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.
- giso/__init__.py +1 -0
- giso/_cli.py +99 -0
- giso/_core.py +149 -0
- giso/data/ne_10m_admin_1_states_provinces.parquet +0 -0
- giso-1.0.0.dist-info/METADATA +85 -0
- giso-1.0.0.dist-info/RECORD +9 -0
- giso-1.0.0.dist-info/WHEEL +4 -0
- giso-1.0.0.dist-info/entry_points.txt +2 -0
- giso-1.0.0.dist-info/licenses/LICENSE +21 -0
giso/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._core import *
|
giso/_cli.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@click.command(
|
|
5
|
+
no_args_is_help=True,
|
|
6
|
+
context_settings={"ignore_unknown_options": True},
|
|
7
|
+
)
|
|
8
|
+
@click.argument("args", nargs=-1)
|
|
9
|
+
@click.option(
|
|
10
|
+
"--update", help="Re-download the lookup dataset before querying it.", is_flag=True
|
|
11
|
+
)
|
|
12
|
+
def cli(args, update):
|
|
13
|
+
# noinspection PyUnresolvedReferences
|
|
14
|
+
"""
|
|
15
|
+
A simple command line tool to help with geocoding country/region ISO 3166-2 codes.
|
|
16
|
+
|
|
17
|
+
Uses the dataset "ne_10m_admin_1_states_provinces" from https://github.com/nvkelso/natural-earth-vector
|
|
18
|
+
|
|
19
|
+
Takes one of two inputs:
|
|
20
|
+
|
|
21
|
+
1. A longitude/latitude coordinate pair. (Decimal degrees in WGS 1984 separated by a comma or a space.)
|
|
22
|
+
Returns the corresponding ISO 3166-2 code.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
>>> giso -122.2483823, 37.8245529
|
|
28
|
+
US-CA
|
|
29
|
+
|
|
30
|
+
2. A valid ISO 3166-2 code. Returns the corresponding geometry as Well-Known Text (WKT).
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
>>> giso US-CA
|
|
36
|
+
MULTIPOLYGON (((-114.724285 32.712836, -114.764541 32.709839, [...]
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
if update:
|
|
40
|
+
from ._core import update
|
|
41
|
+
|
|
42
|
+
update(overwrite=True)
|
|
43
|
+
if len(args) == 0:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
iso_code, x, y = None, None, None
|
|
47
|
+
try:
|
|
48
|
+
if len(args) == 1:
|
|
49
|
+
arg = args[0]
|
|
50
|
+
if "-" in arg and not arg.startswith("-"): # geocode
|
|
51
|
+
iso_code = arg.strip().upper()
|
|
52
|
+
assert len(iso_code.split("-")) == 2
|
|
53
|
+
elif "," in arg: # reverse geocode comma delimited
|
|
54
|
+
coordinates = arg.split(",")
|
|
55
|
+
x, y = map(float, coordinates)
|
|
56
|
+
|
|
57
|
+
elif " " in arg: # reverse geocode space delimited
|
|
58
|
+
while " " in arg:
|
|
59
|
+
arg = arg.replace(" ", " ")
|
|
60
|
+
coordinates = arg.split(" ")
|
|
61
|
+
x, y = map(float, coordinates)
|
|
62
|
+
else:
|
|
63
|
+
assert False
|
|
64
|
+
|
|
65
|
+
elif len(args) == 2: # reverse geocode
|
|
66
|
+
x = float(args[0].strip(","))
|
|
67
|
+
y = float(args[1].strip(","))
|
|
68
|
+
|
|
69
|
+
else:
|
|
70
|
+
assert False
|
|
71
|
+
|
|
72
|
+
except (AssertionError, ValueError):
|
|
73
|
+
click.echo(
|
|
74
|
+
"Invalid input: Please provide either a valid ISO 3166-2 code "
|
|
75
|
+
"or a comma/space-separated longitude/latitude pair.",
|
|
76
|
+
err=True,
|
|
77
|
+
)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
if iso_code:
|
|
81
|
+
# click.echo(f"Geocoding: {iso_code}")
|
|
82
|
+
from ._core import geocode
|
|
83
|
+
|
|
84
|
+
result = geocode(iso_code)
|
|
85
|
+
click.echo(str(result))
|
|
86
|
+
return result
|
|
87
|
+
elif x and y:
|
|
88
|
+
# click.echo(f"Reverse geocoding: x={x}, y={y}")
|
|
89
|
+
from ._core import reverse_geocode
|
|
90
|
+
|
|
91
|
+
result = reverse_geocode(x, y)
|
|
92
|
+
click.echo(str(result))
|
|
93
|
+
return result
|
|
94
|
+
else:
|
|
95
|
+
raise RuntimeError
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
cli()
|
giso/_core.py
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import geopandas as gpd
|
|
4
|
+
import sedona.db
|
|
5
|
+
import shapely
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
_DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
|
|
9
|
+
_DATA_FILE = os.path.join(_DATA_DIR, "ne_10m_admin_1_states_provinces.parquet")
|
|
10
|
+
_DATA_URL = "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson/ne_10m_admin_1_states_provinces.geojson"
|
|
11
|
+
|
|
12
|
+
__all__ = ["Giso", "clear", "geocode", "reverse_geocode", "update"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def clear():
|
|
16
|
+
"""Clears the data directory and file.
|
|
17
|
+
|
|
18
|
+
This function removes the data directory and its contents.
|
|
19
|
+
It first checks if the data file exists and removes it if it does.
|
|
20
|
+
Then, it checks if the data directory exists and removes it if it does.
|
|
21
|
+
"""
|
|
22
|
+
if os.path.exists(_DATA_FILE):
|
|
23
|
+
print(f"Removing {_DATA_FILE}")
|
|
24
|
+
os.remove(_DATA_FILE)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Giso:
|
|
28
|
+
"""
|
|
29
|
+
This class provides functionality to download, read, and geocode
|
|
30
|
+
natural earth vector data.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
data_file: str = _DATA_FILE,
|
|
36
|
+
data_url: str = _DATA_URL,
|
|
37
|
+
autoupdate: bool = True,
|
|
38
|
+
):
|
|
39
|
+
self._data_file = data_file
|
|
40
|
+
self._data_url = data_url
|
|
41
|
+
self._sd: sedona.db.context.SedonaContext = sedona.db.connect()
|
|
42
|
+
|
|
43
|
+
if autoupdate:
|
|
44
|
+
self.update()
|
|
45
|
+
|
|
46
|
+
if not os.path.exists(self._data_file):
|
|
47
|
+
print("Giso object must be initialized with Giso.update()")
|
|
48
|
+
else:
|
|
49
|
+
df = self._sd.read_parquet(self._data_file)
|
|
50
|
+
df.to_view("subdivisions")
|
|
51
|
+
|
|
52
|
+
def geocode(
|
|
53
|
+
self, iso_3166_2: str
|
|
54
|
+
) -> type[shapely.Polygon, shapely.MultiPolygon] | None:
|
|
55
|
+
"""
|
|
56
|
+
This function geocodes a location based on its ISO 3166-2 country code.
|
|
57
|
+
It retrieves the corresponding geographic data from a GeoDataFrame and
|
|
58
|
+
returns it as a GeoJSON feature. If no data is found, it prints a
|
|
59
|
+
message to the console and returns None.
|
|
60
|
+
"""
|
|
61
|
+
queried: sedona.db.dataframe.DataFrame = self._sd.sql(
|
|
62
|
+
f"SELECT geometry FROM subdivisions WHERE iso_3166_2 = '{iso_3166_2}'"
|
|
63
|
+
)
|
|
64
|
+
queried: gpd.GeoDataFrame = queried.to_pandas()
|
|
65
|
+
if len(queried) >= 1:
|
|
66
|
+
result: type[shapely.Polygon, shapely.MultiPolygon] = queried["geometry"][0]
|
|
67
|
+
return result
|
|
68
|
+
else:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
def reverse_geocode(self, x: float, y: float) -> str | None:
|
|
72
|
+
"""
|
|
73
|
+
This function performs a reverse geocode lookup using a GeoDataFrame.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
x: The longitude coordinate of the point.
|
|
77
|
+
y: The latitude coordinate of the point.
|
|
78
|
+
"""
|
|
79
|
+
query_pt = gpd.GeoDataFrame(geometry=[shapely.Point(x, y)], crs="EPSG:4326")
|
|
80
|
+
query_pt = self._sd.create_data_frame(query_pt)
|
|
81
|
+
query_pt.to_view("query_pt")
|
|
82
|
+
|
|
83
|
+
queried: sedona.db.dataframe.DataFrame = self._sd.sql(
|
|
84
|
+
"""
|
|
85
|
+
SELECT
|
|
86
|
+
subdivisions.iso_3166_2 as iso_3166_2,
|
|
87
|
+
subdivisions.geometry as geometry
|
|
88
|
+
FROM subdivisions
|
|
89
|
+
JOIN query_pt ON ST_Intersects(subdivisions.geometry, query_pt.geometry)
|
|
90
|
+
"""
|
|
91
|
+
)
|
|
92
|
+
queried: gpd.GeoDataFrame = queried.to_pandas()
|
|
93
|
+
self._sd.drop_view("query_pt")
|
|
94
|
+
|
|
95
|
+
if len(queried) >= 1:
|
|
96
|
+
result = queried["iso_3166_2"][0]
|
|
97
|
+
assert isinstance(result, str)
|
|
98
|
+
return result
|
|
99
|
+
else:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def update(self, overwrite: bool = False) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Pulls from a public domain dataset on GitHub
|
|
105
|
+
|
|
106
|
+
References:
|
|
107
|
+
- https://github.com/nvkelso/natural-earth-vector
|
|
108
|
+
- https://www.naturalearthdata.com/
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
data_dir = os.path.dirname(self._data_file)
|
|
112
|
+
|
|
113
|
+
if os.path.exists(self._data_file):
|
|
114
|
+
if not overwrite:
|
|
115
|
+
# print("File already exists")
|
|
116
|
+
return
|
|
117
|
+
else:
|
|
118
|
+
print(f"Removing {self._data_file}")
|
|
119
|
+
os.remove(self._data_file)
|
|
120
|
+
|
|
121
|
+
if not os.path.exists(data_dir):
|
|
122
|
+
os.mkdir(data_dir)
|
|
123
|
+
|
|
124
|
+
print(f"Downloading {self._data_url} to {self._data_file}")
|
|
125
|
+
gdf = gpd.read_file(self._data_url)
|
|
126
|
+
gdf.to_parquet(self._data_file, compression="brotli")
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
assert os.path.exists(self._data_file)
|
|
130
|
+
assert os.path.getsize(self._data_file) > 1000
|
|
131
|
+
except AssertionError:
|
|
132
|
+
raise ValueError(f"Invalid file {self._data_file}")
|
|
133
|
+
|
|
134
|
+
print("Done!")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
_inst = Giso()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def geocode(iso_3166_2: str) -> type[shapely.Polygon, shapely.MultiPolygon] | None:
|
|
141
|
+
return _inst.geocode(iso_3166_2=iso_3166_2)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def reverse_geocode(x: float, y: float) -> str | None:
|
|
145
|
+
return _inst.reverse_geocode(x=x, y=y)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def update(overwrite: bool = False):
|
|
149
|
+
_inst.update(overwrite=overwrite)
|
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: giso
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple command line tool to help with geocoding country/region ISO 3166-2 codes
|
|
5
|
+
Project-URL: homepage, https://pypi.org/project/giso/
|
|
6
|
+
Project-URL: repository, https://github.com/corbel-spatial/giso
|
|
7
|
+
Project-URL: issues, https://github.com/corbel-spatial/giso/issues
|
|
8
|
+
Project-URL: changelog, https://github.com/corbel-spatial/giso/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Peter Von der Porten <dev@corbelspatial.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: apache-sedona[db]<2,>=1.8.0
|
|
27
|
+
Requires-Dist: click
|
|
28
|
+
Requires-Dist: geopandas>=1.1.0
|
|
29
|
+
Requires-Dist: pyarrow
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/giso/)
|
|
33
|
+
[](https://pypi.org/project/giso/)
|
|
34
|
+
[](https://pepy.tech/projects/giso)
|
|
35
|
+
[](https://github.com/corbel-spatial/giso/blob/main/LICENSE)
|
|
36
|
+
[](https://pixi.sh)
|
|
37
|
+
|
|
38
|
+
# giso
|
|
39
|
+
|
|
40
|
+
A simple command line tool to help with geocoding country/region [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) codes.
|
|
41
|
+
|
|
42
|
+
Includes a public domain reference dataset from [Natural Earth Data](https://github.com/nvkelso/natural-earth-vector)
|
|
43
|
+
which is stored in [GeoParquet](https://geoparquet.org/) format and is accessed with [SedonaDB](https://sedona.apache.org/sedonadb/latest/).
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```shell
|
|
48
|
+
python -m pip install giso
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Basic Usage
|
|
52
|
+
|
|
53
|
+
`giso` takes one of two inputs:
|
|
54
|
+
|
|
55
|
+
- A longitude/latitude coordinate pair. (Decimal degrees in WGS 1984 separated by a comma or a space.)
|
|
56
|
+
Returns the corresponding ISO 3166-2 code.
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
giso -122.2483823, 37.8245529
|
|
60
|
+
# US-CA
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- A valid ISO 3166-2 code. Returns the corresponding geometry as Well-Known Text (WKT).
|
|
64
|
+
|
|
65
|
+
```shell
|
|
66
|
+
giso US-CA
|
|
67
|
+
# MULTIPOLYGON (((-114.724285 32.712836, -114.764541 32.709839, [...]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Returns `None` if there are no hits.
|
|
71
|
+
|
|
72
|
+
`giso` can also be used as a Python package:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
>>> import giso
|
|
76
|
+
>>> giso.reverse_geocode(103.8455041, 1.2936855)
|
|
77
|
+
'SG-01'
|
|
78
|
+
>>> giso.geocode("SG-01")
|
|
79
|
+
< POLYGON((103.898 1.305, 103.888 1.301, 103.853 1.277, 103.847 1.272, 103.8 ... >
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## References
|
|
84
|
+
|
|
85
|
+
- [Natural Earth Data homepage](https://www.naturalearthdata.com/)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
giso/__init__.py,sha256=0zpBfzO64sy3FMVSKxe4TzTdDEXGpDaPHgG23YWlvts,21
|
|
2
|
+
giso/_cli.py,sha256=QUY_cQ5lcSnwmjK4mMkdEcoV6zDLvCcGNpsaU0IWsgM,2776
|
|
3
|
+
giso/_core.py,sha256=7tKcjDSBrazV-a_KgIwH_XeA4MvRL_2bIqNu_Gnuz6Q,4812
|
|
4
|
+
giso/data/ne_10m_admin_1_states_provinces.parquet,sha256=BWIaWCPQ1mv68d76h4WawrBPaEqZ0LmvdRLtv_MKilA,11114502
|
|
5
|
+
giso-1.0.0.dist-info/METADATA,sha256=fZNgv0Fsy-ddjuhCneAFIzrn_4GsbfvtUofPgNNwDO8,3192
|
|
6
|
+
giso-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
giso-1.0.0.dist-info/entry_points.txt,sha256=tgEgLvB__Gtfdhv00GDox-nV4VSFjlWIV_Y0W_tcAbE,39
|
|
8
|
+
giso-1.0.0.dist-info/licenses/LICENSE,sha256=oNiQolItTsRy91IINMUk_2K5S1Lwp_KVA1g2BXxwyog,1077
|
|
9
|
+
giso-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Peter Von der Porten
|
|
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.
|