Skip to content

DGGS Compact / Expand

DGGS Compact and Expand functions.

This submodule provides functions to compact and expand various discrete global grid systems (DGGS).

a5compact_cli()

Command-line interface for a5compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/a5compact.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
def a5compact_cli():
    """
    Command-line interface for a5compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="A5 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input A5 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="A5 Hex field")
    parser.add_argument(
        "-f", "--output_format", type=str, default="gpd", choices=OUTPUT_FORMATS
    )
    parser.add_argument(
        "-split",
        "--split_antimeridian",
        action="store_true",
        default=False,
        help="Enable Antimeridian splitting",
    )
    parser.add_argument(
        "-options",
        "--options",
        type=str,
        default=None,
        help="JSON string of options to pass to a52geo. "
        "Example: '{\"segments\": 1000}'",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth [-1, A5 max_res]: 0 = no-op, -1 = compact fully "
        "(default), 1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format
    split_antimeridian = args.split_antimeridian

    # 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)}")
            return

    result = a5compact(
        input_data,
        a5_hex=cellid,
        output_format=output_format,
        options=options,
        split_antimeridian=split_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

a5expand(input_data, resolution=None, a5_hex=None, output_format='gpd', options=None, split_antimeridian=False, verbose=True, depth=None)

Expand (uncompact) A5 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing A5 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of A5 cell IDs resolution : int, optional Target A5 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. a5_hex : str, optional Name of the column containing A5 cell IDs. Defaults to "a5". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path 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. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded A5 cells in the specified format, or None if expansion fails.

Examples

Expand from file

result = a5expand("cells.geojson", resolution=5) print(f"Expanded to {len(result)} cells")

Expand from list

result = a5expand(["8e65b56628e0d07"], resolution=5)

Expand mixed-resolution cells by relative depth

result = a5expand(cells, depth=1) result = a5expand(cells, depth=2)

Expand to GeoJSON file

result = a5expand("cells.geojson", resolution=5, output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/a5compact.py
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
499
500
501
502
503
504
505
506
507
508
509
510
def a5expand(
    input_data,
    resolution=None,
    a5_hex=None,
    output_format="gpd",
    options=None,
    split_antimeridian=False,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) A5 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing A5 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of A5 cell IDs
    resolution : int, optional
        Target A5 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    a5_hex : str, optional
        Name of the column containing A5 cell IDs. Defaults to "a5".
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    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.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded A5 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> # Expand from file
    >>> result = a5expand("cells.geojson", resolution=5)
    >>> print(f"Expanded to {len(result)} cells")

    >>> # Expand from list
    >>> result = a5expand(["8e65b56628e0d07"], resolution=5)

    >>> # Expand mixed-resolution cells by relative depth
    >>> result = a5expand(cells, depth=1)
    >>> result = a5expand(cells, depth=2)

    >>> # Expand to GeoJSON file
    >>> result = a5expand("cells.geojson", resolution=5, output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if a5_hex is None:
        a5_hex = "a5"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("a5", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("a5", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, a5_hex)
    a5_hexes = gdf[a5_hex].drop_duplicates().tolist()
    if not a5_hexes:
        print(f"No A5 Hexes found in <{a5_hex}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(
                a5.get_resolution(a5.hex_to_u64(a5_hex)) for a5_hex in a5_hexes
            )
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            a5_hexes_expand = a5_expand(a5_hexes, resolution=resolution, verbose=verbose)
        else:
            a5_hexes_expand = a5_expand(a5_hexes, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your A5 ID field, resolution, or depth."
        )
    if not a5_hexes_expand:
        return None
    rows = []
    for a5_hex_expand in tqdm(
        a5_hexes_expand,
        desc="Building A5 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = a52geo(
                a5_hex_expand, options, split_antimeridian=split_antimeridian
            )
            cell_resolution = a5.get_resolution(a5.hex_to_u64(a5_hex_expand))
            num_edges = 5  # A5 cells are pentagons
            if cell_resolution == 1:
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "a5", a5_hex_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_a5_expanded"
        else:
            ouput_name = "a5_expanded"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

a5expand_cli()

Command-line interface for a5expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/a5compact.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
def a5expand_cli():
    """
    Command-line interface for a5expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="A5 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input A5 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target A5 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= A5 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="A5 Hex field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-split",
        "--split_antimeridian",
        action="store_true",
        default=False,
        help="Enable Antimeridian splitting",
    )
    parser.add_argument(
        "-options",
        "--options",
        type=str,
        default=None,
        help="JSON string of options to pass to a52geo. "
        "Example: '{\"segments\": 1000}'",
    )
    add_verbose_argument(parser)
    args = parser.parse_args()
    input_data = args.input
    resolution = args.resolution
    cellid = args.cellid
    output_format = args.output_format
    split_antimeridian = args.split_antimeridian

    # 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)}")
            return

    result = a5expand(
        input_data,
        resolution=resolution,
        a5_hex=cellid,
        output_format=output_format,
        options=options,
        split_antimeridian=split_antimeridian,
        depth=args.depth,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

dggalexpand(dggs_type, input_data, resolution=None, zone_id=None, output_format='gpd', split_antimeridian=False, verbose=True, depth=None)

Expand (uncompact) DGGAL cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/dggalcompact.py
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
def dggalexpand(
    dggs_type,
    input_data,
    resolution=None,
    zone_id=None,
    output_format="gpd",
    split_antimeridian=False,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) DGGAL cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    dggs_type = validate_dggal_type(dggs_type)
    max_res = int(DGGAL_TYPES[dggs_type]["max_res"])
    if zone_id is None:
        zone_id = f"dggal_{dggs_type}"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution(
            dggs_type, resolution, max_res=max_res
        )
    elif depth is not None:
        depth = validate_dggs_expand_depth(dggs_type, depth, max_res=max_res)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, zone_id)
    zone_ids = gdf[zone_id].drop_duplicates().tolist()

    if not zone_ids:
        print(f"No Zone IDs found in <{zone_id}> field.")
        return

    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = getattr(dggal, dggs_class_name)()

    try:
        if resolution is not None:
            max_input_res = 0
            for zid in zone_ids:
                try:
                    zone = dggrs.getZoneFromTextID(zid)
                    max_input_res = max(max_input_res, dggrs.getZoneLevel(zone))
                except Exception:
                    continue

            if resolution < max_input_res:
                print(f"Target expand resolution ({resolution}) must >= {max_input_res}.")
                return None
            zone_ids_expand = dggal_expand(
                dggs_type, zone_ids, resolution=resolution, verbose=verbose
            )
        else:
            zone_ids_expand = dggal_expand(dggs_type, zone_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Zone ID field, resolution, or depth."
        )
    if not zone_ids_expand:
        return None

    rows = []
    for zone_id_expand in tqdm(
        zone_ids_expand,
        desc="Building DGGAL expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            zone = dggrs.getZoneFromTextID(zone_id_expand)
            cell_resolution = dggrs.getZoneLevel(zone)
            cell_polygon = dggal2geo(
                dggs_type, zone_id_expand, split_antimeridian=split_antimeridian
            )
            num_edges = dggrs.countZoneEdges(zone)
            row = geodesic_dggs_to_geoseries(
                f"dggal_{dggs_type}",
                zone_id_expand,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_dggal_expanded"
        else:
            ouput_name = "dggal_expanded"

    return convert_to_output_format(out_gdf, output_format, ouput_name)

digipincompact_cli()

Command-line interface for DIGIPIN compaction.

Source code in vgrid/conversion/dggscompact/digipincompact.py
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
def digipincompact_cli():
    """Command-line interface for DIGIPIN compaction."""
    parser = argparse.ArgumentParser(description="DIGIPIN Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input DIGIPIN (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="DIGIPIN ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = digipincompact(
        input_data,
        digipin_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

digipinexpand(input_data, resolution=None, digipin_id='digipin', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) DIGIPIN cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/digipincompact.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
def digipinexpand(
    input_data,
    resolution=None,
    digipin_id="digipin",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) DIGIPIN cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("digipin", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("digipin", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, digipin_id)
    digipin_ids = gdf[digipin_id].drop_duplicates().tolist()

    if not digipin_ids:
        print(f"No DIGIPIN IDs found in <{digipin_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(digipin_resolution(tid) for tid in digipin_ids)
            if isinstance(max_res, str):
                raise ValueError("Invalid DIGIPIN format.")
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            digipin_ids_expand = digipin_expand(digipin_ids, resolution=resolution, verbose=verbose)
        else:
            digipin_ids_expand = digipin_expand(digipin_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your DIGIPIN ID field, resolution, or depth."
        )

    if not digipin_ids_expand:
        return None

    rows = []
    for digipin_id_expand in tqdm(
        digipin_ids_expand,
        desc="Building DIGIPIN expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = digipin2geo(digipin_id_expand)
            cell_resolution = digipin_resolution(digipin_id_expand)
            row = graticule_dggs_to_geoseries(
                "digipin", digipin_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_digipin_expanded"
        else:
            output_name = "digipin_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

digipinexpand_cli()

Command-line interface for DIGIPIN expansion.

Source code in vgrid/conversion/dggscompact/digipincompact.py
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
def digipinexpand_cli():
    """Command-line interface for DIGIPIN expansion."""
    parser = argparse.ArgumentParser(description="DIGIPIN Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input DIGIPIN (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target DIGIPIN resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= DIGIPIN max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="DIGIPIN ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = digipinexpand(
        args.input,
        resolution=args.resolution,
        digipin_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

easecompact_cli()

Command-line interface for EASE compaction.

Source code in vgrid/conversion/dggscompact/easecompact.py
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
def easecompact_cli():
    """Command-line interface for EASE compaction."""
    parser = argparse.ArgumentParser(description="EASE Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input EASE (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="EASE ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = easecompact(
        input_data,
        ease_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

easeexpand(input_data, resolution=None, ease_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) EASE cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/easecompact.py
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 easeexpand(
    input_data,
    resolution=None,
    ease_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) EASE cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if ease_id is None:
        ease_id = "ease"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("ease", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("ease", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, ease_id)
    ease_ids = gdf[ease_id].drop_duplicates().tolist()

    if not ease_ids:
        print(f"No EASE IDs found in <{ease_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(int(eid[1]) for eid in ease_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            ease_ids_expand = ease_expand(ease_ids, resolution=resolution, verbose=verbose)
        else:
            ease_ids_expand = ease_expand(ease_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your EASE ID field, resolution, or depth."
        )

    if not ease_ids_expand:
        return None

    rows = []
    for ease_id_expand in tqdm(
        ease_ids_expand,
        desc="Building EASE expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = ease2geo(ease_id_expand)
            cell_resolution = get_ease_resolution(ease_id_expand)
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "ease", ease_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_ease_expanded"
        else:
            output_name = "ease_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

easeexpand_cli()

Command-line interface for EASE expansion.

Source code in vgrid/conversion/dggscompact/easecompact.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
def easeexpand_cli():
    """Command-line interface for EASE expansion."""
    parser = argparse.ArgumentParser(description="EASE Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input EASE (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target EASE resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= EASE max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="EASE ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = easeexpand(
        args.input,
        resolution=args.resolution,
        ease_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

geohashcompact_cli()

Command-line interface for Geohash compaction.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
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 geohashcompact_cli():
    """Command-line interface for Geohash compaction."""
    parser = argparse.ArgumentParser(description="Geohash Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Geohash (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Geohash ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = geohashcompact(
        input_data,
        geohash_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

geohashexpand(input_data, resolution=None, geohash_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Geohash cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
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
def geohashexpand(
    input_data,
    resolution=None,
    geohash_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Geohash cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if geohash_id is None:
        geohash_id = "geohash"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("geohash", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("geohash", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, geohash_id)
    geohash_ids = gdf[geohash_id].drop_duplicates().tolist()

    if not geohash_ids:
        print(f"No Geohash IDs found in <{geohash_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(gid) for gid in geohash_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            geohash_ids_expand = geohash_expand(geohash_ids, resolution=resolution, verbose=verbose)
        else:
            geohash_ids_expand = geohash_expand(geohash_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Geohash ID field, resolution, or depth."
        )

    if not geohash_ids_expand:
        return None

    rows = []
    for geohash_id_expand in tqdm(
        geohash_ids_expand,
        desc="Building Geohash expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = geohash2geo(geohash_id_expand)
            cell_resolution = len(geohash_id_expand)
            row = graticule_dggs_to_geoseries(
                "geohash", geohash_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_geohash_expanded"
        else:
            output_name = "geohash_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

geohashexpand_cli()

Command-line interface for Geohash expansion.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def geohashexpand_cli():
    """Command-line interface for Geohash expansion."""
    parser = argparse.ArgumentParser(description="Geohash Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Geohash (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Geohash resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Geohash max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Geohash ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = geohashexpand(
        args.input,
        resolution=args.resolution,
        geohash_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

h3_compact(h3_ids, depth=-1, bags=None, verbose=True)

Compact a list of H3 cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

h3_ids : list of str H3 cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible (same result as h3.compact_cells when all inputs share a resolution) - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted H3 cell IDs.

Source code in vgrid/conversion/dggscompact/h3compact.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def h3_compact(h3_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of H3 cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    h3_ids : list of str
        H3 cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible (same result as ``h3.compact_cells``
          when all inputs share a resolution)
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted H3 cell IDs.
    """
    depth = validate_dggs_compact_depth("h3", depth)

    def parent_fn(h3_id):
        cell_res = h3.get_resolution(h3_id)
        if cell_res <= 0:
            return None
        return h3.cell_to_parent(h3_id, cell_res - 1)

    return compact_cells(
        h3_ids,
        parent_fn,
        h3.cell_to_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting H3",
    )

h3compact_cli()

Command-line interface for h3compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/h3compact.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
def h3compact_cli():
    """
    Command-line interface for h3compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="H3 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input H3 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="H3 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Enable Antimeridian fixing",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    fix_antimeridian = args.fix_antimeridian
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = h3compact(
        input_data,
        h3_id=cellid,
        output_format=output_format,
        fix_antimeridian=fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

h3expand(input_data, resolution=None, h3_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) H3 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing H3 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of H3 cell IDs resolution : int, optional Target H3 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. h3_id : str, optional Name of the column containing H3 cell IDs. Defaults to "h3". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded H3 cells in the specified format, or None if expansion fails.

Examples

Expand from file

result = h3expand("cells.geojson", resolution=5) print(f"Expanded to {len(result)} cells")

Expand from list

result = h3expand(["83754efffffffff"], resolution=5)

Expand mixed-resolution cells by relative depth

result = h3expand(cells, depth=1) result = h3expand(cells, depth=2)

Expand to GeoJSON file

result = h3expand("cells.geojson", resolution=5, output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/h3compact.py
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
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
def h3expand(
    input_data,
    resolution=None,
    h3_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) H3 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing H3 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of H3 cell IDs
    resolution : int, optional
        Target H3 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    h3_id : str, optional
        Name of the column containing H3 cell IDs. Defaults to "h3".
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded H3 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> # Expand from file
    >>> result = h3expand("cells.geojson", resolution=5)
    >>> print(f"Expanded to {len(result)} cells")

    >>> # Expand from list
    >>> result = h3expand(["83754efffffffff"], resolution=5)

    >>> # Expand mixed-resolution cells by relative depth
    >>> result = h3expand(cells, depth=1)
    >>> result = h3expand(cells, depth=2)

    >>> # Expand to GeoJSON file
    >>> result = h3expand("cells.geojson", resolution=5, output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if h3_id is None:
        h3_id = "h3"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("h3", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("h3", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, h3_id)
    h3_ids = gdf[h3_id].drop_duplicates().tolist()
    if not h3_ids:
        print(f"No H3 IDs found in <{h3_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(h3.get_resolution(hid) for hid in h3_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            h3_ids_expand = h3_expand(h3_ids, resolution=resolution, verbose=verbose)
        else:
            h3_ids_expand = h3_expand(h3_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your H3 ID field, resolution, or depth."
        )
    if not h3_ids_expand:
        return None
    rows = []
    for h3_id_expand in tqdm(
        h3_ids_expand,
        desc="Building H3 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = h32geo(h3_id_expand, fix_antimeridian=fix_antimeridian)
            cell_resolution = h3.get_resolution(h3_id_expand)
            num_edges = 6
            if h3.is_pentagon(h3_id_expand):
                num_edges = 5
            row = geodesic_dggs_to_geoseries(
                "h3", h3_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    # If output_format is file-based, set ouput_name as just the filename in current directory
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_h3_expanded"
        else:
            ouput_name = "h3_expanded"

    return convert_to_output_format(out_gdf, output_format, ouput_name)

h3expand_cli()

Command-line interface for h3expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/h3compact.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
560
561
562
563
564
def h3expand_cli():
    """
    Command-line interface for h3expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="H3 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input H3 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target H3 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= H3 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="H3 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Enable Antimeridian fixing",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    resolution = args.resolution
    cellid = args.cellid
    output_format = args.output_format

    result = h3expand(
        input_data,
        resolution=resolution,
        h3_id=cellid,
        output_format=output_format,
        fix_antimeridian=args.fix_antimeridian,
        verbose=args.verbose,
        depth=args.depth,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

isea3hcompact_cli()

Command-line interface for ISEA3H compaction.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
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
def isea3hcompact_cli():
    """Command-line interface for ISEA3H compaction."""
    parser = argparse.ArgumentParser(description="ISEA3H Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input ISEA3H (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="ISEA3H ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )
    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = isea3hcompact(
        input_data,
        isea3h_id=cellid,
        output_format=output_format,
        fix_antimeridian=args.fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

isea3hexpand(input_data, resolution=None, isea3h_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) ISEA3H cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def isea3hexpand(
    input_data,
    resolution=None,
    isea3h_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) ISEA3H cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if isea3h_id is None:
        isea3h_id = "isea3h"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea3h", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("isea3h", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, isea3h_id)
    isea3h_ids = gdf[isea3h_id].drop_duplicates().tolist()

    if not isea3h_ids:
        print(f"No ISEA3H IDs found in <{isea3h_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(get_isea3h_resolution(cid) for cid in isea3h_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            isea3h_cells_expand = isea3h_expand(isea3h_ids, resolution=resolution, verbose=verbose)
        else:
            isea3h_cells_expand = isea3h_expand(isea3h_ids, depth=depth, verbose=verbose)
        isea3h_ids_expand = [cell.get_cell_id() for cell in isea3h_cells_expand]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your ISEA3H ID field, resolution, or depth."
        )

    if not isea3h_ids_expand:
        return None

    rows = []
    for isea3h_id_expand in tqdm(
        isea3h_ids_expand,
        desc="Building ISEA3H expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea3h2geo(
                isea3h_id_expand, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea3h_resolution(isea3h_id_expand)
            num_edges = 6
            row = geodesic_dggs_to_geoseries(
                "isea3h", isea3h_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    file_formats = ["csv", "geojson", "shapefile", "gpkg", "parquet", "geoparquet"]
    output_name = None
    if output_format in file_formats:
        ext_map = {
            "csv": ".csv",
            "geojson": ".geojson",
            "shapefile": ".shp",
            "gpkg": ".gpkg",
            "parquet": ".parquet",
            "geoparquet": ".parquet",
        }
        ext = ext_map.get(output_format, "")
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_isea3h_expanded{ext}"
        else:
            output_name = f"isea3h_expanded{ext}"

    return convert_to_output_format(out_gdf, output_format, output_name)

isea3hexpand_cli()

Command-line interface for ISEA3H expansion.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
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
596
597
598
599
600
601
602
603
604
605
606
def isea3hexpand_cli():
    """Command-line interface for ISEA3H expansion."""
    parser = argparse.ArgumentParser(description="ISEA3H Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input ISEA3H (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target ISEA3H resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= ISEA3H max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="ISEA3H ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default=None,
        help="Output format (None, csv, geojson, shapefile, gpd, geojson_dict, gpkg, geoparquet)",
    )
    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",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    input_data = args.input
    output_format = args.output_format
    if platform.system() == "Windows":
        result = isea3hexpand(
            input_data,
            resolution=args.resolution,
            isea3h_id=args.cellid,
            output_format=output_format,
            fix_antimeridian=args.fix_antimeridian,
            depth=args.depth,
            verbose=args.verbose,
        )

        if output_format is None:
            print(result)
        elif output_format in [
            "csv",
            "geojson",
            "geojson_dict",
            "shapefile",
            "gpkg",
            "geoparquet",
            "parquet",
        ]:
            if isinstance(input_data, str):
                base = os.path.splitext(os.path.basename(input_data))[0]
                ext_map = {
                    "csv": ".csv",
                    "geojson": ".geojson",
                    "geojson_dict": ".geojson",
                    "shapefile": ".shp",
                    "gpkg": ".gpkg",
                    "parquet": ".parquet",
                    "geoparquet": ".parquet",
                }
                ext = ext_map.get(output_format, "")
                output = f"{base}_isea3h_expanded{ext}"
            else:
                output = f"isea3h_expanded{ext_map.get(output_format, '')}"
            print(f"Output written to {output}")
        elif output_format in ["gpd", "geopandas"]:
            print(result)
        else:
            print("ISEA3H expand completed.")
    else:
        print("ISEA3H is only supported on Windows systems")

isea4texpand(input_data, resolution=None, isea4t_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) ISEA4T cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def isea4texpand(
    input_data,
    resolution=None,
    isea4t_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) ISEA4T cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if isea4t_id is None:
        isea4t_id = "isea4t"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea4t", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("isea4t", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, isea4t_id)
    isea4t_ids = gdf[isea4t_id].drop_duplicates().tolist()
    if not isea4t_ids:
        print(f"No ISEA4T IDs found in <{isea4t_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(get_isea4t_resolution(cid) for cid in isea4t_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            isea4t_cells_expand = isea4t_expand(isea4t_ids, resolution=resolution, verbose=verbose)
        else:
            isea4t_cells_expand = isea4t_expand(isea4t_ids, depth=depth, verbose=verbose)
        isea4t_ids_expand = [c.get_cell_id() for c in isea4t_cells_expand]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your ISEA4T ID field, resolution, or depth."
        )
    if not isea4t_ids_expand:
        return None
    rows = []
    for isea4t_id_expand in tqdm(
        isea4t_ids_expand,
        desc="Building ISEA4T expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea4t2geo(
                isea4t_id_expand, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea4t_resolution(isea4t_id_expand)
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "isea4t", isea4t_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_isea4t_expanded"
        else:
            ouput_name = "isea4t_expanded"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

olccompact_cli()

Command-line interface for OLC compaction.

Source code in vgrid/conversion/dggscompact/olccompact.py
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
def olccompact_cli():
    """Command-line interface for OLC compaction."""
    parser = argparse.ArgumentParser(description="OLC Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input OLC (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="OLC ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = olccompact(
        input_data,
        olc_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

olcexpand(input_data, resolution=None, olc_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) OLC cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute code length (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded depth OLC steps down.

Source code in vgrid/conversion/dggscompact/olccompact.py
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
def olcexpand(
    input_data,
    resolution=None,
    olc_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) OLC cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute code length (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded ``depth`` OLC steps down.
    """
    if olc_id is None:
        olc_id = "olc"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("olc", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("olc", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, olc_id)
    olc_ids = gdf[olc_id].drop_duplicates().tolist()

    if not olc_ids:
        print(f"No OLC IDs found in <{olc_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(olc.decode(oid).codeLength for oid in olc_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            olc_ids_expand = olc_expand(olc_ids, resolution=resolution, verbose=verbose)
        else:
            olc_ids_expand = olc_expand(olc_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your OLC ID field, resolution, or depth."
        )

    if not olc_ids_expand:
        return None

    rows = []
    for olc_id_expand in tqdm(
        olc_ids_expand,
        desc="Building OLC expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = olc2geo(olc_id_expand)
            cell_resolution = olc.decode(olc_id_expand).codeLength
            row = graticule_dggs_to_geoseries(
                "olc", olc_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_olc_expanded"
        else:
            output_name = "olc_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

olcexpand_cli()

Command-line interface for OLC expansion.

Source code in vgrid/conversion/dggscompact/olccompact.py
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 olcexpand_cli():
    """Command-line interface for OLC expansion."""
    parser = argparse.ArgumentParser(description="OLC Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input OLC (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target OLC resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many OLC child steps (1 = next valid OLC "
        "resolution, 2 = the one after that, ...; 1 <= depth <= OLC max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="OLC ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = olcexpand(
        args.input,
        resolution=args.resolution,
        olc_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

qtmcompact_cli()

Command-line interface for QTM compaction.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmcompact_cli():
    """Command-line interface for QTM compaction."""
    parser = argparse.ArgumentParser(description="QTM Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input QTM (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="QTM ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = qtmcompact(
        input_data,
        qtm_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

qtmexpand(input_data, resolution=None, qtm_id='qtm', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) QTM cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmexpand(
    input_data,
    resolution=None,
    qtm_id="qtm",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) QTM cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if qtm_id is None:
        qtm_id = "qtm"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("qtm", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("qtm", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, qtm_id)
    qtm_ids = gdf[qtm_id].drop_duplicates().tolist()

    if not qtm_ids:
        print(f"No QTM IDs found in <{qtm_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(qid) for qid in qtm_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            qtm_ids_expand = qtm_expand(qtm_ids, resolution=resolution, verbose=verbose)
        else:
            qtm_ids_expand = qtm_expand(qtm_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your QTM ID field, resolution, or depth."
        )

    if not qtm_ids_expand:
        return None

    rows = []
    for qtm_id_expand in tqdm(
        qtm_ids_expand,
        desc="Building QTM expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = qtm2geo(qtm_id_expand)
            cell_resolution = len(qtm_id_expand)
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "qtm", qtm_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_qtm_expanded"
        else:
            output_name = "qtm_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

qtmexpand_cli()

Command-line interface for QTM expansion.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmexpand_cli():
    """Command-line interface for QTM expansion."""
    parser = argparse.ArgumentParser(description="QTM Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input QTM (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target QTM resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= QTM max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="QTM ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = qtmexpand(
        args.input,
        resolution=args.resolution,
        qtm_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

quadkeycompact_cli()

Command-line interface for Quadkey compaction.

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
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
def quadkeycompact_cli():
    """Command-line interface for Quadkey compaction."""
    parser = argparse.ArgumentParser(description="Quadkey Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Quadkey (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Quadkey ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = quadkeycompact(
        input_data,
        quadkey_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

quadkeyexpand(input_data, resolution=None, quadkey_id='quadkey', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Quadkey cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def quadkeyexpand(
    input_data,
    resolution=None,
    quadkey_id="quadkey",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Quadkey cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("quadkey", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("quadkey", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, quadkey_id)
    quadkey_ids = gdf[quadkey_id].drop_duplicates().tolist()

    if not quadkey_ids:
        print(f"No Quadkey IDs found in <{quadkey_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(qid) for qid in quadkey_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            quadkey_ids_expand = quadkey_expand(quadkey_ids, resolution=resolution, verbose=verbose)
        else:
            quadkey_ids_expand = quadkey_expand(quadkey_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Quadkey ID field, resolution, or depth."
        )

    if not quadkey_ids_expand:
        return None

    rows = []
    for quadkey_id_expand in tqdm(
        quadkey_ids_expand,
        desc="Building Quadkey expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = quadkey2geo(quadkey_id_expand)
            cell_resolution = len(quadkey_id_expand)
            row = graticule_dggs_to_geoseries(
                "quadkey", quadkey_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_quadkey_expanded"
        else:
            output_name = "quadkey_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

quadkeyexpand_cli()

Command-line interface for Quadkey expansion.

Source code in vgrid/conversion/dggscompact/quadkeycompact.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
def quadkeyexpand_cli():
    """Command-line interface for Quadkey expansion."""
    parser = argparse.ArgumentParser(description="Quadkey Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Quadkey (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Quadkey resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Quadkey max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Quadkey ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = quadkeyexpand(
        args.input,
        resolution=args.resolution,
        quadkey_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

rhealpixexpand(input_data, resolution=None, rhealpix_id='rhealpix', output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) RHEALPix cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/rhealpixcompact.py
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
def rhealpixexpand(
    input_data,
    resolution=None,
    rhealpix_id="rhealpix",
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) RHEALPix cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("rhealpix", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("rhealpix", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, rhealpix_id)
    rhealpix_ids = sorted(gdf[rhealpix_id].drop_duplicates().tolist())
    if not rhealpix_ids:
        print(f"No rHEALPix tokens found in <{rhealpix_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(get_rhealpix_resolution(token) for token in rhealpix_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            expanded_cells = rhealpix_expand(rhealpix_ids, resolution=resolution, verbose=verbose)
        else:
            expanded_cells = rhealpix_expand(rhealpix_ids, depth=depth, verbose=verbose)
        rhealpix_tokens_expand = [str(cell) for cell in expanded_cells]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your rHEALPix ID field, resolution, or depth."
        )
    if not rhealpix_tokens_expand:
        return None
    rows = []
    for rhealpix_token_expand in tqdm(
        rhealpix_tokens_expand,
        desc="Building rHEALPix expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = rhealpix2geo(
                rhealpix_token_expand, fix_antimeridian=fix_antimeridian
            )
            rhealpix_uids = (rhealpix_token_expand[0],) + tuple(
                map(int, rhealpix_token_expand[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",
                rhealpix_token_expand,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_rhealpix_expanded"
        else:
            output_name = "rhealpix_expanded"
    return convert_to_output_format(out_gdf, output_format, output_name)

s2compact_cli()

Command-line interface for s2compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/s2compact.py
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
def s2compact_cli():
    """
    Command-line interface for s2compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="S2 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input S2 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="S2 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format
    fix_antimeridian = args.fix_antimeridian
    result = s2compact(
        input_data,
        s2_token=cellid,
        output_format=output_format,
        fix_antimeridian=fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

s2expand(input_data, resolution=None, s2_token='s2', output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) S2 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing S2 cell tokens. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of S2 cell tokens resolution : int, optional Target S2 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. s2_token : str, default "s2" Name of the column containing S2 cell tokens. output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded S2 cells in the specified format, or None if expansion fails.

Examples

result = s2expand("cells.geojson", resolution=10) result = s2expand(["31752f45cc94"], resolution=10) result = s2expand(cells, depth=1)

Source code in vgrid/conversion/dggscompact/s2compact.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
def s2expand(
    input_data,
    resolution=None,
    s2_token="s2",
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) S2 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing S2 cell tokens. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of S2 cell tokens
    resolution : int, optional
        Target S2 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    s2_token : str, default "s2"
        Name of the column containing S2 cell tokens.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded S2 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> result = s2expand("cells.geojson", resolution=10)
    >>> result = s2expand(["31752f45cc94"], resolution=10)
    >>> result = s2expand(cells, depth=1)
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("s2", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("s2", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, s2_token)
    s2_tokens = gdf[s2_token].drop_duplicates().tolist()
    if not s2_tokens:
        print(f"No S2 tokens found in <{s2_token}> field.")
        return
    try:
        if resolution is not None:
            s2_cells = [s2.CellId.from_token(token) for token in s2_tokens]
            if not s2_cells:
                print(f"No valid S2 tokens found in <{s2_token}> field.")
                return
            max_res = max(cell.level() for cell in s2_cells)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            s2_tokens_expand = s2_expand(s2_tokens, resolution=resolution, verbose=verbose)
        else:
            s2_tokens_expand = s2_expand(s2_tokens, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your S2 ID field, resolution, or depth."
        )
    if not s2_tokens_expand:
        return None
    rows = []
    for s2_token_expand in tqdm(
        s2_tokens_expand,
        desc="Building S2 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = s22geo(s2_token_expand, fix_antimeridian=fix_antimeridian)
            cell_resolution = s2.CellId.from_token(s2_token_expand).level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", s2_token_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_s2_expanded"
        else:
            output_name = "s2_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

s2expand_cli()

Command-line interface for s2expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/s2compact.py
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
def s2expand_cli():
    """
    Command-line interface for s2expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="S2 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input S2 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target S2 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= S2 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="S2 Token field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    add_verbose_argument(parser)
    args = parser.parse_args()
    result = s2expand(
        args.input,
        resolution=args.resolution,
        s2_token=args.cellid,
        output_format=args.output_format,
        fix_antimeridian=args.fix_antimeridian,
        depth=args.depth,
        verbose=args.verbose,
    )
    if args.output_format in STRUCTURED_FORMATS:
        print(result)

tilecodecompact_cli()

Command-line interface for Tilecode compaction.

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
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
def tilecodecompact_cli():
    """Command-line interface for Tilecode compaction."""
    parser = argparse.ArgumentParser(description="Tilecode Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Tilecode (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Tilecode ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = tilecodecompact(
        input_data,
        tilecode_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

tilecodeexpand(input_data, resolution=None, tilecode_id='tilecode', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Tilecode cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/tilecodecompact.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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
def tilecodeexpand(
    input_data,
    resolution=None,
    tilecode_id="tilecode",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Tilecode cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("tilecode", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("tilecode", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, tilecode_id)
    tilecode_ids = gdf[tilecode_id].drop_duplicates().tolist()

    if not tilecode_ids:
        print(f"No Tilecode IDs found in <{tilecode_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(tilecode_resolution(tid) for tid in tilecode_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            tilecode_ids_expand = tilecode_expand(tilecode_ids, resolution=resolution, verbose=verbose)
        else:
            tilecode_ids_expand = tilecode_expand(tilecode_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Tilecode ID field, resolution, or depth."
        )

    if not tilecode_ids_expand:
        return None

    rows = []
    for tilecode_id_expand in tqdm(
        tilecode_ids_expand,
        desc="Building Tilecode expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = tilecode2geo(tilecode_id_expand)
            cell_resolution = tilecode_resolution(tilecode_id_expand)
            row = graticule_dggs_to_geoseries(
                "tilecode", tilecode_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_tilecode_expanded"
        else:
            output_name = "tilecode_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

tilecodeexpand_cli()

Command-line interface for Tilecode expansion.

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def tilecodeexpand_cli():
    """Command-line interface for Tilecode expansion."""
    parser = argparse.ArgumentParser(description="Tilecode Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Tilecode (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Tilecode resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Tilecode max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Tilecode ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = tilecodeexpand(
        args.input,
        resolution=args.resolution,
        tilecode_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

H3 Compact Module

This module provides functionality to compact and expand H3 cells with flexible input and output formats.

Key Functions

h3_compact(h3_ids, depth=-1, bags=None, verbose=True)

Compact a list of H3 cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

h3_ids : list of str H3 cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible (same result as h3.compact_cells when all inputs share a resolution) - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted H3 cell IDs.

Source code in vgrid/conversion/dggscompact/h3compact.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def h3_compact(h3_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of H3 cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    h3_ids : list of str
        H3 cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible (same result as ``h3.compact_cells``
          when all inputs share a resolution)
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted H3 cell IDs.
    """
    depth = validate_dggs_compact_depth("h3", depth)

    def parent_fn(h3_id):
        cell_res = h3.get_resolution(h3_id)
        if cell_res <= 0:
            return None
        return h3.cell_to_parent(h3_id, cell_res - 1)

    return compact_cells(
        h3_ids,
        parent_fn,
        h3.cell_to_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting H3",
    )

h3_expand(h3_ids, resolution=None, depth=None, verbose=True)

Expand H3 cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are uncompacted to that absolute resolution. When only depth is set, resolution is ignored and each cell (at any resolution) is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

h3_ids : list of str H3 cell IDs to expand. Mixed resolutions are allowed when expanding by depth. resolution : int, optional Target H3 resolution to expand all cells to. When set, depth is ignored. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. 1 expands each cell to its direct children; 2 to the next level, and so on.

Returns

list of str Expanded H3 cell IDs.

Examples

h3_ids = ["83754efffffffff"] expanded = h3_expand(h3_ids, resolution=5) children = h3_expand(h3_ids, depth=1) grandchildren = h3_expand(h3_ids, depth=2)

Source code in vgrid/conversion/dggscompact/h3compact.py
 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
def h3_expand(h3_ids, resolution=None, depth=None, verbose=True):
    """
    Expand H3 cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are uncompacted
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell (at any resolution) is expanded to its descendants
    ``depth`` levels down (``1`` = direct children, ``2`` = grandchildren, and
    so on).

    Parameters
    ----------
    h3_ids : list of str
        H3 cell IDs to expand. Mixed resolutions are allowed when expanding
        by depth.
    resolution : int, optional
        Target H3 resolution to expand all cells to. When set, ``depth`` is
        ignored.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. ``1`` expands each cell to its direct
        children; ``2`` to the next level, and so on.

    Returns
    -------
    list of str
        Expanded H3 cell IDs.

    Examples
    --------
    >>> h3_ids = ["83754efffffffff"]
    >>> expanded = h3_expand(h3_ids, resolution=5)
    >>> children = h3_expand(h3_ids, depth=1)
    >>> grandchildren = h3_expand(h3_ids, depth=2)
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("h3", resolution)
        return list(h3.uncompact_cells(h3_ids, resolution))

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("h3", depth)
    h3_ids_expand = []
    for h3_id in tqdm(h3_ids, desc="Expanding H3", unit=" cells", disable=not verbose):
        try:
            child_res = h3.get_resolution(h3_id) + depth
            h3_ids_expand.extend(h3.cell_to_children(h3_id, child_res))
        except Exception:
            continue
    return h3_ids_expand

h3compact(input_data, h3_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', fix_antimeridian=None, verbose=True)

Compact H3 cells to their covering set at a given parent depth.

Compacts a set of H3 cells by replacing complete sets of children with their parent cells. Unlike h3.compact_cells, mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing H3 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of H3 cell IDs h3_id : str, optional Name of the column containing H3 cell IDs. Defaults to "h3". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted H3 cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = h3compact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = h3compact(["8e65b56628e0d07", "8e65b56628e0d08"])

Compact only one parent level

result = h3compact(cells, depth=1)

Mean of a numeric field on compacted parents

result = h3compact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = h3compact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/h3compact.py
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
def h3compact(
    input_data,
    h3_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
):
    """
    Compact H3 cells to their covering set at a given parent depth.

    Compacts a set of H3 cells by replacing complete sets of children with their
    parent cells. Unlike ``h3.compact_cells``, mixed input resolutions are allowed
    and ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing H3 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of H3 cell IDs
    h3_id : str, optional
        Name of the column containing H3 cell IDs. Defaults to "h3".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted H3 cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = h3compact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = h3compact(["8e65b56628e0d07", "8e65b56628e0d08"])

    >>> # Compact only one parent level
    >>> result = h3compact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = h3compact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = h3compact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if h3_id is None:
        h3_id = "h3"
    bags, agg_col = prepare_compact_bags(
        input_data,
        h3_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="H3 cells",
    )
    if bags is None:
        print(f"No H3 IDs found in <{h3_id}> field.")
        return

    h3_ids_compact = h3_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not h3_ids_compact:
        return None

    rows = []
    for h3_id_compact in tqdm(
        h3_ids_compact,
        desc="Building H3 compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = h32geo(h3_id_compact, fix_antimeridian=fix_antimeridian)
            cell_resolution = h3.get_resolution(h3_id_compact)
            num_edges = 6
            if h3.is_pentagon(h3_id_compact):
                num_edges = 5
            row = geodesic_dggs_to_geoseries(
                "h3", h3_id_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(h3_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_h3_compacted"
        else:
            ouput_name = "h3_compacted"

    return convert_to_output_format(out_gdf, output_format, ouput_name)

h3compact_cli()

Command-line interface for h3compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/h3compact.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
def h3compact_cli():
    """
    Command-line interface for h3compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="H3 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input H3 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="H3 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Enable Antimeridian fixing",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    fix_antimeridian = args.fix_antimeridian
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = h3compact(
        input_data,
        h3_id=cellid,
        output_format=output_format,
        fix_antimeridian=fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

h3expand(input_data, resolution=None, h3_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) H3 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing H3 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of H3 cell IDs resolution : int, optional Target H3 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. h3_id : str, optional Name of the column containing H3 cell IDs. Defaults to "h3". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded H3 cells in the specified format, or None if expansion fails.

Examples

Expand from file

result = h3expand("cells.geojson", resolution=5) print(f"Expanded to {len(result)} cells")

Expand from list

result = h3expand(["83754efffffffff"], resolution=5)

Expand mixed-resolution cells by relative depth

result = h3expand(cells, depth=1) result = h3expand(cells, depth=2)

Expand to GeoJSON file

result = h3expand("cells.geojson", resolution=5, output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/h3compact.py
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
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
def h3expand(
    input_data,
    resolution=None,
    h3_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) H3 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing H3 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of H3 cell IDs
    resolution : int, optional
        Target H3 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    h3_id : str, optional
        Name of the column containing H3 cell IDs. Defaults to "h3".
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded H3 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> # Expand from file
    >>> result = h3expand("cells.geojson", resolution=5)
    >>> print(f"Expanded to {len(result)} cells")

    >>> # Expand from list
    >>> result = h3expand(["83754efffffffff"], resolution=5)

    >>> # Expand mixed-resolution cells by relative depth
    >>> result = h3expand(cells, depth=1)
    >>> result = h3expand(cells, depth=2)

    >>> # Expand to GeoJSON file
    >>> result = h3expand("cells.geojson", resolution=5, output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if h3_id is None:
        h3_id = "h3"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("h3", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("h3", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, h3_id)
    h3_ids = gdf[h3_id].drop_duplicates().tolist()
    if not h3_ids:
        print(f"No H3 IDs found in <{h3_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(h3.get_resolution(hid) for hid in h3_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            h3_ids_expand = h3_expand(h3_ids, resolution=resolution, verbose=verbose)
        else:
            h3_ids_expand = h3_expand(h3_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your H3 ID field, resolution, or depth."
        )
    if not h3_ids_expand:
        return None
    rows = []
    for h3_id_expand in tqdm(
        h3_ids_expand,
        desc="Building H3 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = h32geo(h3_id_expand, fix_antimeridian=fix_antimeridian)
            cell_resolution = h3.get_resolution(h3_id_expand)
            num_edges = 6
            if h3.is_pentagon(h3_id_expand):
                num_edges = 5
            row = geodesic_dggs_to_geoseries(
                "h3", h3_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    # If output_format is file-based, set ouput_name as just the filename in current directory
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_h3_expanded"
        else:
            ouput_name = "h3_expanded"

    return convert_to_output_format(out_gdf, output_format, ouput_name)

h3expand_cli()

Command-line interface for h3expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/h3compact.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
560
561
562
563
564
def h3expand_cli():
    """
    Command-line interface for h3expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="H3 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input H3 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target H3 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= H3 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="H3 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-fix",
        "--fix_antimeridian",
        type=str,
        choices=[
            "shift",
            "shift_balanced",
            "shift_west",
            "shift_east",
            "split",
            "none",
        ],
        default=None,
        help="Enable Antimeridian fixing",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    resolution = args.resolution
    cellid = args.cellid
    output_format = args.output_format

    result = h3expand(
        input_data,
        resolution=resolution,
        h3_id=cellid,
        output_format=output_format,
        fix_antimeridian=args.fix_antimeridian,
        verbose=args.verbose,
        depth=args.depth,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

S2 Compact Module

This module provides functionality to compact and expand S2 cells with flexible input and output formats.

Key Functions

s2_compact(s2_tokens, depth=-1, bags=None, verbose=True)

Compact a list of S2 cell tokens by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

s2_tokens : list of str S2 cell tokens to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted S2 cell tokens.

Source code in vgrid/conversion/dggscompact/s2compact.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
def s2_compact(s2_tokens, depth=-1, bags=None, verbose=True):
    """
    Compact a list of S2 cell tokens by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    s2_tokens : list of str
        S2 cell tokens to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted S2 cell tokens.
    """
    depth = validate_dggs_compact_depth("s2", depth)
    return compact_cells(
        s2_tokens,
        _s2_parent,
        _s2_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting S2",
    )

s2_expand(s2_tokens, resolution=None, depth=None, verbose=True)

Expand S2 cell tokens to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are uncompacted to that absolute resolution. When only depth is set, resolution is ignored and each cell (at any resolution) is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/s2compact.py
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
def s2_expand(s2_tokens, resolution=None, depth=None, verbose=True):
    """
    Expand S2 cell tokens to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are uncompacted
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell (at any resolution) is expanded to its descendants
    ``depth`` levels down (``1`` = direct children, ``2`` = grandchildren, and
    so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("s2", resolution)
        s2_cells = list({s2.CellId.from_token(token) for token in s2_tokens})
        covering = s2.CellUnion(s2_cells, raw=False)
        expanded_cells = covering.denormalize(resolution, 1)
        return [cell_id.to_token() for cell_id in expanded_cells]

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("s2", depth)
    expanded = []
    for token in tqdm(s2_tokens, desc="Expanding S2", unit=" cells", disable=not verbose):
        try:
            cid = s2.CellId.from_token(str(token))
            expanded.extend(c.to_token() for c in cid.children(cid.level() + depth))
        except Exception:
            continue
    return expanded

s2compact(input_data, s2_token='s2', depth=-1, agg='count', numeric_col=None, output_format='gpd', fix_antimeridian=None, verbose=True)

Compact S2 cells to their covering set at a given parent depth.

Compacts a set of S2 cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing S2 cell tokens. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of S2 cell tokens s2_token : str, default "s2" Name of the column containing S2 cell tokens. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as DGGS binning (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted S2 cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = s2compact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = s2compact(["31752f45cc94", "31752f45cc95"])

Compact only one parent level

result = s2compact(cells, depth=1)

Mean of a numeric field on compacted parents

result = s2compact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = s2compact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/s2compact.py
 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
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
def s2compact(
    input_data,
    s2_token="s2",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
):
    """
    Compact S2 cells to their covering set at a given parent depth.

    Compacts a set of S2 cells by replacing complete sets of children with their
    parent cells. Mixed input resolutions are allowed and ``depth`` limits how
    far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing S2 cell tokens. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of S2 cell tokens
    s2_token : str, default "s2"
        Name of the column containing S2 cell tokens.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as DGGS binning (``count``, ``min``, ``max``,
        ``sum``, ``mean``, ``median``, ``std``, ``var``, ``range``,
        ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted S2 cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = s2compact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = s2compact(["31752f45cc94", "31752f45cc95"])

    >>> # Compact only one parent level
    >>> result = s2compact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = s2compact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = s2compact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not s2_token:
        s2_token = "s2"
    bags, agg_col = prepare_compact_bags(
        input_data,
        s2_token,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="S2 cells",
    )
    if bags is None:
        print(f"No S2 tokens found in <{s2_token}> field.")
        return

    s2_tokens_compact = s2_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not s2_tokens_compact:
        return None

    rows = []
    for s2_token_compact in tqdm(
        s2_tokens_compact,
        desc="Building S2 compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = s22geo(s2_token_compact, fix_antimeridian=fix_antimeridian)
            cell_resolution = s2.CellId.from_token(s2_token_compact).level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", s2_token_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(s2_token_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_s2_compacted"
        else:
            output_name = "s2_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

s2compact_cli()

Command-line interface for s2compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/s2compact.py
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
def s2compact_cli():
    """
    Command-line interface for s2compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="S2 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input S2 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="S2 ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format
    fix_antimeridian = args.fix_antimeridian
    result = s2compact(
        input_data,
        s2_token=cellid,
        output_format=output_format,
        fix_antimeridian=fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

s2expand(input_data, resolution=None, s2_token='s2', output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) S2 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing S2 cell tokens. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of S2 cell tokens resolution : int, optional Target S2 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. s2_token : str, default "s2" Name of the column containing S2 cell tokens. output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded S2 cells in the specified format, or None if expansion fails.

Examples

result = s2expand("cells.geojson", resolution=10) result = s2expand(["31752f45cc94"], resolution=10) result = s2expand(cells, depth=1)

Source code in vgrid/conversion/dggscompact/s2compact.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
def s2expand(
    input_data,
    resolution=None,
    s2_token="s2",
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) S2 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing S2 cell tokens. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of S2 cell tokens
    resolution : int, optional
        Target S2 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    s2_token : str, default "s2"
        Name of the column containing S2 cell tokens.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded S2 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> result = s2expand("cells.geojson", resolution=10)
    >>> result = s2expand(["31752f45cc94"], resolution=10)
    >>> result = s2expand(cells, depth=1)
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("s2", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("s2", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, s2_token)
    s2_tokens = gdf[s2_token].drop_duplicates().tolist()
    if not s2_tokens:
        print(f"No S2 tokens found in <{s2_token}> field.")
        return
    try:
        if resolution is not None:
            s2_cells = [s2.CellId.from_token(token) for token in s2_tokens]
            if not s2_cells:
                print(f"No valid S2 tokens found in <{s2_token}> field.")
                return
            max_res = max(cell.level() for cell in s2_cells)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            s2_tokens_expand = s2_expand(s2_tokens, resolution=resolution, verbose=verbose)
        else:
            s2_tokens_expand = s2_expand(s2_tokens, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your S2 ID field, resolution, or depth."
        )
    if not s2_tokens_expand:
        return None
    rows = []
    for s2_token_expand in tqdm(
        s2_tokens_expand,
        desc="Building S2 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = s22geo(s2_token_expand, fix_antimeridian=fix_antimeridian)
            cell_resolution = s2.CellId.from_token(s2_token_expand).level()
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "s2", s2_token_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_s2_expanded"
        else:
            output_name = "s2_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

s2expand_cli()

Command-line interface for s2expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/s2compact.py
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
def s2expand_cli():
    """
    Command-line interface for s2expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="S2 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input S2 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target S2 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= S2 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="S2 Token field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    add_verbose_argument(parser)
    args = parser.parse_args()
    result = s2expand(
        args.input,
        resolution=args.resolution,
        s2_token=args.cellid,
        output_format=args.output_format,
        fix_antimeridian=args.fix_antimeridian,
        depth=args.depth,
        verbose=args.verbose,
    )
    if args.output_format in STRUCTURED_FORMATS:
        print(result)

A5 Compact Module

This module provides functionality to compact and expand A5 cells with flexible input and output formats.

Key Functions

a5_compact(a5_hexes, depth=-1, bags=None, verbose=True)

Compact a list of A5 hex cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

a5_hexes : list of str A5 hex string cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb (-1 to max_res): - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted A5 hex string cell IDs.

Source code in vgrid/conversion/dggscompact/a5compact.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
def a5_compact(a5_hexes, depth=-1, bags=None, verbose=True):
    """
    Compact a list of A5 hex cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    a5_hexes : list of str
        A5 hex string cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb (``-1`` to ``max_res``):
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted A5 hex string cell IDs.
    """
    depth = validate_dggs_compact_depth("a5", depth)
    return compact_cells(
        a5_hexes,
        _a5_parent,
        _a5_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting A5",
    )

a5_expand(a5_hexes, resolution=None, depth=None, verbose=True)

Expand A5 hex strings to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are uncompacted to that absolute resolution. When only depth is set, resolution is ignored and each cell (at any resolution) is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

a5_hexes : list of str List of A5 hex string cell IDs. Mixed resolutions are allowed when expanding by depth. resolution : int, optional Target A5 resolution to expand all cells to. When set, depth is ignored. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. 1 expands each cell to its direct children; 2 to the next level, and so on.

Returns

list of str List of expanded A5 hex string cell IDs.

Examples

hexes = ["8e65b56628e0d07"] expanded = a5_expand(hexes, resolution=5) children = a5_expand(hexes, depth=1) grandchildren = a5_expand(hexes, depth=2)

Source code in vgrid/conversion/dggscompact/a5compact.py
 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
def a5_expand(a5_hexes, resolution=None, depth=None, verbose=True):
    """
    Expand A5 hex strings to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are uncompacted
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell (at any resolution) is expanded to its descendants
    ``depth`` levels down (``1`` = direct children, ``2`` = grandchildren, and
    so on).

    Parameters
    ----------
    a5_hexes : list of str
        List of A5 hex string cell IDs. Mixed resolutions are allowed when
        expanding by depth.
    resolution : int, optional
        Target A5 resolution to expand all cells to. When set, ``depth`` is
        ignored.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. ``1`` expands each cell to its direct
        children; ``2`` to the next level, and so on.

    Returns
    -------
    list of str
        List of expanded A5 hex string cell IDs.

    Examples
    --------
    >>> hexes = ["8e65b56628e0d07"]
    >>> expanded = a5_expand(hexes, resolution=5)
    >>> children = a5_expand(hexes, depth=1)
    >>> grandchildren = a5_expand(hexes, depth=2)
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("a5", resolution)
        a5_u64s = [a5.hex_to_u64(a5_hex) for a5_hex in a5_hexes]
        a5_u64s_expand = a5.core.compact.uncompact(a5_u64s, resolution)
        return [a5.u64_to_hex(u64) for u64 in a5_u64s_expand]

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("a5", depth)
    a5_hexes_expand = []
    for a5_hex in tqdm(a5_hexes, desc="Expanding A5", unit=" cells", disable=not verbose):
        try:
            u = a5.hex_to_u64(a5_hex)
            child_res = a5.get_resolution(u) + depth
            a5_hexes_expand.extend(
                a5.u64_to_hex(c) for c in a5.cell_to_children(u, child_res)
            )
        except Exception:
            continue
    return a5_hexes_expand

a5compact(input_data, a5_hex=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', options=None, split_antimeridian=False, verbose=True)

Compact A5 cells to their covering set at a given parent depth.

Compacts a set of A5 cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing A5 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of A5 cell IDs a5_hex : str, optional Name of the column containing A5 cell IDs. Defaults to "a5". depth : int, default -1 Compaction depth (-1 to max_res): 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as DGGS binning (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path 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. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted A5 cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = a5compact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = a5compact(["8e65b56628e0d07", "8e65b56628e0d08"])

Compact only one parent level

result = a5compact(cells, depth=1)

Mean of a numeric field on compacted parents

result = a5compact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = a5compact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/a5compact.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def a5compact(
    input_data,
    a5_hex=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    options=None,
    split_antimeridian=False,
    verbose=True,
):
    """
    Compact A5 cells to their covering set at a given parent depth.

    Compacts a set of A5 cells by replacing complete sets of children with their
    parent cells. Mixed input resolutions are allowed and ``depth`` limits how
    far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing A5 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of A5 cell IDs
    a5_hex : str, optional
        Name of the column containing A5 cell IDs. Defaults to "a5".
    depth : int, default -1
        Compaction depth (``-1`` to ``max_res``): ``0`` leaves cells unchanged,
        ``-1`` compact as far as possible, ``1`` merges to the direct parent,
        ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as DGGS binning (``count``, ``min``, ``max``,
        ``sum``, ``mean``, ``median``, ``std``, ``var``, ``range``,
        ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    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.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted A5 cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = a5compact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = a5compact(["8e65b56628e0d07", "8e65b56628e0d08"])

    >>> # Compact only one parent level
    >>> result = a5compact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = a5compact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = a5compact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not a5_hex:
        a5_hex = "a5"
    bags, agg_col = prepare_compact_bags(
        input_data,
        a5_hex,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="A5 cells",
    )
    if bags is None:
        print(f"No A5 IDs found in <{a5_hex}> field.")
        return

    a5_hexes_compact = a5_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not a5_hexes_compact:
        return None
    rows = []
    for a5_hex_compact in tqdm(
        a5_hexes_compact,
        desc="Building A5 compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = a52geo(
                a5_hex_compact, options, split_antimeridian=split_antimeridian
            )
            cell_resolution = a5.get_resolution(a5.hex_to_u64(a5_hex_compact))
            num_edges = 5  # A5 cells are pentagons
            if cell_resolution == 1:
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "a5", a5_hex_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(a5_hex_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_a5_compacted"
        else:
            ouput_name = "a5_compacted"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

a5compact_cli()

Command-line interface for a5compact with flexible input/output.

Source code in vgrid/conversion/dggscompact/a5compact.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
def a5compact_cli():
    """
    Command-line interface for a5compact with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="A5 Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input A5 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="A5 Hex field")
    parser.add_argument(
        "-f", "--output_format", type=str, default="gpd", choices=OUTPUT_FORMATS
    )
    parser.add_argument(
        "-split",
        "--split_antimeridian",
        action="store_true",
        default=False,
        help="Enable Antimeridian splitting",
    )
    parser.add_argument(
        "-options",
        "--options",
        type=str,
        default=None,
        help="JSON string of options to pass to a52geo. "
        "Example: '{\"segments\": 1000}'",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth [-1, A5 max_res]: 0 = no-op, -1 = compact fully "
        "(default), 1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format
    split_antimeridian = args.split_antimeridian

    # 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)}")
            return

    result = a5compact(
        input_data,
        a5_hex=cellid,
        output_format=output_format,
        options=options,
        split_antimeridian=split_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

a5expand(input_data, resolution=None, a5_hex=None, output_format='gpd', options=None, split_antimeridian=False, verbose=True, depth=None)

Expand (uncompact) A5 cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down (1 = direct children, 2 = grandchildren, and so on).

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing A5 cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of A5 cell IDs resolution : int, optional Target A5 resolution to expand the cells to. Must be >= maximum input resolution. When set, depth is ignored. a5_hex : str, optional Name of the column containing A5 cell IDs. Defaults to "a5". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path 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. depth : int, optional Relative expansion depth (1 <= depth <= max_res). Used when resolution is not set. Each input cell is expanded depth levels: 1 = direct children, 2 = grandchildren, and so on.

Returns

geopandas.GeoDataFrame or str or dict or None The expanded A5 cells in the specified format, or None if expansion fails.

Examples

Expand from file

result = a5expand("cells.geojson", resolution=5) print(f"Expanded to {len(result)} cells")

Expand from list

result = a5expand(["8e65b56628e0d07"], resolution=5)

Expand mixed-resolution cells by relative depth

result = a5expand(cells, depth=1) result = a5expand(cells, depth=2)

Expand to GeoJSON file

result = a5expand("cells.geojson", resolution=5, output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/a5compact.py
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
499
500
501
502
503
504
505
506
507
508
509
510
def a5expand(
    input_data,
    resolution=None,
    a5_hex=None,
    output_format="gpd",
    options=None,
    split_antimeridian=False,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) A5 cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down
    (``1`` = direct children, ``2`` = grandchildren, and so on).

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing A5 cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of A5 cell IDs
    resolution : int, optional
        Target A5 resolution to expand the cells to. Must be >= maximum input
        resolution. When set, ``depth`` is ignored.
    a5_hex : str, optional
        Name of the column containing A5 cell IDs. Defaults to "a5".
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    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.
    depth : int, optional
        Relative expansion depth (``1 <= depth <= max_res``). Used when
        ``resolution`` is not set. Each input cell is expanded ``depth``
        levels: ``1`` = direct children, ``2`` = grandchildren, and so on.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The expanded A5 cells in the specified format, or None if expansion fails.

    Examples
    --------
    >>> # Expand from file
    >>> result = a5expand("cells.geojson", resolution=5)
    >>> print(f"Expanded to {len(result)} cells")

    >>> # Expand from list
    >>> result = a5expand(["8e65b56628e0d07"], resolution=5)

    >>> # Expand mixed-resolution cells by relative depth
    >>> result = a5expand(cells, depth=1)
    >>> result = a5expand(cells, depth=2)

    >>> # Expand to GeoJSON file
    >>> result = a5expand("cells.geojson", resolution=5, output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if a5_hex is None:
        a5_hex = "a5"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("a5", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("a5", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, a5_hex)
    a5_hexes = gdf[a5_hex].drop_duplicates().tolist()
    if not a5_hexes:
        print(f"No A5 Hexes found in <{a5_hex}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(
                a5.get_resolution(a5.hex_to_u64(a5_hex)) for a5_hex in a5_hexes
            )
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            a5_hexes_expand = a5_expand(a5_hexes, resolution=resolution, verbose=verbose)
        else:
            a5_hexes_expand = a5_expand(a5_hexes, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your A5 ID field, resolution, or depth."
        )
    if not a5_hexes_expand:
        return None
    rows = []
    for a5_hex_expand in tqdm(
        a5_hexes_expand,
        desc="Building A5 expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = a52geo(
                a5_hex_expand, options, split_antimeridian=split_antimeridian
            )
            cell_resolution = a5.get_resolution(a5.hex_to_u64(a5_hex_expand))
            num_edges = 5  # A5 cells are pentagons
            if cell_resolution == 1:
                num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "a5", a5_hex_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_a5_expanded"
        else:
            ouput_name = "a5_expanded"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

a5expand_cli()

Command-line interface for a5expand with flexible input/output.

Source code in vgrid/conversion/dggscompact/a5compact.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
def a5expand_cli():
    """
    Command-line interface for a5expand with flexible input/output.
    """
    parser = argparse.ArgumentParser(description="A5 Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input A5 (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target A5 resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= A5 max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="A5 Hex field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-split",
        "--split_antimeridian",
        action="store_true",
        default=False,
        help="Enable Antimeridian splitting",
    )
    parser.add_argument(
        "-options",
        "--options",
        type=str,
        default=None,
        help="JSON string of options to pass to a52geo. "
        "Example: '{\"segments\": 1000}'",
    )
    add_verbose_argument(parser)
    args = parser.parse_args()
    input_data = args.input
    resolution = args.resolution
    cellid = args.cellid
    output_format = args.output_format
    split_antimeridian = args.split_antimeridian

    # 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)}")
            return

    result = a5expand(
        input_data,
        resolution=resolution,
        a5_hex=cellid,
        output_format=output_format,
        options=options,
        split_antimeridian=split_antimeridian,
        depth=args.depth,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

RHEALPix Compact Module

This module provides functionality to compact and expand RHEALPix cells with flexible input and output formats.

Key Functions

rhealpix_compact(rhealpix_ids, depth=-1, bags=None, verbose=True)

Compact a list of RHEALPix cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

rhealpix_ids : list of str List of RHEALPix cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted RHEALPix cell IDs.

Examples

rhealpix_ids = ["A0", "A1", "A2", "A3"] compacted = rhealpix_compact(rhealpix_ids) print(f"Compacted {len(rhealpix_ids)} cells to {len(compacted)} cells")

Source code in vgrid/conversion/dggscompact/rhealpixcompact.py
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
def rhealpix_compact(rhealpix_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of RHEALPix cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    rhealpix_ids : list of str
        List of RHEALPix cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted RHEALPix cell IDs.

    Examples
    --------
    >>> rhealpix_ids = ["A0", "A1", "A2", "A3"]
    >>> compacted = rhealpix_compact(rhealpix_ids)
    >>> print(f"Compacted {len(rhealpix_ids)} cells to {len(compacted)} cells")
    """
    depth = validate_dggs_compact_depth("rhealpix", depth)
    return compact_cells(
        rhealpix_ids,
        _rhealpix_parent,
        _rhealpix_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting rHEALPix",
    )

rhealpix_expand(rhealpix_ids, resolution=None, depth=None, verbose=True)

Expand RHEALPix cells to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Returns cell objects (callers typically convert with str(cell)).

Source code in vgrid/conversion/dggscompact/rhealpixcompact.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def rhealpix_expand(rhealpix_ids, resolution=None, depth=None, verbose=True):
    """
    Expand RHEALPix cells to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).

    Returns cell objects (callers typically convert with ``str(cell)``).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("rhealpix", resolution)
        expand_cells = []
        for rhealpix_id in tqdm(rhealpix_ids, desc="Expanding rHEALPix", unit=" cells", disable=not verbose):
            rhealpix_uids = (rhealpix_id[0],) + tuple(map(int, rhealpix_id[1:]))
            rhealpix_cell = rhealpix_dggs.cell(rhealpix_uids)
            cell_resolution = rhealpix_cell.resolution
            if cell_resolution >= resolution:
                expand_cells.append(rhealpix_cell)
            else:
                expand_cells.extend(rhealpix_cell.subcells(resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("rhealpix", depth)
    expand_cells = []
    for rhealpix_id in tqdm(rhealpix_ids, desc="Expanding rHEALPix", unit=" cells", disable=not verbose):
        try:
            rhealpix_uids = (rhealpix_id[0],) + tuple(map(int, rhealpix_id[1:]))
            rhealpix_cell = rhealpix_dggs.cell(rhealpix_uids)
            expand_cells.extend(
                rhealpix_cell.subcells(rhealpix_cell.resolution + depth)
            )
        except Exception:
            continue
    return expand_cells

rhealpixcompact(input_data, rhealpix_id='rhealpix', depth=-1, agg='count', numeric_col=None, output_format='gpd', fix_antimeridian=None, verbose=True)

Compact RHEALPix cells to their covering set at a given parent depth.

Compacts a set of RHEALPix cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing RHEALPix cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of RHEALPix cell IDs rhealpix_id : str, default "rhealpix" Name of the column containing RHEALPix cell IDs. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as DGGS binning (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path fix_antimeridian : Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none When True, apply antimeridian fixing to the resulting polygons. Defaults to False when None or omitted. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted RHEALPix cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = rhealpixcompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = rhealpixcompact(["A0", "A1", "A2", "A3"])

Compact only one parent level

result = rhealpixcompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = rhealpixcompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = rhealpixcompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/rhealpixcompact.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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 rhealpixcompact(
    input_data,
    rhealpix_id="rhealpix",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
):
    """
    Compact RHEALPix cells to their covering set at a given parent depth.

    Compacts a set of RHEALPix cells by replacing complete sets of children with
    their parent cells. Mixed input resolutions are allowed and ``depth`` limits
    how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing RHEALPix cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of RHEALPix cell IDs
    rhealpix_id : str, default "rhealpix"
        Name of the column containing RHEALPix cell IDs.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as DGGS binning (``count``, ``min``, ``max``,
        ``sum``, ``mean``, ``median``, ``std``, ``var``, ``range``,
        ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    fix_antimeridian : Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        When True, apply antimeridian fixing to the resulting polygons.
        Defaults to False when None or omitted.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted RHEALPix cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = rhealpixcompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = rhealpixcompact(["A0", "A1", "A2", "A3"])

    >>> # Compact only one parent level
    >>> result = rhealpixcompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = rhealpixcompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = rhealpixcompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not rhealpix_id:
        rhealpix_id = "rhealpix"
    bags, agg_col = prepare_compact_bags(
        input_data,
        rhealpix_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="rHEALPix cells",
    )
    if bags is None:
        print(f"No rHEALPix tokens found in <{rhealpix_id}> field.")
        return

    rhealpix_tokens_compact = rhealpix_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not rhealpix_tokens_compact:
        return None
    rows = []
    for rhealpix_token_compact in tqdm(
        rhealpix_tokens_compact,
        desc="Building rHEALPix compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = rhealpix2geo(
                rhealpix_token_compact, fix_antimeridian=fix_antimeridian
            )
            rhealpix_uids = (rhealpix_token_compact[0],) + tuple(
                map(int, rhealpix_token_compact[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",
                rhealpix_token_compact,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            row[agg_col] = aggregate_values(bags.get(rhealpix_token_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_rhealpix_compacted"
        else:
            output_name = "rhealpix_compacted"
    return convert_to_output_format(out_gdf, output_format, output_name)

rhealpixexpand(input_data, resolution=None, rhealpix_id='rhealpix', output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) RHEALPix cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/rhealpixcompact.py
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
def rhealpixexpand(
    input_data,
    resolution=None,
    rhealpix_id="rhealpix",
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) RHEALPix cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("rhealpix", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("rhealpix", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, rhealpix_id)
    rhealpix_ids = sorted(gdf[rhealpix_id].drop_duplicates().tolist())
    if not rhealpix_ids:
        print(f"No rHEALPix tokens found in <{rhealpix_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(get_rhealpix_resolution(token) for token in rhealpix_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            expanded_cells = rhealpix_expand(rhealpix_ids, resolution=resolution, verbose=verbose)
        else:
            expanded_cells = rhealpix_expand(rhealpix_ids, depth=depth, verbose=verbose)
        rhealpix_tokens_expand = [str(cell) for cell in expanded_cells]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your rHEALPix ID field, resolution, or depth."
        )
    if not rhealpix_tokens_expand:
        return None
    rows = []
    for rhealpix_token_expand in tqdm(
        rhealpix_tokens_expand,
        desc="Building rHEALPix expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = rhealpix2geo(
                rhealpix_token_expand, fix_antimeridian=fix_antimeridian
            )
            rhealpix_uids = (rhealpix_token_expand[0],) + tuple(
                map(int, rhealpix_token_expand[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",
                rhealpix_token_expand,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_rhealpix_expanded"
        else:
            output_name = "rhealpix_expanded"
    return convert_to_output_format(out_gdf, output_format, output_name)

DGGAL Compact Module

This module provides functionality to compact and expand DGGAL cells with flexible input and output formats.

Key Functions

dggal_compact(dggs_type, zone_ids, depth=-1, bags=None, verbose=True)

Compact a list of DGGAL cell IDs by replacing complete child sets with parents.

A zone may have multiple parents. Groups cells by every parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

dggs_type : str DGGAL DGGS type (e.g., "isea3h", "isea4t", "rhealpix"). zone_ids : list of str DGGAL zone IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted DGGAL zone IDs.

Source code in vgrid/conversion/dggscompact/dggalcompact.py
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
def dggal_compact(dggs_type, zone_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of DGGAL cell IDs by replacing complete child sets with parents.

    A zone may have multiple parents. Groups cells by every parent and replaces a
    parent when every child is present. Repeats until ``depth`` parent levels have
    been applied, or until no further compaction is possible.

    Parameters
    ----------
    dggs_type : str
        DGGAL DGGS type (e.g., "isea3h", "isea4t", "rhealpix").
    zone_ids : list of str
        DGGAL zone IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted DGGAL zone IDs.
    """
    dggs_type = validate_dggal_type(dggs_type)
    depth = validate_dggs_compact_depth(
        dggs_type, depth, max_res=int(DGGAL_TYPES[dggs_type]["max_res"])
    )
    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = getattr(dggal, dggs_class_name)()

    def parent_fn(zone_id):
        zone = dggrs.getZoneFromTextID(zone_id)
        if dggrs.getZoneLevel(zone) <= 0:
            return None
        return [dggrs.getZoneTextID(p) for p in dggrs.getZoneParents(zone)]

    def children_fn(parent_zone_id):
        parent_zone = dggrs.getZoneFromTextID(parent_zone_id)
        return {dggrs.getZoneTextID(z) for z in dggrs.getZoneChildren(parent_zone)}

    return compact_cells(
        zone_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting DGGAL",
    )

dggal_expand(dggs_type, zone_ids, resolution=None, depth=None, verbose=True)

Expand DGGAL zone IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/dggalcompact.py
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
def dggal_expand(dggs_type, zone_ids, resolution=None, depth=None, verbose=True):
    """
    Expand DGGAL zone IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    dggs_type = validate_dggal_type(dggs_type)
    max_res = int(DGGAL_TYPES[dggs_type]["max_res"])
    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = getattr(dggal, dggs_class_name)()

    if resolution is not None:
        resolution = validate_dggs_expand_resolution(
            dggs_type, resolution, max_res=max_res
        )
        expanded_cells = []
        for zid in tqdm(zone_ids, desc="Expanding DGGAL", unit=" cells", disable=not verbose):
            try:
                zone = dggrs.getZoneFromTextID(zid)
                current_res = dggrs.getZoneLevel(zone)

                if resolution < current_res:
                    print(
                        f"Warning: Target resolution {resolution} is lower than "
                        f"current resolution {current_res} for zone {zid}"
                    )
                    continue

                if resolution == current_res:
                    expanded_cells.append(zid)
                else:
                    sub_zones = dggrs.getSubZones(zone, resolution - current_res)
                    for sub_zone in sub_zones:
                        expanded_cells.append(dggrs.getZoneTextID(sub_zone))
            except Exception as e:
                print(f"Warning: Could not expand zone {zid}: {e}")
                continue
        return expanded_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth(dggs_type, depth, max_res=max_res)
    expanded_cells = []
    for zid in tqdm(zone_ids, desc="Expanding DGGAL", unit=" cells", disable=not verbose):
        try:
            zone = dggrs.getZoneFromTextID(zid)
            for sub_zone in dggrs.getSubZones(zone, depth):
                expanded_cells.append(dggrs.getZoneTextID(sub_zone))
        except Exception:
            continue
    return expanded_cells

dggalcompact(dggs_type, input_data, zone_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', split_antimeridian=False, verbose=True)

Compact DGGAL cells to their covering set at a given parent depth.

Compacts a set of DGGAL cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

dggs_type : str DGGAL DGGS type (e.g., "isea3h", "isea4t", "rhealpix"). input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing DGGAL zone IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of DGGAL zone IDs zone_id : str, optional Name of the column containing DGGAL zone IDs. Defaults to "dggal_{dggs_type}". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path split_antimeridian : bool, optional When True, apply antimeridian fixing to the resulting polygons. Defaults to False when None or omitted. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted DGGAL cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = dggalcompact("isea3h", "cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = dggalcompact("isea3h", ["A0", "A1", "A2", "A3"])

Compact only one parent level

result = dggalcompact("isea3h", cells, depth=1)

Mean of a numeric field on compacted parents

result = dggalcompact("isea3h", cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = dggalcompact("isea3h", "cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/dggalcompact.py
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
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
def dggalcompact(
    dggs_type,
    input_data,
    zone_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    split_antimeridian=False,
    verbose=True,
):
    """
    Compact DGGAL cells to their covering set at a given parent depth.

    Compacts a set of DGGAL cells by replacing complete sets of children with their
    parent cells. Mixed input resolutions are allowed and ``depth`` limits how far
    up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    dggs_type : str
        DGGAL DGGS type (e.g., "isea3h", "isea4t", "rhealpix").
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing DGGAL zone IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of DGGAL zone IDs
    zone_id : str, optional
        Name of the column containing DGGAL zone IDs. Defaults to "dggal_{dggs_type}".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent (``count``, ``min``, ``max``, ``sum``, ``mean``, ``median``,
        ``std``, ``var``, ``range``, ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    split_antimeridian : bool, optional
        When True, apply antimeridian fixing to the resulting polygons.
        Defaults to False when None or omitted.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted DGGAL cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = dggalcompact("isea3h", "cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = dggalcompact("isea3h", ["A0", "A1", "A2", "A3"])

    >>> # Compact only one parent level
    >>> result = dggalcompact("isea3h", cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = dggalcompact("isea3h", cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = dggalcompact("isea3h", "cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    dggs_type = validate_dggal_type(dggs_type)
    if not zone_id:
        zone_id = f"dggal_{dggs_type}"

    bags, agg_col = prepare_compact_bags(
        input_data,
        zone_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="DGGAL cells",
    )
    if bags is None:
        print(f"No DGGAL IDs found in <{zone_id}> field.")
        return

    # Create the appropriate DGGS instance
    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = getattr(dggal, dggs_class_name)()

    dggal_ids_compact = dggal_compact(
        dggs_type, list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )

    if not dggal_ids_compact:
        print("Warning: Compaction returned no results, returning original data")
        gdf = process_input_data_compact(input_data, zone_id)
        return convert_to_output_format(gdf, output_format, f"{dggs_type}_original")

    rows = []
    for dggal_id_compact in tqdm(
        dggal_ids_compact,
        desc="Building DGGAL compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            # Get zone object to get resolution directly
            zone = dggrs.getZoneFromTextID(dggal_id_compact)
            cell_resolution = dggrs.getZoneLevel(zone)
            cell_polygon = dggal2geo(
                dggs_type, dggal_id_compact, split_antimeridian=split_antimeridian
            )
            num_edges = dggrs.countZoneEdges(zone)
            row = geodesic_dggs_to_geoseries(
                f"dggal_{dggs_type}",
                dggal_id_compact,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            row[agg_col] = aggregate_values(bags.get(dggal_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_dggal_compacted"
        else:
            output_name = "dggal_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

dggalexpand(dggs_type, input_data, resolution=None, zone_id=None, output_format='gpd', split_antimeridian=False, verbose=True, depth=None)

Expand (uncompact) DGGAL cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/dggalcompact.py
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
def dggalexpand(
    dggs_type,
    input_data,
    resolution=None,
    zone_id=None,
    output_format="gpd",
    split_antimeridian=False,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) DGGAL cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    dggs_type = validate_dggal_type(dggs_type)
    max_res = int(DGGAL_TYPES[dggs_type]["max_res"])
    if zone_id is None:
        zone_id = f"dggal_{dggs_type}"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution(
            dggs_type, resolution, max_res=max_res
        )
    elif depth is not None:
        depth = validate_dggs_expand_depth(dggs_type, depth, max_res=max_res)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, zone_id)
    zone_ids = gdf[zone_id].drop_duplicates().tolist()

    if not zone_ids:
        print(f"No Zone IDs found in <{zone_id}> field.")
        return

    dggs_class_name = DGGAL_TYPES[dggs_type]["class_name"]
    dggrs = getattr(dggal, dggs_class_name)()

    try:
        if resolution is not None:
            max_input_res = 0
            for zid in zone_ids:
                try:
                    zone = dggrs.getZoneFromTextID(zid)
                    max_input_res = max(max_input_res, dggrs.getZoneLevel(zone))
                except Exception:
                    continue

            if resolution < max_input_res:
                print(f"Target expand resolution ({resolution}) must >= {max_input_res}.")
                return None
            zone_ids_expand = dggal_expand(
                dggs_type, zone_ids, resolution=resolution, verbose=verbose
            )
        else:
            zone_ids_expand = dggal_expand(dggs_type, zone_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Zone ID field, resolution, or depth."
        )
    if not zone_ids_expand:
        return None

    rows = []
    for zone_id_expand in tqdm(
        zone_ids_expand,
        desc="Building DGGAL expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            zone = dggrs.getZoneFromTextID(zone_id_expand)
            cell_resolution = dggrs.getZoneLevel(zone)
            cell_polygon = dggal2geo(
                dggs_type, zone_id_expand, split_antimeridian=split_antimeridian
            )
            num_edges = dggrs.countZoneEdges(zone)
            row = geodesic_dggs_to_geoseries(
                f"dggal_{dggs_type}",
                zone_id_expand,
                cell_resolution,
                cell_polygon,
                num_edges,
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_dggal_expanded"
        else:
            ouput_name = "dggal_expanded"

    return convert_to_output_format(out_gdf, output_format, ouput_name)

ISEA4T Compact Module

This module provides functionality to compact and expand ISEA4T cells with flexible input and output formats.

Key Functions

get_isea4t_cell_children(isea4t_cell, resolution)

Recursively expands a DGGS cell until all children reach the desired resolution.

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def get_isea4t_cell_children(isea4t_cell, resolution):
    """Recursively expands a DGGS cell until all children reach the desired resolution."""
    cell_id = isea4t_cell.get_cell_id()
    cell_resolution = len(cell_id) - 2

    if cell_resolution >= resolution:
        return [
            isea4t_cell
        ]  # Base case: return the cell if it meets/exceeds resolution

    expanded_cells = []
    children = isea4t_dggs.get_dggs_cell_children(isea4t_cell)

    for child in children:
        expanded_cells.extend(get_isea4t_cell_children(child, resolution))

    return expanded_cells

isea4t_compact(isea4t_ids, depth=-1, bags=None, verbose=True)

Compact a list of ISEA4T cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

isea4t_ids : list of str ISEA4T cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted ISEA4T cell IDs.

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
 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
def isea4t_compact(isea4t_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of ISEA4T cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    isea4t_ids : list of str
        ISEA4T cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted ISEA4T cell IDs.
    """
    depth = validate_dggs_compact_depth("isea4t", depth)

    def parent_fn(cell_id):
        if len(cell_id) > 2:
            return cell_id[:-1]
        return None

    def children_fn(parent):
        return {
            child.get_cell_id()
            for child in isea4t_dggs.get_dggs_cell_children(DggsCell(parent))
        }

    return compact_cells(
        isea4t_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting ISEA4T",
    )

isea4t_expand(isea4t_ids, resolution=None, depth=None, verbose=True)

Expand ISEA4T cells to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Returns cell objects (callers typically map .get_cell_id()).

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
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
def isea4t_expand(isea4t_ids, resolution=None, depth=None, verbose=True):
    """
    Expand ISEA4T cells to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).

    Returns cell objects (callers typically map ``.get_cell_id()``).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea4t", resolution)
        expand_cells = []
        for isea4t_id in tqdm(isea4t_ids, desc="Expanding ISEA4T", unit=" cells", disable=not verbose):
            isea4t_cell = DggsCell(isea4t_id)
            expand_cells.extend(get_isea4t_cell_children(isea4t_cell, resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("isea4t", depth)
    expand_cells = []
    for isea4t_id in tqdm(isea4t_ids, desc="Expanding ISEA4T", unit=" cells", disable=not verbose):
        try:
            current = get_isea4t_resolution(isea4t_id)
            expand_cells.extend(
                get_isea4t_cell_children(DggsCell(isea4t_id), current + depth)
            )
        except Exception:
            continue
    return expand_cells

isea4tcompact(input_data, isea4t_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', fix_antimeridian=None, verbose=True)

Compact ISEA4T cells to their covering set at a given parent depth.

Compacts a set of ISEA4T cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing ISEA4T cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of ISEA4T cell IDs isea4t_id : str, optional Name of the column containing ISEA4T cell IDs. Defaults to "isea4t". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path fix_antimeridian : str, optional Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none Defaults to None when omitted. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted ISEA4T cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = isea4tcompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = isea4tcompact(["A0", "A1", "A2", "A3"])

Compact only one parent level

result = isea4tcompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = isea4tcompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = isea4tcompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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 isea4tcompact(
    input_data,
    isea4t_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
):
    """
    Compact ISEA4T cells to their covering set at a given parent depth.

    Compacts a set of ISEA4T cells by replacing complete sets of children with their
    parent cells. Mixed input resolutions are allowed and ``depth`` limits how far
    up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing ISEA4T cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of ISEA4T cell IDs
    isea4t_id : str, optional
        Name of the column containing ISEA4T cell IDs. Defaults to "isea4t".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent (``count``, ``min``, ``max``, ``sum``, ``mean``, ``median``,
        ``std``, ``var``, ``range``, ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    fix_antimeridian : str, optional
        Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted ISEA4T cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = isea4tcompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = isea4tcompact(["A0", "A1", "A2", "A3"])

    >>> # Compact only one parent level
    >>> result = isea4tcompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = isea4tcompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = isea4tcompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not isea4t_id:
        isea4t_id = "isea4t"
    bags, agg_col = prepare_compact_bags(
        input_data,
        isea4t_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="ISEA4T cells",
    )
    if bags is None:
        print(f"No ISEA4T isea4t_ids found in <{isea4t_id}> field.")
        return
    isea4t_ids_compact = isea4t_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not isea4t_ids_compact:
        return None
    rows = []
    for isea4t_id_compact in tqdm(
        isea4t_ids_compact,
        desc="Building ISEA4T compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea4t2geo(
                isea4t_id_compact, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea4t_resolution(isea4t_id_compact)
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "isea4t", isea4t_id_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(isea4t_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_isea4t_compacted"
        else:
            ouput_name = "isea4t_compacted"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

isea4texpand(input_data, resolution=None, isea4t_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) ISEA4T cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/isea4tcompact.py
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
def isea4texpand(
    input_data,
    resolution=None,
    isea4t_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) ISEA4T cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if isea4t_id is None:
        isea4t_id = "isea4t"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea4t", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("isea4t", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")
    gdf = process_input_data_compact(input_data, isea4t_id)
    isea4t_ids = gdf[isea4t_id].drop_duplicates().tolist()
    if not isea4t_ids:
        print(f"No ISEA4T IDs found in <{isea4t_id}> field.")
        return
    try:
        if resolution is not None:
            max_res = max(get_isea4t_resolution(cid) for cid in isea4t_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            isea4t_cells_expand = isea4t_expand(isea4t_ids, resolution=resolution, verbose=verbose)
        else:
            isea4t_cells_expand = isea4t_expand(isea4t_ids, depth=depth, verbose=verbose)
        isea4t_ids_expand = [c.get_cell_id() for c in isea4t_cells_expand]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your ISEA4T ID field, resolution, or depth."
        )
    if not isea4t_ids_expand:
        return None
    rows = []
    for isea4t_id_expand in tqdm(
        isea4t_ids_expand,
        desc="Building ISEA4T expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea4t2geo(
                isea4t_id_expand, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea4t_resolution(isea4t_id_expand)
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "isea4t", isea4t_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue
    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")
    ouput_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            ouput_name = f"{base}_isea4t_expanded"
        else:
            ouput_name = "isea4t_expanded"
    return convert_to_output_format(out_gdf, output_format, ouput_name)

ISEA3H Compact Module

This module provides functionality to compact and expand ISEA3H cells with flexible input and output formats.

Key Functions

get_isea3h_cell_children(isea3h_cell, resolution)

Recursively expands a DGGS cell until all children reach the desired resolution.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def get_isea3h_cell_children(isea3h_cell, resolution):
    """Recursively expands a DGGS cell until all children reach the desired resolution."""
    isea3h2point = isea3h_dggs.convert_dggs_cell_to_point(isea3h_cell)
    cell_accuracy = isea3h2point._accuracy
    cell_resolution = ISEA3H_ACCURACY_RES_DICT.get(cell_accuracy)

    if cell_resolution >= resolution:
        return [
            isea3h_cell
        ]  # Base case: return the cell if it meets/exceeds resolution

    expanded_cells = []
    children = isea3h_dggs.get_dggs_cell_children(isea3h_cell)

    for child in children:
        expanded_cells.extend(get_isea3h_cell_children(child, resolution))

    return expanded_cells

get_isea3h_resolution(isea3h_id)

Get the resolution of an ISEA3H cell ID.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
 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
def get_isea3h_resolution(isea3h_id):
    """Get the resolution of an ISEA3H cell ID."""
    try:
        isea3h_cell = DggsCell(isea3h_id)
        cell_polygon = isea3h2geo(isea3h_id)
        cell_area_perimeter = geod.geometry_area_perimeter(cell_polygon)
        cell_perimeter = abs(cell_area_perimeter[1])

        isea3h2point = isea3h_dggs.convert_dggs_cell_to_point(isea3h_cell)
        cell_accuracy = isea3h2point._accuracy

        avg_edge_len = cell_perimeter / 6
        cell_resolution = ISEA3H_ACCURACY_RES_DICT.get(cell_accuracy)

        if cell_resolution == 0:  # icosahedron faces at resolution = 0
            avg_edge_len = cell_perimeter / 3

        if cell_accuracy == 0.0:
            if round(avg_edge_len, 2) == 0.06:
                cell_resolution = 33
            elif round(avg_edge_len, 2) == 0.03:
                cell_resolution = 34
            elif round(avg_edge_len, 2) == 0.02:
                cell_resolution = 35
            elif round(avg_edge_len, 2) == 0.01:
                cell_resolution = 36
            elif round(avg_edge_len, 3) == 0.007:
                cell_resolution = 37
            elif round(avg_edge_len, 3) == 0.004:
                cell_resolution = 38
            elif round(avg_edge_len, 3) == 0.002:
                cell_resolution = 39
            elif round(avg_edge_len, 3) <= 0.001:
                cell_resolution = 40

        return cell_resolution
    except Exception as e:
        raise ValueError(f"Invalid cell ID <{isea3h_id}> : {e}")

isea3h_compact(isea3h_ids, depth=-1, bags=None, verbose=True)

Compact a list of ISEA3H cell IDs by replacing complete child sets with parents.

A cell may have multiple parents. Groups cells by every parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

isea3h_ids : list of str ISEA3H cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted ISEA3H cell IDs.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
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
def isea3h_compact(isea3h_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of ISEA3H cell IDs by replacing complete child sets with parents.

    A cell may have multiple parents. Groups cells by every parent and replaces a
    parent when every child is present. Repeats until ``depth`` parent levels have
    been applied, or until no further compaction is possible.

    Parameters
    ----------
    isea3h_ids : list of str
        ISEA3H cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted ISEA3H cell IDs.
    """
    depth = validate_dggs_compact_depth("isea3h", depth)

    def parent_fn(cell_id):
        cell = DggsCell(cell_id)
        return [p.get_cell_id() for p in isea3h_dggs.get_dggs_cell_parents(cell)]

    def children_fn(parent_id):
        return {
            c.get_cell_id()
            for c in isea3h_dggs.get_dggs_cell_children(DggsCell(parent_id))
        }

    return compact_cells(
        isea3h_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting ISEA3H",
    )

isea3h_expand(isea3h_ids, resolution=None, depth=None, verbose=True)

Expand ISEA3H cells to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Returns cell objects (callers typically map .get_cell_id()).

Source code in vgrid/conversion/dggscompact/isea3hcompact.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
def isea3h_expand(isea3h_ids, resolution=None, depth=None, verbose=True):
    """
    Expand ISEA3H cells to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).

    Returns cell objects (callers typically map ``.get_cell_id()``).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea3h", resolution)
        expand_cells = []
        for isea3h_id in tqdm(isea3h_ids, desc="Expanding ISEA3H", unit=" cells", disable=not verbose):
            isea3h_cell = DggsCell(isea3h_id)
            expand_cells.extend(get_isea3h_cell_children(isea3h_cell, resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("isea3h", depth)
    expand_cells = []
    for isea3h_id in tqdm(isea3h_ids, desc="Expanding ISEA3H", unit=" cells", disable=not verbose):
        try:
            current = get_isea3h_resolution(isea3h_id)
            expand_cells.extend(
                get_isea3h_cell_children(DggsCell(isea3h_id), current + depth)
            )
        except Exception:
            continue
    return expand_cells

isea3hcompact(input_data, isea3h_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', fix_antimeridian=None, verbose=True)

Compact ISEA3H cells to their covering set at a given parent depth.

Compacts a set of ISEA3H cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing ISEA3H cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of ISEA3H cell IDs isea3h_id : str, optional Name of the column containing ISEA3H cell IDs. Defaults to "isea3h". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path fix_antimeridian : str, optional Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none Defaults to None when omitted. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted ISEA3H cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = isea3hcompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = isea3hcompact(["A0", "A1", "A2", "A3", "A4", "A5"])

Compact only one parent level

result = isea3hcompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = isea3hcompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = isea3hcompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
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
def isea3hcompact(
    input_data,
    isea3h_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
):
    """
    Compact ISEA3H cells to their covering set at a given parent depth.

    Compacts a set of ISEA3H cells by replacing complete sets of children with their
    parent cells. Mixed input resolutions are allowed and ``depth`` limits how far
    up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing ISEA3H cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of ISEA3H cell IDs
    isea3h_id : str, optional
        Name of the column containing ISEA3H cell IDs. Defaults to "isea3h".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent (``count``, ``min``, ``max``, ``sum``, ``mean``, ``median``,
        ``std``, ``var``, ``range``, ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    fix_antimeridian : str, optional
        Antimeridian fixing method: shift, shift_balanced, shift_west, shift_east, split, none
        Defaults to None when omitted.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted ISEA3H cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = isea3hcompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = isea3hcompact(["A0", "A1", "A2", "A3", "A4", "A5"])

    >>> # Compact only one parent level
    >>> result = isea3hcompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = isea3hcompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = isea3hcompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not isea3h_id:
        isea3h_id = "isea3h"

    bags, agg_col = prepare_compact_bags(
        input_data,
        isea3h_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="ISEA3H cells",
    )
    if bags is None:
        print(f"No ISEA3H IDs found in <{isea3h_id}> field.")
        return

    isea3h_ids_compact = isea3h_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not isea3h_ids_compact:
        return None

    rows = []
    for isea3h_id_compact in tqdm(
        isea3h_ids_compact,
        desc="Building ISEA3H compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea3h2geo(
                isea3h_id_compact, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea3h_resolution(isea3h_id_compact)
            num_edges = 6  # ISEA3H cells are hexagonal
            row = geodesic_dggs_to_geoseries(
                "isea3h", isea3h_id_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(isea3h_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_isea3h_compacted"
        else:
            output_name = "isea3h_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

isea3hcompact_cli()

Command-line interface for ISEA3H compaction.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
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
def isea3hcompact_cli():
    """Command-line interface for ISEA3H compaction."""
    parser = argparse.ArgumentParser(description="ISEA3H Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input ISEA3H (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="ISEA3H ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    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",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )
    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = isea3hcompact(
        input_data,
        isea3h_id=cellid,
        output_format=output_format,
        fix_antimeridian=args.fix_antimeridian,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )
    if output_format in STRUCTURED_FORMATS:
        print(result)

isea3hexpand(input_data, resolution=None, isea3h_id=None, output_format='gpd', fix_antimeridian=None, verbose=True, depth=None)

Expand (uncompact) ISEA3H cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def isea3hexpand(
    input_data,
    resolution=None,
    isea3h_id=None,
    output_format="gpd",
    fix_antimeridian=None,
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) ISEA3H cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if isea3h_id is None:
        isea3h_id = "isea3h"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("isea3h", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("isea3h", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, isea3h_id)
    isea3h_ids = gdf[isea3h_id].drop_duplicates().tolist()

    if not isea3h_ids:
        print(f"No ISEA3H IDs found in <{isea3h_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(get_isea3h_resolution(cid) for cid in isea3h_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            isea3h_cells_expand = isea3h_expand(isea3h_ids, resolution=resolution, verbose=verbose)
        else:
            isea3h_cells_expand = isea3h_expand(isea3h_ids, depth=depth, verbose=verbose)
        isea3h_ids_expand = [cell.get_cell_id() for cell in isea3h_cells_expand]
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your ISEA3H ID field, resolution, or depth."
        )

    if not isea3h_ids_expand:
        return None

    rows = []
    for isea3h_id_expand in tqdm(
        isea3h_ids_expand,
        desc="Building ISEA3H expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = isea3h2geo(
                isea3h_id_expand, fix_antimeridian=fix_antimeridian
            )
            cell_resolution = get_isea3h_resolution(isea3h_id_expand)
            num_edges = 6
            row = geodesic_dggs_to_geoseries(
                "isea3h", isea3h_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    file_formats = ["csv", "geojson", "shapefile", "gpkg", "parquet", "geoparquet"]
    output_name = None
    if output_format in file_formats:
        ext_map = {
            "csv": ".csv",
            "geojson": ".geojson",
            "shapefile": ".shp",
            "gpkg": ".gpkg",
            "parquet": ".parquet",
            "geoparquet": ".parquet",
        }
        ext = ext_map.get(output_format, "")
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_isea3h_expanded{ext}"
        else:
            output_name = f"isea3h_expanded{ext}"

    return convert_to_output_format(out_gdf, output_format, output_name)

isea3hexpand_cli()

Command-line interface for ISEA3H expansion.

Source code in vgrid/conversion/dggscompact/isea3hcompact.py
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
596
597
598
599
600
601
602
603
604
605
606
def isea3hexpand_cli():
    """Command-line interface for ISEA3H expansion."""
    parser = argparse.ArgumentParser(description="ISEA3H Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input ISEA3H (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target ISEA3H resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= ISEA3H max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="ISEA3H ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default=None,
        help="Output format (None, csv, geojson, shapefile, gpd, geojson_dict, gpkg, geoparquet)",
    )
    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",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    input_data = args.input
    output_format = args.output_format
    if platform.system() == "Windows":
        result = isea3hexpand(
            input_data,
            resolution=args.resolution,
            isea3h_id=args.cellid,
            output_format=output_format,
            fix_antimeridian=args.fix_antimeridian,
            depth=args.depth,
            verbose=args.verbose,
        )

        if output_format is None:
            print(result)
        elif output_format in [
            "csv",
            "geojson",
            "geojson_dict",
            "shapefile",
            "gpkg",
            "geoparquet",
            "parquet",
        ]:
            if isinstance(input_data, str):
                base = os.path.splitext(os.path.basename(input_data))[0]
                ext_map = {
                    "csv": ".csv",
                    "geojson": ".geojson",
                    "geojson_dict": ".geojson",
                    "shapefile": ".shp",
                    "gpkg": ".gpkg",
                    "parquet": ".parquet",
                    "geoparquet": ".parquet",
                }
                ext = ext_map.get(output_format, "")
                output = f"{base}_isea3h_expanded{ext}"
            else:
                output = f"isea3h_expanded{ext_map.get(output_format, '')}"
            print(f"Output written to {output}")
        elif output_format in ["gpd", "geopandas"]:
            print(result)
        else:
            print("ISEA3H expand completed.")
    else:
        print("ISEA3H is only supported on Windows systems")

EASE Compact Module

This module provides functionality to compact and expand EASE cells with flexible input and output formats.

Key Functions

ease_compact(ease_ids, depth=-1, bags=None, verbose=True)

Compact a list of EASE cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

ease_ids : list of str List of EASE cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted EASE cell IDs.

Examples

ease_ids = ["L4.165767.02.02.20.71", "L4.165767.02.02.20.72"] compacted = ease_compact(ease_ids) print(f"Compacted {len(ease_ids)} cells to {len(compacted)} cells")

Source code in vgrid/conversion/dggscompact/easecompact.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
def ease_compact(ease_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of EASE cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    ease_ids : list of str
        List of EASE cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted EASE cell IDs.

    Examples
    --------
    >>> ease_ids = ["L4.165767.02.02.20.71", "L4.165767.02.02.20.72"]
    >>> compacted = ease_compact(ease_ids)
    >>> print(f"Compacted {len(ease_ids)} cells to {len(compacted)} cells")
    """
    depth = validate_dggs_compact_depth("ease", depth)
    return compact_cells(
        ease_ids,
        _ease_parent,
        _ease_children,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting EASE",
    )

ease_expand(ease_ids, resolution=None, depth=None, verbose=True)

Expand EASE cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored. When only depth is set, each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/easecompact.py
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
def ease_expand(ease_ids, resolution=None, depth=None, verbose=True):
    """
    Expand EASE cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored. When only ``depth`` is
    set, each cell is expanded ``depth`` levels down (``1`` = direct children,
    ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("ease", resolution)
        uncompacted_cells = []
        for ease_id in tqdm(ease_ids, desc="Expanding EASE", unit=" cells", disable=not verbose):
            ease_resolution = int(ease_id[1])
            if ease_resolution >= resolution:
                uncompacted_cells.append(ease_id)
            else:
                uncompacted_cells.extend(
                    _parent_to_children(ease_id, ease_resolution + 1)
                )
        return uncompacted_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("ease", depth)
    cells = list(ease_ids)
    for _ in range(depth):
        nxt = []
        for ease_id in tqdm(cells, desc="Expanding EASE", unit=" cells", disable=not verbose):
            try:
                match = re.match(r"L(\d+)\..+", ease_id)
                res = int(match.group(1))
                nxt.extend(_parent_to_children(ease_id, res + 1))
            except Exception:
                continue
        cells = nxt
    return cells

easecompact(input_data, ease_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact EASE cells to their covering set at a given parent depth.

Compacts a set of EASE cells by replacing complete sets of children with their parent cells. Mixed input resolutions are allowed and depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg. If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing EASE cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of EASE cell IDs ease_id : str, optional Name of the column containing EASE cell IDs. Defaults to "ease". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as DGGS binning (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted EASE cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = easecompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = easecompact(["L4.165767.02.02.20.71", "L4.165767.02.02.20.72"])

Compact only one parent level

result = easecompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = easecompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = easecompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/easecompact.py
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
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
def easecompact(
    input_data,
    ease_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact EASE cells to their covering set at a given parent depth.

    Compacts a set of EASE cells by replacing complete sets of children with
    their parent cells. Mixed input resolutions are allowed and ``depth`` limits
    how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg``. If ``agg`` is ``"count"``, ``numeric_col`` is
    ignored and the output ``count`` is the number of original input cells in
    each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing EASE cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of EASE cell IDs
    ease_id : str, optional
        Name of the column containing EASE cell IDs. Defaults to "ease".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as DGGS binning (``count``, ``min``, ``max``,
        ``sum``, ``mean``, ``median``, ``std``, ``var``, ``range``,
        ``minority``, ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted EASE cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = easecompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = easecompact(["L4.165767.02.02.20.71", "L4.165767.02.02.20.72"])

    >>> # Compact only one parent level
    >>> result = easecompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = easecompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = easecompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not ease_id:
        ease_id = "ease"

    bags, agg_col = prepare_compact_bags(
        input_data,
        ease_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="EASE cells",
    )
    if bags is None:
        print(f"No EASE IDs found in <{ease_id}> field.")
        return

    ease_ids_compact = ease_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not ease_ids_compact:
        return None

    rows = []
    for ease_id_compact in tqdm(
        ease_ids_compact,
        desc="Building EASE compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = ease2geo(ease_id_compact)
            cell_resolution = get_ease_resolution(ease_id_compact)
            num_edges = 4  # EASE cells are rectangular
            row = geodesic_dggs_to_geoseries(
                "ease", ease_id_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(ease_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_ease_compacted"
        else:
            output_name = "ease_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

easecompact_cli()

Command-line interface for EASE compaction.

Source code in vgrid/conversion/dggscompact/easecompact.py
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
def easecompact_cli():
    """Command-line interface for EASE compaction."""
    parser = argparse.ArgumentParser(description="EASE Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input EASE (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="EASE ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = easecompact(
        input_data,
        ease_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

easeexpand(input_data, resolution=None, ease_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) EASE cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/easecompact.py
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 easeexpand(
    input_data,
    resolution=None,
    ease_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) EASE cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if ease_id is None:
        ease_id = "ease"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("ease", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("ease", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, ease_id)
    ease_ids = gdf[ease_id].drop_duplicates().tolist()

    if not ease_ids:
        print(f"No EASE IDs found in <{ease_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(int(eid[1]) for eid in ease_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            ease_ids_expand = ease_expand(ease_ids, resolution=resolution, verbose=verbose)
        else:
            ease_ids_expand = ease_expand(ease_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your EASE ID field, resolution, or depth."
        )

    if not ease_ids_expand:
        return None

    rows = []
    for ease_id_expand in tqdm(
        ease_ids_expand,
        desc="Building EASE expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = ease2geo(ease_id_expand)
            cell_resolution = get_ease_resolution(ease_id_expand)
            num_edges = 4
            row = geodesic_dggs_to_geoseries(
                "ease", ease_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_ease_expanded"
        else:
            output_name = "ease_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

easeexpand_cli()

Command-line interface for EASE expansion.

Source code in vgrid/conversion/dggscompact/easecompact.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
def easeexpand_cli():
    """Command-line interface for EASE expansion."""
    parser = argparse.ArgumentParser(description="EASE Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input EASE (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target EASE resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= EASE max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="EASE ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = easeexpand(
        args.input,
        resolution=args.resolution,
        ease_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

QTM Compact Module

This module provides functionality to compact and expand QTM cells with flexible input and output formats.

Key Functions

get_qtm_resolution(qtm_id)

Get the resolution of a QTM cell ID.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
37
38
39
40
41
42
def get_qtm_resolution(qtm_id):
    """Get the resolution of a QTM cell ID."""
    try:
        return len(qtm_id)
    except Exception as e:
        raise ValueError(f"Invalid QTM ID <{qtm_id}> : {e}")

qtm_compact(qtm_ids, depth=-1, bags=None, verbose=True)

Compact a list of QTM cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

qtm_ids : list of str QTM cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted QTM cell IDs.

Source code in vgrid/conversion/dggscompact/qtmcompact.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
def qtm_compact(qtm_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of QTM cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    qtm_ids : list of str
        QTM cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted QTM cell IDs.
    """
    depth = validate_dggs_compact_depth("qtm", depth)

    def parent_fn(qtm_id):
        parent = qtm.qtm_parent(qtm_id)
        if not parent or parent == qtm_id:
            return None
        return parent

    def children_fn(parent):
        return qtm.qtm_children(parent, len(parent) + 1)

    return compact_cells(
        qtm_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting QTM",
    )

qtm_expand(qtm_ids, resolution=None, depth=None, verbose=True)

Expand QTM cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtm_expand(qtm_ids, resolution=None, depth=None, verbose=True):
    """
    Expand QTM cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("qtm", resolution)
        expand_cells = []
        for qtm_id in tqdm(qtm_ids, desc="Expanding QTM", unit=" cells", disable=not verbose):
            cell_resolution = len(qtm_id)
            if cell_resolution >= resolution:
                expand_cells.append(qtm_id)
            else:
                expand_cells.extend(qtm.qtm_children(qtm_id, resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("qtm", depth)
    expand_cells = []
    for qtm_id in tqdm(qtm_ids, desc="Expanding QTM", unit=" cells", disable=not verbose):
        try:
            expand_cells.extend(qtm.qtm_children(qtm_id, len(qtm_id) + depth))
        except Exception:
            continue
    return expand_cells

qtmcompact(input_data, qtm_id='qtm', depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact QTM cells to their covering set at a given parent depth.

Compacts a set of QTM cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing QTM cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of QTM cell IDs qtm_id : str, default "qtm" Name of the column containing QTM cell IDs. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted QTM cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = qtmcompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = qtmcompact(["A0", "A1", "A2", "A3"])

Compact only one parent level

result = qtmcompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = qtmcompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = qtmcompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/qtmcompact.py
 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
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
def qtmcompact(
    input_data,
    qtm_id="qtm",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact QTM cells to their covering set at a given parent depth.

    Compacts a set of QTM cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing QTM cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of QTM cell IDs
    qtm_id : str, default "qtm"
        Name of the column containing QTM cell IDs.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted QTM cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = qtmcompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = qtmcompact(["A0", "A1", "A2", "A3"])

    >>> # Compact only one parent level
    >>> result = qtmcompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = qtmcompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = qtmcompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not qtm_id:
        qtm_id = "qtm"

    bags, agg_col = prepare_compact_bags(
        input_data,
        qtm_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="QTM cells",
    )
    if bags is None:
        print(f"No QTM IDs found in <{qtm_id}> field.")
        return

    qtm_ids_compact = qtm_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not qtm_ids_compact:
        return None

    rows = []
    for qtm_id_compact in tqdm(
        qtm_ids_compact,
        desc="Building QTM compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = qtm2geo(qtm_id_compact)
            cell_resolution = get_qtm_resolution(qtm_id_compact)
            num_edges = 3  # QTM cells are triangular
            row = geodesic_dggs_to_geoseries(
                "qtm", qtm_id_compact, cell_resolution, cell_polygon, num_edges
            )
            row[agg_col] = aggregate_values(bags.get(qtm_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_qtm_compacted"
        else:
            output_name = "qtm_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

qtmcompact_cli()

Command-line interface for QTM compaction.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmcompact_cli():
    """Command-line interface for QTM compaction."""
    parser = argparse.ArgumentParser(description="QTM Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input QTM (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="QTM ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = qtmcompact(
        input_data,
        qtm_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

qtmexpand(input_data, resolution=None, qtm_id='qtm', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) QTM cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmexpand(
    input_data,
    resolution=None,
    qtm_id="qtm",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) QTM cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if qtm_id is None:
        qtm_id = "qtm"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("qtm", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("qtm", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, qtm_id)
    qtm_ids = gdf[qtm_id].drop_duplicates().tolist()

    if not qtm_ids:
        print(f"No QTM IDs found in <{qtm_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(qid) for qid in qtm_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            qtm_ids_expand = qtm_expand(qtm_ids, resolution=resolution, verbose=verbose)
        else:
            qtm_ids_expand = qtm_expand(qtm_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your QTM ID field, resolution, or depth."
        )

    if not qtm_ids_expand:
        return None

    rows = []
    for qtm_id_expand in tqdm(
        qtm_ids_expand,
        desc="Building QTM expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = qtm2geo(qtm_id_expand)
            cell_resolution = len(qtm_id_expand)
            num_edges = 3
            row = geodesic_dggs_to_geoseries(
                "qtm", qtm_id_expand, cell_resolution, cell_polygon, num_edges
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_qtm_expanded"
        else:
            output_name = "qtm_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

qtmexpand_cli()

Command-line interface for QTM expansion.

Source code in vgrid/conversion/dggscompact/qtmcompact.py
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
def qtmexpand_cli():
    """Command-line interface for QTM expansion."""
    parser = argparse.ArgumentParser(description="QTM Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input QTM (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target QTM resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= QTM max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="QTM ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = qtmexpand(
        args.input,
        resolution=args.resolution,
        qtm_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

OLC Compact Module

This module provides functionality to compact and expand OLC cells with flexible input and output formats.

Key Functions

get_olc_resolution(olc_id)

Get the resolution of an OLC cell ID.

Source code in vgrid/conversion/dggscompact/olccompact.py
38
39
40
41
42
43
44
def get_olc_resolution(olc_id):
    """Get the resolution of an OLC cell ID."""
    try:
        coord = olc.decode(olc_id)
        return coord.codeLength
    except Exception as e:
        raise ValueError(f"Invalid OLC ID <{olc_id}> : {e}")

olc_compact(olc_ids, depth=-1, bags=None, verbose=True)

Compact a list of OLC cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

olc_ids : list of str OLC cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted OLC cell IDs.

Source code in vgrid/conversion/dggscompact/olccompact.py
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
def olc_compact(olc_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of OLC cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    olc_ids : list of str
        OLC cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted OLC cell IDs.
    """
    depth = validate_dggs_compact_depth("olc", depth)

    def parent_fn(olc_id):
        return olc.olc_parent(olc_id)

    def children_fn(parent):
        coord = olc.decode(parent)
        if coord.codeLength <= 10:
            next_res = coord.codeLength + 2
        else:
            next_res = coord.codeLength + 1
        return olc.olc_children(parent, next_res)

    return compact_cells(
        olc_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting OLC",
    )

olc_expand(olc_ids, resolution=None, depth=None, verbose=True)

Expand OLC cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute code length. When only depth is set, resolution is ignored and each cell is expanded depth OLC steps down (1 = next valid OLC resolution, 2 = the one after that, and so on). OLC resolutions are not linear: [2, 4, 6, 8, 10, 11, 12, 13, 14, 15].

Source code in vgrid/conversion/dggscompact/olccompact.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
def olc_expand(olc_ids, resolution=None, depth=None, verbose=True):
    """
    Expand OLC cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute code length. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` OLC steps down (``1`` = next
    valid OLC resolution, ``2`` = the one after that, and so on). OLC
    resolutions are not linear: ``[2, 4, 6, 8, 10, 11, 12, 13, 14, 15]``.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("olc", resolution)
        expand_cells = []
        for olc_id in tqdm(olc_ids, desc="Expanding OLC", unit=" cells", disable=not verbose):
            cell_resolution = olc.decode(olc_id).codeLength
            if cell_resolution >= resolution:
                expand_cells.append(olc_id)
            else:
                expand_cells.extend(olc.olc_children(olc_id, resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("olc", depth)
    expand_cells = []
    for olc_id in tqdm(olc_ids, desc="Expanding OLC", unit=" cells", disable=not verbose):
        try:
            current_len = olc.decode(olc_id).codeLength
            target_res = _olc_resolution_at_depth(current_len, depth)
            expand_cells.extend(olc.olc_children(olc_id, target_res))
        except Exception:
            continue
    return expand_cells

olccompact(input_data, olc_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact OLC cells to their covering set at a given parent depth.

Compacts a set of OLC cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing OLC cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of OLC cell IDs olc_id : str, optional Name of the column containing OLC cell IDs. Defaults to "olc". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted OLC cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = olccompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = olccompact(["7P28QPG4+4P7", "7P28QPG4+4P8"])

Compact only one parent level

result = olccompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = olccompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = olccompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/olccompact.py
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
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
def olccompact(
    input_data,
    olc_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact OLC cells to their covering set at a given parent depth.

    Compacts a set of OLC cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing OLC cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of OLC cell IDs
    olc_id : str, optional
        Name of the column containing OLC cell IDs. Defaults to "olc".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted OLC cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = olccompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = olccompact(["7P28QPG4+4P7", "7P28QPG4+4P8"])

    >>> # Compact only one parent level
    >>> result = olccompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = olccompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = olccompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not olc_id:
        olc_id = "olc"

    bags, agg_col = prepare_compact_bags(
        input_data,
        olc_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="OLC cells",
    )
    if bags is None:
        print(f"No OLC IDs found in <{olc_id}> field.")
        return

    olc_ids_compact = olc_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not olc_ids_compact:
        return None

    rows = []
    for olc_id_compact in tqdm(
        olc_ids_compact,
        desc="Building OLC compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = olc2geo(olc_id_compact)
            cell_resolution = get_olc_resolution(olc_id_compact)
            row = graticule_dggs_to_geoseries(
                "olc", olc_id_compact, cell_resolution, cell_polygon
            )
            row[agg_col] = aggregate_values(bags.get(olc_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_olc_compacted"
        else:
            output_name = "olc_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

olccompact_cli()

Command-line interface for OLC compaction.

Source code in vgrid/conversion/dggscompact/olccompact.py
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
def olccompact_cli():
    """Command-line interface for OLC compaction."""
    parser = argparse.ArgumentParser(description="OLC Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input OLC (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="OLC ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = olccompact(
        input_data,
        olc_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

olcexpand(input_data, resolution=None, olc_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) OLC cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute code length (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded depth OLC steps down.

Source code in vgrid/conversion/dggscompact/olccompact.py
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
def olcexpand(
    input_data,
    resolution=None,
    olc_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) OLC cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute code length (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded ``depth`` OLC steps down.
    """
    if olc_id is None:
        olc_id = "olc"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("olc", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("olc", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, olc_id)
    olc_ids = gdf[olc_id].drop_duplicates().tolist()

    if not olc_ids:
        print(f"No OLC IDs found in <{olc_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(olc.decode(oid).codeLength for oid in olc_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            olc_ids_expand = olc_expand(olc_ids, resolution=resolution, verbose=verbose)
        else:
            olc_ids_expand = olc_expand(olc_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your OLC ID field, resolution, or depth."
        )

    if not olc_ids_expand:
        return None

    rows = []
    for olc_id_expand in tqdm(
        olc_ids_expand,
        desc="Building OLC expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = olc2geo(olc_id_expand)
            cell_resolution = olc.decode(olc_id_expand).codeLength
            row = graticule_dggs_to_geoseries(
                "olc", olc_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_olc_expanded"
        else:
            output_name = "olc_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

olcexpand_cli()

Command-line interface for OLC expansion.

Source code in vgrid/conversion/dggscompact/olccompact.py
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 olcexpand_cli():
    """Command-line interface for OLC expansion."""
    parser = argparse.ArgumentParser(description="OLC Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input OLC (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target OLC resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many OLC child steps (1 = next valid OLC "
        "resolution, 2 = the one after that, ...; 1 <= depth <= OLC max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="OLC ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = olcexpand(
        args.input,
        resolution=args.resolution,
        olc_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

Geohash Compact Module

This module provides functionality to compact and expand Geohash cells with flexible input and output formats.

Key Functions

geohash_compact(geohash_ids, depth=-1, bags=None, verbose=True)

Compact a list of Geohash cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

geohash_ids : list of str Geohash cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted Geohash cell IDs.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def geohash_compact(geohash_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of Geohash cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    geohash_ids : list of str
        Geohash cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted Geohash cell IDs.
    """
    depth = validate_dggs_compact_depth("geohash", depth)

    def parent_fn(geohash_id):
        if len(geohash_id) <= 1:
            return None
        return geohash.geohash_parent(geohash_id)

    def children_fn(parent):
        return geohash.geohash_children(parent, len(parent) + 1)

    return compact_cells(
        geohash_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting Geohash",
    )

geohash_expand(geohash_ids, resolution=None, depth=None, verbose=True)

Expand Geohash cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/geohashcompact.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
324
def geohash_expand(geohash_ids, resolution=None, depth=None, verbose=True):
    """
    Expand Geohash cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("geohash", resolution)
        expand_cells = []
        for geohash_id in tqdm(geohash_ids, desc="Expanding Geohash", unit=" cells", disable=not verbose):
            cell_resolution = len(geohash_id)
            if cell_resolution >= resolution:
                expand_cells.append(geohash_id)
            else:
                expand_cells.extend(
                    geohash.geohash_children(geohash_id, resolution)
                )
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("geohash", depth)
    expand_cells = []
    for geohash_id in tqdm(geohash_ids, desc="Expanding Geohash", unit=" cells", disable=not verbose):
        try:
            expand_cells.extend(
                geohash.geohash_children(geohash_id, len(geohash_id) + depth)
            )
        except Exception:
            continue
    return expand_cells

geohashcompact(input_data, geohash_id=None, depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact Geohash cells to their covering set at a given parent depth.

Compacts a set of Geohash cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing Geohash cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of Geohash cell IDs geohash_id : str, optional Name of the column containing Geohash cell IDs. Defaults to "geohash". depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted Geohash cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = geohashcompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = geohashcompact(["w3gvk1td8", "w3gvk1td9"])

Compact only one parent level

result = geohashcompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = geohashcompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = geohashcompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/geohashcompact.py
 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
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
def geohashcompact(
    input_data,
    geohash_id=None,
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact Geohash cells to their covering set at a given parent depth.

    Compacts a set of Geohash cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing Geohash cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of Geohash cell IDs
    geohash_id : str, optional
        Name of the column containing Geohash cell IDs. Defaults to "geohash".
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted Geohash cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = geohashcompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = geohashcompact(["w3gvk1td8", "w3gvk1td9"])

    >>> # Compact only one parent level
    >>> result = geohashcompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = geohashcompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = geohashcompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not geohash_id:
        geohash_id = "geohash"

    bags, agg_col = prepare_compact_bags(
        input_data,
        geohash_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="Geohash cells",
    )
    if bags is None:
        print(f"No Geohash IDs found in <{geohash_id}> field.")
        return

    geohash_ids_compact = geohash_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not geohash_ids_compact:
        return None

    rows = []
    for geohash_id_compact in tqdm(
        geohash_ids_compact,
        desc="Building Geohash compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = geohash2geo(geohash_id_compact)
            cell_resolution = get_geohash_resolution(geohash_id_compact)
            row = graticule_dggs_to_geoseries(
                "geohash", geohash_id_compact, cell_resolution, cell_polygon
            )
            row[agg_col] = aggregate_values(bags.get(geohash_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_geohash_compacted"
        else:
            output_name = "geohash_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

geohashcompact_cli()

Command-line interface for Geohash compaction.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
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 geohashcompact_cli():
    """Command-line interface for Geohash compaction."""
    parser = argparse.ArgumentParser(description="Geohash Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Geohash (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Geohash ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = geohashcompact(
        input_data,
        geohash_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

geohashexpand(input_data, resolution=None, geohash_id=None, output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Geohash cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
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
def geohashexpand(
    input_data,
    resolution=None,
    geohash_id=None,
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Geohash cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if geohash_id is None:
        geohash_id = "geohash"
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("geohash", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("geohash", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, geohash_id)
    geohash_ids = gdf[geohash_id].drop_duplicates().tolist()

    if not geohash_ids:
        print(f"No Geohash IDs found in <{geohash_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(gid) for gid in geohash_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            geohash_ids_expand = geohash_expand(geohash_ids, resolution=resolution, verbose=verbose)
        else:
            geohash_ids_expand = geohash_expand(geohash_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Geohash ID field, resolution, or depth."
        )

    if not geohash_ids_expand:
        return None

    rows = []
    for geohash_id_expand in tqdm(
        geohash_ids_expand,
        desc="Building Geohash expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = geohash2geo(geohash_id_expand)
            cell_resolution = len(geohash_id_expand)
            row = graticule_dggs_to_geoseries(
                "geohash", geohash_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_geohash_expanded"
        else:
            output_name = "geohash_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

geohashexpand_cli()

Command-line interface for Geohash expansion.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def geohashexpand_cli():
    """Command-line interface for Geohash expansion."""
    parser = argparse.ArgumentParser(description="Geohash Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Geohash (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Geohash resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Geohash max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Geohash ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = geohashexpand(
        args.input,
        resolution=args.resolution,
        geohash_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

get_geohash_resolution(geohash_id)

Get the resolution of a Geohash cell ID.

Source code in vgrid/conversion/dggscompact/geohashcompact.py
37
38
39
def get_geohash_resolution(geohash_id):
    """Get the resolution of a Geohash cell ID."""
    return len(geohash_id)

Tilecode Compact Module

This module provides functionality to compact and expand Tilecode cells with flexible input and output formats.

Key Functions

tilecode_compact(tilecode_ids, depth=-1, bags=None, verbose=True)

Compact a list of Tilecode cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

tilecode_ids : list of str Tilecode cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted Tilecode cell IDs.

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def tilecode_compact(tilecode_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of Tilecode cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    tilecode_ids : list of str
        Tilecode cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted Tilecode cell IDs.
    """
    depth = validate_dggs_compact_depth("tilecode", depth)

    def parent_fn(tilecode_id):
        if not re.match(r"z(\d+)x(\d+)y(\d+)", tilecode_id):
            return None
        return tilecode.tilecode_parent(tilecode_id)

    def children_fn(parent):
        match = re.match(r"z(\d+)x(\d+)y(\d+)", parent)
        parent_res = int(match.group(1))
        return tilecode.tilecode_children(parent, parent_res + 1)

    return compact_cells(
        tilecode_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting Tilecode",
    )

tilecode_expand(tilecode_ids, resolution=None, depth=None, verbose=True)

Expand Tilecode cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
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
def tilecode_expand(tilecode_ids, resolution=None, depth=None, verbose=True):
    """
    Expand Tilecode cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("tilecode", resolution)
        expand_cells = []
        for tilecode_id in tqdm(tilecode_ids, desc="Expanding Tilecode", unit=" cells", disable=not verbose):
            cell_resolution = tilecode_resolution(tilecode_id)
            if cell_resolution >= resolution:
                expand_cells.append(tilecode_id)
            else:
                expand_cells.extend(
                    tilecode.tilecode_children(tilecode_id, resolution)
                )
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("tilecode", depth)
    expand_cells = []
    for tilecode_id in tqdm(tilecode_ids, desc="Expanding Tilecode", unit=" cells", disable=not verbose):
        try:
            expand_cells.extend(
                tilecode.tilecode_children(
                    tilecode_id, tilecode_resolution(tilecode_id) + depth
                )
            )
        except Exception:
            continue
    return expand_cells

tilecodecompact(input_data, tilecode_id='tilecode', depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact Tilecode cells to their covering set at a given parent depth.

Compacts a set of Tilecode cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing Tilecode cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of Tilecode cell IDs tilecode_id : str, default "tilecode" Name of the column containing Tilecode cell IDs. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted Tilecode cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = tilecodecompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = tilecodecompact(["z3x1y1", "z3x1y2", "z3x2y1", "z3x2y2"])

Compact only one parent level

result = tilecodecompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = tilecodecompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = tilecodecompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
 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
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
def tilecodecompact(
    input_data,
    tilecode_id="tilecode",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact Tilecode cells to their covering set at a given parent depth.

    Compacts a set of Tilecode cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing Tilecode cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of Tilecode cell IDs
    tilecode_id : str, default "tilecode"
        Name of the column containing Tilecode cell IDs.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted Tilecode cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = tilecodecompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = tilecodecompact(["z3x1y1", "z3x1y2", "z3x2y1", "z3x2y2"])

    >>> # Compact only one parent level
    >>> result = tilecodecompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = tilecodecompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = tilecodecompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not tilecode_id:
        tilecode_id = "tilecode"

    bags, agg_col = prepare_compact_bags(
        input_data,
        tilecode_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="Tilecode cells",
    )
    if bags is None:
        print(f"No Tilecode IDs found in <{tilecode_id}> field.")
        return

    tilecode_ids_compact = tilecode_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not tilecode_ids_compact:
        return None

    rows = []
    for tilecode_id_compact in tqdm(
        tilecode_ids_compact,
        desc="Building Tilecode compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = tilecode2geo(tilecode_id_compact)
            cell_resolution = tilecode_resolution(tilecode_id_compact)
            row = graticule_dggs_to_geoseries(
                "tilecode", tilecode_id_compact, cell_resolution, cell_polygon
            )
            row[agg_col] = aggregate_values(bags.get(tilecode_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_tilecode_compacted"
        else:
            output_name = "tilecode_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

tilecodecompact_cli()

Command-line interface for Tilecode compaction.

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
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
def tilecodecompact_cli():
    """Command-line interface for Tilecode compaction."""
    parser = argparse.ArgumentParser(description="Tilecode Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Tilecode (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Tilecode ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = tilecodecompact(
        input_data,
        tilecode_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

tilecodeexpand(input_data, resolution=None, tilecode_id='tilecode', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Tilecode cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/tilecodecompact.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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
def tilecodeexpand(
    input_data,
    resolution=None,
    tilecode_id="tilecode",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Tilecode cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("tilecode", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("tilecode", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, tilecode_id)
    tilecode_ids = gdf[tilecode_id].drop_duplicates().tolist()

    if not tilecode_ids:
        print(f"No Tilecode IDs found in <{tilecode_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(tilecode_resolution(tid) for tid in tilecode_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            tilecode_ids_expand = tilecode_expand(tilecode_ids, resolution=resolution, verbose=verbose)
        else:
            tilecode_ids_expand = tilecode_expand(tilecode_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Tilecode ID field, resolution, or depth."
        )

    if not tilecode_ids_expand:
        return None

    rows = []
    for tilecode_id_expand in tqdm(
        tilecode_ids_expand,
        desc="Building Tilecode expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = tilecode2geo(tilecode_id_expand)
            cell_resolution = tilecode_resolution(tilecode_id_expand)
            row = graticule_dggs_to_geoseries(
                "tilecode", tilecode_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_tilecode_expanded"
        else:
            output_name = "tilecode_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

tilecodeexpand_cli()

Command-line interface for Tilecode expansion.

Source code in vgrid/conversion/dggscompact/tilecodecompact.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
def tilecodeexpand_cli():
    """Command-line interface for Tilecode expansion."""
    parser = argparse.ArgumentParser(description="Tilecode Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Tilecode (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Tilecode resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Tilecode max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Tilecode ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = tilecodeexpand(
        args.input,
        resolution=args.resolution,
        tilecode_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

Quadkey Compact Module

This module provides functionality to compact and expand Quadkey cells with flexible input and output formats.

Key Functions

quadkey_compact(quadkey_ids, depth=-1, bags=None, verbose=True)

Compact a list of Quadkey cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

quadkey_ids : list of str Quadkey cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted Quadkey cell IDs.

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def quadkey_compact(quadkey_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of Quadkey cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    quadkey_ids : list of str
        Quadkey cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted Quadkey cell IDs.
    """
    depth = validate_dggs_compact_depth("quadkey", depth)

    def parent_fn(quadkey_id):
        parent = tilecode.quadkey_parent(quadkey_id)
        if not parent:
            return None
        return parent

    def children_fn(parent):
        return tilecode.quadkey_children(
            parent, mercantile.quadkey_to_tile(parent).z + 1
        )

    return compact_cells(
        quadkey_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting Quadkey",
    )

quadkey_expand(quadkey_ids, resolution=None, depth=None, verbose=True)

Expand Quadkey cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
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
def quadkey_expand(quadkey_ids, resolution=None, depth=None, verbose=True):
    """
    Expand Quadkey cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("quadkey", resolution)
        expand_cells = []
        for quadkey_id in tqdm(quadkey_ids, desc="Expanding Quadkey", unit=" cells", disable=not verbose):
            cell_resolution = len(quadkey_id)
            if cell_resolution >= resolution:
                expand_cells.append(quadkey_id)
            else:
                expand_cells.extend(
                    tilecode.quadkey_children(quadkey_id, resolution)
                )
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("quadkey", depth)
    expand_cells = []
    for quadkey_id in tqdm(quadkey_ids, desc="Expanding Quadkey", unit=" cells", disable=not verbose):
        try:
            expand_cells.extend(
                tilecode.quadkey_children(quadkey_id, len(quadkey_id) + depth)
            )
        except Exception:
            continue
    return expand_cells

quadkeycompact(input_data, quadkey_id='quadkey', depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact Quadkey cells to their covering set at a given parent depth.

Compacts a set of Quadkey cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing Quadkey cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of Quadkey cell IDs quadkey_id : str, default "quadkey" Name of the column containing Quadkey cell IDs. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted Quadkey cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = quadkeycompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = quadkeycompact(["13223011131020220011133", "13223011131020220011134"])

Compact only one parent level

result = quadkeycompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = quadkeycompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = quadkeycompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
 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
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
def quadkeycompact(
    input_data,
    quadkey_id="quadkey",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact Quadkey cells to their covering set at a given parent depth.

    Compacts a set of Quadkey cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing Quadkey cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of Quadkey cell IDs
    quadkey_id : str, default "quadkey"
        Name of the column containing Quadkey cell IDs.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted Quadkey cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = quadkeycompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = quadkeycompact(["13223011131020220011133", "13223011131020220011134"])

    >>> # Compact only one parent level
    >>> result = quadkeycompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = quadkeycompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = quadkeycompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not quadkey_id:
        quadkey_id = "quadkey"

    bags, agg_col = prepare_compact_bags(
        input_data,
        quadkey_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="Quadkey cells",
    )
    if bags is None:
        print(f"No Quadkey IDs found in <{quadkey_id}> field.")
        return

    quadkey_ids_compact = quadkey_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not quadkey_ids_compact:
        return None

    rows = []
    for quadkey_id_compact in tqdm(
        quadkey_ids_compact,
        desc="Building Quadkey compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = quadkey2geo(quadkey_id_compact)
            cell_resolution = quadkey_resolution(quadkey_id_compact)
            row = graticule_dggs_to_geoseries(
                "quadkey", quadkey_id_compact, cell_resolution, cell_polygon
            )
            row[agg_col] = aggregate_values(bags.get(quadkey_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_quadkey_compacted"
        else:
            output_name = "quadkey_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

quadkeycompact_cli()

Command-line interface for Quadkey compaction.

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
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
def quadkeycompact_cli():
    """Command-line interface for Quadkey compaction."""
    parser = argparse.ArgumentParser(description="Quadkey Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Quadkey (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Quadkey ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = quadkeycompact(
        input_data,
        quadkey_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

quadkeyexpand(input_data, resolution=None, quadkey_id='quadkey', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) Quadkey cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/quadkeycompact.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def quadkeyexpand(
    input_data,
    resolution=None,
    quadkey_id="quadkey",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) Quadkey cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("quadkey", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("quadkey", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, quadkey_id)
    quadkey_ids = gdf[quadkey_id].drop_duplicates().tolist()

    if not quadkey_ids:
        print(f"No Quadkey IDs found in <{quadkey_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(len(qid) for qid in quadkey_ids)
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            quadkey_ids_expand = quadkey_expand(quadkey_ids, resolution=resolution, verbose=verbose)
        else:
            quadkey_ids_expand = quadkey_expand(quadkey_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your Quadkey ID field, resolution, or depth."
        )

    if not quadkey_ids_expand:
        return None

    rows = []
    for quadkey_id_expand in tqdm(
        quadkey_ids_expand,
        desc="Building Quadkey expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = quadkey2geo(quadkey_id_expand)
            cell_resolution = len(quadkey_id_expand)
            row = graticule_dggs_to_geoseries(
                "quadkey", quadkey_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_quadkey_expanded"
        else:
            output_name = "quadkey_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

quadkeyexpand_cli()

Command-line interface for Quadkey expansion.

Source code in vgrid/conversion/dggscompact/quadkeycompact.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
def quadkeyexpand_cli():
    """Command-line interface for Quadkey expansion."""
    parser = argparse.ArgumentParser(description="Quadkey Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input Quadkey (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target Quadkey resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= Quadkey max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="Quadkey ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = quadkeyexpand(
        args.input,
        resolution=args.resolution,
        quadkey_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)

Digipin Compact Module

This module provides functionality to compact and expand DIGIPIN cells with flexible input and output formats.

Key Functions

digipin_compact(digipin_ids, depth=-1, bags=None, verbose=True)

Compact a list of DIGIPIN cell IDs by replacing complete child sets with parents.

Groups cells by their immediate parent and replaces a parent when every child is present. Repeats until depth parent levels have been applied, or until no further compaction is possible.

Parameters

digipin_ids : list of str DIGIPIN cell IDs to compact. Mixed resolutions are allowed. depth : int, default -1 How many parent levels to climb: - 0: do nothing (return the unique input cells) - -1: compact as far as possible - 1: replace complete sibling sets with their direct parent - 2: then compact those parents (grandparents), and so on bags : dict of list, optional Per-cell lists of original values. When a complete child set is replaced by its parent, child lists are concatenated onto the parent. Mutated in place so remaining keys match the compacted IDs. verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

list of str Sorted compacted DIGIPIN cell IDs.

Source code in vgrid/conversion/dggscompact/digipincompact.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def digipin_compact(digipin_ids, depth=-1, bags=None, verbose=True):
    """
    Compact a list of DIGIPIN cell IDs by replacing complete child sets with parents.

    Groups cells by their immediate parent and replaces a parent when every child
    is present. Repeats until ``depth`` parent levels have been applied, or until
    no further compaction is possible.

    Parameters
    ----------
    digipin_ids : list of str
        DIGIPIN cell IDs to compact. Mixed resolutions are allowed.
    depth : int, default -1
        How many parent levels to climb:
        - ``0``: do nothing (return the unique input cells)
        - ``-1``: compact as far as possible
        - ``1``: replace complete sibling sets with their direct parent
        - ``2``: then compact those parents (grandparents), and so on
    bags : dict of list, optional
        Per-cell lists of original values. When a complete child set is replaced
        by its parent, child lists are concatenated onto the parent. Mutated
        in place so remaining keys match the compacted IDs.
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    list of str
        Sorted compacted DIGIPIN cell IDs.
    """
    depth = validate_dggs_compact_depth("digipin", depth)

    def parent_fn(digipin_id):
        parent = digipin_parent(digipin_id)
        if parent == "Invalid DIGIPIN":
            return None
        return parent

    def children_fn(parent):
        parent_resolution = digipin_resolution(parent)
        if isinstance(parent_resolution, str):
            raise ValueError("Invalid DIGIPIN resolution")
        return digipin_children(parent, parent_resolution + 1)

    return compact_cells(
        digipin_ids,
        parent_fn,
        children_fn,
        depth=depth,
        bags=bags,
        verbose=verbose,
        desc="Compacting DIGIPIN",
    )

digipin_expand(digipin_ids, resolution=None, depth=None, verbose=True)

Expand DIGIPIN cell IDs to a target resolution, or by a relative child depth.

When resolution is set, depth is ignored and all cells are expanded to that absolute resolution. When only depth is set, resolution is ignored and each cell is expanded depth levels down (1 = direct children, 2 = grandchildren, and so on).

Source code in vgrid/conversion/dggscompact/digipincompact.py
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 digipin_expand(digipin_ids, resolution=None, depth=None, verbose=True):
    """
    Expand DIGIPIN cell IDs to a target resolution, or by a relative child depth.

    When ``resolution`` is set, ``depth`` is ignored and all cells are expanded
    to that absolute resolution. When only ``depth`` is set, ``resolution`` is
    ignored and each cell is expanded ``depth`` levels down (``1`` = direct
    children, ``2`` = grandchildren, and so on).
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("digipin", resolution)
        expand_cells = []
        for digipin_id in tqdm(digipin_ids, desc="Expanding DIGIPIN", unit=" cells", disable=not verbose):
            current_resolution = digipin_resolution(digipin_id)
            if isinstance(current_resolution, str):
                raise ValueError("Invalid DIGIPIN format.")
            if current_resolution >= resolution:
                expand_cells.append(digipin_id)
            else:
                expand_cells.extend(digipin_children(digipin_id, resolution))
        return expand_cells

    if depth is None:
        raise ValueError("Either resolution or depth must be specified.")
    depth = validate_dggs_expand_depth("digipin", depth)
    expand_cells = []
    for digipin_id in tqdm(digipin_ids, desc="Expanding DIGIPIN", unit=" cells", disable=not verbose):
        try:
            current_resolution = digipin_resolution(digipin_id)
            if isinstance(current_resolution, str):
                continue
            expand_cells.extend(
                digipin_children(digipin_id, current_resolution + depth)
            )
        except Exception:
            continue
    return expand_cells

digipincompact(input_data, digipin_id='digipin', depth=-1, agg='count', numeric_col=None, output_format='gpd', verbose=True)

Compact DIGIPIN cells to their covering set at a given parent depth.

Compacts a set of DIGIPIN cells by replacing complete sets of children with their parent cells. depth limits how far up the hierarchy to merge.

When a complete sibling set is replaced by its parent, original child values are combined with agg (same options as h3bin). If agg is "count", numeric_col is ignored and the output count is the number of original input cells in each compacted cell.

Parameters

input_data : str, dict, geopandas.GeoDataFrame, or list Input data containing DIGIPIN cell IDs. Can be: - File path (GeoJSON, Shapefile, CSV, Parquet) - URL to a file - GeoJSON dictionary - GeoDataFrame - List of DIGIPIN cell IDs digipin_id : str, default "digipin" Name of the column containing DIGIPIN cell IDs. depth : int, default -1 Compaction depth: 0 leaves cells unchanged, -1 compact as far as possible, 1 merges to the direct parent, 2 to the grandparent, etc. agg : str, default "count" Aggregation applied to original child values when cells compact into a parent. Same options as h3bin (count, min, max, sum, mean, median, std, var, range, minority, majority, variety). numeric_col : str, optional Numeric field to aggregate. Required when agg is not "count"; ignored when agg is "count". output_format : str, default "gpd" Output format. Options: - "gpd": Returns GeoPandas GeoDataFrame (default) - "csv": Returns CSV file path - "geojson": Returns GeoJSON file path - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict - "parquet": Returns Parquet file path - "shapefile"/"shp": Returns Shapefile file path - "gpkg"/"geopackage": Returns GeoPackage file path verbose : bool, default True Show tqdm progress bars. Use False to hide them.

Returns

geopandas.GeoDataFrame or str or dict or None The compacted DIGIPIN cells in the specified format, or None if no valid cells found.

Examples

Compact from file

result = digipincompact("cells.geojson") print(f"Compacted to {len(result)} cells")

Compact from list

result = digipincompact(["F3K-F", "F3K-C", "F3K-9", "F3K-8"])

Compact only one parent level

result = digipincompact(cells, depth=1)

Mean of a numeric field on compacted parents

result = digipincompact(cells, agg="mean", numeric_col="value")

Compact to GeoJSON file

result = digipincompact("cells.geojson", output_format="geojson") print(f"Saved to: {result}")

Source code in vgrid/conversion/dggscompact/digipincompact.py
 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
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
def digipincompact(
    input_data,
    digipin_id="digipin",
    depth=-1,
    agg="count",
    numeric_col=None,
    output_format="gpd",
    verbose=True,
):
    """
    Compact DIGIPIN cells to their covering set at a given parent depth.

    Compacts a set of DIGIPIN cells by replacing complete sets of children with
    their parent cells. ``depth`` limits how far up the hierarchy to merge.

    When a complete sibling set is replaced by its parent, original child values
    are combined with ``agg`` (same options as ``h3bin``). If ``agg`` is
    ``"count"``, ``numeric_col`` is ignored and the output ``count`` is the
    number of original input cells in each compacted cell.

    Parameters
    ----------
    input_data : str, dict, geopandas.GeoDataFrame, or list
        Input data containing DIGIPIN cell IDs. Can be:
        - File path (GeoJSON, Shapefile, CSV, Parquet)
        - URL to a file
        - GeoJSON dictionary
        - GeoDataFrame
        - List of DIGIPIN cell IDs
    digipin_id : str, default "digipin"
        Name of the column containing DIGIPIN cell IDs.
    depth : int, default -1
        Compaction depth: ``0`` leaves cells unchanged, ``-1`` compact as far as
        possible, ``1`` merges to the direct parent, ``2`` to the grandparent, etc.
    agg : str, default "count"
        Aggregation applied to original child values when cells compact into a
        parent. Same options as ``h3bin`` (``count``, ``min``, ``max``, ``sum``,
        ``mean``, ``median``, ``std``, ``var``, ``range``, ``minority``,
        ``majority``, ``variety``).
    numeric_col : str, optional
        Numeric field to aggregate. Required when ``agg`` is not ``"count"``;
        ignored when ``agg`` is ``"count"``.
    output_format : str, default "gpd"
        Output format. Options:
        - "gpd": Returns GeoPandas GeoDataFrame (default)
        - "csv": Returns CSV file path
        - "geojson": Returns GeoJSON file path
        - "geojson_dict": Returns GeoJSON FeatureCollection as Python dict
        - "parquet": Returns Parquet file path
        - "shapefile"/"shp": Returns Shapefile file path
        - "gpkg"/"geopackage": Returns GeoPackage file path
    verbose : bool, default True
        Show tqdm progress bars. Use ``False`` to hide them.

    Returns
    -------
    geopandas.GeoDataFrame or str or dict or None
        The compacted DIGIPIN cells in the specified format, or None if no valid cells found.

    Examples
    --------
    >>> # Compact from file
    >>> result = digipincompact("cells.geojson")
    >>> print(f"Compacted to {len(result)} cells")

    >>> # Compact from list
    >>> result = digipincompact(["F3K-F", "F3K-C", "F3K-9", "F3K-8"])

    >>> # Compact only one parent level
    >>> result = digipincompact(cells, depth=1)

    >>> # Mean of a numeric field on compacted parents
    >>> result = digipincompact(cells, agg="mean", numeric_col="value")

    >>> # Compact to GeoJSON file
    >>> result = digipincompact("cells.geojson", output_format="geojson")
    >>> print(f"Saved to: {result}")
    """
    if not digipin_id:
        digipin_id = "digipin"

    bags, agg_col = prepare_compact_bags(
        input_data,
        digipin_id,
        agg=agg,
        numeric_col=numeric_col,
        verbose=verbose,
        label="DIGIPIN cells",
    )
    if bags is None:
        print(f"No DIGIPIN IDs found in <{digipin_id}> field.")
        return

    digipin_ids_compact = digipin_compact(
        list(bags.keys()), depth=depth, bags=bags, verbose=verbose
    )
    if not digipin_ids_compact:
        return None

    rows = []
    for digipin_id_compact in tqdm(
        digipin_ids_compact,
        desc="Building DIGIPIN compact",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = digipin2geo(digipin_id_compact)
            cell_resolution = digipin_resolution(digipin_id_compact)
            if isinstance(cell_resolution, str):
                continue  # Skip invalid resolutions
            row = graticule_dggs_to_geoseries(
                "digipin", digipin_id_compact, cell_resolution, cell_polygon
            )
            row[agg_col] = aggregate_values(bags.get(digipin_id_compact, []), agg)
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_digipin_compacted"
        else:
            output_name = "digipin_compacted"

    return convert_to_output_format(out_gdf, output_format, output_name)

digipincompact_cli()

Command-line interface for DIGIPIN compaction.

Source code in vgrid/conversion/dggscompact/digipincompact.py
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
def digipincompact_cli():
    """Command-line interface for DIGIPIN compaction."""
    parser = argparse.ArgumentParser(description="DIGIPIN Compact")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input DIGIPIN (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="DIGIPIN ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )
    parser.add_argument(
        "-d",
        "--depth",
        type=int,
        default=-1,
        help="Compaction depth: 0 = no-op, -1 = compact fully (default), "
        "1 = direct parent, 2 = grandparent, ...",
    )
    parser.add_argument(
        "-agg",
        "--agg",
        choices=AGG_OPTIONS,
        default="count",
        help="Aggregation option",
    )
    parser.add_argument(
        "-numeric_col",
        "--numeric_col",
        dest="numeric_col",
        required=False,
        help="Numeric field to aggregate (required if agg != 'count')",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Show progress bar (default: True). Use --no-verbose to hide it.",
    )

    args = parser.parse_args()
    input_data = args.input
    cellid = args.cellid
    output_format = args.output_format

    result = digipincompact(
        input_data,
        digipin_id=cellid,
        output_format=output_format,
        depth=args.depth,
        agg=args.agg,
        numeric_col=args.numeric_col,
        verbose=args.verbose,
    )

    if output_format in STRUCTURED_FORMATS:
        print(result)

digipinexpand(input_data, resolution=None, digipin_id='digipin', output_format='gpd', verbose=True, depth=None)

Expand (uncompact) DIGIPIN cells to a target resolution or by a relative depth.

When resolution is set, depth is ignored and cells are expanded to that absolute resolution (must be >= the maximum input resolution). When only depth is set, resolution is ignored: mixed-resolution input is allowed and each cell is expanded to its descendants depth levels down.

Source code in vgrid/conversion/dggscompact/digipincompact.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
def digipinexpand(
    input_data,
    resolution=None,
    digipin_id="digipin",
    output_format="gpd",
    verbose=True,
    depth=None,
):
    """
    Expand (uncompact) DIGIPIN cells to a target resolution or by a relative depth.

    When ``resolution`` is set, ``depth`` is ignored and cells are expanded to
    that absolute resolution (must be >= the maximum input resolution). When
    only ``depth`` is set, ``resolution`` is ignored: mixed-resolution input is
    allowed and each cell is expanded to its descendants ``depth`` levels down.
    """
    if resolution is not None:
        resolution = validate_dggs_expand_resolution("digipin", resolution)
    elif depth is not None:
        depth = validate_dggs_expand_depth("digipin", depth)
    else:
        raise ValueError("Either resolution or depth must be specified.")

    gdf = process_input_data_compact(input_data, digipin_id)
    digipin_ids = gdf[digipin_id].drop_duplicates().tolist()

    if not digipin_ids:
        print(f"No DIGIPIN IDs found in <{digipin_id}> field.")
        return

    try:
        if resolution is not None:
            max_res = max(digipin_resolution(tid) for tid in digipin_ids)
            if isinstance(max_res, str):
                raise ValueError("Invalid DIGIPIN format.")
            if resolution < max_res:
                print(f"Target expand resolution ({resolution}) must >= {max_res}.")
                return None
            digipin_ids_expand = digipin_expand(digipin_ids, resolution=resolution, verbose=verbose)
        else:
            digipin_ids_expand = digipin_expand(digipin_ids, depth=depth, verbose=verbose)
    except Exception:
        raise Exception(
            "Expand cells failed. Please check your DIGIPIN ID field, resolution, or depth."
        )

    if not digipin_ids_expand:
        return None

    rows = []
    for digipin_id_expand in tqdm(
        digipin_ids_expand,
        desc="Building DIGIPIN expand",
        unit=" cells",
        disable=not verbose,
    ):
        try:
            cell_polygon = digipin2geo(digipin_id_expand)
            cell_resolution = digipin_resolution(digipin_id_expand)
            row = graticule_dggs_to_geoseries(
                "digipin", digipin_id_expand, cell_resolution, cell_polygon
            )
            rows.append(row)
        except Exception:
            continue

    out_gdf = gpd.GeoDataFrame(rows, geometry="geometry", crs="EPSG:4326")

    output_name = None
    if output_format in OUTPUT_FORMATS:
        if isinstance(input_data, str):
            base = os.path.splitext(os.path.basename(input_data))[0]
            output_name = f"{base}_digipin_expanded"
        else:
            output_name = "digipin_expanded"

    return convert_to_output_format(out_gdf, output_format, output_name)

digipinexpand_cli()

Command-line interface for DIGIPIN expansion.

Source code in vgrid/conversion/dggscompact/digipincompact.py
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
def digipinexpand_cli():
    """Command-line interface for DIGIPIN expansion."""
    parser = argparse.ArgumentParser(description="DIGIPIN Expand (Uncompact)")
    parser.add_argument(
        "-i",
        "--input",
        type=str,
        required=True,
        help="Input DIGIPIN (GeoJSON, Shapefile, CSV, Parquet, or pickled GeoDataFrame .gpd/.geopandas)",
    )
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument(
        "-r",
        "--resolution",
        type=int,
        help="Target DIGIPIN resolution to expand to (must be >= maximum input resolution). "
        "Ignores --depth.",
    )
    mode.add_argument(
        "-d",
        "--depth",
        type=int,
        help="Expand each cell by this many child levels (1 = direct children, "
        "2 = grandchildren, ...; 1 <= depth <= DIGIPIN max_res). "
        "Mixed input resolutions are allowed. Ignores --resolution.",
    )
    parser.add_argument("-cellid", "--cellid", type=str, help="DIGIPIN ID field")
    parser.add_argument(
        "-f",
        "--output_format",
        type=str,
        default="gpd",
        choices=OUTPUT_FORMATS,
        help="Output format",
    )

    add_verbose_argument(parser)
    args = parser.parse_args()
    result = digipinexpand(
        args.input,
        resolution=args.resolution,
        digipin_id=args.cellid,
        output_format=args.output_format,
        depth=args.depth,
        verbose=args.verbose,
    )

    if args.output_format in STRUCTURED_FORMATS:
        print(result)