Skip to content

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

Output Formats

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

1
2
3
4
5
6
7
8
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

1
2
3
4
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")