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
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
415
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 = max_res
        # 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,
                )
            )
    if not h3_rows:
        return _empty_h3_gdf()
    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
 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
137
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
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
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)
        bbox_cells = h3.geo_to_cells(bbox, resolution)
        if not bbox_cells:
            continue

        # First collect cells that pass the predicate check
        filtered_cells = []
        for bbox_cell in bbox_cells:
            cell_polygon = h32geo(bbox_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_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 each polyline to an H3 path using all vertices.

For each LineString or MultiLineString, this function: - Iterates over every consecutive vertex pair - Converts each pair to H3 cells - Uses h3.grid_path_cells(start_cell, end_cell) per segment - Merges segment paths into a single ordered, de-duplicated list of cells - Converts those cells back to geometries and appends them to h3_rows

Source code in vgrid/conversion/vector2dggs/vector2h3.py
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
def polyline2h3(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    fix_antimeridian=None,
):
    """
    Convert each polyline to an H3 path using all vertices.

    For each LineString or MultiLineString, this function:
    - Iterates over every consecutive vertex pair
    - Converts each pair to H3 cells
    - Uses h3.grid_path_cells(start_cell, end_cell) per segment
    - Merges segment paths into a single ordered, de-duplicated list of cells
    - Converts those cells back to geometries and appends them to h3_rows
    """

    h3_rows = []

    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        coords = list(polyline.coords)
        if len(coords) < 2:
            continue

        ordered_cells = []
        for i in range(len(coords) - 1):
            start_x, start_y = coords[i]
            end_x, end_y = coords[i + 1]

            start_cell = h3.latlng_to_cell(start_y, start_x, resolution)
            end_cell = h3.latlng_to_cell(end_y, end_x, resolution)

            try:
                segment_cells = h3.grid_path_cells(start_cell, end_cell)
            except Exception:
                segment_cells = []

            for cell_id in segment_cells:
                # Preserve each segment path exactly; only drop immediate duplicates
                # at segment boundaries.
                if ordered_cells and ordered_cells[-1] == cell_id:
                    continue
                ordered_cells.append(cell_id)

        for cell_id in ordered_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

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
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
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
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
551
552
553
554
555
556
557
558
559
560
561
562
563
564
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
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 = max_res
        # 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)
                    cell_diameter = avg_edge_length *2  #math.sqrt(2) # to bypass the big aperture
                    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
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
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
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
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
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
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
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 = max_res
        # 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)
                    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,
                )
            )
            # polygon2a5_new only supports predicate "centroid_within"
    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
 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
129
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 = 5   
        if cell_resolution == 1:
            num_edges = 3   
        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.

Discovery phase: pick a seed cell from the polygon's representative point, then BFS outward (grid disk radius 1) while cells intersect the polygon. Candidate cells are finally filtered by predicate against the input polygon.

Source code in vgrid/conversion/vector2dggs/vector2a5.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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
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.

    Discovery phase: pick a seed cell from the polygon's representative point,
    then BFS outward (grid disk radius 1) while cells intersect the polygon.
    Candidate cells are finally filtered by `predicate` against the input polygon.
    """
    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:
        if polygon is None or polygon.is_empty:
            continue

        rep_pt = polygon.representative_point()
        seed_cell_id = a5.lonlat_to_cell((rep_pt.x, rep_pt.y), resolution)
        seed_cell_resolution = a5.get_resolution(seed_cell_id)
        seed_cell_polygon = a52geo_u64(
            seed_cell_id,
            options=options,
            split_antimeridian=split_antimeridian,
        )
        if seed_cell_polygon.contains(polygon):
            num_edges = 5
            if seed_cell_resolution == 1:
                num_edges = 3   
            row = geodesic_dggs_to_geoseries(
                "a5", seed_cell_id, seed_cell_resolution, seed_cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            a5_rows.append(row)
            return a5_rows
        else:
            intersecting_cells = {}  # {cell_u64: cell_polygon}
            covered_cells = set()
            queue = deque([seed_cell_id])

            while queue:
                current_cell_id = queue.popleft()
                if current_cell_id in covered_cells:
                    continue
                covered_cells.add(current_cell_id)

                cell_polygon = a52geo_u64(
                    current_cell_id,
                    options=options,
                    split_antimeridian=split_antimeridian,
                )
                if cell_polygon is None or cell_polygon.is_empty:
                    continue

                if cell_polygon.intersects(polygon):
                    intersecting_cells[current_cell_id] = cell_polygon
                    neighbors = a5.uncompact(
                        a5.grid_disk_vertex(current_cell_id, 1), resolution
                    )
                    for neighbor_id in neighbors:
                        if neighbor_id not in covered_cells:
                            queue.append(neighbor_id)

            for cell_id, cell_polygon in tqdm(
                intersecting_cells.items(), desc="Generating A5 cells", unit=" cells"
            ):
                if check_predicate(cell_polygon, polygon, predicate):
                    cell_hex = a5.u64_to_hex(cell_id)
                    cell_resolution = a5.get_resolution(cell_id)
                    num_edges = 5
                    if cell_resolution == 1:
                        num_edges = 3   
                    row = geodesic_dggs_to_geoseries(
                        "a5", cell_hex, cell_resolution, cell_polygon, num_edges
                    )
                    if include_properties and feature_properties:
                        row.update(feature_properties)
                    a5_rows.append(row)

            # Apply compact mode if enabled
            if compact and a5_rows:
                temp_gdf = gpd.GeoDataFrame(a5_rows, geometry="geometry", crs="EPSG:4326")
                compacted_gdf = a5compact(temp_gdf, a5_hex="a5", output_format="gpd")
                if compacted_gdf is not None:
                    a5_rows = compacted_gdf.to_dict("records")

    return a5_rows      

polygon2a5_new(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.

Uses a5.polygon_to_cells (dense boundary sampling + flood fill, with center-point containment) to discover all A5 cells covering each ring, then applies the requested spatial predicate against each cell polygon, with optional compaction at the end.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

A5 resolution [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

Returns:

Name Type Description
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

Source code in vgrid/conversion/vector2dggs/vector2a5.py
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
def polygon2a5_new(
    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.

    Uses ``a5.polygon_to_cells`` (dense boundary sampling + flood fill, with
    center-point containment) to discover all A5 cells covering each ring,
    then applies the requested spatial predicate against each cell polygon,
    with optional compaction at the end.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): A5 resolution [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 == "Polygon":
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        if polygon is None or polygon.is_empty:
            continue

        # a5.polygon_to_cells expects an unclosed [(lon, lat), ...] ring
        ring = [(lon, lat) for lon, lat in polygon.exterior.coords[:-1]]
        if len(ring) < 3:
            continue

        try:
            compacted_cells = a5.polygon_to_cells(ring, resolution)
        except Exception:
            compacted_cells = []
        if not compacted_cells:
            continue

        # Expand to the target resolution so each cell can be predicate-filtered
        try:
            candidate_cells = a5.uncompact(compacted_cells, resolution)
        except Exception:
            candidate_cells = list(compacted_cells)

        # First collect cells that pass the predicate check
        filtered_cells = []
        for cell_id in candidate_cells:
            cell_polygon = a52geo_u64(
                cell_id,
                options=options,
                split_antimeridian=split_antimeridian,
            )
            if cell_polygon is None or cell_polygon.is_empty:
                continue
            if not check_predicate(cell_polygon, polygon, predicate):
                continue
            filtered_cells.append(cell_id)

        # Apply compact after predicate check
        if compact and filtered_cells:
            try:
                filtered_cells = a5.compact(filtered_cells)
            except Exception:
                pass

        # Convert filtered/compacted cells to rows
        for cell_id in filtered_cells:
            cell_hex = a5.u64_to_hex(cell_id)
            cell_polygon = a52geo_u64(
                cell_id,
                options=options,
                split_antimeridian=split_antimeridian,
            )
            cell_resolution = a5.get_resolution(cell_id)
            num_edges = 5
            if cell_resolution == 1:
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "a5", cell_hex, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            a5_rows.append(row)

    return a5_rows

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

Convert each polyline to an A5 path along its vertices.

For each LineString or MultiLineString, this function: - Collects vertices as (lon, lat) waypoints - Uses a5.line_string_to_cells(waypoints, resolution) to trace cells along great-circle arcs between consecutive waypoints - Converts those cells back to geometries and appends them to a5_rows

Parameters

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 (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 of dict List of dictionaries representing A5 cells along the polyline(s).

Examples

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
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
def polyline2a5(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
    options=None,
    split_antimeridian=False,
):
    """
    Convert each polyline to an A5 path along its vertices.

    For each LineString or MultiLineString, this function:
    - Collects vertices as (lon, lat) waypoints
    - Uses a5.line_string_to_cells(waypoints, resolution) to trace cells
      along great-circle arcs between consecutive waypoints
    - Converts those cells back to geometries and appends them to a5_rows

    Parameters
    ----------
    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 (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 of dict
        List of dictionaries representing A5 cells along the polyline(s).

    Examples
    --------
    >>> 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 == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        coords = list(polyline.coords)
        if len(coords) < 2:
            continue

        waypoints = [(lon, lat) for lon, lat in coords]

        try:
            ordered_cell_ids = a5.line_string_to_cells(waypoints, resolution)
        except Exception:
            ordered_cell_ids = []

        for cell_id in ordered_cell_ids:
            cell_hex = a5.u64_to_hex(cell_id)
            cell_polygon = a52geo(
                cell_hex, options, split_antimeridian=split_antimeridian
            )
            cell_resolution = a5.get_resolution(cell_id)
            num_edges = 5
            if cell_resolution == 1:
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "a5", cell_hex, cell_resolution, cell_polygon, num_edges
            )
            if include_properties and feature_properties:
                row.update(feature_properties)
            a5_rows.append(row)

    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
572
573
574
575
576
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
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
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
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
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).",
    )
    parser.add_argument(
        "-split",
        "--split_antimeridian",
        action="store_true",
        default=False,
        help="Apply antimeridian fixing to the resulting polygons",
    )
    parser.add_argument(
        "-options",
        "--options",
        type=str,
        default=None,
        help="JSON string of options to pass to a52geo. "
             "Example: '{\"segments\": 1000}'",
    )

    args = parser.parse_args()

    # Parse options JSON if provided
    options = None
    if args.options:
        try:
            options = json.loads(args.options)
        except json.JSONDecodeError as e:
            print(f"Error: Invalid JSON in options: {str(e)}", file=sys.stderr)
            sys.exit(1)

    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=options,
            split_antimeridian=args.split_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 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
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
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
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 = max_res
        # 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)
                    cell_diameter = avg_edge_length * math.sqrt(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,
                )
            )
            #   void using native rhp polyfill because it only supports "within" predicate
    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
 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
129
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
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
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
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:
        rep_pt = polygon.representative_point()
        seed_point = (rep_pt.x, rep_pt.y)
        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(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(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               

                    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 using linetrace.

Cells are those touched by the line at the requested resolution (see rhp_wrappers.linetrace).

Parameters

feature : shapely.geometry.LineString or shapely.geometry.MultiLineString Polyline geometry to convert. resolution : int rHEALPix resolution level [0..15]. feature_properties : dict, optional Properties to include in output features. predicate : str, optional Not used for polylines (kept for API compatibility). compact : bool, optional Not used for polylines (kept for API compatibility). topology : bool, optional Handled by geodataframe2rhealpix. include_properties : bool, optional Whether to include properties in output. fix_antimeridian : str, optional Antimeridian fixing method.

Returns

list of dict rHEALPix cell rows along the polyline.

Examples

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
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
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 using ``linetrace``.

    Cells are those touched by the line at the requested resolution
    (see ``rhp_wrappers.linetrace``).

    Parameters
    ----------
    feature : shapely.geometry.LineString or shapely.geometry.MultiLineString
        Polyline geometry to convert.
    resolution : int
        rHEALPix resolution level [0..15].
    feature_properties : dict, optional
        Properties to include in output features.
    predicate : str, optional
        Not used for polylines (kept for API compatibility).
    compact : bool, optional
        Not used for polylines (kept for API compatibility).
    topology : bool, optional
        Handled by geodataframe2rhealpix.
    include_properties : bool, optional
        Whether to include properties in output.
    fix_antimeridian : str, optional
        Antimeridian fixing method.

    Returns
    -------
    list of dict
        rHEALPix cell rows along the polyline.

    Examples
    --------
    >>> 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
    """
    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    seen_ids = set()
    ordered_cell_ids = []
    for polyline in polylines:
        if polyline.is_empty or polyline.length == 0:
            continue
        traced = linetrace(polyline, resolution, plane=False, dggs=rhealpix_dggs)
        if not traced:
            continue
        for cell_id in traced:
            if cell_id in seen_ids:
                continue
            seen_ids.add(cell_id)
            ordered_cell_ids.append(cell_id)

    return _rhealpix_rows_from_cell_ids(
        ordered_cell_ids,
        resolution,
        feature_properties,
        include_properties,
        fix_antimeridian,
    )

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
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
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
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
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
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 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=None, 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 when topology=False, auto-calculated from point spacing when topology=True.

None
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 to ensure disjoint points have disjoint DGGAL cells

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
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
394
395
396
397
def geodataframe2dggal(
    dggs_type: str,
    gdf,
    resolution=None,
    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, optional): Integer resolution. Required when topology=False,
            auto-calculated from point spacing when topology=True.
        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 to ensure disjoint
            points have disjoint DGGAL cells
        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
    """
    dggs_min_res = int(DGGAL_TYPES[dggs_type]["min_res"])
    dggs_max_res = int(DGGAL_TYPES[dggs_type]["max_res"])

    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = dggs_max_res
        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)
            shortest_distance = shortest_point_distance(all_points)

            if shortest_distance > 0:
                for res in range(dggs_min_res, dggs_max_res + 1):
                    cell_diameter = _dggal_cell_diameter(dggs_type, res)
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        resolution = estimated_resolution

    resolution = validate_dggal_resolution(dggs_type, resolution)

    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=topology,  # Topology already processed above
                    include_properties=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 (handled by geodataframe2dggal). 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
 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
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 (handled by ``geodataframe2dggal``).
    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=None, 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 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 DGGAL compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint points have disjoint DGGAL cells

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
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
def vector2dggal(
    dggs_type: str,
    vector_data,
    resolution=None,
    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, optional): Integer resolution. 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 DGGAL compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint points
            have disjoint DGGAL cells
        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}")
    """
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    if resolution is not None:
        resolution = validate_dggal_resolution(dggs_type, resolution)

    gdf_input = process_input_data_vector(vector_data, **kwargs)
    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:
        res_label = resolution
        if res_label is None and not result.empty and "resolution" in result.columns:
            res_label = result["resolution"].iloc[0]
        output_name = f"{dggs_type}_grid_{res_label}"
    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, options=None)

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. 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 compact mode for polygons and lines

False
topology bool

Enable topology preserving mode to ensure disjoint points have disjoint DGGRID cells

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
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
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
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,
    options=None,
):
    """
    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, optional): Integer resolution. 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 compact mode for polygons and lines
        topology (bool, optional): Enable topology preserving mode to ensure disjoint
            points have disjoint DGGRID cells
        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
    """
    dggs_type = validate_dggrid_type(dggs_type)

    if topology:
        min_res = int(DGGRID_TYPES[dggs_type]["min_res"])
        max_res = int(DGGRID_TYPES[dggs_type]["max_res"])
        estimated_resolution = max_res
        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 len(points_list) >= 2:
            points_gdf = gpd.GeoDataFrame(
                geometry=points_list, crs=gdf.crs or "EPSG:4326"
            )
            shortest_distance = _shortest_point_distance_sjoin(points_gdf)

            if shortest_distance > 0:
                grid_stats = dggridstats(dggrid_instance, dggs_type, unit="m")
                for res in range(min_res, max_res + 1):
                    try:
                        cell_diameter = _dggrid_cell_diameter(
                            dggrid_instance,
                            dggs_type,
                            res,
                            grid_stats=grid_stats,
                        )
                    except ValueError:
                        continue
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break

        if estimated_resolution is None:
            estimated_resolution = int(DGGRID_TYPES[dggs_type]["default_res"])
        resolution = estimated_resolution

    resolution = validate_dggrid_resolution(dggs_type, resolution)

    # 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=predicate,
                compact=compact,
                topology=False,  # topology already processed above
                include_properties=include_properties,
                feature_properties=props,
                output_address_type=output_address_type,
                split_antimeridian=split_antimeridian,
                aggregate=aggregate,
                options=options,
            )
            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,
                options=options,
            )
            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,
                options=options,
            )
            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, options=None)

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 (handled by geodataframe2dggrid). 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
 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
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
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,
    options=None,
):
    """
    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 (handled by ``geodataframe2dggrid``).
    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)

    if feature.geom_type == "Point":
        points = [feature]
    elif feature.geom_type == "MultiPoint":
        points = list(feature.geoms)
    else:
        return gpd.GeoDataFrame(columns=["geometry"], crs="EPSG:4326")

    seqnums = []
    for point in points:
        seqnum = latlon2dggrid(
            dggrid_instance,
            dggs_type,
            float(point.y),
            float(point.x),
            resolution,
            output_address_type,
        )
        seqnums.append(seqnum)

    # Build polygons from SEQNUM ids
    gdf = dggrid2geo(
        dggrid_instance,
        dggs_type,
        seqnums,
        resolution,
        output_address_type,
        split_antimeridian=split_antimeridian,
        aggregate=aggregate,
        options=options,
    )
    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, options=None)

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
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
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,
    options=None,
):
    """
    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(*polygon.bounds)

        # Generate grid cells for the bounding box
        kwargs = {
            "split_dateline": split_antimeridian,
            "output_address_type": output_address_type,
        }
        if options:
            kwargs.update(options)
        dggrid_gdf = dggrid_instance.grid_cell_polygons_for_extent(
            dggs_type,
            resolution,
            clip_geom=bounding_box,
            **kwargs,
        )

        # 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, polygon, predicate)
                )
            ]
        else:
            dggrid_gdf = dggrid_gdf[dggrid_gdf.intersects(polygon)]
        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="global_id", as_index=False)
    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, options=None)

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
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
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,
    options=None,
):
    """
    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
        kwargs = {
            "split_dateline": split_antimeridian,
            "output_address_type": output_address_type,
        }
        if options:
            kwargs.update(options)
        dggrid_gdf = dggrid_instance.grid_cell_polygons_for_extent(
            dggs_type,
            resolution,
            clip_geom=bounding_box,
            **kwargs,
        )

        # 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="global_id", as_index=False)
    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, options=None, **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
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
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,
    options=None,
    **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)
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")
    if resolution is not None:
        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,
        options=options,
    )

    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 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
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 = max_res
        # 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)
                    cell_diameter = (2 *avg_edge_length * math.sqrt(3)) / 3
                    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
 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 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
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
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
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
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 = max_res
        # 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
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
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 = max_res
        # 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 * math.sqrt(2)
                        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
 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 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
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
264
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
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
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
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
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
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
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 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
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 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 = max_res
        # 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 = (2 *avg_edge_length * math.sqrt(3)) / 3
                    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
 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 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
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
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:
                for i, facet in enumerate(QTM_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
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
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:
                for i, facet in enumerate(QTM_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
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
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
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
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
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
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 = max_res
        # 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 olc_resolutions:  # OLC valid resolutions
                    _, avg_edge_length, _, _ = olc_metrics(res)
                    cell_diameter = avg_edge_length * math.sqrt(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
 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
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
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
309
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
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
204
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
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
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
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
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=olc_resolutions,
        help=f"OLC resolution {olc_resolutions}. 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
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
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 = max_res
        # 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:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = geohash_metrics(res, unit="m")
                    cell_diameter = avg_edge_len * sqrt(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
 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
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
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
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
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
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
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
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
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
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 GEOREF Module

Convert vector geometries to World Geographic Reference System (GEOREF) cells, mirroring :mod:vector2geohash but using :func:~vgrid.conversion.latlon2dggs.latlon2georef, :func:~vgrid.conversion.dggs2geo.georef2geo.georef2geo, and the same bbox lattice as :func:~vgrid.generator.georefgrid.georef_grid (numpy.arange over bounds at :data:~vgrid.utils.constants.GEOREF_RESOLUTION_DEGREES for the target resolution).

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

Convert a GeoDataFrame to GEOREF cells.

Source code in vgrid/conversion/vector2dggs/vector2georef.py
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
def geodataframe2georef(
    gdf,
    resolution=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert a GeoDataFrame to GEOREF cells."""
    if topology:
        estimated_resolution = max_res
        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)
            shortest_distance = shortest_point_distance(all_points)
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = georef_metrics(res, unit="m")
                    cell_diameter_m = avg_edge_len * sqrt(2) 
                    if cell_diameter_m < shortest_distance:
                        estimated_resolution = res
                        break
        resolution = estimated_resolution

    resolution = validate_georef_resolution(resolution)

    geom_col = gdf.geometry.name
    georef_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 geom_col in props:
            del props[geom_col]

        if not include_properties:
            props = {}

        if geom.geom_type in ("Point", "MultiPoint"):
            georef_rows.extend(
                point2georef(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("LineString", "MultiLineString"):
            georef_rows.extend(
                polyline2georef(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            georef_rows.extend(
                polygon2georef(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
    if not georef_rows:
        return gpd.GeoDataFrame(
            {"geometry": gpd.GeoSeries([], crs="EPSG:4326")},
            geometry="geometry",
            crs="EPSG:4326",
        )
    return gpd.GeoDataFrame(georef_rows, geometry="geometry", crs="EPSG:4326")

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

Convert point or multipoint geometries to GEOREF cells at resolution.

Source code in vgrid/conversion/vector2dggs/vector2georef.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
def point2georef(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert point or multipoint geometries to GEOREF cells at ``resolution``."""
    rows = []
    if feature.geom_type == "Point":
        points = [feature]
    elif feature.geom_type == "MultiPoint":
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        georef_id = latlon2georef(point.y, point.x, resolution)       
        cell_polygon = georef2geo(georef_id)        
        row = graticule_dggs_to_geoseries(
            "georef", georef_id, resolution, cell_polygon
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        rows.append(row)
    return rows

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

Collect GEOREF cells at resolution using predicate against the polygon.

Source code in vgrid/conversion/vector2dggs/vector2georef.py
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
142
143
def polygon2georef(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect GEOREF cells at ``resolution`` using ``predicate`` against the polygon."""
    rows = []
    if feature.geom_type == "Polygon":
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        polygons = list(feature.geoms)
    else:
        return []

    for polygon in polygons:
        min_lon, min_lat, max_lon, max_lat = polygon.bounds
        resolution_degrees = GEOREF_RESOLUTION_DEGREES.get(resolution)
        longitudes = np.arange(min_lon, max_lon, resolution_degrees)
        latitudes = np.arange(min_lat, max_lat, resolution_degrees)
        for lon in longitudes:
            for lat in latitudes:
                georef_id = latlon2georef(lat, lon, resolution)                    
                cell_polygon = georef2geo(georef_id)    
                if cell_polygon is not None and check_predicate(cell_polygon, polygon, predicate):
                    row = graticule_dggs_to_geoseries(
                        "georef", georef_id, resolution, cell_polygon
                    )
                    if include_properties and feature_properties:
                        row.update(feature_properties)
                    rows.append(row)                
    return rows

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

Collect GEOREF cells at resolution that intersect the line geometry.

Source code in vgrid/conversion/vector2dggs/vector2georef.py
 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
def polyline2georef(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect GEOREF cells at ``resolution`` that intersect the line geometry."""
    rows = []
    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    for polyline in polylines:
        min_lon, min_lat, max_lon, max_lat = polyline.bounds
        resolution_degrees = GEOREF_RESOLUTION_DEGREES.get(resolution)
        longitudes = np.arange(min_lon, max_lon, resolution_degrees)
        latitudes = np.arange(min_lat, max_lat, resolution_degrees)     
        for lon in longitudes:
            for lat in latitudes:
                georef_id = latlon2georef(lat, lon, resolution)                    
                cell_polygon = georef2geo(georef_id)    
                if cell_polygon is not None and cell_polygon.intersects(polyline):
                    row = graticule_dggs_to_geoseries(
                        "georef", georef_id, resolution, cell_polygon
                    )
                    if include_properties and feature_properties:
                        row.update(feature_properties)
                    rows.append(row)
    return rows

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

Convert vector data to GEOREF grid cells.

Parameters mirror :func:vector2geohash except GEOREF has no compact mode. resolution is in [0..10] (see :data:~vgrid.utils.constants.DGGS_TYPES).

Source code in vgrid/conversion/vector2dggs/vector2georef.py
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
def vector2georef(
    vector_data,
    resolution=None,
    predicate=None,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to GEOREF grid cells.

    Parameters mirror :func:`vector2geohash` except GEOREF has no compact mode.
    ``resolution`` is in ``[0..10]`` (see :data:`~vgrid.utils.constants.DGGS_TYPES`).
    """
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    if resolution is not None:
        resolution = validate_georef_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2georef(
        gdf, resolution, predicate, 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}2georef"
        else:
            output_name = "georef"
    return convert_to_output_format(result, output_format, output_name)

vector2georef_cli()

CLI for :func:vector2georef.

Source code in vgrid/conversion/vector2dggs/vector2georef.py
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
def vector2georef_cli():
    """CLI for :func:`vector2georef`."""
    parser = argparse.ArgumentParser(
        description="Convert vector data to GEOREF 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"GEOREF resolution [{min_res}..{max_res}]. Required when topology=False.",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate for polygons",
    )
    parser.add_argument(
        "-t",
        "--topology",
        action="store_true",
        help="Topology-preserving resolution for disjoint points",
    )
    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 = vector2georef(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            topology=args.topology,
            output_format=args.output_format,
            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 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
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
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 = max_res
        # 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:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = tilecode_metrics(res, unit="m")
                    cell_diameter = avg_edge_len * sqrt(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
 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 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
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
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
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
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
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
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
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
540
541
542
543
544
545
546
547
548
549
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
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
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 = max_res
        # 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:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = quadkey_metrics(res, unit="m")
                    cell_diameter = avg_edge_len * sqrt(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
 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
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
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
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
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
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
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
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
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
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)

Vector to Maidenhead Module

Convert vector geometries to Maidenhead locator cells using :func:~vgrid.conversion.latlon2dggs.latlon2maidenhead, :func:~vgrid.conversion.dggs2geo.maidenhead2geo.maidenhead2geo, and for lines and polygons the same bbox-relative grid walk as :func:~vgrid.generator.maidenheadgrid.maidenhead_grid_within_bbox (indexed from (-90, -180) with resolution-dependent cell sizes).

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

Convert a GeoDataFrame to Maidenhead cells.

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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
def geodataframe2maidenhead(
    gdf,
    resolution=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert a GeoDataFrame to Maidenhead cells."""
    if topology:
        estimated_resolution = max_res
        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)
            shortest_distance = shortest_point_distance(all_points)
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = maidenhead_metrics(res, unit="m")
                    cell_diameter_m = avg_edge_len * math.sqrt(2)
                    if cell_diameter_m < shortest_distance:
                        estimated_resolution = res
                        break
        resolution = estimated_resolution

    resolution = validate_maidenhead_resolution(resolution)

    geom_col = gdf.geometry.name
    maidenhead_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 geom_col in props:
            del props[geom_col]

        if not include_properties:
            props = {}

        if geom.geom_type in ("Point", "MultiPoint"):
            maidenhead_rows.extend(
                point2maidenhead(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("LineString", "MultiLineString"):
            maidenhead_rows.extend(
                polyline2maidenhead(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            maidenhead_rows.extend(
                polygon2maidenhead(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
    if not maidenhead_rows:
        return gpd.GeoDataFrame(
            {"geometry": gpd.GeoSeries([], crs="EPSG:4326")},
            geometry="geometry",
            crs="EPSG:4326",
        )
    return gpd.GeoDataFrame(maidenhead_rows, geometry="geometry", crs="EPSG:4326")

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

Convert point or multipoint geometries to Maidenhead cells at resolution.

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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 point2maidenhead(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert point or multipoint geometries to Maidenhead cells at ``resolution``."""
    rows = []
    if feature.geom_type == "Point":
        points = [feature]
    elif feature.geom_type == "MultiPoint":
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        maidenhead_id = latlon2maidenhead(point.y, point.x, resolution)
        cell_polygon  = maidenhead2geo(maidenhead_id)
        row = graticule_dggs_to_geoseries(
            "maidenhead", maidenhead_id, resolution, cell_polygon
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        rows.append(row)
    return rows

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

Collect cells from the bbox Maidenhead lattice, filtered by predicate.

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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
def polygon2maidenhead(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect cells from the bbox Maidenhead lattice, filtered by ``predicate``."""
    rows = []
    if feature.geom_type == "Polygon":
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        polygons = list(feature.geoms)
    else:
        return []

    seen = set()
    for polygon in polygons:
        bbox = list(polygon.bounds)
        for record in _maidenhead_cell_records_for_bbox(resolution, bbox):
            mid = record["maidenhead"]
            if mid in seen:
                continue
            cell_geom = record["geometry"]
            if not check_predicate(cell_geom, polygon, predicate):
                continue
            seen.add(mid)
            row = dict(record)
            if include_properties and feature_properties:
                row = {**row, **feature_properties}
            rows.append(row)
    return rows

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

Collect cells from the bbox Maidenhead lattice that intersect the line.

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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
def polyline2maidenhead(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect cells from the bbox Maidenhead lattice that intersect the line."""
    rows = []
    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    seen = set()
    for polyline in polylines:
        bbox = list(polyline.bounds)
        for record in _maidenhead_cell_records_for_bbox(resolution, bbox):
            mid = record["maidenhead"]
            if mid in seen:
                continue
            cell_geom = record["geometry"]
            if not cell_geom.intersects(polyline):
                continue
            seen.add(mid)
            row = dict(record)
            if include_properties and feature_properties:
                row = {**row, **feature_properties}
            rows.append(row)
    return rows

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

Convert vector data to Maidenhead grid cells.

resolution is 1..4. Lines and polygons walk the same bbox-relative Maidenhead lattice as maidenhead_grid_within_bbox (implemented locally).

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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
def vector2maidenhead(
    vector_data,
    resolution=None,
    predicate=None,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to Maidenhead grid cells.

    ``resolution`` is ``1..4``. Lines and polygons walk the same bbox-relative
    Maidenhead lattice as ``maidenhead_grid_within_bbox`` (implemented locally).
    """
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    if resolution is not None:
        resolution = validate_maidenhead_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2maidenhead(
        gdf, resolution, predicate, 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}2maidenhead"
        else:
            output_name = "maidenhead"
    return convert_to_output_format(result, output_format, output_name)

vector2maidenhead_cli()

CLI for :func:vector2maidenhead.

Source code in vgrid/conversion/vector2dggs/vector2maidenhead.py
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
def vector2maidenhead_cli():
    """CLI for :func:`vector2maidenhead`."""
    parser = argparse.ArgumentParser(
        description="Convert vector data to Maidenhead 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"Maidenhead resolution [{min_res}..{max_res}]. Required when topology=False.",
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate for polygons",
    )
    parser.add_argument(
        "-t",
        "--topology",
        action="store_true",
        help="Topology-preserving resolution for disjoint points",
    )
    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 = vector2maidenhead(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            topology=args.topology,
            output_format=args.output_format,
            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 GARS Module

Convert vector geometries to GARS (Global Area Reference System) cells using :func:~vgrid.conversion.latlon2dggs.latlon2gars, :func:~vgrid.conversion.dggs2geo.gars2geo.gars2geo, and the same bbox lattice as :func:~vgrid.generator.garsgrid.gars_grid.

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

Convert a GeoDataFrame to GARS cells.

Source code in vgrid/conversion/vector2dggs/vector2gars.py
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
def geodataframe2gars(
    gdf,
    resolution=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert a GeoDataFrame to GARS cells."""
    if topology:
        estimated_resolution = max_res
        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)
            shortest_distance = shortest_point_distance(all_points)
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = gars_metrics(res, unit="m")
                    cell_diameter_m = avg_edge_len * sqrt(2)
                    if cell_diameter_m < shortest_distance:
                        estimated_resolution = res
                        break
        resolution = estimated_resolution

    resolution = validate_gars_resolution(resolution)

    geom_col = gdf.geometry.name
    gars_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 geom_col in props:
            del props[geom_col]

        if not include_properties:
            props = {}

        if geom.geom_type in ("Point", "MultiPoint"):
            gars_rows.extend(
                point2gars(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("LineString", "MultiLineString"):
            gars_rows.extend(
                polyline2gars(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            gars_rows.extend(
                polygon2gars(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    topology=topology,
                    include_properties=include_properties,
                )
            )
    if not gars_rows:
        return gpd.GeoDataFrame(
            {"geometry": gpd.GeoSeries([], crs="EPSG:4326")},
            geometry="geometry",
            crs="EPSG:4326",
        )
    return gpd.GeoDataFrame(gars_rows, geometry="geometry", crs="EPSG:4326")

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

Convert point or multipoint geometries to GARS cells at resolution.

Source code in vgrid/conversion/vector2dggs/vector2gars.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
def point2gars(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Convert point or multipoint geometries to GARS cells at ``resolution``."""
    rows = []
    if feature.geom_type == "Point":
        points = [feature]
    elif feature.geom_type == "MultiPoint":
        points = list(feature.geoms)
    else:
        return []

    for point in points:
        gars_id = latlon2gars(point.y, point.x, resolution)
        cell_polygon = gars2geo(gars_id)       
        row = graticule_dggs_to_geoseries(
            "gars", gars_id, resolution, cell_polygon
        )
        if include_properties and feature_properties:
            row.update(feature_properties)
        rows.append(row)
    return rows

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

Collect GARS cells at resolution using predicate against the polygon.

Source code in vgrid/conversion/vector2dggs/vector2gars.py
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
142
143
144
145
146
147
148
149
150
151
def polygon2gars(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect GARS cells at ``resolution`` using ``predicate`` against the polygon."""
    rows = []
    if feature.geom_type == "Polygon":
        polygons = [feature]
    elif feature.geom_type == "MultiPolygon":
        polygons = list(feature.geoms)
    else:
        return []

    resolution_minutes = GARS_RESOLUTION_MINUTES.get(resolution)
    resolution_degrees = resolution_minutes / 60.0

    for polygon in polygons:
        min_lon, min_lat, max_lon, max_lat = polygon.bounds
        longitudes = np.arange(min_lon, max_lon, resolution_degrees)
        latitudes = np.arange(min_lat, max_lat, resolution_degrees)
        seen = set()
        for lon in longitudes:
            for lat in latitudes:
                gars_id = latlon2gars(lat, lon, resolution)              
                cell_polygon = gars2geo(gars_id)
                if not check_predicate(
                    cell_polygon, polygon, predicate
                ):
                    continue
                seen.add(gars_id)
                row = graticule_dggs_to_geoseries(
                    "gars", gars_id, resolution, cell_polygon
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                rows.append(row)
    return rows

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

Collect GARS cells at resolution that intersect the line geometry.

Source code in vgrid/conversion/vector2dggs/vector2gars.py
 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
def polyline2gars(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    topology=False,
    include_properties=True,
):
    """Collect GARS cells at ``resolution`` that intersect the line geometry."""
    rows = []
    if feature.geom_type == "LineString":
        polylines = [feature]
    elif feature.geom_type == "MultiLineString":
        polylines = list(feature.geoms)
    else:
        return []

    resolution_minutes = GARS_RESOLUTION_MINUTES.get(resolution)
    resolution_degrees = resolution_minutes / 60.0

    for polyline in polylines:
        min_lon, min_lat, max_lon, max_lat = polyline.bounds
        longitudes = np.arange(min_lon, max_lon, resolution_degrees)
        latitudes = np.arange(min_lat, max_lat, resolution_degrees)     
        for lon in longitudes:
            for lat in latitudes:              
                gars_id = latlon2gars(lat, lon, resolution)              
                cell_polygon = gars2geo(gars_id)
                if not cell_polygon.intersects(polyline):
                    continue              
                row = graticule_dggs_to_geoseries(
                    "gars", gars_id, resolution, cell_polygon
                )
                if include_properties and feature_properties:
                    row.update(feature_properties)
                rows.append(row)
    return rows

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

Convert vector data to GARS grid cells.

resolution is 1..4 (30′, 15′, 5′, 1′ cells). There is no compact mode.

Source code in vgrid/conversion/vector2dggs/vector2gars.py
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
def vector2gars(
    vector_data,
    resolution=None,
    predicate=None,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to GARS grid cells.

    ``resolution`` is ``1..4`` (30′, 15′, 5′, 1′ cells). There is no compact mode.
    """
    if not topology and resolution is None:
        raise ValueError("resolution parameter is required when topology=False")

    if resolution is not None:
        resolution = validate_gars_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2gars(
        gdf, resolution, predicate, 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}2gars"
        else:
            output_name = "gars"
    return convert_to_output_format(result, output_format, output_name)

vector2gars_cli()

CLI for :func:vector2gars.

Source code in vgrid/conversion/vector2dggs/vector2gars.py
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
def vector2gars_cli():
    """CLI for :func:`vector2gars`."""
    parser = argparse.ArgumentParser(
        description="Convert vector data to GARS 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"GARS resolution [{min_res}..{max_res}] "
            "(1=30min, 2=15min, 3=5min, 4=1min). Required when topology=False."
        ),
    )
    parser.add_argument(
        "-p",
        "--predicate",
        choices=["intersect", "within", "centroid_within", "largest_overlap"],
        help="Spatial predicate for polygons",
    )
    parser.add_argument(
        "-t",
        "--topology",
        action="store_true",
        help="Topology-preserving resolution for disjoint points",
    )
    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 = vector2gars(
            vector_data=args.input,
            resolution=args.resolution,
            predicate=args.predicate,
            topology=args.topology,
            output_format=args.output_format,
            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 DIGIPIN Module

This module provides functionality to convert vector geometries to DIGIPIN grid cells with flexible input and output formats. DIGIPIN is a hierarchical geocoding system for India that divides geographic areas into a 4x4 grid recursively.

Key Functions

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

Convert a GeoDataFrame to DIGIPIN grid cells.

Parameters:

Name Type Description Default
gdf GeoDataFrame

GeoDataFrame to convert

required
resolution int

DIGIPIN resolution level [1..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 DIGIPIN compact mode for polygons

False
topology bool

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

False
include_properties bool

Whether to include properties in output

True

Returns:

Type Description

geopandas.GeoDataFrame: GeoDataFrame with DIGIPIN grid cells

Example

import geopandas as gpd from shapely.geometry import Point gdf = gpd.GeoDataFrame({ ... 'name': ['Delhi'], ... 'geometry': [Point(77.2090, 28.6139)] ... }) result = geodataframe2digipin(gdf, 10) len(result) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2digipin.py
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
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def geodataframe2digipin(
    gdf,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a GeoDataFrame to DIGIPIN grid cells.

    Args:
        gdf (geopandas.GeoDataFrame): GeoDataFrame to convert
        resolution (int, optional): DIGIPIN resolution level [1..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 DIGIPIN compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint DIGIPIN cells
        include_properties (bool, optional): Whether to include properties in output

    Returns:
        geopandas.GeoDataFrame: GeoDataFrame with DIGIPIN grid cells

    Example:
        >>> import geopandas as gpd
        >>> from shapely.geometry import Point
        >>> gdf = gpd.GeoDataFrame({
        ...     'name': ['Delhi'],
        ...     'geometry': [Point(77.2090, 28.6139)]
        ... })
        >>> result = geodataframe2digipin(gdf, 10)
        >>> len(result) > 0
        True
    """
    # Process topology for points and multipoints if enabled
    if topology:
        estimated_resolution = max_res
        # 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 DIGIPIN cell size is smaller than shortest distance
            # This ensures disjoint points have disjoint DIGIPIN cells
            if shortest_distance > 0:
                for res in range(min_res, max_res + 1):
                    _, avg_edge_len, _, _ = digipin_metrics(res, unit="m")
                    cell_diameter = avg_edge_len*sqrt(2)
                    if cell_diameter < shortest_distance:
                        estimated_resolution = res
                        break
        resolution = estimated_resolution

    resolution = validate_digipin_resolution(resolution)

    digipin_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":
            digipin_rows.extend(
                point2digipin(
                    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"):
            digipin_rows.extend(
                polyline2digipin(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
        elif geom.geom_type in ("Polygon", "MultiPolygon"):
            digipin_rows.extend(
                polygon2digipin(
                    feature=geom,
                    resolution=resolution,
                    feature_properties=props,
                    predicate=predicate,
                    compact=compact,
                    include_properties=include_properties,
                )
            )
    return gpd.GeoDataFrame(digipin_rows, geometry="geometry", crs="EPSG:4326")

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

Convert a point geometry to DIGIPIN grid cells.

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

Parameters

feature : shapely.geometry.Point or shapely.geometry.MultiPoint Point geometry to convert to DIGIPIN cells. resolution : int DIGIPIN 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 DIGIPIN compact mode (not used for points). topology : bool, optional Enable topology preserving mode (handled by geodataframe2digipin). include_properties : bool, optional Whether to include properties in output.

Returns

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

Examples

from shapely.geometry import Point point = Point(77.2090, 28.6139) # Delhi, India cells = point2digipin(point, 10, {"name": "Delhi"}) len(cells) 1

from shapely.geometry import MultiPoint points = MultiPoint([(77.2090, 28.6139), (72.8777, 19.0760)]) # Delhi, Mumbai cells = point2digipin(points, 8) len(cells) 2

Source code in vgrid/conversion/vector2dggs/vector2digipin.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
117
118
119
120
121
122
def point2digipin(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a point geometry to DIGIPIN grid cells.

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

    Parameters
    ----------
    feature : shapely.geometry.Point or shapely.geometry.MultiPoint
        Point geometry to convert to DIGIPIN cells.
    resolution : int
        DIGIPIN 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 DIGIPIN compact mode (not used for points).
    topology : bool, optional
        Enable topology preserving mode (handled by geodataframe2digipin).
    include_properties : bool, optional
        Whether to include properties in output.

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

    Examples
    --------
    >>> from shapely.geometry import Point
    >>> point = Point(77.2090, 28.6139)  # Delhi, India
    >>> cells = point2digipin(point, 10, {"name": "Delhi"})
    >>> len(cells)
    1

    >>> from shapely.geometry import MultiPoint
    >>> points = MultiPoint([(77.2090, 28.6139), (72.8777, 19.0760)])  # Delhi, Mumbai
    >>> cells = point2digipin(points, 8)
    >>> len(cells)
    2
    """
    digipin_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:
        digipin_id = latlon2digipin(point.y, point.x, resolution)

        # Skip if point is out of bounds
        if digipin_id == "Out of Bound":
            continue

        cell_polygon = digipin2geo(digipin_id)
        if isinstance(cell_polygon, str):
            continue

        digipin_row = graticule_dggs_to_geoseries(
            "digipin", digipin_id, resolution, cell_polygon
        )
        if include_properties and feature_properties:
            digipin_row.update(feature_properties)
        digipin_rows.append(digipin_row)
    return digipin_rows

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

Convert a polygon geometry to DIGIPIN grid cells.

Parameters:

Name Type Description Default
feature Polygon or MultiPolygon

Polygon geometry to convert

required
resolution int

DIGIPIN 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 DIGIPIN compact mode to reduce cell count

False
topology bool

Enable topology preserving mode (handled by geodataframe2digipin)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing DIGIPIN cells based on predicate

Example

from shapely.geometry import Polygon poly = Polygon([(77.1, 28.5), (77.3, 28.5), (77.3, 28.7), (77.1, 28.7)]) # Delhi area cells = polygon2digipin(poly, 10, {"name": "area"}, predicate="intersect") len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2digipin.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
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
def polygon2digipin(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polygon geometry to DIGIPIN grid cells.

    Args:
        feature (shapely.geometry.Polygon or shapely.geometry.MultiPolygon): Polygon geometry to convert
        resolution (int): DIGIPIN 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 DIGIPIN compact mode to reduce cell count
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2digipin)
        include_properties (bool, optional): Whether to include properties in output

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

    Example:
        >>> from shapely.geometry import Polygon
        >>> poly = Polygon([(77.1, 28.5), (77.3, 28.5), (77.3, 28.7), (77.1, 28.7)])  # Delhi area
        >>> cells = polygon2digipin(poly, 10, {"name": "area"}, predicate="intersect")
        >>> len(cells) > 0
        True
    """
    from vgrid.dggs.digipin import BOUNDS

    digipin_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

        # Constrain to DIGIPIN bounds (India region)
        min_lat = max(min_lat, BOUNDS["minLat"])
        min_lon = max(min_lon, BOUNDS["minLon"])
        max_lat = min(max_lat, BOUNDS["maxLat"])
        max_lon = min(max_lon, BOUNDS["maxLon"])

        # Calculate sampling density based on resolution (following digipingrid.py approach)
        # Each level divides the cell by 4 (2x2 grid)
        base_width = 9.0  # degrees at resolution 1
        factor = 0.25 ** (resolution - 1)  # each level divides by 4
        sample_width = base_width * factor

        seen_cells = set()

        # Sample points across the bounding box
        lon = min_lon
        while lon <= max_lon:
            lat = min_lat
            while lat <= max_lat:
                try:
                    # Get DIGIPIN code for this point at the specified resolution
                    digipin_id = latlon2digipin(lat, lon, resolution)

                    if digipin_id == "Out of Bound":
                        lat += sample_width
                        continue

                    if digipin_id in seen_cells:
                        lat += sample_width
                        continue

                    seen_cells.add(digipin_id)

                    # Get the bounds for this DIGIPIN cell
                    cell_polygon = digipin2geo(digipin_id)

                    if isinstance(cell_polygon, str):  # Error like 'Invalid DIGIPIN'
                        lat += sample_width
                        continue
                    # Check spatial predicate
                    if check_predicate(cell_polygon, polygon, predicate):
                        digipin_row = graticule_dggs_to_geoseries(
                            "digipin", digipin_id, resolution, cell_polygon
                        )
                        if include_properties and feature_properties:
                            digipin_row.update(feature_properties)
                        digipin_rows.append(digipin_row)

                except Exception:
                    # Skip cells with errors
                    pass

                lat += sample_width
            lon += sample_width

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

        # Use digipincompact function directly
        compacted_gdf = digipincompact(
            temp_gdf, digipin_id="digipin", output_format="gpd"
        )

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

    return digipin_rows

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

Convert a polyline geometry to DIGIPIN grid cells.

Parameters:

Name Type Description Default
feature LineString or MultiLineString

Polyline geometry to convert

required
resolution int

DIGIPIN 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 DIGIPIN compact mode (not used for polylines)

False
topology bool

Enable topology preserving mode (handled by geodataframe2digipin)

False
include_properties bool

Whether to include properties in output

True

Returns:

Name Type Description
list

List of dictionaries representing DIGIPIN cells intersecting the polyline

Example

from shapely.geometry import LineString line = LineString([(77.2090, 28.6139), (77.2200, 28.6200)]) # Delhi area cells = polyline2digipin(line, 10, {"name": "route"}) len(cells) > 0 True

Source code in vgrid/conversion/vector2dggs/vector2digipin.py
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def polyline2digipin(
    feature,
    resolution,
    feature_properties=None,
    predicate=None,
    compact=False,
    topology=False,
    include_properties=True,
):
    """
    Convert a polyline geometry to DIGIPIN grid cells.

    Args:
        feature (shapely.geometry.LineString or shapely.geometry.MultiLineString): Polyline geometry to convert
        resolution (int): DIGIPIN 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 DIGIPIN compact mode (not used for polylines)
        topology (bool, optional): Enable topology preserving mode (handled by geodataframe2digipin)
        include_properties (bool, optional): Whether to include properties in output

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

    Example:
        >>> from shapely.geometry import LineString
        >>> line = LineString([(77.2090, 28.6139), (77.2200, 28.6200)])  # Delhi area
        >>> cells = polyline2digipin(line, 10, {"name": "route"})
        >>> len(cells) > 0
        True
    """

    digipin_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

        # Constrain to DIGIPIN bounds (India region)
        min_lat = max(min_lat, BOUNDS["minLat"])
        min_lon = max(min_lon, BOUNDS["minLon"])
        max_lat = min(max_lat, BOUNDS["maxLat"])
        max_lon = min(max_lon, BOUNDS["maxLon"])

        # Calculate sampling density based on resolution (following digipingrid.py approach)
        # Each level divides the cell by 4 (2x2 grid)
        base_width = 9.0  # degrees at resolution 1
        factor = 0.25 ** (resolution - 1)  # each level divides by 4
        sample_width = base_width * factor

        seen_cells = set()

        # Sample points across the bounding box
        lon = min_lon
        while lon <= max_lon:
            lat = min_lat
            while lat <= max_lat:
                try:
                    # Get DIGIPIN code for this point at the specified resolution
                    digipin_id = latlon2digipin(lat, lon, resolution)

                    if digipin_id == "Out of Bound":
                        lat += sample_width
                        continue

                    if digipin_id in seen_cells:
                        lat += sample_width
                        continue

                    seen_cells.add(digipin_id)

                    # Get the bounds for this DIGIPIN cell
                    cell_polygon = digipin2geo(digipin_id)

                    if isinstance(cell_polygon, str):  # Error like 'Invalid DIGIPIN'
                        lat += sample_width
                        continue

                    # Check if cell intersects with polyline
                    if cell_polygon.intersects(polyline):
                        digipin_row = graticule_dggs_to_geoseries(
                            "digipin", digipin_id, resolution, cell_polygon
                        )
                        if include_properties and feature_properties:
                            digipin_row.update(feature_properties)
                        digipin_rows.append(digipin_row)

                except Exception:
                    # Skip cells with errors
                    pass

                lat += sample_width
            lon += sample_width
    return digipin_rows

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

Convert vector data to DIGIPIN 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

DIGIPIN resolution level [1..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 DIGIPIN compact mode for polygons

False
topology bool

Enable topology preserving mode to ensure disjoint features have disjoint DIGIPIN 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 = vector2digipin("data/points.geojson", resolution=10, output_format="geojson") print(f"Output saved to: {result}")

Source code in vgrid/conversion/vector2dggs/vector2digipin.py
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
def vector2digipin(
    vector_data,
    resolution=None,
    predicate=None,
    compact=False,
    topology=False,
    output_format="gpd",
    include_properties=True,
    **kwargs,
):
    """
    Convert vector data to DIGIPIN grid cells from various input formats.

    Args:
        vector_data (str, geopandas.GeoDataFrame, pandas.DataFrame, dict, or list): Input vector data
        resolution (int, optional): DIGIPIN resolution level [1..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 DIGIPIN compact mode for polygons
        topology (bool, optional): Enable topology preserving mode to ensure disjoint features have disjoint DIGIPIN 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 = vector2digipin("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_digipin_resolution(resolution)

    gdf = process_input_data_vector(vector_data, **kwargs)
    result = geodataframe2digipin(
        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}2digipin"
        else:
            output_name = "digipin"
    return convert_to_output_format(result, output_format, output_name)

vector2digipin_cli()

Command-line interface for vector2digipin conversion.

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

Usage

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

Source code in vgrid/conversion/vector2dggs/vector2digipin.py
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
def vector2digipin_cli():
    """
    Command-line interface for vector2digipin conversion.

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

    Usage:
        python vector2digipin.py -i input.geojson -r 10 -f geojson
        python vector2digipin.py -i input.shp -r 8 -p intersect -c -t
    """
    parser = argparse.ArgumentParser(
        description="Convert vector data to DIGIPIN 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"DIGIPIN 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 DIGIPIN 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 = vector2digipin(
            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)