Skip to content

API Reference

Auto-generated code documentation.

civic_lib_geo

__main__

Entry point for Civic Lib Geo CLI.

File: civic_lib_geo.main

cli

check_size

check_size.py.

Check the size of a GeoJSON file and whether it exceeds GitHub Pages limits.

Used by civic-geo CLI

civic-geo check-size

main
main(path: Path) -> int

Print the size of a GeoJSON file and whether it exceeds GitHub Pages limits.

Parameters:

Name Type Description Default
path Path

Path to the GeoJSON file to inspect.

required

Returns:

Name Type Description
int int

0 if OK, 1 if an error occurs.

Source code in src/civic_lib_geo/cli/check_size.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def main(path: Path) -> int:
    """Print the size of a GeoJSON file and whether it exceeds GitHub Pages limits.

    Args:
        path (Path): Path to the GeoJSON file to inspect.

    Returns:
        int: 0 if OK, 1 if an error occurs.
    """
    try:
        path = Path(path)  # Defensive cast
        size_mb = get_file_size_mb(path)
        logger.info(f"File size: {size_mb:.2f} MB")

        if needs_chunking(path):
            logger.warning("File exceeds 25MB GitHub Pages limit. Consider chunking.")
        else:
            logger.info("File is within acceptable size limits.")
        return 0
    except Exception as e:
        logger.error(f"Error checking file size: {e}")
        return 1

chunk_geojson

chunk_geojson.py.

Utility to split a GeoJSON FeatureCollection into smaller files.

Used by civic-geo CLI

civic-geo chunk path/to/file.geojson --max-features 500 civic-geo chunk path/to/folder --all-files

chunk_one
chunk_one(path: Path, max_features: int, output_dir: Path)

Chunk a single GeoJSON file and write the output files.

Parameters:

Name Type Description Default
path Path

Path to input GeoJSON file.

required
max_features int

Max features per chunk.

required
output_dir Path

Output folder to store chunks.

required
Source code in src/civic_lib_geo/cli/chunk_geojson.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def chunk_one(path: Path, max_features: int, output_dir: Path):
    """Chunk a single GeoJSON file and write the output files.

    Args:
        path (Path): Path to input GeoJSON file.
        max_features (int): Max features per chunk.
        output_dir (Path): Output folder to store chunks.
    """
    try:
        geojson_dict = json.loads(path.read_text(encoding="utf-8"))
        chunk_paths = chunk_geojson_features(
            geojson=geojson_dict,
            max_features=max_features,
            output_dir=output_dir,
            base_name=path.stem + "_chunk",
        )
        logger.info(f"Wrote {len(chunk_paths)} chunks to {output_dir}")
    except Exception as e:
        logger.error(f"Error chunking {path}: {e}")
        raise
main
main(
    path: Path,
    max_features: int = 500,
    output_dir: Path = Path('chunks'),
    all_files: bool = False,
) -> int

Chunk a single file or all .geojson files in a folder.

Parameters:

Name Type Description Default
path Path

Input file or folder path.

required
max_features int

Max features per chunk.

500
output_dir Path

Directory to store chunks.

Path('chunks')
all_files bool

If True and path is a folder, chunk all files in it.

False

Returns:

Name Type Description
int int

Exit code, 0 if success, 1 if failure.

Source code in src/civic_lib_geo/cli/chunk_geojson.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def main(
    path: Path,
    max_features: int = 500,
    output_dir: Path = Path("chunks"),
    all_files: bool = False,
) -> int:
    """Chunk a single file or all .geojson files in a folder.

    Args:
        path (Path): Input file or folder path.
        max_features (int): Max features per chunk.
        output_dir (Path): Directory to store chunks.
        all_files (bool): If True and path is a folder, chunk all files in it.

    Returns:
        int: Exit code, 0 if success, 1 if failure.
    """
    try:
        if path.is_dir():
            if not all_files:
                logger.error(
                    "Provided path is a directory. Use --all-files to process all files in it."
                )
                return 1

            apply_to_geojson_folder(
                folder=path,
                action_fn=chunk_one,
                suffix="_chunked.geojson",
                max_features=max_features,
            )
        else:
            chunk_one(path, max_features, output_dir)

        return 0
    except Exception:
        return 1

cli

cli.py.

Command-line interface (CLI) for civic-lib-geo.

Provides repo-specific commands for: - Chunking - Simplifying - Property inspection - File size checks

Run civic-geo --help for usage.

check_size_command
check_size_command(path: Path)

Report the size of a GeoJSON file and whether it exceeds the GitHub Pages 25MB limit.

Source code in src/civic_lib_geo/cli/cli.py
32
33
34
35
36
37
38
@app.command("check-size")
@app.command("size")
def check_size_command(path: Path):
    """Report the size of a GeoJSON file and whether it exceeds the GitHub Pages 25MB limit."""
    from . import check_size

    check_size.main(path)
chunk_command
chunk_command(
    folder: Path = FOLDER_ARG,
    max_features: int = 500,
    single_file: Path = SINGLE_FILE_OPT,
)

Chunk a GeoJSON file or all files in a folder into smaller files with limited features.

Source code in src/civic_lib_geo/cli/cli.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@app.command("chunk-geojson")
@app.command("chunk")
def chunk_command(
    folder: Path = FOLDER_ARG,
    max_features: int = 500,
    single_file: Path = SINGLE_FILE_OPT,
):
    """Chunk a GeoJSON file or all files in a folder into smaller files with limited features."""
    from . import chunk_geojson

    if single_file:
        chunk_geojson.main(single_file, max_features)
    else:
        geojson_utils.apply_to_geojson_folder(
            folder, chunk_geojson.main, max_features=max_features, suffix="_chunked.geojson"
        )
main
main() -> int

Run the main entry point for the CLI application.

This function serves as the primary entry point for the command-line interface. It initializes and runs the app, then returns a success status code.

Returns:

Name Type Description
int int

Exit status code (0 for success).

Source code in src/civic_lib_geo/cli/cli.py
104
105
106
107
108
109
110
111
112
113
114
def main() -> int:
    """Run the main entry point for the CLI application.

    This function serves as the primary entry point for the command-line interface.
    It initializes and runs the app, then returns a success status code.

    Returns:
        int: Exit status code (0 for success).
    """
    app()
    return 0
props_command
props_command(path: Path)

Display the property keys from the first feature of a GeoJSON file.

Source code in src/civic_lib_geo/cli/cli.py
59
60
61
62
63
64
@app.command("read-props")
def props_command(path: Path):
    """Display the property keys from the first feature of a GeoJSON file."""
    from . import read_props

    read_props.main(path)
shapefile_to_geojson_command
shapefile_to_geojson_command(
    shp_path: Path, geojson_path: Path
)

Convert a shapefile to GeoJSON.

Source code in src/civic_lib_geo/cli/cli.py
85
86
87
88
89
90
91
@app.command("shapefile-to-geojson")
@app.command("shp-to-geo")
def shapefile_to_geojson_command(shp_path: Path, geojson_path: Path):
    """Convert a shapefile to GeoJSON."""
    from . import shapefile_to_geojson

    shapefile_to_geojson.main(shp_path, geojson_path)
simplify_command
simplify_command(
    folder: Path = FOLDER_ARG,
    tolerance: float = 0.01,
    single_file: Path = SINGLE_FILE_OPT,
)

Simplify one GeoJSON file or all files in a folder using the given tolerance.

Source code in src/civic_lib_geo/cli/cli.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@app.command("simplify-geojson")
@app.command("simplify")
def simplify_command(
    folder: Path = FOLDER_ARG,
    tolerance: float = 0.01,
    single_file: Path = SINGLE_FILE_OPT,
):
    """Simplify one GeoJSON file or all files in a folder using the given tolerance."""
    from . import simplify_geojson

    if single_file:
        simplify_geojson.main(single_file, tolerance)
    else:
        geojson_utils.apply_to_geojson_folder(
            folder, simplify_geojson.main, tolerance=tolerance, suffix="_simplified.geojson"
        )
topojson_to_geojson_command
topojson_to_geojson_command(
    topo_path: Path, geojson_path: Path
)

Convert a TopoJSON file to GeoJSON using GeoPandas (if supported).

Source code in src/civic_lib_geo/cli/cli.py
 94
 95
 96
 97
 98
 99
100
101
@app.command("topojson-to-geojson")
@app.command("topo-to-geo")
def topojson_to_geojson_command(topo_path: Path, geojson_path: Path):
    """Convert a TopoJSON file to GeoJSON using GeoPandas (if supported)."""
    from . import topojson_to_geojson

    logger.warning("⚠️ TopoJSON support depends on GDAL. Consider using mapshaper if this fails.")
    topojson_to_geojson.main(topo_path, geojson_path)

read_props

read_props.py.

Command-line utility to preview feature properties in one or more GeoJSON files.

Displays the .properties dictionary for the first few features in each file. Useful for inspecting available fields before cleaning, chunking, or simplifying.

Supports: - A single file: civic-geo read-props file.geojson - A folder of files: civic-geo read-props data/ --all-files

main
main(path: Path, all_files: bool = False) -> int

Display feature properties for a single file or all .geojson files in a folder.

Parameters:

Name Type Description Default
path Path

File or folder path.

required
all_files bool

If True and path is a folder, process all .geojson files inside.

False

Returns:

Name Type Description
int int

0 if success, 1 on error.

Source code in src/civic_lib_geo/cli/read_props.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def main(path: Path, all_files: bool = False) -> int:
    """Display feature properties for a single file or all .geojson files in a folder.

    Args:
        path (Path): File or folder path.
        all_files (bool): If True and path is a folder, process all .geojson files inside.

    Returns:
        int: 0 if success, 1 on error.
    """
    try:
        if path.is_dir():
            if not all_files:
                logger.error(
                    "Provided path is a directory. Use --all-files to process all files in it."
                )
                return 1
            apply_to_geojson_folder(
                folder=path,
                action_fn=read_props_one,
                suffix="",  # No new file written
            )
        else:
            read_props_one(path)
        return 0
    except Exception:
        return 1
read_props_one
read_props_one(
    path: Path,
    output: Path | None = None,
    max_rows: int = 5,
)

Print properties from the first few features in a GeoJSON file.

Parameters:

Name Type Description Default
path Path

Path to the GeoJSON file.

required
output Path | None

Ignored in this function (kept for compatibility).

None
max_rows int

Number of feature properties to preview.

5
Source code in src/civic_lib_geo/cli/read_props.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def read_props_one(path: Path, output: Path | None = None, max_rows: int = 5):
    """Print properties from the first few features in a GeoJSON file.

    Args:
        path (Path): Path to the GeoJSON file.
        output (Path | None): Ignored in this function (kept for compatibility).
        max_rows (int): Number of feature properties to preview.
    """
    try:
        props = read_geojson_props(path)
        logger.info(f"{path.name}: {len(props)} features loaded")

        for i, row in enumerate(props[:max_rows], 1):
            print(f"\nFeature {i}:")
            for key, value in row.items():
                print(f"  {key}: {value}")
        if len(props) > max_rows:
            print(f"\n... and {len(props) - max_rows} more.")
    except Exception as e:
        logger.error(f"Error reading properties from {path}: {e}")
        raise

shapefile_to_geojson

shapefile_to_geojson.py.

Command-line utility to convert a shapefile to GeoJSON.

Used by

civic-geo shapefile-to-geojson SHAPEFILE_PATH GEOJSON_PATH

main
main(shp_path: Path, geojson_path: Path) -> int

Convert a shapefile to GeoJSON.

Parameters:

Name Type Description Default
shp_path Path

Input .shp file path.

required
geojson_path Path

Output .geojson file path.

required

Returns:

Name Type Description
int int

0 if success, 1 on error.

Source code in src/civic_lib_geo/cli/shapefile_to_geojson.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def main(shp_path: Path, geojson_path: Path) -> int:
    """Convert a shapefile to GeoJSON.

    Args:
        shp_path (Path): Input .shp file path.
        geojson_path (Path): Output .geojson file path.

    Returns:
        int: 0 if success, 1 on error.
    """
    try:
        shapefile_utils.convert_shapefile_to_geojson(shp_path, geojson_path)
        logger.info(f"Converted {shp_path} to {geojson_path}")
        return 0
    except Exception as e:
        logger.error(f"Error converting shapefile: {e}")
        return 1

simplify_geojson

simplify_geojson.py.

Command-line utility to simplify the geometry of one or more GeoJSON files.

Reduces geometry complexity using a specified tolerance and saves the simplified output to a new file. When given a directory with --all-files, all .geojson files will be processed.

Usage

civic-geo simplify path/to/file.geojson civic-geo simplify path/to/folder --all-files

main
main(
    path: Path,
    tolerance: float = 0.01,
    output: Path | None = None,
    all_files: bool = False,
)

Simplify a single file or all .geojson files in a folder.

Parameters:

Name Type Description Default
path Path

Input file or folder.

required
tolerance float

Simplification tolerance.

0.01
output Path | None

Output file path for single file use.

None
all_files bool

If True and path is a folder, simplify all .geojson files.

False
Source code in src/civic_lib_geo/cli/simplify_geojson.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def main(
    path: Path,
    tolerance: float = 0.01,
    output: Path | None = None,
    all_files: bool = False,
):
    """Simplify a single file or all .geojson files in a folder.

    Args:
        path (Path): Input file or folder.
        tolerance (float): Simplification tolerance.
        output (Path | None): Output file path for single file use.
        all_files (bool): If True and path is a folder, simplify all .geojson files.
    """
    if path.is_dir():
        if not all_files:
            logger.error(
                "Provided path is a directory. Use --all-files to process all files in it."
            )
            raise SystemExit(1)
        apply_to_geojson_folder(
            folder=path,
            action_fn=simplify_one,
            suffix="_simplified.geojson",
            tolerance=tolerance,
        )
    else:
        out = output or path.with_name(path.stem + "_simplified.geojson")
        simplify_one(path, tolerance, out)
simplify_one
simplify_one(path: Path, tolerance: float, output: Path)

Simplify a single GeoJSON file and write the output.

Parameters:

Name Type Description Default
path Path

Path to the original GeoJSON file.

required
tolerance float

Tolerance for simplification.

required
output Path

Output path for the simplified file.

required
Source code in src/civic_lib_geo/cli/simplify_geojson.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def simplify_one(path: Path, tolerance: float, output: Path):
    """Simplify a single GeoJSON file and write the output.

    Args:
        path (Path): Path to the original GeoJSON file.
        tolerance (float): Tolerance for simplification.
        output (Path): Output path for the simplified file.
    """
    try:
        gdf = load_geojson(path)
        logger.info(f"Loaded {len(gdf)} features from {path}")
        simplified = simplify_geojson(gdf, tolerance)
        save_geojson(simplified, output)
        logger.info(f"Simplified saved to: {output}")
    except Exception as e:
        logger.error(f"Error simplifying {path}: {e}")
        raise SystemExit(1) from e

topojson_to_geojson

topojson_to_geojson.py.

Command-line utility to convert a TopoJSON file to GeoJSON.

Used by

civic-geo topojson-to-geojson TOPOJSON_PATH GEOJSON_PATH

main
main(topo_path: Path, geojson_path: Path) -> int

Convert a TopoJSON file to GeoJSON using GeoPandas (if supported).

Parameters:

Name Type Description Default
topo_path Path

Input .topojson file path.

required
geojson_path Path

Output .geojson file path.

required

Returns:

Name Type Description
int int

0 if success, 1 if failure.

Source code in src/civic_lib_geo/cli/topojson_to_geojson.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def main(topo_path: Path, geojson_path: Path) -> int:
    """Convert a TopoJSON file to GeoJSON using GeoPandas (if supported).

    Args:
        topo_path (Path): Input .topojson file path.
        geojson_path (Path): Output .geojson file path.

    Returns:
        int: 0 if success, 1 if failure.
    """
    try:
        import geopandas as gpd

        gdf = gpd.read_file(topo_path)
        gdf.to_file(geojson_path, driver="GeoJSON")
        logger.info(f"Converted {topo_path} to {geojson_path}")
        return 0
    except Exception as e:
        logger.error(f"TopoJSON conversion failed: {e}")
        return 1

fips_utils

civic_lib_geo/fips_utils.py.

Utilities for working with FIPS codes.

MIT License — maintained by Civic Interconnect

get_fips_by_state_code

get_fips_by_state_code(
    state_code: str, source: Path | None = None
) -> str

Return the FIPS code for a given 2-letter state code.

Parameters:

Name Type Description Default
state_code str

A 2-letter state abbreviation (e.g., 'MN').

required
source Path | None

Optional override path to a custom CSV file.

None

Returns:

Name Type Description
str str

Corresponding FIPS code (e.g., '27').

Raises:

Type Description
ValueError

If the state code is not found.

Source code in src/civic_lib_geo/fips_utils.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def get_fips_by_state_code(state_code: str, source: Path | None = None) -> str:
    """Return the FIPS code for a given 2-letter state code.

    Args:
        state_code (str): A 2-letter state abbreviation (e.g., 'MN').
        source (Path | None): Optional override path to a custom CSV file.

    Returns:
        str: Corresponding FIPS code (e.g., '27').

    Raises:
        ValueError: If the state code is not found.
    """
    df = get_state_fips_df(source)
    result = df[df["state_code"].str.upper() == state_code.upper()]
    if result.empty:
        raise ValueError(f"State code '{state_code}' not found in FIPS data.")
    return result.iloc[0]["fips_code"]

get_state_fips_df

get_state_fips_df(
    source: Path | None = None,
) -> pd.DataFrame

Load and return a DataFrame of US state FIPS codes.

Parameters:

Name Type Description Default
source Path | None

Path to a CSV file. If None, uses the default embedded CSV.

None

Returns:

Type Description
DataFrame

pd.DataFrame: A DataFrame with columns ['state_code', 'state_name', 'fips_code'].

Source code in src/civic_lib_geo/fips_utils.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def get_state_fips_df(source: Path | None = None) -> pd.DataFrame:
    """Load and return a DataFrame of US state FIPS codes.

    Args:
        source (Path | None): Path to a CSV file. If None, uses the default embedded CSV.

    Returns:
        pd.DataFrame: A DataFrame with columns ['state_code', 'state_name', 'fips_code'].
    """
    if source is None:
        source = Path(__file__).parent / "data" / "us-state-fips.csv"
    df = read_csv_from_path(source)
    df.columns = [col.strip().lower() for col in df.columns]

    expected = {"state_code", "state_name", "fips_code"}
    if not expected.issubset(df.columns):
        raise ValueError(f"Missing expected columns. Found: {df.columns}")

    return df

get_state_name_by_code

get_state_name_by_code(
    state_code: str, source: Path | None = None
) -> str

Return the full state name for a given 2-letter state code.

Parameters:

Name Type Description Default
state_code str

A 2-letter state abbreviation.

required
source Path | None

Optional override path to a custom CSV file.

None

Returns:

Name Type Description
str str

Full state name (e.g., 'Minnesota').

Raises:

Type Description
ValueError

If the state code is not found.

Source code in src/civic_lib_geo/fips_utils.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def get_state_name_by_code(state_code: str, source: Path | None = None) -> str:
    """Return the full state name for a given 2-letter state code.

    Args:
        state_code (str): A 2-letter state abbreviation.
        source (Path | None): Optional override path to a custom CSV file.

    Returns:
        str: Full state name (e.g., 'Minnesota').

    Raises:
        ValueError: If the state code is not found.
    """
    df = get_state_fips_df(source)
    result = df[df["state_code"].str.upper() == state_code.upper()]
    if result.empty:
        raise ValueError(f"State code '{state_code}' not found in FIPS data.")
    return result.iloc[0]["state_name"]

read_csv_from_path

read_csv_from_path(source: Path) -> pd.DataFrame

Read a CSV file from the given path and returns a DataFrame.

Parameters:

Name Type Description Default
source Path

Path to the CSV file.

required

Returns:

Type Description
DataFrame

pd.DataFrame: DataFrame containing the CSV data.

Source code in src/civic_lib_geo/fips_utils.py
20
21
22
23
24
25
26
27
28
29
def read_csv_from_path(source: Path) -> pd.DataFrame:
    """Read a CSV file from the given path and returns a DataFrame.

    Args:
        source (Path): Path to the CSV file.

    Returns:
        pd.DataFrame: DataFrame containing the CSV data.
    """
    return pd.read_csv(source, dtype=str)

geojson_utils

civic_lib_geo/geojson_utils.py.

GeoJSON utility functions for Civic Interconnect.

MIT License — maintained by Civic Interconnect

apply_to_geojson_folder

apply_to_geojson_folder(
    folder: Path,
    action_fn: Callable,
    *,
    suffix: str = '_processed.geojson',
    tolerance: float | None = None,
    max_features: int | None = None,
)

Apply an action to every .geojson file in a folder.

Parameters:

Name Type Description Default
folder Path

Path to folder containing .geojson files.

required
action_fn Callable

Function to apply to each file.

required
suffix str

Suffix to add to output filenames.

'_processed.geojson'
tolerance float | None

Optional tolerance value for simplification.

None
max_features int | None

Optional limit for chunking.

None
Source code in src/civic_lib_geo/geojson_utils.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def apply_to_geojson_folder(
    folder: Path,
    action_fn: Callable,
    *,
    suffix: str = "_processed.geojson",
    tolerance: float | None = None,
    max_features: int | None = None,
):
    """Apply an action to every .geojson file in a folder.

    Args:
        folder (Path): Path to folder containing .geojson files.
        action_fn (Callable): Function to apply to each file.
        suffix (str): Suffix to add to output filenames.
        tolerance (float | None): Optional tolerance value for simplification.
        max_features (int | None): Optional limit for chunking.
    """
    files = list(folder.glob("*.geojson"))
    if not files:
        logger.warning(f"No .geojson files found in {folder}")
        return

    logger.info(f"Found {len(files)} GeoJSON file(s) in {folder}")
    for file in files:
        output_path = file.with_name(file.stem + suffix)
        try:
            logger.info(f"Processing {file.name}")
            if tolerance is not None:
                action_fn(file, tolerance, output_path)
            elif max_features is not None:
                action_fn(file, max_features, output_path)
            else:
                action_fn(file, output_path)
        except Exception as e:
            logger.error(f"Failed to process {file.name}: {e}")

chunk_geojson_features

chunk_geojson_features(
    geojson: dict,
    max_features: int = 500,
    output_dir: str | Path = 'chunks',
    base_name: str = 'chunk',
) -> list[Path]

Split a GeoJSON FeatureCollection into multiple smaller files.

Parameters:

Name Type Description Default
geojson dict

Loaded GeoJSON dictionary (must contain a 'features' list).

required
max_features int

Maximum number of features per chunk.

500
output_dir str | Path

Directory to write chunked files to.

'chunks'
base_name str

Base filename prefix for each chunk.

'chunk'

Returns:

Type Description
list[Path]

List of Paths to chunked files.

Raises:

Type Description
ValueError

If 'features' is missing or not a list.

Source code in src/civic_lib_geo/geojson_utils.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def chunk_geojson_features(
    geojson: dict,
    max_features: int = 500,
    output_dir: str | Path = "chunks",
    base_name: str = "chunk",
) -> list[Path]:
    """Split a GeoJSON FeatureCollection into multiple smaller files.

    Args:
        geojson: Loaded GeoJSON dictionary (must contain a 'features' list).
        max_features: Maximum number of features per chunk.
        output_dir: Directory to write chunked files to.
        base_name: Base filename prefix for each chunk.

    Returns:
        List of Paths to chunked files.

    Raises:
        ValueError: If 'features' is missing or not a list.
    """
    if "features" not in geojson or not isinstance(geojson["features"], list):
        raise ValueError("Invalid GeoJSON: missing or malformed 'features' array.")

    features = geojson["features"]
    total = len(features)
    logger.info(f"Splitting {total} features into chunks of {max_features}")

    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    chunks_written = []
    for i in range(0, total, max_features):
        chunk = {"type": "FeatureCollection", "features": features[i : i + max_features]}
        chunk_path = output_dir / f"{base_name}_{i // max_features + 1}.geojson"
        try:
            with chunk_path.open("w", encoding="utf-8") as f:
                json.dump(chunk, f, ensure_ascii=False, indent=2)
            logger.info(f"Wrote {len(chunk['features'])} features to {chunk_path}")
            chunks_written.append(chunk_path)
        except Exception as e:
            logger.error(f"Failed to write chunk to {chunk_path}: {e}")
            raise

    return chunks_written

get_file_size_mb

get_file_size_mb(path: str | Path) -> float

Return the file size in megabytes (MB).

Parameters:

Name Type Description Default
path str | Path

Path to the file.

required

Returns:

Type Description
float

File size in megabytes.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

Source code in src/civic_lib_geo/geojson_utils.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def get_file_size_mb(path: str | Path) -> float:
    """Return the file size in megabytes (MB).

    Args:
        path: Path to the file.

    Returns:
        File size in megabytes.

    Raises:
        FileNotFoundError: If the file does not exist.
    """
    path = Path(path)
    if not path.is_file():
        raise FileNotFoundError(f"File not found: {path}")
    size_mb = path.stat().st_size / (1024 * 1024)
    logger.debug(f"File size of '{path}': {size_mb:.2f} MB")
    return size_mb

is_valid_geojson_feature_collection

is_valid_geojson_feature_collection(obj: dict) -> bool

Quick check if an object looks like a valid GeoJSON FeatureCollection.

Parameters:

Name Type Description Default
obj dict

Dictionary to check.

required

Returns:

Type Description
bool

True if valid structure, else False.

Source code in src/civic_lib_geo/geojson_utils.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def is_valid_geojson_feature_collection(obj: dict) -> bool:
    """Quick check if an object looks like a valid GeoJSON FeatureCollection.

    Args:
        obj: Dictionary to check.

    Returns:
        True if valid structure, else False.
    """
    return (
        isinstance(obj, dict)
        and obj.get("type") == "FeatureCollection"
        and isinstance(obj.get("features"), list)
    )

list_geojson_files

list_geojson_files(folder: Path) -> list[Path]

Return a list of .geojson files in the specified folder.

Parameters:

Name Type Description Default
folder Path

Directory to search.

required

Returns:

Type Description
list[Path]

list[Path]: List of .geojson file paths.

Source code in src/civic_lib_geo/geojson_utils.py
152
153
154
155
156
157
158
159
160
161
def list_geojson_files(folder: Path) -> list[Path]:
    """Return a list of .geojson files in the specified folder.

    Args:
        folder (Path): Directory to search.

    Returns:
        list[Path]: List of .geojson file paths.
    """
    return list(folder.glob("*.geojson"))

load_geojson

load_geojson(path: Path) -> gpd.GeoDataFrame

Load a GeoJSON file into a GeoDataFrame.

Parameters:

Name Type Description Default
path Path

Path to the GeoJSON file.

required

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: A GeoDataFrame with geometries and attributes.

Source code in src/civic_lib_geo/geojson_utils.py
164
165
166
167
168
169
170
171
172
173
174
175
def load_geojson(path: Path) -> gpd.GeoDataFrame:
    """Load a GeoJSON file into a GeoDataFrame.

    Args:
        path (Path): Path to the GeoJSON file.

    Returns:
        gpd.GeoDataFrame: A GeoDataFrame with geometries and attributes.
    """
    import geopandas as gpd

    return gpd.read_file(path)

needs_chunking

needs_chunking(
    path: str | Path, max_mb: float = 25.0
) -> bool

Determine whether the GeoJSON file exceeds the size threshold.

Parameters:

Name Type Description Default
path str | Path

Path to the file.

required
max_mb float

Maximum file size before chunking is recommended.

25.0

Returns:

Type Description
bool

True if file exceeds max_mb, else False.

Source code in src/civic_lib_geo/geojson_utils.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
def needs_chunking(path: str | Path, max_mb: float = 25.0) -> bool:
    """Determine whether the GeoJSON file exceeds the size threshold.

    Args:
        path: Path to the file.
        max_mb: Maximum file size before chunking is recommended.

    Returns:
        True if file exceeds max_mb, else False.
    """
    size_mb = get_file_size_mb(path)
    needs_split = size_mb > max_mb
    logger.debug(f"Needs chunking: {needs_split} (size: {size_mb:.2f} MB, limit: {max_mb} MB)")
    return needs_split

read_geojson_props

read_geojson_props(path: Path) -> list[dict[str, Any]]

Load only the properties from a GeoJSON file.

Parameters:

Name Type Description Default
path Path

Path to the GeoJSON file.

required

Returns:

Type Description
list[dict[str, Any]]

list[dict[str, Any]]: A list of property dictionaries from each feature.

Source code in src/civic_lib_geo/geojson_utils.py
194
195
196
197
198
199
200
201
202
203
204
205
def read_geojson_props(path: Path) -> list[dict[str, Any]]:
    """Load only the properties from a GeoJSON file.

    Args:
        path (Path): Path to the GeoJSON file.

    Returns:
        list[dict[str, Any]]: A list of property dictionaries from each feature.
    """
    with path.open(encoding="utf-8") as f:
        data = json.load(f)
    return [feature["properties"] for feature in data["features"]]

save_geojson

save_geojson(
    gdf: GeoDataFrame, path: Path, indent: int = 2
) -> Path

Save a GeoDataFrame to GeoJSON format.

Parameters:

Name Type Description Default
gdf GeoDataFrame

The GeoDataFrame to save.

required
path Path

Output file path.

required
indent int

Indentation level for formatting (unused by GeoPandas but included for consistency).

2

Returns:

Name Type Description
Path Path

The path to the saved file.

Source code in src/civic_lib_geo/geojson_utils.py
208
209
210
211
212
213
214
215
216
217
218
219
220
def save_geojson(gdf: "gpd.GeoDataFrame", path: Path, indent: int = 2) -> Path:
    """Save a GeoDataFrame to GeoJSON format.

    Args:
        gdf (gpd.GeoDataFrame): The GeoDataFrame to save.
        path (Path): Output file path.
        indent (int): Indentation level for formatting (unused by GeoPandas but included for consistency).

    Returns:
        Path: The path to the saved file.
    """
    gdf.to_file(path, driver="GeoJSON")
    return path

simplify_geojson

simplify_geojson(
    gdf: GeoDataFrame, tolerance: float
) -> gpd.GeoDataFrame

Return a simplified copy of the GeoDataFrame using the given tolerance.

Parameters:

Name Type Description Default
gdf GeoDataFrame

The input GeoDataFrame.

required
tolerance float

Tolerance for simplification (smaller values retain more detail).

required

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: A new GeoDataFrame with simplified geometry.

Source code in src/civic_lib_geo/geojson_utils.py
223
224
225
226
227
228
229
230
231
232
233
def simplify_geojson(gdf: gpd.GeoDataFrame, tolerance: float) -> gpd.GeoDataFrame:
    """Return a simplified copy of the GeoDataFrame using the given tolerance.

    Args:
        gdf (gpd.GeoDataFrame): The input GeoDataFrame.
        tolerance (float): Tolerance for simplification (smaller values retain more detail).

    Returns:
        gpd.GeoDataFrame: A new GeoDataFrame with simplified geometry.
    """
    return gdf.copy().assign(geometry=gdf.geometry.simplify(tolerance, preserve_topology=True))

shapefile_utils

civic_lib_geo/shapefile_utils.py.

Shapefile utility functions for Civic Interconnect.

MIT License — maintained by Civic Interconnect

convert_shapefile_to_geojson

convert_shapefile_to_geojson(
    shp_path: Path, geojson_path: Path
) -> Path

Convert a shapefile to a GeoJSON file.

Parameters:

Name Type Description Default
shp_path Path

Path to the source shapefile (.shp).

required
geojson_path Path

Path to the output GeoJSON file.

required

Returns:

Name Type Description
Path Path

The path to the saved GeoJSON file.

Source code in src/civic_lib_geo/shapefile_utils.py
20
21
22
23
24
25
26
27
28
29
30
31
32
def convert_shapefile_to_geojson(shp_path: Path, geojson_path: Path) -> Path:
    """Convert a shapefile to a GeoJSON file.

    Args:
        shp_path (Path): Path to the source shapefile (.shp).
        geojson_path (Path): Path to the output GeoJSON file.

    Returns:
        Path: The path to the saved GeoJSON file.
    """
    gdf = load_shapefile(shp_path)
    gdf.to_file(geojson_path, driver="GeoJSON")
    return geojson_path

load_shapefile

load_shapefile(path: Path) -> gpd.GeoDataFrame

Load a shapefile into a GeoDataFrame.

Parameters:

Name Type Description Default
path Path

Path to the shapefile (.shp).

required

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: A GeoDataFrame with geometries and attributes.

Source code in src/civic_lib_geo/shapefile_utils.py
35
36
37
38
39
40
41
42
43
44
def load_shapefile(path: Path) -> gpd.GeoDataFrame:
    """Load a shapefile into a GeoDataFrame.

    Args:
        path (Path): Path to the shapefile (.shp).

    Returns:
        gpd.GeoDataFrame: A GeoDataFrame with geometries and attributes.
    """
    return gpd.read_file(path)

topojson_utils

civic_lib_geo/topojson_utils.py.

TopoJSON utility functions for Civic Interconnect.

MIT License — maintained by Civic Interconnect

convert_topojson_to_geojson

convert_topojson_to_geojson(
    topojson_path: Path, geojson_path: Path
) -> Path

Convert a TopoJSON file to a GeoJSON file using geopandas or CLI tools.

Source code in src/civic_lib_geo/topojson_utils.py
20
21
22
23
24
def convert_topojson_to_geojson(topojson_path: Path, geojson_path: Path) -> Path:
    """Convert a TopoJSON file to a GeoJSON file using geopandas or CLI tools."""
    gdf = gpd.read_file(topojson_path)
    gdf.to_file(geojson_path, driver="GeoJSON")
    return geojson_path

us_constants

civic_lib_geo/us_constants.py.

US State Code Constants for Civic Interconnect

This module defines reusable mappings and lookup records for U.S. state information, including:

  • 2-letter abbreviations
  • Full state names
  • FIPS codes
  • Multiple lookup formats (by name, abbr, FIPS, lowercase)
  • Pre-built record dictionaries and choice lists for UI/display

These constants are used to standardize geodata processing and support consistent reporting.

get_state_dir_name

get_state_dir_name(state_abbr: str) -> str

Return the standardized directory name for a state (full lowercase name with underscores).

Source code in src/civic_lib_geo/us_constants.py
275
276
277
278
def get_state_dir_name(state_abbr: str) -> str:
    """Return the standardized directory name for a state (full lowercase name with underscores)."""
    name = US_STATE_ABBR_TO_NAME[state_abbr]
    return name.lower().replace(" ", "_")

get_state_record_by_abbr

get_state_record_by_abbr(abbr: str) -> dict | None

Return state record by 2-letter abbreviation (e.g., 'MN').

Source code in src/civic_lib_geo/us_constants.py
240
241
242
def get_state_record_by_abbr(abbr: str) -> dict | None:
    """Return state record by 2-letter abbreviation (e.g., 'MN')."""
    return US_STATE_RECORDS_BY_ABBR.get(abbr.upper())

get_state_record_by_any

get_state_record_by_any(value: str) -> dict | None

Return state record by abbr, name, or FIPS code (case-insensitive).

Source code in src/civic_lib_geo/us_constants.py
255
256
257
258
259
260
261
262
def get_state_record_by_any(value: str) -> dict | None:
    """Return state record by abbr, name, or FIPS code (case-insensitive)."""
    value = value.strip().lower()
    return (
        US_STATE_RECORDS_BY_ABBR_LOWER.get(value)
        or US_STATE_RECORDS_BY_NAME_LOWER.get(value)
        or US_STATE_RECORDS_BY_FIPS_LOWER.get(value)
    )

get_state_record_by_fips

get_state_record_by_fips(fips: str) -> dict | None

Return state record by FIPS code (e.g., '27').

Source code in src/civic_lib_geo/us_constants.py
250
251
252
def get_state_record_by_fips(fips: str) -> dict | None:
    """Return state record by FIPS code (e.g., '27')."""
    return US_STATE_RECORDS_BY_FIPS.get(fips)

get_state_record_by_name

get_state_record_by_name(name: str) -> dict | None

Return state record by full name (e.g., 'Minnesota').

Source code in src/civic_lib_geo/us_constants.py
245
246
247
def get_state_record_by_name(name: str) -> dict | None:
    """Return state record by full name (e.g., 'Minnesota')."""
    return US_STATE_RECORDS_BY_NAME.get(name)

list_state_choices

list_state_choices() -> list[tuple[str, str]]

Return list of (abbr, name) tuples for all states (for dropdowns/UI).

Source code in src/civic_lib_geo/us_constants.py
265
266
267
def list_state_choices() -> list[tuple[str, str]]:
    """Return list of (abbr, name) tuples for all states (for dropdowns/UI)."""
    return US_STATE_CHOICES

list_state_choices_by_fips

list_state_choices_by_fips() -> list[tuple[str, str]]

Return list of (FIPS, name) tuples for all states (for dropdowns/UI).

Source code in src/civic_lib_geo/us_constants.py
270
271
272
def list_state_choices_by_fips() -> list[tuple[str, str]]:
    """Return list of (FIPS, name) tuples for all states (for dropdowns/UI)."""
    return US_STATE_CHOICES_BY_FIPS