sameer-graph-lib 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.
Files changed (25) hide show
  1. sameer_graph_lib-0.1.0/LICENSE +21 -0
  2. sameer_graph_lib-0.1.0/PKG-INFO +197 -0
  3. sameer_graph_lib-0.1.0/README.md +163 -0
  4. sameer_graph_lib-0.1.0/pyproject.toml +41 -0
  5. sameer_graph_lib-0.1.0/setup.cfg +4 -0
  6. sameer_graph_lib-0.1.0/src/sameer_graph_lib/__init__.py +28 -0
  7. sameer_graph_lib-0.1.0/src/sameer_graph_lib/_h3.py +123 -0
  8. sameer_graph_lib-0.1.0/src/sameer_graph_lib/affinity_graph.py +775 -0
  9. sameer_graph_lib-0.1.0/src/sameer_graph_lib/corridor_extractor.py +82 -0
  10. sameer_graph_lib-0.1.0/src/sameer_graph_lib/geometry.py +75 -0
  11. sameer_graph_lib-0.1.0/src/sameer_graph_lib/hex_graph.py +15 -0
  12. sameer_graph_lib-0.1.0/src/sameer_graph_lib/plotting.py +211 -0
  13. sameer_graph_lib-0.1.0/src/sameer_graph_lib/spatial_ingestor.py +170 -0
  14. sameer_graph_lib-0.1.0/src/sameer_graph_lib/topology_analyzer.py +174 -0
  15. sameer_graph_lib-0.1.0/src/sameer_graph_lib.egg-info/PKG-INFO +197 -0
  16. sameer_graph_lib-0.1.0/src/sameer_graph_lib.egg-info/SOURCES.txt +23 -0
  17. sameer_graph_lib-0.1.0/src/sameer_graph_lib.egg-info/dependency_links.txt +1 -0
  18. sameer_graph_lib-0.1.0/src/sameer_graph_lib.egg-info/requires.txt +15 -0
  19. sameer_graph_lib-0.1.0/src/sameer_graph_lib.egg-info/top_level.txt +1 -0
  20. sameer_graph_lib-0.1.0/tests/test_geometry.py +42 -0
  21. sameer_graph_lib-0.1.0/tests/test_h3_cell_plotting.py +44 -0
  22. sameer_graph_lib-0.1.0/tests/test_h3_cells_map_plotting.py +45 -0
  23. sameer_graph_lib-0.1.0/tests/test_h3_grid_distance.py +12 -0
  24. sameer_graph_lib-0.1.0/tests/test_plotting.py +33 -0
  25. sameer_graph_lib-0.1.0/tests/test_smoke.py +62 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sameer Kumar
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.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: sameer-graph-lib
3
+ Version: 0.1.0
4
+ Summary: H3 and NetworkX based route affinity graph toolkit.
5
+ Author-email: Sameer <sameerkumarroy073@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/iams31/sameer_graph_lib
8
+ Project-URL: Repository, https://github.com/iams31/sameer_graph_lib
9
+ Project-URL: Issues, https://github.com/iams31/sameer_graph_lib/issues
10
+ Keywords: h3,networkx,geospatial,routing,graph
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: GIS
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: h3>=4.0.0
22
+ Requires-Dist: networkx>=3.0
23
+ Provides-Extra: plot
24
+ Requires-Dist: matplotlib>=3.7; extra == "plot"
25
+ Provides-Extra: geo
26
+ Requires-Dist: contextily>=1.5; extra == "geo"
27
+ Requires-Dist: geopandas>=0.14; extra == "geo"
28
+ Requires-Dist: shapely>=2.0; extra == "geo"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: build>=1.2; extra == "dev"
32
+ Requires-Dist: twine>=5.1; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # sameer-graph-lib
36
+
37
+ `sameer-graph-lib` is an editable Python library for building H3-based route affinity graphs with NetworkX.
38
+
39
+ It turns H3 arrays, latitude/longitude sequences, and encoded polylines into connected hex chains, inserts them into a weighted graph, extracts high-affinity corridors, and decomposes the graph into a main trunk plus minor branches.
40
+
41
+ ## Editable install
42
+
43
+ ```powershell
44
+ python -m pip install -e ".[dev,plot]"
45
+ ```
46
+
47
+ Because the install is editable, changes you make inside `src/sameer_graph_lib` are picked up immediately by Python without reinstalling.
48
+
49
+ If your machine uses `uv`, run commands through the managed environment:
50
+
51
+ ```powershell
52
+ uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
53
+ ```
54
+
55
+ ## Install
56
+
57
+ From PyPI after publication:
58
+
59
+ ```powershell
60
+ pip install sameer-graph-lib
61
+ ```
62
+
63
+ With optional plotting and geospatial extras:
64
+
65
+ ```powershell
66
+ pip install "sameer-graph-lib[plot,geo]"
67
+ ```
68
+
69
+ ## Quick start
70
+
71
+ ```python
72
+ from sameer_graph_lib import HexGraph
73
+
74
+ graph = HexGraph(hex_resolution=9)
75
+
76
+ route = graph.add_latlng_sequence([
77
+ (12.9716, 77.5946),
78
+ (12.9760, 77.5990),
79
+ ])
80
+
81
+ print(graph.get_graph_stats())
82
+ selected = graph.get_appropriate_hexes(cutoff=0.8)
83
+ fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
84
+ print(graph.decompose_branches())
85
+ ```
86
+
87
+ ## Main APIs
88
+
89
+ - `SpatialIngestor`: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.
90
+ - `AffinityGraph`: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.
91
+ - `CorridorExtractor`: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.
92
+ - `TopologyAnalyzer`: separates the main branch from residual minor branches.
93
+ - `HexGraph`: backwards-compatible convenience class for your original code style.
94
+
95
+ Graph creation follows the original per-node procedure: H3 arrays are normalized, then each hex is inserted with `add_node`/`add_hex`. Lat/lng sequences and encoded polylines are first converted into H3 arrays at the requested resolution, then inserted the same way.
96
+
97
+ For QC, use:
98
+
99
+ ```python
100
+ fig = graph.visualize_graph(highlight_hexes=selected)
101
+ fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
102
+
103
+ fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
104
+ fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
105
+ ```
106
+
107
+ To plot actual H3 hex boundaries as geospatial polygons:
108
+
109
+ ```python
110
+ from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
111
+
112
+ fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
113
+ fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
114
+
115
+ fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
116
+ fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
117
+
118
+ fig = plot_h3_cells_map(route, selected_cells=selected)
119
+ fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
120
+ ```
121
+
122
+ `plot_h3_cells_map` uses GeoPandas + Contextily. Install it with:
123
+
124
+ ```powershell
125
+ python -m pip install -e ".[plot,geo]"
126
+ uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
127
+ ```
128
+
129
+ To get H3 centers and a convex hull:
130
+
131
+ ```python
132
+ from sameer_graph_lib import getLatLng, h3_convex_hull
133
+
134
+ points = getLatLng(route) # [(lat, lng), ...]
135
+ hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
136
+ graph_hull = graph.convex_hull() # Same, using graph nodes
137
+ ```
138
+
139
+ ## Useful commands
140
+
141
+ ```powershell
142
+ python -m pytest
143
+ python -m build
144
+ uv run --extra dev pytest -q
145
+ uv run --extra dev python -m build
146
+ uv run --extra dev python -m twine check dist/*
147
+ ```
148
+
149
+ Build artifacts will appear in `dist/` after `python -m build`.
150
+
151
+ ## Publish To PyPI
152
+
153
+ 1. Build the package:
154
+
155
+ ```powershell
156
+ uv run --extra dev python -m build
157
+ ```
158
+
159
+ 2. Validate the package metadata:
160
+
161
+ ```powershell
162
+ uv run --extra dev python -m twine check dist/*
163
+ ```
164
+
165
+ 3. Upload to PyPI:
166
+
167
+ ```powershell
168
+ uv run --extra dev python -m twine upload dist/*
169
+ ```
170
+
171
+ After upload, users can install it with:
172
+
173
+ ```powershell
174
+ pip install sameer-graph-lib
175
+ ```
176
+
177
+ ## Publish From GitHub
178
+
179
+ This repo also includes a Trusted Publishing workflow in
180
+ [.github/workflows/publish.yml](C:/Users/rrran/Desktop/sameer_graph_lib/.github/workflows/publish.yml:1).
181
+
182
+ To finish that setup:
183
+
184
+ 1. Create the project on PyPI, or reserve the name `sameer-graph-lib`.
185
+ 2. On PyPI, open the project settings and add a Trusted Publisher for:
186
+ `owner`: `iams31`
187
+ `repository`: `sameer_graph_lib`
188
+ `workflow`: `publish.yml`
189
+ `environment`: `pypi`
190
+ 3. Create a GitHub Release, or run the workflow manually from the Actions tab.
191
+
192
+ After that, GitHub Actions can publish without storing a long-lived PyPI token.
193
+
194
+ Official references:
195
+
196
+ - PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
197
+ - Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/
@@ -0,0 +1,163 @@
1
+ # sameer-graph-lib
2
+
3
+ `sameer-graph-lib` is an editable Python library for building H3-based route affinity graphs with NetworkX.
4
+
5
+ It turns H3 arrays, latitude/longitude sequences, and encoded polylines into connected hex chains, inserts them into a weighted graph, extracts high-affinity corridors, and decomposes the graph into a main trunk plus minor branches.
6
+
7
+ ## Editable install
8
+
9
+ ```powershell
10
+ python -m pip install -e ".[dev,plot]"
11
+ ```
12
+
13
+ Because the install is editable, changes you make inside `src/sameer_graph_lib` are picked up immediately by Python without reinstalling.
14
+
15
+ If your machine uses `uv`, run commands through the managed environment:
16
+
17
+ ```powershell
18
+ uv run --extra dev --extra plot python -c "import sameer_graph_lib; print(sameer_graph_lib.__version__)"
19
+ ```
20
+
21
+ ## Install
22
+
23
+ From PyPI after publication:
24
+
25
+ ```powershell
26
+ pip install sameer-graph-lib
27
+ ```
28
+
29
+ With optional plotting and geospatial extras:
30
+
31
+ ```powershell
32
+ pip install "sameer-graph-lib[plot,geo]"
33
+ ```
34
+
35
+ ## Quick start
36
+
37
+ ```python
38
+ from sameer_graph_lib import HexGraph
39
+
40
+ graph = HexGraph(hex_resolution=9)
41
+
42
+ route = graph.add_latlng_sequence([
43
+ (12.9716, 77.5946),
44
+ (12.9760, 77.5990),
45
+ ])
46
+
47
+ print(graph.get_graph_stats())
48
+ selected = graph.get_appropriate_hexes(cutoff=0.8)
49
+ fig = graph.visualize_graph(title="80% compact cluster", highlight_hexes=selected)
50
+ print(graph.decompose_branches())
51
+ ```
52
+
53
+ ## Main APIs
54
+
55
+ - `SpatialIngestor`: converts H3 arrays, lat/lng sequences, and encoded polylines into contiguous H3 chains.
56
+ - `AffinityGraph`: NetworkX wrapper for array-based insertion, nearest attachment, affinity scoring, editing, and JSON persistence.
57
+ - `CorridorExtractor`: uses exact all-node Dijkstra selection to extract the most compact cluster covering a target percentage of graph traversal volume.
58
+ - `TopologyAnalyzer`: separates the main branch from residual minor branches.
59
+ - `HexGraph`: backwards-compatible convenience class for your original code style.
60
+
61
+ Graph creation follows the original per-node procedure: H3 arrays are normalized, then each hex is inserted with `add_node`/`add_hex`. Lat/lng sequences and encoded polylines are first converted into H3 arrays at the requested resolution, then inserted the same way.
62
+
63
+ For QC, use:
64
+
65
+ ```python
66
+ fig = graph.visualize_graph(highlight_hexes=selected)
67
+ fig.savefig("graph_qc.png", dpi=150, bbox_inches="tight")
68
+
69
+ fig = graph.visualize_step_by_step(route[:5], labels=["A", "B", "C", "D", "E"])
70
+ fig.savefig("insertion_steps.png", dpi=150, bbox_inches="tight")
71
+ ```
72
+
73
+ To plot actual H3 hex boundaries as geospatial polygons:
74
+
75
+ ```python
76
+ from sameer_graph_lib import plot_h3_cells, plot_h3_cells_map
77
+
78
+ fig = plot_h3_cells("88618c4f29fffff", label_full_hex=True)
79
+ fig.savefig("single_h3_cell.png", dpi=150, bbox_inches="tight")
80
+
81
+ fig = graph.plot_h3_cells(highlight_hexes=selected, show_labels=False)
82
+ fig.savefig("h3_cell_footprint.png", dpi=150, bbox_inches="tight")
83
+
84
+ fig = plot_h3_cells_map(route, selected_cells=selected)
85
+ fig.savefig("h3_cell_basemap.png", dpi=150, bbox_inches="tight")
86
+ ```
87
+
88
+ `plot_h3_cells_map` uses GeoPandas + Contextily. Install it with:
89
+
90
+ ```powershell
91
+ python -m pip install -e ".[plot,geo]"
92
+ uv run --extra plot --extra geo python -c "from sameer_graph_lib import plot_h3_cells_map"
93
+ ```
94
+
95
+ To get H3 centers and a convex hull:
96
+
97
+ ```python
98
+ from sameer_graph_lib import getLatLng, h3_convex_hull
99
+
100
+ points = getLatLng(route) # [(lat, lng), ...]
101
+ hull = h3_convex_hull(route) # Shapely geometry in (lng, lat)
102
+ graph_hull = graph.convex_hull() # Same, using graph nodes
103
+ ```
104
+
105
+ ## Useful commands
106
+
107
+ ```powershell
108
+ python -m pytest
109
+ python -m build
110
+ uv run --extra dev pytest -q
111
+ uv run --extra dev python -m build
112
+ uv run --extra dev python -m twine check dist/*
113
+ ```
114
+
115
+ Build artifacts will appear in `dist/` after `python -m build`.
116
+
117
+ ## Publish To PyPI
118
+
119
+ 1. Build the package:
120
+
121
+ ```powershell
122
+ uv run --extra dev python -m build
123
+ ```
124
+
125
+ 2. Validate the package metadata:
126
+
127
+ ```powershell
128
+ uv run --extra dev python -m twine check dist/*
129
+ ```
130
+
131
+ 3. Upload to PyPI:
132
+
133
+ ```powershell
134
+ uv run --extra dev python -m twine upload dist/*
135
+ ```
136
+
137
+ After upload, users can install it with:
138
+
139
+ ```powershell
140
+ pip install sameer-graph-lib
141
+ ```
142
+
143
+ ## Publish From GitHub
144
+
145
+ This repo also includes a Trusted Publishing workflow in
146
+ [.github/workflows/publish.yml](C:/Users/rrran/Desktop/sameer_graph_lib/.github/workflows/publish.yml:1).
147
+
148
+ To finish that setup:
149
+
150
+ 1. Create the project on PyPI, or reserve the name `sameer-graph-lib`.
151
+ 2. On PyPI, open the project settings and add a Trusted Publisher for:
152
+ `owner`: `iams31`
153
+ `repository`: `sameer_graph_lib`
154
+ `workflow`: `publish.yml`
155
+ `environment`: `pypi`
156
+ 3. Create a GitHub Release, or run the workflow manually from the Actions tab.
157
+
158
+ After that, GitHub Actions can publish without storing a long-lived PyPI token.
159
+
160
+ Official references:
161
+
162
+ - PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
163
+ - Packaging guide upload flow: https://packaging.python.org/tutorials/packaging-projects/
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sameer-graph-lib"
7
+ version = "0.1.0"
8
+ description = "H3 and NetworkX based route affinity graph toolkit."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Sameer", email = "sameerkumarroy073@gmail.com" }
14
+ ]
15
+ dependencies = [
16
+ "h3>=4.0.0",
17
+ "networkx>=3.0"
18
+ ]
19
+ keywords = ["h3", "networkx", "geospatial", "routing", "graph"]
20
+ classifiers = [
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Developers",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Scientific/Engineering :: GIS"
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/iams31/sameer_graph_lib"
32
+ Repository = "https://github.com/iams31/sameer_graph_lib"
33
+ Issues = "https://github.com/iams31/sameer_graph_lib/issues"
34
+
35
+ [project.optional-dependencies]
36
+ plot = ["matplotlib>=3.7"]
37
+ geo = ["contextily>=1.5", "geopandas>=0.14", "shapely>=2.0"]
38
+ dev = ["pytest>=8.0", "build>=1.2", "twine>=5.1"]
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,28 @@
1
+ """Public API for sameer_graph_lib."""
2
+
3
+ from .affinity_graph import AffinityGraph
4
+ from .corridor_extractor import CorridorExtractor
5
+ from .geometry import getLatLng, get_latlng, h3_convex_hull, h3_convex_hull_geojson, makingHull, making_hull
6
+ from .hex_graph import HexGraph
7
+ from .plotting import cells_to_geodataframe, plot_h3_cells, plot_h3_cells_map
8
+ from .spatial_ingestor import SpatialIngestor
9
+ from .topology_analyzer import TopologyAnalyzer
10
+
11
+ __all__ = [
12
+ "AffinityGraph",
13
+ "CorridorExtractor",
14
+ "HexGraph",
15
+ "cells_to_geodataframe",
16
+ "getLatLng",
17
+ "get_latlng",
18
+ "h3_convex_hull",
19
+ "h3_convex_hull_geojson",
20
+ "makingHull",
21
+ "making_hull",
22
+ "plot_h3_cells",
23
+ "plot_h3_cells_map",
24
+ "SpatialIngestor",
25
+ "TopologyAnalyzer",
26
+ ]
27
+
28
+ __version__ = "0.1.0"
@@ -0,0 +1,123 @@
1
+ """Small compatibility layer for h3-py 3.x and 4.x APIs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import List, Tuple
7
+
8
+ import h3
9
+
10
+ LatLng = Tuple[float, float]
11
+
12
+ EARTH_RADIUS_KM = 6371.0088
13
+
14
+
15
+ def is_valid_cell(cell: str) -> bool:
16
+ fn = getattr(h3, "is_valid_cell", None) or getattr(h3, "h3_is_valid", None)
17
+ return bool(fn(cell)) if fn else False
18
+
19
+
20
+ def latlng_to_cell(lat: float, lng: float, resolution: int) -> str:
21
+ fn = getattr(h3, "latlng_to_cell", None)
22
+ if fn:
23
+ return fn(lat, lng, resolution)
24
+ return h3.geo_to_h3(lat, lng, resolution)
25
+
26
+
27
+ def cell_to_latlng(cell: str) -> LatLng:
28
+ fn = getattr(h3, "cell_to_latlng", None)
29
+ if fn:
30
+ lat, lng = fn(cell)
31
+ else:
32
+ lat, lng = h3.h3_to_geo(cell)
33
+ return float(lat), float(lng)
34
+
35
+
36
+ def cell_to_boundary(cell: str) -> List[LatLng]:
37
+ fn = getattr(h3, "cell_to_boundary", None)
38
+ if fn:
39
+ return [(float(lat), float(lng)) for lat, lng in fn(cell)]
40
+
41
+ legacy_fn = getattr(h3, "h3_to_geo_boundary", None)
42
+ if legacy_fn:
43
+ return [(float(lat), float(lng)) for lat, lng in legacy_fn(cell, geo_json=False)]
44
+
45
+ raise RuntimeError("Installed h3 package does not expose cell boundary helpers.")
46
+
47
+
48
+ def get_resolution(cell: str) -> int:
49
+ fn = getattr(h3, "get_resolution", None) or getattr(h3, "h3_get_resolution", None)
50
+ return int(fn(cell))
51
+
52
+
53
+ def grid_path_cells(start: str, end: str) -> List[str]:
54
+ fn = getattr(h3, "grid_path_cells", None) or getattr(h3, "h3_line", None)
55
+ if not fn:
56
+ raise RuntimeError("Installed h3 package does not expose grid path helpers.")
57
+ return list(fn(start, end))
58
+
59
+
60
+ def average_hexagon_edge_length(resolution: int, unit: str = "km") -> float:
61
+ fn = getattr(h3, "average_hexagon_edge_length", None)
62
+ if fn:
63
+ return float(fn(resolution, unit=unit))
64
+
65
+ fn = getattr(h3, "edge_length", None)
66
+ if fn:
67
+ try:
68
+ return float(fn(resolution, unit=unit))
69
+ except TypeError:
70
+ return float(fn(res=resolution, unit=unit))
71
+
72
+ raise RuntimeError("Installed h3 package does not expose edge length helpers.")
73
+
74
+
75
+ def cell_area(cell: str, unit: str = "km^2") -> float:
76
+ fn = getattr(h3, "cell_area", None)
77
+ if not fn:
78
+ raise RuntimeError("Installed h3 package does not expose cell_area.")
79
+ return float(fn(cell, unit=unit))
80
+
81
+
82
+ def haversine_km(a: LatLng, b: LatLng) -> float:
83
+ lat1, lng1 = map(math.radians, a)
84
+ lat2, lng2 = map(math.radians, b)
85
+ dlat = lat2 - lat1
86
+ dlng = lng2 - lng1
87
+ sin_dlat = math.sin(dlat / 2.0)
88
+ sin_dlng = math.sin(dlng / 2.0)
89
+ h = sin_dlat**2 + math.cos(lat1) * math.cos(lat2) * sin_dlng**2
90
+ return 2.0 * EARTH_RADIUS_KM * math.asin(min(1.0, math.sqrt(h)))
91
+
92
+
93
+ def approximate_grid_distance(start: str, end: str) -> int:
94
+ if start == end:
95
+ return 0
96
+
97
+ resolution = get_resolution(start)
98
+ edge_km = average_hexagon_edge_length(resolution, unit="km")
99
+ center_spacing_km = max(edge_km * math.sqrt(3), 1e-9)
100
+ distance_km = haversine_km(cell_to_latlng(start), cell_to_latlng(end))
101
+ return max(1, int(round(distance_km / center_spacing_km)))
102
+
103
+
104
+ def grid_distance(start: str, end: str) -> int:
105
+ """Return native H3 grid distance between two cells."""
106
+ if start == end:
107
+ return 0
108
+
109
+ fn = getattr(h3, "grid_distance", None)
110
+ if fn:
111
+ try:
112
+ return int(fn(start, end))
113
+ except Exception:
114
+ pass
115
+
116
+ legacy_fn = getattr(h3, "h3_distance", None)
117
+ if legacy_fn:
118
+ try:
119
+ return int(legacy_fn(start, end))
120
+ except Exception:
121
+ pass
122
+
123
+ return approximate_grid_distance(start, end)