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 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)
@@ -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
+ [![PyPI - Version](https://img.shields.io/pypi/v/giso)](https://pypi.org/project/giso/)
33
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/giso)](https://pypi.org/project/giso/)
34
+ [![PyPI Downloads](https://static.pepy.tech/badge/giso/month)](https://pepy.tech/projects/giso)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-lightgrey.svg?logo=)](https://github.com/corbel-spatial/giso/blob/main/LICENSE)
36
+ [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ giso = giso._cli:cli
@@ -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.