Generator Module
The generator module provides comprehensive functions to generate discrete global grid systems (DGGS) for various coordinate systems and geographic areas.
Overview
The generator module supports the creation of grid systems for multiple DGGS types:
- H3: Uber's hexagonal hierarchical geospatial indexing system
- S2: Google's spherical geometry library
- A5: Pentagonal DGGS
- RHEALPix: Equal-area hierarchical triangular mesh
- ISEA4T: Icosahedral Snyder Equal Area Aperture 4 Triangle
- ISEA3H: Icosahedral Snyder Equal Area Aperture 3 Hexagon
- EASE: Equal-Area Scalable Earth Grid
- QTM: Quaternary Triangular Mesh
- OLC: Open Location Code (Plus Codes)
- Geohash: Hierarchical spatial data structure
- GEOREF: World Geographic Reference System
- MGRS: Military Grid Reference System
- Tilecode: Hierarchical tiling system
- Quadkey: Microsoft's hierarchical spatial index
- Maidenhead: Amateur radio grid square system
- GARS: Global Area Reference System
- DGGAL: External DGGS family via the
dgg
CLI (e.g., GNOSIS, ISEA3H/9R, IVEA3H/9R, RTEA3H/9R, RHEALPix)
- DGGRID: DGGRIDv7-based generators
Generation Methods
Each DGGS system supports multiple generation methods:
- Global Grid: Generate grid cells covering the entire world
- Bounded Grid: Generate grid cells within a specified bounding box
Grid generation functions support multiple output formats:
- GeoPandas:
gpd
/ geopandas
/ gdf
/ geodataframe
(returns a GeoDataFrame)
- GeoJSON dict:
geojson_dict
/ json_dict
(returns a Python dict)
- GeoJSON file:
geojson
/ json
- Shapefile:
shapefile
/ shp
- GeoPackage:
gpkg
/ geopackage
- Parquet:
parquet
/ geoparquet
- CSV:
csv
Usage Examples
Generate H3 Grid
| from vgrid.generator.h3grid import h3_grid, h3_grid_within_bbox
# Generate global H3 grid at resolution 8
grid = h3_grid(8)
# Generate H3 grid within bounding box
bbox = [106.699007, 10.762811, 106.717674, 10.778649] # [min_lon, min_lat, max_lon, max_lat]
grid = h3_grid_within_bbox(8, bbox=bbox)
# Generate H3 grid as CSV
grid_csv = h3grid(8, output_format="csv")
|
Generate S2 Grid
| from vgrid.generator.s2grid import s2_grid, s2grid
# Generate S2 grid within bounding box at level 13
bbox = [106.699007, 10.762811, 106.717674, 10.778649]
grid = s2_grid(13, bbox)
# Save as GeoJSON
s2grid(13, bbox=bbox, output_format="geojson")
|
Generate DGGAL Grid
| from vgrid.generator.dggalgen import dggalgen
# Generate ISEA3H grid via the external `dgg` CLI (requires `dggal` installed and `dgg` on PATH)
gdf = dggalgen(dggs_type="isea3h", resolution=10, output_format="gpd")
|
Note: When writing files, outputs are saved in the current folder with an auto-generated name like <base>_grid_<resolution>.<ext>
.
DGGRID (Linux only)
DGGRID generation is available on Linux and requires an installed DGGRID executable. See vgrid.generator.dggridgen
for CLI usage and details.
Generator module for vgrid.
This module provides functions to generate discrete global grid systems (DGGS)
for various coordinate systems and geographic areas.
geohash_grid(resolution)
Generate GeoJSON for the entire world at the given geohash resolution.
Source code in vgrid/generator/geohashgrid.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 | def geohash_grid(resolution):
"""Generate GeoJSON for the entire world at the given geohash resolution."""
resolution = validate_geohash_resolution(resolution)
geohashes = set()
for gh in INITIAL_GEOHASHES:
expand_geohash(gh, resolution, geohashes)
geohash_records = []
for gh in tqdm(geohashes, desc="Generating Geohash DGGS", unit=" cells"):
cell_polygon = geohash2geo(gh)
geohash_record = graticule_dggs_to_geoseries(
"geohash", gh, resolution, cell_polygon
)
geohash_records.append(geohash_record)
return gpd.GeoDataFrame(geohash_records, geometry="geometry", crs="EPSG:4326")
|
geohash_grid_within_bbox(resolution, bbox)
Generate GeoJSON for geohashes within a bounding box at the given resolution.
Source code in vgrid/generator/geohashgrid.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | def geohash_grid_within_bbox(resolution, bbox):
"""Generate GeoJSON for geohashes within a bounding box at the given resolution."""
resolution = validate_geohash_resolution(resolution)
geohash_records = []
bbox_polygon = Polygon.from_bounds(*bbox)
intersected_geohashes = {
gh
for gh in INITIAL_GEOHASHES
if geohash2geo(gh).intersects(bbox_polygon)
}
geohashes_bbox = set()
for gh in intersected_geohashes:
expand_geohash_bbox(gh, resolution, geohashes_bbox, bbox_polygon)
for gh in tqdm(geohashes_bbox, desc="Generating Geohash DGGS", unit=" cells"):
geohash_record = graticule_dggs_to_geoseries("geohash", gh, resolution, geohash2geo(gh))
geohash_records.append(geohash_record)
return gpd.GeoDataFrame(geohash_records, geometry="geometry", crs="EPSG:4326")
|
isea3h_grid(resolution)
Generate DGGS cells and convert them to GeoJSON features.
Source code in vgrid/generator/isea3hgrid.py
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 | def isea3h_grid(resolution):
"""
Generate DGGS cells and convert them to GeoJSON features.
"""
resolution = validate_isea3h_resolution(resolution)
children = get_isea3h_children_cells(ISEA3H_BASE_CELLS, resolution)
records = []
for child in tqdm(children, desc="Generating ISEA3H DGGS", unit=" cells"):
try:
isea3h_cell = DggsCell(child)
cell_polygon = isea3h_cell_to_polygon(isea3h_cell)
isea3h_id = isea3h_cell.get_cell_id()
num_edges = 6 if resolution > 0 else 3
record = geodesic_dggs_to_geoseries(
"isea3h", isea3h_id, resolution, cell_polygon, num_edges
)
records.append(record)
except Exception as e:
print(f"Error generating ISEA3H DGGS cell {child}: {e}")
continue
return gpd.GeoDataFrame(records, geometry="geometry", crs="EPSG:4326")
|
olc_grid_within_bbox(resolution, bbox)
Generate a grid of Open Location Codes (Plus Codes) within the specified bounding box.
Source code in vgrid/generator/olcgrid.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110 | def olc_grid_within_bbox(resolution, bbox):
"""
Generate a grid of Open Location Codes (Plus Codes) within the specified bounding box.
"""
resolution = validate_olc_resolution(resolution)
min_lon, min_lat, max_lon, max_lat = bbox
bbox_poly = box(min_lon, min_lat, max_lon, max_lat)
# Step 1: Generate base cells at the lowest resolution (e.g., resolution 2)
base_resolution = 2
base_gdf = olc_grid(base_resolution, verbose=False)
# Step 2: Identify seed cells that intersect with the bounding box
seed_cells = []
for idx, base_cell in base_gdf.iterrows():
base_cell_poly = base_cell["geometry"]
if bbox_poly.intersects(base_cell_poly):
seed_cells.append(base_cell)
refined_records = []
# Step 3: Iterate over seed cells and refine to the output resolution
for seed_cell in seed_cells:
seed_cell_poly = seed_cell["geometry"]
if seed_cell_poly.contains(bbox_poly) and resolution == base_resolution:
# Append the seed cell directly if fully contained and resolution matches
refined_records.append(seed_cell)
else:
# Refine the seed cell to the output resolution and add it to the output
refined_records.extend(
olc_refine_cell(
seed_cell_poly.bounds, base_resolution, resolution, bbox_poly
)
)
gdf = gpd.GeoDataFrame(refined_records, geometry="geometry", crs="EPSG:4326")
gdf = gdf[gdf["resolution"] == resolution]
gdf = gdf.drop_duplicates(subset=["olc"])
return gdf
|
s2_grid(resolution, bbox)
Generate an S2 DGGS grid for a given resolution and bounding box.
Source code in vgrid/generator/s2grid.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 | def s2_grid(resolution, bbox):
"""
Generate an S2 DGGS grid for a given resolution and bounding box.
"""
resolution = validate_s2_resolution(resolution)
min_lng, min_lat, max_lng, max_lat = bbox
# Define the cell level (S2 uses a level system for zoom, where level 30 is the highest resolution)
level = resolution
# Create a list to store the S2 cell IDs
cell_ids = []
# Define the cell covering
coverer = s2.RegionCoverer()
coverer.min_level = level
coverer.max_level = level
# coverer.max_cells = 1000_000 # Adjust as needed
# coverer.max_cells = 0 # Adjust as needed
# Define the region to cover (in this example, we'll use the entire world)
region = s2.LatLngRect(
s2.LatLng.from_degrees(min_lat, min_lng),
s2.LatLng.from_degrees(max_lat, max_lng),
)
# Get the covering cells
covering = coverer.get_covering(region)
# Convert the covering cells to S2 cell IDs
for cell_id in covering:
cell_ids.append(cell_id)
s2_rows = []
num_edges = 4
for cell_id in tqdm(cell_ids, desc="Generating S2 DGGS", unit=" cells"):
# Generate a Shapely Polygon
cell_polygon = s2_cell_to_polygon(cell_id)
s2_token = cell_id.to_token()
row = geodesic_dggs_to_geoseries(
"s2", s2_token, resolution, cell_polygon, num_edges
)
s2_rows.append(row)
return gpd.GeoDataFrame(s2_rows, geometry="geometry", crs="EPSG:4326")
|
s2_grid_resample(resolution, geojson_features)
Generate an S2 DGGS grid for a given resolution and GeoJSON features.
Source code in vgrid/generator/s2grid.py
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 | def s2_grid_resample(resolution, geojson_features):
"""
Generate an S2 DGGS grid for a given resolution and GeoJSON features.
"""
resolution = validate_s2_resolution(resolution)
geometries = [
shape(feature["geometry"]) for feature in geojson_features["features"]
]
unified_geom = unary_union(geometries)
# Step 2: Get bounding box from unified geometry
min_lng, min_lat, max_lng, max_lat = unified_geom.bounds
# Step 3: Configure the S2 coverer
level = resolution
coverer = s2.RegionCoverer()
coverer.min_level = level
coverer.max_level = level
# Step 4: Create a LatLngRect from the bounding box
region = s2.LatLngRect(
s2.LatLng.from_degrees(min_lat, min_lng),
s2.LatLng.from_degrees(max_lat, max_lng),
)
# Step 5: Get the covering cells
covering = coverer.get_covering(region)
s2_rows = []
for cell_id in tqdm(covering, desc="Generating S2 DGGS", unit=" cells"):
# Convert S2 cell to polygon (must define `s2_cell_to_polygon`)
cell_polygon = s2_cell_to_polygon(cell_id)
# Check intersection with actual geometry
if cell_polygon.intersects(unified_geom):
s2_token = cell_id.to_token()
num_edges = 4
row = geodesic_dggs_to_geoseries(
"s2", s2_token, resolution, cell_polygon, num_edges
)
s2_rows.append(row)
return gpd.GeoDataFrame(s2_rows, geometry="geometry", crs="EPSG:4326")
|