Skip to content

Vector to DGGS

Vector to DGGS conversion functions.

This submodule provides functions to convert vector geometries to various discrete global grid systems (DGGS).

Vector to H3 Module

This module provides functionality to convert vector geometries to H3 grid cells with flexible input and output formats.

Key Functions

geodataframe2h3(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a GeoDataFrame to H3 grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

H3 resolution [0..15]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable H3 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint H3 cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with H3 grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2h3(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2h3.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
def geodataframe2h3(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a GeoDataFrame to H3 grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): H3 resolution [0..15]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable H3 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint H3 cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with H3 grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2h3(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where H3 cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint H3 cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    avg_edge_length = h3.average_hexagon_edge_length(res, unit="m")
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_h3_resolution(resolution)

    h3_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            h3_rows.extend(
                point2h3(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            h3_rows.extend(
                polyline2h3(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            h3_rows.extend(
                polygon2h3(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
    return gpd.GeoDataFrame(h3_rows, geometry="geometry", crs="EPSG:4326")

point2h3(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a point geometry to H3 grid cells.

Converts point or multipoint geometries to H3 grid cells at the specified resolution. Each point is assigned to its containing H3 cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to H3 cells. resolution : int H3 resolution level [0..15]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable H3 compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2h3). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing H3 cells containing the point(s). Each dictionary contains H3 cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2h3(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2h3(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2h3.py
 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
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
def point2h3(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a point geometry to H3 grid cells.

    Converts point or multipoint geometries to H3 grid cells at the specified resolution.
    Each point is assigned to its containing H3 cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to H3 cells.
    resolution : int
        H3 resolution level [0..15].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable H3 compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2h3).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing H3 cells containing the point(s).
        Each dictionary contains H3 cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2h3(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2h3(points, 8)
    >>> len(cells)
    2
    """

    h3_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []
    for point in points:
        h3_id = h3.latlng_to_cell(point.y, point.x, resolution)
        cell_polygon = h32geo(h3_id, fix_antimeridian=fix_antimeridian)
        cell_resolution = h3.get_resolution(h3_id)
        num_edges = 6
        if h3.is_pentagon(h3_id):
            num_edges = 5
        row = geodesic_dggs_to_geoseries(
            "h3", h3_id, cell_resolution, cell_polygon, num_edges
        )

        # Add properties if requested
        if include_properties and feature_properties:
            row.update(feature_properties)

        h3_rows.append(row)
    return h3_rows

polygon2h3(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polygon geometry to H3 grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

H3 resolution [0..15]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable H3 compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2h3)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing H3 cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2h3(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2h3.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
def polygon2h3(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polygon geometry to H3 grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): H3 resolution [0..15]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable H3 compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2h3)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing H3 cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2h3(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """

    h3_rows = []
    if feature.geom_type == "Polygon":
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        bbox = box(*polygon.bounds)
        distance = h3.average_hexagon_edge_length(resolution, unit="m") * 2
        bbox_buffer = geodesic_buffer(bbox, distance)
        bbox_buffer_cells = h3.geo_to_cells(bbox_buffer, resolution)

        # First collect cells that pass the predicate check
        filtered_cells = []
        for bbox_buffer_cell in bbox_buffer_cells:
            cell_polygon = h32geo(bbox_buffer_cell, fix_antimeridian=fix_antimeridian)
            # Use the check_predicate function to determine if we should keep this cell
            if not check_predicate(cell_polygon, polygon, predicate):
                continue  # Skip non-matching cells
            filtered_cells.append(bbox_buffer_cell)

        # Apply compact after predicate check
        if compact:
            filtered_cells = h3.compact_cells(filtered_cells)

        # Convert filtered/compacted cells to rows
        for cell_id in filtered_cells:
            cell_polygon = h32geo(cell_id, fix_antimeridian=fix_antimeridian)
            cell_resolution = h3.get_resolution(cell_id)
            num_edges = 6
            if h3.is_pentagon(cell_id):
                num_edges = 5
            row = geodesic_dggs_to_geoseries(
                "h3", cell_id, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            h3_rows.append(row)

    return h3_rows

polyline2h3(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polyline geometry to H3 grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

H3 resolution [0..15]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable H3 compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2h3)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing H3 cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2h3(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2h3.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def polyline2h3(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polyline geometry to H3 grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): H3 resolution [0..15]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable H3 compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2h3)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing H3 cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2h3(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """

    h3_rows = []
    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []
    for polyline in polylines:
        bbox = box(*polyline.bounds)
        distance = h3.average_hexagon_edge_length(resolution, unit="m") * 2
        bbox_buffer = geodesic_buffer(bbox, distance)
        bbox_buffer_cells = h3.geo_to_cells(bbox_buffer, resolution)

        for bbox_buffer_cell in bbox_buffer_cells:
            cell_polygon = h32geo(bbox_buffer_cell, fix_antimeridian=fix_antimeridian)

            # Use the check_predicate function to determine if we should keep this cell
            if not check_predicate(cell_polygon, polyline, "intersects"):
                continue  # Skip non-matching cells

            cell_resolution = h3.get_resolution(bbox_buffer_cell)
            num_edges = 6
            if h3.is_pentagon(bbox_buffer_cell):
                num_edges = 5
            row = geodesic_dggs_to_geoseries(
                "h3", bbox_buffer_cell, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            h3_rows.append(row)

    return h3_rows

vector2h3(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, fix_antimeridian=None, **kwargs)

Convert vector data to H3 grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

H3 resolution [0..15]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable H3 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint H3 cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2h3("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2h3.py
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def vector2h3(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    fix_antimeridian=None,
    **kwargs,
):
    """
    Convert vector data to H3 grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): H3 resolution [0..15]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable H3 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint H3 cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2h3("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_h3_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2h3(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        fix_antimeridian=fix_antimeridian,
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2h3"
        else:
            output_name = "h3"
    return convert_to_output_format(result, output_format, output_name)

vector2h3_cli()

Command-line interface for vector2h3 conversion.

This function provides a command-line interface for converting vector data to H3 grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2h3.py -i input.geojson -r 10 -f geojson python vector2h3.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2h3.py
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
def vector2h3_cli():
    """
    Command-line interface for vector2h3 conversion.

    This function provides a command-line interface for converting vector data to H3 grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2h3.py -i input.geojson -r 10 -f geojson
        python vector2h3.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(description="Convert vector data to H3 grid cells")
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"H3 resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable H3 compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none",
    )
    args = parser.parse_args()
    fix_antimeridian = args.fix_antimeridian
    try:
        result = vector2h3(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
            fix_antimeridian=fix_antimeridian,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to S2 Module

This module provides functionality to convert vector geometries to S2 grid cells with flexible input and output formats.

Key Functions

geodataframe2s2(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a GeoDataFrame to S2 grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

S2 resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable S2 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint S2 cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with S2 grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2s2(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2s2.py
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def geodataframe2s2(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a GeoDataFrame to S2 grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): S2 resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable S2 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint S2 cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with S2 grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2s2(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where S2 cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint S2 cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_length, _, _ = s2_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * math.sqrt(2) * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_s2_resolution(resolution)

    s2_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            s2_rows.extend(
                point2s2(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            s2_rows.extend(
                polyline2s2(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            s2_rows.extend(
                polygon2s2(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
    return gpd.GeoDataFrame(s2_rows, geometry="geometry", crs="EPSG:4326")

point2s2(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a point geometry to S2 grid cells.

Converts point or multipoint geometries to S2 grid cells at the specified resolution. Each point is assigned to its containing S2 cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to S2 cells. resolution : int S2 resolution level [0..30]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable S2 compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2s2). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing S2 cells containing the point(s). Each dictionary contains S2 cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2s2(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2s2(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2s2.py
 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
 68
 69
 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
111
112
113
114
115
116
def point2s2(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a point geometry to S2 grid cells.

    Converts point or multipoint geometries to S2 grid cells at the specified resolution.
    Each point is assigned to its containing S2 cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to S2 cells.
    resolution : int
        S2 resolution level [0..30].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable S2 compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2s2).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing S2 cells containing the point(s).
        Each dictionary contains S2 cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2s2(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2s2(points, 8)
    >>> len(cells)
    2
    """
    s2_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        lat_lng = s2.LatLng.from_degrees(point.y, point.x)
        cell_id_max_res = s2.CellId.from_lat_lng(lat_lng)
        cell_id = cell_id_max_res.parent(resolution)
        s2_cell = s2.Cell(cell_id)
        cell_token = s2.CellId.to_token(s2_cell.id())
        if s2_cell:
            cell_polygon = s22geo(cell_token, fix_antimeridian=fix_antimeridian)
            cell_resolution = cell_id.level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", cell_token, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            s2_rows.append(row)
    return s2_rows

polygon2s2(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, all_polygons=None, fix_antimeridian=None)

Convert a polygon geometry to S2 grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

S2 resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable S2 compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2s2)

False
include_properties bool

Whether to include properties in output

True
all_polygons list

List of all polygons for topology preservation (not used in this function)

None

Returns:

Name Type Description
list

List of dictionaries representing S2 cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2s2(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2s2.py
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
def polygon2s2(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    all_polygons=None,  # New parameter for topology preservation
    fix_antimeridian=None,
):
    """
    Convert a polygon geometry to S2 grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): S2 resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable S2 compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2s2)
        include_properties (bool, optional): Whether to include properties in output
        all_polygons (list, optional): List of all polygons for topology preservation (not used in this function)

    Returns:
        list: List of dictionaries representing S2 cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2s2(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    s2_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        polygon_rows = []
        min_lng, min_lat, max_lng, max_lat = polygon.bounds
        level = resolution
        coverer = s2.RegionCoverer()
        coverer.min_level = level
        coverer.max_level = level
        region = s2.LatLngRect(
            s2.LatLng.from_degrees(min_lat, min_lng),
            s2.LatLng.from_degrees(max_lat, max_lng),
        )
        covering = coverer.get_covering(region)
        cell_ids = covering
        for cell_id in cell_ids:
            cell_polygon = s22geo(cell_id.to_token(), fix_antimeridian=fix_antimeridian)
            if not check_predicate(cell_polygon, polygon, predicate):
                continue
            cell_token = s2.CellId.to_token(cell_id)
            cell_resolution = cell_id.level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", cell_token, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            polygon_rows.append(row)

        if compact and polygon_rows:
            try:
                polygon_cell_ids = [
                    s2.CellId.from_token(row.get("s2"))
                    for row in polygon_rows
                    if row.get("s2")
                ]
            except Exception:
                polygon_cell_ids = []

            if polygon_cell_ids:
                covering = s2.CellUnion(polygon_cell_ids)
                covering.normalize()
                compact_cell_ids = covering.cell_ids()
                compact_rows = []
                for compact_cell in compact_cell_ids:
                    cell_polygon = s22geo(
                        compact_cell.to_token(), fix_antimeridian=fix_antimeridian
                    )
                    cell_token = s2.CellId.to_token(compact_cell)
                    cell_resolution = compact_cell.level()
                    num_edges = 4
                    row = geodesic_dggs_to_geoseries(
                        "s2", cell_token, cell_resolution, cell_polygon, num_edges
                    )
                    if include_properties and feature_properties:
                        row.update(feature_properties)
                    compact_rows.append(row)
                polygon_rows = compact_rows

        s2_rows.extend(polygon_rows)

    return s2_rows

polyline2s2(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, all_polylines=None, fix_antimeridian=None)

Convert a polyline geometry to S2 grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

S2 resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable S2 compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2s2)

False
include_properties bool

Whether to include properties in output

True
all_polylines list

List of all polylines for topology preservation (not used in this function)

None

Returns:

Name Type Description
list

List of dictionaries representing S2 cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2s2(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2s2.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
def polyline2s2(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    all_polylines=None,  # New parameter for topology preservation
    fix_antimeridian=None,
):
    """
    Convert a polyline geometry to S2 grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): S2 resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable S2 compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2s2)
        include_properties (bool, optional): Whether to include properties in output
        all_polylines (list, optional): List of all polylines for topology preservation (not used in this function)

    Returns:
        list: List of dictionaries representing S2 cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2s2(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    s2_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        min_lng, min_lat, max_lng, max_lat = polyline.bounds
        level = resolution
        coverer = s2.RegionCoverer()
        coverer.min_level = level
        coverer.max_level = level
        region = s2.LatLngRect(
            s2.LatLng.from_degrees(min_lat, min_lng),
            s2.LatLng.from_degrees(max_lat, max_lng),
        )
        covering = coverer.get_covering(region)
        cell_ids = covering
        for cell_id in cell_ids:
            cell_polygon = s22geo(cell_id.to_token(), fix_antimeridian=fix_antimeridian)
            if not cell_polygon.intersects(polyline):
                continue
            cell_token = s2.CellId.to_token(cell_id)
            cell_resolution = cell_id.level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", cell_token, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            s2_rows.append(row)
    return s2_rows

vector2s2(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, fix_antimeridian=None, **kwargs)

Convert vector data to S2 grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

S2 resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable S2 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint S2 cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2s2("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2s2.py
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
def vector2s2(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    fix_antimeridian=None,
    **kwargs,
):
    """
    Convert vector data to S2 grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): S2 resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable S2 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint S2 cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2s2("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_s2_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2s2(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        fix_antimeridian,
    )

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2s2"
        else:
            output_name = "s2"
    return convert_to_output_format(result, output_format, output_name)

vector2s2_cli()

Command-line interface for vector2s2 conversion.

This function provides a command-line interface for converting vector data to S2 grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2s2.py -i input.geojson -r 10 -f geojson python vector2s2.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2s2.py
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
def vector2s2_cli():
    """
    Command-line interface for vector2s2 conversion.

    This function provides a command-line interface for converting vector data to S2 grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2s2.py -i input.geojson -r 10 -f geojson
        python vector2s2.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(description="Convert vector data to S2 grid cells")
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"S2 resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable S2 compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none",
    )
    args = parser.parse_args()

    try:
        result = vector2s2(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
            fix_antimeridian=args.fix_antimeridian,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to A5 Module

This module provides functionality to convert vector geometries to A5 grid cells with flexible input and output formats.

Key Functions

geodataframe2a5(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, options=None, split_antimeridian=False)

Convert a GeoDataFrame to A5 grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

A5 resolution level [0..28]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable A5 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint A5 cells

False
include_properties bool

Whether to include properties in output

True
options dict

Options for a52geo.

None
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2a5(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2a5.py
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
def geodataframe2a5(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    options=None,
    split_antimeridian=False,
):
    """
    Convert a GeoDataFrame to A5 grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): A5 resolution level [0..28]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable A5 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint A5 cells
        include_properties (bool, optional): Whether to include properties in output
        options (dict, optional): Options for a52geo.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with A5 grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2a5(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where A5 cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint A5 cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_length, _, _ = a5_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * 1.4
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_a5_resolution(resolution)

    a5_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            a5_rows.extend(
                point2a5(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    options=options,
                    split_antimeridian=split_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            a5_rows.extend(
                polyline2a5(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    options=options,
                    split_antimeridian=split_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            a5_rows.extend(
                polygon2a5(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    options=options,
                    split_antimeridian=split_antimeridian,
                )
            )
    return gpd.GeoDataFrame(a5_rows, geometry="geometry", crs="EPSG:4326")

point2a5(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, options=None, split_antimeridian=False)

Convert a point geometry to A5 grid cells.

Converts point or multipoint geometries to A5 grid cells at the specified resolution. Each point is assigned to its containing A5 cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to A5 cells. resolution : int A5 resolution level [0..28]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable A5 compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2a5). include_properties : bool, optional Whether to include properties in output. options : dict, optional Options for a52geo. split_antimeridian : bool, optional When True, apply antimeridian fixing to the resulting polygons. Defaults to False when None or omitted. Returns


list of dict List of dictionaries representing A5 cells containing the point(s). Each dictionary contains A5 cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2a5(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2a5(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2a5.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def point2a5(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    options=None,
    split_antimeridian=False,
):
    """
    Convert a point geometry to A5 grid cells.

    Converts point or multipoint geometries to A5 grid cells at the specified resolution.
    Each point is assigned to its containing A5 cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to A5 cells.
    resolution : int
        A5 resolution level [0..28].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable A5 compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2a5).
    include_properties : bool, optional
        Whether to include properties in output.
    options : dict, optional
        Options for a52geo.
    split_antimeridian : bool, optional
        When True, apply antimeridian fixing to the resulting polygons.
        Defaults to False when None or omitted.
    Returns
    -------
    list of dict
        List of dictionaries representing A5 cells containing the point(s).
        Each dictionary contains A5 cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2a5(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2a5(points, 8)
    >>> len(cells)
    2
    """
    a5_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []
    for point in points:
        a5_hex = latlon2a5(point.y, point.x, resolution)
        cell_polygon = a52geo(a5_hex, options, split_antimeridian=split_antimeridian)
        cell_resolution = a5.get_resolution(a5.hex_to_u64(a5_hex))
        num_edges = 4
        row = geodesic_dggs_to_geoseries(
            "a5", a5_hex, cell_resolution, cell_polygon, num_edges
        )

        # Add properties if requested
        if include_properties and feature_properties:
            row.update(feature_properties)

        a5_rows.append(row)
    return a5_rows

polygon2a5(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, options=None, split_antimeridian=False)

Convert a polygon geometry to A5 grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

A5 resolution level [0..28]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable A5 compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2a5)

False
include_properties bool

Whether to include properties in output

True
options dict

Options for a52geo.

None
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2a5(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2a5.py
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def polygon2a5(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    options=None,
    split_antimeridian=False,
):
    """
    Convert a polygon geometry to A5 grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): A5 resolution level [0..28]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable A5 compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2a5)
        include_properties (bool, optional): Whether to include properties in output
        options (dict, optional): Options for a52geo.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
    Returns:
        list: List of dictionaries representing A5 cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2a5(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    a5_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        min_lng, min_lat, max_lng, max_lat = polygon.bounds

        # Calculate longitude and latitude width based on resolution
        if resolution == 0:
            lon_width = 35
            lat_width = 35
        elif resolution == 1:
            lon_width = 18
            lat_width = 18
        elif resolution == 2:
            lon_width = 10
            lat_width = 10
        elif resolution == 3:
            lon_width = 5
            lat_width = 5
        elif resolution > 3:
            base_width = 5  # at resolution 3
            factor = 0.5 ** (resolution - 3)
            lon_width = base_width * factor
            lat_width = base_width * factor

        # Generate longitude and latitude arrays
        longitudes = []
        latitudes = []

        lon = min_lng
        while lon < max_lng:
            longitudes.append(lon)
            lon += lon_width

        lat = min_lat
        while lat < max_lat:
            latitudes.append(lat)
            lat += lat_width

        seen_a5_hex = set()  # Track unique A5 hex codes

        # Generate and check each grid cell
        for lon in longitudes:
            for lat in latitudes:
                min_lon = lon
                min_lat = lat
                max_lon = lon + lon_width
                max_lat = lat + lat_width

                # Calculate centroid
                centroid_lat = (min_lat + max_lat) / 2
                centroid_lon = (min_lon + max_lon) / 2

                try:
                    # Convert centroid to A5 cell ID using direct A5 functions
                    a5_hex = latlon2a5(centroid_lat, centroid_lon, resolution)
                    cell_polygon = a52geo(
                        a5_hex, options, split_antimeridian=split_antimeridian
                    )

                    # Only process if this A5 hex code hasn't been seen before
                    if a5_hex not in seen_a5_hex:
                        seen_a5_hex.add(a5_hex)

                        # Check if cell satisfies the predicate with polygon
                        if check_predicate(cell_polygon, polygon, predicate):
                            cell_resolution = a5.get_resolution(a5.hex_to_u64(a5_hex))
                            num_edges = 4
                            row = geodesic_dggs_to_geoseries(
                                "a5", a5_hex, cell_resolution, cell_polygon, num_edges
                            )

                            # Add properties if requested
                            if include_properties and feature_properties:
                                row.update(feature_properties)

                            a5_rows.append(row)

                except Exception:
                    # Skip cells that can't be processed
                    continue

    # Apply compact mode if enabled
    if compact and a5_rows:
        # Create a GeoDataFrame from the current results
        temp_gdf = gpd.GeoDataFrame(a5_rows, geometry="geometry", crs="EPSG:4326")

        # Use a5compact function directly
        compacted_gdf = a5compact(temp_gdf, a5_hex="a5", output_format="gpd")

        if compacted_gdf is not None:
            # Convert back to list of dictionaries
            a5_rows = compacted_gdf.to_dict("records")
        # If compaction failed, keep original results

    return a5_rows

polyline2a5(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, options=None, split_antimeridian=False)

Convert a polyline geometry to A5 grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

A5 resolution level [0..28]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable A5 compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2a5)

False
include_properties bool

Whether to include properties in output

True
options dict

Options for a52geo.

None
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2a5(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2a5.py
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def polyline2a5(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    options=None,
    split_antimeridian=False,
):
    """
    Convert a polyline geometry to A5 grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): A5 resolution level [0..28]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable A5 compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2a5)
        include_properties (bool, optional): Whether to include properties in output
        options (dict, optional): Options for a52geo.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
    Returns:
        list: List of dictionaries representing A5 cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2a5(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """

    a5_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []
    for polyline in polylines:
        min_lng, min_lat, max_lng, max_lat = polyline.bounds

        # Calculate longitude and latitude width based on resolution
        if resolution == 0:
            # For resolution 0, use larger width
            lon_width = 35
            lat_width = 35
        elif resolution == 1:
            lon_width = 18
            lat_width = 18
        elif resolution == 2:
            lon_width = 10
            lat_width = 10
        elif resolution == 3:
            lon_width = 5
            lat_width = 5
        elif resolution > 3:
            base_width = 5  # at resolution 3
            factor = 0.5 ** (resolution - 3)
            lon_width = base_width * factor
            lat_width = base_width * factor

        # Generate longitude and latitude arrays
        longitudes = []
        latitudes = []

        lon = min_lng
        while lon < max_lng:
            longitudes.append(lon)
            lon += lon_width

        lat = min_lat
        while lat < max_lat:
            latitudes.append(lat)
            lat += lat_width

        seen_a5_hex = set()  # Track unique A5 hex codes

        # Generate and check each grid cell
        for lon in longitudes:
            for lat in latitudes:
                min_lon = lon
                min_lat = lat
                max_lon = lon + lon_width
                max_lat = lat + lat_width

                # Calculate centroid
                centroid_lat = (min_lat + max_lat) / 2
                centroid_lon = (min_lon + max_lon) / 2

                try:
                    # Convert centroid to A5 cell ID using direct A5 functions
                    a5_hex = latlon2a5(centroid_lat, centroid_lon, resolution)
                    cell_polygon = a52geo(
                        a5_hex, options, split_antimeridian=split_antimeridian
                    )

                    if cell_polygon is not None:
                        # Only process if this A5 hex code hasn't been seen before
                        if a5_hex not in seen_a5_hex:
                            seen_a5_hex.add(a5_hex)
                            # Check if cell intersects with polyline
                            if cell_polygon.intersects(polyline):
                                cell_resolution = a5.get_resolution(
                                    a5.hex_to_u64(a5_hex)
                                )
                                num_edges = 4
                                row = geodesic_dggs_to_geoseries(
                                    "a5",
                                    a5_hex,
                                    cell_resolution,
                                    cell_polygon,
                                    num_edges,
                                )

                                # Add properties if requested
                                if include_properties and feature_properties:
                                    row.update(feature_properties)

                                a5_rows.append(row)

                except Exception:
                    # Skip cells that can't be processed
                    continue

    return a5_rows

vector2a5(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, options=None, split_antimeridian=False, **kwargs)

Convert vector data to A5 grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

A5 resolution level [0..28]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable A5 compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint A5 cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
options dict

Options for a52geo.

None
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2a5("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2a5.py
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
def vector2a5(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    options=None,
    split_antimeridian=False,
    **kwargs,
):
    """
    Convert vector data to A5 grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): A5 resolution level [0..28]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable A5 compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint A5 cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        options (dict, optional): Options for a52geo.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2a5("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_a5_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2a5(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        options,
        split_antimeridian=split_antimeridian,
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2a5"
        else:
            output_name = "a5"
    return convert_to_output_format(result, output_format, output_name)

vector2a5_cli()

Command-line interface for vector2a5 conversion.

This function provides a command-line interface for converting vector data to A5 grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2a5.py -i input.geojson -r 10 -f geojson python vector2a5.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2a5.py
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
def vector2a5_cli():
    """
    Command-line interface for vector2a5 conversion.

    This function provides a command-line interface for converting vector data to A5 grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2a5.py -i input.geojson -r 10 -f geojson
        python vector2a5.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(description="Convert vector data to A5 grid cells")
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"A5 resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable A5 compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )

    args = parser.parse_args()

    try:
        result = vector2a5(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
            options=args.options,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to RHEALPix Module

This module provides functionality to convert vector geometries to RHEALPix grid cells with flexible input and output formats.

Key Functions

geodataframe2rhealpix(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a GeoDataFrame to rHEALPix grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

rHEALPix resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable rHEALPix compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint rHEALPix cells

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with rHEALPix grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2rhealpix(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def geodataframe2rhealpix(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a GeoDataFrame to rHEALPix grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): rHEALPix resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable rHEALPix compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint rHEALPix cells
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with rHEALPix grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2rhealpix(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where rHEALPix cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint rHEALPix cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_length, _, _ = rhealpix_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_rhealpix_resolution(resolution)

    rhealpix_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            rhealpix_rows.extend(
                point2rhealpix(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            rhealpix_rows.extend(
                polyline2rhealpix(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            rhealpix_rows.extend(
                polygon2rhealpix(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
    return gpd.GeoDataFrame(rhealpix_rows, geometry="geometry", crs="EPSG:4326")

point2rhealpix(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a point geometry to RHEALPix grid cells.

Converts point or multipoint geometries to RHEALPix grid cells at the specified resolution. Each point is assigned to its containing RHEALPix cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to RHEALPix cells. resolution : int RHEALPix resolution level [0..30]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable RHEALPix compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2rhealpix). include_properties : bool, optional Whether to include properties in output. fix_antimeridian : str, optional Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none Defaults to None when omitted.

Returns

list of dict List of dictionaries representing RHEALPix cells containing the point(s). Each dictionary contains RHEALPix cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2rhealpix(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2rhealpix(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
def point2rhealpix(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a point geometry to RHEALPix grid cells.

    Converts point or multipoint geometries to RHEALPix grid cells at the specified resolution.
    Each point is assigned to its containing RHEALPix cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to RHEALPix cells.
    resolution : int
        RHEALPix resolution level [0..30].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable RHEALPix compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2rhealpix).
    include_properties : bool, optional
        Whether to include properties in output.
    fix_antimeridian : str, optional
        Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns
    -------
    list of dict
        List of dictionaries representing RHEALPix cells containing the point(s).
        Each dictionary contains RHEALPix cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2rhealpix(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2rhealpix(points, 8)
    >>> len(cells)
    2
    """
    rhealpix_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        seed_cell = rhealpix_dggs.cell_from_point(
            resolution, (point.x, point.y), plane=False
        )

        seed_cell_id = str(seed_cell)
        seed_cell_polygon = rhealpix2geo(
            seed_cell_id, fix_antimeridian=fix_antimeridian
        )
        if seed_cell_polygon:
            num_edges = 4
            if seed_cell.ellipsoidal_shape() == "dart":
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "rhealpix", seed_cell_id, resolution, seed_cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            rhealpix_rows.append(row)
    return rhealpix_rows

polygon2rhealpix(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polygon geometry to rHEALPix grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

rHEALPix resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable rHEALPix compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2rhealpix)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing rHEALPix cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2rhealpix(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
def polygon2rhealpix(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polygon geometry to rHEALPix grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): rHEALPix resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable rHEALPix compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2rhealpix)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        list: List of dictionaries representing rHEALPix cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2rhealpix(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    rhealpix_rows = []
    polygons = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)

    for polygon in polygons:
        minx, miny, maxx, maxy = polygon.bounds
        bbox_polygon = box(minx, miny, maxx, maxy)
        bbox_center_lon = bbox_polygon.centroid.x
        bbox_center_lat = bbox_polygon.centroid.y
        seed_point = (bbox_center_lon, bbox_center_lat)
        seed_cell = rhealpix_dggs.cell_from_point(resolution, seed_point, plane=False)
        seed_cell_id = str(seed_cell)
        seed_cell_polygon = rhealpix2geo(
            seed_cell_id, fix_antimeridian=fix_antimeridian
        )
        if seed_cell_polygon.contains(bbox_polygon):
            num_edges = 4
            if seed_cell.ellipsoidal_shape() == "dart":
                num_edges = 3
            cell_resolution = resolution
            row = geodesic_dggs_to_geoseries(
                "rhealpix", seed_cell_id, cell_resolution, seed_cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            rhealpix_rows.append(row)
            return rhealpix_rows
        else:
            covered_cells = set()
            queue = deque([seed_cell])  # Use deque for BFS

            while queue:
                current_cell = queue.popleft()  # BFS: FIFO
                current_cell_id = str(current_cell)
                if current_cell_id in covered_cells:
                    continue
                covered_cells.add(current_cell_id)

                cell_polygon = rhealpix2geo(
                    current_cell_id, fix_antimeridian=fix_antimeridian
                )

                if not cell_polygon.intersects(bbox_polygon):
                    continue

                neighbors = current_cell.neighbors(plane=False)
                for _, neighbor in neighbors.items():
                    neighbor_id = str(neighbor)
                    if neighbor_id not in covered_cells:
                        queue.append(neighbor)

            for cell_id in covered_cells:
                cell_polygon = rhealpix2geo(cell_id, fix_antimeridian=fix_antimeridian)
                rhealpix_uids = (cell_id[0],) + tuple(map(int, cell_id[1:]))
                rhealpix_cell = rhealpix_dggs.cell(rhealpix_uids)
                cell_resolution = rhealpix_cell.resolution

                if not check_predicate(cell_polygon, polygon, predicate):
                    continue

                num_edges = 4
                if rhealpix_cell.ellipsoidal_shape() == "dart":
                    num_edges = 3
                row = geodesic_dggs_to_geoseries(
                    "rhealpix", cell_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                rhealpix_rows.append(row)

            # Compact mode: apply to rhealpix_rows after predicate check
            if compact:
                # Extract cell IDs from rhealpix_rows
                cells_to_process = [row.get("rhealpix") for row in rhealpix_rows]
                # Apply compact
                cells_to_process = rhealpix_compact(cells_to_process)
                # Rebuild rhealpix_rows with compacted cells
                rhealpix_rows = []
                for cell_id in cells_to_process:
                    cell_polygon = rhealpix2geo(
                        cell_id, fix_antimeridian=fix_antimeridian
                    )
                    rhealpix_uids = (cell_id[0],) + tuple(map(int, cell_id[1:]))
                    rhealpix_cell = rhealpix_dggs.cell(rhealpix_uids)
                    cell_resolution = rhealpix_cell.resolution

                    # No need to re-check predicate for parent cells from compact mode
                    # if not check_predicate(cell_polygon, polygon, predicate):
                    #     continue

                    num_edges = 4
                    if rhealpix_cell.ellipsoidal_shape() == "dart":
                        num_edges = 3
                    row = geodesic_dggs_to_geoseries(
                        "rhealpix", cell_id, cell_resolution, cell_polygon, num_edges
                    )
                    if include_properties and feature_properties:
                        row.update(feature_properties)
                    rhealpix_rows.append(row)

    return rhealpix_rows

polyline2rhealpix(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polyline geometry to rHEALPix grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

rHEALPix resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable rHEALPix compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2rhealpix)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing rHEALPix cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2rhealpix(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
def polyline2rhealpix(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polyline geometry to rHEALPix grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): rHEALPix resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable rHEALPix compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2rhealpix)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        list: List of dictionaries representing rHEALPix cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2rhealpix(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    rhealpix_rows = []
    polylines = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)

    for polyline in polylines:
        minx, miny, maxx, maxy = polyline.bounds
        bbox_polygon = box(minx, miny, maxx, maxy)
        bbox_center_lon = bbox_polygon.centroid.x
        bbox_center_lat = bbox_polygon.centroid.y
        seed_point = (bbox_center_lon, bbox_center_lat)
        seed_cell = rhealpix_dggs.cell_from_point(resolution, seed_point, plane=False)
        seed_cell_id = str(seed_cell)
        seed_cell_polygon = rhealpix2geo(
            seed_cell_id, fix_antimeridian=fix_antimeridian
        )
        if seed_cell_polygon.contains(bbox_polygon):
            num_edges = 4
            if seed_cell.ellipsoidal_shape() == "dart":
                num_edges = 3
            cell_resolution = resolution
            row = geodesic_dggs_to_geoseries(
                "rhealpix", seed_cell_id, cell_resolution, seed_cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            rhealpix_rows.append(row)
            return rhealpix_rows
        else:
            # Store intersecting cells with their polygons and cell objects
            intersecting_cells = {}  # {cell_id: (cell, polygon)}
            covered_cells = set()
            queue = deque([seed_cell])  # Use deque for BFS

            while queue:
                current_cell = queue.popleft()  # BFS: FIFO
                current_cell_id = str(current_cell)
                if current_cell_id in covered_cells:
                    continue
                covered_cells.add(current_cell_id)

                # Convert polygon once
                cell_polygon = rhealpix2geo(
                    current_cell_id, fix_antimeridian=fix_antimeridian
                )

                # Only process if intersects bbox
                if cell_polygon.intersects(bbox_polygon):
                    # Store for later processing
                    intersecting_cells[current_cell_id] = (current_cell, cell_polygon)

                    # Add neighbors to queue
                    neighbors = current_cell.neighbors(plane=False)
                    for _, neighbor in neighbors.items():
                        neighbor_id = str(neighbor)
                        if neighbor_id not in covered_cells:
                            queue.append(neighbor)

            # Process only intersecting cells (no double conversion)
            # Note: fix_antimeridian already applied when creating polygon in BFS loop
            for cell_id, (cell, cell_polygon) in intersecting_cells.items():
                # Check if cell intersects polyline (not just bbox)
                if not cell_polygon.intersects(polyline):
                    continue

                cell_resolution = cell.resolution
                num_edges = 4
                if (
                    cell.ellipsoidal_shape() == "dart"
                ):  # FIX: Use current cell, not seed
                    num_edges = 3
                row = geodesic_dggs_to_geoseries(
                    "rhealpix", cell_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                rhealpix_rows.append(row)

    return rhealpix_rows

vector2rhealpix(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, fix_antimeridian=None, **kwargs)

Convert vector data to rHEALPix grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

rHEALPix resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable rHEALPix compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint rHEALPix cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2rhealpix("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
def vector2rhealpix(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    fix_antimeridian=None,
    **kwargs,
):
    """
    Convert vector data to rHEALPix grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): rHEALPix resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable rHEALPix compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint rHEALPix cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2rhealpix("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_rhealpix_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2rhealpix(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        fix_antimeridian=fix_antimeridian,
    )

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2rhealpix"
        else:
            output_name = "rhealpix"
    return convert_to_output_format(result, output_format, output_name)

vector2rhealpix_cli()

Command-line interface for vector2rhealpix conversion.

This function provides a command-line interface for converting vector data to rHEALPix grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2rhealpix.py -i input.geojson -r 10 -f geojson python vector2rhealpix.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2rhealpix.py
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
def vector2rhealpix_cli():
    """
    Command-line interface for vector2rhealpix conversion.

    This function provides a command-line interface for converting vector data to rHEALPix grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2rhealpix.py -i input.geojson -r 10 -f geojson
        python vector2rhealpix.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to rHEALPix grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"rHEALPix resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable rHEALPix compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
    )
    parser.add_argument(
        "-fix",
        "--fix-antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        help="Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none",
    )
    args = parser.parse_args()

    try:
        result = vector2rhealpix(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
            fix_antimeridian=args.fix_antimeridian,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to ISEA4T Module

This module provides functionality to convert vector geometries to ISEA4T grid cells with flexible input and output formats.

Key Functions

geodataframe2isea4t(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a GeoDataFrame to ISEA4T grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

ISEA4T resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA4T compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint ISEA4T cells

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with ISEA4T grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2isea4t(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
def geodataframe2isea4t(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a GeoDataFrame to ISEA4T grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): ISEA4T resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA4T compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint ISEA4T cells
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with ISEA4T grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2isea4t(gdf, 10)
        >>> len(result) > 0
        True
    """

    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where ISEA4T cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint ISEA4T cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_length, _, _ = isea4t_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * 4
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_isea4t_resolution(resolution)

    isea4t_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            isea4t_rows.extend(
                point2isea4t(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            isea4t_rows.extend(
                polyline2isea4t(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            isea4t_rows.extend(
                polygon2isea4t(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

    if isea4t_rows:
        gdf = gpd.GeoDataFrame(isea4t_rows, geometry="geometry", crs="EPSG:4326")
    else:
        gdf = gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")
    return gdf

point2isea4t(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a point geometry to ISEA4T grid cells.

Converts point or multipoint geometries to ISEA4T grid cells at the specified resolution. Each point is assigned to its containing ISEA4T cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to ISEA4T cells. resolution : int ISEA4T resolution level [0..30]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable ISEA4T compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2isea4t). include_properties : bool, optional Whether to include properties in output. fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

Returns

list of dict List of dictionaries representing ISEA4T cells containing the point(s). Each dictionary contains ISEA4T cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2isea4t(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2isea4t(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def point2isea4t(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a point geometry to ISEA4T grid cells.

    Converts point or multipoint geometries to ISEA4T grid cells at the specified resolution.
    Each point is assigned to its containing ISEA4T cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to ISEA4T cells.
    resolution : int
        ISEA4T resolution level [0..30].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable ISEA4T compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2isea4t).
    include_properties : bool, optional
        Whether to include properties in output.
    fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

    Returns
    -------
    list of dict
        List of dictionaries representing ISEA4T cells containing the point(s).
        Each dictionary contains ISEA4T cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2isea4t(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2isea4t(points, 8)
    >>> len(cells)
    2
    """
    isea4t_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        accuracy = ISEA4T_RES_ACCURACY_DICT.get(resolution)
        lat_long_point = LatLongPoint(point.y, point.x, accuracy)
        isea4t_cell = isea4t_dggs.convert_point_to_dggs_cell(lat_long_point)
        isea4t_id = isea4t_cell.get_cell_id()
        cell_polygon = isea4t2geo(isea4t_id, fix_antimeridian=fix_antimeridian)
        num_edges = 3
        row = geodesic_dggs_to_geoseries(
            "isea4t", isea4t_id, resolution, cell_polygon, num_edges
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        isea4t_rows.append(row)
    return isea4t_rows

polygon2isea4t(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polygon geometry to ISEA4T grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

ISEA4T resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA4T compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2isea4t)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing ISEA4T cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2isea4t(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def polygon2isea4t(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polygon geometry to ISEA4T grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): ISEA4T resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA4T compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2isea4t)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

    Returns:
        list: List of dictionaries representing ISEA4T cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2isea4t(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    isea4t_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        accuracy = ISEA4T_RES_ACCURACY_DICT.get(resolution)
        bounding_box = box(*polygon.bounds)
        bounding_box_wkt = bounding_box.wkt
        shapes = isea4t_dggs.convert_shape_string_to_dggs_shapes(
            bounding_box_wkt, ShapeStringFormat.WKT, accuracy
        )
        shape = shapes[0]
        bbox_cells = shape.get_shape().get_outer_ring().get_cells()
        bounding_cell = isea4t_dggs.get_bounding_dggs_cell(bbox_cells)
        bounding_child_cells = get_isea4t_children_cells_within_bbox(
            bounding_cell.get_cell_id(), bounding_box, resolution
        )
        for child in bounding_child_cells:
            isea4t_id = child
            cell_polygon = isea4t2geo(isea4t_id, fix_antimeridian=fix_antimeridian)
            if check_predicate(cell_polygon, polygon, predicate):
                num_edges = 3
                cell_resolution = len(isea4t_id) - 2
                row = geodesic_dggs_to_geoseries(
                    "isea4t", isea4t_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea4t_rows.append(row)

        # Compact mode: apply to isea4t_rows after predicate check
        if compact:
            # Extract cell IDs from isea4t_rows
            cells_to_process = [row.get("isea4t") for row in isea4t_rows]
            # Apply compact
            cells_to_process = isea4t_compact(cells_to_process)
            # Rebuild isea4t_rows with compacted cells
            isea4t_rows = []
            for cell_id in cells_to_process:
                cell_polygon = isea4t2geo(cell_id, fix_antimeridian=fix_antimeridian)
                num_edges = 3
                cell_resolution = len(cell_id) - 2
                row = geodesic_dggs_to_geoseries(
                    "isea4t", cell_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea4t_rows.append(row)
    return isea4t_rows

polyline2isea4t(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polyline geometry to ISEA4T grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

ISEA4T resolution level [0..30]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable ISEA4T compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2isea4t)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing ISEA4T cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2isea4t(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def polyline2isea4t(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polyline geometry to ISEA4T grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): ISEA4T resolution level [0..30]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable ISEA4T compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2isea4t)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

    Returns:
        list: List of dictionaries representing ISEA4T cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2isea4t(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    isea4t_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        accuracy = ISEA4T_RES_ACCURACY_DICT.get(resolution)
        bounding_box = box(*polyline.bounds)
        bounding_box_wkt = bounding_box.wkt
        shapes = isea4t_dggs.convert_shape_string_to_dggs_shapes(
            bounding_box_wkt, ShapeStringFormat.WKT, accuracy
        )
        shape = shapes[0]
        bbox_cells = shape.get_shape().get_outer_ring().get_cells()
        bounding_cell = isea4t_dggs.get_bounding_dggs_cell(bbox_cells)
        bounding_child_cells = get_isea4t_children_cells_within_bbox(
            bounding_cell.get_cell_id(), bounding_box, resolution
        )
        for child in bounding_child_cells:
            isea4t_id = child
            cell_polygon = isea4t2geo(isea4t_id, fix_antimeridian=fix_antimeridian)
            if cell_polygon.intersects(polyline):
                num_edges = 3
                cell_resolution = len(isea4t_id) - 2
                row = geodesic_dggs_to_geoseries(
                    "isea4t", isea4t_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea4t_rows.append(row)
    return isea4t_rows

vector2isea4t(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, fix_antimeridian=None, **kwargs)

Convert vector data to ISEA4T grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

ISEA4T resolution level [0..30]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA4T compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint ISEA4T cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2isea4t("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
def vector2isea4t(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    fix_antimeridian=None,
    **kwargs,
):
    """
    Convert vector data to ISEA4T grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): ISEA4T resolution level [0..30]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA4T compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint ISEA4T cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2isea4t("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_isea4t_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2isea4t(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        fix_antimeridian,
    )

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2isea4t"
        else:
            output_name = "isea4t"
    return convert_to_output_format(result, output_format, output_name)

vector2isea4t_cli()

Command-line interface for vector2isea4t conversion.

This function provides a command-line interface for converting vector data to ISEA4T grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2isea4t.py -i input.geojson -r 10 -f geojson python vector2isea4t.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2isea4t.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
def vector2isea4t_cli():
    """
    Command-line interface for vector2isea4t conversion.

    This function provides a command-line interface for converting vector data to ISEA4T grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2isea4t.py -i input.geojson -r 10 -f geojson
        python vector2isea4t.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to ISEA4T grid cells."
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"ISEA4T resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable ISEA4T compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        help="Output format (default: gpd).",
        default="gpd",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none",
    )
    args = parser.parse_args()

    # Allow running on all platforms
    if platform.system() == "Windows":
        try:
            result = vector2isea4t(
                vector_data=args.input,
                resolution=args.resolution,
                predicate=args.predicate,
                compact=args.compact,
                topology=args.topology,
                output_format=args.output_format,
                include_properties=args.include_properties,
                fix_antimeridian=args.fix_antimeridian,
            )
            if args.output_format in STRUCTURED_FORMATS:
                print(result)
            # For file outputs, the utility prints the saved path
        except Exception as e:
            print(f"Error: {str(e)}", file=sys.stderr)
            sys.exit(1)
    else:
        print(
            "ISEA4T conversion is only supported on Windows systems.", file=sys.stderr
        )
        sys.exit(1)

Vector to ISEA3H Module

This module provides functionality to convert vector geometries to ISEA3H grid cells with flexible input and output formats.

Key Functions

geodataframe2isea3h(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a GeoDataFrame to ISEA3H grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

ISEA3H resolution level [0..32]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA3H compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint ISEA3H cells

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with ISEA3H grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2isea3h(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def geodataframe2isea3h(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a GeoDataFrame to ISEA3H grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): ISEA3H resolution level [0..32]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA3H compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint ISEA3H cells
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with ISEA3H grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2isea3h(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where ISEA3H cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint ISEA3H cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_length, _, _ = isea3h_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_isea3h_resolution(resolution)

    isea3h_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            isea3h_rows.extend(
                point2isea3h(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            isea3h_rows.extend(
                polyline2isea3h(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            isea3h_rows.extend(
                polygon2isea3h(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                    fix_antimeridian=fix_antimeridian,
                )
            )

    import geopandas as gpd

    if isea3h_rows:
        gdf = gpd.GeoDataFrame(isea3h_rows, geometry="geometry", crs="EPSG:4326")
    else:
        gdf = gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")
    return gdf

point2isea3h(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a point geometry to ISEA3H grid cells.

Converts point or multipoint geometries to ISEA3H grid cells at the specified resolution. Each point is assigned to its containing ISEA3H cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to ISEA3H cells. resolution : int ISEA3H resolution level [0..32]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable ISEA3H compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2isea3h). include_properties : bool, optional Whether to include properties in output. fix_antimeridian : str, optional Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none Defaults to None when omitted.

Returns

list of dict List of dictionaries representing ISEA3H cells containing the point(s). Each dictionary contains ISEA3H cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2isea3h(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2isea3h(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
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
def point2isea3h(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a point geometry to ISEA3H grid cells.

    Converts point or multipoint geometries to ISEA3H grid cells at the specified resolution.
    Each point is assigned to its containing ISEA3H cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to ISEA3H cells.
    resolution : int
        ISEA3H resolution level [0..32].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable ISEA3H compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2isea3h).
    include_properties : bool, optional
        Whether to include properties in output.
    fix_antimeridian : str, optional
        Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns
    -------
    list of dict
        List of dictionaries representing ISEA3H cells containing the point(s).
        Each dictionary contains ISEA3H cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2isea3h(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2isea3h(points, 8)
    >>> len(cells)
    2
    """
    isea3h_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        accuracy = ISEA3H_RES_ACCURACY_DICT.get(resolution)
        lat_long_point = LatLongPoint(point.y, point.x, accuracy)
        isea3h_cell = isea3h_dggs.convert_point_to_dggs_cell(lat_long_point)
        isea3h_id = isea3h_cell.get_cell_id()
        cell_polygon = isea3h2geo(isea3h_id, fix_antimeridian=fix_antimeridian)
        if cell_polygon:
            cell_resolution = resolution
            num_edges = 3 if cell_resolution == 0 else 6
            row = geodesic_dggs_to_geoseries(
                "isea3h", isea3h_id, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            isea3h_rows.append(row)
    return isea3h_rows

polygon2isea3h(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polygon geometry to ISEA3H grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

ISEA3H resolution level [0..32]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA3H compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2isea3h)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing ISEA3H cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2isea3h(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def polygon2isea3h(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polygon geometry to ISEA3H grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): ISEA3H resolution level [0..32]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA3H compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2isea3h)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        list: List of dictionaries representing ISEA3H cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2isea3h(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    isea3h_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        accuracy = ISEA3H_RES_ACCURACY_DICT.get(resolution)
        bounding_box = box(*polygon.bounds)
        bounding_box_wkt = bounding_box.wkt
        shapes = isea3h_dggs.convert_shape_string_to_dggs_shapes(
            bounding_box_wkt, ShapeStringFormat.WKT, accuracy
        )
        shape = shapes[0]
        bbox_cells = shape.get_shape().get_outer_ring().get_cells()
        bounding_cell = isea3h_dggs.get_bounding_dggs_cell(bbox_cells)
        bounding_child_cells = get_isea3h_children_cells_within_bbox(
            bounding_cell.get_cell_id(), bounding_box, resolution
        )
        for child in bounding_child_cells:
            isea3h_cell = DggsCell(child)
            isea3h_id = isea3h_cell.get_cell_id()
            cell_polygon = isea3h2geo(isea3h_id, fix_antimeridian=fix_antimeridian)
            if check_predicate(cell_polygon, polygon, predicate):
                isea3h2point = isea3h_dggs.convert_dggs_cell_to_point(isea3h_cell)
                cell_accuracy = isea3h2point._accuracy
                cell_resolution = ISEA3H_ACCURACY_RES_DICT.get(cell_accuracy)
                num_edges = 3 if cell_resolution == 0 else 6
                row = geodesic_dggs_to_geoseries(
                    "isea3h", isea3h_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea3h_rows.append(row)

        # Compact mode: apply to isea3h_rows after predicate check
        if compact:
            # Extract cell IDs from isea3h_rows
            cells_to_process = [row.get("isea3h") for row in isea3h_rows]
            # Apply compact
            cells_to_process = isea3h_compact(cells_to_process)
            # Rebuild isea3h_rows with compacted cells
            isea3h_rows = []
            for cell_id in cells_to_process:
                cell_polygon = isea3h2geo(cell_id, fix_antimeridian=fix_antimeridian)
                isea3h_cell = DggsCell(cell_id)
                isea3h2point = isea3h_dggs.convert_dggs_cell_to_point(isea3h_cell)
                cell_accuracy = isea3h2point._accuracy
                cell_resolution = ISEA3H_ACCURACY_RES_DICT.get(cell_accuracy)
                num_edges = 3 if cell_resolution == 0 else 6
                row = geodesic_dggs_to_geoseries(
                    "isea3h", cell_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea3h_rows.append(row)
    return isea3h_rows

polyline2isea3h(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, fix_antimeridian=None)

Convert a polyline geometry to ISEA3H grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

ISEA3H resolution level [0..32]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable ISEA3H compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2isea3h)

False
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None

Returns:

Name Type Description
list

List of dictionaries representing ISEA3H cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2isea3h(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
def polyline2isea3h(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert a polyline geometry to ISEA3H grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): ISEA3H resolution level [0..32]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable ISEA3H compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2isea3h)
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.

    Returns:
        list: List of dictionaries representing ISEA3H cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2isea3h(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    isea3h_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        accuracy = ISEA3H_RES_ACCURACY_DICT.get(resolution)
        bounding_box = box(*polyline.bounds)
        bounding_box_wkt = bounding_box.wkt
        shapes = isea3h_dggs.convert_shape_string_to_dggs_shapes(
            bounding_box_wkt, ShapeStringFormat.WKT, accuracy
        )
        shape = shapes[0]
        bbox_cells = shape.get_shape().get_outer_ring().get_cells()
        bounding_cell = isea3h_dggs.get_bounding_dggs_cell(bbox_cells)
        bounding_child_cells = get_isea3h_children_cells_within_bbox(
            bounding_cell.get_cell_id(), bounding_box, resolution
        )
        for child in bounding_child_cells:
            isea3h_cell = DggsCell(child)
            isea3h_id = isea3h_cell.get_cell_id()
            cell_polygon = isea3h2geo(isea3h_id, fix_antimeridian=fix_antimeridian)
            if cell_polygon.intersects(polyline):
                isea3h2point = isea3h_dggs.convert_dggs_cell_to_point(isea3h_cell)
                cell_accuracy = isea3h2point._accuracy
                cell_resolution = ISEA3H_ACCURACY_RES_DICT.get(cell_accuracy)
                num_edges = 3 if cell_resolution == 0 else 6
                row = geodesic_dggs_to_geoseries(
                    "isea3h", isea3h_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                isea3h_rows.append(row)
    return isea3h_rows

vector2isea3h(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, fix_antimeridian=None, **kwargs)

Convert vector data to ISEA3H grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

ISEA3H resolution level [0..32]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable ISEA3H compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint ISEA3H cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
fix_antimeridian str

Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none

None
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2isea3h("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
def vector2isea3h(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    fix_antimeridian=None,
    **kwargs,
):
    """
    Convert vector data to ISEA3H grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): ISEA3H resolution level [0..32]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable ISEA3H compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint ISEA3H cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        fix_antimeridian (str, optional): Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2isea3h("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_isea3h_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2isea3h(
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        fix_antimeridian=fix_antimeridian,
    )

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2isea3h"
        else:
            output_name = "isea3h"
    return convert_to_output_format(result, output_format, output_name)

vector2isea3h_cli()

Command-line interface for vector2isea3h conversion.

This function provides a command-line interface for converting vector data to ISEA3H grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2isea3h.py -i input.geojson -r 10 -f geojson python vector2isea3h.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2isea3h.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
def vector2isea3h_cli():
    """
    Command-line interface for vector2isea3h conversion.

    This function provides a command-line interface for converting vector data to ISEA3H grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2isea3h.py -i input.geojson -r 10 -f geojson
        python vector2isea3h.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to ISEA3H grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"ISEA3H resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable ISEA3H compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        help="Output format (default: gpd).",
        default="gpd",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none",
    )
    args = parser.parse_args()
    fix_antimeridian = args.fix_antimeridian
    # Allow running on all platforms
    if platform.system() == "Windows":
        try:
            result = vector2isea3h(
                vector_data=args.input,
                resolution=args.resolution,
                predicate=args.predicate,
                compact=args.compact,
                topology=args.topology,
                output_format=args.output_format,
                include_properties=args.include_properties,
                fix_antimeridian=fix_antimeridian,
            )
            if args.output_format in STRUCTURED_FORMATS:
                print(result)
            # For file outputs, the utility prints the saved path
        except Exception as e:
            print(f"Error: {str(e)}", file=sys.stderr)
            sys.exit(1)
    else:
        print("ISEA3H is only supported on Windows systems.", file=sys.stderr)
        sys.exit(1)

Vector to EASE Module

This module provides functionality to convert vector geometries to EASE grid cells with flexible input and output formats.

Key Functions

geodataframe2ease(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to EASE grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

EASE resolution level [0..6]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable EASE compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint EASE cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with EASE grid cells

Source code in vgrid/conversion/vector2dggs/vector2ease.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
def geodataframe2ease(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to EASE grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): EASE resolution level [0..6]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable EASE compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint EASE cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with EASE grid cells
    """

    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where EASE cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint EASE cells
            if shortest_distance > 0:
                for res in range(
                    min_res, max_res + 1
                ):  # EASE resolution range is [0..6]
                    if res in levels_specs:
                        cell_width = levels_specs[res]["x_length"]
                        # Use a factor to ensure sufficient separation (cell diagonal is ~1.4x cell width)
                        cell_diagonal = cell_width * 1.4
                        if cell_diagonal < shortest_distance:
                            estimated_resolution = res
                            break

        resolution = estimated_resolution

    resolution = validate_ease_resolution(resolution)

    ease_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            ease_rows.extend(
                point2ease(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            ease_rows.extend(
                polyline2ease(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            ease_rows.extend(
                polygon2ease(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(ease_rows, geometry="geometry", crs="EPSG:4326")

point2ease(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to EASE grid cells.

Converts point or multipoint geometries to EASE grid cells at the specified resolution. Each point is assigned to its containing EASE cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to EASE cells. resolution : int EASE resolution level [0..6]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable EASE compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2ease). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing EASE cells containing the point(s). Each dictionary contains EASE cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2ease(point, 4, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2ease(points, 3) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2ease.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
def point2ease(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to EASE grid cells.

    Converts point or multipoint geometries to EASE grid cells at the specified resolution.
    Each point is assigned to its containing EASE cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to EASE cells.
    resolution : int
        EASE resolution level [0..6].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable EASE compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2ease).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing EASE cells containing the point(s).
        Each dictionary contains EASE cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2ease(point, 4, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2ease(points, 3)
    >>> len(cells)
    2
    """
    ease_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []
    for point in points:
        ease_id = latlon2ease(point.y, point.x, resolution)
        cell_polygon = ease2geo(ease_id)
        num_edges = 4
        row = geodesic_dggs_to_geoseries(
            "ease", ease_id, int(ease_id[1]), cell_polygon, num_edges
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        ease_rows.append(row)
        return ease_rows

polygon2ease(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert polygon geometries (Polygon, MultiPolygon) to EASE grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

EASE resolution level [0..6]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable EASE compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2ease)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of GeoJSON feature dictionaries representing EASE cells based on predicate

Source code in vgrid/conversion/vector2dggs/vector2ease.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def polygon2ease(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert polygon geometries (Polygon, MultiPolygon) to EASE grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): EASE resolution level [0..6]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable EASE compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2ease)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of GeoJSON feature dictionaries representing EASE cells based on predicate
    """
    ease_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []
    for polygon in polygons:
        poly_bbox = box(*polygon.bounds)
        polygon_bbox_wkt = poly_bbox.wkt
        cells_bbox = geo_polygon_to_grid_ids(
            polygon_bbox_wkt,
            resolution,
            geo_crs,
            ease_crs,
            levels_specs,
            return_centroids=True,
            wkt_geom=True,
        )
        ease_ids = cells_bbox["result"]["data"]
        if not ease_ids:
            continue
        polygon_ease_rows = []
        for ease_id in ease_ids:
            cell_resolution = int(ease_id[1])
            # Use ease2geo to get the cell geometry
            cell_polygon = ease2geo(ease_id)
            if cell_polygon and check_predicate(cell_polygon, polygon, predicate):
                num_edges = 4
                row = geodesic_dggs_to_geoseries(
                    "ease", str(ease_id), cell_resolution, cell_polygon, num_edges
                )
                if feature_properties:
                    row.update(feature_properties)
                polygon_ease_rows.append(row)

        # Compact mode: apply to polygon_ease_rows after predicate check
        if compact:
            # Extract cell IDs from polygon_ease_rows
            cells_to_process = [row.get("ease") for row in polygon_ease_rows]
            # Apply compact
            cells_to_process = ease_compact(cells_to_process)
            # Rebuild polygon_ease_rows with compacted cells
            polygon_ease_rows = []
            for cell_id in cells_to_process:
                cell_polygon = ease2geo(cell_id)
                cell_resolution = get_ease_resolution(cell_id)

                # No need to re-check predicate for parent cells from compact mode
                # if not check_predicate(cell_polygon, polygon, predicate):
                #     continue

                num_edges = 4
                row = geodesic_dggs_to_geoseries(
                    "ease", cell_id, cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                polygon_ease_rows.append(row)

        ease_rows.extend(polygon_ease_rows)
    return ease_rows

polyline2ease(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert line geometries (LineString, MultiLineString) to EASE grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Line geometry to convert

required
resolution int

EASE resolution level [0..6]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for lines)

None
compact bool

Enable EASE compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2ease)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of GeoJSON feature dictionaries representing EASE cells intersecting the line

Source code in vgrid/conversion/vector2dggs/vector2ease.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def polyline2ease(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert line geometries (LineString, MultiLineString) to EASE grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Line geometry to convert
        resolution (int): EASE resolution level [0..6]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for lines)
        compact (bool, optional): Enable EASE compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2ease)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of GeoJSON feature dictionaries representing EASE cells intersecting the line
    """
    ease_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        poly_bbox = box(*polyline.bounds)
        polygon_bbox_wkt = poly_bbox.wkt
        cells_bbox = geo_polygon_to_grid_ids(
            polygon_bbox_wkt,
            resolution,
            geo_crs,
            ease_crs,
            levels_specs,
            return_centroids=True,
            wkt_geom=True,
        )
        ease_ids = cells_bbox["result"]["data"]
        if compact:
            ease_ids = ease_compact(ease_ids)
        for ease_id in ease_ids:
            cell_resolution = int(ease_id[1])
            # Use ease2geo to get the cell geometry
            cell_polygon = ease2geo(ease_id)
            if cell_polygon and cell_polygon.intersects(polyline):
                num_edges = 4
                row = geodesic_dggs_to_geoseries(
                    "ease", str(ease_id), cell_resolution, cell_polygon, num_edges
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                ease_rows.append(row)
    return ease_rows

vector2ease(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to EASE grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

EASE resolution level [0..6]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable EASE compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint EASE cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Source code in vgrid/conversion/vector2dggs/vector2ease.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
def vector2ease(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to EASE grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): EASE resolution level [0..6]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable EASE compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint EASE cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_ease_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2ease(
        gdf, resolution, predicate, compact, topology, include_properties
    )

    output_name = kwargs.get("output_name", None)
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2ease"
        else:
            output_name = "ease"
    return convert_to_output_format(result, output_format, output_name)

vector2ease_cli()

Command-line interface for vector2ease conversion.

Source code in vgrid/conversion/vector2dggs/vector2ease.py
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def vector2ease_cli():
    """
    Command-line interface for vector2ease conversion.
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to EASE grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"EASE resolution [{min_res}..{max_res}] (0=coarsest, {max_res}=finest)",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable EASE compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        help="Output format (default: gpd).",
        default="gpd",
    )
    args = parser.parse_args()
    args.resolution = validate_ease_resolution(args.resolution)
    output_name = None
    try:
        result = vector2ease(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            topology=args.topology,
            compact=args.compact,
            output_format=args.output_format,
            output_name=output_name,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to DGGAL Module

This module provides functionality to convert vector geometries to DGGAL grid cells with flexible input and output formats.

Key Functions

geodataframe2dggal(dggs_type, gdf, resolution, predicate=None, compact=False, topology=False, include_properties=True, split_antimeridian=False)

Convert a GeoDataFrame to DGGAL grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
dggs_type str

One of DGGAL_TYPES

required
resolution int

Integer resolution

required
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable DGGAL compact mode for polygons and lines

False
topology bool

Enable topology preserving mode (not yet implemented for DGGAL)

False
include_properties bool

Whether to include properties in output

True
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with DGGAL grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2dggal("isea3h", gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2dggal.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
def geodataframe2dggal(
    dggs_type: str,
    gdf,
    resolution: int,
    predicate: str | None = None,
    compact: bool = False,
    topology: bool = False,
    include_properties: bool = True,
    split_antimeridian: bool = False,
):
    """
    Convert a GeoDataFrame to DGGAL grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        dggs_type (str): One of DGGAL_TYPES
        resolution (int): Integer resolution
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable DGGAL compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode (not yet implemented for DGGAL)
        include_properties (bool, optional): Whether to include properties in output
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with DGGAL grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2dggal("isea3h", gdf, 10)
        >>> len(result) > 0
        True
    """
    # Build GeoDataFrames per geometry type and concatenate for performance
    dggal_rows = []
    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            dggal_rows.extend(
                point2dggal(
                    dggs_type,
                    geom,
                    resolution,
                    props,
                    predicate,
                    compact,
                    topology,
                    include_properties,
                    split_antimeridian=split_antimeridian,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            dggal_rows.extend(
                polyline2dggal(
                    dggs_type,
                    geom,
                    resolution,
                    props,
                    predicate,
                    compact,
                    include_properties,
                    split_antimeridian=split_antimeridian,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            dggal_rows.extend(
                polygon2dggal(
                    dggs_type,
                    geom,
                    resolution,
                    props,
                    predicate,
                    compact,
                    include_properties,
                    split_antimeridian=split_antimeridian,
                )
            )
    return gpd.GeoDataFrame(dggal_rows, geometry="geometry", crs="EPSG:4326")

point2dggal(dggs_type, feature=None, resolution=None, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True, split_antimeridian=False)

Convert a point geometry to DGGAL grid cells.

Converts point or multipoint geometries to DGGAL grid cells at the specified resolution. Each point is assigned to its containing DGGAL cell.

Parameters

dggs_type : str DGGAL DGGS type (e.g., "isea3h", "isea4t", "isea7h", "isea9h"). feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to DGGAL cells. resolution : int DGGAL resolution level. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable DGGAL compact mode (not used for points). topology : bool, optional Enable topology preserving mode. include_properties : bool, optional Whether to include properties in output. split_antimeridian : bool, optional When True, apply antimeridian fixing to the resulting polygons. Defaults to False when None or omitted.

Returns

list of dict List of dictionaries representing DGGAL cells containing the point(s). Each dictionary contains DGGAL cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2dggal("isea3h", point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2dggal("isea4t", points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2dggal.py
 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
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def point2dggal(
    dggs_type: str,
    feature=None,
    resolution=None,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    split_antimeridian=False,
):
    """
    Convert a point geometry to DGGAL grid cells.

    Converts point or multipoint geometries to DGGAL grid cells at the specified resolution.
    Each point is assigned to its containing DGGAL cell.

    Parameters
    ----------
    dggs_type : str
        DGGAL DGGS type (e.g., "isea3h", "isea4t", "isea7h", "isea9h").
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to DGGAL cells.
    resolution : int
        DGGAL resolution level.
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable DGGAL compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode.
    include_properties : bool, optional
        Whether to include properties in output.
    split_antimeridian : bool, optional
        When True, apply antimeridian fixing to the resulting polygons.
        Defaults to False when None or omitted.

    Returns
    -------
    list of dict
        List of dictionaries representing DGGAL cells containing the point(s).
        Each dictionary contains DGGAL cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2dggal("isea3h", point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2dggal("isea4t", points, 8)
    >>> len(cells)
    2
    """
    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = globals()[dggs_class_name]()

    dggal_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        zone_id = latlon2dggal(dggs_type, point.y, point.x, resolution)
        zone = dggrs.getZoneFromTextID(zone_id)
        cell_resolution = dggrs.getZoneLevel(zone)
        num_edges = dggrs.countZoneEdges(zone)
        cell_polygon = dggal2geo(
            dggs_type, zone_id, split_antimeridian=split_antimeridian
        )
        row = geodesic_dggs_to_geoseries(
            f"dggal_{dggs_type}", zone_id, cell_resolution, cell_polygon, num_edges
        )
        # Add properties if requested
        if include_properties and feature_properties:
            row.update(feature_properties)

        dggal_rows.append(row)

    return dggal_rows

vector2dggal(dggs_type, vector_data, resolution, predicate=None, compact=False, topology=False, include_properties=True, output_format='gpd', split_antimeridian=False, **kwargs)

Convert vector data to DGGAL grid cells for a given type and resolution.

Parameters:

Name Type Description Default
dggs_type str

One of DGGAL_TYPES

required
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

Integer resolution

required
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable DGGAL compact mode for polygons and lines

False
topology bool

Enable topology preserving mode (not yet implemented for DGGAL)

False
include_properties bool

Whether to include properties in output

True
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2dggal("isea3h", "data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2dggal.py
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
def vector2dggal(
    dggs_type: str,
    vector_data,
    resolution: int,
    predicate: str | None = None,
    compact: bool = False,
    topology: bool = False,
    include_properties: bool = True,
    output_format: str = "gpd",
    split_antimeridian: bool = False,
    **kwargs,
):
    """
    Convert vector data to DGGAL grid cells for a given type and resolution.

    Args:
        dggs_type (str): One of DGGAL_TYPES
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int): Integer resolution
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable DGGAL compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode (not yet implemented for DGGAL)
        include_properties (bool, optional): Whether to include properties in output
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2dggal("isea3h", "data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    gdf_input = process_input_data_vector(vector_data, **kwargs)
    resolution = validate_dggal_resolution(dggs_type, resolution)
    result = geodataframe2dggal(
        dggs_type,
        gdf_input,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        split_antimeridian=split_antimeridian,
    )

    # Return or export
    output_name = None
    if output_format in OUTPUT_FORMATS:
        # File outputs: prefer a stable name like <type>_grid_<res>
        output_name = f"{dggs_type}_grid_{resolution}"
    return convert_to_output_format(result, output_format, output_name)

Vector to DGGRID Module

This module provides functionality to convert vector geometries to DGGRID grid cells with flexible input and output formats.

Key Functions

geodataframe2dggrid(dggrid_instance, dggs_type, gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, output_address_type='SEQNUM', split_antimeridian=False, aggregate=False)

Convert a GeoDataFrame to DGGRID grid cells.

Parameters:

Name Type Description Default
dggrid_instance

DGGRIDv7 instance for grid operations

required
gdf GeoDataFrame

GeoDataFrame to convert

required
dggs_type str

One of DGGRID_TYPES

required
resolution int

Integer resolution

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable compact mode for polygons and lines

False
topology bool

Enable topology preserving mode

False
include_properties bool

Whether to include properties in output

True
output_address_type str

Output address type (SEQNUM, Q2DI, Q2DD, etc.)

'SEQNUM'
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with DGGRID grid cells

Source code in vgrid/conversion/vector2dggs/vector2dggrid.py
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def geodataframe2dggrid(
    dggrid_instance,
    dggs_type,
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    output_address_type="SEQNUM",
    split_antimeridian=False,
    aggregate=False,
):
    """
    Convert a GeoDataFrame to DGGRID grid cells.

    Args:
        dggrid_instance: DGGRIDv7 instance for grid operations
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        dggs_type (str): One of DGGRID_TYPES
        resolution (int): Integer resolution
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode
        include_properties (bool, optional): Whether to include properties in output
        output_address_type (str, optional): Output address type (SEQNUM, Q2DI, Q2DD, etc.)
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with DGGRID grid cells
    """
    # Build GeoDataFrames per geometry type and concatenate for performance
    dggrid_rows = []
    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            gdf_result = point2dggrid(
                dggrid_instance,
                dggs_type,
                geom,
                resolution,
                predicate,
                compact,
                topology,
                include_properties,
                props,
                output_address_type,
                split_antimeridian=split_antimeridian,
                aggregate=aggregate,
            )
            if not gdf_result.empty:
                dggrid_rows.append(gdf_result)

        elif geom.geom_type in ("LineString", "MultiLineString"):
            gdf_result = polyline2dggrid(
                dggrid_instance,
                dggs_type,
                geom,
                resolution,
                predicate,
                compact,
                topology,
                include_properties,
                props,
                output_address_type,
                split_antimeridian=split_antimeridian,
                aggregate=aggregate,
            )
            if not gdf_result.empty:
                dggrid_rows.append(gdf_result)

        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            gdf_result = polygon2dggrid(
                dggrid_instance,
                dggs_type,
                geom,
                resolution,
                predicate,
                compact,
                topology,
                include_properties,
                props,
                output_address_type,
                split_antimeridian=split_antimeridian,
                aggregate=aggregate,
            )
            if not gdf_result.empty:
                dggrid_rows.append(gdf_result)

    if dggrid_rows:
        final_grid = gpd.GeoDataFrame(
            pd.concat(dggrid_rows, ignore_index=True), crs=dggrid_rows[0].crs
        )
    else:
        final_grid = gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")

    return final_grid

point2dggrid(dggrid_instance, dggs_type, feature, resolution, predicate=None, compact=False, topology=False, include_properties=True, feature_properties=None, output_address_type='SEQNUM', split_antimeridian=False, aggregate=False)

Convert a point geometry to DGGRID grid cells.

Converts point or multipoint geometries to DGGRID grid cells at the specified resolution. Each point is assigned to its containing DGGRID cell.

Parameters

dggrid_instance : object DGGRID instance for grid operations. dggs_type : str DGGRID DGGS type (e.g., "isea4h", "fuller"). feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to DGGRID cells. resolution : int DGGRID resolution level. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable DGGRID compact mode (not used for points). topology : bool, optional Enable topology preserving mode. include_properties : bool, optional Whether to include properties in output. feature_properties : dict, optional Properties to include in output features. output_address_type : str, optional Output address type (e.g., "SEQNUM", "Q2DI", "Q2DD"). Defaults to "SEQNUM". split_antimeridian : bool, optional When True, apply antimeridian fixing to the resulting polygons. Defaults to False when None or omitted.

Returns

geopandas.GeoDataFrame GeoDataFrame containing DGGRID cells with the point(s).

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco gdf = point2dggrid(dggrid_instance, "isea4h", point, 10) len(gdf) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) gdf = point2dggrid(dggrid_instance, "fuller", points, 8) len(gdf) 2

Source code in vgrid/conversion/vector2dggs/vector2dggrid.py
 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
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def point2dggrid(
    dggrid_instance,
    dggs_type,
    feature,
    resolution,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    feature_properties=None,
    output_address_type="SEQNUM",
    split_antimeridian=False,
    aggregate=False,
):
    """
    Convert a point geometry to DGGRID grid cells.

    Converts point or multipoint geometries to DGGRID grid cells at the specified resolution.
    Each point is assigned to its containing DGGRID cell.

    Parameters
    ----------
    dggrid_instance : object
        DGGRID instance for grid operations.
    dggs_type : str
        DGGRID DGGS type (e.g., "isea4h", "fuller").
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to DGGRID cells.
    resolution : int
        DGGRID resolution level.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable DGGRID compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode.
    include_properties : bool, optional
        Whether to include properties in output.
    feature_properties : dict, optional
        Properties to include in output features.
    output_address_type : str, optional
        Output address type (e.g., "SEQNUM", "Q2DI", "Q2DD"). Defaults to "SEQNUM".
    split_antimeridian : bool, optional
        When True, apply antimeridian fixing to the resulting polygons.
        Defaults to False when None or omitted.

    Returns
    -------
    geopandas.GeoDataFrame
        GeoDataFrame containing DGGRID cells with the point(s).

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> gdf = point2dggrid(dggrid_instance, "isea4h", point, 10)
    >>> len(gdf)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> gdf = point2dggrid(dggrid_instance, "fuller", points, 8)
    >>> len(gdf)
    2
    """
    dggs_type = validate_dggrid_type(dggs_type)
    resolution = validate_dggrid_resolution(dggs_type, resolution)

    # Expect a single Point; MultiPoint handled by geometry2dggrid
    lat = float(feature.y)
    lon = float(feature.x)
    seqnum = latlon2dggrid(
        dggrid_instance, dggs_type, lat, lon, resolution, output_address_type
    )
    seqnums = [seqnum]

    # Build polygons from SEQNUM ids
    gdf = dggrid2geo(
        dggrid_instance,
        dggs_type,
        seqnums,
        resolution,
        output_address_type,
        split_antimeridian=split_antimeridian,
        aggregate=aggregate,
    )
    if include_properties and feature_properties:
        for key, value in feature_properties.items():
            gdf[key] = value
    return gdf

polygon2dggrid(dggrid_instance, dggs_type, feature, resolution, predicate=None, compact=False, topology=False, include_properties=True, feature_properties=None, output_address_type='SEQNUM', split_antimeridian=False, aggregate=False)

Generate DGGRID cells intersecting with a given polygon or multipolygon geometry.

Parameters:

Name Type Description Default
dggrid_instance

DGGRIDv7 instance for grid operations.

required
dggs_type str

Type of DGGS (e.g., ISEA4H, FULLER, etc.).

required
res int

Resolution for the DGGRID.

required
address_type str

Address type for the output grid cells.

required
geometry Polygon or MultiPolygon

Input geometry.

required
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
Source code in vgrid/conversion/vector2dggs/vector2dggrid.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def polygon2dggrid(
    dggrid_instance,
    dggs_type,
    feature,
    resolution,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    feature_properties=None,
    output_address_type="SEQNUM",
    split_antimeridian=False,
    aggregate=False,
):
    """
    Generate DGGRID cells intersecting with a given polygon or multipolygon geometry.

    Args:
        dggrid_instance: DGGRIDv7 instance for grid operations.
        dggs_type (str): Type of DGGS (e.g., ISEA4H, FULLER, etc.).
        res (int): Resolution for the DGGRID.
        address_type (str): Address type for the output grid cells.
        geometry (shapely.geometry.Polygon or MultiPolygon): Input geometry.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
    Returns:
        geopandas.GeoDataFrame: GeoDataFrame containing DGGRID cells intersecting with the input geometry.
    """
    # Initialize an empty list to store filtered grid cells
    merged_grids = []

    # Check the geometry type
    if feature.geom_type == "Polygon":
        # Handle single Polygon
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        # Handle MultiPolygon: process each polygon separately
        polygons = list(feature.geoms)  # Use .geoms to get components of MultiPolygon

    # Process each polygon
    for polygon in polygons:
        # Get bounding box for the current polygon
        bounding_box = box(*feature.bounds)

        # Generate grid cells for the bounding box
        dggrid_gdf = dggrid_instance.grid_cell_polygons_for_extent(
            dggs_type,
            resolution,
            clip_geom=bounding_box,
            split_dateline=split_antimeridian,
            output_address_type=output_address_type,
        )

        # Keep only grid cells that satisfy predicate (defaults to intersects)
        if predicate:
            dggrid_gdf = dggrid_gdf[
                dggrid_gdf.geometry.apply(
                    lambda cell: check_predicate(cell, feature, predicate)
                )
            ]
        else:
            dggrid_gdf = dggrid_gdf[dggrid_gdf.intersects(feature)]
        try:
            if output_address_type != "SEQNUM":

                def address_transform(
                    dggrid_seqnum, dggs_type, resolution, address_type
                ):
                    address_type_transform = dggrid_instance.address_transform(
                        [dggrid_seqnum],
                        dggs_type=dggs_type,
                        resolution=resolution,
                        mixed_aperture_level=None,
                        input_address_type="SEQNUM",
                        output_address_type=output_address_type,
                    )
                    return address_type_transform.loc[0, address_type]

                dggrid_gdf["name"] = dggrid_gdf["name"].astype(str)
                dggrid_gdf["name"] = dggrid_gdf["name"].apply(
                    lambda val: address_transform(
                        val, dggs_type, resolution, output_address_type
                    )
                )
                dggrid_gdf = dggrid_gdf.rename(
                    columns={"name": output_address_type.lower()}
                )
            else:
                dggrid_gdf = dggrid_gdf.rename(columns={"name": "seqnum"})

        except Exception:
            pass

        # Append the filtered GeoDataFrame to the list
        if include_properties and feature_properties and not dggrid_gdf.empty:
            for key, value in feature_properties.items():
                dggrid_gdf[key] = value
        merged_grids.append(dggrid_gdf)

    # Merge all filtered grids into one GeoDataFrame
    if merged_grids:
        final_grid = gpd.GeoDataFrame(
            pd.concat(merged_grids, ignore_index=True), crs=merged_grids[0].crs
        )
    else:
        final_grid = gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")
    if split_antimeridian:
        if aggregate:
            final_grid = final_grid.dissolve(by=f"dggrid_{dggs_type.lower()}")
    return final_grid

polyline2dggrid(dggrid_instance, dggs_type, feature, resolution, predicate=None, compact=False, topology=False, include_properties=True, feature_properties=None, output_address_type='SEQNUM ', split_antimeridian=False, aggregate=False)

Generate DGGRID cells intersecting with a LineString or MultiLineString geometry.

Parameters:

Name Type Description Default
dggrid_instance

DGGRIDv7 instance for grid operations.

required
dggs_type str

Type of DGGS (e.g., ISEA4H, FULLER, etc.).

required
res int

Resolution for the DGGRID.

required
address_type str

Address type for the output grid cells.

required
geometry LineString or MultiLineString

Input geometry.

required
split_antimeridian bool

When True, apply antimeridian fixing to the resulting polygons.

False
Source code in vgrid/conversion/vector2dggs/vector2dggrid.py
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def polyline2dggrid(
    dggrid_instance,
    dggs_type,
    feature,
    resolution,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    feature_properties=None,
    output_address_type="SEQNUM ",
    split_antimeridian=False,
    aggregate=False,
):
    """
    Generate DGGRID cells intersecting with a LineString or MultiLineString geometry.

    Args:
        dggrid_instance: DGGRIDv7 instance for grid operations.
        dggs_type (str): Type of DGGS (e.g., ISEA4H, FULLER, etc.).
        res (int): Resolution for the DGGRID.
        address_type (str): Address type for the output grid cells.
        geometry (shapely.geometry.LineString or MultiLineString): Input geometry.
        split_antimeridian (bool, optional): When True, apply antimeridian fixing to the resulting polygons.
    Returns:
        geopandas.GeoDataFrame: GeoDataFrame containing DGGRID cells intersecting with the input geometry.
    """
    # Initialize an empty list to store filtered grid cells
    merged_grids = []

    # Check the geometry type
    if feature.geom_type == "LineString":
        # Handle single LineString
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        # Handle MultiLineString: process each line separately
        polylines = list(feature.geoms)

    # Process each polyline
    for polyline in polylines:
        # Get bounding box for the current polyline
        bounding_box = box(*polyline.bounds)

        # Generate grid cells for the bounding box
        dggrid_gdf = dggrid_instance.grid_cell_polygons_for_extent(
            dggs_type,
            resolution,
            clip_geom=bounding_box,
            split_dateline=split_antimeridian,
            output_address_type=output_address_type,
        )

        # Keep only grid cells that match predicate (defaults to intersects)
        dggrid_gdf = dggrid_gdf[dggrid_gdf.intersects(polyline)]

        try:
            if output_address_type != "SEQNUM":

                def address_transform(
                    dggrid_seqnum, dggal_type, resolution, address_type
                ):
                    address_type_transform = dggrid_instance.address_transform(
                        [dggrid_seqnum],
                        dggs_type=dggs_type,
                        resolution=resolution,
                        mixed_aperture_level=None,
                        input_address_type="SEQNUM",
                        output_address_type=output_address_type,
                    )
                    return address_type_transform.loc[0, address_type]

                dggrid_gdf["name"] = dggrid_gdf["name"].astype(str)
                dggrid_gdf["name"] = dggrid_gdf["name"].apply(
                    lambda val: address_transform(
                        val, dggs_type, resolution, output_address_type
                    )
                )
                dggrid_gdf = dggrid_gdf.rename(
                    columns={"name": output_address_type.lower()}
                )
            else:
                dggrid_gdf = dggrid_gdf.rename(columns={"name": "seqnum"})

        except Exception:
            pass
        # Append the filtered GeoDataFrame to the list
        if include_properties and feature_properties and not dggrid_gdf.empty:
            for key, value in feature_properties.items():
                dggrid_gdf[key] = value
        merged_grids.append(dggrid_gdf)

    # Merge all filtered grids into one GeoDataFrame
    if merged_grids:
        final_grid = gpd.GeoDataFrame(
            pd.concat(merged_grids, ignore_index=True), crs=merged_grids[0].crs
        )
    else:
        final_grid = gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")

    if split_antimeridian:
        if aggregate:
            final_grid = final_grid.dissolve(by=f"dggrid_{dggs_type.lower()}")
    return final_grid

vector2dggrid(dggrid_instance, dggs_type, vector_data, resolution=None, predicate=None, compact=False, topology=False, include_properties=True, output_address_type='SEQNUM', output_format='gpd', split_antimeridian=False, aggregate=False, **kwargs)

Convert vector data to DGGRID grid cells from various input formats. If output_format is a file-based format (csv, geojson, shapefile, gpkg, parquet, geoparquet), the output will be saved to a file in the current directory with a default name based on the input. Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Parameters:

Name Type Description Default
data

Input data (file path, URL, GeoDataFrame, GeoJSON, etc.)

required
dggrid_instance

DGGRIDv7 instance for grid operations.

required
dggs_type

DGGS type (e.g., ISEA4H, FULLER, etc.)

required
resolution

Resolution for the DGGRID

None
address_type

Output address type (default: SEQNUM)

required
output_format

Output format (gpd, geojson, csv, etc.)

'gpd'
include_properties

Whether to include original feature properties

True
split_antimeridian

When True, apply antimeridian fixing to the resulting polygons.

False
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

GeoDataFrame or file path depending on output_format

Source code in vgrid/conversion/vector2dggs/vector2dggrid.py
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def vector2dggrid(
    dggrid_instance,
    dggs_type,
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    output_address_type="SEQNUM",
    output_format="gpd",
    split_antimeridian=False,
    aggregate=False,
    **kwargs,
):
    """
    Convert vector data to DGGRID grid cells from various input formats.
    If output_format is a file-based format (csv, geojson, shapefile, gpkg, parquet, geoparquet),
    the output will be saved to a file in the current directory with a default name based on the input.
    Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Args:
        data: Input data (file path, URL, GeoDataFrame, GeoJSON, etc.)
        dggrid_instance: DGGRIDv7 instance for grid operations.
        dggs_type: DGGS type (e.g., ISEA4H, FULLER, etc.)
        resolution: Resolution for the DGGRID
        address_type: Output address type (default: SEQNUM)
        output_format: Output format (gpd, geojson, csv, etc.)
        include_properties: Whether to include original feature properties
        split_antimeridian: When True, apply antimeridian fixing to the resulting polygons.
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        GeoDataFrame or file path depending on output_format
    """
    dggs_type = validate_dggrid_type(dggs_type)
    resolution = validate_dggrid_resolution(dggs_type, resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2dggrid(
        dggrid_instance,
        dggs_type,
        gdf,
        resolution,
        predicate,
        compact,
        topology,
        include_properties,
        output_address_type,
        split_antimeridian=split_antimeridian,
        aggregate=aggregate,
    )

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2dggrid_{dggs_type}_{resolution}"
        else:
            output_name = f"dggrid_{dggs_type}_{resolution}"

    return convert_to_output_format(result, output_format, output_name)

Vector to QTM Module

This module provides functionality to convert vector geometries to QTM grid cells with flexible input and output formats.

Key Functions

geodataframe2qtm(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to QTM grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

QTM resolution [1..24]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable QTM compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint QTM cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with QTM grid cells

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
def geodataframe2qtm(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to QTM grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): QTM resolution [1..24]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable QTM compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint QTM cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with QTM grid cells
    """

    # Process topology for points and multipoints if enabled
    if topology:
        resolution = None
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where QTM cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint QTM cells
            if shortest_distance > 0:
                for res in range(
                    min_res, max_res + 1
                ):  # QTM resolution range is [1..24]
                    _, avg_edge_length, _, _ = qtm_metrics(res)
                    # Use a factor to ensure sufficient separation (triangle diameter is ~2x edge length)
                    triangle_diameter = avg_edge_length * 2
                    if triangle_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_qtm_resolution(resolution)

    qtm_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            qtm_rows.extend(
                point2qtm(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            qtm_rows.extend(
                polyline2qtm(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            qtm_rows.extend(
                polygon2qtm(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(qtm_rows, geometry="geometry", crs="EPSG:4326")

point2qtm(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to QTM grid cells.

Converts point or multipoint geometries to QTM grid cells at the specified resolution. Each point is assigned to its containing QTM cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to QTM cells. resolution : int QTM resolution level [1..24]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable QTM compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2qtm). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing QTM cells containing the point(s). Each dictionary contains QTM cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2qtm(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2qtm(points, 8) len(cells) 2

Returns:

Name Type Description
list

List of GeoJSON feature dictionaries representing QTM cells containing the point

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
 65
 66
 67
 68
 69
 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
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
138
139
140
141
def point2qtm(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to QTM grid cells.

    Converts point or multipoint geometries to QTM grid cells at the specified resolution.
    Each point is assigned to its containing QTM cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to QTM cells.
    resolution : int
        QTM resolution level [1..24].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable QTM compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2qtm).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing QTM cells containing the point(s).
        Each dictionary contains QTM cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2qtm(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2qtm(points, 8)
    >>> len(cells)
    2

    Returns:
        list: List of GeoJSON feature dictionaries representing QTM cells containing the point
    """
    qtm_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        latitude = point.y
        longitude = point.x
        qtm_id = qtm.latlon_to_qtm_id(latitude, longitude, resolution)
        cell_polygon = qtm2geo(qtm_id)
        if cell_polygon:
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "qtm", qtm_id, resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            qtm_rows.append(row)
    return qtm_rows

polygon2qtm(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert polygon geometries (Polygon, MultiPolygon) to QTM grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

QTM resolution [1..24]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable QTM compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2qtm)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of GeoJSON feature dictionaries representing QTM cells based on predicate

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
def polygon2qtm(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert polygon geometries (Polygon, MultiPolygon) to QTM grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): QTM resolution [1..24]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable QTM compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2qtm)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of GeoJSON feature dictionaries representing QTM cells based on predicate
    """
    qtm_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []
    for polygon in polygons:
        levelFacets = {}
        QTMID = {}
        for lvl in range(resolution):
            levelFacets[lvl] = []
            QTMID[lvl] = []
            if lvl == 0:
                initial_facets = [
                    [p0_n180, p0_n90, p90_n90, p90_n180, p0_n180, True],
                    [p0_n90, p0_p0, p90_p0, p90_n90, p0_n90, True],
                    [p0_p0, p0_p90, p90_p90, p90_p0, p0_p0, True],
                    [p0_p90, p0_p180, p90_p180, p90_p90, p0_p90, True],
                    [n90_n180, n90_n90, p0_n90, p0_n180, n90_n180, False],
                    [n90_n90, n90_p0, p0_p0, p0_n90, n90_n90, False],
                    [n90_p0, n90_p90, p0_p90, p0_p0, n90_p0, False],
                    [n90_p90, n90_p180, p0_p180, p0_p90, n90_p90, False],
                ]
                for i, facet in enumerate(initial_facets):
                    QTMID[0].append(str(i + 1))
                    levelFacets[0].append(facet)
                    facet_geom = qtm.constructGeometry(facet)
                    if Polygon(facet_geom).intersects(polygon) and resolution == 1:
                        qtm_id = QTMID[0][i]
                        num_edges = 3
                        row = geodesic_dggs_to_geoseries(
                            "qtm", qtm_id, resolution, facet_geom, num_edges
                        )
                        if include_properties and feature_properties:
                            row.update(feature_properties)
                        qtm_rows.append(row)
                        return qtm_rows
            else:
                for i, pf in enumerate(levelFacets[lvl - 1]):
                    subdivided_facets = qtm.divideFacet(pf)
                    for j, subfacet in enumerate(subdivided_facets):
                        subfacet_geom = qtm.constructGeometry(subfacet)
                        if Polygon(subfacet_geom).intersects(polygon):
                            new_id = QTMID[lvl - 1][i] + str(j)
                            QTMID[lvl].append(new_id)
                            levelFacets[lvl].append(subfacet)
                            if lvl == resolution - 1:
                                if not check_predicate(
                                    Polygon(subfacet_geom), polygon, predicate
                                ):
                                    continue
                                num_edges = 3
                                row = geodesic_dggs_to_geoseries(
                                    "qtm", new_id, resolution, subfacet_geom, num_edges
                                )
                                if include_properties and feature_properties:
                                    row.update(feature_properties)
                                qtm_rows.append(row)
    return qtm_rows

polyline2qtm(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert line geometries (LineString, MultiLineString) to QTM grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Line geometry to convert

required
resolution int

QTM resolution [1..24]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for lines)

None
compact bool

Enable QTM compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2qtm)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of GeoJSON feature dictionaries representing QTM cells intersecting the line

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def polyline2qtm(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert line geometries (LineString, MultiLineString) to QTM grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Line geometry to convert
        resolution (int): QTM resolution [1..24]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for lines)
        compact (bool, optional): Enable QTM compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2qtm)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of GeoJSON feature dictionaries representing QTM cells intersecting the line
    """
    qtm_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []
    for polyline in polylines:
        levelFacets = {}
        QTMID = {}
        for lvl in range(resolution):
            levelFacets[lvl] = []
            QTMID[lvl] = []
            if lvl == 0:
                initial_facets = [
                    [p0_n180, p0_n90, p90_n90, p90_n180, p0_n180, True],
                    [p0_n90, p0_p0, p90_p0, p90_n90, p0_n90, True],
                    [p0_p0, p0_p90, p90_p90, p90_p0, p0_p0, True],
                    [p0_p90, p0_p180, p90_p180, p90_p90, p0_p90, True],
                    [n90_n180, n90_n90, p0_n90, p0_n180, n90_n180, False],
                    [n90_n90, n90_p0, p0_p0, p0_n90, n90_n90, False],
                    [n90_p0, n90_p90, p0_p90, p0_p0, n90_p0, False],
                    [n90_p90, n90_p180, p0_p180, p0_p90, n90_p90, False],
                ]
                for i, facet in enumerate(initial_facets):
                    QTMID[0].append(str(i + 1))
                    levelFacets[0].append(facet)
                    facet_geom = qtm.constructGeometry(facet)
                    if Polygon(facet_geom).intersects(polyline) and resolution == 1:
                        qtm_id = QTMID[0][i]
                        num_edges = 3
                        row = geodesic_dggs_to_geoseries(
                            "qtm", qtm_id, resolution, facet_geom, num_edges
                        )
                        if include_properties and feature_properties:
                            row.update(feature_properties)
                        qtm_rows.append(row)
                        return qtm_rows
            else:
                for i, pf in enumerate(levelFacets[lvl - 1]):
                    subdivided_facets = qtm.divideFacet(pf)
                    for j, subfacet in enumerate(subdivided_facets):
                        subfacet_geom = qtm.constructGeometry(subfacet)
                        if Polygon(subfacet_geom).intersects(polyline):
                            new_id = QTMID[lvl - 1][i] + str(j)
                            QTMID[lvl].append(new_id)
                            levelFacets[lvl].append(subfacet)
                            if lvl == resolution - 1:
                                num_edges = 3
                                row = geodesic_dggs_to_geoseries(
                                    "qtm", new_id, resolution, subfacet_geom, num_edges
                                )
                                if include_properties and feature_properties:
                                    row.update(feature_properties)
                                qtm_rows.append(row)
    return qtm_rows

vector2qtm(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to QTM grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

QTM resolution [1..24]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable QTM compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint QTM cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
def vector2qtm(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to QTM grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): QTM resolution [1..24]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable QTM compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint QTM cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_qtm_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2qtm(
        gdf, resolution, predicate, compact, topology, include_properties
    )

    # Apply compaction if requested
    if compact:
        result = qtmcompact(result, qtm_id="qtm", output_format="gpd")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2qtm"
        else:
            output_name = "qtm"
    return convert_to_output_format(result, output_format, output_name)

vector2qtm_cli()

Command-line interface for vector2qtm conversion.

Source code in vgrid/conversion/vector2dggs/vector2qtm.py
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
def vector2qtm_cli():
    """
    Command-line interface for vector2qtm conversion.
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to QTM grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")
    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        metavar=f"[{min_res}..{max_res}]",
        help=f"QTM resolution [{min_res}..{max_res}] (coarsest={min_res}, finest={max_res})",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable QTM compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format (default: gpd).",
    )

    args = parser.parse_args()
    args.resolution = validate_qtm_resolution(args.resolution)
    try:
        result = vector2qtm(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to OLC Module

This module provides functionality to convert vector geometries to OLC grid cells with flexible input and output formats.

Key Functions

geodataframe2olc(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to OLC grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

OLC resolution level [2,4,6,8,10,11,12,13,14,15]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable OLC compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint OLC cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with OLC grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2olc(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2olc.py
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def geodataframe2olc(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to OLC grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): OLC resolution level [2,4,6,8,10,11,12,13,14,15]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable OLC compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint OLC cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with OLC grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2olc(gdf, 10)
        >>> len(result) > 0
        True
    """

    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where OLC cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint OLC cells
            if shortest_distance > 0:
                for res in [
                    2,
                    4,
                    6,
                    8,
                    10,
                    11,
                    12,
                    13,
                    14,
                    15,
                ]:  # OLC valid resolutions
                    _, avg_edge_length, _, _ = olc_metrics(res)
                    # Use a factor to ensure sufficient separation (cell diameter is ~2x edge length)
                    cell_diameter = avg_edge_length * sqrt(2) * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_olc_resolution(resolution)

    olc_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            olc_rows.extend(
                point2olc(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            olc_rows.extend(
                polyline2olc(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            olc_rows.extend(
                polygon2olc(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(olc_rows, geometry="geometry", crs="EPSG:4326")

point2olc(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to OLC grid cells.

Converts point or multipoint geometries to OLC grid cells at the specified resolution. Each point is assigned to its containing OLC cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to OLC cells. resolution : int OLC resolution level [2,4,6,8,10,11,12,13,14,15]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable OLC compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2olc). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing OLC cells containing the point(s). Each dictionary contains OLC cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2olc(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2olc(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2olc.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
def point2olc(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to OLC grid cells.

    Converts point or multipoint geometries to OLC grid cells at the specified resolution.
    Each point is assigned to its containing OLC cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to OLC cells.
    resolution : int
        OLC resolution level [2,4,6,8,10,11,12,13,14,15].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable OLC compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2olc).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing OLC cells containing the point(s).
        Each dictionary contains OLC cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2olc(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2olc(points, 8)
    >>> len(cells)
    2
    """
    olc_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        olc_id = olc.encode(point.y, point.x, resolution)
        cell_polygon = olc2geo(olc_id)
        if cell_polygon:
            olc_row = graticule_dggs_to_geoseries(
                "olc", olc_id, resolution, cell_polygon
            )
            if include_properties and feature_properties:
                olc_row.update(feature_properties)
            olc_rows.append(olc_row)
    return olc_rows

polygon2olc(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polygon geometry to OLC grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

OLC resolution level [2,4,6,8,10,11,12,13,14,15]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable OLC compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2olc)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing OLC cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2olc(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2olc.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
def polygon2olc(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polygon geometry to OLC grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): OLC resolution level [2,4,6,8,10,11,12,13,14,15]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable OLC compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2olc)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing OLC cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2olc(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    olc_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        base_resolution = 2
        base_cells = olc_grid(base_resolution, verbose=False)
        seed_cells = []
        for idx, base_cell in base_cells.iterrows():
            base_cell_poly = base_cell["geometry"]
            if polygon.intersects(base_cell_poly):
                seed_cells.append(base_cell)
        refined_features = []
        for seed_cell in seed_cells:
            seed_cell_poly = seed_cell["geometry"]
            if seed_cell_poly.contains(polygon) and resolution == base_resolution:
                refined_features.append(seed_cell)
            else:
                refined_features.extend(
                    olc_refine_cell(
                        seed_cell_poly.bounds, base_resolution, resolution, polygon
                    )
                )
        # refined_features may be a mix of GeoDataFrame rows and dicts from refine_cell
        # Normalize all to dicts for downstream processing
        normalized_features = []
        for feat in refined_features:
            if isinstance(feat, dict):
                normalized_features.append(feat)
            else:
                # Convert GeoDataFrame row to dict
                d = dict(feat)
                d["geometry"] = feat["geometry"]
                normalized_features.append(d)
        resolution_features = [
            refined_feature
            for refined_feature in normalized_features
            if refined_feature["resolution"] == resolution
        ]
        seen_olc_codes = set()
        for resolution_feature in resolution_features:
            olc_id = resolution_feature["olc"]
            if olc_id not in seen_olc_codes:
                cell_geom = olc2geo(olc_id)
                if not check_predicate(cell_geom, polygon, predicate):
                    continue
                olc_row = graticule_dggs_to_geoseries(
                    "olc", olc_id, resolution, cell_geom
                )
                if include_properties and feature_properties:
                    olc_row.update(feature_properties)
                olc_rows.append(olc_row)
                seen_olc_codes.add(olc_id)

    # Apply compact mode if enabled
    if compact and olc_rows:
        # Create a GeoDataFrame from the current results
        temp_gdf = gpd.GeoDataFrame(olc_rows, geometry="geometry", crs="EPSG:4326")

        # Use olccompact function directly
        compacted_gdf = olccompact(temp_gdf, olc_id="olc", output_format="gpd")

        if compacted_gdf is not None:
            # Convert back to list of dictionaries
            olc_rows = compacted_gdf.to_dict("records")
        # If compaction failed, keep original results

    return olc_rows

polyline2olc(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polyline geometry to OLC grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

OLC resolution level [2,4,6,8,10,11,12,13,14,15]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable OLC compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2olc)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing OLC cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2olc(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2olc.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
def polyline2olc(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polyline geometry to OLC grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): OLC resolution level [2,4,6,8,10,11,12,13,14,15]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable OLC compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2olc)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing OLC cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2olc(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    olc_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        base_resolution = 2
        base_cells = olc_grid(base_resolution, verbose=False)
        seed_cells = []
        for idx, base_cell in base_cells.iterrows():
            base_cell_poly = base_cell["geometry"]
            if polyline.intersects(base_cell_poly):
                seed_cells.append(base_cell)
        refined_features = []
        for seed_cell in seed_cells:
            seed_cell_poly = seed_cell["geometry"]
            if seed_cell_poly.contains(polyline) and resolution == base_resolution:
                refined_features.append(seed_cell)
            else:
                refined_features.extend(
                    olc_refine_cell(
                        seed_cell_poly.bounds, base_resolution, resolution, polyline
                    )
                )
        # refined_features may be a mix of GeoDataFrame rows and dicts from refine_cell
        # Normalize all to dicts for downstream processing
        normalized_features = []
        for feat in refined_features:
            if isinstance(feat, dict):
                normalized_features.append(feat)
            else:
                # Convert GeoDataFrame row to dict
                d = dict(feat)
                d["geometry"] = feat["geometry"]
                normalized_features.append(d)
        resolution_features = [
            refined_feature
            for refined_feature in normalized_features
            if refined_feature["resolution"] == resolution
        ]
        seen_olc_codes = set()
        for resolution_feature in resolution_features:
            olc_id = resolution_feature["olc"]
            if olc_id not in seen_olc_codes:
                cell_polygon = olc2geo(olc_id)
                olc_row = graticule_dggs_to_geoseries(
                    "olc", olc_id, resolution, cell_polygon
                )
                if include_properties and feature_properties:
                    olc_row.update(feature_properties)
                olc_rows.append(olc_row)
                seen_olc_codes.add(olc_id)
    return olc_rows

vector2olc(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to OLC grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

OLC resolution level [2,4,6,8,10,11,12,13,14,15]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable OLC compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint OLC cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2olc("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2olc.py
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
def vector2olc(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to OLC grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): OLC resolution level [2,4,6,8,10,11,12,13,14,15]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable OLC compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint OLC cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2olc("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_olc_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2olc(
        gdf, resolution, predicate, compact, topology, include_properties
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2olc"
        else:
            output_name = "olc"
    return convert_to_output_format(result, output_format, output_name)

vector2olc_cli()

Command-line interface for vector2olc conversion.

This function provides a command-line interface for converting vector data to OLC grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2olc.py -i input.geojson -r 10 -f geojson python vector2olc.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2olc.py
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
def vector2olc_cli():
    """
    Command-line interface for vector2olc conversion.

    This function provides a command-line interface for converting vector data to OLC grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2olc.py -i input.geojson -r 10 -f geojson
        python vector2olc.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to OLC grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=[2, 4, 6, 8, 10, 11, 12, 13, 14, 15],
        help="OLC resolution [2,4,6,8,10,11,12,13,14,15]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable OLC compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )
    args = parser.parse_args()

    try:
        result = vector2olc(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to Geohash Module

This module provides functionality to convert vector geometries to Geohash grid cells with flexible input and output formats.

Key Functions

geodataframe2geohash(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to Geohash grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

Geohash resolution level [1..10]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Geohash compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Geohash cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with Geohash grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2geohash(gdf, 6) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
def geodataframe2geohash(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to Geohash grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): Geohash resolution level [1..10]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Geohash compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Geohash cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with Geohash grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2geohash(gdf, 6)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where Geohash cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint Geohash cells
            if shortest_distance > 0:
                # Geohash cell size (approx): each character increases resolution, cell size shrinks by ~1/8 to 1/32
                # We'll use a rough estimate: cell size halves every character, so use a lookup or formula
                # For simplicity, use a fixed table for WGS84 (meters) for geohash length 1-10
                geohash_cell_sizes = [
                    5000000,
                    1250000,
                    156000,
                    39100,
                    4890,
                    1220,
                    153,
                    38.2,
                    4.77,
                    1.19,
                ]  # meters
                for res in range(min_res, max_res + 1):
                    cell_diameter = geohash_cell_sizes[res - 1] * sqrt(2) * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_geohash_resolution(resolution)

    geohash_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            geohash_rows.extend(
                point2geohash(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            geohash_rows.extend(
                polyline2geohash(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            geohash_rows.extend(
                polygon2geohash(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(geohash_rows, geometry="geometry", crs="EPSG:4326")

point2geohash(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to Geohash grid cells.

Converts point or multipoint geometries to Geohash grid cells at the specified resolution. Each point is assigned to its containing Geohash cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to Geohash cells. resolution : int Geohash resolution level [1..10]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable Geohash compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2geohash). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing Geohash cells containing the point(s). Each dictionary contains Geohash cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2geohash(point, 6, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2geohash(points, 5) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
115
116
def point2geohash(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to Geohash grid cells.

    Converts point or multipoint geometries to Geohash grid cells at the specified resolution.
    Each point is assigned to its containing Geohash cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to Geohash cells.
    resolution : int
        Geohash resolution level [1..10].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable Geohash compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2geohash).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing Geohash cells containing the point(s).
        Each dictionary contains Geohash cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2geohash(point, 6, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2geohash(points, 5)
    >>> len(cells)
    2
    """
    geohash_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        longitude = point.x
        latitude = point.y
        geohash_id = geohash.encode(latitude, longitude, resolution)
        cell_polygon = geohash2geo(geohash_id)
        row = graticule_dggs_to_geoseries(
            "geohash", geohash_id, resolution, cell_polygon
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        geohash_rows.append(row)
    return geohash_rows

polygon2geohash(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polygon geometry to Geohash grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

Geohash resolution level [1..10]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Geohash compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2geohash)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Geohash cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2geohash(poly, 6, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
def polygon2geohash(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polygon geometry to Geohash grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): Geohash resolution level [1..10]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Geohash compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2geohash)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Geohash cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2geohash(poly, 6, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    geohash_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        intersected_geohashes = {
            gh for gh in INITIAL_GEOHASHES if geohash2geo(gh).intersects(polygon)
        }
        geohashes_bbox = set()
        for gh in intersected_geohashes:
            expand_geohash_bbox(gh, resolution, geohashes_bbox, polygon)

        for gh in geohashes_bbox:
            cell_polygon = geohash2geo(gh)
            row = graticule_dggs_to_geoseries("geohash", gh, resolution, cell_polygon)
            cell_geom = row["geometry"]
            if not check_predicate(cell_geom, polygon, predicate):
                continue
            if include_properties and feature_properties:
                row.update(feature_properties)
            geohash_rows.append(row)

    # Apply compact mode if enabled
    if compact and geohash_rows:
        # Create a GeoDataFrame from the current results
        temp_gdf = gpd.GeoDataFrame(geohash_rows, geometry="geometry", crs="EPSG:4326")

        # Use geohashcompact function directly
        compacted_gdf = geohashcompact(
            temp_gdf, geohash_id="geohash", output_format="gpd"
        )

        if compacted_gdf is not None:
            # Convert back to list of dictionaries
            geohash_rows = compacted_gdf.to_dict("records")
        # If compaction failed, keep original results

    return geohash_rows

polyline2geohash(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polyline geometry to Geohash grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

Geohash resolution level [1..10]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable Geohash compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2geohash)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Geohash cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2geohash(line, 6, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def polyline2geohash(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polyline geometry to Geohash grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): Geohash resolution level [1..10]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable Geohash compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2geohash)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Geohash cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2geohash(line, 6, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    geohash_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        intersected_geohashes = {
            gh for gh in INITIAL_GEOHASHES if geohash2geo(gh).intersects(polyline)
        }
        geohashes_bbox = set()
        for gh in intersected_geohashes:
            expand_geohash_bbox(gh, resolution, geohashes_bbox, polyline)

        for gh in geohashes_bbox:
            cell_polygon = geohash2geo(gh)
            row = graticule_dggs_to_geoseries("geohash", gh, resolution, cell_polygon)
            if include_properties and feature_properties:
                row.update(feature_properties)
            geohash_rows.append(row)
    return geohash_rows

vector2geohash(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to Geohash grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

Geohash resolution level [1..10]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Geohash compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Geohash cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2geohash("data/points.geojson", resolution=6, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
def vector2geohash(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to Geohash grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): Geohash resolution level [1..10]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Geohash compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Geohash cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2geohash("data/points.geojson", resolution=6, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_geohash_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2geohash(
        gdf, resolution, predicate, compact, topology, include_properties
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2geohash"
        else:
            output_name = "geohash"
    return convert_to_output_format(result, output_format, output_name)

vector2geohash_cli()

Command-line interface for vector2geohash conversion.

This function provides a command-line interface for converting vector data to Geohash grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2geohash.py -i input.geojson -r 6 -f geojson python vector2geohash.py -i input.shp -r 5 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2geohash.py
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def vector2geohash_cli():
    """
    Command-line interface for vector2geohash conversion.

    This function provides a command-line interface for converting vector data to Geohash grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2geohash.py -i input.geojson -r 6 -f geojson
        python vector2geohash.py -i input.shp -r 5 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to Geohash grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"Geohash resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable Geohash compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )
    args = parser.parse_args()

    try:
        result = vector2geohash(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to Tilecode Module

This module provides functionality to convert vector geometries to Tilecode grid cells with flexible input and output formats. Tilecodes are hierarchical spatial identifiers in the format 'z{x}x{y}y{z}' where z is the zoom level and x,y are tile coordinates.

Key Functions

geodataframe2tilecode(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to Tilecode grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

Tilecode resolution level [0..29]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Tilecode compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Tilecode cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with Tilecode grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2tilecode(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def geodataframe2tilecode(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to Tilecode grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): Tilecode resolution level [0..29]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Tilecode compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Tilecode cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with Tilecode grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2tilecode(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where Tilecode cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint Tilecode cells
            if shortest_distance > 0:
                tilecode_cell_sizes = [
                    40075016.68557849 / (2**z) for z in range(min_res, max_res + 1)
                ]
                for res in range(min_res, max_res + 1):
                    cell_diameter = tilecode_cell_sizes[res] * sqrt(2) * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break
        resolution = estimated_resolution

    resolution = validate_tilecode_resolution(resolution)

    tilecode_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            tilecode_rows.extend(
                point2tilecode(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            tilecode_rows.extend(
                polyline2tilecode(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            tilecode_rows.extend(
                polygon2tilecode(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(tilecode_rows, geometry="geometry", crs="EPSG:4326")

point2tilecode(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to Tilecode grid cells.

Converts point or multipoint geometries to Tilecode grid cells at the specified resolution. Each point is assigned to its containing Tilecode cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to Tilecode cells. resolution : int Tilecode resolution level [0..29]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable Tilecode compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2tilecode). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing Tilecode cells containing the point(s). Each dictionary contains Tilecode cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2tilecode(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2tilecode(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def point2tilecode(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to Tilecode grid cells.

    Converts point or multipoint geometries to Tilecode grid cells at the specified resolution.
    Each point is assigned to its containing Tilecode cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to Tilecode cells.
    resolution : int
        Tilecode resolution level [0..29].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable Tilecode compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2tilecode).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing Tilecode cells containing the point(s).
        Each dictionary contains Tilecode cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2tilecode(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2tilecode(points, 8)
    >>> len(cells)
    2
    """
    tilecode_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        tilecode_id = tilecode.latlon2tilecode(point.y, point.x, resolution)
        tilecode_cell = mercantile.tile(point.x, point.y, resolution)
        bounds = mercantile.bounds(tilecode_cell)
        if bounds:
            min_lat, min_lon = bounds.south, bounds.west
            max_lat, max_lon = bounds.north, bounds.east
            cell_polygon = Polygon(
                [
                    [min_lon, min_lat],
                    [max_lon, min_lat],
                    [max_lon, max_lat],
                    [min_lon, max_lat],
                    [min_lon, min_lat],
                ]
            )
            tilecode_row = graticule_dggs_to_geoseries(
                "tilecode", tilecode_id, resolution, cell_polygon
            )
            if include_properties and feature_properties:
                tilecode_row.update(feature_properties)
            tilecode_rows.append(tilecode_row)
    return tilecode_rows

polygon2tilecode(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polygon geometry to Tilecode grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

Tilecode resolution level [0..29]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Tilecode compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2tilecode)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Tilecode cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2tilecode(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
def polygon2tilecode(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polygon geometry to Tilecode grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): Tilecode resolution level [0..29]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Tilecode compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2tilecode)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Tilecode cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2tilecode(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    tilecode_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        min_lon, min_lat, max_lon, max_lat = polygon.bounds
        tilecodes = mercantile.tiles(min_lon, min_lat, max_lon, max_lat, resolution)
        tilecode_ids = []
        for tile in tilecodes:
            tilecode_id = f"z{tile.z}x{tile.x}y{tile.y}"
            tilecode_ids.append(tilecode_id)
        for tilecode_id in tilecode_ids:
            match = re.match(r"z(\d+)x(\d+)y(\d+)", tilecode_id)
            if not match:
                raise ValueError(
                    "Invalid tilecode output_format. Expected output_format: 'zXxYyZ'"
                )
            cell_resolution = int(match.group(1))
            z = int(match.group(1))
            x = int(match.group(2))
            y = int(match.group(3))
            bounds = mercantile.bounds(x, y, z)
            if bounds:
                min_lat, min_lon = bounds.south, bounds.west
                max_lat, max_lon = bounds.north, bounds.east
                cell_polygon = Polygon(
                    [
                        [min_lon, min_lat],
                        [max_lon, min_lat],
                        [max_lon, max_lat],
                        [min_lon, max_lat],
                        [min_lon, min_lat],
                    ]
                )
                if check_predicate(cell_polygon, polygon, predicate):
                    tilecode_row = graticule_dggs_to_geoseries(
                        "tilecode", tilecode_id, cell_resolution, cell_polygon
                    )
                    if include_properties and feature_properties:
                        tilecode_row.update(feature_properties)
                    tilecode_rows.append(tilecode_row)

    # Apply compact mode if enabled
    if compact and tilecode_rows:
        # Create a GeoDataFrame from the current results
        temp_gdf = gpd.GeoDataFrame(tilecode_rows, geometry="geometry", crs="EPSG:4326")

        # Use tilecodecompact function directly
        compacted_gdf = tilecodecompact(
            temp_gdf, tilecode_id="tilecode", output_format="gpd"
        )

        if compacted_gdf is not None:
            # Convert back to list of dictionaries
            tilecode_rows = compacted_gdf.to_dict("records")
        # If compaction failed, keep original results

    return tilecode_rows

polyline2tilecode(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polyline geometry to Tilecode grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

Tilecode resolution level [0..29]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable Tilecode compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2tilecode)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Tilecode cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2tilecode(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def polyline2tilecode(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polyline geometry to Tilecode grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): Tilecode resolution level [0..29]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable Tilecode compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2tilecode)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Tilecode cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2tilecode(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    tilecode_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        min_lon, min_lat, max_lon, max_lat = polyline.bounds
        tilecodes = mercantile.tiles(min_lon, min_lat, max_lon, max_lat, resolution)
        tilecode_ids = []
        for tile in tilecodes:
            tilecode_id = f"z{tile.z}x{tile.x}y{tile.y}"
            tilecode_ids.append(tilecode_id)
        for tilecode_id in tilecode_ids:
            match = re.match(r"z(\d+)x(\d+)y(\d+)", tilecode_id)
            if not match:
                raise ValueError(
                    "Invalid tilecode output_format. Expected output_format: 'zXxYyZ'"
                )
            cell_resolution = int(match.group(1))
            z = int(match.group(1))
            x = int(match.group(2))
            y = int(match.group(3))
            bounds = mercantile.bounds(x, y, z)
            if bounds:
                min_lat, min_lon = bounds.south, bounds.west
                max_lat, max_lon = bounds.north, bounds.east
                cell_polygon = Polygon(
                    [
                        [min_lon, min_lat],
                        [max_lon, min_lat],
                        [max_lon, max_lat],
                        [min_lon, max_lat],
                        [min_lon, min_lat],
                    ]
                )
                if cell_polygon.intersects(polyline):
                    tilecode_row = graticule_dggs_to_geoseries(
                        "tilecode", tilecode_id, cell_resolution, cell_polygon
                    )
                    if feature_properties:
                        tilecode_row.update(feature_properties)
                    tilecode_rows.append(tilecode_row)
    return tilecode_rows

vector2tilecode(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to Tilecode grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

Tilecode resolution level [0..29]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Tilecode compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Tilecode cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2tilecode("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
def vector2tilecode(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to Tilecode grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): Tilecode resolution level [0..29]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Tilecode compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Tilecode cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2tilecode("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_tilecode_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2tilecode(
        gdf, resolution, predicate, compact, topology, include_properties
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2tilecode"
        else:
            output_name = "tilecode"
    return convert_to_output_format(result, output_format, output_name)

vector2tilecode_cli()

Command-line interface for vector2tilecode conversion.

This function provides a command-line interface for converting vector data to Tilecode grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2tilecode.py -i input.geojson -r 10 -f geojson python vector2tilecode.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2tilecode.py
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
def vector2tilecode_cli():
    """
    Command-line interface for vector2tilecode conversion.

    This function provides a command-line interface for converting vector data to Tilecode grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2tilecode.py -i input.geojson -r 10 -f geojson
        python vector2tilecode.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to Tilecode grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"Tilecode resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable Tilecode compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )
    args = parser.parse_args()

    try:
        result = vector2tilecode(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)

Vector to Quadkey Module

This module provides functionality to convert vector geometries to Quadkey grid cells with flexible input and output formats.

Key Functions

geodataframe2quadkey(gdf, resolution=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a GeoDataFrame to Quadkey grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

Quadkey resolution level [0..29]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Quadkey compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Quadkey cells

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with Quadkey grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['San Francisco'], ... 'geometry': [Point(-122.4194, 37.7749)] ... }) result = geodataframe2quadkey(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def geodataframe2quadkey(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to Quadkey grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): Quadkey resolution level [0..29]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Quadkey compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Quadkey cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with Quadkey grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['San Francisco'],
        ...     'geometry': [Point(-122.4194, 37.7749)]
        ... })
        >>> result = geodataframe2quadkey(gdf, 10)
        >>> len(result) > 0
        True
    """

    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = resolution
        # Collect all points for topology preservation
        points_list = []
        for _, row in gdf.iterrows():
            geom = row.geometry
            if geom is None:
                continue
            if geom.geom_type == "Point":
                points_list.append(geom)
            elif geom.geom_type == "MultiPoint":
                points_list.extend(list(geom.geoms))

        if points_list:
            all_points = MultiPoint(points_list)

            # Calculate the shortest distance between all points
            shortest_distance = shortest_point_distance(all_points)

            # Find resolution where Quadkey cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint Quadkey cells
            if shortest_distance > 0:
                quadkey_cell_sizes = [
                    40075016.68557849 / (2**z) for z in range(min_res, max_res + 1)
                ]
                for res in range(min_res, max_res + 1):
                    cell_diameter = quadkey_cell_sizes[res] * sqrt(2) * 2
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_quadkey_resolution(resolution)

    quadkey_rows = []

    for _, row in tqdm(gdf.iterrows(), desc="Processing features", total=len(gdf)):
        geom = row.geometry
        if geom is None:
            continue

        props = row.to_dict()
        if "geometry" in props:
            del props["geometry"]

        if not include_properties:
            props = {}

        if geom.geom_type == "Point" or geom.geom_type == "MultiPoint":
            quadkey_rows.extend(
                point2quadkey(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    topology=topology,  # Topology already processed above
                    include_properties=include_properties,
                )
            )

        elif geom.geom_type in ("LineString", "MultiLineString"):
            quadkey_rows.extend(
                polyline2quadkey(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            quadkey_rows.extend(
                polygon2quadkey(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(quadkey_rows, geometry="geometry", crs="EPSG:4326")

point2quadkey(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a point geometry to Quadkey grid cells.

Converts point or multipoint geometries to Quadkey grid cells at the specified resolution. Each point is assigned to its containing Quadkey cell.

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to Quadkey cells. resolution : int Quadkey resolution level [0..29]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Spatial predicate to apply (not used for points). compact : bool, optional Enable Quadkey compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2quadkey). include_properties : bool, optional Whether to include properties in output.

Returns

list of dict List of dictionaries representing Quadkey cells containing the point(s). Each dictionary contains Quadkey cell properties and geometry.

Examples

from shapely.geometry import Point point = Point(-122.4194, 37.7749) # San Francisco cells = point2quadkey(point, 10, {"name": "SF"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)]) cells = point2quadkey(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
 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
 68
 69
 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def point2quadkey(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to Quadkey grid cells.

    Converts point or multipoint geometries to Quadkey grid cells at the specified resolution.
    Each point is assigned to its containing Quadkey cell.

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to Quadkey cells.
    resolution : int
        Quadkey resolution level [0..29].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Spatial predicate to apply (not used for points).
    compact : bool, optional
        Enable Quadkey compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2quadkey).
    include_properties : bool, optional
        Whether to include properties in output.

    Returns
    -------
    list of dict
        List of dictionaries representing Quadkey cells containing the point(s).
        Each dictionary contains Quadkey cell properties and geometry.

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(-122.4194, 37.7749)  # San Francisco
    >>> cells = point2quadkey(point, 10, {"name": "SF"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(-122.4194, 37.7749), (-74.0060, 40.7128)])
    >>> cells = point2quadkey(points, 8)
    >>> len(cells)
    2
    """
    quadkey_rows = []
    if feature.geom_type in ("Point"):
        points = [feature]
    elif feature.geom_type in ("MultiPoint"):
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        quadkey_id = tilecode.latlon2quadkey(point.y, point.x, resolution)
        quadkey_cell = mercantile.tile(point.x, point.y, resolution)
        bounds = mercantile.bounds(quadkey_cell)
        if bounds:
            min_lat, min_lon = bounds.south, bounds.west
            max_lat, max_lon = bounds.north, bounds.east
            cell_polygon = Polygon(
                [
                    [min_lon, min_lat],
                    [max_lon, min_lat],
                    [max_lon, max_lat],
                    [min_lon, max_lat],
                    [min_lon, min_lat],
                ]
            )
            quadkey_row = graticule_dggs_to_geoseries(
                "quadkey", quadkey_id, resolution, cell_polygon
            )
            if include_properties and feature_properties:
                quadkey_row.update(feature_properties)
            quadkey_rows.append(quadkey_row)
    return quadkey_rows

polygon2quadkey(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polygon geometry to Quadkey grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

Quadkey resolution level [0..29]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Quadkey compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2quadkey)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Quadkey cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)]) cells = polygon2quadkey(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
def polygon2quadkey(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polygon geometry to Quadkey grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): Quadkey resolution level [0..29]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Quadkey compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2quadkey)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Quadkey cells based on predicate

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(-122.5, 37.7), (-122.3, 37.7), (-122.3, 37.9), (-122.5, 37.9)])
        >>> cells = polygon2quadkey(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    quadkey_rows = []
    if feature.geom_type in ("Polygon"):
        polygons = [feature]
    elif feature.geom_type in ("MultiPolygon"):
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        min_lon, min_lat, max_lon, max_lat = polygon.bounds
        tiles = mercantile.tiles(min_lon, min_lat, max_lon, max_lat, resolution)
        for tile in tiles:
            z, x, y = tile.z, tile.x, tile.y
            bounds = mercantile.bounds(x, y, z)
            if bounds:
                min_lat, min_lon = bounds.south, bounds.west
                max_lat, max_lon = bounds.north, bounds.east
                quadkey_id = mercantile.quadkey(tile)
                cell_polygon = Polygon(
                    [
                        [min_lon, min_lat],
                        [max_lon, min_lat],
                        [max_lon, max_lat],
                        [min_lon, max_lat],
                        [min_lon, min_lat],
                    ]
                )
                if check_predicate(cell_polygon, polygon, predicate):
                    quadkey_row = graticule_dggs_to_geoseries(
                        "quadkey", quadkey_id, resolution, cell_polygon
                    )
                    if include_properties and feature_properties:
                        quadkey_row.update(feature_properties)
                    quadkey_rows.append(quadkey_row)

    # Apply compact mode if enabled
    if compact and quadkey_rows:
        # Create a GeoDataFrame from the current results
        temp_gdf = gpd.GeoDataFrame(quadkey_rows, geometry="geometry", crs="EPSG:4326")

        # Use quadkeycompact function directly
        compacted_gdf = quadkeycompact(
            temp_gdf, quadkey_id="quadkey", output_format="gpd"
        )

        if compacted_gdf is not None:
            # Convert back to list of dictionaries
            quadkey_rows = compacted_gdf.to_dict("records")
        # If compaction failed, keep original results

    return quadkey_rows

polyline2quadkey(feature, resolution, feature_properties=None, predicate=None, compact=False, topology=False, include_properties=True)

Convert a polyline geometry to Quadkey grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

Quadkey resolution level [0..29]

required
feature_properties dict

Properties to include in output features

None
predicate str

Spatial predicate to apply (not used for polylines)

None
compact bool

Enable Quadkey compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2quadkey)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing Quadkey cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)]) cells = polyline2quadkey(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def polyline2quadkey(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polyline geometry to Quadkey grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): Quadkey resolution level [0..29]
        feature_properties (dict, optional): Properties to include in output features
        predicate (str, optional): Spatial predicate to apply (not used for polylines)
        compact (bool, optional): Enable Quadkey compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2quadkey)
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        list: List of dictionaries representing Quadkey cells intersecting the polyline

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(-122.4194, 37.7749), (-122.4000, 37.7800)])
        >>> cells = polyline2quadkey(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """
    quadkey_rows = []
    if feature.geom_type in ("LineString"):
        polylines = [feature]
    elif feature.geom_type in ("MultiLineString"):
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        min_lon, min_lat, max_lon, max_lat = polyline.bounds
        tiles = mercantile.tiles(min_lon, min_lat, max_lon, max_lat, resolution)
        for tile in tiles:
            z, x, y = tile.z, tile.x, tile.y
            bounds = mercantile.bounds(x, y, z)
            if bounds:
                min_lat, min_lon = bounds.south, bounds.west
                max_lat, max_lon = bounds.north, bounds.east
                quadkey_id = mercantile.quadkey(tile)
                cell_polygon = Polygon(
                    [
                        [min_lon, min_lat],
                        [max_lon, min_lat],
                        [max_lon, max_lat],
                        [min_lon, max_lat],
                        [min_lon, min_lat],
                    ]
                )
                if cell_polygon.intersects(polyline):
                    quadkey_row = graticule_dggs_to_geoseries(
                        "quadkey", quadkey_id, resolution, cell_polygon
                    )
                    if include_properties and feature_properties:
                        quadkey_row.update(feature_properties)
                    quadkey_rows.append(quadkey_row)

    return quadkey_rows

vector2quadkey(vector_data, resolution=None, predicate=None, compact=False, topology=False, output_format='gpd', include_properties=True, **kwargs)

Convert vector data to Quadkey grid cells from various input formats.

Parameters:

Name Type Description Default
vector_data str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list

Input vector data

required
resolution int

Quadkey resolution level [0..29]. Required when topology=False, auto-calculated when topology=True

None
predicate str

Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')

None
compact bool

Enable Quadkey compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint Quadkey cells

False
output_format str

Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')

'gpd'
include_properties bool

Whether to include properties in output

True
**kwargs

Additional arguments passed to process_input_data_vector

{}

Returns:

Type Description

geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,

the output will be saved to a file in the current directory with a default name based on the input.

Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

Example

result = vector2quadkey("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def vector2quadkey(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to Quadkey grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): Quadkey resolution level [0..29]. Required when topology=False, auto-calculated when topology=True
        predicate (str, optional): Spatial predicate to apply for polygons ('intersect', 'within', 'centroid_within', 'largest_overlap')
        compact (bool, optional): Enable Quadkey compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint Quadkey cells
        output_format (str, optional): Output format ('gpd', 'geojson', 'csv', 'shapefile', 'gpkg', 'parquet', 'geoparquet')
        include_properties (bool, optional): Whether to include properties in output
        **kwargs: Additional arguments passed to process_input_data_vector

    Returns:
        geopandas.GeoDataFrame, dict, or str: Output in the specified format. If output_format is a file-based format,
        the output will be saved to a file in the current directory with a default name based on the input.
        Otherwise, returns a Python object (GeoDataFrame, dict, etc.) depending on output_format.

    Example:
        >>> result = vector2quadkey("data/points.geojson", resolution=10, output_format="geojson")
        >>> print(f"Output saved to: {result}")
    """
    # Validate resolution parameter
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    # Only validate resolution if it's not None
    if resolution is not None:
        resolution = validate_quadkey_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2quadkey(
        gdf, resolution, predicate, compact, topology, include_properties
    )
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(vector_data, str):
            base = os.path.splitext(os.path.basename(vector_data))[0]
            output_name = f"{base}2quadkey"
        else:
            output_name = "quadkey"
    return convert_to_output_format(result, output_format, output_name)

vector2quadkey_cli()

Command-line interface for vector2quadkey conversion.

This function provides a command-line interface for converting vector data to Quadkey grid cells. It supports various input formats and output formats, with options for resolution control, spatial predicates, compact mode, and topology preservation.

Usage

python vector2quadkey.py -i input.geojson -r 10 -f geojson python vector2quadkey.py -i input.shp -r 8 -p intersect -c -t

Source code in vgrid/conversion/vector2dggs/vector2quadkey.py
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
def vector2quadkey_cli():
    """
    Command-line interface for vector2quadkey conversion.

    This function provides a command-line interface for converting vector data to Quadkey grid cells.
    It supports various input formats and output formats, with options for resolution control,
    spatial predicates, compact mode, and topology preservation.

    Usage:
        python vector2quadkey.py -i input.geojson -r 10 -f geojson
        python vector2quadkey.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to Quadkey grid cells"
    )
    parser.add_argument("-i", "--input", help="Input file path, URL")

    parser.add_argument(
        "-r",
        "--resolution",
        type=int,
        choices=range(min_res, max_res + 1),
        help=f"Quadkey resolution [{min_res}..{max_res}]. Required when topology=False, auto-calculated when topology=True",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate: intersect, within, centroid_within, largest_overlap for polygons",
    )
    parser.add_argument(
        "-c",
        "--compact",
        action="store_true",
        help="Enable Quadkey compact mode for polygons",
    )
    parser.add_argument(
        "-t", "--topology", action="store_true", help="Enable topology preserving mode"
    )
    parser.add_argument(
        "-np",
        "-no-props",
        dest="include_properties",
        action="store_false",
        help="Do not include original feature properties.",
    )
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        choices=OUTPUT_FORMATS,
        default="gpd",
        help="Output format (default: gpd).",
    )
    args = parser.parse_args()

    try:
        result = vector2quadkey(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            compact=args.compact,
            topology=args.topology,
            output_format=args.output_format,
            include_properties=args.include_properties,
        )
        if args.output_format in STRUCTURED_FORMATS:
            print(result)
        # For file outputs, the utility prints the saved path
    except Exception as e:
        print(f"Error: {str(e)}", file=sys.stderr)
        sys.exit(1)