Geo
Geospatial: the gravitational center. Boundaries, geocoding, spatial transforms, Census data intelligence, isochrones, redistricting analysis, CRS management, GEOID utilities, interpolation, gazetteers, place history, overlays.
Geographic utilities for spatial data analysis, Census data access, and mapping.
All submodules load on first attribute access via PEP 562 __getattr__. Pure-Python modules (census_constants, geoid_utils) are importable without geopandas.
- siege_utilities.geo.geo_capabilities()[source]
Detect available spatial packages and report the active tier.
Returns a dict with boolean flags for each optional package and a
tierstring ("geodjango","geo","geo-lite", or"none").>>> caps = geo_capabilities() >>> caps["tier"] in ("geodjango", "geo", "geo-lite", "none") True >>> isinstance(caps["shapely"], bool) True
- siege_utilities.geo.get_default_crs()[source]
Return the current default CRS string (e.g.
"EPSG:4326").- Return type:
- siege_utilities.geo.set_default_crs(crs)[source]
Set the default CRS used by all spatial-returning functions.
- Parameters:
crs (str) – Any CRS string accepted by
pyproj(e.g."EPSG:4326","EPSG:3857","ESRI:102003").- Raises:
pyproj.exceptions.CRSError – If crs is not recognized by pyproj.
- Return type:
None
- siege_utilities.geo.reproject_if_needed(gdf, crs=None)[source]
Reproject gdf to crs if it differs from the GeoDataFrame’s current CRS.
If crs is
None, usesget_default_crs(). Returns the GeoDataFrame unchanged if it is already in crs or is empty.When the GeoDataFrame has no CRS (
gdf.crs is None) and a target is specified, the target CRS is set (not reprojected) on the assumption that the data is already in the target CRS. A warning is logged because this assumption may be wrong.- Parameters:
crs (str | None)
- siege_utilities.geo.detect_crs(geom_or_ds)[source]
Resolve the source CRS of a spatial object.
- Parameters:
geom_or_ds (Any) –
One of:
A GeoDataFrame — reads
.crsand returns anEPSG:<n>string when authoritative, otherwise the WKT.A GeoJSON-like
dict— reads["crs"]["properties"]["name"]if present.A fiona
Collection— reads.crs/.crs_wkt.A bare shapely geometry — bare geometries carry no CRS metadata; this returns
None(callers should treat this as “consultget_default_crs()”).None— returnsNone.
- Returns:
An EPSG string (
"EPSG:4326"), a WKT string, orNone.- Return type:
str | None
Mirrors the SRS-detection branch of the salvage source (
ImportServerRepo.py:250-253), which read the spatial reference from an OGR DataSource viaGetGeometryRef().GetSpatialReference(). This implementation reads.crs/.crs_wktfrom GeoDataFrames or fiona collections — same intent, declared-deps surface.
- siege_utilities.geo.distance_to_decimal_degrees(distance_meters, latitude)[source]
Convert a distance in meters to approximate decimal degrees.
Uses the WGS-84 ellipsoid approximation. The result is the longitudinal span at the given latitude (degrees of longitude shrink toward the poles).
- Parameters:
- Returns:
Approximate equivalent distance in decimal degrees of longitude at latitude.
- Raises:
ValueError – If latitude is outside [-90, 90].
- Return type:
- siege_utilities.geo.reproject_geom(geom, src_crs, dst_crs=4326, axis_order='trad_gis')[source]
Reproject a shapely geometry with explicit axis-order control.
- Parameters:
geom – A shapely geometry (
Point,LineString,Polygon, etc.) orNone.src_crs (Any) – The geometry’s source CRS, in any form
pyproj.CRS.from_user_inputaccepts ("EPSG:4326", an integer EPSG code, a WKT string, apyproj.CRSobject).dst_crs (Any) – Target CRS, in any form
pyproj.CRS.from_user_inputaccepts. Defaults to4326(WGS 84).axis_order (str) –
One of:
"trad_gis"(default) —pyprojalways_xy=True. Coordinates are interpreted and emitted as (lon, lat), matching GeoJSON, Shapefile, KML, and most consumer expectations. This is the modern-pyproj equivalent ofosgeo.osr.OAMS_TRADITIONAL_GIS_ORDERused in the salvage source (ImportServerRepo.py:267-268)."auth_compliant"—always_xy=False. Coordinates follow the CRS authority’s declared axis order (lat-first for EPSG:4326). Use only if you know your input data is lat-first and your consumer expects lat-first.
- Returns:
A new shapely geometry in the target CRS, or
NoneifgeomisNone.- Raises:
ValueError – If
axis_orderis not one of the two accepted tokens, or ifsrc_crsisNone(a CRS-less geometry cannot be reprojected without an explicit source).ImportError – If
pyprojorshapelyare not installed.
Round-trip note: 4326 -> 3857 -> 4326 on a Point should return the same coordinates within 1e-6 degrees. The test suite verifies this.
- class siege_utilities.geo.BoundaryFetchResult[source]
Bases:
objectStructured result from a boundary retrieval attempt.
- geodataframe
The resulting GeoDataFrame (None on failure).
- Type:
Any
- error_stage
Pipeline stage where failure occurred (None on success). One of: input_validation, discovery, url_validation, download, parse.
- Type:
str | None
- context
Diagnostic details (attempted URLs, year fallback, HTTP status, etc.).
- Type:
Dict[str, Any]
- raise_on_error()[source]
Raise the appropriate typed exception if this result is a failure.
Returns self on success so callers can chain:
gdf = fetch_geographic_boundaries(...).raise_on_error().geodataframe
- Return type:
- classmethod ok(gdf, message='', context=None)[source]
Construct a success result.
- Parameters:
- Return type:
- exception siege_utilities.geo.BoundaryRetrievalError[source]
Bases:
SiegeGeoErrorBase exception for all boundary retrieval failures.
Inherits from
SiegeGeoErrorso callers can catch the entire siege_utilities exception family with a singleexcept SiegeError:. Previously this stood alone outside the documented hierarchy and slipped pastexcept SiegeErrorblocks.
- exception siege_utilities.geo.BoundaryInputError[source]
Bases:
BoundaryRetrievalErrorInvalid input parameters (state FIPS, year, geographic level).
- exception siege_utilities.geo.BoundaryDiscoveryError[source]
Bases:
BoundaryRetrievalErrorFailed to discover available boundary types or construct a URL.
- exception siege_utilities.geo.BoundaryConfigurationError[source]
Bases:
BoundaryRetrievalErrorBoundary type requires parameters that were not provided (e.g., state FIPS, congress number).
- exception siege_utilities.geo.BoundaryUrlValidationError[source]
Bases:
BoundaryRetrievalErrorConstructed URL is not accessible (HTTP error, timeout, etc.).
- exception siege_utilities.geo.BoundaryDownloadError[source]
Bases:
BoundaryRetrievalErrorDownload succeeded but the file is corrupt or not a valid zip.
- exception siege_utilities.geo.BoundaryParseError[source]
Bases:
BoundaryRetrievalErrorDownloaded data could not be parsed as a shapefile/GeoDataFrame.
- class siege_utilities.geo.BoundaryProvider[source]
Bases:
ABCAbstract base class for geographic boundary data providers.
- abstractmethod get_boundary(level, identifier=None, **kwargs)[source]
Fetch boundary geometry for a given geographic level.
- Parameters:
level (str) – Geographic level (e.g. ‘county’, ‘tract’, ‘admin1’).
identifier (str | None) – Optional identifier to narrow the query (e.g. state FIPS).
**kwargs (Any) –
Provider-specific options. Recognized cross-provider kwargs: engine: Optional DataFrameEngine instance. When provided,
the result is converted to the engine’s native format via
engine.from_geodataframe(). Default (None) returns a GeoDataFrame.
- Returns:
GeoDataFrame when engine is None; engine-native DataFrame otherwise.
- Return type:
- class siege_utilities.geo.CensusTIGERProvider[source]
Bases:
BoundaryProviderUS Census TIGER/Line boundary provider.
Wraps
siege_utilities.geo.spatial_data.CensusDataSourceandsiege_utilities.config.census_constants.CANONICAL_GEOGRAPHIC_LEVELS.- get_boundary(level, identifier=None, **kwargs)[source]
Fetch US Census TIGER boundaries.
- Parameters:
level (str) – A canonical geographic level (e.g. ‘county’, ‘tract’, ‘cd’).
identifier (str | None) – State FIPS code when the level requires it.
**kwargs (Any) – Forwarded to
CensusDataSource.fetch_geographic_boundaries(e.g.year,congress_number).engine: Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
BoundaryFetchError – When the boundary retrieval fails.
- is_available()[source]
Census TIGER provider is always available (pure-HTTP downloads).
- Return type:
- class siege_utilities.geo.GADMProvider[source]
Bases:
BoundaryProviderGADM (Global Administrative Areas) boundary provider.
Downloads GeoJSON boundary files from the GADM project for non-US countries. Requires geopandas at runtime.
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch GADM boundaries for a country.
- Parameters:
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
ValueError – If level is unknown or country_code is missing.
ImportError – If geopandas is not installed.
- class siege_utilities.geo.RDHProvider[source]
Bases:
BoundaryProviderRedistricting Data Hub boundary provider.
Wraps
siege_utilities.geo.providers.redistricting_data_hub.RDHClientto expose precinct / VTD boundaries — and enacted legislative plans — through the standardBoundaryProviderinterface.RDH requires a “designated API user” account. Contact info@redistrictingdatahub.org to request access. Pass credentials either via constructor arguments or environment variables
RDH_USERNAME/RDH_PASSWORD.Supported levels
'precinct'— precinct/VTD boundaries with election results'congress'— enacted congressional district plans'state_senate'— enacted upper-chamber plans'state_house'— enacted lower-chamber plans
Usage:
import os from siege_utilities.geo.providers.boundary_providers import RDHProvider # Prefer env vars (RDH_USERNAME / RDH_PASSWORD); never hardcode provider = RDHProvider( username=os.environ["RDH_USERNAME"], password=os.environ["RDH_PASSWORD"], ) gdf = provider.get_boundary('precinct', identifier='TX')
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch RDH boundary data for a state.
- Parameters:
level (str) – One of
'precinct','congress','state_senate','state_house'.identifier (str | None) – Two-letter state abbreviation (e.g.
'TX'). Can also be passed asstatekwarg.**kwargs (Any) –
state— alias for identifier.year— filter datasets by year string (e.g.'2022').format—'shp'(default) or'csv'.engine— Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided. None if no matching datasets found.
- Raises:
ValueError – If level is not recognised or state is missing.
ImportError – If geopandas is not installed.
- exception siege_utilities.geo.BoundaryFetchError[source]
Bases:
RuntimeErrorRaised when a boundary provider cannot satisfy a request after retries.
- siege_utilities.geo.resolve_boundary_provider(country='US', **kwargs)[source]
Return an appropriate
BoundaryProviderfor the given country.- Parameters:
country (str) – ISO-2 or ISO-3 country code (default
'US').**kwargs (Any) – Forwarded to
GADMProviderconstructor for non-US countries. Ignored for US (CensusTIGERProvider takes no options).
- Returns:
CensusTIGERProvider for US / US territories, GADMProvider otherwise.
- Return type:
- class siege_utilities.geo.CensusDirectoryDiscovery[source]
Bases:
objectDiscovers available Census TIGER/Line data dynamically.
- get_available_years(force_refresh=False)[source]
Get list of available Census years with retry and exponential backoff.
- get_year_directory_contents(year, force_refresh=False, on_error='skip')[source]
Get contents of a specific TIGER year directory.
- Parameters:
- Return type:
- get_year_specific_url_patterns(year)[source]
Get year-specific URL patterns and directory structures.
- construct_download_url(year, boundary_type, state_fips=None, congress_number=None)[source]
Construct download URL using FIPS validation and year-specific patterns.
- Raises:
BoundaryInputError – If state_fips is invalid.
BoundaryDiscoveryError – If boundary_type is not available or URL cannot be constructed.
- Parameters:
- Return type:
- validate_download_url(url)[source]
Check if a download URL is actually accessible.
Uses GET with stream=True instead of HEAD because CDNs like Cloudflare often block or throttle HEAD requests from non-browser User-Agents.
- Raises:
BoundaryUrlValidationError – If the URL is not accessible.
- Parameters:
url (str)
- Return type:
- class siege_utilities.geo.CensusDataSource[source]
Bases:
SpatialDataSourceCensus TIGER/Line boundary data source with dynamic year discovery.
Supports all standard TIGER/Line boundary types:
- National-scope (no state_fips required):
nation,state,county,place,zcta,cd- State-scope (state_fips required):
tract,block_group,block,tabblock20,sldu,sldl
GEOID formats by boundary type:
Boundary
Digits
Components
state
2
SS
county
5
SS + CCC
tract
11
SS + CCC + TTTTTT
block_group
12
SS + CCC + TTTTTT + G
tabblock20
15
SS + CCC + TTTTTT + BBBB
cd
4
SS + DD
sldu
5
SS + DDD
sldl
5
SS + DDD
- discover_boundary_types(year)[source]
Discover available boundary types for a year.
Returns a mapping of boundary-type slug (
'county','tract', …) to the canonical TIGER directory name ('COUNTY','TRACT', …). Matches the return contract ofTigerDiscovery.discover_boundary_types(). The previousList[str]annotation was incorrect – the underlying call has always returned the dict.
- get_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, county_fips=None, congress_number=None)[source]
Download geographic boundaries from Census TIGER/Line files with dynamic discovery.
This method preserves the legacy
Optional[GeoDataFrame]return type. For structured diagnostics, usefetch_geographic_boundaries()instead.- Parameters:
year (int) – Census year (will find closest available if not available)
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code for filtering (required for some levels)
county_fips (str | None) – County FIPS code for filtering
congress_number (int | None) – Congress number for congressional districts
- Returns:
GeoDataFrame with boundaries or None if failed
- Return type:
geopandas.GeoDataFrame | None
- fetch_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, county_fips=None, congress_number=None)[source]
Download geographic boundaries with structured diagnostics.
Returns a
BoundaryFetchResultthat carries either the GeoDataFrame on success or a machine-readable error code, stage, and context dict on failure.- Parameters:
year (int) – Census year (will find closest available if not available)
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code for filtering (required for some levels)
county_fips (str | None) – County FIPS code for filtering
congress_number (int | None) – Congress number for congressional districts
- Returns:
BoundaryFetchResult with .success, .geodataframe, .error_stage, etc.
- Return type:
- normalize_state_identifier(state_input)[source]
Convert any state identifier (FIPS, abbreviation, name) to FIPS code.
Deprecated since version 3.16.0: Use the module-level
normalize_state_identifier()directly.- Raises:
ValueError – If the input cannot be resolved to a valid state FIPS.
- Return type:
- get_comprehensive_state_info()[source]
Get comprehensive state information including FIPS, name, and abbreviation.
- class siege_utilities.geo.SpatialDataSource[source]
Bases:
objectBase class for spatial data sources.
- download_data(**kwargs)[source]
Download data from the source.
- Return type:
geopandas.GeoDataFrame | None
- class siege_utilities.geo.GovernmentDataSource[source]
Bases:
SpatialDataSourceGovernment data portal spatial data source.
- download_dataset(dataset_id, format_type='geojson')[source]
Download a dataset from the government data portal.
- Parameters:
- Returns:
GeoDataFrame with spatial data.
- Raises:
SpatialDataError – On any download or processing failure.
- Return type:
- class siege_utilities.geo.OpenStreetMapDataSource[source]
Bases:
SpatialDataSourceOpenStreetMap data source using Overpass API.
- download_osm_data(query, bbox=None)[source]
Download data from OpenStreetMap using Overpass QL.
- Parameters:
- Returns:
GeoDataFrame with OSM data.
- Raises:
SpatialDataError – On HTTP or processing failure.
- Return type:
- siege_utilities.geo.get_census_boundaries(year=2026, geographic_level='county', state_fips=None, state_identifier=None)[source]
Convenience function to get Census boundaries.
Deprecated since version 3.16.0: Use
fetch_geographic_boundaries()(via CensusDataSource) for structured diagnostics viaBoundaryFetchResult.get_census_boundarieswill be removed in v3.17.0.- Parameters:
- Return type:
geopandas.GeoDataFrame | None
Note
Provide either state_fips OR state_identifier, not both. If both are provided, state_identifier takes precedence.
- Returns:
GeoDataFrame with Census boundaries (filtered by state if specified)
- Parameters:
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.download_data(year, geographic_level, state_fips=None, *, crs=None)[source]
Download Census data.
This is a convenience wrapper around get_geographic_boundaries().
- Parameters:
year (int) – Census year
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code (required for tract, block_group, etc.)
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
GeoDataFrame with boundaries in crs, or None if failed.
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.normalize_state_identifier(state_input)[source]
Normalize a state identifier to its 2-digit zero-padded FIPS code.
- Parameters:
state_input (str) – Any of: 2-digit FIPS code (
"48"), 2-letter abbreviation ("TX"), or full state name ("Texas"). Case-insensitive.- Returns:
Zero-padded 2-digit FIPS code (e.g.,
"06"for California).- Return type:
- Raises:
ValueError – If state_input doesn’t match any known state, territory, or DC.
Examples
>>> normalize_state_identifier("TX") '48' >>> normalize_state_identifier("48") '48' >>> normalize_state_identifier("Texas") '48' >>> normalize_state_identifier("DC") '11'
- siege_utilities.geo.get_state_by_abbreviation(abbreviation)[source]
Get state info by abbreviation.
- siege_utilities.geo.discover_boundary_types(year)[source]
Discover available boundary types for a year.
Returns the same
{boundary_type: tiger_dir}mapping asCensusDataSource.discover_boundary_types(). The historicalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.get_census_data(api_key=None)[source]
Get Census data source instance.
- Parameters:
api_key (str | None)
- Return type:
- siege_utilities.geo.download_osm_data(query, bbox=None, *, crs=None)[source]
Convenience function to download OSM data.
- Parameters:
query (str) – OSM query string.
bbox (List[float] | None) – Optional bounding box
[west, south, east, north].crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.get_year_directory_contents(year)[source]
Get directory contents for a specific year.
- siege_utilities.geo.construct_download_url(year, geographic_level, state_fips=None)[source]
Construct download URL for Census data.
- siege_utilities.geo.get_optimal_year(geographic_level, preferred_year=None)[source]
Get optimal year for geographic level.
- siege_utilities.geo.get_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, state_identifier=None, *, crs=None)[source]
Get geographic boundaries (legacy, returns None on failure).
Deprecated since version 3.16.0: Use
fetch_geographic_boundaries()for structured diagnostics.get_geographic_boundarieswill be removed in v3.17.0.- Parameters:
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.fetch_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, congress_number=None, *, crs=None)[source]
Get geographic boundaries with structured diagnostics.
Returns a
BoundaryFetchResultinstead ofOptional[GeoDataFrame].- Parameters:
year (int) – Census year
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code (required for tract, block_group, etc.)
congress_number (int | None) – Congress number for congressional districts
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
BoundaryFetchResult with .success, .geodataframe in crs, .error_stage, etc.
- Return type:
- siege_utilities.geo.get_available_boundary_types(year)[source]
Get available boundary types for a year.
Returns
{boundary_type: tiger_dir}– same shape asCensusDataSource.get_available_boundary_types(). The historicalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.refresh_discovery_cache()[source]
Refresh the discovery cache.
- Return type:
None
- siege_utilities.geo.get_available_state_fips()[source]
Get available state FIPS codes as a
{fips_code: state_name}mapping.Same shape as
CensusDataSource.get_available_state_fips(). HistoricalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.get_state_abbreviations()[source]
Get a
{fips_code: state_abbreviation}mapping.Same shape as
CensusDataSource.get_state_abbreviations(). HistoricalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.download_dataset(year, geographic_level, state_fips=None, *, crs=None)[source]
Download Census dataset.
- Parameters:
year (int) – Census year.
geographic_level (str) – Geographic level.
state_fips (str | None) – State FIPS code.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.get_unified_fips_data()[source]
Get unified FIPS data with state names and abbreviations.
- siege_utilities.geo.normalize_state_identifier_standalone(identifier)[source]
Normalize a state identifier to its 2-digit FIPS code.
Delegates to
siege_utilities.config.census_registry.normalize_state_identifier(), the canonical implementation.- Parameters:
identifier (str) – Any of: 2-digit FIPS code, 2-letter abbreviation, or full state name.
- Returns:
Zero-padded 2-digit FIPS code.
- Return type:
- Raises:
ValueError – If identifier doesn’t match any known state, territory, or DC.
- siege_utilities.geo.normalize_state_input(raw_input)[source]
Normalize state input to handle formatting and trivial errors.
- Parameters:
raw_input (str) – Raw state input (name, abbreviation, or FIPS)
- Returns:
Normalized input (uppercase, trimmed, FIPS padded)
- Return type:
Examples
normalize_state_input(” ca “) -> “CA” normalize_state_input(“CAlifornia”) -> “CALIFORNIA” normalize_state_input(“6”) -> “06”
- siege_utilities.geo.normalize_state_name(name)[source]
Normalize state name input (e.g., “CAlifornia” -> “CALIFORNIA”).
- siege_utilities.geo.normalize_state_abbreviation(abbrev)[source]
Normalize state abbreviation input (e.g., “ ca “ -> “CA”).
- siege_utilities.geo.normalize_fips_code(fips)[source]
Normalize FIPS code input (e.g., 6 -> “06”, “6” -> “06”).
- class siege_utilities.geo.CensusDatasetMapper[source]
Bases:
objectMaps relationships between Census datasets and provides intelligent data selection.
This class helps users understand: - Which datasets are available for different geography levels - How different survey types relate to each other - When to use different datasets based on analysis needs - Data quality and reliability considerations
- get_dataset_info(dataset_name)[source]
Get information about a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
CensusDataset | None
- list_datasets_by_type(survey_type)[source]
List all datasets of a specific survey type.
- Parameters:
survey_type (SurveyType)
- Return type:
- list_datasets_by_geography(geography_level)[source]
List all datasets available for a specific geography level.
- Parameters:
geography_level (GeographyLevel)
- Return type:
- get_best_dataset_for_use_case(use_case, geography_level, time_period=None)[source]
Get the best dataset(s) for a specific use case.
- Parameters:
use_case (str) – Description of what you’re trying to analyze
geography_level (GeographyLevel) – Required geography level
time_period (str | None) – Specific time period (optional)
- Returns:
List of recommended datasets, ranked by suitability
- Return type:
- get_dataset_relationships(dataset_name)[source]
Get all relationships for a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
- get_data_selection_guide(analysis_type, geography_level, time_sensitivity='medium')[source]
Get a comprehensive guide for selecting Census data.
- Parameters:
analysis_type (str) – Type of analysis (e.g., “demographics”, “business”, “housing”)
geography_level (GeographyLevel) – Required geography level
time_sensitivity (str) – How time-sensitive the analysis is (“low”, “medium”, “high”)
- Returns:
Dictionary with data selection recommendations
- Return type:
- class siege_utilities.geo.SurveyType[source]
Bases:
EnumEnumeration of Census survey types.
- DECENNIAL = 'decennial'
- ACS_1YR = 'acs_1yr'
- ACS_3YR = 'acs_3yr'
- ACS_5YR = 'acs_5yr'
- CENSUS_BUSINESS = 'census_business'
- POPULATION_ESTIMATES = 'population_estimates'
- HOUSING_ESTIMATES = 'housing_estimates'
- class siege_utilities.geo.GeographyLevel[source]
Bases:
EnumEnumeration of Census geography levels (canonical names).
- NATION = 'nation'
- REGION = 'region'
- DIVISION = 'division'
- STATE = 'state'
- COUNTY = 'county'
- COUSUB = 'cousub'
- PLACE = 'place'
- CD = 'cd'
- SLDU = 'sldu'
- SLDL = 'sldl'
- TRACT = 'tract'
- BLOCK_GROUP = 'block_group'
- BLOCK = 'block'
- ZCTA = 'zcta'
- CBSA = 'cbsa'
- PUMA = 'puma'
- VTD = 'vtd'
- VTD20 = 'vtd20'
- TABBLOCK20 = 'tabblock20'
- TABBLOCK10 = 'tabblock10'
- COUNTY_SUBDIVISION = 'cousub'
- CONGRESSIONAL_DISTRICT = 'cd'
- STATE_LEGISLATIVE_DISTRICT = 'sldu'
- ZIP_CODE = 'zcta'
- VOTING_DISTRICT = 'vtd'
- class siege_utilities.geo.DataReliability[source]
Bases:
EnumEnumeration of data reliability levels.
- HIGH = 'high'
- MEDIUM = 'medium'
- LOW = 'low'
- ESTIMATED = 'estimated'
- class siege_utilities.geo.CensusDataset[source]
Bases:
objectRepresents a Census dataset with metadata.
- survey_type: SurveyType
- geography_levels: List[GeographyLevel]
- reliability: DataReliability
- __init__(dataset_id, name, survey_type, geography_levels, time_period, reliability, description, variables, limitations, best_for, alternatives, data_quality_notes, last_updated, next_update, api_endpoint=None, download_url=None)
- Parameters:
dataset_id (str)
name (str)
survey_type (SurveyType)
geography_levels (List[GeographyLevel])
time_period (str)
reliability (DataReliability)
description (str)
data_quality_notes (str)
last_updated (str)
next_update (str)
api_endpoint (str | None)
download_url (str | None)
- Return type:
None
- class siege_utilities.geo.DatasetRelationship[source]
Bases:
objectRepresents a relationship between Census datasets.
- __init__(primary_dataset, related_dataset, relationship_type, description, when_to_use_primary, when_to_use_related, overlap_period=None, compatibility_score=1.0)
- siege_utilities.geo.get_census_dataset_mapper()[source]
Get a configured Census dataset mapper instance.
- Return type:
- siege_utilities.geo.get_best_dataset_for_analysis(analysis_type, geography_level, time_sensitivity='medium')[source]
Convenience function to get the best dataset for analysis.
- siege_utilities.geo.compare_census_datasets(dataset1_name, dataset2_name)[source]
Convenience function to compare two Census datasets.
- siege_utilities.geo.get_dataset_info(dataset_name)[source]
Convenience function to get information about a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
CensusDataset | None
- siege_utilities.geo.list_datasets_by_type(survey_type)[source]
Convenience function to list all datasets of a specific survey type.
- Parameters:
survey_type (str)
- Return type:
- siege_utilities.geo.list_datasets_by_geography(geography_level)[source]
Convenience function to list all datasets for a specific geography level.
- Parameters:
geography_level (str)
- Return type:
- siege_utilities.geo.get_best_dataset_for_use_case(use_case, geography_level='county', time_sensitivity='medium')[source]
Convenience function to get the best dataset for a specific use case.
- siege_utilities.geo.get_dataset_relationships(dataset_name)[source]
Convenience function to get relationships for a dataset.
- Parameters:
dataset_name (str)
- Return type:
- siege_utilities.geo.compare_datasets(dataset1_name, dataset2_name)[source]
Convenience function to compare two datasets.
- siege_utilities.geo.get_data_selection_guide(analysis_type, geography_level, time_sensitivity='medium')[source]
Convenience function to get data selection guide.
- siege_utilities.geo.export_dataset_catalog(filepath)[source]
Convenience function to export the dataset catalog.
- Parameters:
filepath (str)
- class siege_utilities.geo.CensusDataSelector[source]
Bases:
objectIntelligent selector for Census data based on analysis requirements.
This class automatically determines the best Census datasets to use for different types of analysis, considering factors like: - Geography level requirements - Time sensitivity - Data reliability needs - Variable requirements - Analysis purpose
- select_datasets_for_analysis(analysis_type, geography_level, time_period=None, variables=None, reliability_requirement=None)[source]
Select the best Census datasets for a specific analysis.
- Parameters:
analysis_type (str) – Type of analysis (e.g., “demographics”, “housing”, “business”)
geography_level (str | GeographyLevel) – Required geography level
time_period (str | None) – Specific time period (optional)
variables (List[str] | None) – Required variables (optional)
reliability_requirement (str | None) – Reliability requirement (“low”, “medium”, “high”)
- Returns:
Dictionary with dataset recommendations and rationale. Suitability scores use 0-5 scale: 0-2.0 (poor), 2.1-3.0 (moderate), 3.1-4.0 (good), 4.1-5.0 (excellent match)
- Return type:
- get_dataset_compatibility_matrix(analysis_types=None)[source]
Generate a compatibility matrix showing which datasets work best for different analysis types.
- siege_utilities.geo.get_census_data_selector()[source]
Get a configured Census data selector instance.
- Return type:
- siege_utilities.geo.select_census_datasets(analysis_type, geography_level, time_period=None, variables=None)[source]
Convenience function to select Census datasets for analysis.
- siege_utilities.geo.select_datasets_for_analysis(analysis_type, geography_level, time_period=None, variables=None)[source]
Standalone function to select datasets for analysis.
- Parameters:
- Returns:
Dataset recommendations and rationale
- Return type:
- siege_utilities.geo.get_dataset_compatibility_matrix(analysis_types=None)[source]
Standalone function to get dataset compatibility matrix.
- siege_utilities.geo.get_analysis_approach(analysis_type, geography_level, time_constraints=None)[source]
Convenience function to get analysis approach recommendations.
- siege_utilities.geo.suggest_analysis_approach(analysis_type, geography_level, time_constraints=None)[source]
Standalone function to suggest analysis approach.
- class siege_utilities.geo.SpatialDataTransformer[source]
Bases:
objectTransform spatial data between different formats and coordinate systems.
- convert_format(gdf, output_format, **kwargs)[source]
Convert GeoDataFrame to different format.
- Parameters:
gdf (None) – Input GeoDataFrame
output_format (str) – Desired output format
**kwargs – Additional format-specific parameters
- Raises:
ValueError – If output_format is unsupported
ImportError – If required dependency is not available
OSError – If file write fails
- Return type:
None
- class siege_utilities.geo.SWMapsArchive[source]
Bases:
objectContext-manager handle to an opened SWMaps archive or raw SQLite file.
Use
open_swmaps()to construct. Callclose()(or use as awithblock) to clean up any extracted-archive temp directory.
- siege_utilities.geo.open_swmaps(path)[source]
Open a SWMaps archive (
.swmz/.zip) or raw SQLite file.- Parameters:
path (str) – Path to a
.swmzarchive, a.ziparchive containing a.sqlitemember, or a raw.sqlite/.dbfile.- Returns:
An
SWMapsArchivehandle. Use it as a context manager so any extracted temp directory is cleaned up on exit.- Raises:
FileNotFoundError – If
pathdoes not exist.ValueError – If an archive contains no SQLite member, or if the file extension is not one of the recognized forms.
- Return type:
- siege_utilities.geo.read_features(archive, mapper=None)[source]
Yield per-feature dicts from a SWMaps archive.
Each yielded dict has:
feature_id— SWMaps UUID.feature_type— fromfeature_layers.name.feature_group— fromfeature_layers.group_name.layer_id— fromfeature_layers.uuid.feature_name— fromfeatures.name.geometry_wkt— POINT (single coordinate) or LINESTRING (two or more coordinates), built viashapely. Z dimension included when every coordinate row carries a non-nullelv; dropped otherwise.attributes—{field_name: value}mapping pulled fromattribute_valuesjoined toattribute_fields. Keys can be renamed by passingmapper.
- Parameters:
archive (SWMapsArchive) – An
SWMapsArchiveopened viaopen_swmaps().mapper (dict[str, str] | None) – Optional
{source_name: consumer_name}rename map. WhenNone, attribute keys are the verbatim SWMaps field names. Unknown source keys are logged and skipped (kept verbatim).
- Yields:
Dicts as described above. Empty-features (zero coordinate rows) are logged at WARNING and skipped.
- Raises:
ImportError – If
shapelyis not installed.- Return type:
The geometry rule matches the salvage source (
DataUtils.py:749-768) but usesshapely.geometry.Point/shapely.geometry.LineStringwith.wktrather than manual string concatenation.
- siege_utilities.geo.find_vector_dataset_file_in_directory(directory, extensions=None)[source]
Find a vector dataset file in a directory.
Searches directory recursively for files matching common geospatial vector formats. Returns the first match found.
- siege_utilities.geo.concatenate_addresses(street=None, city=None, state_province_area=None, postal_code=None, country=None)[source]
Concatenate address components into a single string suitable for geocoding. Returns a properly formatted address string.
- siege_utilities.geo.use_nominatim_geocoder(query_address, id=None, country_codes=None, max_retries=3, server_url=None)[source]
Geocode an address using Nominatim with proper rate limiting and error handling. Returns the result as a JSON string for Spark UDF compatibility.
- Parameters:
query_address – The address to geocode
id – An identifier for tracking
country_codes – Optional country code filter (defaults to US)
max_retries – Number of retry attempts for transient errors
server_url – Optional custom Nominatim server URL (e.g., “http://nominatim.nominatim.svc.cluster.local” for self-hosted). Defaults to None (public OSM Nominatim with rate limiting). When set, rate limiting is skipped (self-hosted servers don’t require it).
- Returns:
JSON string of the geocoding result, or
Nonewhen Nominatim returned no match for the address (a legitimate non-error outcome).- Raises:
ValueError – If
query_addressis empty or None.GeocodingError – If geocoding failed due to a network, service, or parse error after retries (i.e., the geocoder could not even attempt to match). Wraps the underlying geopy exception via
__cause__.
- class siege_utilities.geo.NominatimGeoClassifier[source]
Bases:
objectA classifier for geocoding results using Nominatim. Provides methods to categorize and analyze geocoding results.
- get_place_ranks_by_label(label)[source]
Reverse lookup on
place_rank_dict: label → matching rank IDs.Nominatim returns an integer
place_rankper result (0=country … 10=building). This helper goes the other direction so callers can writefilter(rank=cls.get_place_ranks_by_label("City"))without hard-coding the integer.Returns
[](notNone) when the label is unknown so the caller canin/ iterate without a None-check.
- get_importance_threshold_by_label(label)[source]
Reverse lookup on
importance_dict: label → importance threshold.Nominatim’s
importancefield is a float in [0, 1] that mixes Wikipedia hit-count, place type, and population. We bucket those floats into ordinal labels (Country/State/ …) and this method returns the float threshold that corresponds to a label — useful when filtering a Nominatim result set programmatically.Returns
Nonewhen no label matches; callers should treat that as “don’t filter on importance.”
- to_json()[source]
Serialize the classifier’s lookup tables to a JSON string.
Used to persist customised rank/importance overrides between runs or to ship a classifier configuration to a worker process. The result round-trips through
from_json().
- from_json(json_string)[source]
Replace the classifier’s lookup tables from a JSON string.
- Parameters:
json_string – JSON produced by
to_json().
Inverse of
to_json()— overwritesplace_rank_dictandimportance_dictin place. Unknown top-level keys are ignored; missing keys leave the corresponding table empty rather than raising, so a partial payload doesn’t poison the classifier.
- siege_utilities.geo.get_country_name(country_code)[source]
Get the full country name from a country code.
- Parameters:
country_code – Two-letter country code (e.g., ‘us’, ‘gb’, ‘ca’)
- Returns:
Full country name or the code if not found
- Return type:
- siege_utilities.geo.get_country_code(country_name)[source]
Get the country code from a country name.
- Parameters:
country_name – Full country name (e.g., ‘United States’, ‘Canada’)
- Returns:
Two-letter country code, or None if not found.
- Return type:
str | None
- siege_utilities.geo.list_countries()[source]
Get a list of all available countries with their codes.
- Returns:
Dictionary mapping country codes to country names
- Return type:
- siege_utilities.geo.get_coordinates(query_address, country_codes=None, max_retries=3, server_url=None)[source]
Get coordinates (latitude, longitude) for an address using Nominatim.
- Parameters:
query_address – The address to geocode
country_codes – Optional country code filter (defaults to US)
max_retries – Maximum number of retry attempts
server_url – Optional custom Nominatim server URL (e.g., “http://nominatim.nominatim.svc.cluster.local” for self-hosted). Defaults to None (public OSM Nominatim).
- Returns:
(latitude, longitude), or
Nonewhen Nominatim returned no match for the address (a legitimate non-error outcome).- Return type:
- Raises:
ValueError – If
query_addressis empty or None.GeocodingError – If geocoding failed due to a network, service, or parse error — i.e., the geocoder could not even attempt to match. Wraps the underlying exception via
__cause__.
- siege_utilities.geo.geocode_with_nominatim_public(address, country_codes=None)[source]
Geocode a single address using the public Nominatim service.
Thin wrapper around
get_coordinates()that forces the public endpoint (no customserver_url).- Parameters:
address – Address string to geocode.
country_codes – Optional country code filter (defaults to US).
- Returns:
(lat, lon)tuple, orNoneif no match found.- Raises:
ValueError – If address is empty.
GeocodingError – On network/service failure.
- siege_utilities.geo.geocode_addresses_with_nominatim(addresses, country_codes=None, max_retries=3, server_url=None)[source]
Batch-geocode a list of addresses using Nominatim.
Calls
get_coordinates()for each address sequentially with Nominatim’s built-in rate limiting (1 s for public, 50 ms for self-hosted).- Parameters:
addresses – Iterable of address strings.
country_codes – Optional country code filter (defaults to US).
max_retries – Retry attempts per address.
server_url – Optional custom Nominatim server URL.
- Returns:
List of
{"address": str, "lat": float, "lon": float}dicts. Addresses that returned no match havelatandlonset toNone. Addresses that raisedGeocodingErrorare logged and included withNonecoordinates.
- exception siege_utilities.geo.GeocodingError[source]
Bases:
RuntimeErrorRaised when Nominatim geocoding fails for reasons other than ‘no match’.
Covers network timeouts (after retries exhausted), service errors, parse errors, and other unexpected failures from the geocoder. Distinct from a legitimate “no result found” outcome, which still returns
None. Use__cause__to inspect the underlying exception.Mirrors
siege_utilities.geo.providers.census_geocoder.CensusGeocodeError.
- siege_utilities.geo.validate_geocode_data_pandas(df, lat_col, lon_col, crs=None)[source]
Filter rows with invalid geographic coordinates.
Pandas equivalent of
spark_utils.validate_geocode_data().- Parameters:
df (pandas.DataFrame) – Input DataFrame.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
crs – Optional CRS (pyproj CRS, EPSG int, or string). When supplied, the valid coordinate bounds are derived from the CRS area of use. Defaults to WGS84 (-90..90, -180..180).
- Returns:
DataFrame with only rows whose coordinates fall within the valid range.
- Return type:
- siege_utilities.geo.mark_valid_geocode_data_pandas(df, lat_col, lon_col, output_col='is_valid', crs=None)[source]
Add a boolean validity column without removing rows.
Pandas equivalent of
spark_utils.mark_valid_geocode_data().- Parameters:
df (pandas.DataFrame) – Input DataFrame.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
output_col (str) – Name of the boolean column to add.
crs – Optional CRS (pyproj CRS, EPSG int, or string). When supplied, the valid coordinate bounds are derived from the CRS area of use. Defaults to WGS84 (-90..90, -180..180).
- Returns:
Copy of df with output_col appended.
- Return type:
- exception siege_utilities.geo.IsochroneError[source]
Bases:
ExceptionBase exception for isochrone operations.
- exception siege_utilities.geo.IsochroneNetworkError[source]
Bases:
IsochroneErrorNetwork failure (timeout, connection refused, DNS resolution).
- exception siege_utilities.geo.IsochroneProviderError[source]
Bases:
IsochroneErrorProvider returned an HTTP error or an unparseable response.
- class siege_utilities.geo.IsochroneRequest[source]
Bases:
TypedDictStructured request definition returned by
build_isochrone_request().
- class siege_utilities.geo.IsochroneProvider[source]
Bases:
ABCAbstract base class for isochrone providers.
Subclasses must implement
fetch(),validate_config(), and theprovider_nameproperty.- abstractmethod fetch(lat, lon, time_minutes, profile='driving-car')[source]
Fetch an isochrone as a GeoJSON dict.
- Parameters:
- Returns:
A GeoJSON dict (typically a FeatureCollection).
- Return type:
- class siege_utilities.geo.OpenRouteServiceProvider[source]
Bases:
IsochroneProviderConcrete
IsochroneProviderwrapping the OpenRouteService API.- Parameters:
api_key – ORS API key. Required for the hosted service; optional for self-hosted instances.
base_url – Root URL of the ORS server. Defaults to
DEFAULT_ORS_BASE_URL.timeout_seconds – HTTP timeout in seconds (default 30).
max_retries – Maximum retry attempts for transient failures (default 3).
- validate_config()[source]
Check that the provider is configured.
For the hosted ORS service (default URL), an API key is required. For self-hosted instances, the base URL must be non-empty.
- Return type:
- class siege_utilities.geo.ValhallaProvider[source]
Bases:
IsochroneProviderConcrete
IsochroneProviderwrapping a Valhalla server.- Parameters:
base_url – Root URL of the Valhalla server. Defaults to
DEFAULT_VALHALLA_BASE_URL.timeout_seconds – HTTP timeout in seconds (default 30).
max_retries – Maximum retry attempts for transient failures (default 3).
- siege_utilities.geo.build_isochrone_request(latitude, longitude, travel_time_minutes, *, provider='openrouteservice', base_url=None, profile='driving-car', api_key=None, extra_params=None)[source]
Build a provider-specific HTTP request definition for an isochrone call.
This supports open-source providers and self-hosted/custom endpoints.
- Parameters:
latitude (float) – Center latitude (-90 to 90).
longitude (float) – Center longitude (-180 to 180).
travel_time_minutes (int) – Travel-time contour in minutes (must be > 0).
provider (str) –
"openrouteservice"(or"ors") or"valhalla".base_url (str | None) – Custom server root for self-hosted providers.
profile (str) – Travel profile (
"driving-car","cycling-regular","foot-walking", etc.).api_key (str | None) – Optional API key (commonly used for hosted ORS).
extra_params (Mapping[str, Any] | None) – Provider-specific extra request fields merged into the JSON body (Valhalla) or query params (ORS).
- Returns:
An
IsochroneRequestdict withprovider,method,url,headers,params, andjsonkeys.- Raises:
ValueError – If inputs are out of range or provider is unknown.
- Return type:
- siege_utilities.geo.get_isochrone(latitude, longitude, travel_time_minutes, *, provider='openrouteservice', base_url=None, profile='driving-car', api_key=None, timeout_seconds=30, extra_params=None, max_retries=3)[source]
Fetch an isochrone GeoJSON response.
- Parameters:
latitude (float) – Center latitude.
longitude (float) – Center longitude.
travel_time_minutes (int) – Travel-time contour in minutes.
provider (str) –
"openrouteservice"(default) or"valhalla".base_url (str | None) – Custom server root for self-hosted providers.
profile (str) – Travel profile (
"driving-car","cycling-regular","foot-walking", etc.).api_key (str | None) – Optional API key (commonly used for hosted ORS).
timeout_seconds (int) – HTTP timeout in seconds (default 30).
extra_params (Mapping[str, Any] | None) – Provider-specific extra request fields.
max_retries (int) – Maximum number of attempts for transient failures (default 3). Set to 1 to disable retries.
- Returns:
Provider response parsed as JSON (typically a GeoJSON FeatureCollection).
- Raises:
IsochroneNetworkError – On timeout, connection, or DNS failure.
IsochroneProviderError – On HTTP error or unparseable response.
ValueError – If inputs are out of range or provider is unknown.
- Return type:
- siege_utilities.geo.isochrone_to_geodataframe(isochrone_geojson, *, crs=None)[source]
Convert an isochrone GeoJSON object to a GeoDataFrame.
Requires
geopandas. Install withpip install siege_utilities[geo].GeoJSON is always ingested as EPSG:4326 (the GeoJSON spec mandates WGS 84). If crs differs from EPSG:4326 the result is reprojected to the requested CRS before returning.
- Parameters:
isochrone_geojson (Mapping[str, Any]) – A GeoJSON dict (typically a FeatureCollection returned by
get_isochrone()).crs (str | None) – Target coordinate reference system. Defaults to
get_default_crs()(initially"EPSG:4326"). Pass any string accepted bypyproj.
- Returns:
A GeoDataFrame with one row per feature in the requested crs.
- Raises:
ImportError – If geopandas is not installed.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.get_provider(name='ors', **kwargs)[source]
Factory function returning a configured
IsochroneProvider.- Parameters:
- Returns:
A configured provider instance.
- Raises:
ValueError – If name is not a recognised provider.
- Return type:
- exception siege_utilities.geo.CensusGeocodeError[source]
Bases:
RuntimeErrorRaised when the Census geocoder API call fails unexpectedly.
Distinct from “no match” results (which return a CensusGeocodeResult with matched=False). This exception indicates an API / network / parse failure where the geocoder could not even attempt to match. Use __cause__ to inspect the underlying exception.
- class siege_utilities.geo.CensusVintage[source]
-
Census geocoder benchmark/vintage pairs.
Each member’s value encodes
<benchmark>|<vintage>where both halves are the exact strings the Census Geocoder API expects. Use the.benchmarkand.vintageproperties to extract them.The benchmark names follow the
Public_AR_<label>pattern used byhttps://geocoding.geo.census.gov/geocoder/benchmarks. The vintage names follow the<label>_<benchmark-label>pattern returned byhttps://geocoding.geo.census.gov/geocoder/vintages?benchmark=….- CENSUS_2010 = 'Public_AR_Census2020|Census2010_Census2020'
- CENSUS_2020 = 'Public_AR_Census2020|Census2020_Census2020'
- CURRENT = 'Public_AR_Current|Current_Current'
- ACS_2022 = 'Public_AR_Current|ACS2022_Current'
- ACS_2023 = 'Public_AR_Current|ACS2023_Current'
- __new__(value)
- class siege_utilities.geo.CensusGeocodeResult[source]
Bases:
objectResult from Census Bureau geocoding.
- __init__(matched=False, input_address='', matched_address='', lat=None, lon=None, state_fips='', county_fips='', tract='', block='', match_type='No_Match', side='', tiger_line_id='', input_id='')
- siege_utilities.geo.select_vintage_for_cycle(year)[source]
Select the appropriate Census vintage for an FEC cycle year.
- Parameters:
year (int) – The FEC election cycle year.
- Returns:
CensusVintage matching the boundaries in effect for that cycle.
- Return type:
Examples
>>> select_vintage_for_cycle(2024) <CensusVintage.CURRENT: 'Public_AR_Current|Current_Current'> >>> select_vintage_for_cycle(2016) <CensusVintage.CENSUS_2020: 'Public_AR_Census2020|Census2020_Census2020'> >>> select_vintage_for_cycle(2008) <CensusVintage.CENSUS_2010: 'Public_AR_Census2020|Census2010_Census2020'>
- siege_utilities.geo.geocode_single(street, city, state, zipcode, vintage=CensusVintage.CURRENT)[source]
Geocode a single address via the Census Bureau API.
- Parameters:
street (str) – Street address (e.g., “1600 Pennsylvania Ave NW”).
city (str) – City name.
state (str) – State abbreviation or name.
zipcode (str) – ZIP code.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
CensusGeocodeResult with lat/lon and FIPS codes if matched.
- Return type:
- siege_utilities.geo.geocode_batch(addresses, vintage=CensusVintage.CURRENT)[source]
Geocode a batch of addresses via the Census Bureau batch API.
The Census batch API accepts up to 10,000 addresses per request. Each address dict should have keys: id, street, city, state, zipcode.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
List of CensusGeocodeResult, one per input address (order preserved).
- Raises:
ValueError – If batch exceeds 10,000 addresses.
- Return type:
- siege_utilities.geo.geocode_batch_chunked(addresses, vintage=CensusVintage.CURRENT, chunk_size=10000)[source]
Geocode addresses in chunks of up to chunk_size (default 10,000).
Convenience wrapper around geocode_batch() for larger datasets.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
chunk_size (int) – Max addresses per API call (max 10,000).
- Returns:
List of CensusGeocodeResult, one per input address.
- Return type:
- siege_utilities.geo.geocode_results_to_dataframe(results)[source]
Convert a list of CensusGeocodeResult to a pandas DataFrame.
Extracts all dataclass fields and computed GEOID properties into a flat DataFrame with standard column names. This eliminates the per-field list-comprehension boilerplate that every caller of
geocode_batch()otherwise has to write.- Parameters:
results (list[CensusGeocodeResult]) – List of
CensusGeocodeResultobjects, typically returned bygeocode_batch()orgeocode_batch_chunked().- Returns:
input_id,input_address,matched,match_type,matched_address,latitude,longitude,state_geoid,county_geoid,tract_geoid,block_geoid,block_group_geoid,state_fips,county_fips,tract,block,side,tiger_line_id. For unmatched rows, latitude/longitude areNoneand GEOID columns are empty strings.- Return type:
DataFrame with columns
- Raises:
TypeError – If results is not a list.
Example:
results = geocode_batch(addresses) df = geocode_results_to_dataframe(results) # df now has tract_geoid, block_geoid, lat/lon, etc.
- siege_utilities.geo.is_valid_state_fips(value)[source]
Validate a 2-digit state FIPS code against known codes.
- siege_utilities.geo.is_valid_county_fips(value)[source]
Validate a 5-digit county FIPS code (2-digit state + 3-digit county).
- siege_utilities.geo.is_valid_tract_geoid(value)[source]
Validate an 11-digit tract GEOID (2-digit state + 3-digit county + 6-digit tract).
- siege_utilities.geo.is_valid_block_group_geoid(value)[source]
Validate a 12-digit block group GEOID (state + county + tract + block group).
- siege_utilities.geo.resolve_geographic_level(level)[source]
Resolve any geographic level name variant to its canonical form.
Accepts long forms (congressional_district), short forms (cd), Census abbreviations (CD), and common aliases (zip_code → zcta).
- class siege_utilities.geo.LocaleCode[source]
Bases:
objectAn NCES 12-code locale classification result.
- classmethod from_code(code)[source]
Construct a
LocaleCodefrom an integer code (11-43).- Raises:
ValueError – If the code is not a valid NCES locale code.
- Parameters:
code (int)
- Return type:
- class siege_utilities.geo.LocaleType[source]
Bases:
IntEnumNCES major locale types.
- CITY = 1
- SUBURB = 2
- TOWN = 3
- RURAL = 4
- __new__(value)
- class siege_utilities.geo.NCESLocaleClassifier[source]
Bases:
objectClassify geographic points and polygons into NCES locale codes.
Uses Census TIGER spatial data (Urbanized Areas, Urban Clusters, Places) and population thresholds to implement the NCES urban-centric locale classification algorithm (12-code system).
The classifier can be initialized with pre-loaded GeoDataFrames (for testing or custom data) or constructed from Census downloads via the
from_census_yearandfrom_nces_boundariesfactory methods.- Parameters:
urbanized_areas – GeoDataFrame of UA polygons (pop >= 50,000). Must contain a
geometrycolumn and a population field.urban_clusters – GeoDataFrame of UC polygons (pop 2,500-49,999). Must contain a
geometrycolumn. May beNonefor 2020+ Census data where UCs were eliminated.principal_cities – GeoDataFrame of Place polygons that are designated principal cities of CBSAs.
place_populations – Dict mapping place identifiers to population counts. Used to classify City subtypes (Large/Midsize/Small).
ua_populations – Optional dict mapping UA identifiers to populations. Used to classify Suburb subtypes. If
None, reads from theurbanized_areasGeoDataFrame attributes.projection_crs – EPSG code for distance computations. Defaults to
settings.PROJECTION_CRS(2163, US National Atlas Equal Area). Override for Alaska (3338) or Hawaii (26963).
- __init__(urbanized_areas, urban_clusters, principal_cities, place_populations, ua_populations=None, projection_crs=None)[source]
- property locale_index: LocaleIndex | None
- classify_geoid(geoid)[source]
Fast-path locale lookup by Census GEOID.
Returns the precomputed locale code if the GEOID is in the index, or
Noneif no index is set or the GEOID is not found. Useclassify_geoid_or_point()for automatic fallback to spatial classification.- Parameters:
geoid (str)
- Return type:
LocaleCode | None
- classify_geoid_or_point(geoid, lon, lat)[source]
Classify by GEOID if available, falling back to spatial.
- Parameters:
- Return type:
- classify_point(lon, lat)[source]
Classify a single geographic point.
Implements the NCES decision tree: 1. Spatial join against Urbanized Areas 2. If in UA: check principal city → City or Suburb 3. If not in UA: check Urban Cluster → Town (by distance to UA) 4. If not in UA/UC → Rural (by distance to UA and UC)
- Parameters:
- Returns:
A
LocaleCodedescribing the point’s NCES classification.- Return type:
- classify_points(gdf, geometry_col='geometry')[source]
Bulk classify a GeoDataFrame of points.
Adds columns:
locale_code(int),locale_category(str),locale_subcategory(str),locale_label(str).
- classify_polygon(polygon, method='area_weighted')[source]
Classify a polygon by locale distribution.
- Parameters:
- Returns:
{"locale_code": 21, "locale_label": "..."}Ifmethod="distribution"or"area_weighted": dict of{code: fraction}pairs summing to ~1.0.- Return type:
If
method="majority"- Raises:
ValueError – If method is not one of the valid options.
- classify_polygons(gdf, method='majority', geometry_col='geometry')[source]
Bulk classify a GeoDataFrame of polygons.
For
method="majority", adds columns:locale_code(int),locale_label(str).For
method="distribution"or"area_weighted", adds alocale_distributioncolumn containing per-row dicts of{code: fraction}pairs.- Parameters:
- Returns:
The input GeoDataFrame with locale columns added.
- Raises:
ValueError – If method is not one of the valid options.
- Return type:
- classmethod from_census_year(year=2020, cache_dir=None, projection_crs=None)[source]
Construct a classifier from Census TIGER downloads.
Downloads UA/UC (uac), Place, and CBSA shapefiles for the given year, identifies principal cities from population thresholds, and loads population estimates from shapefile attributes.
For 2020+ Census data where Urban Clusters were eliminated, this method derives a UC-equivalent set by filtering urban areas with population < 50,000 (the original UC threshold). This ensures Town codes (31-33) remain reachable.
Principal cities are identified by population threshold (>= 25,000), not from the OMB CBSA delineation file. This is a best-effort approximation. For precise NCES-matching classification, use
from_nces_boundaries()when available.- Parameters:
- Returns:
An initialized
NCESLocaleClassifier.- Raises:
ImportError – If geopandas is not available.
- Return type:
- classmethod from_nces_boundaries(year=2023, cache_dir=None, projection_crs=None)[source]
Construct a classifier from NCES pre-computed locale boundary shapefiles.
NCES publishes locale territory polygons that are already classified with locale codes (11-43). This is faster than computing from Census inputs but limited to years NCES has published.
The downloaded boundaries are split into: - Urbanized Area proxies: City (11-13) + Suburb (21-23) territories - Urban Cluster proxies: Town (31-33) territories - Principal city proxies: City (11-13) territories only
The classifier then works identically to the Census-based one.
- Parameters:
- Returns:
An initialized
NCESLocaleClassifier.- Return type:
- siege_utilities.geo.locale_from_code(code)[source]
Fast lookup of a pre-built
LocaleCodeby integer code.Returns the pre-built constant when possible, avoiding dataclass construction.
- Parameters:
code (int)
- Return type:
- class siege_utilities.geo.NCESDownloader[source]
Bases:
objectDownload NCES locale boundaries, school locations, and district data.
Downloads from the NCES EDGE (Education Demographic and Geographic Estimates) program and returns data as GeoDataFrames.
- Parameters:
cache_dir – Directory for caching downloaded files. Defaults to a temporary directory.
timeout – HTTP request timeout in seconds.
Example:
downloader = NCESDownloader(cache_dir="./nces_cache") boundaries = downloader.download_locale_boundaries(2023) # GeoDataFrame with 12 locale territory polygons
- download_locale_boundaries(year=2023, *, crs=None)[source]
Download NCES locale boundary polygons.
Returns a GeoDataFrame with 12 rows — one polygon per locale territory (codes 11-43). Each polygon represents the geographic extent of that locale classification.
- Parameters:
year (int) – NCES publication year.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
locale_code, locale_category, locale_subcategory, name, geometry.
- Return type:
GeoDataFrame with columns
- download_school_locations(year=2023, state_abbr=None, *, crs=None)[source]
Download geocoded school locations.
Returns a GeoDataFrame of school point locations with NCES locale codes.
- Parameters:
year (int) – NCES publication year.
state_abbr (str | None) – Optional 2-letter state abbreviation to filter.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
ncessch, school_name, lea_id, state_abbr, locale_code, locale_category, locale_subcategory, geometry.
- Return type:
GeoDataFrame with columns
- download_district_data(year=2023)[source]
Download district administrative data with locale codes.
Returns a DataFrame (not GeoDataFrame) of school district administrative records keyed by LEA ID.
- Parameters:
year (int) – NCES publication year.
- Returns:
lea_id, lea_name, state_abbr, locale_code, locale_category, locale_subcategory, survey_year.
- Return type:
DataFrame with columns
- exception siege_utilities.geo.NCESDownloadError[source]
Bases:
ExceptionRaised when an NCES download fails.
- class siege_utilities.geo.Gazetteer[source]
Bases:
ProtocolName → geometry resolver.
The protocol is intentionally narrow — four methods. Backends layer LRU caches, ISO-code translation, and other plumbing internally; callers just see this surface.
- lookup(name, *, country_hint=None, admin_hint=None)[source]
Best match for name.
- Parameters:
- Raises:
GazetteerNotFoundError – No match.
GazetteerAmbiguousError – Multiple matches; caller should pass more hints or use
search()and pick.GazetteerBackendError – Network / dependency / parse failure.
- Return type:
- search(name, *, country_hint=None, limit=10)[source]
Top-N candidate matches.
Used to surface ambiguity to a caller (interactive UI, or programmatic disambiguation when the caller has external knowledge to pick a candidate).
- Parameters:
- Return type:
- is_available()[source]
Return True if the backend’s dependencies are installed and the backend can answer queries.
Should NOT make a network round-trip — this is a fast probe used by the factory to pick a default backend.
- Return type:
- __init__(*args, **kwargs)
- class siege_utilities.geo.GazetteerResult[source]
Bases:
objectA resolved place: name, hierarchy, geometry, centroid.
- canonical_path
Hierarchical breadcrumb the source resolves the place through, e.g.
("US", "AL", "Birmingham"). Useful for downstream filtering and for re-querying the same backend without going through fuzzy search again.
- geometry
shapely shape (Polygon / MultiPolygon for admin areas; may be a Point for cities-as-points).
- Type:
Any
- centroid
shapely Point. Pre-computed because half the consumer code needs it and recomputing on every call is wasteful.
- Type:
Any
- admin_levels
Optional mapping of admin level name → value (e.g.
{"region": "Alabama", "county": "Jefferson"}). Backend-specific; not assumed by the resolver layer.
- raw
Backend-native object for callers that need full fidelity. Excluded from
reprandhashso the dataclass stays sane to print and stable as a cache key.- Type:
Any
- class siege_utilities.geo.GazetteerCandidate[source]
Bases:
objectA search hit — a place that might be the answer.
Search returns multiple of these; lookup picks one. Keep the structure small and JSON-safe so it round-trips through APIs and logs cleanly.
- exception siege_utilities.geo.GazetteerError[source]
Bases:
RuntimeErrorBase class for all gazetteer failures.
- exception siege_utilities.geo.GazetteerNotFoundError[source]
Bases:
GazetteerErrorNo place matched the lookup criteria.
- exception siege_utilities.geo.GazetteerAmbiguousError[source]
Bases:
GazetteerErrorThe query is ambiguous and the caller didn’t pass enough hints.
Carries the candidates so the caller can surface them to a user or pick programmatically.
- __init__(message, candidates)[source]
- Parameters:
message (str)
candidates (list[GazetteerCandidate])
- exception siege_utilities.geo.GazetteerBackendError[source]
Bases:
GazetteerErrorThe backend raised an error we couldn’t translate.
Network failure, malformed upstream response, missing optional dependency at lookup time. Always chains the original via
__cause__.
- siege_utilities.geo.resolve_gazetteer(*, prefer=None, cache_size=1024)[source]
Return a configured
Gazetteerbackend.Selection order:
If
preferis given, use that backend (raises if unavailable).Otherwise, try WKLS (global coverage, no API key).
Then Nominatim (OSM-backed, public service).
Raise if nothing works.
- Parameters:
- Raises:
- Return type:
- exception siege_utilities.geo.EtterError[source]
Bases:
RuntimeErrorBase class for connector-side Etter failures.
- exception siege_utilities.geo.EtterParseError[source]
Bases:
EtterErrorEtter failed to parse the query into a structured filter.
- exception siege_utilities.geo.EtterLowConfidenceError[source]
Bases:
EtterErrorParser succeeded but confidence is below the threshold.
- class siege_utilities.geo.EtterFilter[source]
Bases:
objectA normalized parsed query ready for downstream consumption.
Wraps the upstream
GeoQueryPydantic model into a dataclass with the fields siege_utilities consumers actually need. The original upstream object is preserved onrawfor callers that need full fidelity.- spatial_relation
One of the relations Etter recognises —
"in","near","north_of","south_of","east_of","west_of", etc.Noneif the query doesn’t carry a relation (e.g. a bare place name).- Type:
str | None
- reference_location
The place the relation hangs off —
"Lausanne","Lake Geneva","Birmingham, AL".- Type:
str | None
- buffer_distance_m
Buffer distance in meters if the query had one (
"5 km"→ 5000).Noneotherwise.- Type:
float | None
- raw
The upstream
etter.GeoQueryobject, in case the consumer needs the full breakdown.- Type:
Any
- classmethod from_geoquery(original_query, geo_query)[source]
Translate an upstream
GeoQueryto anEtterFilter.Tolerant of upstream field-name drift — uses
getattrwith defaults rather than positional unpacking.- Parameters:
- Return type:
- class siege_utilities.geo.EtterParser[source]
Bases:
objectWrap
etter.GeoFilterParserwith a sensible default LLM.Usage:
from siege_utilities.geo.providers.etter_filter import EtterParser # Defaults: gpt-4o, temperature=0, OPENAI_API_KEY from env. parser = EtterParser() result = parser.parse("5 km north of Lausanne") # result.spatial_relation == "north_of" # result.reference_location == "Lausanne" # result.buffer_distance_m == 5000.0
Pass
llm=to use a pre-built chat model (e.g. for tests or for pinning to a specific Anthropic / Azure deployment). All other constructor kwargs are forwarded to the upstream parser.- parse(query)[source]
Parse query into an
EtterFilter.- Raises:
EtterParseError – Upstream parser raised an unexpected error. The original exception is chained via
__cause__.EtterLowConfidenceError – Parser succeeded but confidence is below the threshold and
strict_mode=True. (Whenstrict_modeis False, this case logs and returns normally; checkEtterFilter.confidenceif the caller needs to gate.) Also raised if a future upstream exposes its ownLowConfidenceErrorand our local check wasn’t reached — we re-raise it as our type so consumers don’t have to know about upstream class names.
- Parameters:
query (str)
- Return type:
- siege_utilities.geo.default_llm(*, model='gpt-4o', temperature=0.0, api_key=None)[source]
Return a langchain chat model suitable for
EtterParser.Picks the right adapter based on the model name.
api_keyfalls back to the standard environment variable for the chosen provider (OPENAI_API_KEY/ANTHROPIC_API_KEY). Lazy-imports langchain so the rest of this module is importable without it.
- class siege_utilities.geo.RelationSemantics[source]
-
How to interpret directional/proximity relations.
See module docstring for the full rationale.
- BOUNDED = 'bounded'
- HALFPLANE = 'halfplane'
- CONTAINS_CENTROID = 'contains_centroid'
- __new__(value)
- class siege_utilities.geo.EtterGeometryResult[source]
Bases:
objectThe resolved geometry plus diagnostic context.
- geometry
The shapely geometry (or
PointPredicateforCONTAINS_CENTROIDmode).- Type:
Any
- relation
The Etter spatial_relation that was applied, or
Noneif the input had no relation (just a bare reference location → the reference geometry itself).- Type:
str | None
- reference
The
GazetteerResultthat anchored the relation.Noneonly when the filter had no reference_location either, which is a degenerate input.- Type:
- buffer_km
Buffer distance actually used (filter wins over default;
Nonefor relations that don’t buffer).- Type:
float | None
- semantics
Which
RelationSemanticsmode produced this result.
- notes
Free-text diagnostic notes (e.g., “halfplane truncated to ±90° lat to remain valid GeoJSON”).
- reference: GazetteerResult | None
- semantics: RelationSemantics
- __init__(geometry, relation, reference, buffer_km, semantics, notes=())
- Parameters:
geometry (Any)
relation (str | None)
reference (GazetteerResult | None)
buffer_km (float | None)
semantics (RelationSemantics)
- Return type:
None
- exception siege_utilities.geo.EtterToGeometryError[source]
Bases:
RuntimeErrorRaised when an EtterFilter cannot be turned into a geometry.
Carries the parsed filter and the failure reason. The most common causes — the reference location not being found in the gazetteer, or a relation Etter parsed that we don’t know how to translate — each have their own subclass so callers can branch.
- exception siege_utilities.geo.EtterReferenceNotFoundError[source]
Bases:
EtterToGeometryErrorThe reference location couldn’t be resolved by the gazetteer.
- exception siege_utilities.geo.EtterUnknownRelationError[source]
Bases:
EtterToGeometryErrorEtter returned a relation we don’t have a translation for.
- siege_utilities.geo.etter_to_geometry(filter_, *, gazetteer, semantics=RelationSemantics.BOUNDED, default_buffer_km=25.0, country_hint=None, admin_hint=None)[source]
Resolve an
EtterFilterto a geometry.- Parameters:
filter – The parsed query.
gazetteer (Gazetteer) – The gazetteer that resolves the reference location. Pass the result of
siege_utilities.geo.gazetteers.resolve_gazetteer().semantics (RelationSemantics) – Which mode of relation interpretation to use. The default
RelationSemantics.BOUNDEDis safe for most indexed-lookup workloads.default_buffer_km (float) – Buffer to apply for “near”-style relations and as the half-width of bounded directional buffers when the filter doesn’t carry an explicit distance.
admin_hint (str | None) – Forwarded to
gazetteer.lookupto disambiguate the reference location. Etter often emits just the place name; the consumer may know the country from a sibling field.filter_ (EtterFilter)
country_hint (str | None)
admin_hint
- Returns:
EtterGeometryResultwith the geometry (or aPointPredicate) and diagnostic context.- Raises:
EtterReferenceNotFoundError – Gazetteer couldn’t find the reference location.
EtterUnknownRelationError – Etter parsed a relation we don’t know how to translate.
ValueError –
filter_.reference_locationis missing.
- Return type:
- class siege_utilities.geo.SpatialLoaderPlan[source]
Bases:
objectChosen loader with deterministic fallback ordering.
- siege_utilities.geo.select_spatial_loader(ogr2ogr_available, sedona_available, native_spatial_available=False)[source]
Select a spatial loader strategy for Databricks-style environments.
Loader order: 1. databricks_native 2. ogr2ogr 3. sedona 4. python
- Parameters:
- Return type:
- siege_utilities.geo.build_census_table_name(year, geography_level, prefix='census')[source]
Build a normalized census table name like census_2024_block_group.
- siege_utilities.geo.build_census_ingest_targets(year, geography_levels, prefix='census')[source]
Build de-duplicated target table names for ingest planning.
- class siege_utilities.geo.SpatialRuntimePlan[source]
Bases:
objectExecution plan for spatial operations with deterministic fallback order.
- siege_utilities.geo.resolve_spatial_runtime_plan(*, databricks_runtime_version=None, native_spatial_available=None, sedona_available=None)[source]
Resolve spatial runtime execution plan.
Priority order: 1. databricks_native 2. sedona 3. python
- Parameters:
- Return type:
- class siege_utilities.geo.GeometryPayload[source]
Bases:
objectSpark-safe container for a single geometry with optional multi-format encoding.
At least one of
geometry_wkt,geometry_wkb, orgeometry_geojsonshould be populated for the payload to be useful.
- siege_utilities.geo.encode_geometry(geom, fmt='wkt', crs='EPSG:4326')[source]
Encode a shapely geometry into a
GeometryPayload.- Parameters:
geom (Any) – A
shapely.geometry.base.BaseGeometryinstance.fmt (str) – Primary serialisation format –
"wkt","wkb", or"geojson". All three representations are always populated; fmt validates that the caller’s intended primary format is supported.crs (str) – Coordinate reference system identifier stored on the payload.
- Return type:
- Raises:
ValueError – If fmt is not one of
"wkt","wkb","geojson".
- siege_utilities.geo.decode_geometry(payload)[source]
Decode a
GeometryPayloadback to a shapely geometry.Tries WKT first, then WKB, then GeoJSON.
- Return type:
shapely.geometry.base.BaseGeometry
- Parameters:
payload (GeometryPayload)
- siege_utilities.geo.payload_to_spark_row(payload)[source]
Convert a
GeometryPayloadto a Spark-safedict.Binary
geometry_wkbis base64-encoded to a string;geometry_geojsonis JSON-serialised. All resulting values are plain strings (orNone).- Parameters:
payload (GeometryPayload)
- Return type:
- siege_utilities.geo.spark_row_to_payload(row)[source]
Reconstruct a
GeometryPayloadfrom a Spark-rowdict.Reverses the encoding performed by
payload_to_spark_row().- Parameters:
- Return type:
- siege_utilities.geo.s2_index_points(df, lat_col, lon_col, level=12, *, as_token=True)[source]
Compute the S2 cell ID for each point.
- Parameters:
df (pd.DataFrame) – DataFrame with latitude / longitude columns.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
level (int) – S2 level (0–30). Default 12 (~5.8 km² per cell).
as_token (bool) – If True (default), return the cell as a 16-char hex token string (compact, human-readable, JSON-safe). If False, return the int64 cell ID (sortable, ideal for database keys). Both round-trip via the
s2_cell_id_to_token()/s2_token_to_cell_id()helpers.
- Returns:
"s2_index".- Return type:
pd.Seriesindexed like the input. Name- Raises:
ImportError – If s2sphere is not installed.
ValueError – If level is out of range or columns are missing.
- siege_utilities.geo.s2_index_polygon(geometry, level=12, *, refine=True)[source]
Return the set of S2 cells at level covering geometry.
Single-level coverage — symmetric with
h3_index_polygon(). For variable-resolution coverage with a cell-count budget, sees2_region_cover().Because the pure-Python
s2spherelacks polygon support, this function covers the polygon’s bounding box and (by default) post- filters the result to cells whose centers fall inside the actual geometry, using shapely. Setrefine=Falseto skip the filter when the input is a near-rectangle (e.g. a county) and you want the bbox cover unchanged.- Parameters:
- Returns:
setof int64 cell IDs.- Raises:
ImportError – If s2sphere (or shapely, when refine=True) is missing.
ValueError – If level is out of range.
TypeError – If geometry can’t be converted to a shapely shape.
- Return type:
- siege_utilities.geo.s2_region_cover(geometry, *, min_level=0, max_level=30, max_cells=100)[source]
Cover geometry’s bounding box with at most max_cells S2 cells.
The S2-specific operation: returns a mixed-resolution set of cells that together cover the bounding box, preferring coarse cells in the interior and fine cells along boundaries. The output is what you want for a database-side spatial-index lookup.
Use
s2_cells_to_ranges()to turn the result into(min, max)int64 ranges suitable for a SQLBETWEENclause.- Parameters:
- Returns:
A list of int64 cell IDs at varying levels, ordered as the coverer produced them (sortable but not sorted).
- Return type:
- siege_utilities.geo.s2_spatial_join(points_df, polygons_gdf, lat_col, lon_col, level=12, polygon_id_col=None)[source]
Join points to polygons via S2 cell matching (approximate PiP).
Same shape as
h3_spatial_join(). Points and polygons sharing a cell are joined; polygons are decomposed into S2 cells vias2_index_polygon(). First-polygon-wins on overlap.
- siege_utilities.geo.s2_cell_to_boundary(cell_id)[source]
Return the 4 vertex coordinates of a cell as
(lat, lng)tuples.Accepts an int64 cell ID or a hex token string.
- siege_utilities.geo.s2_level_for_area(target_area_km2)[source]
Suggest the S2 level whose average cell area matches target_area_km2.
Comparison is in log-space, mirroring
h3_resolution_for_area().
- siege_utilities.geo.s2_level_for_admin_level(level)[source]
Suggest an S2 level whose average cell area matches a US admin level.
Same admin levels recognised by
h3_resolution_for_admin_level()(state / county / zcta / tract / block_group / block, with the usual aliases). Returns the S2 level that’s closest in log-space.
- siege_utilities.geo.s2_kring(cell_id, k=1)[source]
Return cell IDs within k steps of cell_id on the same level.
The k-ring is approximate for S2 (squares don’t tile uniformly); we walk neighbors recursively. For exact k-ring on a uniform grid use
h3_kring()instead.
- siege_utilities.geo.s2_distance(a, b)[source]
Edge-step distance between two S2 cells at the same level.
Counts cell-boundary crossings via repeated neighbor expansion until b is reached.
O(distance); use only for small radii. For large distances, switch to great-circle distance on the cell centers.- Return type:
- siege_utilities.geo.s2_parent(cell_id, level)[source]
Return the ancestor cell at level (must be ≤ current level).
- siege_utilities.geo.s2_children(cell_id)[source]
Return the four immediate children of a cell.
Raises if called on a leaf cell (level 30).
- siege_utilities.geo.s2_cell_id_to_uint64(cell_or_token)[source]
Return the int64 form of a cell, accepting either a token or an id.
Useful when receiving a token string (e.g.
"88891b") and needing the integer for a database column. Accepts any integer-like type (Python int, numpy.int64, numpy.uint64) for round-trip convenience.- Return type:
- siege_utilities.geo.s2_uint64_to_cell_id(n)[source]
Return an
s2sphere.CellIdfrom an int64 value.- Parameters:
n (int)
- siege_utilities.geo.s2_cell_id_to_token(cell_id)[source]
Return the compact hex token form of a cell.
Tokens are stable, JSON-safe, and 16 characters or fewer.
- Return type:
- siege_utilities.geo.s2_token_to_cell_id(token)[source]
Parse a hex token back to its int64 cell ID.
- siege_utilities.geo.s2_bbox_to_cells(min_lat, min_lon, max_lat, max_lon, *, max_level=18, max_cells=64)[source]
Cover an axis-aligned bounding box with at most max_cells cells.
Convenience wrapper around
s2_region_cover()for the common case of a map-viewport query. Returns int64 cell IDs at varying levels.
- siege_utilities.geo.s2_cells_to_ranges(cell_ids)[source]
Convert cell IDs to
(range_min, range_max)pairs for SQL.Each S2 cell, regardless of level, has a contiguous range of leaf cell IDs that lie within it:
[cell.range_min, cell.range_max]. Pre-computing those ranges lets a SQL query test “is point’s cell inside this region” with a simpleWHERE leaf_cell_id BETWEEN min AND max(one comparison per cell in the cover, no spatial library needed at query time).
- siege_utilities.geo.index_points(df, lat_col, lon_col, *, grid=None, resolution=None, level=None, engine=None, **kwargs)[source]
Compute a grid cell ID for each row in df.
Dispatches to
h3_index_points()ors2_index_points()based on the inference rules. Passresolution=for H3 (default 8) orlevel=for S2 (default 12). Extra keyword arguments are forwarded to the underlying function (e.g.as_token=Falsefor S2).
- siege_utilities.geo.index_polygon(geometry, *, grid=None, resolution=None, level=None, max_cells=None, min_level=None, max_level=None, refine=True)[source]
Cover a polygon with grid cells.
With H3: returns a
setof hex IDs at the requested resolution (uniform).With S2: if
max_cellsis provided (or any ofmin_level,max_level), returns alistof int64 cell IDs froms2_region_cover()(variable-resolution coverage with a cell- count budget). Otherwise returns asetof int64 cell IDs at a singlelevelfroms2_index_polygon().The shape of the return value (set vs list) intentionally signals which mode you got. For SQL-friendly cell-id ranges, see
s2_cells_to_ranges().
- siege_utilities.geo.infer_grid(grid, kwargs)[source]
Resolve
grid=per the documented inference rules.Returns
"h3"or"s2"; raisesValueErrorif the inputs contradict the chosen grid. Pure function — does not mutate kwargs.
- class siege_utilities.geo.PlanAuthority[source]
-
Who drew the plan — legally meaningful for citation and audit.
- LEGISLATURE = 'legislature'
Enacted by the state legislature (the default cycle path).
- COURT = 'court'
Court-imposed map (interim or final).
- COMMISSION = 'commission'
Independent or politician redistricting commission.
- DEFAULT = 'default'
Pre-decennial-shift baseline; used by the legacy resolver when no explicit plan covers a date.
- __new__(value)
- class siege_utilities.geo.PlanDistrict[source]
Bases:
objectA single district within a redistricting plan.
All boundaries fields are optional — many consumers only need the identity (which plan, which district, valid when) and resolve the geometry separately via a
BoundaryProvider.- district_type
Lowercase district class —
"cd","sldu","sldl","county_commission", etc. Match the keys used byCensusTIGERProvider.- Type:
- district_id
District identifier within the plan.
"7"for AL-7,"01"for SLDU 1, etc. Stored as the string form so leading-zero district numbers (sometimes used by states) are preserved.- Type:
- plan_name
Stable, human-readable plan identifier (e.g.
"AL_2023_CD_INTERIM"). Conventional but not enforced.- Type:
- authority
Who drew the plan (see
PlanAuthority).
- effective_from
First date the district is in legal effect.
- Type:
- effective_to
Last date the district is in legal effect, or ``None`` for an open-ended plan (the currently active one).
- Type:
datetime.date | None
- geometry_source
Optional pointer to the geometry — a URL, a local path, an RDH dataset slug, or any string the consuming
BoundaryProviderunderstands.Noneis fine if callers only need identity resolution.- Type:
str | None
- notes
Free-text annotation (e.g. court docket, statute citation). Goes through to logs and audit trails verbatim.
- Type:
str | None
- authority: PlanAuthority
- covers_date(when)[source]
Return True iff when falls within this district’s effective span.
Half-open at the upper end:
effective_tois inclusive (if the new plan starts the next day, encode that aseffective_from = old_to + timedelta(days=1)). This matches how court orders are typically written (“effective for elections on or after DATE”) and avoids the off-by-one trap of half-open Python slices.
- __init__(state_fips, district_type, district_id, plan_name, authority, effective_from, effective_to=None, geometry_source=None, notes=None)
- class siege_utilities.geo.RedistrictingPlan[source]
Bases:
objectA full plan: a collection of districts plus plan-level metadata.
The plan is the unit of court orders, statutes, and commission actions; the individual
PlanDistrictrows just decompose it for query convenience.effective_from/effective_toon the plan should match the values on its constituent districts.- plan_name
Same convention as
PlanDistrict.plan_name.- Type:
- authority
Who drew it.
- effective_from
First date the plan is in legal effect.
- Type:
- effective_to
Last date in effect, or
Noneif currently active.- Type:
datetime.date | None
- districts
Tuple of
PlanDistrictrows under this plan.- Type:
- metadata
Free-form mapping for citation, source URLs, court docket numbers, etc. Not used for resolution — consumers can stuff whatever they want here.
Note:
frozen=Trueonly forbids attribute reassignment; themetadatadict remains mutable in place. Instances are therefore not hashable and should be treated as value objects, never used as dict keys or set members.- authority: PlanAuthority
- districts: Tuple[PlanDistrict, ...]
- district(district_id)[source]
Find a district within this plan by id, or
None.- Parameters:
district_id (str)
- Return type:
PlanDistrict | None
- __init__(plan_name, state_fips, district_type, authority, effective_from, effective_to=None, districts=<factory>, metadata=<factory>)
- class siege_utilities.geo.PlanRegistry[source]
Bases:
objectStores redistricting plans and resolves them by date.
Plans are keyed by
(state_fips, district_type). Within a key, plans should be temporally non-overlapping; the registry detects overlap at registration time (warn) and at resolution time (raise in strict mode).Thread-safe for concurrent readers; mutation under a lock.
- register_plan(plan)[source]
Add plan to the registry.
Logs a warning if it overlaps an already-registered plan for the same
(state_fips, district_type). Does not raise on overlap — registration is forgiving so consumers can load partial data; resolution is the strict gate.- Parameters:
plan (RedistrictingPlan)
- Return type:
None
- register_plans(plans)[source]
Bulk-register an iterable of plans.
- Parameters:
plans (Iterable[RedistrictingPlan])
- Return type:
None
- plans_for_state(state_fips, district_type)[source]
Return all registered plans for state_fips / district_type.
Sorted by
effective_fromascending. Empty list if none.- Parameters:
- Return type:
- resolve_plan_at_date(state_fips, district_type, when, *, strict=True)[source]
Find the plan in effect for state_fips / district_type on when.
- Parameters:
state_fips (str) – 2-char state FIPS.
district_type (str) –
"cd","sldu","sldl", etc.when (date) – The date to resolve.
strict (bool) – If
True(default), raisePlanOverlapErrorwhen more than one plan covers when. IfFalse, log a warning and return the most recently enacted one (latesteffective_from) — appropriate when court interim/final pairs both technically cover the same day.
- Raises:
PlanResolutionError – No plan covers the date.
PlanOverlapError – Multiple plans cover the date and
strict=True.
- Return type:
- resolve_district_at_date(state_fips, district_type, district_id, when, *, strict=True)[source]
Find the specific district covering when.
Convenience wrapper: resolves the plan, then looks up the district by id. Raises
PlanResolutionErrorif the plan does not contain district_id.
- exception siege_utilities.geo.PlanResolutionError[source]
Bases:
LookupErrorNo plan covers the requested (state, district_type, date) tuple.
- exception siege_utilities.geo.PlanOverlapError[source]
Bases:
ValueErrorTwo registered plans cover the same date for the same state+type.
Raised by
PlanRegistry.resolve_plan_at_date()when the registry finds more than one match in strict mode. Caller can rerun withstrict=Falseto get the first match (and a warning), or fix the underlying plan data.
- siege_utilities.geo.get_default_plan_registry()[source]
Return the process-global default
PlanRegistry.Lazily instantiated on first call. Consumers that need isolation (multi-tenant code, unit tests) should instantiate
PlanRegistrydirectly instead.- Return type:
- siege_utilities.geo.detect_format(file_path)[source]
Return a format token for a vector GIS file.
- Parameters:
file_path (str) – Path to a vector GIS file on disk.
- Returns:
"shapefile","geojson","kml","kmz","geopackage","dxf","swmaps","unknown".- Return type:
One of
- Raises:
FileNotFoundError – If the file does not exist.
Detection algorithm:
Extension lookup (case-insensitive) against a small canonical map.
For ambiguous extensions (
.json,.sqlite,.db,.zip), a magic-byte / archive-peek fallback inspects the file body.
Directories return
"unknown"— walking is the caller’s responsibility.
- siege_utilities.geo.extract_attributes_ogr(feature, field_names, missing='none')[source]
Extract one attribute from a fiona-record-shaped feature.
Mirrors the candidate-list semantics of the salvage-source helper
_getOGRDataFromSource(DataUtils.py:489-513) which accepts a primary plus a fallback field name and returnsNonewhen neither is present. The salvage source used the GDAL/OGR Python bindings directly; this implementation is fiona-record-shaped (the high-level OGR wrapper that SU declares as a geo dep).- Parameters:
feature (Any) – A fiona-style record (
{"properties": {...}, ...}) OR a plaindict(e.g. the property dict directly).field_names (Iterable[str]) – Candidate field names, in priority order. First name present on the feature wins.
missing (str) – One of
"none"(default; returnNone),"raise"(KeyError), or"warn"(log + returnNone).
- Returns:
The attribute value or
None.- Return type:
- siege_utilities.geo.extract_attributes_kml(kml_extended_data, field_names, missing='none')[source]
Extract one attribute from a KML extended-data mapping.
Mirrors the salvage-source helper
_getKMLDataFromSource(DataUtils.py:516-520). The salvage version was a one-liner dict lookup with no candidate fallback; this version adds the same candidate-list semantics asextract_attributes_ogr()for consistency across the dispatcher.
- siege_utilities.geo.extract_attributes_swmaps(db_path, feature_id, field_names, missing='none')[source]
Extract one attribute for a SWMaps feature by attribute-field name.
Queries the SWMaps SQLite
attribute_valuestable joined toattribute_fieldsfor the named field, scoped tofeature_id. This is the same join pattern as the salvage source’sswmapsAttributeQuery(DataUtils.py:693-698); the application-schema-specific mapper (swMapsAttributeMapperdict atDataUtils.py:672-686) is NOT lifted — that lives with the consumer.For full SWMaps feature iteration (geometry + all attributes), use
siege_utilities.geo.swmaps_readerinstead; this function is the thin dispatcher-friendly accessor.- Parameters:
- Returns:
The attribute value (as stored in
attribute_values.value) orNone.- Return type:
- class siege_utilities.geo.TemporalDataStore[source]
Bases:
objectPure-Python persistence for temporal geographic data.
Follows the CrosswalkClient pattern: class with module-level convenience functions for the common case.
- SUPPORTED_FORMATS = ('parquet', 'gpkg')
- load_boundaries(geography_type, vintage_year, state_fips=None)[source]
Load boundaries for a geography type and vintage year.
- query_boundaries_at_date(geography_type, query_date, state_fips=None)[source]
Load the best-matching vintage for a given date.
If boundaries have valid_from/valid_to columns, those are checked. Otherwise, the nearest vintage_year <= query_date.year is used.
- list_available_vintages(geography_type)[source]
List vintage years available on disk for a geography type.
- save_demographics(snapshots, geography_type, dataset='acs5', year=None)[source]
Persist demographic snapshot data as Parquet.
- load_demographics(geography_type, year=None, dataset='acs5')[source]
Load demographic data. If year is None, loads all available years.
- siege_utilities.geo.get_temporal_store(root_dir=None, format='parquet')[source]
Get or create the singleton TemporalDataStore.
- Parameters:
- Return type:
- siege_utilities.geo.save_boundaries(gdf, geography_type, vintage_year, **kwargs)[source]
Save boundaries via the default store.
- siege_utilities.geo.load_boundaries(geography_type, vintage_year, state_fips=None, **kwargs)[source]
Load boundaries via the default store.
- siege_utilities.geo.query_boundaries_at_date(geography_type, query_date, state_fips=None, **kwargs)[source]
Query boundaries at a date via the default store.
- Parameters:
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.save_demographics(snapshots, geography_type, dataset='acs5', year=None, **kwargs)[source]
Save demographics via the default store.
- Parameters:
snapshots (pd.DataFrame) – DataFrame of demographic snapshot data.
geography_type (str) – Type of geography (e.g. “county”, “tract”).
dataset (str) – Dataset identifier (default “acs5”).
year (int | None) – Year of the snapshot. Inferred from DataFrame if omitted.
**kwargs – Forwarded to get_temporal_store(). Accepts root_dir and format.
- Return type:
- siege_utilities.geo.load_demographics(geography_type, year=None, dataset='acs5', **kwargs)[source]
Load demographics via the default store.
- Parameters:
- Return type:
pd.DataFrame
- siege_utilities.geo.spatial_query(boundaries, points, predicate='intersects', *, crs=None)[source]
Spatial join between boundaries and points using geopandas.sjoin.
Aligns CRS before joining if both GeoDataFrames have a CRS set.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons (left)
points (gpd.GeoDataFrame) – GeoDataFrame of query geometries (right)
predicate (str) – Spatial predicate (intersects, within, contains, etc.)
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
Joined GeoDataFrame with columns from both inputs, in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal_filter(gdf, query_date, valid_from_col='valid_from', valid_to_col='valid_to', vintage_year_col='vintage_year')[source]
Filter a GeoDataFrame to rows valid at query_date.
First tries valid_from/valid_to date range filtering. Falls back to vintage_year if date columns are missing or all-null.
- Parameters:
- Returns:
Filtered GeoDataFrame
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.point_in_boundary(boundaries, lon, lat, predicate='intersects', *, crs=None)[source]
Find which boundaries contain a single point.
Convenience wrapper that creates a Point GeoDataFrame and calls spatial_query.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons
lon (float) – Longitude
lat (float) – Latitude
predicate (str) – Spatial predicate
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
GeoDataFrame of matching boundaries in crs.
- Return type:
gpd.GeoDataFrame
Core Spatial Modules
Modern spatial data sources for siege_utilities. Provides clean, type-safe access to Census, Government, and OpenStreetMap data.
- exception siege_utilities.geo.spatial_data.SpatialDataError[source]
Bases:
RuntimeErrorRaised when a non-boundary spatial data fetch fails unexpectedly.
Used by GovernmentDataSource and OpenStreetMapDataSource, which load generic portal datasets and OSM Overpass results respectively. Boundary retrieval has its own exception hierarchy in boundary_result.py.
Use the
__cause__attribute (set viaraise ... from e) to inspect the underlying exception (HTTPError, JSONDecodeError, etc.).
- class siege_utilities.geo.spatial_data.SpatialDataSource[source]
Bases:
objectBase class for spatial data sources.
- download_data(**kwargs)[source]
Download data from the source.
- Return type:
geopandas.GeoDataFrame | None
- class siege_utilities.geo.spatial_data.CensusDataSource[source]
Bases:
SpatialDataSourceCensus TIGER/Line boundary data source with dynamic year discovery.
Supports all standard TIGER/Line boundary types:
- National-scope (no state_fips required):
nation,state,county,place,zcta,cd- State-scope (state_fips required):
tract,block_group,block,tabblock20,sldu,sldl
GEOID formats by boundary type:
Boundary
Digits
Components
state
2
SS
county
5
SS + CCC
tract
11
SS + CCC + TTTTTT
block_group
12
SS + CCC + TTTTTT + G
tabblock20
15
SS + CCC + TTTTTT + BBBB
cd
4
SS + DD
sldu
5
SS + DDD
sldl
5
SS + DDD
- discover_boundary_types(year)[source]
Discover available boundary types for a year.
Returns a mapping of boundary-type slug (
'county','tract', …) to the canonical TIGER directory name ('COUNTY','TRACT', …). Matches the return contract ofTigerDiscovery.discover_boundary_types(). The previousList[str]annotation was incorrect – the underlying call has always returned the dict.
- get_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, county_fips=None, congress_number=None)[source]
Download geographic boundaries from Census TIGER/Line files with dynamic discovery.
This method preserves the legacy
Optional[GeoDataFrame]return type. For structured diagnostics, usefetch_geographic_boundaries()instead.- Parameters:
year (int) – Census year (will find closest available if not available)
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code for filtering (required for some levels)
county_fips (str | None) – County FIPS code for filtering
congress_number (int | None) – Congress number for congressional districts
- Returns:
GeoDataFrame with boundaries or None if failed
- Return type:
geopandas.GeoDataFrame | None
- fetch_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, county_fips=None, congress_number=None)[source]
Download geographic boundaries with structured diagnostics.
Returns a
BoundaryFetchResultthat carries either the GeoDataFrame on success or a machine-readable error code, stage, and context dict on failure.- Parameters:
year (int) – Census year (will find closest available if not available)
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code for filtering (required for some levels)
county_fips (str | None) – County FIPS code for filtering
congress_number (int | None) – Congress number for congressional districts
- Returns:
BoundaryFetchResult with .success, .geodataframe, .error_stage, etc.
- Return type:
- normalize_state_identifier(state_input)[source]
Convert any state identifier (FIPS, abbreviation, name) to FIPS code.
Deprecated since version 3.16.0: Use the module-level
normalize_state_identifier()directly.- Raises:
ValueError – If the input cannot be resolved to a valid state FIPS.
- Return type:
- get_comprehensive_state_info()[source]
Get comprehensive state information including FIPS, name, and abbreviation.
- class siege_utilities.geo.spatial_data.GovernmentDataSource[source]
Bases:
SpatialDataSourceGovernment data portal spatial data source.
- download_dataset(dataset_id, format_type='geojson')[source]
Download a dataset from the government data portal.
- Parameters:
- Returns:
GeoDataFrame with spatial data.
- Raises:
SpatialDataError – On any download or processing failure.
- Return type:
- class siege_utilities.geo.spatial_data.OpenStreetMapDataSource[source]
Bases:
SpatialDataSourceOpenStreetMap data source using Overpass API.
- download_osm_data(query, bbox=None)[source]
Download data from OpenStreetMap using Overpass QL.
- Parameters:
- Returns:
GeoDataFrame with OSM data.
- Raises:
SpatialDataError – On HTTP or processing failure.
- Return type:
- class siege_utilities.geo.spatial_data.CensusDirectoryDiscovery[source]
Bases:
objectDiscovers available Census TIGER/Line data dynamically.
- get_available_years(force_refresh=False)[source]
Get list of available Census years with retry and exponential backoff.
- get_year_directory_contents(year, force_refresh=False, on_error='skip')[source]
Get contents of a specific TIGER year directory.
- Parameters:
- Return type:
- get_year_specific_url_patterns(year)[source]
Get year-specific URL patterns and directory structures.
- construct_download_url(year, boundary_type, state_fips=None, congress_number=None)[source]
Construct download URL using FIPS validation and year-specific patterns.
- Raises:
BoundaryInputError – If state_fips is invalid.
BoundaryDiscoveryError – If boundary_type is not available or URL cannot be constructed.
- Parameters:
- Return type:
- validate_download_url(url)[source]
Check if a download URL is actually accessible.
Uses GET with stream=True instead of HEAD because CDNs like Cloudflare often block or throttle HEAD requests from non-browser User-Agents.
- Raises:
BoundaryUrlValidationError – If the URL is not accessible.
- Parameters:
url (str)
- Return type:
- siege_utilities.geo.spatial_data.get_census_data(api_key=None)[source]
Get Census data source instance.
- Parameters:
api_key (str | None)
- Return type:
- siege_utilities.geo.spatial_data.get_census_boundaries(year=2026, geographic_level='county', state_fips=None, state_identifier=None)[source]
Convenience function to get Census boundaries.
Deprecated since version 3.16.0: Use
fetch_geographic_boundaries()(via CensusDataSource) for structured diagnostics viaBoundaryFetchResult.get_census_boundarieswill be removed in v3.17.0.- Parameters:
- Return type:
geopandas.GeoDataFrame | None
Note
Provide either state_fips OR state_identifier, not both. If both are provided, state_identifier takes precedence.
- Returns:
GeoDataFrame with Census boundaries (filtered by state if specified)
- Parameters:
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.spatial_data.get_available_years(force_refresh=False)[source]
Get available Census years.
- siege_utilities.geo.spatial_data.get_year_directory_contents(year)[source]
Get directory contents for a specific year.
- siege_utilities.geo.spatial_data.discover_boundary_types(year)[source]
Discover available boundary types for a year.
Returns the same
{boundary_type: tiger_dir}mapping asCensusDataSource.discover_boundary_types(). The historicalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.spatial_data.download_data(year, geographic_level, state_fips=None, *, crs=None)[source]
Download Census data.
This is a convenience wrapper around get_geographic_boundaries().
- Parameters:
year (int) – Census year
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code (required for tract, block_group, etc.)
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
GeoDataFrame with boundaries in crs, or None if failed.
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.spatial_data.download_dataset(year, geographic_level, state_fips=None, *, crs=None)[source]
Download Census dataset.
- Parameters:
year (int) – Census year.
geographic_level (str) – Geographic level.
state_fips (str | None) – State FIPS code.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.spatial_data.get_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, state_identifier=None, *, crs=None)[source]
Get geographic boundaries (legacy, returns None on failure).
Deprecated since version 3.16.0: Use
fetch_geographic_boundaries()for structured diagnostics.get_geographic_boundarieswill be removed in v3.17.0.- Parameters:
- Return type:
geopandas.GeoDataFrame | None
- siege_utilities.geo.spatial_data.fetch_geographic_boundaries(year=2026, geographic_level='county', state_fips=None, congress_number=None, *, crs=None)[source]
Get geographic boundaries with structured diagnostics.
Returns a
BoundaryFetchResultinstead ofOptional[GeoDataFrame].- Parameters:
year (int) – Census year
geographic_level (str) – Geographic level (state, county, tract, etc.)
state_fips (str | None) – State FIPS code (required for tract, block_group, etc.)
congress_number (int | None) – Congress number for congressional districts
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
BoundaryFetchResult with .success, .geodataframe in crs, .error_stage, etc.
- Return type:
- siege_utilities.geo.spatial_data.load_census_inventory(path=None)[source]
Load the saved Census inventory from disk. Returns None if absent.
- Raises:
SpatialDataError – If the file exists but cannot be parsed.
- Parameters:
path (Path | None)
- Return type:
dict | None
- siege_utilities.geo.spatial_data.update_census_inventory(years=None, include_genz=True, save_path=None, request_delay=0.5, timeout=15)[source]
Crawl the Census FTP and return a timestamped directory inventory.
Fetches the TIGER (and optionally GENZ) year→layers structure using BeautifulSoup and saves it as JSON so callers can use it as a fallback when live Census requests are blocked (403) or rate-limited (429).
- Parameters:
years (List[int] | None) – Specific years to crawl. Defaults to 2010 through the current year.
include_genz (bool) – Also crawl the Cartographic Boundary File (GENZ) tree.
save_path (Path | None) – Where to save the JSON. Defaults to
~/.cache/siege_utilities/census_inventory.json.request_delay (float) – Seconds to sleep between HTTP requests to avoid hammering the Census server.
timeout (int) – Per-request timeout in seconds.
- Returns:
dict with keys
updated_at,tiger, and (optionally)genz.tiger[year]is a sorted list of layer directory names.genz[year]is a sorted list of available CB file name stems.
- Return type:
- siege_utilities.geo.spatial_data.refresh_discovery_cache()[source]
Refresh the discovery cache.
- Return type:
None
- siege_utilities.geo.spatial_data.construct_download_url(year, geographic_level, state_fips=None)[source]
Construct download URL for Census data.
- siege_utilities.geo.spatial_data.get_optimal_year(geographic_level, preferred_year=None)[source]
Get optimal year for geographic level.
- siege_utilities.geo.spatial_data.get_available_boundary_types(year)[source]
Get available boundary types for a year.
Returns
{boundary_type: tiger_dir}– same shape asCensusDataSource.get_available_boundary_types(). The historicalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.spatial_data.normalize_state_identifier(state_input)[source]
Normalize a state identifier to its 2-digit zero-padded FIPS code.
- Parameters:
state_input (str) – Any of: 2-digit FIPS code (
"48"), 2-letter abbreviation ("TX"), or full state name ("Texas"). Case-insensitive.- Returns:
Zero-padded 2-digit FIPS code (e.g.,
"06"for California).- Return type:
- Raises:
ValueError – If state_input doesn’t match any known state, territory, or DC.
Examples
>>> normalize_state_identifier("TX") '48' >>> normalize_state_identifier("48") '48' >>> normalize_state_identifier("Texas") '48' >>> normalize_state_identifier("DC") '11'
- siege_utilities.geo.spatial_data.normalize_state_identifier_standalone(identifier)[source]
Normalize a state identifier to its 2-digit FIPS code.
Delegates to
siege_utilities.config.census_registry.normalize_state_identifier(), the canonical implementation.- Parameters:
identifier (str) – Any of: 2-digit FIPS code, 2-letter abbreviation, or full state name.
- Returns:
Zero-padded 2-digit FIPS code.
- Return type:
- Raises:
ValueError – If identifier doesn’t match any known state, territory, or DC.
- siege_utilities.geo.spatial_data.normalize_state_input(raw_input)[source]
Normalize state input to handle formatting and trivial errors.
- Parameters:
raw_input (str) – Raw state input (name, abbreviation, or FIPS)
- Returns:
Normalized input (uppercase, trimmed, FIPS padded)
- Return type:
Examples
normalize_state_input(” ca “) -> “CA” normalize_state_input(“CAlifornia”) -> “CALIFORNIA” normalize_state_input(“6”) -> “06”
- siege_utilities.geo.spatial_data.normalize_state_name(name)[source]
Normalize state name input (e.g., “CAlifornia” -> “CALIFORNIA”).
- siege_utilities.geo.spatial_data.normalize_state_abbreviation(abbrev)[source]
Normalize state abbreviation input (e.g., “ ca “ -> “CA”).
- siege_utilities.geo.spatial_data.normalize_fips_code(fips)[source]
Normalize FIPS code input (e.g., 6 -> “06”, “6” -> “06”).
- siege_utilities.geo.spatial_data.get_state_by_abbreviation(abbreviation)[source]
Get state info by abbreviation.
- siege_utilities.geo.spatial_data.get_state_abbreviation(fips)[source]
Get state abbreviation from FIPS code.
- siege_utilities.geo.spatial_data.get_state_abbreviations()[source]
Get a
{fips_code: state_abbreviation}mapping.Same shape as
CensusDataSource.get_state_abbreviations(). HistoricalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.spatial_data.get_available_state_fips()[source]
Get available state FIPS codes as a
{fips_code: state_name}mapping.Same shape as
CensusDataSource.get_available_state_fips(). HistoricalList[str]annotation never matched runtime behavior.
- siege_utilities.geo.spatial_data.get_unified_fips_data()[source]
Get unified FIPS data with state names and abbreviations.
- siege_utilities.geo.spatial_data.get_comprehensive_state_info()[source]
Get comprehensive state information.
- siege_utilities.geo.spatial_data.download_osm_data(query, bbox=None, *, crs=None)[source]
Convenience function to download OSM data.
- Parameters:
query (str) – OSM query string.
bbox (List[float] | None) – Optional bounding box
[west, south, east, north].crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Return type:
geopandas.GeoDataFrame | None
Spatial data transformation utilities for siege_utilities. Provides format conversion and transformation capabilities using the core geospatial stack. DuckDB support is included but optional for enhanced performance on large datasets.
- exception siege_utilities.geo.spatial_transformations.SpatialQueryError[source]
Bases:
RuntimeErrorRaised when a spatial SQL query fails.
Use the
__cause__attribute (set viaraise ... from e) to inspect the underlying database exception.
- class siege_utilities.geo.spatial_transformations.SpatialDataTransformer[source]
Bases:
objectTransform spatial data between different formats and coordinate systems.
- convert_format(gdf, output_format, **kwargs)[source]
Convert GeoDataFrame to different format.
- Parameters:
gdf (None) – Input GeoDataFrame
output_format (str) – Desired output format
**kwargs – Additional format-specific parameters
- Raises:
ValueError – If output_format is unsupported
ImportError – If required dependency is not available
OSError – If file write fails
- Return type:
None
- class siege_utilities.geo.spatial_transformations.PostGISConnector[source]
Bases:
objectHandles PostGIS database connections and operations.
- __init__(connection_string=None)[source]
Initialize PostGIS connector.
- Parameters:
connection_string (str | None) – PostgreSQL connection string. When omitted, we look up the configured
postgresqlconnection from the user-config; if that’s unavailable too, the connector stays uninitialized (self.connection is None).
- upload_spatial_data(gdf, table_name, **kwargs)[source]
Upload spatial data to PostGIS with all columns preserved.
Uses geopandas.GeoDataFrame.to_postgis under the hood, which handles full-column writes (geometry + every attribute column) via SQLAlchemy + GeoAlchemy2. This is the canonical geopandas idiom for PostGIS upload.
Pre-#516 behavior (replaced): the old implementation manually built INSERT INTO {table} (geom) VALUES (…) statements, dropping all tabular columns from the GeoDataFrame. Any caller passing attribute data got an empty-attribute table silently.
- Parameters:
gdf (None) – GeoDataFrame to upload (all columns including geometry).
table_name (str) – Target table name.
**kwargs –
Additional parameters: if_exists: How to handle existing table –
'fail','replace'(default), or'append'.schema: PostgreSQL schema name (default
'public').
- Raises:
RuntimeError – If no connection string is configured
ImportError – If geoalchemy2 is not available
SpatialQueryError – If the upload fails
- Return type:
None
- download_spatial_data(table_name, *, crs=None, **kwargs)[source]
Download spatial data from PostGIS.
- Parameters:
table_name (str) – Source table name
crs (str | None) – Output CRS. Defaults to
get_default_crs().**kwargs – Additional parameters. Accepts
geom_col(default'geom') to specify the geometry column name.
- Returns:
GeoDataFrame with spatial data.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
- Return type:
None
- execute_spatial_query(query, *, crs=None, **kwargs)[source]
Execute a spatial SQL query.
For SELECT queries the result is a
GeoDataFrame. For non-SELECT queries (INSERT, UPDATE, DELETE, DDL) the result is thecursor.rowcountinteger (-1when the row count is not determinable, e.g. DDL).- Parameters:
query (str) – SQL query string
crs (str | None) – Output CRS. Defaults to
get_default_crs().**kwargs – Additional parameters
- Returns:
GeoDataFrame for SELECT queries, int rowcount for others.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails. The original exception is chained as
__cause__.- Return type:
None | int
- class siege_utilities.geo.spatial_transformations.DuckDBConnector[source]
Bases:
objectHandles DuckDB database connections and operations (optional).
- __init__(db_path=None)[source]
Initialize DuckDB connector.
- Parameters:
db_path (str | None) – Path to DuckDB database file (optional, uses user config if not provided)
- connect()[source]
Establish database connection.
- Raises:
RuntimeError – If connection fails
- Return type:
None
- upload_spatial_data(gdf, table_name, **kwargs)[source]
Upload spatial data to DuckDB.
- Parameters:
gdf (None) – GeoDataFrame to upload
table_name (str) – Target table name
**kwargs – Additional parameters
- Raises:
RuntimeError – If connection or upload fails
- Return type:
None
- download_spatial_data(table_name, *, crs=None, **kwargs)[source]
Download spatial data from DuckDB.
- Parameters:
table_name (str) – Source table name
crs (str | None) – Output CRS. Defaults to
get_default_crs().**kwargs – Additional parameters
- Returns:
GeoDataFrame with spatial data. WKT geometries stored in DuckDB lack SRID metadata; EPSG:4326 is assumed as the source CRS. Pass crs to reproject to a different target.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
- Return type:
None
- execute_spatial_query(query, *, crs=None, **kwargs)[source]
Execute a spatial SQL query.
For SELECT queries the result is a
GeoDataFrame. For non-SELECT queries (INSERT, UPDATE, DELETE, DDL) the result is thecursor.rowcountinteger (-1when the row count is not determinable, e.g. DDL).- Parameters:
query (str) – SQL query string
crs (str | None) – Output CRS. Defaults to
get_default_crs().**kwargs – Additional parameters
- Returns:
GeoDataFrame for SELECT queries, int rowcount for others.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails. The original exception is chained as
__cause__.- Return type:
None | int
- siege_utilities.geo.spatial_transformations.convert_spatial_format(gdf, output_format, **kwargs)[source]
Convert spatial data to different format.
- Parameters:
gdf (None) – Input GeoDataFrame.
output_format (str) – Target format (
'shp','geojson','gpkg','kml','gml','wkt','wkb','postgis','duckdb').**kwargs –
Format-specific parameters: output_path (str): Output file path (default varies by format). db_path (str): DuckDB database path (
'duckdb'format only). table_name (str): DuckDB table name ('duckdb'format only,default
'spatial_data').
- Raises:
ValueError – If output_format is unsupported.
ImportError – If a required dependency is not available.
- Return type:
None
- siege_utilities.geo.spatial_transformations.upload_to_postgis(gdf, table_name, connection_string=None, **kwargs)[source]
Upload spatial data to PostGIS.
- Parameters:
gdf (None) – GeoDataFrame to upload.
table_name (str) – Target table name.
connection_string (str | None) – PostgreSQL connection string (uses user config when omitted).
**kwargs – Forwarded to
PostGISConnector.upload_spatial_data(). Acceptsif_exists('fail','replace','append'; default'replace') andschema(default'public').
- Raises:
RuntimeError – If no connection string is configured.
ImportError – If geoalchemy2 is not available.
SpatialQueryError – If the upload fails.
- Return type:
None
- siege_utilities.geo.spatial_transformations.download_from_postgis(table_name, connection_string=None, **kwargs)[source]
Download spatial data from PostGIS.
- Parameters:
table_name (str) – Source table name (may be schema-qualified, e.g.
'public.my_table').connection_string (str | None) – PostgreSQL connection string (uses user config when omitted).
**kwargs – Forwarded to
PostGISConnector.download_spatial_data(). Acceptsgeom_col(default'geom') andcrs(output CRS).
- Returns:
GeoDataFrame with spatial data.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
- Return type:
None
- siege_utilities.geo.spatial_transformations.execute_postgis_query(query, connection_string=None, **kwargs)[source]
Execute a spatial SQL query on PostGIS.
- Parameters:
query (str) – SQL query string.
connection_string (str | None) – PostgreSQL connection string (uses user config when omitted).
**kwargs – Forwarded to
PostGISConnector.execute_spatial_query(). Acceptsgeom_col(default'geometry') andcrs(output CRS, applies to SELECT queries only).
- Returns:
GeoDataFrame for SELECT queries, int rowcount for others.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
- Return type:
None | int
- siege_utilities.geo.spatial_transformations.upload_to_duckdb(gdf, table_name, db_path=None, **kwargs)[source]
Upload spatial data to DuckDB (optional).
- Parameters:
gdf (None) – GeoDataFrame to upload.
table_name (str) – Target table name.
db_path (str | None) – Path to DuckDB database file (uses user config or in-memory when omitted).
**kwargs – Forwarded to
DuckDBConnector.upload_spatial_data(). Acceptsif_exists('replace'by default).
- Raises:
ImportError – If DuckDB is not installed.
RuntimeError – If upload fails.
- Return type:
None
- siege_utilities.geo.spatial_transformations.download_from_duckdb(table_name, db_path=None, **kwargs)[source]
Download spatial data from DuckDB (optional).
- Parameters:
table_name (str) – Source table name.
db_path (str | None) – Path to DuckDB database file (uses user config or in-memory when omitted).
**kwargs – Forwarded to
DuckDBConnector.download_spatial_data(). Acceptswhere_clause(optional SQL WHERE fragment) andcrs(output CRS).
- Returns:
GeoDataFrame with spatial data.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
ImportError – If DuckDB is not installed.
- Return type:
None
- siege_utilities.geo.spatial_transformations.execute_duckdb_query(query, db_path=None, **kwargs)[source]
Execute a spatial SQL query on DuckDB (optional).
- Parameters:
query (str) – SQL query string.
db_path (str | None) – Path to DuckDB database file (uses user config or in-memory when omitted).
**kwargs – Forwarded to
DuckDBConnector.execute_spatial_query(). Acceptscrs(output CRS, applies to SELECT queries only).
- Returns:
GeoDataFrame for SELECT queries, int rowcount for others.
- Raises:
SpatialQueryError – If the connection is unavailable or the query fails.
ImportError – If DuckDB is not installed.
- Return type:
None | int
Spatial runtime capability planning across Databricks/Spark/Python contexts.
Includes GeometryPayload for Spark-safe geometry transport and codec helpers
(encode_geometry, decode_geometry, payload_to_spark_row,
spark_row_to_payload) that round-trip shapely geometries through WKT / WKB /
GeoJSON representations.
- class siege_utilities.geo.spatial_runtime.SpatialRuntimePlan[source]
Bases:
objectExecution plan for spatial operations with deterministic fallback order.
- siege_utilities.geo.spatial_runtime.resolve_spatial_runtime_plan(*, databricks_runtime_version=None, native_spatial_available=None, sedona_available=None)[source]
Resolve spatial runtime execution plan.
Priority order: 1. databricks_native 2. sedona 3. python
- Parameters:
- Return type:
- class siege_utilities.geo.spatial_runtime.GeometryPayload[source]
Bases:
objectSpark-safe container for a single geometry with optional multi-format encoding.
At least one of
geometry_wkt,geometry_wkb, orgeometry_geojsonshould be populated for the payload to be useful.
- siege_utilities.geo.spatial_runtime.encode_geometry(geom, fmt='wkt', crs='EPSG:4326')[source]
Encode a shapely geometry into a
GeometryPayload.- Parameters:
geom (Any) – A
shapely.geometry.base.BaseGeometryinstance.fmt (str) – Primary serialisation format –
"wkt","wkb", or"geojson". All three representations are always populated; fmt validates that the caller’s intended primary format is supported.crs (str) – Coordinate reference system identifier stored on the payload.
- Return type:
- Raises:
ValueError – If fmt is not one of
"wkt","wkb","geojson".
- siege_utilities.geo.spatial_runtime.decode_geometry(payload)[source]
Decode a
GeometryPayloadback to a shapely geometry.Tries WKT first, then WKB, then GeoJSON.
- Return type:
shapely.geometry.base.BaseGeometry
- Parameters:
payload (GeometryPayload)
- siege_utilities.geo.spatial_runtime.payload_to_spark_row(payload)[source]
Convert a
GeometryPayloadto a Spark-safedict.Binary
geometry_wkbis base64-encoded to a string;geometry_geojsonis JSON-serialised. All resulting values are plain strings (orNone).- Parameters:
payload (GeometryPayload)
- Return type:
- siege_utilities.geo.spatial_runtime.spark_row_to_payload(row)[source]
Reconstruct a
GeometryPayloadfrom a Spark-rowdict.Reverses the encoding performed by
payload_to_spark_row().- Parameters:
- Return type:
Configurable default CRS plus geometry-level CRS detection and axis-order-aware reprojection.
The default-CRS layer (get_default_crs / set_default_crs /
reproject_if_needed) is the existing thin GeoDataFrame-shaped API
that every spatial-returning function in siege_utilities.geo
uses as its default. Call set_default_crs() once at the top of a
notebook or script to change the default globally:
import siege_utilities as su
su.set_default_crs("EPSG:3857")
# All subsequent calls return Web Mercator unless overridden:
gdf = su.download_data(2020, "county") # already in EPSG:3857
detect_crs() and reproject_geom() extend the module to
geometry-level work with explicit axis-order control. Pattern lifted
from a third-party tile-server audit (common/repos/ImportServerRepo.py:150-336,
specifically the SRS handling at lines 255-274). The salvage source
used the GDAL/OGR Python bindings (osgeo.osr) with
OAMS_TRADITIONAL_GIS_ORDER to force lon-first axis order; SU does
not declare osgeo as a dep, so this module is built on pyproj
(which IS declared). pyproj.Transformer.from_crs(..., always_xy=True)
is the documented modern equivalent of the OGR axis-mapping strategy.
Why the axis-order toggle exists: GeoJSON / Shapefile / KML files store
coordinates lon-first (x, y) but the EPSG authority specifies lat-first
(y, x) for many CRSes including EPSG:4326. The default "trad_gis"
mode honors the file conventions (what most consumers expect). The
"auth_compliant" mode honors the EPSG authority (what GIS
standards-compliant code paths require). Most consumer code expects
trad_gis; choosing auth_compliant without knowing why is a
subtle source of transposed lat/lon bugs.
- siege_utilities.geo.crs.get_default_crs()[source]
Return the current default CRS string (e.g.
"EPSG:4326").- Return type:
- siege_utilities.geo.crs.set_default_crs(crs)[source]
Set the default CRS used by all spatial-returning functions.
- Parameters:
crs (str) – Any CRS string accepted by
pyproj(e.g."EPSG:4326","EPSG:3857","ESRI:102003").- Raises:
pyproj.exceptions.CRSError – If crs is not recognized by pyproj.
- Return type:
None
- siege_utilities.geo.crs.reproject_if_needed(gdf, crs=None)[source]
Reproject gdf to crs if it differs from the GeoDataFrame’s current CRS.
If crs is
None, usesget_default_crs(). Returns the GeoDataFrame unchanged if it is already in crs or is empty.When the GeoDataFrame has no CRS (
gdf.crs is None) and a target is specified, the target CRS is set (not reprojected) on the assumption that the data is already in the target CRS. A warning is logged because this assumption may be wrong.- Parameters:
crs (str | None)
- siege_utilities.geo.crs.detect_crs(geom_or_ds)[source]
Resolve the source CRS of a spatial object.
- Parameters:
geom_or_ds (Any) –
One of:
A GeoDataFrame — reads
.crsand returns anEPSG:<n>string when authoritative, otherwise the WKT.A GeoJSON-like
dict— reads["crs"]["properties"]["name"]if present.A fiona
Collection— reads.crs/.crs_wkt.A bare shapely geometry — bare geometries carry no CRS metadata; this returns
None(callers should treat this as “consultget_default_crs()”).None— returnsNone.
- Returns:
An EPSG string (
"EPSG:4326"), a WKT string, orNone.- Return type:
str | None
Mirrors the SRS-detection branch of the salvage source (
ImportServerRepo.py:250-253), which read the spatial reference from an OGR DataSource viaGetGeometryRef().GetSpatialReference(). This implementation reads.crs/.crs_wktfrom GeoDataFrames or fiona collections — same intent, declared-deps surface.
- siege_utilities.geo.crs.distance_to_decimal_degrees(distance_meters, latitude)[source]
Convert a distance in meters to approximate decimal degrees.
Uses the WGS-84 ellipsoid approximation. The result is the longitudinal span at the given latitude (degrees of longitude shrink toward the poles).
- Parameters:
- Returns:
Approximate equivalent distance in decimal degrees of longitude at latitude.
- Raises:
ValueError – If latitude is outside [-90, 90].
- Return type:
- siege_utilities.geo.crs.reproject_geom(geom, src_crs, dst_crs=4326, axis_order='trad_gis')[source]
Reproject a shapely geometry with explicit axis-order control.
- Parameters:
geom – A shapely geometry (
Point,LineString,Polygon, etc.) orNone.src_crs (Any) – The geometry’s source CRS, in any form
pyproj.CRS.from_user_inputaccepts ("EPSG:4326", an integer EPSG code, a WKT string, apyproj.CRSobject).dst_crs (Any) – Target CRS, in any form
pyproj.CRS.from_user_inputaccepts. Defaults to4326(WGS 84).axis_order (str) –
One of:
"trad_gis"(default) —pyprojalways_xy=True. Coordinates are interpreted and emitted as (lon, lat), matching GeoJSON, Shapefile, KML, and most consumer expectations. This is the modern-pyproj equivalent ofosgeo.osr.OAMS_TRADITIONAL_GIS_ORDERused in the salvage source (ImportServerRepo.py:267-268)."auth_compliant"—always_xy=False. Coordinates follow the CRS authority’s declared axis order (lat-first for EPSG:4326). Use only if you know your input data is lat-first and your consumer expects lat-first.
- Returns:
A new shapely geometry in the target CRS, or
NoneifgeomisNone.- Raises:
ValueError – If
axis_orderis not one of the two accepted tokens, or ifsrc_crsisNone(a CRS-less geometry cannot be reprojected without an explicit source).ImportError – If
pyprojorshapelyare not installed.
Round-trip note: 4326 -> 3857 -> 4326 on a Point should return the same coordinates within 1e-6 degrees. The test suite verifies this.
GEOID utilities for Census data processing.
This module provides functions for constructing, normalizing, and validating Census GEOIDs (Geographic Identifiers). GEOIDs are hierarchical codes that uniquely identify geographic areas.
GEOID Structure: - State: 2 digits (e.g., “06” for California) - County: 5 digits (state + county, e.g., “06037” for LA County) - Tract: 11 digits (state + county + tract, e.g., “06037101100”) - Block Group: 12 digits (state + county + tract + block group) - Block: 15 digits (state + county + tract + block)
- Example usage:
from siege_utilities.geo import normalize_geoid, construct_geoid, validate_geoid, can_normalize_geoid
# Normalize a GEOID to proper length geoid = normalize_geoid(“6037”, “county”) # Returns “06037”
# Construct GEOID from components geoid = construct_geoid(state=”06”, county=”037”, geography=”county”)
# Validate GEOID format (strict by default — requires leading zeros) is_valid = validate_geoid(“06037”, “county”) # Returns True is_valid = validate_geoid(“6037”, “county”) # Returns False (missing leading zero)
# Check if a value can be normalized to a valid GEOID can_fix = can_normalize_geoid(“6037”, “county”) # Returns True
- class siege_utilities.geo.geoid_utils.GEOIDValidator[source]
Bases:
objectCallable validator for GEOID fields.
Validates that a GEOID is digits-only and the correct length for the given geography level.
- Usage in Django models:
geoid = CharField(validators=[GEOIDValidator(‘tract’)])
- siege_utilities.geo.geoid_utils.normalize_geoid(geoid, geography_level)[source]
Normalize a GEOID to the standard format with proper zero-padding.
- Parameters:
- Returns:
Normalized GEOID string with proper zero-padding
- Raises:
ValueError – If geography level is unknown or GEOID is invalid
- Return type:
Example
>>> normalize_geoid("6037", "county") '06037' >>> normalize_geoid(6, "state") '06' >>> normalize_geoid("6037101100", "tract") '06037101100'
- siege_utilities.geo.geoid_utils.normalize_geoid_column(df, geoid_column, geography_level, inplace=False)[source]
Normalize a GEOID column in a DataFrame.
- Parameters:
df (pandas.DataFrame) – DataFrame containing GEOID column
geoid_column (str) – Name of the GEOID column
geography_level (str) – Geographic level for normalization
inplace (bool) – If True, modify DataFrame in place
- Returns:
DataFrame with normalized GEOID column
- Return type:
- siege_utilities.geo.geoid_utils.construct_geoid(geography, state=None, county=None, tract=None, block_group=None, block=None, place=None)[source]
Construct a GEOID from component parts.
- Parameters:
geography (str) – Target geography level
state (str | None) – State FIPS code (2 digits)
county (str | None) – County FIPS code (3 digits)
tract (str | None) – Census tract code (6 digits)
block_group (str | None) – Block group code (1 digit)
block (str | None) – Block code (4 digits)
place (str | None) – Place code (5 digits)
- Returns:
Constructed GEOID string
- Raises:
ValueError – If required components are missing
- Return type:
Example
>>> construct_geoid("county", state="06", county="037") '06037' >>> construct_geoid("tract", state="06", county="037", tract="101100") '06037101100'
- siege_utilities.geo.geoid_utils.construct_geoid_from_row(row, geography_level)[source]
Construct a GEOID from a DataFrame row containing Census API response columns.
This function handles the column names returned by the Census API.
- Parameters:
row (pandas.Series) – DataFrame row with Census columns (state, county, tract, etc.)
geography_level (str) – Target geography level
- Returns:
Constructed GEOID string
- Return type:
Example
# For a row from Census API response: # {‘state’: ‘06’, ‘county’: ‘037’, ‘tract’: ‘101100’, …} geoid = construct_geoid_from_row(row, ‘tract’) # Returns: ‘06037101100’
- siege_utilities.geo.geoid_utils.parse_geoid(geoid, geography_level)[source]
Parse a GEOID into its component parts.
- Parameters:
- Returns:
Dictionary with component parts (state, county, tract, etc.)
- Return type:
Example
>>> parse_geoid("06037101100", "tract") {'state': '06', 'county': '037', 'tract': '101100'}
- siege_utilities.geo.geoid_utils.extract_parent_geoid(geoid, child_geography, parent_geography)[source]
Extract a parent GEOID from a child GEOID.
Supports extracting state, county, or tract as the parent level. Other parent levels raise ValueError.
- Parameters:
- Returns:
Parent GEOID
- Raises:
ValueError – If parent_geography is not one of the supported parent levels (state, county, tract).
- Return type:
Example
>>> extract_parent_geoid("06037101100", "tract", "county") '06037' >>> extract_parent_geoid("060371011001", "block_group", "state") '06'
- siege_utilities.geo.geoid_utils.validate_geoid(geoid, geography_level, strict=True)[source]
Validate a GEOID format.
Census GEOIDs are always fixed-width, zero-padded strings. A value missing leading zeros (e.g., ‘6037’ instead of ‘06037’) is NOT a valid GEOID — it may be normalizable via normalize_geoid(), but it would fail joins against Census shapefiles.
- Parameters:
- Returns:
True if GEOID is valid, False otherwise
- Return type:
Example
>>> validate_geoid("06037", "county") True >>> validate_geoid("6037", "county") # Missing leading zero False >>> validate_geoid("6037", "county", strict=False) True
- siege_utilities.geo.geoid_utils.can_normalize_geoid(geoid, geography_level)[source]
Check whether a value can be normalized to a valid GEOID.
Unlike validate_geoid(), this accepts values that are shorter than the expected length (e.g., ‘6037’ for county) because they can be zero-padded to a valid GEOID via normalize_geoid().
- Parameters:
- Returns:
True if value can be normalized to a valid GEOID
- Return type:
Example
>>> can_normalize_geoid("6037", "county") True >>> can_normalize_geoid("06037", "county") True >>> can_normalize_geoid("CA037", "county") False >>> can_normalize_geoid("123456", "county") False
- siege_utilities.geo.geoid_utils.validate_geoid_column(df, geoid_column, geography_level, strict=True)[source]
Validate a GEOID column in a DataFrame.
- Parameters:
df (pandas.DataFrame) – DataFrame containing GEOID column
geoid_column (str) – Name of the GEOID column
geography_level (str) – Expected geography level
strict (bool) – If True (default), require exact length match
- Returns:
Boolean Series indicating validity of each GEOID
- Return type:
- siege_utilities.geo.geoid_utils.prepare_geoid_for_join(df, geoid_column, geography_level, output_column=None)[source]
Prepare a GEOID column for joining with another dataset.
This function: 1. Normalizes GEOIDs to standard format 2. Validates GEOIDs 3. Optionally creates a new column for the normalized GEOID
- Parameters:
df (pandas.DataFrame) – DataFrame with GEOID column
geoid_column (str) – Name of the GEOID column
geography_level (str) – Geography level
output_column (str | None) – Name for normalized column (defaults to geoid_column)
- Returns:
DataFrame with normalized GEOID column
- Return type:
- siege_utilities.geo.geoid_utils.geoid_to_slug(geoid, geography_level)[source]
Convert a GEOID to a URL-friendly slug.
Encodes the state FIPS as a lowercase abbreviation and appends the remaining GEOID components separated by hyphens.
- Parameters:
- Returns:
URL-friendly slug string
- Return type:
Example
>>> geoid_to_slug("06037101100", "tract") 'ca-037-tract-101100' >>> geoid_to_slug("06", "state") 'ca' >>> geoid_to_slug("0614", "cd") 'ca-cd-14'
- siege_utilities.geo.geoid_utils.slug_to_geoid(slug)[source]
Convert a slug back to a GEOID.
Inverse of geoid_to_slug(). Parses the state abbreviation and component parts from the slug.
- Parameters:
slug (str) – URL-friendly slug string
- Returns:
Full GEOID string
- Raises:
ValueError – If slug cannot be parsed
- Return type:
Example
>>> slug_to_geoid("ca-037-tract-101100") '06037101100' >>> slug_to_geoid("ca") '06'
- siege_utilities.geo.geoid_utils.find_geoid_column(df)[source]
Find the GEOID column in a DataFrame.
Looks for common GEOID column names used in Census data.
- Parameters:
df (pandas.DataFrame) – DataFrame to search
- Returns:
Name of GEOID column if found, None otherwise
- Return type:
str | None
- exception siege_utilities.geo.geocoding.GeocodingError[source]
Bases:
RuntimeErrorRaised when Nominatim geocoding fails for reasons other than ‘no match’.
Covers network timeouts (after retries exhausted), service errors, parse errors, and other unexpected failures from the geocoder. Distinct from a legitimate “no result found” outcome, which still returns
None. Use__cause__to inspect the underlying exception.Mirrors
siege_utilities.geo.providers.census_geocoder.CensusGeocodeError.
- siege_utilities.geo.geocoding.get_country_name(country_code)[source]
Get the full country name from a country code.
- Parameters:
country_code – Two-letter country code (e.g., ‘us’, ‘gb’, ‘ca’)
- Returns:
Full country name or the code if not found
- Return type:
- siege_utilities.geo.geocoding.get_country_code(country_name)[source]
Get the country code from a country name.
- Parameters:
country_name – Full country name (e.g., ‘United States’, ‘Canada’)
- Returns:
Two-letter country code, or None if not found.
- Return type:
str | None
- siege_utilities.geo.geocoding.list_countries()[source]
Get a list of all available countries with their codes.
- Returns:
Dictionary mapping country codes to country names
- Return type:
- siege_utilities.geo.geocoding.concatenate_addresses(street=None, city=None, state_province_area=None, postal_code=None, country=None)[source]
Concatenate address components into a single string suitable for geocoding. Returns a properly formatted address string.
- siege_utilities.geo.geocoding.get_coordinates(query_address, country_codes=None, max_retries=3, server_url=None)[source]
Get coordinates (latitude, longitude) for an address using Nominatim.
- Parameters:
query_address – The address to geocode
country_codes – Optional country code filter (defaults to US)
max_retries – Maximum number of retry attempts
server_url – Optional custom Nominatim server URL (e.g., “http://nominatim.nominatim.svc.cluster.local” for self-hosted). Defaults to None (public OSM Nominatim).
- Returns:
(latitude, longitude), or
Nonewhen Nominatim returned no match for the address (a legitimate non-error outcome).- Return type:
- Raises:
ValueError – If
query_addressis empty or None.GeocodingError – If geocoding failed due to a network, service, or parse error — i.e., the geocoder could not even attempt to match. Wraps the underlying exception via
__cause__.
- siege_utilities.geo.geocoding.use_nominatim_geocoder(query_address, id=None, country_codes=None, max_retries=3, server_url=None)[source]
Geocode an address using Nominatim with proper rate limiting and error handling. Returns the result as a JSON string for Spark UDF compatibility.
- Parameters:
query_address – The address to geocode
id – An identifier for tracking
country_codes – Optional country code filter (defaults to US)
max_retries – Number of retry attempts for transient errors
server_url – Optional custom Nominatim server URL (e.g., “http://nominatim.nominatim.svc.cluster.local” for self-hosted). Defaults to None (public OSM Nominatim with rate limiting). When set, rate limiting is skipped (self-hosted servers don’t require it).
- Returns:
JSON string of the geocoding result, or
Nonewhen Nominatim returned no match for the address (a legitimate non-error outcome).- Raises:
ValueError – If
query_addressis empty or None.GeocodingError – If geocoding failed due to a network, service, or parse error after retries (i.e., the geocoder could not even attempt to match). Wraps the underlying geopy exception via
__cause__.
- siege_utilities.geo.geocoding.geocode_with_nominatim_public(address, country_codes=None)[source]
Geocode a single address using the public Nominatim service.
Thin wrapper around
get_coordinates()that forces the public endpoint (no customserver_url).- Parameters:
address – Address string to geocode.
country_codes – Optional country code filter (defaults to US).
- Returns:
(lat, lon)tuple, orNoneif no match found.- Raises:
ValueError – If address is empty.
GeocodingError – On network/service failure.
- siege_utilities.geo.geocoding.geocode_addresses_with_nominatim(addresses, country_codes=None, max_retries=3, server_url=None)[source]
Batch-geocode a list of addresses using Nominatim.
Calls
get_coordinates()for each address sequentially with Nominatim’s built-in rate limiting (1 s for public, 50 ms for self-hosted).- Parameters:
addresses – Iterable of address strings.
country_codes – Optional country code filter (defaults to US).
max_retries – Retry attempts per address.
server_url – Optional custom Nominatim server URL.
- Returns:
List of
{"address": str, "lat": float, "lon": float}dicts. Addresses that returned no match havelatandlonset toNone. Addresses that raisedGeocodingErrorare logged and included withNonecoordinates.
- class siege_utilities.geo.geocoding.NominatimGeoClassifier[source]
Bases:
objectA classifier for geocoding results using Nominatim. Provides methods to categorize and analyze geocoding results.
- get_place_ranks_by_label(label)[source]
Reverse lookup on
place_rank_dict: label → matching rank IDs.Nominatim returns an integer
place_rankper result (0=country … 10=building). This helper goes the other direction so callers can writefilter(rank=cls.get_place_ranks_by_label("City"))without hard-coding the integer.Returns
[](notNone) when the label is unknown so the caller canin/ iterate without a None-check.
- get_importance_threshold_by_label(label)[source]
Reverse lookup on
importance_dict: label → importance threshold.Nominatim’s
importancefield is a float in [0, 1] that mixes Wikipedia hit-count, place type, and population. We bucket those floats into ordinal labels (Country/State/ …) and this method returns the float threshold that corresponds to a label — useful when filtering a Nominatim result set programmatically.Returns
Nonewhen no label matches; callers should treat that as “don’t filter on importance.”
- to_json()[source]
Serialize the classifier’s lookup tables to a JSON string.
Used to persist customised rank/importance overrides between runs or to ship a classifier configuration to a worker process. The result round-trips through
from_json().
- from_json(json_string)[source]
Replace the classifier’s lookup tables from a JSON string.
- Parameters:
json_string – JSON produced by
to_json().
Inverse of
to_json()— overwritesplace_rank_dictandimportance_dictin place. Unknown top-level keys are ignored; missing keys leave the corresponding table empty rather than raising, so a partial payload doesn’t poison the classifier.
- siege_utilities.geo.geocoding.validate_geocode_data_pandas(df, lat_col, lon_col, crs=None)[source]
Filter rows with invalid geographic coordinates.
Pandas equivalent of
spark_utils.validate_geocode_data().- Parameters:
df (pandas.DataFrame) – Input DataFrame.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
crs – Optional CRS (pyproj CRS, EPSG int, or string). When supplied, the valid coordinate bounds are derived from the CRS area of use. Defaults to WGS84 (-90..90, -180..180).
- Returns:
DataFrame with only rows whose coordinates fall within the valid range.
- Return type:
- siege_utilities.geo.geocoding.mark_valid_geocode_data_pandas(df, lat_col, lon_col, output_col='is_valid', crs=None)[source]
Add a boolean validity column without removing rows.
Pandas equivalent of
spark_utils.mark_valid_geocode_data().- Parameters:
df (pandas.DataFrame) – Input DataFrame.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
output_col (str) – Name of the boolean column to add.
crs – Optional CRS (pyproj CRS, EPSG int, or string). When supplied, the valid coordinate bounds are derived from the CRS area of use. Defaults to WGS84 (-90..90, -180..180).
- Returns:
Copy of df with output_col appended.
- Return type:
- class siege_utilities.geo.geocoding.SpatiaLiteCache[source]
Bases:
objectSpatiaLite-backed local cache for geocoding results and boundary lookups.
Stores: - geocode results: address_hash → (lat, lon, WKT point, source, timestamp) - boundary lookups: geoid → (point WKT, boundary WKT, vintage_year) - crosswalk mappings: (source_geoid, target_geoid) → weight
Spatial index on point geometry enables bounding-box queries. The database file is portable across machines.
Usage:
cache = SpatiaLiteCache() cache.put_geocode("123 Main St, Springfield, IL 62701", 39.7817, -89.6501, source="nominatim") result = cache.get_geocode("123 Main St, Springfield, IL 62701")
- put_geocode(address, latitude, longitude, source='nominatim', raw_response=None)[source]
Store a geocoding result. Returns the address hash.
- get_geocode_or_fetch(address, country_codes=None, server_url=None)[source]
Look up cache, falling back to Nominatim if not cached.
Returns
Nonewhen the cache misses AND Nominatim has no match for the address. RaisesGeocodingErrorwhen Nominatim fails (network/service/parse) — callers that want fail-open behavior must catch it explicitly. RaisesValueErrorfor an empty/Noneaddress.
- get_geocodes_in_bbox(lat_min, lat_max, lon_min, lon_max)[source]
Return all cached geocodes within a bounding box.
- put_boundary(geoid, vintage_year, point_wkt=None, boundary_wkt=None, source='tiger')[source]
Cache a boundary lookup result.
- put_crosswalk(source_geoid, target_geoid, weight=1.0, source='census')[source]
Cache a crosswalk mapping.
Grid-agnostic dispatch wrappers over H3 and S2.
Lets consumer code call index_points() / index_polygon()
without committing to one grid system at the call site. The wrappers
infer the grid from which keyword arguments are present, with explicit
override via grid="h3" or grid="s2".
Inference rules (applied in order):
If
grid=is passed → that wins.Else if any S2-only kwarg is present (
max_cells,min_level,max_level,level) → S2.Else if
resolution=is present → H3.Else → H3 (the more common viz path).
Mixing resolution= with S2-only kwargs in one call raises
ValueError rather than guessing — the caller should pass
grid= explicitly to disambiguate.
These wrappers exist to keep reporting/ and consumer modules
backend-agnostic; they don’t add functionality beyond what the underlying
h3_utils / s2_utils modules already do.
- siege_utilities.geo.grids.Grid
Recognised
grid=argument values.Nonemeans “infer”.
- siege_utilities.geo.grids.index_points(df, lat_col, lon_col, *, grid=None, resolution=None, level=None, engine=None, **kwargs)[source]
Compute a grid cell ID for each row in df.
Dispatches to
h3_index_points()ors2_index_points()based on the inference rules. Passresolution=for H3 (default 8) orlevel=for S2 (default 12). Extra keyword arguments are forwarded to the underlying function (e.g.as_token=Falsefor S2).
- siege_utilities.geo.grids.index_polygon(geometry, *, grid=None, resolution=None, level=None, max_cells=None, min_level=None, max_level=None, refine=True)[source]
Cover a polygon with grid cells.
With H3: returns a
setof hex IDs at the requested resolution (uniform).With S2: if
max_cellsis provided (or any ofmin_level,max_level), returns alistof int64 cell IDs froms2_region_cover()(variable-resolution coverage with a cell- count budget). Otherwise returns asetof int64 cell IDs at a singlelevelfroms2_index_polygon().The shape of the return value (set vs list) intentionally signals which mode you got. For SQL-friendly cell-id ranges, see
s2_cells_to_ranges().
- siege_utilities.geo.grids.infer_grid(grid, kwargs)[source]
Resolve
grid=per the documented inference rules.Returns
"h3"or"s2"; raisesValueErrorif the inputs contradict the chosen grid. Pure function — does not mutate kwargs.
H3 hexagonal hierarchical spatial index utilities.
Provides lightweight spatial join approximation using Uber’s H3 indexing system. This serves as a “geo-lite” fallback when full GeoPandas point-in-polygon operations are too expensive or when an approximate spatial join is acceptable.
Requires: h3>=4.0 (primary), with v3 fallback support.
- siege_utilities.geo.h3_utils.h3_hex_to_boundary(hex_id)[source]
Return the boundary coordinates for an H3 hex cell.
- Parameters:
hex_id (str) – H3 hex ID string.
- Returns:
list of (lat, lng) tuples forming the hex boundary (closed ring).
- Raises:
ImportError – If h3 is not installed.
- Return type:
- siege_utilities.geo.h3_utils.h3_index_points(df, lat_col, lon_col, resolution=8)[source]
Compute H3 hex index for each point in a DataFrame.
- Parameters:
df (pandas.DataFrame) – DataFrame with latitude and longitude columns.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
resolution (int) – H3 resolution (0-15). Default 8 (~0.74 km^2 hexes).
- Returns:
pd.Series of H3 hex ID strings, indexed like the input DataFrame.
- Raises:
ImportError – If h3 is not installed.
ValueError – If resolution is out of range or columns are missing.
- Return type:
- siege_utilities.geo.h3_utils.h3_index_polygon(geometry, resolution=8)[source]
Return the set of H3 hexes covering a polygon geometry.
Accepts a Shapely Polygon/MultiPolygon or a GeoJSON-like dict.
- Parameters:
geometry – A Shapely Polygon/MultiPolygon, or a GeoJSON-like dict with
typeandcoordinateskeys.resolution (int) – H3 resolution (0-15). Default 8.
- Returns:
set of H3 hex ID strings that cover the polygon.
- Raises:
ImportError – If h3 is not installed.
ValueError – If resolution is out of range.
TypeError – If geometry type is unsupported.
- Return type:
- siege_utilities.geo.h3_utils.h3_resolution_for_admin_level(level)[source]
Suggest an H3 resolution whose average hex area matches a US admin level.
Useful when you want to bin point data into hexes that are roughly the same scale as a familiar admin unit (e.g. “give me hexes the size of counties”). The mapping is deliberately approximate — admin polygons vary by orders of magnitude within a single level, so this is a starting point, not a precision tool.
- Recognised levels (case-insensitive):
state,county,zcta,tract,block_group,block.
Common aliases are accepted (
zip→zcta,bg→block_group, plurals, etc.).- Parameters:
level (str) – Admin geography level name.
- Returns:
H3 resolution (0-15) closest in average hex area to the average polygon area at the requested admin level.
- Return type:
- Raises:
ImportError – If h3 is not installed.
ValueError – If
levelis not recognised.
- siege_utilities.geo.h3_utils.h3_resolution_for_area(target_area_km2)[source]
Suggest the H3 resolution whose hex area best matches a target area.
- Parameters:
target_area_km2 (float) – Target hex area in square kilometres.
- Returns:
H3 resolution (0-15) whose average hex area is closest to the target. Comparison is done in log-space for better matching across orders of magnitude.
- Return type:
- Raises:
ImportError – If h3 is not installed.
ValueError – If target_area_km2 is not positive.
- siege_utilities.geo.h3_utils.h3_spatial_join(points_df, polygons_gdf, lat_col, lon_col, resolution=8, polygon_id_col=None)[source]
Join points to polygons via H3 hex matching (approximate point-in-polygon).
This is a lightweight alternative to GeoPandas
sjoin. Each polygon is decomposed into H3 hexes, and each point is assigned to the hex it falls in. Points and polygons sharing a hex are joined.Note: This is an approximation. Points near polygon boundaries may be misclassified due to hex granularity.
- Parameters:
points_df (pandas.DataFrame) – DataFrame with latitude/longitude columns.
polygons_gdf – GeoDataFrame (or any object with a
geometrycolumn and an iterable of rows). Each geometry must be a Polygon or MultiPolygon.lat_col (str) – Name of the latitude column in
points_df.lon_col (str) – Name of the longitude column in
points_df.resolution (int) – H3 resolution (0-15). Default 8.
polygon_id_col (str | None) – Optional column in
polygons_gdfto use as the polygon identifier. If None, the index is used.
- Returns:
pd.DataFrame with the original point columns plus polygon attributes for matched rows. Unmatched points are excluded.
- Raises:
ImportError – If h3 is not installed.
- Return type:
S2 cell-grid utilities.
Sibling of siege_utilities.geo.h3_utils. Where H3 gives you uniform
hexagonal cells at a single resolution, S2 gives you square cells with
integer IDs that sort by spatial proximity. The same lat/lon-to-cell
operations are mirrored across both modules; the S2-specific functions
exposed here (cell-id ↔ uint64, bbox-to-cells, parent/children, region
cover with cell-count budget, range-bound generation for SQL) are the
reason to reach for S2 rather than H3.
Why both
Use case |
Reach for |
Why |
|---|---|---|
Density choropleth, hex-bins |
H3 |
Uniform cell area |
K-nearest-neighbors via k-ring |
H3 |
Uniform neighbor distances |
Spatial primary key in DB |
S2 |
Sortable int64 IDs |
Bbox / range queries in SQL |
S2 |
Cell-id ranges = bboxes |
Hierarchical rollups |
S2 |
4 children per cell, clean |
Apple Photos / iOS data |
S2 |
Native to that ecosystem |
Polygon coverage limitation
The pure-Python s2sphere package supports LatLngRect (bounding
boxes) but not the full Loop / Polygon types from C++ S2.
s2_index_polygon() covers the polygon’s bounding box and
optionally post-filters cells against the actual geometry using shapely.
For a heavily concave polygon (e.g. Idaho), the bbox cover is loose; the
post-filter step (default on) trims cells whose centers fall outside.
Requires: s2sphere>=0.2.5.
- siege_utilities.geo.s2_utils.s2_index_points(df, lat_col, lon_col, level=12, *, as_token=True)[source]
Compute the S2 cell ID for each point.
- Parameters:
df (pd.DataFrame) – DataFrame with latitude / longitude columns.
lat_col (str) – Name of the latitude column.
lon_col (str) – Name of the longitude column.
level (int) – S2 level (0–30). Default 12 (~5.8 km² per cell).
as_token (bool) – If True (default), return the cell as a 16-char hex token string (compact, human-readable, JSON-safe). If False, return the int64 cell ID (sortable, ideal for database keys). Both round-trip via the
s2_cell_id_to_token()/s2_token_to_cell_id()helpers.
- Returns:
"s2_index".- Return type:
pd.Seriesindexed like the input. Name- Raises:
ImportError – If s2sphere is not installed.
ValueError – If level is out of range or columns are missing.
- siege_utilities.geo.s2_utils.s2_index_polygon(geometry, level=12, *, refine=True)[source]
Return the set of S2 cells at level covering geometry.
Single-level coverage — symmetric with
h3_index_polygon(). For variable-resolution coverage with a cell-count budget, sees2_region_cover().Because the pure-Python
s2spherelacks polygon support, this function covers the polygon’s bounding box and (by default) post- filters the result to cells whose centers fall inside the actual geometry, using shapely. Setrefine=Falseto skip the filter when the input is a near-rectangle (e.g. a county) and you want the bbox cover unchanged.- Parameters:
- Returns:
setof int64 cell IDs.- Raises:
ImportError – If s2sphere (or shapely, when refine=True) is missing.
ValueError – If level is out of range.
TypeError – If geometry can’t be converted to a shapely shape.
- Return type:
- siege_utilities.geo.s2_utils.s2_region_cover(geometry, *, min_level=0, max_level=30, max_cells=100)[source]
Cover geometry’s bounding box with at most max_cells S2 cells.
The S2-specific operation: returns a mixed-resolution set of cells that together cover the bounding box, preferring coarse cells in the interior and fine cells along boundaries. The output is what you want for a database-side spatial-index lookup.
Use
s2_cells_to_ranges()to turn the result into(min, max)int64 ranges suitable for a SQLBETWEENclause.- Parameters:
- Returns:
A list of int64 cell IDs at varying levels, ordered as the coverer produced them (sortable but not sorted).
- Return type:
- siege_utilities.geo.s2_utils.s2_spatial_join(points_df, polygons_gdf, lat_col, lon_col, level=12, polygon_id_col=None)[source]
Join points to polygons via S2 cell matching (approximate PiP).
Same shape as
h3_spatial_join(). Points and polygons sharing a cell are joined; polygons are decomposed into S2 cells vias2_index_polygon(). First-polygon-wins on overlap.
- siege_utilities.geo.s2_utils.s2_cell_to_boundary(cell_id)[source]
Return the 4 vertex coordinates of a cell as
(lat, lng)tuples.Accepts an int64 cell ID or a hex token string.
- siege_utilities.geo.s2_utils.s2_level_for_area(target_area_km2)[source]
Suggest the S2 level whose average cell area matches target_area_km2.
Comparison is in log-space, mirroring
h3_resolution_for_area().
- siege_utilities.geo.s2_utils.s2_level_for_admin_level(level)[source]
Suggest an S2 level whose average cell area matches a US admin level.
Same admin levels recognised by
h3_resolution_for_admin_level()(state / county / zcta / tract / block_group / block, with the usual aliases). Returns the S2 level that’s closest in log-space.
- siege_utilities.geo.s2_utils.s2_kring(cell_id, k=1)[source]
Return cell IDs within k steps of cell_id on the same level.
The k-ring is approximate for S2 (squares don’t tile uniformly); we walk neighbors recursively. For exact k-ring on a uniform grid use
h3_kring()instead.
- siege_utilities.geo.s2_utils.s2_distance(a, b)[source]
Edge-step distance between two S2 cells at the same level.
Counts cell-boundary crossings via repeated neighbor expansion until b is reached.
O(distance); use only for small radii. For large distances, switch to great-circle distance on the cell centers.- Return type:
- siege_utilities.geo.s2_utils.s2_parent(cell_id, level)[source]
Return the ancestor cell at level (must be ≤ current level).
- siege_utilities.geo.s2_utils.s2_children(cell_id)[source]
Return the four immediate children of a cell.
Raises if called on a leaf cell (level 30).
- siege_utilities.geo.s2_utils.s2_cell_id_to_uint64(cell_or_token)[source]
Return the int64 form of a cell, accepting either a token or an id.
Useful when receiving a token string (e.g.
"88891b") and needing the integer for a database column. Accepts any integer-like type (Python int, numpy.int64, numpy.uint64) for round-trip convenience.- Return type:
- siege_utilities.geo.s2_utils.s2_uint64_to_cell_id(n)[source]
Return an
s2sphere.CellIdfrom an int64 value.- Parameters:
n (int)
- siege_utilities.geo.s2_utils.s2_cell_id_to_token(cell_id)[source]
Return the compact hex token form of a cell.
Tokens are stable, JSON-safe, and 16 characters or fewer.
- Return type:
- siege_utilities.geo.s2_utils.s2_token_to_cell_id(token)[source]
Parse a hex token back to its int64 cell ID.
- siege_utilities.geo.s2_utils.s2_bbox_to_cells(min_lat, min_lon, max_lat, max_lon, *, max_level=18, max_cells=64)[source]
Cover an axis-aligned bounding box with at most max_cells cells.
Convenience wrapper around
s2_region_cover()for the common case of a map-viewport query. Returns int64 cell IDs at varying levels.
- siege_utilities.geo.s2_utils.s2_cells_to_ranges(cell_ids)[source]
Convert cell IDs to
(range_min, range_max)pairs for SQL.Each S2 cell, regardless of level, has a contiguous range of leaf cell IDs that lie within it:
[cell.range_min, cell.range_max]. Pre-computing those ranges lets a SQL query test “is point’s cell inside this region” with a simpleWHERE leaf_cell_id BETWEEN min AND max(one comparison per cell in the cover, no spatial library needed at query time).
Isochrone utilities with open-source-first providers and custom server support.
This module provides both a function-based API (backward-compatible) and a
provider-based OOP architecture via IsochroneProvider and its concrete
subclasses OpenRouteServiceProvider and ValhallaProvider.
Use get_provider() to obtain a configured provider instance by name.
- exception siege_utilities.geo.isochrones.IsochroneError[source]
Bases:
ExceptionBase exception for isochrone operations.
- exception siege_utilities.geo.isochrones.IsochroneNetworkError[source]
Bases:
IsochroneErrorNetwork failure (timeout, connection refused, DNS resolution).
- class siege_utilities.geo.isochrones.IsochroneProvider[source]
Bases:
ABCAbstract base class for isochrone providers.
Subclasses must implement
fetch(),validate_config(), and theprovider_nameproperty.- abstractmethod fetch(lat, lon, time_minutes, profile='driving-car')[source]
Fetch an isochrone as a GeoJSON dict.
- Parameters:
- Returns:
A GeoJSON dict (typically a FeatureCollection).
- Return type:
- exception siege_utilities.geo.isochrones.IsochroneProviderError[source]
Bases:
IsochroneErrorProvider returned an HTTP error or an unparseable response.
- class siege_utilities.geo.isochrones.IsochroneRequest[source]
Bases:
TypedDictStructured request definition returned by
build_isochrone_request().
- class siege_utilities.geo.isochrones.OpenRouteServiceProvider[source]
Bases:
IsochroneProviderConcrete
IsochroneProviderwrapping the OpenRouteService API.- Parameters:
api_key – ORS API key. Required for the hosted service; optional for self-hosted instances.
base_url – Root URL of the ORS server. Defaults to
DEFAULT_ORS_BASE_URL.timeout_seconds – HTTP timeout in seconds (default 30).
max_retries – Maximum retry attempts for transient failures (default 3).
- validate_config()[source]
Check that the provider is configured.
For the hosted ORS service (default URL), an API key is required. For self-hosted instances, the base URL must be non-empty.
- Return type:
- class siege_utilities.geo.isochrones.ValhallaProvider[source]
Bases:
IsochroneProviderConcrete
IsochroneProviderwrapping a Valhalla server.- Parameters:
base_url – Root URL of the Valhalla server. Defaults to
DEFAULT_VALHALLA_BASE_URL.timeout_seconds – HTTP timeout in seconds (default 30).
max_retries – Maximum retry attempts for transient failures (default 3).
- siege_utilities.geo.isochrones.build_isochrone_request(latitude, longitude, travel_time_minutes, *, provider='openrouteservice', base_url=None, profile='driving-car', api_key=None, extra_params=None)[source]
Build a provider-specific HTTP request definition for an isochrone call.
This supports open-source providers and self-hosted/custom endpoints.
- Parameters:
latitude (float) – Center latitude (-90 to 90).
longitude (float) – Center longitude (-180 to 180).
travel_time_minutes (int) – Travel-time contour in minutes (must be > 0).
provider (str) –
"openrouteservice"(or"ors") or"valhalla".base_url (str | None) – Custom server root for self-hosted providers.
profile (str) – Travel profile (
"driving-car","cycling-regular","foot-walking", etc.).api_key (str | None) – Optional API key (commonly used for hosted ORS).
extra_params (Mapping[str, Any] | None) – Provider-specific extra request fields merged into the JSON body (Valhalla) or query params (ORS).
- Returns:
An
IsochroneRequestdict withprovider,method,url,headers,params, andjsonkeys.- Raises:
ValueError – If inputs are out of range or provider is unknown.
- Return type:
- siege_utilities.geo.isochrones.get_isochrone(latitude, longitude, travel_time_minutes, *, provider='openrouteservice', base_url=None, profile='driving-car', api_key=None, timeout_seconds=30, extra_params=None, max_retries=3)[source]
Fetch an isochrone GeoJSON response.
- Parameters:
latitude (float) – Center latitude.
longitude (float) – Center longitude.
travel_time_minutes (int) – Travel-time contour in minutes.
provider (str) –
"openrouteservice"(default) or"valhalla".base_url (str | None) – Custom server root for self-hosted providers.
profile (str) – Travel profile (
"driving-car","cycling-regular","foot-walking", etc.).api_key (str | None) – Optional API key (commonly used for hosted ORS).
timeout_seconds (int) – HTTP timeout in seconds (default 30).
extra_params (Mapping[str, Any] | None) – Provider-specific extra request fields.
max_retries (int) – Maximum number of attempts for transient failures (default 3). Set to 1 to disable retries.
- Returns:
Provider response parsed as JSON (typically a GeoJSON FeatureCollection).
- Raises:
IsochroneNetworkError – On timeout, connection, or DNS failure.
IsochroneProviderError – On HTTP error or unparseable response.
ValueError – If inputs are out of range or provider is unknown.
- Return type:
- siege_utilities.geo.isochrones.get_provider(name='ors', **kwargs)[source]
Factory function returning a configured
IsochroneProvider.- Parameters:
- Returns:
A configured provider instance.
- Raises:
ValueError – If name is not a recognised provider.
- Return type:
- siege_utilities.geo.isochrones.isochrone_to_geodataframe(isochrone_geojson, *, crs=None)[source]
Convert an isochrone GeoJSON object to a GeoDataFrame.
Requires
geopandas. Install withpip install siege_utilities[geo].GeoJSON is always ingested as EPSG:4326 (the GeoJSON spec mandates WGS 84). If crs differs from EPSG:4326 the result is reprojected to the requested CRS before returning.
- Parameters:
isochrone_geojson (Mapping[str, Any]) – A GeoJSON dict (typically a FeatureCollection returned by
get_isochrone()).crs (str | None) – Target coordinate reference system. Defaults to
get_default_crs()(initially"EPSG:4326"). Pass any string accepted bypyproj.
- Returns:
A GeoDataFrame with one row per feature in the requested crs.
- Raises:
ImportError – If geopandas is not installed.
- Return type:
gpd.GeoDataFrame
GEOID validators for Django models and standalone use.
Provides both Django field validators and standalone validation functions for Census GEOID formats at various geographic levels.
- Example usage (standalone):
from siege_utilities.geo.validators import is_valid_state_fips, is_valid_tract_geoid
is_valid_state_fips(“06”) # True (California) is_valid_tract_geoid(“06037101100”) # True
- Example usage (Django):
from siege_utilities.geo.validators import StateFIPSValidator
- class MyModel(models.Model):
state_fips = models.CharField(max_length=2, validators=[StateFIPSValidator()])
- class siege_utilities.geo.validators.BlockGroupGEOIDValidator[source]
Bases:
objectDjango validator for 12-digit block group GEOIDs.
- message = 'Enter a valid 12-digit Census block group GEOID.'
- code = 'invalid_block_group_geoid'
- class siege_utilities.geo.validators.CountyFIPSValidator[source]
Bases:
objectDjango validator for 5-digit county FIPS codes.
- message = 'Enter a valid 5-digit county FIPS code (2-digit state + 3-digit county).'
- code = 'invalid_county_fips'
- class siege_utilities.geo.validators.StateFIPSValidator[source]
Bases:
objectDjango validator for 2-digit state FIPS codes.
- message = 'Enter a valid 2-digit state FIPS code.'
- code = 'invalid_state_fips'
- class siege_utilities.geo.validators.TractGEOIDValidator[source]
Bases:
objectDjango validator for 11-digit tract GEOIDs.
- message = 'Enter a valid 11-digit Census tract GEOID.'
- code = 'invalid_tract_geoid'
- siege_utilities.geo.validators.is_valid_block_group_geoid(value)[source]
Validate a 12-digit block group GEOID (state + county + tract + block group).
- siege_utilities.geo.validators.is_valid_county_fips(value)[source]
Validate a 5-digit county FIPS code (2-digit state + 3-digit county).
- siege_utilities.geo.validators.is_valid_state_fips(value)[source]
Validate a 2-digit state FIPS code against known codes.
- siege_utilities.geo.validators.is_valid_tract_geoid(value)[source]
Validate an 11-digit tract GEOID (2-digit state + 3-digit county + 6-digit tract).
NCES Urban-Centric Locale Classification for siege_utilities.
Implements the National Center for Education Statistics (NCES) 12-code locale classification system. Classifies arbitrary geographic points and polygons into City/Suburb/Town/Rural categories with size/distance subtypes.
Two modes of operation: 1. Pre-computed: Uses NCES-published locale boundary shapefiles (fastest) 2. On-the-fly: Computes from Census UA/UC/Place inputs (any year)
See docs/design/nces-locale-classification.md for full algorithm description.
- class siege_utilities.geo.locale.LocaleCode[source]
Bases:
objectAn NCES 12-code locale classification result.
- classmethod from_code(code)[source]
Construct a
LocaleCodefrom an integer code (11-43).- Raises:
ValueError – If the code is not a valid NCES locale code.
- Parameters:
code (int)
- Return type:
- class siege_utilities.geo.locale.LocaleIndex[source]
Bases:
objectPrecomputed GEOID → LocaleCode mapping for fast lookup.
Built from any data source that pairs GEOIDs with NCES locale codes (e.g., NCES district data, school locations, or Census tract assignments).
- get(geoid)[source]
- Parameters:
geoid (str)
- Return type:
LocaleCode | None
- class siege_utilities.geo.locale.LocaleType[source]
Bases:
IntEnumNCES major locale types.
- CITY = 1
- SUBURB = 2
- TOWN = 3
- RURAL = 4
- __new__(value)
- class siege_utilities.geo.locale.NCESLocaleClassifier[source]
Bases:
objectClassify geographic points and polygons into NCES locale codes.
Uses Census TIGER spatial data (Urbanized Areas, Urban Clusters, Places) and population thresholds to implement the NCES urban-centric locale classification algorithm (12-code system).
The classifier can be initialized with pre-loaded GeoDataFrames (for testing or custom data) or constructed from Census downloads via the
from_census_yearandfrom_nces_boundariesfactory methods.- Parameters:
urbanized_areas – GeoDataFrame of UA polygons (pop >= 50,000). Must contain a
geometrycolumn and a population field.urban_clusters – GeoDataFrame of UC polygons (pop 2,500-49,999). Must contain a
geometrycolumn. May beNonefor 2020+ Census data where UCs were eliminated.principal_cities – GeoDataFrame of Place polygons that are designated principal cities of CBSAs.
place_populations – Dict mapping place identifiers to population counts. Used to classify City subtypes (Large/Midsize/Small).
ua_populations – Optional dict mapping UA identifiers to populations. Used to classify Suburb subtypes. If
None, reads from theurbanized_areasGeoDataFrame attributes.projection_crs – EPSG code for distance computations. Defaults to
settings.PROJECTION_CRS(2163, US National Atlas Equal Area). Override for Alaska (3338) or Hawaii (26963).
- __init__(urbanized_areas, urban_clusters, principal_cities, place_populations, ua_populations=None, projection_crs=None)[source]
- property locale_index: LocaleIndex | None
- classify_geoid(geoid)[source]
Fast-path locale lookup by Census GEOID.
Returns the precomputed locale code if the GEOID is in the index, or
Noneif no index is set or the GEOID is not found. Useclassify_geoid_or_point()for automatic fallback to spatial classification.- Parameters:
geoid (str)
- Return type:
LocaleCode | None
- classify_geoid_or_point(geoid, lon, lat)[source]
Classify by GEOID if available, falling back to spatial.
- Parameters:
- Return type:
- classify_point(lon, lat)[source]
Classify a single geographic point.
Implements the NCES decision tree: 1. Spatial join against Urbanized Areas 2. If in UA: check principal city → City or Suburb 3. If not in UA: check Urban Cluster → Town (by distance to UA) 4. If not in UA/UC → Rural (by distance to UA and UC)
- Parameters:
- Returns:
A
LocaleCodedescribing the point’s NCES classification.- Return type:
- classify_points(gdf, geometry_col='geometry')[source]
Bulk classify a GeoDataFrame of points.
Adds columns:
locale_code(int),locale_category(str),locale_subcategory(str),locale_label(str).
- classify_polygon(polygon, method='area_weighted')[source]
Classify a polygon by locale distribution.
- Parameters:
- Returns:
{"locale_code": 21, "locale_label": "..."}Ifmethod="distribution"or"area_weighted": dict of{code: fraction}pairs summing to ~1.0.- Return type:
If
method="majority"- Raises:
ValueError – If method is not one of the valid options.
- classify_polygons(gdf, method='majority', geometry_col='geometry')[source]
Bulk classify a GeoDataFrame of polygons.
For
method="majority", adds columns:locale_code(int),locale_label(str).For
method="distribution"or"area_weighted", adds alocale_distributioncolumn containing per-row dicts of{code: fraction}pairs.- Parameters:
- Returns:
The input GeoDataFrame with locale columns added.
- Raises:
ValueError – If method is not one of the valid options.
- Return type:
- classmethod from_census_year(year=2020, cache_dir=None, projection_crs=None)[source]
Construct a classifier from Census TIGER downloads.
Downloads UA/UC (uac), Place, and CBSA shapefiles for the given year, identifies principal cities from population thresholds, and loads population estimates from shapefile attributes.
For 2020+ Census data where Urban Clusters were eliminated, this method derives a UC-equivalent set by filtering urban areas with population < 50,000 (the original UC threshold). This ensures Town codes (31-33) remain reachable.
Principal cities are identified by population threshold (>= 25,000), not from the OMB CBSA delineation file. This is a best-effort approximation. For precise NCES-matching classification, use
from_nces_boundaries()when available.- Parameters:
- Returns:
An initialized
NCESLocaleClassifier.- Raises:
ImportError – If geopandas is not available.
- Return type:
- classmethod from_nces_boundaries(year=2023, cache_dir=None, projection_crs=None)[source]
Construct a classifier from NCES pre-computed locale boundary shapefiles.
NCES publishes locale territory polygons that are already classified with locale codes (11-43). This is faster than computing from Census inputs but limited to years NCES has published.
The downloaded boundaries are split into: - Urbanized Area proxies: City (11-13) + Suburb (21-23) territories - Urban Cluster proxies: Town (31-33) territories - Principal city proxies: City (11-13) territories only
The classifier then works identically to the Census-based one.
- Parameters:
- Returns:
An initialized
NCESLocaleClassifier.- Return type:
- siege_utilities.geo.locale.locale_from_code(code)[source]
Fast lookup of a pre-built
LocaleCodeby integer code.Returns the pre-built constant when possible, avoiding dataclass construction.
- Parameters:
code (int)
- Return type:
Runtime capability detection for the geo subsystem.
Reports which optional spatial packages are available so callers can adapt gracefully (e.g. fall back to geo-lite operations when geopandas is absent).
- siege_utilities.geo.capabilities.geo_capabilities()[source]
Detect available spatial packages and report the active tier.
Returns a dict with boolean flags for each optional package and a
tierstring ("geodjango","geo","geo-lite", or"none").>>> caps = geo_capabilities() >>> caps["tier"] in ("geodjango", "geo", "geo-lite", "none") True >>> isinstance(caps["shapely"], bool) True
Boundaries and Providers
Deprecation shim — re-exports from siege_utilities.geo.providers.boundary_providers.
Moved during ELE-2438 (spatial providers consolidated under
geo/providers/). Will be removed in v4.0.0.
- exception siege_utilities.geo.boundary_providers.BoundaryFetchError[source]
Bases:
RuntimeErrorRaised when a boundary provider cannot satisfy a request after retries.
- class siege_utilities.geo.boundary_providers.BoundaryProvider[source]
Bases:
ABCAbstract base class for geographic boundary data providers.
- abstractmethod get_boundary(level, identifier=None, **kwargs)[source]
Fetch boundary geometry for a given geographic level.
- Parameters:
level (str) – Geographic level (e.g. ‘county’, ‘tract’, ‘admin1’).
identifier (str | None) – Optional identifier to narrow the query (e.g. state FIPS).
**kwargs (Any) –
Provider-specific options. Recognized cross-provider kwargs: engine: Optional DataFrameEngine instance. When provided,
the result is converted to the engine’s native format via
engine.from_geodataframe(). Default (None) returns a GeoDataFrame.
- Returns:
GeoDataFrame when engine is None; engine-native DataFrame otherwise.
- Return type:
- class siege_utilities.geo.boundary_providers.CensusTIGERProvider[source]
Bases:
BoundaryProviderUS Census TIGER/Line boundary provider.
Wraps
siege_utilities.geo.spatial_data.CensusDataSourceandsiege_utilities.config.census_constants.CANONICAL_GEOGRAPHIC_LEVELS.- get_boundary(level, identifier=None, **kwargs)[source]
Fetch US Census TIGER boundaries.
- Parameters:
level (str) – A canonical geographic level (e.g. ‘county’, ‘tract’, ‘cd’).
identifier (str | None) – State FIPS code when the level requires it.
**kwargs (Any) – Forwarded to
CensusDataSource.fetch_geographic_boundaries(e.g.year,congress_number).engine: Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
BoundaryFetchError – When the boundary retrieval fails.
- is_available()[source]
Census TIGER provider is always available (pure-HTTP downloads).
- Return type:
- class siege_utilities.geo.boundary_providers.GADMProvider[source]
Bases:
BoundaryProviderGADM (Global Administrative Areas) boundary provider.
Downloads GeoJSON boundary files from the GADM project for non-US countries. Requires geopandas at runtime.
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch GADM boundaries for a country.
- Parameters:
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
ValueError – If level is unknown or country_code is missing.
ImportError – If geopandas is not installed.
- class siege_utilities.geo.boundary_providers.RDHProvider[source]
Bases:
BoundaryProviderRedistricting Data Hub boundary provider.
Wraps
siege_utilities.geo.providers.redistricting_data_hub.RDHClientto expose precinct / VTD boundaries — and enacted legislative plans — through the standardBoundaryProviderinterface.RDH requires a “designated API user” account. Contact info@redistrictingdatahub.org to request access. Pass credentials either via constructor arguments or environment variables
RDH_USERNAME/RDH_PASSWORD.Supported levels
'precinct'— precinct/VTD boundaries with election results'congress'— enacted congressional district plans'state_senate'— enacted upper-chamber plans'state_house'— enacted lower-chamber plans
Usage:
import os from siege_utilities.geo.providers.boundary_providers import RDHProvider # Prefer env vars (RDH_USERNAME / RDH_PASSWORD); never hardcode provider = RDHProvider( username=os.environ["RDH_USERNAME"], password=os.environ["RDH_PASSWORD"], ) gdf = provider.get_boundary('precinct', identifier='TX')
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch RDH boundary data for a state.
- Parameters:
level (str) – One of
'precinct','congress','state_senate','state_house'.identifier (str | None) – Two-letter state abbreviation (e.g.
'TX'). Can also be passed asstatekwarg.**kwargs (Any) –
state— alias for identifier.year— filter datasets by year string (e.g.'2022').format—'shp'(default) or'csv'.engine— Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided. None if no matching datasets found.
- Raises:
ValueError – If level is not recognised or state is missing.
ImportError – If geopandas is not installed.
- siege_utilities.geo.boundary_providers.resolve_boundary_provider(country='US', **kwargs)[source]
Return an appropriate
BoundaryProviderfor the given country.- Parameters:
country (str) – ISO-2 or ISO-3 country code (default
'US').**kwargs (Any) – Forwarded to
GADMProviderconstructor for non-US countries. Ignored for US (CensusTIGERProvider takes no options).
- Returns:
CensusTIGERProvider for US / US territories, GADMProvider otherwise.
- Return type:
Structured diagnostics for Census boundary retrieval.
Provides typed exceptions and a result object so callers can programmatically branch on failure reason without parsing free-text logs.
Usage:
from siege_utilities.geo import fetch_geographic_boundaries
result = fetch_geographic_boundaries(2020, 'county', state_fips='48')
if result.success:
gdf = result.geodataframe
else:
print(f"Failed at stage '{result.error_stage}': {result.message}")
print(f"Context: {result.context}")
- exception siege_utilities.geo.boundary_result.BoundaryConfigurationError[source]
Bases:
BoundaryRetrievalErrorBoundary type requires parameters that were not provided (e.g., state FIPS, congress number).
- exception siege_utilities.geo.boundary_result.BoundaryDiscoveryError[source]
Bases:
BoundaryRetrievalErrorFailed to discover available boundary types or construct a URL.
- exception siege_utilities.geo.boundary_result.BoundaryDownloadError[source]
Bases:
BoundaryRetrievalErrorDownload succeeded but the file is corrupt or not a valid zip.
- class siege_utilities.geo.boundary_result.BoundaryFetchResult[source]
Bases:
objectStructured result from a boundary retrieval attempt.
- geodataframe
The resulting GeoDataFrame (None on failure).
- Type:
Any
- error_stage
Pipeline stage where failure occurred (None on success). One of: input_validation, discovery, url_validation, download, parse.
- Type:
str | None
- context
Diagnostic details (attempted URLs, year fallback, HTTP status, etc.).
- Type:
Dict[str, Any]
- raise_on_error()[source]
Raise the appropriate typed exception if this result is a failure.
Returns self on success so callers can chain:
gdf = fetch_geographic_boundaries(...).raise_on_error().geodataframe
- Return type:
- classmethod ok(gdf, message='', context=None)[source]
Construct a success result.
- Parameters:
- Return type:
- exception siege_utilities.geo.boundary_result.BoundaryInputError[source]
Bases:
BoundaryRetrievalErrorInvalid input parameters (state FIPS, year, geographic level).
- exception siege_utilities.geo.boundary_result.BoundaryParseError[source]
Bases:
BoundaryRetrievalErrorDownloaded data could not be parsed as a shapefile/GeoDataFrame.
- exception siege_utilities.geo.boundary_result.BoundaryRetrievalError[source]
Bases:
SiegeGeoErrorBase exception for all boundary retrieval failures.
Inherits from
SiegeGeoErrorso callers can catch the entire siege_utilities exception family with a singleexcept SiegeError:. Previously this stood alone outside the documented hierarchy and slipped pastexcept SiegeErrorblocks.
- exception siege_utilities.geo.boundary_result.BoundaryUrlValidationError[source]
Bases:
BoundaryRetrievalErrorConstructed URL is not accessible (HTTP error, timeout, etc.).
Boundary data source convenience functions (engine-agnostic).
Thin wrappers that know about specific data sources — Census TIGER, GADM,
RDH redistricting plans. These call engine.load_polygons() with the
correct paths and parameters.
Works with ANY DataFrameEngine (Pandas, DuckDB, Spark, PostGIS).
For custom boundary sources, call engine.load_polygons() directly.
Usage:
from siege_utilities.engines.dataframe_engine import get_engine, Engine
from siege_utilities.geo.boundary_sources import load_census_boundaries
engine = get_engine(Engine.SPARK, enable_sedona=True)
# Load congressional districts via the engine
districts = load_census_boundaries(engine, "cd", year=2020)
# Assign addresses to districts
enriched = engine.assign_boundaries(addresses, districts)
- siege_utilities.geo.boundary_sources.load_census_boundaries(engine, boundary_type, year=2020, state_fips=None, s3_base='s3a://boundaries/tiger')[source]
Load Census TIGER boundaries via any DataFrameEngine.
- Parameters:
engine (DataFrameEngine) – Any engine instance (Pandas, DuckDB, Spark, PostGIS).
boundary_type (str) – One of: state, county, tract, blockgroup, block, place, zcta, cd, sldu, sldl, vtd, cbsa.
year (int) – Census vintage year (default 2020).
state_fips (str, optional) – 2-digit state FIPS to filter.
s3_base (str) – Base path for staged GeoParquet.
- Return type:
Engine-native DataFrame with geometry column.
- siege_utilities.geo.boundary_sources.load_gadm_boundaries(engine, level=0, country=None, s3_base='s3a://boundaries/gadm')[source]
Load GADM (Global Administrative Areas) boundaries via any engine.
- Parameters:
engine (DataFrameEngine)
level (int) – Admin level 0-5 (0=countries, 1=states/provinces, etc.)
country (str, optional) – ISO 3166-1 alpha-3 country code to filter.
s3_base (str) – Base path for staged GADM data.
- Return type:
- siege_utilities.geo.boundary_sources.load_rdh_boundaries(engine, state, plan_type='enacted', s3_base='s3a://boundaries/rdh')[source]
Load Redistricting Data Hub plan boundaries via any engine.
- Parameters:
engine (DataFrameEngine)
state (str) – 2-letter state abbreviation.
plan_type (str) – ‘enacted’, ‘proposed’, ‘alternative’.
s3_base (str)
- Return type:
- siege_utilities.geo.boundary_sources.stage_census_boundaries(engine, boundary_type, year=2020, s3_base='s3a://boundaries/tiger')[source]
Stage Census TIGER boundaries for fast loading.
Fetches from Census API via CensusTIGERProvider, converts geometry to the engine’s format, writes to the staging path.
- Parameters:
engine (DataFrameEngine)
boundary_type (str)
year (int)
s3_base (str)
- Returns:
str
- Return type:
path where boundaries were written.
External spatial data sources — one folder, one shape per provider.
This is the canonical home for every module that fetches spatial data from an external source. Housed here under ELE-2438 (D3) as the first step toward a unified provider interface.
Current providers:
boundary_providers— CensusTIGER, GADM, RDH boundary retrievalcensus_geocoder— US Census geocoder (address → FIPS)nces_download— NCES school-district and locale boundariesredistricting_data_hub— RDH API client (precincts, plans, CVAP, PL 94-171, compactness metrics)nlrb_clients— NLRB case data from data.gov, labordata GitHub, and NxGen search
Not yet migrated (planned for the interface-unification follow-up epic):
geo/spatial_data.py—CensusDataSource+GovernmentDataSource+OpenStreetMapDataSource. File is 1,500+ lines mixing concerns; will be split as part of the provider-interface work.geo/census/subpackage — Census API client. Moves once the variable-registry split stabilizes.
The “structural moves only” scope of ELE-2438 does not unify these
providers behind a common fetch / accepts interface — that’s a
separate, larger effort.
Boundary provider abstraction for geographic boundary data.
Provides a pluggable architecture for fetching administrative boundary geometries from different sources:
CensusTIGERProvider: US boundaries via Census TIGER/Line shapefiles
GADMProvider: International boundaries via the Database of Global Administrative Areas
resolve_boundary_provider(): Factory that selects the appropriate provider by country
Usage:
from siege_utilities.geo.providers.boundary_providers import resolve_boundary_provider
provider = resolve_boundary_provider('US')
gdf = provider.get_boundary('county', state_fips='06')
intl_provider = resolve_boundary_provider('DE')
gdf = intl_provider.get_boundary('admin1', country_code='DEU')
- exception siege_utilities.geo.providers.boundary_providers.BoundaryFetchError[source]
Bases:
RuntimeErrorRaised when a boundary provider cannot satisfy a request after retries.
- class siege_utilities.geo.providers.boundary_providers.BoundaryProvider[source]
Bases:
ABCAbstract base class for geographic boundary data providers.
- abstractmethod get_boundary(level, identifier=None, **kwargs)[source]
Fetch boundary geometry for a given geographic level.
- Parameters:
level (str) – Geographic level (e.g. ‘county’, ‘tract’, ‘admin1’).
identifier (str | None) – Optional identifier to narrow the query (e.g. state FIPS).
**kwargs (Any) –
Provider-specific options. Recognized cross-provider kwargs: engine: Optional DataFrameEngine instance. When provided,
the result is converted to the engine’s native format via
engine.from_geodataframe(). Default (None) returns a GeoDataFrame.
- Returns:
GeoDataFrame when engine is None; engine-native DataFrame otherwise.
- Return type:
- class siege_utilities.geo.providers.boundary_providers.CensusTIGERProvider[source]
Bases:
BoundaryProviderUS Census TIGER/Line boundary provider.
Wraps
siege_utilities.geo.spatial_data.CensusDataSourceandsiege_utilities.config.census_constants.CANONICAL_GEOGRAPHIC_LEVELS.- get_boundary(level, identifier=None, **kwargs)[source]
Fetch US Census TIGER boundaries.
- Parameters:
level (str) – A canonical geographic level (e.g. ‘county’, ‘tract’, ‘cd’).
identifier (str | None) – State FIPS code when the level requires it.
**kwargs (Any) – Forwarded to
CensusDataSource.fetch_geographic_boundaries(e.g.year,congress_number).engine: Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
BoundaryFetchError – When the boundary retrieval fails.
- is_available()[source]
Census TIGER provider is always available (pure-HTTP downloads).
- Return type:
- class siege_utilities.geo.providers.boundary_providers.GADMProvider[source]
Bases:
BoundaryProviderGADM (Global Administrative Areas) boundary provider.
Downloads GeoJSON boundary files from the GADM project for non-US countries. Requires geopandas at runtime.
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch GADM boundaries for a country.
- Parameters:
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided.
- Raises:
ValueError – If level is unknown or country_code is missing.
ImportError – If geopandas is not installed.
- class siege_utilities.geo.providers.boundary_providers.RDHProvider[source]
Bases:
BoundaryProviderRedistricting Data Hub boundary provider.
Wraps
siege_utilities.geo.providers.redistricting_data_hub.RDHClientto expose precinct / VTD boundaries — and enacted legislative plans — through the standardBoundaryProviderinterface.RDH requires a “designated API user” account. Contact info@redistrictingdatahub.org to request access. Pass credentials either via constructor arguments or environment variables
RDH_USERNAME/RDH_PASSWORD.Supported levels
'precinct'— precinct/VTD boundaries with election results'congress'— enacted congressional district plans'state_senate'— enacted upper-chamber plans'state_house'— enacted lower-chamber plans
Usage:
import os from siege_utilities.geo.providers.boundary_providers import RDHProvider # Prefer env vars (RDH_USERNAME / RDH_PASSWORD); never hardcode provider = RDHProvider( username=os.environ["RDH_USERNAME"], password=os.environ["RDH_PASSWORD"], ) gdf = provider.get_boundary('precinct', identifier='TX')
- get_boundary(level, identifier=None, **kwargs)[source]
Fetch RDH boundary data for a state.
- Parameters:
level (str) – One of
'precinct','congress','state_senate','state_house'.identifier (str | None) – Two-letter state abbreviation (e.g.
'TX'). Can also be passed asstatekwarg.**kwargs (Any) –
state— alias for identifier.year— filter datasets by year string (e.g.'2022').format—'shp'(default) or'csv'.engine— Optional DataFrameEngine for result conversion.
- Returns:
GeoDataFrame (default) or engine-native DataFrame when engine is provided. None if no matching datasets found.
- Raises:
ValueError – If level is not recognised or state is missing.
ImportError – If geopandas is not installed.
- siege_utilities.geo.providers.boundary_providers.resolve_boundary_provider(country='US', **kwargs)[source]
Return an appropriate
BoundaryProviderfor the given country.- Parameters:
country (str) – ISO-2 or ISO-3 country code (default
'US').**kwargs (Any) – Forwarded to
GADMProviderconstructor for non-US countries. Ignored for US (CensusTIGERProvider takes no options).
- Returns:
CensusTIGERProvider for US / US territories, GADMProvider otherwise.
- Return type:
Census Bureau Geocoder integration with historical vintage support.
Wraps the Census Bureau’s geocoding API for US address geocoding. Returns FIPS codes directly (state, county, tract, block) so most addresses don’t need a spatial join. Supports historical vintages for temporal accuracy.
- Usage:
- from siege_utilities.geo import (
CensusVintage, select_vintage_for_cycle, geocode_single, geocode_batch, CensusGeocodeResult,
)
# Single address result = geocode_single(“1600 Pennsylvania Ave”, “Washington”, “DC”, “20500”)
# Batch (up to 10,000) results = geocode_batch(addresses, vintage=CensusVintage.CENSUS_2020)
# Auto-select vintage for an FEC cycle year vintage = select_vintage_for_cycle(2018) # -> CensusVintage.CENSUS_2020
- exception siege_utilities.geo.providers.census_geocoder.CensusGeocodeError[source]
Bases:
RuntimeErrorRaised when the Census geocoder API call fails unexpectedly.
Distinct from “no match” results (which return a CensusGeocodeResult with matched=False). This exception indicates an API / network / parse failure where the geocoder could not even attempt to match. Use __cause__ to inspect the underlying exception.
- class siege_utilities.geo.providers.census_geocoder.CensusGeocodeResult[source]
Bases:
objectResult from Census Bureau geocoding.
- __init__(matched=False, input_address='', matched_address='', lat=None, lon=None, state_fips='', county_fips='', tract='', block='', match_type='No_Match', side='', tiger_line_id='', input_id='')
- class siege_utilities.geo.providers.census_geocoder.CensusVintage[source]
-
Census geocoder benchmark/vintage pairs.
Each member’s value encodes
<benchmark>|<vintage>where both halves are the exact strings the Census Geocoder API expects. Use the.benchmarkand.vintageproperties to extract them.The benchmark names follow the
Public_AR_<label>pattern used byhttps://geocoding.geo.census.gov/geocoder/benchmarks. The vintage names follow the<label>_<benchmark-label>pattern returned byhttps://geocoding.geo.census.gov/geocoder/vintages?benchmark=….- CENSUS_2010 = 'Public_AR_Census2020|Census2010_Census2020'
- CENSUS_2020 = 'Public_AR_Census2020|Census2020_Census2020'
- CURRENT = 'Public_AR_Current|Current_Current'
- ACS_2022 = 'Public_AR_Current|ACS2022_Current'
- ACS_2023 = 'Public_AR_Current|ACS2023_Current'
- __new__(value)
- siege_utilities.geo.providers.census_geocoder.geocode_batch(addresses, vintage=CensusVintage.CURRENT)[source]
Geocode a batch of addresses via the Census Bureau batch API.
The Census batch API accepts up to 10,000 addresses per request. Each address dict should have keys: id, street, city, state, zipcode.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
List of CensusGeocodeResult, one per input address (order preserved).
- Raises:
ValueError – If batch exceeds 10,000 addresses.
- Return type:
- siege_utilities.geo.providers.census_geocoder.geocode_batch_chunked(addresses, vintage=CensusVintage.CURRENT, chunk_size=10000)[source]
Geocode addresses in chunks of up to chunk_size (default 10,000).
Convenience wrapper around geocode_batch() for larger datasets.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
chunk_size (int) – Max addresses per API call (max 10,000).
- Returns:
List of CensusGeocodeResult, one per input address.
- Return type:
- siege_utilities.geo.providers.census_geocoder.geocode_results_to_dataframe(results)[source]
Convert a list of CensusGeocodeResult to a pandas DataFrame.
Extracts all dataclass fields and computed GEOID properties into a flat DataFrame with standard column names. This eliminates the per-field list-comprehension boilerplate that every caller of
geocode_batch()otherwise has to write.- Parameters:
results (list[CensusGeocodeResult]) – List of
CensusGeocodeResultobjects, typically returned bygeocode_batch()orgeocode_batch_chunked().- Returns:
input_id,input_address,matched,match_type,matched_address,latitude,longitude,state_geoid,county_geoid,tract_geoid,block_geoid,block_group_geoid,state_fips,county_fips,tract,block,side,tiger_line_id. For unmatched rows, latitude/longitude areNoneand GEOID columns are empty strings.- Return type:
DataFrame with columns
- Raises:
TypeError – If results is not a list.
Example:
results = geocode_batch(addresses) df = geocode_results_to_dataframe(results) # df now has tract_geoid, block_geoid, lat/lon, etc.
- siege_utilities.geo.providers.census_geocoder.geocode_single(street, city, state, zipcode, vintage=CensusVintage.CURRENT)[source]
Geocode a single address via the Census Bureau API.
- Parameters:
street (str) – Street address (e.g., “1600 Pennsylvania Ave NW”).
city (str) – City name.
state (str) – State abbreviation or name.
zipcode (str) – ZIP code.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
CensusGeocodeResult with lat/lon and FIPS codes if matched.
- Return type:
- siege_utilities.geo.providers.census_geocoder.select_vintage_for_cycle(year)[source]
Select the appropriate Census vintage for an FEC cycle year.
- Parameters:
year (int) – The FEC election cycle year.
- Returns:
CensusVintage matching the boundaries in effect for that cycle.
- Return type:
Examples
>>> select_vintage_for_cycle(2024) <CensusVintage.CURRENT: 'Public_AR_Current|Current_Current'> >>> select_vintage_for_cycle(2016) <CensusVintage.CENSUS_2020: 'Public_AR_Census2020|Census2020_Census2020'> >>> select_vintage_for_cycle(2008) <CensusVintage.CENSUS_2010: 'Public_AR_Census2020|Census2010_Census2020'>
Composite batch geocoder with fallback chaining.
Chains multiple BatchGeocoder backends by priority,
falling back to the next backend for addresses that fail or
match below a quality threshold.
Typical usage: Census first (free, returns GEOIDs), then Nominatim or TAMU for failures.
- class siege_utilities.geo.providers.composite_geocoder.CompositeBatchGeocoder[source]
Bases:
BatchGeocoderChains geocoding backends by priority with fallback.
For each address, tries backends in order. If a backend returns a match below the minimum quality threshold, the next backend is tried. The best result across all backends is kept.
- Parameters:
backends – Ordered list of geocoder backends (highest priority first).
min_quality – Minimum match quality to accept without fallback. Default “approximate” — only “centroid” and “no_match” trigger fallback.
skip_unavailable – Skip backends where is_available() returns False. Default True.
- __init__(backends, min_quality='approximate', skip_unavailable=True)[source]
- Parameters:
backends (list[BatchGeocoder])
min_quality (str)
skip_unavailable (bool)
Unified batch geocoding interface.
Defines the BatchGeocoder abstract base class and
GeocodingResult schema for consistent geocoding across
multiple backends (Census, Nominatim, TAMU, etc.).
Usage:
from siege_utilities.geo.providers.batch_geocoder import (
BatchGeocoder, GeocodingResult, MatchQuality,
)
- class siege_utilities.geo.providers.batch_geocoder.AddressInput[source]
Bases:
objectNormalized address input for batch geocoding.
- class siege_utilities.geo.providers.batch_geocoder.BatchGeocoder[source]
Bases:
ABCAbstract base class for batch geocoding backends.
Subclasses implement
geocode()to process a list of addresses through a specific geocoding service. The interface accepts flexible input formats and returns aBatchGeocodingResult.Example:
class MyGeocoder(BatchGeocoder): @property def backend_name(self) -> str: return "my_service" def geocode(self, addresses, **kwargs): normalized = normalize_addresses(addresses) results = [] for addr in normalized: # ... call service ... results.append(GeocodingResult(...)) return BatchGeocodingResult(results=results, backend=self.backend_name)
- abstract property backend_name: str
Short identifier for this backend (e.g., ‘census’, ‘nominatim’).
- class siege_utilities.geo.providers.batch_geocoder.BatchGeocodingResult[source]
Bases:
objectContainer for a batch geocoding run.
- results: list[GeocodingResult]
- to_dataframe()[source]
Convert results to a pandas DataFrame.
- Returns:
DataFrame with one row per result, including all fields.
- class siege_utilities.geo.providers.batch_geocoder.GeocodingResult[source]
Bases:
objectUnified geocoding result across all backends.
- __init__(input_address='', input_id='', matched_address='', lat=None, lon=None, match_quality='no_match', block_geoid='', tract_geoid='', county_geoid='', state_geoid='', backend='')
- class siege_utilities.geo.providers.batch_geocoder.MatchQuality[source]
-
Geocoding match quality levels.
- EXACT = 'exact'
- INTERPOLATED = 'interpolated'
- APPROXIMATE = 'approximate'
- CENTROID = 'centroid'
- NO_MATCH = 'no_match'
- __new__(value)
- siege_utilities.geo.providers.batch_geocoder.normalize_addresses(addresses)[source]
Normalize address input to a list of AddressInput.
Accepts: - List of dicts with keys: street, city, state, zipcode, id (optional) - List of one-line address strings - List of AddressInput instances (returned as-is)
- Parameters:
- Return type:
Census Bureau batch geocoder backend.
Wraps geocode_batch_chunked() behind the BatchGeocoder ABC,
converting CensusGeocodeResult to the unified
GeocodingResult schema.
- class siege_utilities.geo.providers.census_batch.CensusBatchGeocoder[source]
Bases:
BatchGeocoderCensus Bureau batch geocoding backend.
Wraps the existing Census geocoder functions with automatic chunking for datasets larger than 10,000 addresses. Returns block-level GEOIDs directly from the Census API (no spatial join needed).
- Parameters:
vintage – Census vintage to use. Defaults to Current.
chunk_size – Max addresses per API call. Default 10,000.
Nominatim batch geocoder backend.
Wraps Nominatim geocoding (public or self-hosted) behind the
BatchGeocoder ABC with configurable rate limiting.
Nominatim does not return Census GEOIDs — those must be assigned via spatial join if needed.
- class siege_utilities.geo.providers.nominatim_batch.NominatimBatchGeocoder[source]
Bases:
BatchGeocoderNominatim geocoding backend with rate limiting.
Uses the existing geocoding infrastructure to query Nominatim one address at a time (Nominatim has no true batch API).
- Parameters:
server_url – Nominatim endpoint. Defaults to public instance.
rate_limit – Seconds between requests. Default 1.0 for public, 0.05 for self-hosted (auto-detected from URL).
country_codes – Restrict results to these countries (e.g., [‘us’]).
max_retries – Max retries per address on transient failure.
Texas A&M Geoservices batch geocoder backend.
Wraps the TAMU geocoding API behind the BatchGeocoder ABC.
TAMU returns Census geography (state, county, tract, block GEOIDs)
directly in its response, so no spatial join is needed.
Requires an API key — get one at https://geoservices.tamu.edu/.
- class siege_utilities.geo.providers.tamu_batch.TAMUBatchGeocoder[source]
Bases:
BatchGeocoderTexas A&M Geoservices geocoding backend.
Geocodes addresses one at a time via TAMU’s HTTP API. Returns Census geography (block-level GEOIDs) directly.
- Parameters:
api_key – TAMU API key. Falls back to TAMU_API_KEY env var.
rate_limit – Seconds between requests. Default 0.25.
census_year – Census year for geography lookup. Default “2020”.
max_retries – Max retries per address on transient failure.
- is_available()[source]
Check if this backend is currently available.
Default returns True. Override for backends that need API keys or network connectivity checks.
- Return type:
Natural-language geographic query parsing via Etter.
Wraps the upstream etter package to parse natural-language geographic queries (“5 km north of Lausanne”, “donations near Birmingham”, “tracts in Alabama”) into structured filters.
Why a thin wrapper?
The upstream library hands you a Pydantic GeoQuery object. That’s
good shape, but two practical problems for siege_utilities consumers:
Constructing the LLM (langchain
ChatOpenAI,ChatAnthropic, etc.) is boilerplate that bleeds into every call site.Consumers want a shape they can hand to a
BoundaryProvideror aDataFrameEngine— not a Pydantic model from an unrelated lib.
This module hides both: EtterParser gives you a default LLM
factory and a EtterFilter dataclass that the rest of the
geo toolchain can act on.
The LLM dependency is opt-in via the [etter] extra. Etter calls
make a network round-trip per query, so this is for ad-hoc
exploration / interactive UI / one-off ETL prep — not for batch
pipelines where every donor address gets re-parsed.
- exception siege_utilities.geo.providers.etter_filter.EtterError[source]
Bases:
RuntimeErrorBase class for connector-side Etter failures.
- exception siege_utilities.geo.providers.etter_filter.EtterParseError[source]
Bases:
EtterErrorEtter failed to parse the query into a structured filter.
- exception siege_utilities.geo.providers.etter_filter.EtterLowConfidenceError[source]
Bases:
EtterErrorParser succeeded but confidence is below the threshold.
- class siege_utilities.geo.providers.etter_filter.EtterFilter[source]
Bases:
objectA normalized parsed query ready for downstream consumption.
Wraps the upstream
GeoQueryPydantic model into a dataclass with the fields siege_utilities consumers actually need. The original upstream object is preserved onrawfor callers that need full fidelity.- spatial_relation
One of the relations Etter recognises —
"in","near","north_of","south_of","east_of","west_of", etc.Noneif the query doesn’t carry a relation (e.g. a bare place name).- Type:
str | None
- reference_location
The place the relation hangs off —
"Lausanne","Lake Geneva","Birmingham, AL".- Type:
str | None
- buffer_distance_m
Buffer distance in meters if the query had one (
"5 km"→ 5000).Noneotherwise.- Type:
float | None
- raw
The upstream
etter.GeoQueryobject, in case the consumer needs the full breakdown.- Type:
Any
- classmethod from_geoquery(original_query, geo_query)[source]
Translate an upstream
GeoQueryto anEtterFilter.Tolerant of upstream field-name drift — uses
getattrwith defaults rather than positional unpacking.- Parameters:
- Return type:
- class siege_utilities.geo.providers.etter_filter.EtterParser[source]
Bases:
objectWrap
etter.GeoFilterParserwith a sensible default LLM.Usage:
from siege_utilities.geo.providers.etter_filter import EtterParser # Defaults: gpt-4o, temperature=0, OPENAI_API_KEY from env. parser = EtterParser() result = parser.parse("5 km north of Lausanne") # result.spatial_relation == "north_of" # result.reference_location == "Lausanne" # result.buffer_distance_m == 5000.0
Pass
llm=to use a pre-built chat model (e.g. for tests or for pinning to a specific Anthropic / Azure deployment). All other constructor kwargs are forwarded to the upstream parser.- parse(query)[source]
Parse query into an
EtterFilter.- Raises:
EtterParseError – Upstream parser raised an unexpected error. The original exception is chained via
__cause__.EtterLowConfidenceError – Parser succeeded but confidence is below the threshold and
strict_mode=True. (Whenstrict_modeis False, this case logs and returns normally; checkEtterFilter.confidenceif the caller needs to gate.) Also raised if a future upstream exposes its ownLowConfidenceErrorand our local check wasn’t reached — we re-raise it as our type so consumers don’t have to know about upstream class names.
- Parameters:
query (str)
- Return type:
- siege_utilities.geo.providers.etter_filter.default_llm(*, model='gpt-4o', temperature=0.0, api_key=None)[source]
Return a langchain chat model suitable for
EtterParser.Picks the right adapter based on the model name.
api_keyfalls back to the standard environment variable for the chosen provider (OPENAI_API_KEY/ANTHROPIC_API_KEY). Lazy-imports langchain so the rest of this module is importable without it.
Etter → geometry resolver.
Turns a parsed siege_utilities.geo.providers.etter_filter.EtterFilter
into a shapely geometry, using a siege_utilities.geo.gazetteers.Gazetteer
to look up the reference location.
The interesting design decision is what geometry “X near Y” means. We ship three modes:
RelationSemantics.BOUNDED— default; produces a finite polygon. Directional relations (“north of Y”) are evaluated as a bounded buffer (default_buffer_km× 2) on the directional side of Y’s centroid. This is the safe default for indexed-lookup workloads — the output is always intersectable with a polygon set.RelationSemantics.HALFPLANE— directional relations produce an infinite halfplane. “North of Lake Geneva” → the entire northern halfplane from Lake Geneva’s latitude. Useful for filter semantics where the consumer will intersect with a country / region boundary later. Not ideal for indexed lookups; the geometry is unbounded.RelationSemantics.CONTAINS_CENTROID— instead of returning a region geometry, returns aPointPredicatecallable that evaluatescontains(candidate.centroid)for each candidate. Use when you want directional filtering without producing a polygon at all.
Buffer-distance precedence:
If the Etter filter parsed an explicit distance (“within 5 km of …”), that wins.
Else,
default_buffer_kmon the resolver (default 25 km).
- class siege_utilities.geo.providers.etter_to_geometry.RelationSemantics[source]
-
How to interpret directional/proximity relations.
See module docstring for the full rationale.
- BOUNDED = 'bounded'
- HALFPLANE = 'halfplane'
- CONTAINS_CENTROID = 'contains_centroid'
- __new__(value)
- class siege_utilities.geo.providers.etter_to_geometry.EtterGeometryResult[source]
Bases:
objectThe resolved geometry plus diagnostic context.
- geometry
The shapely geometry (or
PointPredicateforCONTAINS_CENTROIDmode).- Type:
Any
- relation
The Etter spatial_relation that was applied, or
Noneif the input had no relation (just a bare reference location → the reference geometry itself).- Type:
str | None
- reference
The
GazetteerResultthat anchored the relation.Noneonly when the filter had no reference_location either, which is a degenerate input.- Type:
- buffer_km
Buffer distance actually used (filter wins over default;
Nonefor relations that don’t buffer).- Type:
float | None
- semantics
Which
RelationSemanticsmode produced this result.
- notes
Free-text diagnostic notes (e.g., “halfplane truncated to ±90° lat to remain valid GeoJSON”).
- reference: GazetteerResult | None
- semantics: RelationSemantics
- __init__(geometry, relation, reference, buffer_km, semantics, notes=())
- Parameters:
geometry (Any)
relation (str | None)
reference (GazetteerResult | None)
buffer_km (float | None)
semantics (RelationSemantics)
- Return type:
None
- exception siege_utilities.geo.providers.etter_to_geometry.EtterToGeometryError[source]
Bases:
RuntimeErrorRaised when an EtterFilter cannot be turned into a geometry.
Carries the parsed filter and the failure reason. The most common causes — the reference location not being found in the gazetteer, or a relation Etter parsed that we don’t know how to translate — each have their own subclass so callers can branch.
- exception siege_utilities.geo.providers.etter_to_geometry.EtterReferenceNotFoundError[source]
Bases:
EtterToGeometryErrorThe reference location couldn’t be resolved by the gazetteer.
- exception siege_utilities.geo.providers.etter_to_geometry.EtterUnknownRelationError[source]
Bases:
EtterToGeometryErrorEtter returned a relation we don’t have a translation for.
- siege_utilities.geo.providers.etter_to_geometry.etter_to_geometry(filter_, *, gazetteer, semantics=RelationSemantics.BOUNDED, default_buffer_km=25.0, country_hint=None, admin_hint=None)[source]
Resolve an
EtterFilterto a geometry.- Parameters:
filter – The parsed query.
gazetteer (Gazetteer) – The gazetteer that resolves the reference location. Pass the result of
siege_utilities.geo.gazetteers.resolve_gazetteer().semantics (RelationSemantics) – Which mode of relation interpretation to use. The default
RelationSemantics.BOUNDEDis safe for most indexed-lookup workloads.default_buffer_km (float) – Buffer to apply for “near”-style relations and as the half-width of bounded directional buffers when the filter doesn’t carry an explicit distance.
admin_hint (str | None) – Forwarded to
gazetteer.lookupto disambiguate the reference location. Etter often emits just the place name; the consumer may know the country from a sibling field.filter_ (EtterFilter)
country_hint (str | None)
admin_hint
- Returns:
EtterGeometryResultwith the geometry (or aPointPredicate) and diagnostic context.- Raises:
EtterReferenceNotFoundError – Gazetteer couldn’t find the reference location.
EtterUnknownRelationError – Etter parsed a relation we don’t know how to translate.
ValueError –
filter_.reference_locationis missing.
- Return type:
Redistricting Data Hub (RDH) API client for siege_utilities.
Provides authenticated access to redistrictingdatahub.org datasets: - PL 94-171 redistricting data (population by race at block level) - CVAP special tabulation (citizen voting age population) - Legislative boundary shapefiles (enacted congressional + state leg) - Precinct boundaries with election results - ACS 5-year demographic estimates - Population projections
Authentication requires a “designated API user” account. Contact info@redistrictingdatahub.org to request access.
Usage:
import os
from siege_utilities.geo.providers.redistricting_data_hub import RDHClient
# Prefer env vars (RDH_USERNAME / RDH_PASSWORD); never hardcode
client = RDHClient(
username=os.environ["RDH_USERNAME"],
password=os.environ["RDH_PASSWORD"],
)
datasets = client.list_datasets(states=["VA"], format="csv")
df = client.download_dataset(datasets[0])
- class siege_utilities.geo.providers.redistricting_data_hub.RDHClient[source]
Bases:
objectClient for the Redistricting Data Hub download API.
- Parameters:
username (str, optional) – RDH account email. Falls back to
RDH_USERNAMEenv var.password (str, optional) – RDH account password. Falls back to
RDH_PASSWORDenv var.base_url (str, optional) – Override API endpoint (for testing).
cache_dir (Path or str, optional) – Directory for caching downloaded files. Defaults to
~/.cache/siege_utilities/rdh/.
- __init__(username=None, password=None, base_url='https://redistrictingdatahub.org/wp-json/download/list', cache_dir=None)[source]
- credentials_present()[source]
Return True if a username and password are both set.
This is a presence check only – it does NOT contact RDH or verify that the credentials authenticate. A live check is impractical here: the RDH catalog endpoint is WAF-gated to allowlisted ingest hosts (#1115), so from other hosts an authenticated-style request returns the website HTML rather than the JSON API. Callers needing a true liveness check should call
list_datasets()and handle theValueErrorit now raises on an API error envelope.- Return type:
- validate_credentials()[source]
Backward-compatible alias for
credentials_present().Retained for existing callers. The name is a misnomer kept for compatibility: it checks credential presence, not live authentication (see
credentials_present()). (#1115)- Return type:
- list_datasets(states=None, format=None, year=None, dataset_type=None, geography=None, official=None)[source]
List available datasets with optional filters.
- Parameters:
states (list of str, optional) – State abbreviations to filter (e.g.
["VA", "MD"]). Due to API limits, queries more than 4 states sequentially.format (str, optional) –
"csv"or"shp".year (str, optional) – Four-digit year.
dataset_type (str, optional) – Filter keyword in title.
geography (str, optional) – Geographic level filter.
official (bool, optional) – If True, only return official/enacted datasets.
- Return type:
list of RDHDataset
- download_dataset(dataset, output_dir=None, use_cache=True)[source]
Download a dataset file.
- Parameters:
dataset (RDHDataset or str) – Dataset object or direct download URL.
output_dir (Path, optional) – Where to save. Defaults to cache_dir.
use_cache (bool) – If True, skip download if file already exists.
- Return type:
Path to the downloaded file (or extracted directory for ZIPs).
- load_csv(dataset, **kwargs)[source]
Download (if needed) and load a CSV dataset into a DataFrame.
- Parameters:
dataset (RDHDataset, str, or Path) – Dataset to load.
**kwargs – Passed to
pd.read_csv().
- Return type:
pd.DataFrame
- load_shapefile(dataset, **kwargs)[source]
Download (if needed) and load a shapefile into a GeoDataFrame.
- Parameters:
dataset (RDHDataset, str, or Path) – Dataset to load.
**kwargs – Passed to
gpd.read_file().
- Return type:
gpd.GeoDataFrame
- get_enacted_plans(state, chamber='congress', year=None, format='shp')[source]
Find enacted redistricting plan datasets for a state.
- Parameters:
state (str) – Two-letter state abbreviation.
chamber (str) –
"congress","state_senate", or"state_house".year (str, optional) – Filter by year.
format (str, default "shp") – Dataset format filter. Only
"shp"is loadable viaload_shapefile(); other formats are returned as metadata only.
- Return type:
- get_precinct_data(state, year=None, format='shp')[source]
Find precinct-level datasets for a state.
- Parameters:
- Return type:
- get_cvap_data(state, year=None)[source]
Find CVAP (Citizen Voting Age Population) datasets.
- Parameters:
- Return type:
- get_pl94171_data(state, year=None)[source]
Find PL 94-171 redistricting data datasets.
- Parameters:
- Return type:
- static to_crosstab_input(df, geography_col='GEOID', variable_cols=None)[source]
Reshape an RDH DataFrame into long format for cross-tabulation.
Returns a DataFrame with columns
[geography, variable, value]suitable for use withsiege_utilities.data.cross_tabulation.contingency_table().- Parameters:
- Return type:
DataFrame with columns
geography,variable,value.
- class siege_utilities.geo.providers.redistricting_data_hub.RDHDataset[source]
Bases:
objectMetadata for a single RDH dataset entry.
- __init__(title, url, state='', format='', year='', geography='', dataset_type='', official=False, file_size='', raw=<factory>)
- class siege_utilities.geo.providers.redistricting_data_hub.RDHDataFormat[source]
-
File format for RDH downloads.
- CSV = 'csv'
- SHAPEFILE = 'shp'
- __new__(value)
- class siege_utilities.geo.providers.redistricting_data_hub.RDHDatasetType[source]
-
Categories of RDH datasets.
- PL94171 = 'pl94171'
- CVAP = 'cvap'
- ACS5 = 'acs5'
- ELECTION = 'election'
- LEGISLATIVE = 'legislative'
- PRECINCT = 'precinct'
- VOTER_FILE = 'voter_file'
- PROJECTION = 'projection'
- __new__(value)
- siege_utilities.geo.providers.redistricting_data_hub.polsby_popper(geometry, source_crs=None)[source]
Compute Polsby-Popper compactness score for a geometry.
Score = (4 * pi * area) / perimeter^2 Range: 0 (least compact) to 1 (circle).
- Parameters:
geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.
source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area before calculation. Pass the CRS (e.g., “EPSG:4326”) when calling with unprojected data.
- Return type:
- siege_utilities.geo.providers.redistricting_data_hub.reock(geometry, source_crs=None)[source]
Compute Reock compactness score for a geometry.
Score = area / area_of_minimum_bounding_circle Range: 0 (least compact) to 1 (circle).
- Parameters:
geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.
source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.
- Return type:
- siege_utilities.geo.providers.redistricting_data_hub.convex_hull_ratio(geometry, source_crs=None)[source]
Compute convex hull ratio for a geometry.
Score = area / convex_hull_area Range: 0 to 1.
- Parameters:
geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.
source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.
- Return type:
- siege_utilities.geo.providers.redistricting_data_hub.schwartzberg(geometry, source_crs=None)[source]
Compute Schwartzberg compactness score.
Score = 1 / (perimeter / circumference_of_equal_area_circle) Range: 0 to 1.
- Parameters:
geometry (shapely geometry) – Must be in a projected (equal-area) CRS for meaningful results.
source_crs (optional) – If provided and geographic, geometry is reprojected to equal-area.
- Return type:
- siege_utilities.geo.providers.redistricting_data_hub.compute_compactness(gdf, district_id_col='GEOID', geometry_col='geometry')[source]
Compute all compactness metrics for a GeoDataFrame of districts.
Automatically reprojects to an equal-area CRS (Mollweide) before calculating area-dependent metrics.
- class siege_utilities.geo.providers.redistricting_data_hub.CompactnessScores[source]
Bases:
objectCompactness metrics for a district geometry.
- siege_utilities.geo.providers.redistricting_data_hub.compare_plans(plan_a, plan_b, population_col='TOTPOP', district_id_col='GEOID')[source]
Compare two redistricting plans on key metrics.
- Parameters:
- Return type:
dict with comparison metrics.
- siege_utilities.geo.providers.redistricting_data_hub.demographic_profile(plan_gdf, census_gdf, district_id_col='GEOID', population_cols=None)[source]
Overlay Census ACS data onto a redistricting plan.
Performs a spatial join between district geometries and Census block/tract geometries, then aggregates demographics per district.
- Parameters:
plan_gdf (GeoDataFrame) – District plan with geometries.
census_gdf (GeoDataFrame) – Census data with geometries and demographic columns.
district_id_col (str) – Column in plan_gdf identifying districts.
population_cols (list of str, optional) – Demographic columns to aggregate. Defaults to common ACS fields.
- Return type:
DataFrame with one row per district and summed demographic columns.
- siege_utilities.geo.providers.redistricting_data_hub.fetch_enacted_plan(state, chamber='congress', year=None, client=None)[source]
Fetch enacted district plan as GeoDataFrame with boundaries + attributes.
- Parameters:
- Return type:
GeoDataFrame with district boundaries and attributes.
- Raises:
FileNotFoundError – If no matching enacted plan dataset is found.
- siege_utilities.geo.providers.redistricting_data_hub.fetch_precinct_results(state, year=None, client=None)[source]
Fetch precinct boundaries with election results as GeoDataFrame.
- siege_utilities.geo.providers.redistricting_data_hub.fetch_cvap(state, year=None, geography='tract', client=None)[source]
Fetch CVAP (Citizen Voting Age Population) data as DataFrame.
- siege_utilities.geo.providers.redistricting_data_hub.fetch_pl94171(state, year=None, geography='block', client=None)[source]
Fetch PL 94-171 redistricting population data as DataFrame.
- siege_utilities.geo.providers.redistricting_data_hub.fetch_demographic_summary(state, year=None, client=None)[source]
Fetch ACS 5-year demographic summary by district.
NCES data download infrastructure for locale boundaries and school locations.
Downloads pre-computed locale boundary polygons, school geocoded locations, and district administrative data from the NCES EDGE program.
Uses siege_utilities.files.remote for HTTP downloads and caches locally to avoid re-downloading.
- exception siege_utilities.geo.providers.nces_download.NCESDownloadError[source]
Bases:
ExceptionRaised when an NCES download fails.
- class siege_utilities.geo.providers.nces_download.NCESDownloader[source]
Bases:
objectDownload NCES locale boundaries, school locations, and district data.
Downloads from the NCES EDGE (Education Demographic and Geographic Estimates) program and returns data as GeoDataFrames.
- Parameters:
cache_dir – Directory for caching downloaded files. Defaults to a temporary directory.
timeout – HTTP request timeout in seconds.
Example:
downloader = NCESDownloader(cache_dir="./nces_cache") boundaries = downloader.download_locale_boundaries(2023) # GeoDataFrame with 12 locale territory polygons
- download_locale_boundaries(year=2023, *, crs=None)[source]
Download NCES locale boundary polygons.
Returns a GeoDataFrame with 12 rows — one polygon per locale territory (codes 11-43). Each polygon represents the geographic extent of that locale classification.
- Parameters:
year (int) – NCES publication year.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
locale_code, locale_category, locale_subcategory, name, geometry.
- Return type:
GeoDataFrame with columns
- download_school_locations(year=2023, state_abbr=None, *, crs=None)[source]
Download geocoded school locations.
Returns a GeoDataFrame of school point locations with NCES locale codes.
- Parameters:
year (int) – NCES publication year.
state_abbr (str | None) – Optional 2-letter state abbreviation to filter.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
ncessch, school_name, lea_id, state_abbr, locale_code, locale_category, locale_subcategory, geometry.
- Return type:
GeoDataFrame with columns
- download_district_data(year=2023)[source]
Download district administrative data with locale codes.
Returns a DataFrame (not GeoDataFrame) of school district administrative records keyed by LEA ID.
- Parameters:
year (int) – NCES publication year.
- Returns:
lea_id, lea_name, state_abbr, locale_code, locale_category, locale_subcategory, survey_year.
- Return type:
DataFrame with columns
NLRB data caching — disk persistence with TTL for NLRBFetchResult.
Stores fetched NLRB case data as JSON in ~/.siege_utilities/cache/nlrb/.
The loader orchestrates: cache hit → API fetch → cache write.
- class siege_utilities.geo.providers.nlrb_cache.NLRBCache[source]
Bases:
objectJSON disk cache for NLRB fetch results.
- Parameters:
cache_dir – Directory for cache files. Defaults to
~/.siege_utilities/cache/nlrb/.ttl_days – Cache entry lifetime in days. Default 30.
- get(key)[source]
Load a cached result if it exists and is not expired.
- Parameters:
key (str)
- Return type:
NLRBFetchResult | None
- put(key, result)[source]
Write a result to the cache.
- Parameters:
key (str)
result (NLRBFetchResult)
- Return type:
None
- class siege_utilities.geo.providers.nlrb_cache.NLRBLoader[source]
Bases:
objectCache-aware loader: cache hit → fetch → cache write.
- Parameters:
cache – NLRBCache instance. If None, creates default.
client – NLRBDataClient or similar with
fetch_all()method.
NLRB data source clients for case, election, and ULP data.
Three backends:
data.gov — XML dumps of C-Cases (ULP) and R-Cases (representation).
labordata GitHub — cleaned CSV/Parquet exports from labordata/nlrb-data.
NxGen Advanced Search — HTML scraping of the NLRB case search (fragile; marked as best-effort).
A unified NLRBDataClient facade reconciles records across sources.
- class siege_utilities.geo.providers.nlrb_clients.CaseStatus[source]
-
High-level case statuses.
- OPEN = 'Open'
- CLOSED = 'Closed'
- PENDING = 'Pending'
- UNKNOWN = 'Unknown'
- __new__(value)
- class siege_utilities.geo.providers.nlrb_clients.CaseType[source]
-
NLRB case type prefix codes.
- R = 'R'
- C = 'C'
- CB = 'CB'
- CD = 'CD'
- CE = 'CE'
- CA = 'CA'
- AC = 'AC'
- UC = 'UC'
- UD = 'UD'
- WH = 'WH'
- RM = 'RM'
- RD = 'RD'
- RC = 'RC'
- __new__(value)
- class siege_utilities.geo.providers.nlrb_clients.ElectionRecord[source]
Bases:
objectElection tally result linked to a case.
- __init__(case_number, tally_date=None, eligible_voters=None, votes_for=None, votes_against=None, void_ballots=None, union_name='', source='')
- class siege_utilities.geo.providers.nlrb_clients.NLRBCaseRecord[source]
Bases:
objectA single NLRB case record from any data source.
- __init__(case_number, case_type, case_name='', status='Unknown', date_filed=None, date_closed=None, region=None, city='', state='', union_name='', unit_description='', naics_code='', employee_count=None, source='')
- class siege_utilities.geo.providers.nlrb_clients.NLRBDataClient[source]
Bases:
objectUnified NLRB data client that reconciles across multiple sources.
Fetches from available backends and deduplicates by case number. When the same case appears in multiple sources, labordata is preferred (cleaned data), then data.gov, then NxGen.
- Parameters:
datagov_client – Optional pre-configured data.gov client.
labordata_client – Optional pre-configured labordata client.
nxgen_client – Optional pre-configured NxGen client.
use_nxgen – Whether to include the fragile NxGen source. Default False.
- SOURCE_PRIORITY = ('labordata', 'datagov', 'nxgen')
- __init__(datagov_client=None, labordata_client=None, nxgen_client=None, use_nxgen=False)[source]
- Parameters:
datagov_client (NLRBDatagovClient | None)
labordata_client (NLRBLabordataClient | None)
nxgen_client (NLRBNxGenClient | None)
use_nxgen (bool)
- class siege_utilities.geo.providers.nlrb_clients.NLRBDatagovClient[source]
Bases:
objectDownload and parse NLRB case data from data.gov XML exports.
The NLRB publishes case data through its open data portal. This client fetches the XML feed and parses it into structured records.
- Parameters:
timeout – HTTP request timeout in seconds.
base_url – Override the default data.gov endpoint.
- class siege_utilities.geo.providers.nlrb_clients.NLRBFetchResult[source]
Bases:
objectResult from a client fetch operation.
- cases: list[NLRBCaseRecord]
- elections: list[ElectionRecord]
- __init__(cases=<factory>, elections=<factory>, ulp_charges=<factory>, errors=<factory>, source='')
- Parameters:
cases (list[NLRBCaseRecord])
elections (list[ElectionRecord])
source (str)
- Return type:
None
- class siege_utilities.geo.providers.nlrb_clients.NLRBLabordataClient[source]
Bases:
objectDownload and parse NLRB data from the labordata/nlrb-data GitHub repo.
The labordata project maintains cleaned CSV exports of NLRB data. This client downloads the CSV files and parses them into structured records.
- Parameters:
timeout – HTTP request timeout in seconds.
base_url – Override the default GitHub raw content URL.
- class siege_utilities.geo.providers.nlrb_clients.NLRBNxGenClient[source]
Bases:
objectScrape NLRB case data from the NxGen Advanced Search page.
Warning
This client relies on HTML structure that may change without notice. Use it as a fallback when data.gov or labordata sources are insufficient. Prefer the other clients for bulk data.
- Parameters:
timeout – HTTP request timeout in seconds.
base_url – Override the default NxGen search URL.
- class siege_utilities.geo.providers.nlrb_clients.ULPRecord[source]
Bases:
objectUnfair labor practice charge.
DataFrame export for NLRB records.
Converts NLRBFetchResult or lists of dataclass records into pandas DataFrames for analysis.
- siege_utilities.geo.providers.nlrb_export.cases_to_dataframe(cases)[source]
Convert a list of NLRBCaseRecord to a DataFrame.
- Parameters:
cases – List of NLRBCaseRecord instances, or an NLRBFetchResult.
- Returns:
pandas DataFrame with one row per case.
- siege_utilities.geo.providers.nlrb_export.elections_to_dataframe(elections)[source]
Convert a list of ElectionRecord to a DataFrame.
- siege_utilities.geo.providers.nlrb_export.fetch_result_to_dataframes(result)[source]
Convert an NLRBFetchResult to a dict of DataFrames.
- Returns:
Dict with keys ‘cases’, ‘elections’, ‘ulp_charges’, each a DataFrame.
- siege_utilities.geo.providers.nlrb_export.ulp_charges_to_dataframe(charges)[source]
Convert a list of ULPRecord to a DataFrame.
NLRB region lookup, case-to-region assignment, and aggregation.
Pure-Python utilities — no Django dependency. Works with NLRBCaseRecord
dataclasses, dicts, or any object with region and state attributes.
- siege_utilities.geo.providers.nlrb_regions.aggregate_by_region(records)[source]
Group records by their NLRB region number.
- siege_utilities.geo.providers.nlrb_regions.assign_region(case_record)[source]
Assign an NLRB region number to a case record.
Uses the region number from the case number if available, otherwise falls back to state-based lookup (first match).
- Parameters:
case_record – Object with
regionandstateattributes, or a dict with those keys.- Returns:
Region number or None if not determinable.
- Return type:
int | None
Census Data Intelligence
Census API subpackage — split components of CensusAPIClient.
Each module handles one concern:
variable_registry — variable groups, descriptions, lookup, metadata
dataset_selector — pure-logic dataset/geography validation and URL building
api — HTTP transport, caching, rate limiting, response processing
catalog — hierarchical table metadata (Dataset → Subject → Family → Table → Variable)
catalog_populator — fetches metadata from Census API to populate the catalog
- class siege_utilities.geo.census.CatalogCache[source]
Bases:
objectDisk-backed JSON cache with TTL for CensusCatalog instances.
- get(dataset, year)[source]
- Parameters:
- Return type:
CensusCatalog | None
- put(dataset, year, catalog)[source]
- Parameters:
dataset (str)
year (int)
catalog (CensusCatalog)
- Return type:
None
- class siege_utilities.geo.census.CatalogLoader[source]
Bases:
objectCache-aware catalog loader.
Resolution order: cache → API fetch → cache write.
- __init__(cache=None, base_url='')[source]
- Parameters:
cache (CatalogCache | None)
base_url (str)
- class siege_utilities.geo.census.CensusAPI[source]
Bases:
objectHTTP transport layer for Census Bureau API.
Responsibilities: API key resolution, request building, retry/rate-limit handling, caching (parquet or Django), and response normalization.
- __init__(api_key=None, timeout=None, cache_dir=None, cache_backend='parquet', cache_ttl=86400)[source]
- fetch_data(variables, year, dataset='acs5', geography='county', state_fips=None, county_fips=None, include_moe=True)[source]
Fetch demographic data from the Census API.
Delegates variable resolution to VariableRegistry and geography validation to DatasetSelector, then handles the HTTP round-trip.
- Parameters:
variables (str | List[str]) – Variable name(s) to fetch (resolved via VariableRegistry).
year (int) – Census data year.
dataset (str) – Census dataset identifier (e.g., ‘acs5’, ‘dec/sf1’).
geography (str) – Geographic level (e.g., ‘county’, ‘tract’, ‘zcta’).
state_fips (str | None) – State FIPS filter (required for sub-state geographies).
county_fips (str | None) – County FIPS filter.
include_moe (bool) – If True and dataset is ACS, append margin-of-error variables. Silently ignored for non-ACS datasets.
- Return type:
- clear_cache()[source]
Clear cached API responses from the parquet file cache.
Only clears the local parquet cache. Has no effect when
cache_backend='django'(Django cache must be cleared viadjango.core.cache.cache.clear()).- Return type:
None
- class siege_utilities.geo.census.CensusCatalog[source]
Bases:
object- add_table(table)[source]
- Parameters:
table (CensusTable)
- Return type:
None
- add_tables(tables)[source]
- Parameters:
tables (list[CensusTable])
- Return type:
None
- add_family(family)[source]
- Parameters:
family (CensusFamily)
- Return type:
None
- add_subject(subject)[source]
- Parameters:
subject (CensusSubject)
- Return type:
None
- add_dataset(dataset)[source]
- Parameters:
dataset (CensusCatalogDataset)
- Return type:
None
- get_table(table_id)[source]
- Parameters:
table_id (str)
- Return type:
CensusTable | None
- get_family(family_id)[source]
- Parameters:
family_id (str)
- Return type:
CensusFamily | None
- get_subject(subject_id)[source]
- Parameters:
subject_id (str)
- Return type:
CensusSubject | None
- get_dataset(dataset_id)[source]
- Parameters:
dataset_id (str)
- Return type:
CensusCatalogDataset | None
- property tables: dict[str, CensusTable]
- property families: dict[str, CensusFamily]
- property subjects: dict[str, CensusSubject]
- property datasets: dict[str, CensusCatalogDataset]
- search(query, level=None, dataset=None, max_results=25)[source]
Search the catalog across multiple levels.
- Parameters:
query (str) – Free-text search query.
level (SearchLevel | None) – Restrict to a single search level, or None for all.
dataset (str | None) – Filter TABLE and VARIABLE results to tables belonging to this dataset. Has no effect on FAMILY, SUBJECT, or DATASET level results (those are cross-dataset).
max_results (int) – Maximum results to return.
- Return type:
- class siege_utilities.geo.census.CensusCatalogPopulator[source]
Bases:
object
- class siege_utilities.geo.census.CensusCatalogDataset[source]
Bases:
objectCensusCatalogDataset(dataset_id: ‘str’, survey_type: ‘str’, year: ‘int’, subjects: ‘list[CensusSubject]’ = <factory>, tables: ‘dict[str, CensusTable]’ = <factory>)
- subjects: list[CensusSubject]
- tables: dict[str, CensusTable]
- __init__(dataset_id, survey_type, year, subjects=<factory>, tables=<factory>)
- Parameters:
dataset_id (str)
survey_type (str)
year (int)
subjects (list[CensusSubject])
tables (dict[str, CensusTable])
- Return type:
None
- class siege_utilities.geo.census.CensusFamily[source]
Bases:
objectCensusFamily(family_id: ‘str’, family_type: ‘FamilyType’, root_table_id: ‘str’, tables: ‘list[CensusTable]’ = <factory>, description: ‘str’ = ‘’)
- family_type: FamilyType
- tables: list[CensusTable]
- __init__(family_id, family_type, root_table_id, tables=<factory>, description='')
- Parameters:
family_id (str)
family_type (FamilyType)
root_table_id (str)
tables (list[CensusTable])
description (str)
- Return type:
None
- class siege_utilities.geo.census.CensusSubject[source]
Bases:
objectCensusSubject(subject_id: ‘str’, label: ‘str’, families: ‘list[CensusFamily]’ = <factory>, tables: ‘list[CensusTable]’ = <factory>)
- families: list[CensusFamily]
- tables: list[CensusTable]
- property all_tables: list[CensusTable]
- __init__(subject_id, label, families=<factory>, tables=<factory>)
- Parameters:
subject_id (str)
label (str)
families (list[CensusFamily])
tables (list[CensusTable])
- Return type:
None
- class siege_utilities.geo.census.CensusTable[source]
Bases:
objectCensusTable(table_id: ‘str’, label: ‘str’, concept: ‘str’, universe: ‘str’ = ‘’, variables: ‘list[CensusVariable]’ = <factory>, geography_levels: ‘list[str]’ = <factory>)
- variables: list[CensusVariable]
- class siege_utilities.geo.census.CensusVariable[source]
Bases:
objectCensusVariable(code: ‘str’, label: ‘str’, concept: ‘str’, table_id: ‘str’, stat_type: ‘str’ = ‘E’)
- class siege_utilities.geo.census.DatasetSelector[source]
Bases:
objectHandles dataset path resolution and geography validation/normalization for Census API queries. All methods are stateless class/static methods.
- API_SUPPORTED_GEOGRAPHIES = frozenset({'block_group', 'county', 'place', 'state', 'tract', 'zcta'})
- static get_dataset_path(year, dataset)[source]
Map a dataset identifier + year to the Census API URL path segment.
- Parameters:
- Returns:
URL path segment, e.g. ‘2020/acs/acs5’
- Raises:
ValueError – If dataset is unknown.
- Return type:
- classmethod validate_geography(geography, state_fips, county_fips)[source]
Validate and normalize a geography string to its canonical form.
- Parameters:
- Returns:
Canonical geography string (e.g. ‘block_group’)
- Raises:
ValueError – If geography is invalid or required FIPS codes are missing.
- Return type:
- static build_geography_clause(geography, state_fips, county_fips)[source]
Build the
for=/in=geography clause for the Census API URL.
- static normalize_state(state_input)[source]
Normalize a state name, abbreviation, or FIPS code to its FIPS code.
- Raises:
ValueError – If the state cannot be resolved.
- Parameters:
state_input (str)
- Return type:
- class siege_utilities.geo.census.FamilyType[source]
Bases:
Enum- RACE_ITERATION = 'race_iteration'
- TOPICAL_KINSHIP = 'topical_kinship'
- class siege_utilities.geo.census.SearchLevel[source]
Bases:
Enum- DATASET = 'dataset'
- SUBJECT = 'subject'
- FAMILY = 'family'
- TABLE = 'table'
- VARIABLE = 'variable'
- class siege_utilities.geo.census.SearchResult[source]
Bases:
objectSearchResult(level: ‘SearchLevel’, id: ‘str’, label: ‘str’, score: ‘float’, obj: ‘object’)
- level: SearchLevel
- class siege_utilities.geo.census.VariableRegistry[source]
Bases:
objectRegistry for Census variable groups, descriptions, and metadata lookup.
Pure data + logic — all methods are stateless except optional API calls for unknown variable metadata.
- get_variable_metadata(variable, year, dataset='acs5', base_url=None, timeout=30)[source]
Get metadata for a Census variable.
Checks local descriptions first, falls back to API if needed.
HTTP transport, caching, and response processing for Census API queries.
Handles all I/O: network requests, file/Django caching, rate limiting, retry logic, and DataFrame post-processing.
- class siege_utilities.geo.census.api.CensusAPI[source]
Bases:
objectHTTP transport layer for Census Bureau API.
Responsibilities: API key resolution, request building, retry/rate-limit handling, caching (parquet or Django), and response normalization.
- __init__(api_key=None, timeout=None, cache_dir=None, cache_backend='parquet', cache_ttl=86400)[source]
- fetch_data(variables, year, dataset='acs5', geography='county', state_fips=None, county_fips=None, include_moe=True)[source]
Fetch demographic data from the Census API.
Delegates variable resolution to VariableRegistry and geography validation to DatasetSelector, then handles the HTTP round-trip.
- Parameters:
variables (str | List[str]) – Variable name(s) to fetch (resolved via VariableRegistry).
year (int) – Census data year.
dataset (str) – Census dataset identifier (e.g., ‘acs5’, ‘dec/sf1’).
geography (str) – Geographic level (e.g., ‘county’, ‘tract’, ‘zcta’).
state_fips (str | None) – State FIPS filter (required for sub-state geographies).
county_fips (str | None) – County FIPS filter.
include_moe (bool) – If True and dataset is ACS, append margin-of-error variables. Silently ignored for non-ACS datasets.
- Return type:
- clear_cache()[source]
Clear cached API responses from the parquet file cache.
Only clears the local parquet cache. Has no effect when
cache_backend='django'(Django cache must be cleared viadjango.core.cache.cache.clear()).- Return type:
None
Census table catalog — hierarchical metadata for Census tables and variables.
Hierarchy: Dataset → Subject → Family → Table → Variable
The key abstraction is Family, which the Census Bureau does not expose natively. Two family types exist:
Race iteration families: tables like B19001 / B19001A–I share a root table; the letter suffix signals the race/ethnicity iteration.
Topical kinship families: tables like B19001, B19013, B19025 share a subject-area prefix and related concepts (all income-related).
Both family types are first-class objects in the catalog.
- class siege_utilities.geo.census.catalog.CensusCatalog[source]
Bases:
object- add_table(table)[source]
- Parameters:
table (CensusTable)
- Return type:
None
- add_tables(tables)[source]
- Parameters:
tables (list[CensusTable])
- Return type:
None
- add_family(family)[source]
- Parameters:
family (CensusFamily)
- Return type:
None
- add_subject(subject)[source]
- Parameters:
subject (CensusSubject)
- Return type:
None
- add_dataset(dataset)[source]
- Parameters:
dataset (CensusCatalogDataset)
- Return type:
None
- get_table(table_id)[source]
- Parameters:
table_id (str)
- Return type:
CensusTable | None
- get_family(family_id)[source]
- Parameters:
family_id (str)
- Return type:
CensusFamily | None
- get_subject(subject_id)[source]
- Parameters:
subject_id (str)
- Return type:
CensusSubject | None
- get_dataset(dataset_id)[source]
- Parameters:
dataset_id (str)
- Return type:
CensusCatalogDataset | None
- property tables: dict[str, CensusTable]
- property families: dict[str, CensusFamily]
- property subjects: dict[str, CensusSubject]
- property datasets: dict[str, CensusCatalogDataset]
- search(query, level=None, dataset=None, max_results=25)[source]
Search the catalog across multiple levels.
- Parameters:
query (str) – Free-text search query.
level (SearchLevel | None) – Restrict to a single search level, or None for all.
dataset (str | None) – Filter TABLE and VARIABLE results to tables belonging to this dataset. Has no effect on FAMILY, SUBJECT, or DATASET level results (those are cross-dataset).
max_results (int) – Maximum results to return.
- Return type:
- class siege_utilities.geo.census.catalog.CensusCatalogDataset[source]
Bases:
objectCensusCatalogDataset(dataset_id: ‘str’, survey_type: ‘str’, year: ‘int’, subjects: ‘list[CensusSubject]’ = <factory>, tables: ‘dict[str, CensusTable]’ = <factory>)
- subjects: list[CensusSubject]
- tables: dict[str, CensusTable]
- __init__(dataset_id, survey_type, year, subjects=<factory>, tables=<factory>)
- Parameters:
dataset_id (str)
survey_type (str)
year (int)
subjects (list[CensusSubject])
tables (dict[str, CensusTable])
- Return type:
None
- class siege_utilities.geo.census.catalog.CensusFamily[source]
Bases:
objectCensusFamily(family_id: ‘str’, family_type: ‘FamilyType’, root_table_id: ‘str’, tables: ‘list[CensusTable]’ = <factory>, description: ‘str’ = ‘’)
- family_type: FamilyType
- tables: list[CensusTable]
- __init__(family_id, family_type, root_table_id, tables=<factory>, description='')
- Parameters:
family_id (str)
family_type (FamilyType)
root_table_id (str)
tables (list[CensusTable])
description (str)
- Return type:
None
- class siege_utilities.geo.census.catalog.CensusSubject[source]
Bases:
objectCensusSubject(subject_id: ‘str’, label: ‘str’, families: ‘list[CensusFamily]’ = <factory>, tables: ‘list[CensusTable]’ = <factory>)
- families: list[CensusFamily]
- tables: list[CensusTable]
- property all_tables: list[CensusTable]
- __init__(subject_id, label, families=<factory>, tables=<factory>)
- Parameters:
subject_id (str)
label (str)
families (list[CensusFamily])
tables (list[CensusTable])
- Return type:
None
- class siege_utilities.geo.census.catalog.CensusTable[source]
Bases:
objectCensusTable(table_id: ‘str’, label: ‘str’, concept: ‘str’, universe: ‘str’ = ‘’, variables: ‘list[CensusVariable]’ = <factory>, geography_levels: ‘list[str]’ = <factory>)
- variables: list[CensusVariable]
- class siege_utilities.geo.census.catalog.CensusVariable[source]
Bases:
objectCensusVariable(code: ‘str’, label: ‘str’, concept: ‘str’, table_id: ‘str’, stat_type: ‘str’ = ‘E’)
- class siege_utilities.geo.census.catalog.FamilyType[source]
Bases:
Enum- RACE_ITERATION = 'race_iteration'
- TOPICAL_KINSHIP = 'topical_kinship'
- class siege_utilities.geo.census.catalog.SearchLevel[source]
Bases:
Enum- DATASET = 'dataset'
- SUBJECT = 'subject'
- FAMILY = 'family'
- TABLE = 'table'
- VARIABLE = 'variable'
- class siege_utilities.geo.census.catalog.SearchResult[source]
Bases:
objectSearchResult(level: ‘SearchLevel’, id: ‘str’, label: ‘str’, score: ‘float’, obj: ‘object’)
- level: SearchLevel
- siege_utilities.geo.census.catalog.detect_race_iteration_families(tables)[source]
- Parameters:
tables (list[CensusTable])
- Return type:
- siege_utilities.geo.census.catalog.detect_topical_families(tables, max_number_gap=100)[source]
- Parameters:
tables (list[CensusTable])
max_number_gap (int)
- Return type:
Census catalog caching — disk persistence with TTL for CensusCatalog instances.
Stores serialized catalogs as JSON in ~/.siege_utilities/cache/census_catalog/.
Follows the same cache-dir convention as CensusAPI and
PLFileDownloader.
The loader orchestrates: cache hit → API fetch → cache write.
- class siege_utilities.geo.census.catalog_cache.CatalogCache[source]
Bases:
objectDisk-backed JSON cache with TTL for CensusCatalog instances.
- get(dataset, year)[source]
- Parameters:
- Return type:
CensusCatalog | None
- put(dataset, year, catalog)[source]
- Parameters:
dataset (str)
year (int)
catalog (CensusCatalog)
- Return type:
None
- class siege_utilities.geo.census.catalog_cache.CatalogLoader[source]
Bases:
objectCache-aware catalog loader.
Resolution order: cache → API fetch → cache write.
- __init__(cache=None, base_url='')[source]
- Parameters:
cache (CatalogCache | None)
base_url (str)
Census catalog documentation generator.
Produces Markdown documentation from a CensusCatalog instance: - Index page with Subject → Family → Table tree - Per-table pages with variable list, geography availability, dataset membership
- siege_utilities.geo.census.catalog_docs.generate_catalog_docs(catalog, output_dir, title='Census Table Catalog')[source]
- Parameters:
catalog (CensusCatalog)
output_dir (Path)
title (str)
- Return type:
- siege_utilities.geo.census.catalog_docs.generate_catalog_markdown(catalog, title='Census Table Catalog')[source]
- Parameters:
catalog (CensusCatalog)
title (str)
- Return type:
Census catalog populator — fetches metadata from Census API discovery endpoints.
Populates CensusCatalog instances from:
- /data/{year}/{dataset}/variables.json — variable definitions
- /data/{year}/{dataset}/groups.json — concept groupings
Separated from catalog.py to keep the data model I/O-free.
- class siege_utilities.geo.census.catalog_populator.CensusCatalogPopulator[source]
Bases:
object
Dataset selection and geography validation for Census API queries.
Pure logic — no I/O, no network calls. Fully unit-testable without mocks.
- class siege_utilities.geo.census.dataset_selector.DatasetSelector[source]
Bases:
objectHandles dataset path resolution and geography validation/normalization for Census API queries. All methods are stateless class/static methods.
- API_SUPPORTED_GEOGRAPHIES = frozenset({'block_group', 'county', 'place', 'state', 'tract', 'zcta'})
- static get_dataset_path(year, dataset)[source]
Map a dataset identifier + year to the Census API URL path segment.
- Parameters:
- Returns:
URL path segment, e.g. ‘2020/acs/acs5’
- Raises:
ValueError – If dataset is unknown.
- Return type:
- classmethod validate_geography(geography, state_fips, county_fips)[source]
Validate and normalize a geography string to its canonical form.
- Parameters:
- Returns:
Canonical geography string (e.g. ‘block_group’)
- Raises:
ValueError – If geography is invalid or required FIPS codes are missing.
- Return type:
- static build_geography_clause(geography, state_fips, county_fips)[source]
Build the
for=/in=geography clause for the Census API URL.
- static normalize_state(state_input)[source]
Normalize a state name, abbreviation, or FIPS code to its FIPS code.
- Raises:
ValueError – If the state cannot be resolved.
- Parameters:
state_input (str)
- Return type:
Last-fetched-vintage state tracking for Census TIGER pulls.
Supports cron-friendly “is there a new vintage published?” checks. The state file holds a single integer (the year most recently downloaded).
Usage:
from pathlib import Path
from siege_utilities.geo.census.tiger_state import check_for_updates
state = Path('/var/lib/sw/tiger.year')
has_update, latest = check_for_updates(state)
if has_update:
# download via CensusTIGERProvider.get_boundary(...)
set_last_fetched_vintage(state, latest)
- siege_utilities.geo.census.tiger_state.get_last_fetched_vintage(state_file)[source]
Read the last-fetched vintage year from state_file.
Returns None when the file is absent or unparseable.
- siege_utilities.geo.census.tiger_state.set_last_fetched_vintage(state_file, year)[source]
Write year to state_file. Creates parent dirs if needed.
- siege_utilities.geo.census.tiger_state.check_for_updates(state_file, *, provider=None)[source]
Return (has_update, latest_year).
has_update is True when the latest published vintage is greater than the recorded last-fetched year, OR when no state file exists. Returns (False, None) when discovery fails (offline / endpoint down).
provider defaults to a fresh CensusTIGERProvider; pass a pre-built one to share session state or to inject a test double.
Variable registry for Census API variable groups, descriptions, and metadata.
Pure data + lookup logic — no I/O except optional API calls for unknown variables.
- class siege_utilities.geo.census.variable_registry.VariableRegistry[source]
Bases:
objectRegistry for Census variable groups, descriptions, and metadata lookup.
Pure data + logic — all methods are stateless except optional API calls for unknown variable metadata.
- get_variable_metadata(variable, year, dataset='acs5', base_url=None, timeout=30)[source]
Get metadata for a Census variable.
Checks local descriptions first, falls back to API if needed.
Census API Client for fetching demographic data from the Census Bureau.
This module provides a client for fetching demographic data (population, income, education, etc.) from the Census Bureau API. The data can be joined with existing TIGER/Line geometries for spatial analysis.
- Example usage:
from siege_utilities.geo import CensusAPIClient, get_demographics
# Convenience function df = get_demographics(state=’California’, geography=’county’, year=2020)
# Direct client usage client = CensusAPIClient() df = client.fetch_data(
variables=’income’, year=2020, dataset=’acs5’, geography=’tract’, state_fips=’06’, county_fips=’037’, include_moe=True
)
Implementation is split across three focused modules under census/:
census.variable_registry— variable groups, descriptions, metadatacensus.dataset_selector— pure-logic geography/dataset validationcensus.api— HTTP transport, caching, retry, response processing
This file provides the backward-compatible CensusAPIClient facade.
- exception siege_utilities.geo.census_api_client.CensusAPIError[source]
Bases:
ExceptionBase exception for Census API errors.
- exception siege_utilities.geo.census_api_client.CensusAPIKeyError[source]
Bases:
CensusAPIErrorError related to API key authentication.
- exception siege_utilities.geo.census_api_client.CensusRateLimitError[source]
Bases:
CensusAPIErrorRate limit exceeded error.
- exception siege_utilities.geo.census_api_client.CensusVariableError[source]
Bases:
CensusAPIErrorInvalid or unavailable variable error.
- exception siege_utilities.geo.census_api_client.CensusGeographyError[source]
Bases:
CensusAPIErrorInvalid geography specification error.
- class siege_utilities.geo.census_api_client.CensusAPIClient[source]
Bases:
objectClient for fetching demographic data from the Census Bureau API.
This is a backward-compatible facade that delegates to:
CensusAPIfor HTTP, caching, and retry logicDatasetSelectorfor geography/dataset validationVariableRegistryfor variable resolution and metadata
All existing method signatures are preserved.
- __init__(api_key=None, timeout=None, cache_dir=None, cache_backend='parquet', cache_ttl=86400)[source]
- property api_key
- property timeout
- property base_url
- property cache_dir
- property cache_backend
- property cache_ttl
- fetch_data(variables, year, dataset='acs5', geography='county', state_fips=None, county_fips=None, include_moe=True)[source]
Fetch demographic data from the Census API.
- siege_utilities.geo.census_api_client.get_demographics(state, geography='county', year=None, variables='demographics_basic', include_moe=True, dataset='acs5')[source]
Convenience function to get demographic data.
- Parameters:
state (str) – State name, abbreviation, or FIPS code
geography (str) – Geographic level (‘county’, ‘tract’, ‘block_group’)
year (int | None) – Census year (defaults to most recent available)
variables (str) – Variable group name or list of variables
include_moe (bool) – Include margin of error columns
dataset (str) – Census dataset (‘acs5’, ‘acs1’, ‘dec’)
- Returns:
DataFrame with demographic data
- Return type:
- siege_utilities.geo.census_api_client.get_population(state, geography='tract', year=None, dataset='acs5')[source]
Convenience function to get population data.
- Parameters:
- Return type:
- siege_utilities.geo.census_api_client.get_income_data(state, geography='tract', year=None, include_moe=True, dataset='acs5')[source]
Convenience function to get income data.
- siege_utilities.geo.census_api_client.get_education_data(state, geography='tract', year=None, include_moe=True, dataset='acs5')[source]
Convenience function to get educational attainment data.
- siege_utilities.geo.census_api_client.get_housing_data(state, geography='tract', year=None, include_moe=True, dataset='acs5')[source]
Convenience function to get housing data.
- siege_utilities.geo.census_api_client.get_census_api_client()[source]
Get or create the default Census API client.
- Return type:
- siege_utilities.geo.census_api_client.get_census_data_with_geometry(year, geography, variables, state=None, county_fips=None, dataset='acs5', include_moe=True)[source]
Fetch Census boundaries AND demographic data, returning a merged GeoDataFrame.
This is the primary function for getting choropleth-ready data. It: 1. Fetches TIGER/Line geometry for the specified geography 2. Fetches demographic variables from the Census API 3. Joins them on GEOID
- siege_utilities.geo.census_api_client.join_demographics_to_shapes(shapes_gdf, demographics_df, shapes_geoid_col=None, demographics_geoid_col='GEOID', how='left')[source]
Join demographic data to existing shapes GeoDataFrame.
Use this when you already have shapes loaded and want to add demographic data.
- Parameters:
shapes_gdf (gpd.GeoDataFrame)
demographics_df (pandas.DataFrame)
shapes_geoid_col (str | None)
demographics_geoid_col (str)
how (str)
- Return type:
gpd.GeoDataFrame
Intelligent Census Data Selector
This module provides intelligent functions for automatically selecting the best Census datasets based on analysis requirements, geography needs, and time sensitivity.
- class siege_utilities.geo.census_data_selector.CensusDataSelector[source]
Bases:
objectIntelligent selector for Census data based on analysis requirements.
This class automatically determines the best Census datasets to use for different types of analysis, considering factors like: - Geography level requirements - Time sensitivity - Data reliability needs - Variable requirements - Analysis purpose
- select_datasets_for_analysis(analysis_type, geography_level, time_period=None, variables=None, reliability_requirement=None)[source]
Select the best Census datasets for a specific analysis.
- Parameters:
analysis_type (str) – Type of analysis (e.g., “demographics”, “housing”, “business”)
geography_level (str | GeographyLevel) – Required geography level
time_period (str | None) – Specific time period (optional)
variables (List[str] | None) – Required variables (optional)
reliability_requirement (str | None) – Reliability requirement (“low”, “medium”, “high”)
- Returns:
Dictionary with dataset recommendations and rationale. Suitability scores use 0-5 scale: 0-2.0 (poor), 2.1-3.0 (moderate), 3.1-4.0 (good), 4.1-5.0 (excellent match)
- Return type:
- get_dataset_compatibility_matrix(analysis_types=None)[source]
Generate a compatibility matrix showing which datasets work best for different analysis types.
- siege_utilities.geo.census_data_selector.get_census_data_selector()[source]
Get a configured Census data selector instance.
- Return type:
- siege_utilities.geo.census_data_selector.select_census_datasets(analysis_type, geography_level, time_period=None, variables=None)[source]
Convenience function to select Census datasets for analysis.
- siege_utilities.geo.census_data_selector.select_datasets_for_analysis(analysis_type, geography_level, time_period=None, variables=None)[source]
Standalone function to select datasets for analysis.
- Parameters:
- Returns:
Dataset recommendations and rationale
- Return type:
- siege_utilities.geo.census_data_selector.get_dataset_compatibility_matrix(analysis_types=None)[source]
Standalone function to get dataset compatibility matrix.
- siege_utilities.geo.census_data_selector.get_analysis_approach(analysis_type, geography_level, time_constraints=None)[source]
Convenience function to get analysis approach recommendations.
- siege_utilities.geo.census_data_selector.suggest_analysis_approach(analysis_type, geography_level, time_constraints=None)[source]
Standalone function to suggest analysis approach.
Census Dataset Relationship Mapper
This module provides comprehensive mapping and relationship information for Census datasets, making it easier to understand which datasets to use for different analysis needs.
- class siege_utilities.geo.census_dataset_mapper.SurveyType[source]
Bases:
EnumEnumeration of Census survey types.
- DECENNIAL = 'decennial'
- ACS_1YR = 'acs_1yr'
- ACS_3YR = 'acs_3yr'
- ACS_5YR = 'acs_5yr'
- CENSUS_BUSINESS = 'census_business'
- POPULATION_ESTIMATES = 'population_estimates'
- HOUSING_ESTIMATES = 'housing_estimates'
- class siege_utilities.geo.census_dataset_mapper.GeographyLevel[source]
Bases:
EnumEnumeration of Census geography levels (canonical names).
- NATION = 'nation'
- REGION = 'region'
- DIVISION = 'division'
- STATE = 'state'
- COUNTY = 'county'
- COUSUB = 'cousub'
- PLACE = 'place'
- CD = 'cd'
- SLDU = 'sldu'
- SLDL = 'sldl'
- TRACT = 'tract'
- BLOCK_GROUP = 'block_group'
- BLOCK = 'block'
- ZCTA = 'zcta'
- CBSA = 'cbsa'
- PUMA = 'puma'
- VTD = 'vtd'
- VTD20 = 'vtd20'
- TABBLOCK20 = 'tabblock20'
- TABBLOCK10 = 'tabblock10'
- COUNTY_SUBDIVISION = 'cousub'
- CONGRESSIONAL_DISTRICT = 'cd'
- STATE_LEGISLATIVE_DISTRICT = 'sldu'
- ZIP_CODE = 'zcta'
- VOTING_DISTRICT = 'vtd'
- class siege_utilities.geo.census_dataset_mapper.DataReliability[source]
Bases:
EnumEnumeration of data reliability levels.
- HIGH = 'high'
- MEDIUM = 'medium'
- LOW = 'low'
- ESTIMATED = 'estimated'
- class siege_utilities.geo.census_dataset_mapper.CensusDataset[source]
Bases:
objectRepresents a Census dataset with metadata.
- survey_type: SurveyType
- geography_levels: List[GeographyLevel]
- reliability: DataReliability
- __init__(dataset_id, name, survey_type, geography_levels, time_period, reliability, description, variables, limitations, best_for, alternatives, data_quality_notes, last_updated, next_update, api_endpoint=None, download_url=None)
- Parameters:
dataset_id (str)
name (str)
survey_type (SurveyType)
geography_levels (List[GeographyLevel])
time_period (str)
reliability (DataReliability)
description (str)
data_quality_notes (str)
last_updated (str)
next_update (str)
api_endpoint (str | None)
download_url (str | None)
- Return type:
None
- class siege_utilities.geo.census_dataset_mapper.DatasetRelationship[source]
Bases:
objectRepresents a relationship between Census datasets.
- __init__(primary_dataset, related_dataset, relationship_type, description, when_to_use_primary, when_to_use_related, overlap_period=None, compatibility_score=1.0)
- class siege_utilities.geo.census_dataset_mapper.CensusDatasetMapper[source]
Bases:
objectMaps relationships between Census datasets and provides intelligent data selection.
This class helps users understand: - Which datasets are available for different geography levels - How different survey types relate to each other - When to use different datasets based on analysis needs - Data quality and reliability considerations
- datasets: Dict[str, CensusDataset]
- relationships: List[DatasetRelationship]
- get_dataset_info(dataset_name)[source]
Get information about a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
CensusDataset | None
- list_datasets_by_type(survey_type)[source]
List all datasets of a specific survey type.
- Parameters:
survey_type (SurveyType)
- Return type:
- list_datasets_by_geography(geography_level)[source]
List all datasets available for a specific geography level.
- Parameters:
geography_level (GeographyLevel)
- Return type:
- get_best_dataset_for_use_case(use_case, geography_level, time_period=None)[source]
Get the best dataset(s) for a specific use case.
- Parameters:
use_case (str) – Description of what you’re trying to analyze
geography_level (GeographyLevel) – Required geography level
time_period (str | None) – Specific time period (optional)
- Returns:
List of recommended datasets, ranked by suitability
- Return type:
- get_dataset_relationships(dataset_name)[source]
Get all relationships for a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
- get_data_selection_guide(analysis_type, geography_level, time_sensitivity='medium')[source]
Get a comprehensive guide for selecting Census data.
- Parameters:
analysis_type (str) – Type of analysis (e.g., “demographics”, “business”, “housing”)
geography_level (GeographyLevel) – Required geography level
time_sensitivity (str) – How time-sensitive the analysis is (“low”, “medium”, “high”)
- Returns:
Dictionary with data selection recommendations
- Return type:
- siege_utilities.geo.census_dataset_mapper.get_census_dataset_mapper()[source]
Get a configured Census dataset mapper instance.
- Return type:
- siege_utilities.geo.census_dataset_mapper.get_best_dataset_for_analysis(analysis_type, geography_level, time_sensitivity='medium')[source]
Convenience function to get the best dataset for analysis.
- siege_utilities.geo.census_dataset_mapper.get_dataset_info(dataset_name)[source]
Convenience function to get information about a specific dataset.
- Parameters:
dataset_name (str)
- Return type:
CensusDataset | None
- siege_utilities.geo.census_dataset_mapper.list_datasets_by_type(survey_type)[source]
Convenience function to list all datasets of a specific survey type.
- Parameters:
survey_type (str)
- Return type:
- siege_utilities.geo.census_dataset_mapper.list_datasets_by_geography(geography_level)[source]
Convenience function to list all datasets for a specific geography level.
- Parameters:
geography_level (str)
- Return type:
- siege_utilities.geo.census_dataset_mapper.get_dataset_relationships(dataset_name)[source]
Convenience function to get relationships for a dataset.
- Parameters:
dataset_name (str)
- Return type:
- siege_utilities.geo.census_dataset_mapper.get_best_dataset_for_use_case(use_case, geography_level='county', time_sensitivity='medium')[source]
Convenience function to get the best dataset for a specific use case.
- siege_utilities.geo.census_dataset_mapper.export_dataset_catalog(filepath)[source]
Convenience function to export the dataset catalog.
- Parameters:
filepath (str)
- siege_utilities.geo.census_dataset_mapper.get_data_selection_guide(analysis_type, geography_level, time_sensitivity='medium')[source]
Convenience function to get data selection guide.
- siege_utilities.geo.census_dataset_mapper.compare_datasets(dataset1_name, dataset2_name)[source]
Convenience function to compare two datasets.
- siege_utilities.geo.census_dataset_mapper.compare_census_datasets(dataset1_name, dataset2_name)[source]
Convenience function to compare two Census datasets.
Deprecation shim — re-exports from siege_utilities.geo.providers.census_geocoder.
Moved during ELE-2438. Will be removed in v4.0.0.
- exception siege_utilities.geo.census_geocoder.CensusGeocodeError[source]
Bases:
RuntimeErrorRaised when the Census geocoder API call fails unexpectedly.
Distinct from “no match” results (which return a CensusGeocodeResult with matched=False). This exception indicates an API / network / parse failure where the geocoder could not even attempt to match. Use __cause__ to inspect the underlying exception.
- class siege_utilities.geo.census_geocoder.CensusGeocodeResult[source]
Bases:
objectResult from Census Bureau geocoding.
- __init__(matched=False, input_address='', matched_address='', lat=None, lon=None, state_fips='', county_fips='', tract='', block='', match_type='No_Match', side='', tiger_line_id='', input_id='')
- class siege_utilities.geo.census_geocoder.CensusVintage[source]
-
Census geocoder benchmark/vintage pairs.
Each member’s value encodes
<benchmark>|<vintage>where both halves are the exact strings the Census Geocoder API expects. Use the.benchmarkand.vintageproperties to extract them.The benchmark names follow the
Public_AR_<label>pattern used byhttps://geocoding.geo.census.gov/geocoder/benchmarks. The vintage names follow the<label>_<benchmark-label>pattern returned byhttps://geocoding.geo.census.gov/geocoder/vintages?benchmark=….- CENSUS_2010 = 'Public_AR_Census2020|Census2010_Census2020'
- CENSUS_2020 = 'Public_AR_Census2020|Census2020_Census2020'
- CURRENT = 'Public_AR_Current|Current_Current'
- ACS_2022 = 'Public_AR_Current|ACS2022_Current'
- ACS_2023 = 'Public_AR_Current|ACS2023_Current'
- __new__(value)
- siege_utilities.geo.census_geocoder.geocode_batch(addresses, vintage=CensusVintage.CURRENT)[source]
Geocode a batch of addresses via the Census Bureau batch API.
The Census batch API accepts up to 10,000 addresses per request. Each address dict should have keys: id, street, city, state, zipcode.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
List of CensusGeocodeResult, one per input address (order preserved).
- Raises:
ValueError – If batch exceeds 10,000 addresses.
- Return type:
- siege_utilities.geo.census_geocoder.geocode_batch_chunked(addresses, vintage=CensusVintage.CURRENT, chunk_size=10000)[source]
Geocode addresses in chunks of up to chunk_size (default 10,000).
Convenience wrapper around geocode_batch() for larger datasets.
- Parameters:
addresses (list[dict]) – List of dicts with keys {id, street, city, state, zipcode}.
vintage (CensusVintage) – Census vintage for boundary matching.
chunk_size (int) – Max addresses per API call (max 10,000).
- Returns:
List of CensusGeocodeResult, one per input address.
- Return type:
- siege_utilities.geo.census_geocoder.geocode_results_to_dataframe(results)[source]
Convert a list of CensusGeocodeResult to a pandas DataFrame.
Extracts all dataclass fields and computed GEOID properties into a flat DataFrame with standard column names. This eliminates the per-field list-comprehension boilerplate that every caller of
geocode_batch()otherwise has to write.- Parameters:
results (list[CensusGeocodeResult]) – List of
CensusGeocodeResultobjects, typically returned bygeocode_batch()orgeocode_batch_chunked().- Returns:
input_id,input_address,matched,match_type,matched_address,latitude,longitude,state_geoid,county_geoid,tract_geoid,block_geoid,block_group_geoid,state_fips,county_fips,tract,block,side,tiger_line_id. For unmatched rows, latitude/longitude areNoneand GEOID columns are empty strings.- Return type:
DataFrame with columns
- Raises:
TypeError – If results is not a list.
Example:
results = geocode_batch(addresses) df = geocode_results_to_dataframe(results) # df now has tract_geoid, block_geoid, lat/lon, etc.
- siege_utilities.geo.census_geocoder.geocode_single(street, city, state, zipcode, vintage=CensusVintage.CURRENT)[source]
Geocode a single address via the Census Bureau API.
- Parameters:
street (str) – Street address (e.g., “1600 Pennsylvania Ave NW”).
city (str) – City name.
state (str) – State abbreviation or name.
zipcode (str) – ZIP code.
vintage (CensusVintage) – Census vintage for boundary matching.
- Returns:
CensusGeocodeResult with lat/lon and FIPS codes if matched.
- Return type:
- siege_utilities.geo.census_geocoder.select_vintage_for_cycle(year)[source]
Select the appropriate Census vintage for an FEC cycle year.
- Parameters:
year (int) – The FEC election cycle year.
- Returns:
CensusVintage matching the boundaries in effect for that cycle.
- Return type:
Examples
>>> select_vintage_for_cycle(2024) <CensusVintage.CURRENT: 'Public_AR_Current|Current_Current'> >>> select_vintage_for_cycle(2016) <CensusVintage.CENSUS_2020: 'Public_AR_Census2020|Census2020_Census2020'> >>> select_vintage_for_cycle(2008) <CensusVintage.CENSUS_2010: 'Public_AR_Census2020|Census2010_Census2020'>
Census file download and processing utilities.
This module provides tools for downloading Census data files directly, including PL 94-171 redistricting files that are not available via API.
Key features: - Download PL 94-171 files for block-level redistricting data - Download DHC (Demographic and Housing Characteristics) files - Download TIGER/Line shapefiles - Automatic caching and state filtering
- class siege_utilities.geo.census_files.PLFileDownloader[source]
Bases:
objectDownloader for PL 94-171 redistricting data files.
This class handles downloading, caching, and parsing PL files from the Census Bureau.
- get_data(state, year=2020, geography='tract', county=None, tables=None)[source]
Get PL 94-171 data for a state.
- Parameters:
state (str) – State name, abbreviation, or FIPS code
year (int) – Census year (2010 or 2020)
geography (str) – Geographic level (‘state’, ‘county’, ‘tract’, ‘block_group’, ‘block’)
county (str | None) – Optional county FIPS code to filter to
tables (List[str] | None) – List of tables to include (e.g., [‘P1’, ‘P2’, ‘H1’]). If None, includes all tables.
- Returns:
DataFrame with PL data at specified geography level
- Return type:
- siege_utilities.geo.census_files.get_pl_data(state, year=2020, geography='tract', county=None, tables=None)[source]
Get PL 94-171 redistricting data.
- Parameters:
- Returns:
DataFrame with PL data
- Return type:
Example
# Get California tract data df = get_pl_data(state=’CA’, geography=’tract’)
# Get LA County block data df = get_pl_data(state=’CA’, geography=’block’, county=’037’)
- siege_utilities.geo.census_files.get_pl_blocks(state, county=None, year=2020, tables=None)[source]
Get block-level PL 94-171 data.
This is the primary function for redistricting analysis as it provides the finest geographic detail available.
- siege_utilities.geo.census_files.get_pl_tracts(state, county=None, year=2020, tables=None)[source]
Get tract-level PL 94-171 data.
- siege_utilities.geo.census_files.download_pl_file(state, year=2020, output_dir=None)[source]
Download raw PL 94-171 zip file.
- siege_utilities.geo.census_files.list_available_pl_files(year=2020)[source]
List available PL 94-171 files.
PL 94-171 Redistricting Data File Downloader.
This module downloads and processes PL 94-171 redistricting data files from the Census Bureau. Unlike the Census API, these files provide block-level data essential for redistricting.
PL 94-171 File Structure: - Files are distributed as pipe-delimited text files - Each state has separate files for geographic header and data segments - Data is organized by summary level (state, county, tract, block group, block)
- Example usage:
from siege_utilities.geo.census_files import get_pl_data, get_pl_blocks
# Get all PL data for California at tract level df = get_pl_data(state=’CA’, geography=’tract’, year=2020)
# Get block-level data for LA County blocks = get_pl_blocks(state=’CA’, county=’037’, year=2020)
- class siege_utilities.geo.census_files.pl_downloader.PLFileDownloader[source]
Bases:
objectDownloader for PL 94-171 redistricting data files.
This class handles downloading, caching, and parsing PL files from the Census Bureau.
- get_data(state, year=2020, geography='tract', county=None, tables=None)[source]
Get PL 94-171 data for a state.
- Parameters:
state (str) – State name, abbreviation, or FIPS code
year (int) – Census year (2010 or 2020)
geography (str) – Geographic level (‘state’, ‘county’, ‘tract’, ‘block_group’, ‘block’)
county (str | None) – Optional county FIPS code to filter to
tables (List[str] | None) – List of tables to include (e.g., [‘P1’, ‘P2’, ‘H1’]). If None, includes all tables.
- Returns:
DataFrame with PL data at specified geography level
- Return type:
- siege_utilities.geo.census_files.pl_downloader.get_pl_data(state, year=2020, geography='tract', county=None, tables=None)[source]
Get PL 94-171 redistricting data.
- Parameters:
- Returns:
DataFrame with PL data
- Return type:
Example
# Get California tract data df = get_pl_data(state=’CA’, geography=’tract’)
# Get LA County block data df = get_pl_data(state=’CA’, geography=’block’, county=’037’)
- siege_utilities.geo.census_files.pl_downloader.get_pl_blocks(state, county=None, year=2020, tables=None)[source]
Get block-level PL 94-171 data.
This is the primary function for redistricting analysis as it provides the finest geographic detail available.
- siege_utilities.geo.census_files.pl_downloader.get_pl_tracts(state, county=None, year=2020, tables=None)[source]
Get tract-level PL 94-171 data.
Crosswalks and Interpolation
Census boundary crosswalk support for tracking geographic changes over time.
This module provides tools for working with Census boundary crosswalks, which describe how geographic units (tracts, block groups, etc.) changed between decennial census years (e.g., 2010 to 2020).
Key features: - Download and cache crosswalk files from Census Bureau - Apply crosswalks to transform data between boundary vintages - Identify which geographies were split, merged, or unchanged
- Example usage:
- from siege_utilities.geo.crosswalk import (
get_crosswalk, apply_crosswalk, identify_boundary_changes, WeightMethod
)
# Get the crosswalk for California tracts crosswalk = get_crosswalk(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
# Transform 2010 data to 2020 boundaries df_2020 = apply_crosswalk(
df=df_2010, source_year=2010, target_year=2020, geography_level=’tract’, weight_method=WeightMethod.AREA
)
# See which tracts changed changes = identify_boundary_changes(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
- class siege_utilities.geo.crosswalk.RelationshipType[source]
Bases:
EnumTypes of relationships between source and target geographies in crosswalks.
These describe how a geographic unit changed between Census years.
- ONE_TO_ONE = 'one_to_one'
Geographic unit unchanged - maps directly to one target unit.
- SPLIT = 'split'
One source unit split into multiple target units (one-to-many).
- MERGED = 'merged'
Multiple source units merged into one target unit (many-to-one).
- PARTIAL_OVERLAP = 'partial_overlap'
Complex relationship - parts of units overlap (many-to-many).
- class siege_utilities.geo.crosswalk.WeightMethod[source]
Bases:
EnumMethods for weighting data when applying crosswalks.
Different methods are appropriate for different types of data: - AREA: Best for land-use data, environmental measures - POPULATION: Best for demographic data, social measures - HOUSING: Best for housing-related variables - EQUAL: Simple equal weighting (use when weights unavailable)
- AREA = 'area'
Weight by land area proportion.
- POPULATION = 'population'
Weight by population proportion (requires population data).
- HOUSING = 'housing'
Weight by housing unit proportion.
- EQUAL = 'equal'
Equal weighting across all relationships.
- class siege_utilities.geo.crosswalk.CrosswalkRelationship[source]
Bases:
objectRepresents a single relationship between source and target geographies.
This is one row in a crosswalk file, describing how data should flow from a source geography (e.g., 2010 tract) to a target geography (e.g., 2020 tract).
- relationship_type
Type of relationship (split, merge, etc.)
- relationship_type: RelationshipType = 'one_to_one'
- get_weight(method)[source]
Get the appropriate weight for the specified method.
- Parameters:
method (WeightMethod) – Weighting method to use
- Returns:
Weight value (0.0 to 1.0)
- Raises:
ValueError – If requested weight type is not available
- Return type:
- __init__(source_geoid, target_geoid, relationship_type=RelationshipType.ONE_TO_ONE, area_weight=1.0, population_weight=None, housing_weight=None, source_area=None, target_area=None, overlap_area=None)
- class siege_utilities.geo.crosswalk.CrosswalkMetadata[source]
Bases:
objectMetadata about a crosswalk dataset.
- property change_rate: float
Calculate the rate of geographic change.
- Returns:
Proportion of geographies that changed (0.0 to 1.0)
- summary()[source]
Get a summary dictionary of crosswalk statistics.
- Returns:
Dictionary with crosswalk statistics
- Return type:
- __init__(source_year, target_year, geography_level, state_fips=None, total_source_units=0, total_target_units=0, one_to_one_count=0, split_count=0, merged_count=0, source_url=None, download_date=None)
- class siege_utilities.geo.crosswalk.GeographyChange[source]
Bases:
objectSummary of changes for a single geography between Census years.
This provides a higher-level view than individual relationships, summarizing all the changes for a single source geography.
- relationship_type
Overall relationship type
- relationship_type: RelationshipType
- __init__(source_geoid, relationship_type, target_geoids=<factory>, weights=<factory>, population_2010=None, population_2020=None)
- class siege_utilities.geo.crosswalk.CrosswalkClient[source]
Bases:
objectClient for downloading and managing Census crosswalk files.
This client handles: - Downloading crosswalk files from Census Bureau - Caching files locally as parquet for fast access - Parsing pipe-delimited Census files - Filtering by state if needed
- cache_dir
Directory for cached crosswalk files
- timeout
Request timeout in seconds
- get_crosswalk(source_year=2010, target_year=2020, geography_level='tract', state_fips=None, force_refresh=False)[source]
Get a crosswalk DataFrame for the specified years and geography.
- Parameters:
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS code to filter results
force_refresh (bool) – If True, re-download even if cached
- Returns:
source_geoid: GEOID in source year
target_geoid: GEOID in target year
area_weight: Proportion based on land area overlap
Additional metadata columns
- Return type:
DataFrame with crosswalk relationships including
- Raises:
ValueError – If year combination is not supported
requests.RequestException – If download fails
- get_crosswalk_metadata(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Get metadata about a crosswalk.
- Parameters:
- Returns:
CrosswalkMetadata with statistics about the crosswalk
- Return type:
- siege_utilities.geo.crosswalk.get_crosswalk(source_year=2010, target_year=2020, geography_level='tract', state_fips=None, force_refresh=False)[source]
Convenience function to get a crosswalk DataFrame.
- Parameters:
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS code to filter results
force_refresh (bool) – If True, re-download even if cached
- Returns:
DataFrame with crosswalk relationships
- Return type:
Example
# Get California tract crosswalk df = get_crosswalk(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
- siege_utilities.geo.crosswalk.get_crosswalk_client()[source]
Get or create the default crosswalk client.
- Return type:
- siege_utilities.geo.crosswalk.get_crosswalk_metadata(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Convenience function to get crosswalk metadata.
- Parameters:
- Returns:
CrosswalkMetadata with statistics
- Return type:
- siege_utilities.geo.crosswalk.list_available_crosswalks()[source]
List all available crosswalk combinations.
- class siege_utilities.geo.crosswalk.CrosswalkProcessor[source]
Bases:
objectProcessor for applying crosswalks to transform data between Census years.
This class handles the logic of transforming data from one boundary vintage to another, accounting for splits, merges, and complex changes.
- crosswalk_df
The crosswalk DataFrame with relationships
- source_year
Source boundary year
- target_year
Target boundary year
- geography_level
Geographic level being processed
- __init__(crosswalk_df, source_year, target_year, geography_level)[source]
Initialize the processor with a crosswalk.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame from get_crosswalk()
source_year (int) – Source Census year
target_year (int) – Target Census year
geography_level (str) – Geographic level (‘tract’, ‘county’, etc.)
- get_relationship_type(source_geoid)[source]
Determine the relationship type for a source geography.
- Parameters:
source_geoid (str) – GEOID in source year
- Returns:
RelationshipType indicating how this geography changed
- Return type:
- get_geography_change(source_geoid)[source]
Get detailed change information for a source geography.
- Parameters:
source_geoid (str) – GEOID in source year
- Returns:
GeographyChange object with relationship details
- Return type:
- transform(df, geoid_column='GEOID', value_columns=None, weight_method=WeightMethod.AREA, aggregation_func='sum')[source]
Transform data from source boundaries to target boundaries.
- Parameters:
df (pandas.DataFrame) – DataFrame with data in source boundary vintage
geoid_column (str) – Name of the GEOID column
value_columns (List[str] | None) – List of columns to transform. If None, all numeric columns.
weight_method (WeightMethod) – How to weight data for splits/merges
aggregation_func (str | Callable) – How to aggregate merged data (‘sum’, ‘mean’, ‘weighted_mean’)
- Returns:
DataFrame with data in target boundary vintage
- Raises:
ValueError – If geoid_column not found in DataFrame
- Return type:
- siege_utilities.geo.crosswalk.apply_crosswalk(df, source_year=2010, target_year=2020, geography_level='tract', state_fips=None, geoid_column='GEOID', value_columns=None, weight_method=WeightMethod.AREA, aggregation_func='sum')[source]
Apply a crosswalk to transform data from one boundary vintage to another.
This is the main function for transforming data between Census years. It handles all relationship types (unchanged, splits, merges) automatically.
- Parameters:
df (pandas.DataFrame) – DataFrame with data in source boundary vintage
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS to filter crosswalk
geoid_column (str) – Name of the GEOID column in df
value_columns (List[str] | None) – List of columns to transform. If None, all numeric columns.
weight_method (WeightMethod) – How to weight data for splits/merges
aggregation_func (str | Callable) – How to aggregate data (‘sum’, ‘mean’, ‘weighted_mean’)
- Returns:
DataFrame with data transformed to target boundary vintage
- Return type:
Example
# Transform 2010 income data to 2020 tracts df_2020 = apply_crosswalk(
df=income_2010, source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’, # California value_columns=[‘median_income’, ‘total_population’]
)
- siege_utilities.geo.crosswalk.normalize_to_year(df, data_year, target_year=2020, geography_level='tract', state_fips=None, geoid_column='GEOID', value_columns=None)[source]
Normalize data to a specific boundary year.
Convenience function that determines the correct crosswalk direction and applies it to transform data to the target year’s boundaries.
- Parameters:
- Returns:
DataFrame normalized to target year boundaries
- Return type:
Example
# Normalize mixed-year data to 2020 boundaries df_normalized = normalize_to_year(
df=old_data, data_year=2010, target_year=2020, geography_level=’tract’
)
- siege_utilities.geo.crosswalk.identify_boundary_changes(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Identify and summarize boundary changes between Census years.
Returns a DataFrame showing how each source geography changed, useful for understanding the extent of boundary changes in an area.
- Parameters:
- Returns:
source_geoid: GEOID in source year
relationship_type: Type of change (unchanged, split, merged)
num_targets: Number of target GEOIDs this maps to
target_geoids: List of target GEOIDs (as string)
- Return type:
DataFrame with columns
Example
# See which California tracts changed changes = identify_boundary_changes(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
# Filter to just splits splits = changes[changes[‘relationship_type’] == ‘split’]
- siege_utilities.geo.crosswalk.get_split_tracts(source_year=2010, target_year=2020, state_fips=None)[source]
Get a list of tracts that were split between Census years.
- Parameters:
- Returns:
DataFrame with split tracts and their target tracts
- Return type:
- siege_utilities.geo.crosswalk.get_merged_tracts(source_year=2010, target_year=2020, state_fips=None)[source]
Get a list of tracts that were merged between Census years.
- Parameters:
- Returns:
DataFrame with merged tracts and their resulting tract
- Return type:
Client for downloading and caching Census boundary crosswalk files.
This module provides functions to download crosswalk files from the Census Bureau and cache them locally for efficient repeated access.
Census crosswalk files describe how geographic boundaries changed between decennial census years. They are essential for: - Comparing data across different Census years - Tracking demographic changes in consistent geographies - Normalizing historical data to current boundaries
- Example usage:
from siege_utilities.geo.crosswalk import get_crosswalk
# Get 2010-2020 tract crosswalk for California crosswalk_df = get_crosswalk(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
# View relationships print(crosswalk_df.head())
- class siege_utilities.geo.crosswalk.crosswalk_client.CrosswalkClient[source]
Bases:
objectClient for downloading and managing Census crosswalk files.
This client handles: - Downloading crosswalk files from Census Bureau - Caching files locally as parquet for fast access - Parsing pipe-delimited Census files - Filtering by state if needed
- cache_dir
Directory for cached crosswalk files
- timeout
Request timeout in seconds
- get_crosswalk(source_year=2010, target_year=2020, geography_level='tract', state_fips=None, force_refresh=False)[source]
Get a crosswalk DataFrame for the specified years and geography.
- Parameters:
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS code to filter results
force_refresh (bool) – If True, re-download even if cached
- Returns:
source_geoid: GEOID in source year
target_geoid: GEOID in target year
area_weight: Proportion based on land area overlap
Additional metadata columns
- Return type:
DataFrame with crosswalk relationships including
- Raises:
ValueError – If year combination is not supported
requests.RequestException – If download fails
- get_crosswalk_metadata(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Get metadata about a crosswalk.
- Parameters:
- Returns:
CrosswalkMetadata with statistics about the crosswalk
- Return type:
- siege_utilities.geo.crosswalk.crosswalk_client.get_crosswalk_client()[source]
Get or create the default crosswalk client.
- Return type:
- siege_utilities.geo.crosswalk.crosswalk_client.get_crosswalk(source_year=2010, target_year=2020, geography_level='tract', state_fips=None, force_refresh=False)[source]
Convenience function to get a crosswalk DataFrame.
- Parameters:
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS code to filter results
force_refresh (bool) – If True, re-download even if cached
- Returns:
DataFrame with crosswalk relationships
- Return type:
Example
# Get California tract crosswalk df = get_crosswalk(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
- siege_utilities.geo.crosswalk.crosswalk_client.get_crosswalk_metadata(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Convenience function to get crosswalk metadata.
- Parameters:
- Returns:
CrosswalkMetadata with statistics
- Return type:
- siege_utilities.geo.crosswalk.crosswalk_client.list_available_crosswalks()[source]
List all available crosswalk combinations.
Crosswalk processor for transforming data between Census boundary years.
This module provides functions to apply crosswalk relationships to transform data from one Census boundary vintage to another. It handles:
One-to-one mappings: Simple GEOID rename
One-to-many (splits): Disaggregate data using weights
Many-to-one (merges): Aggregate data using weights
- Example usage:
from siege_utilities.geo.crosswalk import apply_crosswalk, WeightMethod
# Transform 2010 data to 2020 boundaries df_2020 = apply_crosswalk(
df=df_2010, source_year=2010, target_year=2020, weight_method=WeightMethod.AREA, aggregation_func=’sum’
)
- class siege_utilities.geo.crosswalk.crosswalk_processor.CrosswalkProcessor[source]
Bases:
objectProcessor for applying crosswalks to transform data between Census years.
This class handles the logic of transforming data from one boundary vintage to another, accounting for splits, merges, and complex changes.
- crosswalk_df
The crosswalk DataFrame with relationships
- source_year
Source boundary year
- target_year
Target boundary year
- geography_level
Geographic level being processed
- __init__(crosswalk_df, source_year, target_year, geography_level)[source]
Initialize the processor with a crosswalk.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame from get_crosswalk()
source_year (int) – Source Census year
target_year (int) – Target Census year
geography_level (str) – Geographic level (‘tract’, ‘county’, etc.)
- get_relationship_type(source_geoid)[source]
Determine the relationship type for a source geography.
- Parameters:
source_geoid (str) – GEOID in source year
- Returns:
RelationshipType indicating how this geography changed
- Return type:
- get_geography_change(source_geoid)[source]
Get detailed change information for a source geography.
- Parameters:
source_geoid (str) – GEOID in source year
- Returns:
GeographyChange object with relationship details
- Return type:
- transform(df, geoid_column='GEOID', value_columns=None, weight_method=WeightMethod.AREA, aggregation_func='sum')[source]
Transform data from source boundaries to target boundaries.
- Parameters:
df (pandas.DataFrame) – DataFrame with data in source boundary vintage
geoid_column (str) – Name of the GEOID column
value_columns (List[str] | None) – List of columns to transform. If None, all numeric columns.
weight_method (WeightMethod) – How to weight data for splits/merges
aggregation_func (str | Callable) – How to aggregate merged data (‘sum’, ‘mean’, ‘weighted_mean’)
- Returns:
DataFrame with data in target boundary vintage
- Raises:
ValueError – If geoid_column not found in DataFrame
- Return type:
- siege_utilities.geo.crosswalk.crosswalk_processor.apply_crosswalk(df, source_year=2010, target_year=2020, geography_level='tract', state_fips=None, geoid_column='GEOID', value_columns=None, weight_method=WeightMethod.AREA, aggregation_func='sum')[source]
Apply a crosswalk to transform data from one boundary vintage to another.
This is the main function for transforming data between Census years. It handles all relationship types (unchanged, splits, merges) automatically.
- Parameters:
df (pandas.DataFrame) – DataFrame with data in source boundary vintage
source_year (int) – Source Census year (e.g., 2010)
target_year (int) – Target Census year (e.g., 2020)
geography_level (str) – Geographic level (‘tract’, ‘block_group’, ‘county’)
state_fips (str | None) – Optional state FIPS to filter crosswalk
geoid_column (str) – Name of the GEOID column in df
value_columns (List[str] | None) – List of columns to transform. If None, all numeric columns.
weight_method (WeightMethod) – How to weight data for splits/merges
aggregation_func (str | Callable) – How to aggregate data (‘sum’, ‘mean’, ‘weighted_mean’)
- Returns:
DataFrame with data transformed to target boundary vintage
- Return type:
Example
# Transform 2010 income data to 2020 tracts df_2020 = apply_crosswalk(
df=income_2010, source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’, # California value_columns=[‘median_income’, ‘total_population’]
)
- siege_utilities.geo.crosswalk.crosswalk_processor.normalize_to_year(df, data_year, target_year=2020, geography_level='tract', state_fips=None, geoid_column='GEOID', value_columns=None)[source]
Normalize data to a specific boundary year.
Convenience function that determines the correct crosswalk direction and applies it to transform data to the target year’s boundaries.
- Parameters:
- Returns:
DataFrame normalized to target year boundaries
- Return type:
Example
# Normalize mixed-year data to 2020 boundaries df_normalized = normalize_to_year(
df=old_data, data_year=2010, target_year=2020, geography_level=’tract’
)
- siege_utilities.geo.crosswalk.crosswalk_processor.identify_boundary_changes(source_year=2010, target_year=2020, geography_level='tract', state_fips=None)[source]
Identify and summarize boundary changes between Census years.
Returns a DataFrame showing how each source geography changed, useful for understanding the extent of boundary changes in an area.
- Parameters:
- Returns:
source_geoid: GEOID in source year
relationship_type: Type of change (unchanged, split, merged)
num_targets: Number of target GEOIDs this maps to
target_geoids: List of target GEOIDs (as string)
- Return type:
DataFrame with columns
Example
# See which California tracts changed changes = identify_boundary_changes(
source_year=2010, target_year=2020, geography_level=’tract’, state_fips=’06’
)
# Filter to just splits splits = changes[changes[‘relationship_type’] == ‘split’]
- siege_utilities.geo.crosswalk.crosswalk_processor.get_split_tracts(source_year=2010, target_year=2020, state_fips=None)[source]
Get a list of tracts that were split between Census years.
- Parameters:
- Returns:
DataFrame with split tracts and their target tracts
- Return type:
- siege_utilities.geo.crosswalk.crosswalk_processor.get_merged_tracts(source_year=2010, target_year=2020, state_fips=None)[source]
Get a list of tracts that were merged between Census years.
- Parameters:
- Returns:
DataFrame with merged tracts and their resulting tract
- Return type:
Relationship types and data classes for Census boundary crosswalks.
This module defines enumerations and data classes that describe how Census geographic boundaries change between decennial census years (e.g., 2010 to 2020).
Census tracts can: - Remain unchanged (ONE_TO_ONE) - Be split into multiple tracts (ONE_TO_MANY) - Be merged with other tracts (MANY_TO_ONE)
The crosswalk files from Census Bureau provide relationship weights based on: - Land area proportions - Population distribution
- Example usage:
from siege_utilities.geo.crosswalk import RelationshipType, WeightMethod
# Check relationship type if relationship == RelationshipType.SPLIT:
# Handle tract that was split …
# Specify weighting method method = WeightMethod.POPULATION
- class siege_utilities.geo.crosswalk.relationship_types.RelationshipType[source]
Bases:
EnumTypes of relationships between source and target geographies in crosswalks.
These describe how a geographic unit changed between Census years.
- ONE_TO_ONE = 'one_to_one'
Geographic unit unchanged - maps directly to one target unit.
- SPLIT = 'split'
One source unit split into multiple target units (one-to-many).
- MERGED = 'merged'
Multiple source units merged into one target unit (many-to-one).
- PARTIAL_OVERLAP = 'partial_overlap'
Complex relationship - parts of units overlap (many-to-many).
- class siege_utilities.geo.crosswalk.relationship_types.WeightMethod[source]
Bases:
EnumMethods for weighting data when applying crosswalks.
Different methods are appropriate for different types of data: - AREA: Best for land-use data, environmental measures - POPULATION: Best for demographic data, social measures - HOUSING: Best for housing-related variables - EQUAL: Simple equal weighting (use when weights unavailable)
- AREA = 'area'
Weight by land area proportion.
- POPULATION = 'population'
Weight by population proportion (requires population data).
- HOUSING = 'housing'
Weight by housing unit proportion.
- EQUAL = 'equal'
Equal weighting across all relationships.
- class siege_utilities.geo.crosswalk.relationship_types.CrosswalkRelationship[source]
Bases:
objectRepresents a single relationship between source and target geographies.
This is one row in a crosswalk file, describing how data should flow from a source geography (e.g., 2010 tract) to a target geography (e.g., 2020 tract).
- relationship_type
Type of relationship (split, merge, etc.)
- relationship_type: RelationshipType = 'one_to_one'
- get_weight(method)[source]
Get the appropriate weight for the specified method.
- Parameters:
method (WeightMethod) – Weighting method to use
- Returns:
Weight value (0.0 to 1.0)
- Raises:
ValueError – If requested weight type is not available
- Return type:
- __init__(source_geoid, target_geoid, relationship_type=RelationshipType.ONE_TO_ONE, area_weight=1.0, population_weight=None, housing_weight=None, source_area=None, target_area=None, overlap_area=None)
- class siege_utilities.geo.crosswalk.relationship_types.CrosswalkMetadata[source]
Bases:
objectMetadata about a crosswalk dataset.
- property change_rate: float
Calculate the rate of geographic change.
- Returns:
Proportion of geographies that changed (0.0 to 1.0)
- summary()[source]
Get a summary dictionary of crosswalk statistics.
- Returns:
Dictionary with crosswalk statistics
- Return type:
- __init__(source_year, target_year, geography_level, state_fips=None, total_source_units=0, total_target_units=0, one_to_one_count=0, split_count=0, merged_count=0, source_url=None, download_date=None)
- class siege_utilities.geo.crosswalk.relationship_types.GeographyChange[source]
Bases:
objectSummary of changes for a single geography between Census years.
This provides a higher-level view than individual relationships, summarizing all the changes for a single source geography.
- relationship_type
Overall relationship type
- relationship_type: RelationshipType
- __init__(source_geoid, relationship_type, target_geoids=<factory>, weights=<factory>, population_2010=None, population_2020=None)
Areal interpolation for transferring data between geographic boundaries.
Wraps PySAL’s Tobler library to provide a consistent interface for extensive (population) and intensive (median income) variable interpolation.
Example:
from siege_utilities.geo.interpolation import interpolate_extensive, interpolate_intensive
# Transfer population counts from 2010 tracts to 2020 tracts
result = interpolate_extensive(
source_gdf=tracts_2010,
target_gdf=tracts_2020,
variables=["total_pop", "housing_units"],
)
# Transfer median income (rate) from 2010 to 2020 tracts
result = interpolate_intensive(
source_gdf=tracts_2010,
target_gdf=tracts_2020,
variables=["median_income", "poverty_rate"],
)
- class siege_utilities.geo.interpolation.ArealInterpolationResult[source]
Bases:
objectResult of an areal interpolation operation.
- data
GeoDataFrame with interpolated values in target geometries.
- Type:
- data: geopandas.GeoDataFrame
- __init__(data, extensive_variables=<factory>, intensive_variables=<factory>, source_crs=None, target_crs=None, n_source=0, n_target=0, warnings=<factory>, backend='')
- siege_utilities.geo.interpolation.interpolate_areal(source_gdf, target_gdf, extensive_variables=None, intensive_variables=None, allocate_total=True, n_jobs=1, *, crs=None)[source]
Transfer attribute data between non-coincident geographies.
Uses area-weighted interpolation to redistribute values from source polygons to target polygons based on the area of intersection.
Backend dispatch (in priority order): 1. tobler (PySAL) — when tobler + geopandas are installed 2. DuckDB spatial — when duckdb is installed (no GDAL needed) 3. pure Shapely — STRtree + intersection (always available with geopandas)
- Parameters:
source_gdf (geopandas.GeoDataFrame) – Source GeoDataFrame with attribute columns.
target_gdf (geopandas.GeoDataFrame) – Target GeoDataFrame defining output geometries.
extensive_variables (list[str] | None) – Columns containing totals (population, etc.). These are split proportionally by area overlap.
intensive_variables (list[str] | None) – Columns containing rates/densities. These are area-weighted averaged.
allocate_total (bool) – If True, ensure 100% of source area is allocated (only applies to tobler backend).
n_jobs (int) – Number of parallel jobs (only applies to tobler backend).
crs (str | None) – Output CRS for the result GeoDataFrame (default: source CRS).
- Returns:
ArealInterpolationResult with interpolated GeoDataFrame in crs.
- Raises:
ImportError – If no suitable backend is available.
ValueError – If no variables are specified or columns missing.
- Return type:
- siege_utilities.geo.interpolation.interpolate_extensive(source_gdf, target_gdf, variables, allocate_total=True, n_jobs=1, *, crs=None)[source]
Interpolate extensive (total/count) variables between geographies.
- Parameters:
source_gdf (geopandas.GeoDataFrame)
target_gdf (geopandas.GeoDataFrame)
allocate_total (bool)
n_jobs (int)
crs (str | None)
- Return type:
- siege_utilities.geo.interpolation.interpolate_intensive(source_gdf, target_gdf, variables, allocate_total=True, n_jobs=1, *, crs=None)[source]
Interpolate intensive (rate/density) variables between geographies.
- Parameters:
source_gdf (geopandas.GeoDataFrame)
target_gdf (geopandas.GeoDataFrame)
allocate_total (bool)
n_jobs (int)
crs (str | None)
- Return type:
- siege_utilities.geo.interpolation.compute_area_weights(source_gdf, target_gdf, *, crs=None)[source]
Compute the area overlap matrix between source and target polygons.
Returns a GeoDataFrame with columns: - source_idx: Index of the source polygon. - target_idx: Index of the target polygon. - overlap_area: Area of intersection. - source_fraction: Fraction of source polygon covered. - target_fraction: Fraction of target polygon covered.
- Parameters:
source_gdf (geopandas.GeoDataFrame) – Source polygons.
target_gdf (geopandas.GeoDataFrame) – Target polygons.
crs (str | None) – Output CRS (default: source CRS).
- Returns:
GeoDataFrame with overlap weights in crs.
- Return type:
Areal interpolation using PySAL’s Tobler library.
Provides functions to transfer attribute data between non-coincident geographic boundaries using area-weighted methods.
Two types of variables are handled: - Extensive (e.g., population, housing units): totals that should be
split proportionally when a source polygon overlaps multiple targets.
Intensive (e.g., median income, poverty rate): rates/densities that should be area-weighted averaged across overlapping sources.
Backend dispatch (in priority order): 1. tobler — PySAL’s area_interpolate (requires geopandas + tobler) 2. duckdb — DuckDB spatial extension (ST_Intersection / ST_Area) 3. shapely — Pure Shapely STRtree + intersection (always available)
- class siege_utilities.geo.interpolation.areal.ArealInterpolationResult[source]
Bases:
objectResult of an areal interpolation operation.
- data
GeoDataFrame with interpolated values in target geometries.
- Type:
- data: geopandas.GeoDataFrame
- __init__(data, extensive_variables=<factory>, intensive_variables=<factory>, source_crs=None, target_crs=None, n_source=0, n_target=0, warnings=<factory>, backend='')
- siege_utilities.geo.interpolation.areal.interpolate_areal(source_gdf, target_gdf, extensive_variables=None, intensive_variables=None, allocate_total=True, n_jobs=1, *, crs=None)[source]
Transfer attribute data between non-coincident geographies.
Uses area-weighted interpolation to redistribute values from source polygons to target polygons based on the area of intersection.
Backend dispatch (in priority order): 1. tobler (PySAL) — when tobler + geopandas are installed 2. DuckDB spatial — when duckdb is installed (no GDAL needed) 3. pure Shapely — STRtree + intersection (always available with geopandas)
- Parameters:
source_gdf (geopandas.GeoDataFrame) – Source GeoDataFrame with attribute columns.
target_gdf (geopandas.GeoDataFrame) – Target GeoDataFrame defining output geometries.
extensive_variables (list[str] | None) – Columns containing totals (population, etc.). These are split proportionally by area overlap.
intensive_variables (list[str] | None) – Columns containing rates/densities. These are area-weighted averaged.
allocate_total (bool) – If True, ensure 100% of source area is allocated (only applies to tobler backend).
n_jobs (int) – Number of parallel jobs (only applies to tobler backend).
crs (str | None) – Output CRS for the result GeoDataFrame (default: source CRS).
- Returns:
ArealInterpolationResult with interpolated GeoDataFrame in crs.
- Raises:
ImportError – If no suitable backend is available.
ValueError – If no variables are specified or columns missing.
- Return type:
- siege_utilities.geo.interpolation.areal.interpolate_extensive(source_gdf, target_gdf, variables, allocate_total=True, n_jobs=1, *, crs=None)[source]
Interpolate extensive (total/count) variables between geographies.
- Parameters:
source_gdf (geopandas.GeoDataFrame)
target_gdf (geopandas.GeoDataFrame)
allocate_total (bool)
n_jobs (int)
crs (str | None)
- Return type:
- siege_utilities.geo.interpolation.areal.interpolate_intensive(source_gdf, target_gdf, variables, allocate_total=True, n_jobs=1, *, crs=None)[source]
Interpolate intensive (rate/density) variables between geographies.
- Parameters:
source_gdf (geopandas.GeoDataFrame)
target_gdf (geopandas.GeoDataFrame)
allocate_total (bool)
n_jobs (int)
crs (str | None)
- Return type:
- siege_utilities.geo.interpolation.areal.compute_area_weights(source_gdf, target_gdf, *, crs=None)[source]
Compute the area overlap matrix between source and target polygons.
Returns a GeoDataFrame with columns: - source_idx: Index of the source polygon. - target_idx: Index of the target polygon. - overlap_area: Area of intersection. - source_fraction: Fraction of source polygon covered. - target_fraction: Fraction of target polygon covered.
- Parameters:
source_gdf (geopandas.GeoDataFrame) – Source polygons.
target_gdf (geopandas.GeoDataFrame) – Target polygons.
crs (str | None) – Output CRS (default: source CRS).
- Returns:
GeoDataFrame with overlap weights in crs.
- Return type:
Gazetteers and Place History
Gazetteers — name → geometry resolution (ELE-2483).
Public API:
Gazetteer— Protocol every backend satisfies.GazetteerResult— resolved place (name, path, geometry, centroid, admin levels).GazetteerCandidate— search hit (no geometry yet).resolve_gazetteer()— factory that picks the best available backend (WKLS by default; falls back to Nominatim, then Census).Errors:
GazetteerError,GazetteerNotFoundError,GazetteerAmbiguousError,GazetteerBackendError.
The Etter integration (etter_to_geometry()) consumes this
interface — see siege_utilities.geo.providers.etter_filter.
- class siege_utilities.geo.gazetteers.Gazetteer[source]
Bases:
ProtocolName → geometry resolver.
The protocol is intentionally narrow — four methods. Backends layer LRU caches, ISO-code translation, and other plumbing internally; callers just see this surface.
- lookup(name, *, country_hint=None, admin_hint=None)[source]
Best match for name.
- Parameters:
- Raises:
GazetteerNotFoundError – No match.
GazetteerAmbiguousError – Multiple matches; caller should pass more hints or use
search()and pick.GazetteerBackendError – Network / dependency / parse failure.
- Return type:
- search(name, *, country_hint=None, limit=10)[source]
Top-N candidate matches.
Used to surface ambiguity to a caller (interactive UI, or programmatic disambiguation when the caller has external knowledge to pick a candidate).
- Parameters:
- Return type:
- is_available()[source]
Return True if the backend’s dependencies are installed and the backend can answer queries.
Should NOT make a network round-trip — this is a fast probe used by the factory to pick a default backend.
- Return type:
- __init__(*args, **kwargs)
- class siege_utilities.geo.gazetteers.GazetteerCandidate[source]
Bases:
objectA search hit — a place that might be the answer.
Search returns multiple of these; lookup picks one. Keep the structure small and JSON-safe so it round-trips through APIs and logs cleanly.
- class siege_utilities.geo.gazetteers.GazetteerResult[source]
Bases:
objectA resolved place: name, hierarchy, geometry, centroid.
- canonical_path
Hierarchical breadcrumb the source resolves the place through, e.g.
("US", "AL", "Birmingham"). Useful for downstream filtering and for re-querying the same backend without going through fuzzy search again.
- geometry
shapely shape (Polygon / MultiPolygon for admin areas; may be a Point for cities-as-points).
- Type:
Any
- centroid
shapely Point. Pre-computed because half the consumer code needs it and recomputing on every call is wasteful.
- Type:
Any
- admin_levels
Optional mapping of admin level name → value (e.g.
{"region": "Alabama", "county": "Jefferson"}). Backend-specific; not assumed by the resolver layer.
- raw
Backend-native object for callers that need full fidelity. Excluded from
reprandhashso the dataclass stays sane to print and stable as a cache key.- Type:
Any
- exception siege_utilities.geo.gazetteers.GazetteerError[source]
Bases:
RuntimeErrorBase class for all gazetteer failures.
- exception siege_utilities.geo.gazetteers.GazetteerNotFoundError[source]
Bases:
GazetteerErrorNo place matched the lookup criteria.
- exception siege_utilities.geo.gazetteers.GazetteerAmbiguousError[source]
Bases:
GazetteerErrorThe query is ambiguous and the caller didn’t pass enough hints.
Carries the candidates so the caller can surface them to a user or pick programmatically.
- __init__(message, candidates)[source]
- Parameters:
message (str)
candidates (list[GazetteerCandidate])
- exception siege_utilities.geo.gazetteers.GazetteerBackendError[source]
Bases:
GazetteerErrorThe backend raised an error we couldn’t translate.
Network failure, malformed upstream response, missing optional dependency at lookup time. Always chains the original via
__cause__.
- siege_utilities.geo.gazetteers.resolve_gazetteer(*, prefer=None, cache_size=1024)[source]
Return a configured
Gazetteerbackend.Selection order:
If
preferis given, use that backend (raises if unavailable).Otherwise, try WKLS (global coverage, no API key).
Then Nominatim (OSM-backed, public service).
Raise if nothing works.
- Parameters:
- Raises:
- Return type:
Gazetteer protocol — name → geometry resolution.
A gazetteer takes a place name ("Birmingham, AL", "Lausanne",
"Texas") and returns a structured GazetteerResult carrying
the canonical hierarchy path, the geometry, and a centroid. Backends
live in sibling modules (wkls_gazetteer,
nominatim_gazetteer, etc.) and are selected via the factory in
siege_utilities.geo.gazetteers.
The protocol is typed-Protocol rather than an ABC so backends can be plain classes without inheritance ceremony — useful for adapters wrapping foreign libraries.
Failure-mode discipline (matches the rest of siege_utilities, see
docs/FAILURE_MODES.md): every method raises a typed exception on
failure rather than returning None or an empty container that
silently looks like a successful empty result.
- class siege_utilities.geo.gazetteers.base.Gazetteer[source]
Bases:
ProtocolName → geometry resolver.
The protocol is intentionally narrow — four methods. Backends layer LRU caches, ISO-code translation, and other plumbing internally; callers just see this surface.
- lookup(name, *, country_hint=None, admin_hint=None)[source]
Best match for name.
- Parameters:
- Raises:
GazetteerNotFoundError – No match.
GazetteerAmbiguousError – Multiple matches; caller should pass more hints or use
search()and pick.GazetteerBackendError – Network / dependency / parse failure.
- Return type:
- search(name, *, country_hint=None, limit=10)[source]
Top-N candidate matches.
Used to surface ambiguity to a caller (interactive UI, or programmatic disambiguation when the caller has external knowledge to pick a candidate).
- Parameters:
- Return type:
- is_available()[source]
Return True if the backend’s dependencies are installed and the backend can answer queries.
Should NOT make a network round-trip — this is a fast probe used by the factory to pick a default backend.
- Return type:
- __init__(*args, **kwargs)
- class siege_utilities.geo.gazetteers.base.GazetteerResult[source]
Bases:
objectA resolved place: name, hierarchy, geometry, centroid.
- canonical_path
Hierarchical breadcrumb the source resolves the place through, e.g.
("US", "AL", "Birmingham"). Useful for downstream filtering and for re-querying the same backend without going through fuzzy search again.
- geometry
shapely shape (Polygon / MultiPolygon for admin areas; may be a Point for cities-as-points).
- Type:
Any
- centroid
shapely Point. Pre-computed because half the consumer code needs it and recomputing on every call is wasteful.
- Type:
Any
- admin_levels
Optional mapping of admin level name → value (e.g.
{"region": "Alabama", "county": "Jefferson"}). Backend-specific; not assumed by the resolver layer.
- raw
Backend-native object for callers that need full fidelity. Excluded from
reprandhashso the dataclass stays sane to print and stable as a cache key.- Type:
Any
- class siege_utilities.geo.gazetteers.base.GazetteerCandidate[source]
Bases:
objectA search hit — a place that might be the answer.
Search returns multiple of these; lookup picks one. Keep the structure small and JSON-safe so it round-trips through APIs and logs cleanly.
- exception siege_utilities.geo.gazetteers.base.GazetteerError[source]
Bases:
RuntimeErrorBase class for all gazetteer failures.
- exception siege_utilities.geo.gazetteers.base.GazetteerNotFoundError[source]
Bases:
GazetteerErrorNo place matched the lookup criteria.
- exception siege_utilities.geo.gazetteers.base.GazetteerAmbiguousError[source]
Bases:
GazetteerErrorThe query is ambiguous and the caller didn’t pass enough hints.
Carries the candidates so the caller can surface them to a user or pick programmatically.
- __init__(message, candidates)[source]
- Parameters:
message (str)
candidates (list[GazetteerCandidate])
- exception siege_utilities.geo.gazetteers.base.GazetteerBackendError[source]
Bases:
GazetteerErrorThe backend raised an error we couldn’t translate.
Network failure, malformed upstream response, missing optional dependency at lookup time. Always chains the original via
__cause__.
Census Geocoder + TIGERWeb gazetteer.
US-only. Two upstreams chained:
Census Geocoder’s
onelineaddress-style endpoint accepts a place name and returns matching geographies with FIPS codes (state + county + place + tract).TIGERWeb ArcGIS REST returns the polygon for a given (layer, FIPS) pair.
The split exists because Census Geocoder is address-centric and only
returns coords + FIPS for a place. The polygon comes from TIGER. We
hand back a unified GazetteerResult with shapely geometry,
hiding the two-call shape from callers.
Useful when the consumer already has a FIPS lookup pipeline downstream (electoral / NCES / Decennial workflows) and would prefer to skip the WKLS / Nominatim translation step.
- class siege_utilities.geo.gazetteers.census_gazetteer.CensusGazetteer[source]
Bases:
objectUS Census-backed gazetteer with admin polygons from TIGER.
- Parameters:
timeout – Per-request HTTP timeout (seconds).
cache_size – LRU cache size for the (name, hints) key.
vintage (benchmark /) – Census Geocoder benchmark/vintage strings. Defaults pin to “current”; consumers stuck to a fixed vintage (e.g. for reproducibility against a published decennial roll) can override.
- provider_name = 'census'
- __init__(*, timeout=15.0, cache_size=1024, benchmark='Public_AR_Current', vintage='Current_Current')[source]
Nominatim (OSM) gazetteer backend.
Wraps the public OpenStreetMap Nominatim service via geopy. We
ask for polygon_geojson=1 so the response carries admin polygons,
not just point centroids — that’s what makes this useful as a
Gazetteer.
Operational notes:
Public Nominatim has a 1 req/s rate limit and a Usage Policy that requires a meaningful
user_agent. We pass one through configuration; callers running at scale should self-host.server_urllets you point at a self-hosted Nominatim — when set, rate-limiting is dropped to a tiny delay.In-process LRU cache wraps the per-name resolution. Nominatim does cache server-side, but the round-trip is hundreds of ms; in-process caching matters for interactive use.
- class siege_utilities.geo.gazetteers.nominatim_gazetteer.NominatimGazetteer[source]
Bases:
objectOpenStreetMap-backed gazetteer with polygon geometry.
- Parameters:
user_agent – Required per OSM Usage Policy. Override the default for any production deployment so OSM ops can reach you.
server_url –
Nonefor the public Nominatim (rate-limited at 1 req/s); otherwise a self-hosted Nominatim base URL.timeout – Per-request timeout in seconds.
cache_size – LRU cache size for the (name, country, admin) key.
- provider_name = 'nominatim'
- __init__(*, user_agent='siege-utilities-gazetteer (https://github.com/siege-analytics/siege_utilities)', server_url=None, timeout=10.0, cache_size=1024)[source]
Wikidata + OSM gazetteer.
Two-step lookup:
SPARQL query against Wikidata Query Service for an entity matching the place name. Pull
wdt:P402(OSM relation ID) andwdt:P625(coordinate location) for each candidate.Deref the OSM relation ID against the Overpass API to get the admin polygon.
Useful for non-administrative places that WKLS / Nominatim’s admin boundary indexes miss: cultural regions, historical entities, informal areas. The fallback path (when P402 is absent) returns the P625 point with no polygon.
- class siege_utilities.geo.gazetteers.wikidata_gazetteer.WikidataGazetteer[source]
Bases:
objectWikidata SPARQL + OSM Overpass gazetteer.
- Parameters:
timeout – Per-request HTTP timeout.
cache_size – LRU cache for the (name, country) key.
overpass_url (sparql_url /) – Override for self-hosted instances.
- provider_name = 'wikidata'
- __init__(*, timeout=30.0, cache_size=1024, sparql_url='https://query.wikidata.org/sparql', overpass_url='https://overpass-api.de/api/interpreter')[source]
WKLS (Well Known Locations) gazetteer backend.
Wraps the upstream wkls package to resolve place names against Overture Maps administrative boundaries (~625k globally). Why this is the default global backend:
No API key, no rate limit (reads from public S3 via embedded sedonadb).
sedonadb is Rust + Arrow-native — no JVM / Spark dependency, just a 302 MB install.
Source data is Overture Maps, the well-curated successor to patchwork-of-everything that older gazetteers carried.
Two siege_utilities-specific pieces this backend adds on top of the raw wkls API:
Free-text → ISO code resolver. Etter outputs
"Alabama"; wkls wantswkls.us.al. We use the wkls metadata search to map the human form into the chained-attribute form before calling the geometry getter.In-process LRU cache. The upstream library does not cache geometry fetches in-process — every
.wkt()call hits S3 (~6s cold). For interactive use that’s a deal-breaker. We wrap the path(country, region, place_id)→ shapely geometry withfunctools.lru_cache().
Verified against wkls==1.1.0 on 2026-05-07.
- class siege_utilities.geo.gazetteers.wkls_gazetteer.WklsGazetteer[source]
Bases:
objectWKLS-backed gazetteer with in-process LRU caching.
- provider_name = 'wkls'
Spatio-temporal place history query API.
Answers “what’s the story of this place over time?” by composing crosswalk chains and overlay data into a unified response.
Usage:
result = place_history("17031010100", from_year=2000, to_year=2020)
print(result.lineage)
print(result.overlays)
The overlay system is extensible — see overlay_registry for
registering custom overlays.
- class siege_utilities.geo.place_history.CrosswalkProvider[source]
Bases:
ABCProtocol for fetching crosswalk data.
Implementations provide crosswalk records for a given GEOID and vintage transition. The Django-backed provider queries TemporalCrosswalk; test providers use in-memory data.
- class siege_utilities.geo.place_history.CrosswalkRecord[source]
Bases:
objectA single crosswalk mapping between two GEOIDs.
- class siege_utilities.geo.place_history.DictCrosswalkProvider[source]
Bases:
CrosswalkProviderIn-memory crosswalk provider for testing.
Data is keyed by (source_year, target_year) → dict of source_geoid → list[CrosswalkRecord].
- add(source_year, target_year, source_geoid, target_geoid, weight=1.0, relationship='IDENTICAL')[source]
- class siege_utilities.geo.place_history.Lineage[source]
Bases:
objectComplete crosswalk chain for a GEOID across time.
- steps
Ordered list of transitions.
- steps: list[LineageStep]
- class siege_utilities.geo.place_history.LineageStep[source]
Bases:
objectA single step in the crosswalk chain.
- class siege_utilities.geo.place_history.PlaceHistoryResult[source]
Bases:
objectUnified response for a place history query.
- lineage
Crosswalk chain showing boundary evolution.
- Type:
- siege_utilities.geo.place_history.place_history(geoid, from_year, to_year, overlays=None, state_fips=None, provider=None, overlay_registry=None)[source]
Query the history of a geographic place over time.
Composes crosswalk chains and optional overlays into a unified response showing how a place has evolved.
- Parameters:
geoid (str) – Census GEOID to query (tract, block group, county, etc.).
from_year (int) – Start year of the query range.
to_year (int) – End year of the query range.
overlays (list[str] | None) – List of overlay names to include (e.g., [“seats”, “demographics”]).
state_fips (str | None) – Optional state FIPS code for filtering crosswalks.
provider (CrosswalkProvider | None) – CrosswalkProvider for fetching crosswalk data. If None, attempts to use the Django-backed provider.
overlay_registry (Any | None) – Registry for resolving overlay names to implementations. If None, overlays are skipped.
- Returns:
PlaceHistoryResult with lineage and overlay data.
- Return type:
Overlays
Extensible overlay registry for place history queries.
Overlays add contextual data layers (seats, demographics, urbanicity,
events) to place_history() results. New overlays plug in via
the decorator-based registry without changing core query logic.
Usage:
from siege_utilities.geo.overlay_registry import (
PlaceHistoryOverlay,
overlay_registry,
)
@overlay_registry.register("my_overlay")
class MyOverlay(PlaceHistoryOverlay):
def fetch(self, geoid, from_year, to_year, state_fips=None):
return {"data": "..."}
- class siege_utilities.geo.overlay_registry.OverlayRegistry[source]
Bases:
objectRegistry for place history overlays.
Supports both decorator-based and imperative registration.
Decorator usage:
@overlay_registry.register("seats") class SeatsOverlay(PlaceHistoryOverlay): ...
Imperative usage:
overlay_registry.register_instance("seats", SeatsOverlay())
- register(name)[source]
Decorator to register an overlay class.
The class is instantiated on first use (lazy).
- Parameters:
name (str) – Overlay identifier used in place_history(overlays=[…]).
- Returns:
Decorator that registers the class.
- register_instance(name, instance)[source]
Register a pre-instantiated overlay.
- Parameters:
name (str) – Overlay identifier.
instance (PlaceHistoryOverlay) – PlaceHistoryOverlay instance.
- get(name)[source]
Look up an overlay by name.
Lazily instantiates class-registered overlays on first access.
- Parameters:
name (str) – Overlay identifier.
- Returns:
PlaceHistoryOverlay instance, or None if not registered.
- Raises:
Exception – If the registered overlay class fails to instantiate.
- Return type:
PlaceHistoryOverlay | None
- class siege_utilities.geo.overlay_registry.PlaceHistoryOverlay[source]
Bases:
ABCBase class for place history overlays.
Subclasses implement
fetch()to retrieve overlay-specific data for a queried GEOID and time range.
Place history overlay implementations.
Demographics overlay for place history.
Assembles DemographicSnapshot time series for a queried geography, handling GEOID changes via the crosswalk chain from the lineage.
- class siege_utilities.geo.overlays.demographics.DemographicPoint[source]
Bases:
objectA single demographic data point for a year.
- __init__(year=0, geoid='', dataset='', total_population=None, median_household_income=None, median_age=None, values=<factory>)
- class siege_utilities.geo.overlays.demographics.DemographicsDataProvider[source]
Bases:
ABCProtocol for fetching demographic snapshot data.
- class siege_utilities.geo.overlays.demographics.DemographicsOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay for demographic time series.
Queries DemographicSnapshot data for the queried GEOID and any related GEOIDs from the crosswalk chain (when a GEOID changes across decades).
- Parameters:
provider – DemographicsDataProvider for fetching snapshot data.
dataset – Restrict to a specific dataset (e.g., “acs5”).
terminal_geoids – Additional GEOIDs to query (from lineage).
- __init__(provider=None, dataset=None)[source]
- Parameters:
provider (DemographicsDataProvider | None)
dataset (str | None)
- class siege_utilities.geo.overlays.demographics.DemographicsOverlayResult[source]
Bases:
objectResult of the demographics overlay query.
- time_series
Ordered list of demographic data points.
- time_series: list[DemographicPoint]
- class siege_utilities.geo.overlays.demographics.DictDemographicsProvider[source]
Bases:
DemographicsDataProviderIn-memory demographics provider for testing.
- add(point)[source]
- Parameters:
point (DemographicPoint)
Election results overlay for place history.
Shows precinct-level election returns for a queried geography, reconciled via precinct-VTD mapping where needed.
- class siege_utilities.geo.overlays.election_results.DictElectionDataProvider[source]
Bases:
ElectionDataProviderIn-memory election data provider for testing.
- add(ret)[source]
- Parameters:
ret (ElectionReturn)
- class siege_utilities.geo.overlays.election_results.ElectionDataProvider[source]
Bases:
ABCProtocol for fetching election results for a geography.
- class siege_utilities.geo.overlays.election_results.ElectionResultsOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay for precinct-level election results.
Returns election returns for precincts/VTDs that overlap the queried geography, reconciled via precinct-VTD mapping.
- __init__(provider=None)[source]
- Parameters:
provider (ElectionDataProvider | None)
- class siege_utilities.geo.overlays.election_results.ElectionResultsOverlayResult[source]
Bases:
objectResult of the election results overlay query.
- returns
Election returns ordered by year then office.
- election_years
Distinct years with data.
- offices
Distinct offices with data.
- returns: list[ElectionReturn]
- __init__(geoid='', returns=<factory>)
- Parameters:
geoid (str)
returns (list[ElectionReturn])
- Return type:
None
- class siege_utilities.geo.overlays.election_results.ElectionReturn[source]
Bases:
objectElection result for a single contest in a single precinct/VTD.
Fraction of total votes (0-1).
- Type:
- reconciliation_method
How this precinct was mapped (spatial, name_match, official_crosswalk, or direct).
- Type:
- __init__(precinct_id='', election_year=0, election_type='general', office='', candidate='', party='', votes=0, total_votes=0, vote_share=0.0, state='', reconciliation_method='direct')
Events overlay for place history.
SpatioTemporalEvents intersecting the queried geography and time range. Events include court rulings, natural disasters, redistricting actions, and other discrete occurrences with both spatial and temporal extent.
- class siege_utilities.geo.overlays.events.DictEventsProvider[source]
Bases:
EventsDataProviderIn-memory events provider for testing.
- add(event)[source]
- Parameters:
event (EventRecord)
- class siege_utilities.geo.overlays.events.EventRecord[source]
Bases:
objectA single spatio-temporal event.
- event_type
Category (e.g., “court_ruling”, “natural_disaster”, “redistricting”, “election”, “policy_change”).
- Type:
- date
Event date.
- Type:
datetime.date | None
- end_date
End date for events with duration.
- Type:
datetime.date | None
- __init__(event_id='', event_type='', title='', description='', date=None, end_date=None, geoids_affected=<factory>, source='', metadata=<factory>)
- class siege_utilities.geo.overlays.events.EventsDataProvider[source]
Bases:
ABCProtocol for fetching spatio-temporal event data.
- class siege_utilities.geo.overlays.events.EventsOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay for spatio-temporal events.
Finds events that intersect the queried geography and time range.
- Parameters:
provider – EventsDataProvider for fetching events.
event_types – Optional filter for specific event types.
- __init__(provider=None, event_types=None)[source]
- Parameters:
provider (EventsDataProvider | None)
- class siege_utilities.geo.overlays.events.EventsOverlayResult[source]
Bases:
objectResult of the events overlay query.
- events
Events intersecting the geography and time range.
- events: list[EventRecord]
- __init__(geoid='', events=<factory>)
- Parameters:
geoid (str)
events (list[EventRecord])
- Return type:
None
Redistricting overlay for place history.
Shows which redistricting plan(s) and district(s) applied to a geography over a queried time range, using plan lifecycle data.
- class siege_utilities.geo.overlays.redistricting.DictRedistrictingDataProvider[source]
Bases:
RedistrictingDataProviderIn-memory redistricting data provider for testing.
- add(assignment)[source]
- Parameters:
assignment (DistrictAssignment)
- class siege_utilities.geo.overlays.redistricting.DistrictAssignment[source]
Bases:
objectA district assignment for a geography under a specific plan.
- from_date
When this assignment began.
- Type:
datetime.date | None
- to_date
When this assignment ended (None = still active).
- Type:
datetime.date | None
- __init__(plan_id='', plan_name='', plan_type='', district_id='', state='', from_date=None, to_date=None, enacted_by='', court_case='', containment_pct=1.0)
- class siege_utilities.geo.overlays.redistricting.RedistrictingDataProvider[source]
Bases:
ABCProtocol for fetching redistricting plan assignments for a geography.
- class siege_utilities.geo.overlays.redistricting.RedistrictingOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay for redistricting plan assignments.
Shows which plan(s) and district(s) applied to a geography over the queried time range.
- __init__(provider=None)[source]
- Parameters:
provider (RedistrictingDataProvider | None)
- class siege_utilities.geo.overlays.redistricting.RedistrictingOverlayResult[source]
Bases:
objectResult of the redistricting overlay query.
- assignments
District assignments ordered by time.
- plan_count
Number of distinct plans that applied.
- had_court_intervention
Whether any plan was court-ordered.
- assignments: list[DistrictAssignment]
- __init__(geoid='', assignments=<factory>)
- Parameters:
geoid (str)
assignments (list[DistrictAssignment])
- Return type:
None
Seats overlay for place history.
Shows which congressional/state legislative districts contained a geography over time, under which redistricting plans.
Key design constraint from the ST model: “Seat is identity, not geography.” The overlay returns Seat + Plan, not raw CD geometry.
- class siege_utilities.geo.overlays.seats.DictSeatsProvider[source]
Bases:
SeatsDataProviderIn-memory seats provider for testing.
- add(assignment)[source]
- Parameters:
assignment (SeatAssignment)
- class siege_utilities.geo.overlays.seats.SeatAssignment[source]
Bases:
objectA seat-to-geography assignment for a time period.
- __init__(seat_label='', office='', district_label='', state_fips='', plan_name='', plan_year=None, from_year=None, to_year=None, containment_pct=1.0)
- class siege_utilities.geo.overlays.seats.SeatsDataProvider[source]
Bases:
ABCProtocol for fetching seat assignment data.
Implementations query PlanDistrictAssignment + BoundaryIntersection to find which districts contained a GEOID over time.
- class siege_utilities.geo.overlays.seats.SeatsOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay showing district/seat assignments over time.
- Parameters:
provider – SeatsDataProvider for fetching assignment data. If None, attempts to use the Django-backed provider.
- __init__(provider=None)[source]
- Parameters:
provider (SeatsDataProvider | None)
- class siege_utilities.geo.overlays.seats.SeatsOverlayResult[source]
Bases:
objectResult of the seats overlay query.
- assignments
Seat assignments ordered by time.
- current_districts
Districts containing the GEOID now.
- assignments: list[SeatAssignment]
- property current_districts: list[SeatAssignment]
- __init__(geoid='', assignments=<factory>)
- Parameters:
geoid (str)
assignments (list[SeatAssignment])
- Return type:
None
Urbanicity overlay for place history.
NCES locale classification per vintage for a queried geography. Calls NCESLocaleClassifier (WS3) for each vintage in the range.
- class siege_utilities.geo.overlays.urbanicity.DictUrbanicityProvider[source]
Bases:
UrbanicityDataProviderIn-memory urbanicity provider for testing.
- add(point)[source]
- Parameters:
point (UrbanicityPoint)
- classify(geoid, year)[source]
Classify a GEOID for a given vintage year.
- Parameters:
- Return type:
UrbanicityPoint | None
- class siege_utilities.geo.overlays.urbanicity.UrbanicityDataProvider[source]
Bases:
ABCProtocol for fetching NCES locale classification data.
- abstractmethod classify(geoid, year)[source]
Classify a GEOID for a given vintage year.
- Parameters:
- Return type:
UrbanicityPoint | None
- class siege_utilities.geo.overlays.urbanicity.UrbanicityOverlay[source]
Bases:
PlaceHistoryOverlayPlace history overlay for NCES urbanicity classification.
Classifies the queried GEOID using the NCES 12-code locale system for each relevant vintage in the query range.
- Parameters:
provider – UrbanicityDataProvider for classification.
vintage_years – Years to check. Defaults to NCES vintages.
- __init__(provider=None, vintage_years=None)[source]
- Parameters:
provider (UrbanicityDataProvider | None)
- class siege_utilities.geo.overlays.urbanicity.UrbanicityOverlayResult[source]
Bases:
objectResult of the urbanicity overlay query.
- classifications
Ordered list of urbanicity points by year.
- classifications: list[UrbanicityPoint]
- for_year(year)[source]
- Parameters:
year (int)
- Return type:
UrbanicityPoint | None
- __init__(geoid='', classifications=<factory>)
- Parameters:
geoid (str)
classifications (list[UrbanicityPoint])
- Return type:
None
Redistricting and Plans
Redistricting plan resolution by date (issue #361).
Models a redistricting plan as a named, dated artifact and exposes a
PlanRegistry that resolves the plan in effect for a
(state_fips, district_type, date) tuple. Replaces the
decade-keyed CensusVintageConfig for callers that need to handle
mid-cycle court orders (Alabama 2023, Louisiana 2024, etc.).
This is the foundational module — it has no consumer code yet.
Downstream PRs wire RDHProvider and assign_boundaries to
consult the registry.
- class siege_utilities.geo.plans.PlanAuthority[source]
-
Who drew the plan — legally meaningful for citation and audit.
- LEGISLATURE = 'legislature'
Enacted by the state legislature (the default cycle path).
- COURT = 'court'
Court-imposed map (interim or final).
- COMMISSION = 'commission'
Independent or politician redistricting commission.
- DEFAULT = 'default'
Pre-decennial-shift baseline; used by the legacy resolver when no explicit plan covers a date.
- __new__(value)
- class siege_utilities.geo.plans.PlanDistrict[source]
Bases:
objectA single district within a redistricting plan.
All boundaries fields are optional — many consumers only need the identity (which plan, which district, valid when) and resolve the geometry separately via a
BoundaryProvider.- district_type
Lowercase district class —
"cd","sldu","sldl","county_commission", etc. Match the keys used byCensusTIGERProvider.- Type:
- district_id
District identifier within the plan.
"7"for AL-7,"01"for SLDU 1, etc. Stored as the string form so leading-zero district numbers (sometimes used by states) are preserved.- Type:
- plan_name
Stable, human-readable plan identifier (e.g.
"AL_2023_CD_INTERIM"). Conventional but not enforced.- Type:
- authority
Who drew the plan (see
PlanAuthority).
- effective_from
First date the district is in legal effect.
- Type:
- effective_to
Last date the district is in legal effect, or ``None`` for an open-ended plan (the currently active one).
- Type:
datetime.date | None
- geometry_source
Optional pointer to the geometry — a URL, a local path, an RDH dataset slug, or any string the consuming
BoundaryProviderunderstands.Noneis fine if callers only need identity resolution.- Type:
str | None
- notes
Free-text annotation (e.g. court docket, statute citation). Goes through to logs and audit trails verbatim.
- Type:
str | None
- authority: PlanAuthority
- covers_date(when)[source]
Return True iff when falls within this district’s effective span.
Half-open at the upper end:
effective_tois inclusive (if the new plan starts the next day, encode that aseffective_from = old_to + timedelta(days=1)). This matches how court orders are typically written (“effective for elections on or after DATE”) and avoids the off-by-one trap of half-open Python slices.
- __init__(state_fips, district_type, district_id, plan_name, authority, effective_from, effective_to=None, geometry_source=None, notes=None)
- class siege_utilities.geo.plans.RedistrictingPlan[source]
Bases:
objectA full plan: a collection of districts plus plan-level metadata.
The plan is the unit of court orders, statutes, and commission actions; the individual
PlanDistrictrows just decompose it for query convenience.effective_from/effective_toon the plan should match the values on its constituent districts.- plan_name
Same convention as
PlanDistrict.plan_name.- Type:
- authority
Who drew it.
- effective_from
First date the plan is in legal effect.
- Type:
- effective_to
Last date in effect, or
Noneif currently active.- Type:
datetime.date | None
- districts
Tuple of
PlanDistrictrows under this plan.- Type:
- metadata
Free-form mapping for citation, source URLs, court docket numbers, etc. Not used for resolution — consumers can stuff whatever they want here.
Note:
frozen=Trueonly forbids attribute reassignment; themetadatadict remains mutable in place. Instances are therefore not hashable and should be treated as value objects, never used as dict keys or set members.- authority: PlanAuthority
- districts: Tuple[PlanDistrict, ...]
- district(district_id)[source]
Find a district within this plan by id, or
None.- Parameters:
district_id (str)
- Return type:
PlanDistrict | None
- __init__(plan_name, state_fips, district_type, authority, effective_from, effective_to=None, districts=<factory>, metadata=<factory>)
- class siege_utilities.geo.plans.PlanRegistry[source]
Bases:
objectStores redistricting plans and resolves them by date.
Plans are keyed by
(state_fips, district_type). Within a key, plans should be temporally non-overlapping; the registry detects overlap at registration time (warn) and at resolution time (raise in strict mode).Thread-safe for concurrent readers; mutation under a lock.
- register_plan(plan)[source]
Add plan to the registry.
Logs a warning if it overlaps an already-registered plan for the same
(state_fips, district_type). Does not raise on overlap — registration is forgiving so consumers can load partial data; resolution is the strict gate.- Parameters:
plan (RedistrictingPlan)
- Return type:
None
- register_plans(plans)[source]
Bulk-register an iterable of plans.
- Parameters:
plans (Iterable[RedistrictingPlan])
- Return type:
None
- plans_for_state(state_fips, district_type)[source]
Return all registered plans for state_fips / district_type.
Sorted by
effective_fromascending. Empty list if none.- Parameters:
- Return type:
- resolve_plan_at_date(state_fips, district_type, when, *, strict=True)[source]
Find the plan in effect for state_fips / district_type on when.
- Parameters:
state_fips (str) – 2-char state FIPS.
district_type (str) –
"cd","sldu","sldl", etc.when (date) – The date to resolve.
strict (bool) – If
True(default), raisePlanOverlapErrorwhen more than one plan covers when. IfFalse, log a warning and return the most recently enacted one (latesteffective_from) — appropriate when court interim/final pairs both technically cover the same day.
- Raises:
PlanResolutionError – No plan covers the date.
PlanOverlapError – Multiple plans cover the date and
strict=True.
- Return type:
- resolve_district_at_date(state_fips, district_type, district_id, when, *, strict=True)[source]
Find the specific district covering when.
Convenience wrapper: resolves the plan, then looks up the district by id. Raises
PlanResolutionErrorif the plan does not contain district_id.
- exception siege_utilities.geo.plans.PlanResolutionError[source]
Bases:
LookupErrorNo plan covers the requested (state, district_type, date) tuple.
- exception siege_utilities.geo.plans.PlanOverlapError[source]
Bases:
ValueErrorTwo registered plans cover the same date for the same state+type.
Raised by
PlanRegistry.resolve_plan_at_date()when the registry finds more than one match in strict mode. Caller can rerun withstrict=Falseto get the first match (and a warning), or fix the underlying plan data.
- siege_utilities.geo.plans.get_default_plan_registry()[source]
Return the process-global default
PlanRegistry.Lazily instantiated on first call. Consumers that need isolation (multi-tenant code, unit tests) should instantiate
PlanRegistrydirectly instead.- Return type:
Data models for redistricting plans and their districts.
A plan is a named, dated cartographic artifact: e.g. “Alabama 2023 court-interim congressional map”. Plans replace each other over time — the same state has multiple plans for the same district type within a single decennial cycle when a court intervenes (Alabama 2022/2023, Louisiana 2023/2024, New York 2022/2024, Georgia 2023/2024).
The existing CensusVintageConfig in this package keys boundaries by
decade (“2010 → 2010-2019”), which cannot represent a donation made in
AL-7 in March 2023 vs the same address in September 2023 (different
geometry). These models give consumers a date-resolvable shape.
This module is the foundation of the redistricting-plan resolution
system (issue #361). The matching siege_utilities.geo.plans.registry
provides date-based lookup; downstream PRs wire RDHProvider and
assign_boundaries to use it.
- class siege_utilities.geo.plans.models.PlanAuthority[source]
-
Who drew the plan — legally meaningful for citation and audit.
- LEGISLATURE = 'legislature'
Enacted by the state legislature (the default cycle path).
- COURT = 'court'
Court-imposed map (interim or final).
- COMMISSION = 'commission'
Independent or politician redistricting commission.
- DEFAULT = 'default'
Pre-decennial-shift baseline; used by the legacy resolver when no explicit plan covers a date.
- __new__(value)
- class siege_utilities.geo.plans.models.PlanDistrict[source]
Bases:
objectA single district within a redistricting plan.
All boundaries fields are optional — many consumers only need the identity (which plan, which district, valid when) and resolve the geometry separately via a
BoundaryProvider.- district_type
Lowercase district class —
"cd","sldu","sldl","county_commission", etc. Match the keys used byCensusTIGERProvider.- Type:
- district_id
District identifier within the plan.
"7"for AL-7,"01"for SLDU 1, etc. Stored as the string form so leading-zero district numbers (sometimes used by states) are preserved.- Type:
- plan_name
Stable, human-readable plan identifier (e.g.
"AL_2023_CD_INTERIM"). Conventional but not enforced.- Type:
- authority
Who drew the plan (see
PlanAuthority).
- effective_from
First date the district is in legal effect.
- Type:
- effective_to
Last date the district is in legal effect, or ``None`` for an open-ended plan (the currently active one).
- Type:
datetime.date | None
- geometry_source
Optional pointer to the geometry — a URL, a local path, an RDH dataset slug, or any string the consuming
BoundaryProviderunderstands.Noneis fine if callers only need identity resolution.- Type:
str | None
- notes
Free-text annotation (e.g. court docket, statute citation). Goes through to logs and audit trails verbatim.
- Type:
str | None
- authority: PlanAuthority
- covers_date(when)[source]
Return True iff when falls within this district’s effective span.
Half-open at the upper end:
effective_tois inclusive (if the new plan starts the next day, encode that aseffective_from = old_to + timedelta(days=1)). This matches how court orders are typically written (“effective for elections on or after DATE”) and avoids the off-by-one trap of half-open Python slices.
- __init__(state_fips, district_type, district_id, plan_name, authority, effective_from, effective_to=None, geometry_source=None, notes=None)
- class siege_utilities.geo.plans.models.RedistrictingPlan[source]
Bases:
objectA full plan: a collection of districts plus plan-level metadata.
The plan is the unit of court orders, statutes, and commission actions; the individual
PlanDistrictrows just decompose it for query convenience.effective_from/effective_toon the plan should match the values on its constituent districts.- plan_name
Same convention as
PlanDistrict.plan_name.- Type:
- authority
Who drew it.
- effective_from
First date the plan is in legal effect.
- Type:
- effective_to
Last date in effect, or
Noneif currently active.- Type:
datetime.date | None
- districts
Tuple of
PlanDistrictrows under this plan.- Type:
- metadata
Free-form mapping for citation, source URLs, court docket numbers, etc. Not used for resolution — consumers can stuff whatever they want here.
Note:
frozen=Trueonly forbids attribute reassignment; themetadatadict remains mutable in place. Instances are therefore not hashable and should be treated as value objects, never used as dict keys or set members.- authority: PlanAuthority
- districts: Tuple[PlanDistrict, ...]
- district(district_id)[source]
Find a district within this plan by id, or
None.- Parameters:
district_id (str)
- Return type:
PlanDistrict | None
- __init__(plan_name, state_fips, district_type, authority, effective_from, effective_to=None, districts=<factory>, metadata=<factory>)
Date-resolvable registry of redistricting plans.
Consumers register RedistrictingPlan instances and then ask
“which plan was active in Alabama for congressional districts on
2023-09-15?” — the registry returns the right plan (or raises if there
is overlap or a gap, depending on strictness).
The registry is in-memory by design. File-backed plan catalogs (RDH
manifest, NCSL plan registry) are loaded into an instance via
PlanRegistry.register_plan() at consumer startup; the resolver
itself doesn’t know about disk.
A module-level singleton (get_default_plan_registry()) is provided
for the common case where one process has one global view of plans.
Consumers that need isolation (multi-tenant, tests) instantiate their
own.
- class siege_utilities.geo.plans.registry.PlanRegistry[source]
Bases:
objectStores redistricting plans and resolves them by date.
Plans are keyed by
(state_fips, district_type). Within a key, plans should be temporally non-overlapping; the registry detects overlap at registration time (warn) and at resolution time (raise in strict mode).Thread-safe for concurrent readers; mutation under a lock.
- register_plan(plan)[source]
Add plan to the registry.
Logs a warning if it overlaps an already-registered plan for the same
(state_fips, district_type). Does not raise on overlap — registration is forgiving so consumers can load partial data; resolution is the strict gate.- Parameters:
plan (RedistrictingPlan)
- Return type:
None
- register_plans(plans)[source]
Bulk-register an iterable of plans.
- Parameters:
plans (Iterable[RedistrictingPlan])
- Return type:
None
- plans_for_state(state_fips, district_type)[source]
Return all registered plans for state_fips / district_type.
Sorted by
effective_fromascending. Empty list if none.- Parameters:
- Return type:
- resolve_plan_at_date(state_fips, district_type, when, *, strict=True)[source]
Find the plan in effect for state_fips / district_type on when.
- Parameters:
state_fips (str) – 2-char state FIPS.
district_type (str) –
"cd","sldu","sldl", etc.when (date) – The date to resolve.
strict (bool) – If
True(default), raisePlanOverlapErrorwhen more than one plan covers when. IfFalse, log a warning and return the most recently enacted one (latesteffective_from) — appropriate when court interim/final pairs both technically cover the same day.
- Raises:
PlanResolutionError – No plan covers the date.
PlanOverlapError – Multiple plans cover the date and
strict=True.
- Return type:
- resolve_district_at_date(state_fips, district_type, district_id, when, *, strict=True)[source]
Find the specific district covering when.
Convenience wrapper: resolves the plan, then looks up the district by id. Raises
PlanResolutionErrorif the plan does not contain district_id.
- exception siege_utilities.geo.plans.registry.PlanResolutionError[source]
Bases:
LookupErrorNo plan covers the requested (state, district_type, date) tuple.
- exception siege_utilities.geo.plans.registry.PlanOverlapError[source]
Bases:
ValueErrorTwo registered plans cover the same date for the same state+type.
Raised by
PlanRegistry.resolve_plan_at_date()when the registry finds more than one match in strict mode. Caller can rerun withstrict=Falseto get the first match (and a warning), or fix the underlying plan data.
- siege_utilities.geo.plans.registry.get_default_plan_registry()[source]
Return the process-global default
PlanRegistry.Lazily instantiated on first call. Consumers that need isolation (multi-tenant code, unit tests) should instantiate
PlanRegistrydirectly instead.- Return type:
Redistricting plan lifecycle tracking.
Models the lifecycle of redistricting plans: proposed -> adopted -> enacted -> challenged -> stayed/struck -> superseded. Supports court-order branching and temporal queries (“what plan was in effect for TX congress on 2023-09-15?”).
- class siege_utilities.geo.plan_lifecycle.DictPlanLifecycleProvider[source]
Bases:
PlanLifecycleProviderIn-memory plan lifecycle provider for testing.
- add(plan)[source]
- Parameters:
plan (RedistrictingPlan)
- get_plans(state, plan_type=None)[source]
Get all plans for a state, optionally filtered by type.
- Parameters:
- Return type:
- get_plan(plan_id)[source]
Get a single plan by ID.
- Parameters:
plan_id (str)
- Return type:
RedistrictingPlan | None
- class siege_utilities.geo.plan_lifecycle.EnactedBy[source]
-
Authority that enacted the plan.
- LEGISLATURE = 'legislature'
- COMMISSION = 'commission'
- COURT = 'court'
- SPECIAL_MASTER = 'special_master'
- __new__(value)
- class siege_utilities.geo.plan_lifecycle.PlanLifecycleEvent[source]
Bases:
objectA lifecycle transition for a redistricting plan.
- status
New lifecycle status after this event.
- effective_date
When this status took effect.
- Type:
datetime.date | None
- status: PlanLifecycleStatus = 'proposed'
- class siege_utilities.geo.plan_lifecycle.PlanLifecycleProvider[source]
Bases:
ABCProtocol for querying redistricting plan lifecycle data.
- abstractmethod get_plans(state, plan_type=None)[source]
Get all plans for a state, optionally filtered by type.
- Parameters:
- Return type:
- abstractmethod get_plan(plan_id)[source]
Get a single plan by ID.
- Parameters:
plan_id (str)
- Return type:
RedistrictingPlan | None
- class siege_utilities.geo.plan_lifecycle.PlanLifecycleStatus[source]
-
Status in the lifecycle of a redistricting plan.
- PROPOSED = 'proposed'
- ADOPTED = 'adopted'
- ENACTED = 'enacted'
- CHALLENGED = 'challenged'
- STAYED = 'stayed'
- STRUCK = 'struck'
- SUPERSEDED = 'superseded'
- EXPIRED = 'expired'
- __new__(value)
- class siege_utilities.geo.plan_lifecycle.PlanLineage[source]
Bases:
objectChain of plans linked by supersession.
- plans
Plans in chronological order (oldest first).
- plan_type
Type of plan.
- plans: list[RedistrictingPlan]
- property current_plan: RedistrictingPlan | None
- plan_at(when)[source]
- Parameters:
when (date)
- Return type:
RedistrictingPlan | None
- __init__(plans=<factory>, state='', plan_type=PlanType.CONGRESSIONAL)
- Parameters:
plans (list[RedistrictingPlan])
state (str)
plan_type (PlanType)
- Return type:
None
- class siege_utilities.geo.plan_lifecycle.PlanType[source]
-
Type of redistricting plan.
- CONGRESSIONAL = 'congressional'
- STATE_SENATE = 'state_senate'
- STATE_HOUSE = 'state_house'
- __new__(value)
- class siege_utilities.geo.plan_lifecycle.RedistrictingPlan[source]
Bases:
objectA redistricting plan with lifecycle events.
- plan_type
Congressional, state senate, or state house.
- enacted_by
Authority that enacted the plan.
- enacted_date
When the plan was enacted.
- Type:
datetime.date | None
- events
Ordered lifecycle events.
- events: list[PlanLifecycleEvent]
- property current_status: PlanLifecycleStatus | None
- property latest_event: PlanLifecycleEvent | None
- status_at(when)[source]
- Parameters:
when (date)
- Return type:
PlanLifecycleStatus | None
- __init__(plan_id='', state='', plan_type=PlanType.CONGRESSIONAL, plan_name='', enacted_by=EnactedBy.LEGISLATURE, enacted_date=None, supersedes_id=None, court_case='', events=<factory>)
- siege_utilities.geo.plan_lifecycle.build_lineage(plans)[source]
Build a supersession lineage from a list of related plans.
Plans are ordered by enacted_date.
- Parameters:
plans (list[RedistrictingPlan]) – Plans that form a lineage (same state + type).
- Return type:
- siege_utilities.geo.plan_lifecycle.resolve_plan_at(provider, state, plan_type, when)[source]
Resolve which plan was in effect at a given date.
Queries the provider for all plans of the given state and type, then finds the one that was active at the specified date.
- Parameters:
provider (PlanLifecycleProvider) – Source for plan lifecycle data.
state (str) – Two-letter state abbreviation.
plan_type (PlanType) – Type of plan to resolve.
when (date) – Date to query.
- Returns:
The RedistrictingPlan that was in effect, or None.
- Return type:
RedistrictingPlan | None
Precinct-to-VTD reconciliation.
Reconciles RDH precinct shapefiles with Census VTDs (Voting Tabulation Districts) using spatial overlap, name matching, and official crosswalks.
- class siege_utilities.geo.precinct_vtd.ConfidenceLevel[source]
-
Confidence tier for a reconciliation mapping.
- HIGH = 'high'
- MEDIUM = 'medium'
- LOW = 'low'
- __new__(value)
- class siege_utilities.geo.precinct_vtd.DictNameMatchProvider[source]
Bases:
NameMatchProviderIn-memory name match provider for testing.
- class siege_utilities.geo.precinct_vtd.DictSpatialOverlapProvider[source]
Bases:
SpatialOverlapProviderIn-memory spatial overlap provider for testing.
- add(overlap)[source]
- Parameters:
overlap (SpatialOverlap)
- class siege_utilities.geo.precinct_vtd.NameMatchProvider[source]
Bases:
ABCProtocol for matching precinct names to VTD names.
- class siege_utilities.geo.precinct_vtd.OfficialCrosswalkEntry[source]
Bases:
objectEntry from an official precinct-to-VTD crosswalk.
- class siege_utilities.geo.precinct_vtd.PrecinctVTDMapping[source]
Bases:
objectA single precinct-to-VTD mapping with confidence metadata.
- confidence_level
Categorical confidence tier.
- method
How this mapping was determined.
- confidence_level: ConfidenceLevel = 'low'
- method: ReconciliationMethod = 'spatial'
- __init__(precinct_id='', vtd_geoid='', overlap_pct=0.0, confidence=0.0, confidence_level=ConfidenceLevel.LOW, method=ReconciliationMethod.SPATIAL, name_similarity=None, notes='')
- Parameters:
precinct_id (str)
vtd_geoid (str)
overlap_pct (float)
confidence (float)
confidence_level (ConfidenceLevel)
method (ReconciliationMethod)
name_similarity (float | None)
notes (str)
- Return type:
None
- class siege_utilities.geo.precinct_vtd.PrecinctVTDReconciler[source]
Bases:
objectReconciles precinct boundaries with Census VTDs.
Uses spatial overlap, name matching, and official crosswalks (when available) to produce precinct-to-VTD mappings with confidence scores.
- Parameters:
spatial_provider – Source for spatial overlap computation.
name_provider – Source for precinct/VTD name matching.
official_crosswalk – Pre-loaded official crosswalk entries.
- __init__(spatial_provider=None, name_provider=None, official_crosswalk=None)[source]
- Parameters:
spatial_provider (SpatialOverlapProvider | None)
name_provider (NameMatchProvider | None)
official_crosswalk (list[OfficialCrosswalkEntry] | None)
- class siege_utilities.geo.precinct_vtd.ReconciliationMethod[source]
-
How a precinct-VTD mapping was determined.
- SPATIAL = 'spatial'
- NAME_MATCH = 'name_match'
- OFFICIAL_CROSSWALK = 'official_crosswalk'
- COMBINED = 'combined'
- __new__(value)
- class siege_utilities.geo.precinct_vtd.ReconciliationResult[source]
Bases:
objectComplete result of precinct-VTD reconciliation.
- mappings
All precinct-VTD mappings.
- mappings: list[PrecinctVTDMapping]
- __init__(mappings=<factory>, total_precincts=0, matched_precincts=0, unmatched_precincts=<factory>, method_counts=<factory>, errors=<factory>)
- class siege_utilities.geo.precinct_vtd.SpatialOverlap[source]
Bases:
objectSpatial overlap between a precinct and a VTD.
- class siege_utilities.geo.precinct_vtd.SpatialOverlapProvider[source]
Bases:
ABCProtocol for computing spatial overlaps between precincts and VTDs.
- siege_utilities.geo.precinct_vtd.reconcile_names(precinct_names, vtd_names)[source]
Create mappings from fuzzy name matching.
- siege_utilities.geo.precinct_vtd.reconcile_official(crosswalk)[source]
Create mappings from an official crosswalk.
- Parameters:
crosswalk (list[OfficialCrosswalkEntry])
- Return type:
- siege_utilities.geo.precinct_vtd.reconcile_spatial(overlaps)[source]
Create mappings from spatial overlaps.
- Parameters:
overlaps (list[SpatialOverlap])
- Return type:
Codification
Address-based area codification.
Given a set of addresses, geocodes them, assigns to Census blocks, and builds a structured area portrait with demographics, urbanicity, and recency analysis.
Usage:
from siege_utilities.geo.codification import codify_area
result = codify_area(["123 Main St, Austin, TX", ...])
print(result.block_count)
print(result.summarize())
- class siege_utilities.geo.codification.BlockProfile[source]
Bases:
objectProfile of a single Census block within the codified area.
- __init__(block_geoid='', tract_geoid='', county_geoid='', state_geoid='', address_count=0, demographics=<factory>, urbanicity=<factory>, recency=<factory>)
- class siege_utilities.geo.codification.CodificationResult[source]
Bases:
objectStructured area portrait from address codification.
- blocks
Per-block profiles.
- blocks: list[BlockProfile]
- __init__(total_addresses=0, matched_addresses=0, match_rate=0.0, blocks=<factory>, geocoding_backend='', census_year=None, errors=<factory>)
- siege_utilities.geo.codification.codify_area(addresses, geocoder=None, census_year=None, **kwargs)[source]
Codify an area from a set of addresses.
Geocodes the addresses, assigns them to Census blocks, and builds a structured area portrait.
- Parameters:
- Returns:
CodificationResult with block-level profiles.
- Return type:
Block assignment for codification.
Groups geocoded addresses by Census block GEOID and computes geographic spread metrics (block count, spatial extent, concentration).
- class siege_utilities.geo.codification_blocks.BlockAssignment[source]
Bases:
objectGeocoded address assigned to a Census block.
- class siege_utilities.geo.codification_blocks.BlockGroup[source]
Bases:
objectAddresses grouped by Census block.
- __init__(block_geoid='', tract_geoid='', county_geoid='', state_geoid='', address_count=0, centroid_lat=None, centroid_lon=None)
- class siege_utilities.geo.codification_blocks.SpreadMetrics[source]
Bases:
objectGeographic spread metrics for a set of block assignments.
- concentration
Herfindahl index of block address distribution (0=evenly spread, 1=all in one block).
- Type:
- __init__(block_count=0, tract_count=0, county_count=0, state_count=0, concentration=0.0, max_block_pct=0.0, bbox_lat_range=0.0, bbox_lon_range=0.0)
- siege_utilities.geo.codification_blocks.assign_blocks(geocoded_results)[source]
Convert geocoding results to block assignments.
- Parameters:
geocoded_results – List of GeocodingResult from a BatchGeocoder.
- Returns:
List of BlockAssignment for matched results with coordinates.
- Return type:
- siege_utilities.geo.codification_blocks.group_by_block(assignments)[source]
Group block assignments by block GEOID.
Returns groups sorted by address count (descending).
- Parameters:
assignments (list[BlockAssignment])
- Return type:
- siege_utilities.geo.codification_blocks.compute_spread(assignments, groups=None)[source]
Compute geographic spread metrics for block assignments.
- Parameters:
assignments (list[BlockAssignment]) – All block assignments.
groups (list[BlockGroup] | None) – Pre-computed block groups (optional, computed if not provided).
- Return type:
Demographic enrichment for codification.
Fetches block-level demographics for matched blocks and produces an address-weighted demographic signature: race/ethnicity distribution, housing occupancy, voting-age composition.
- class siege_utilities.geo.codification_demographics.BlockDemographics[source]
Bases:
objectPL 94-171 demographics for a single Census block.
- __init__(block_geoid='', total_population=0, voting_age_population=0, white=0, black=0, hispanic=0, asian=0, other_race=0, housing_total=0, housing_occupied=0, housing_vacant=0)
- class siege_utilities.geo.codification_demographics.DemographicSignature[source]
Bases:
objectAddress-weighted demographic signature for a codified area.
All percentages are weighted by address count per block.
- __init__(total_block_population=0, weighted_blocks=0, pct_white=0.0, pct_black=0.0, pct_hispanic=0.0, pct_asian=0.0, pct_other=0.0, pct_voting_age=0.0, pct_housing_occupied=0.0, pct_housing_vacant=0.0)
- class siege_utilities.geo.codification_demographics.BlockDemographicsProvider[source]
Bases:
ABCProtocol for fetching block-level demographics.
- class siege_utilities.geo.codification_demographics.DictBlockDemographicsProvider[source]
Bases:
BlockDemographicsProviderIn-memory demographics provider for testing.
- add(demo)[source]
- Parameters:
demo (BlockDemographics)
- siege_utilities.geo.codification_demographics.compute_demographic_signature(block_address_counts, demographics)[source]
Compute address-weighted demographic signature.
- Parameters:
- Returns:
DemographicSignature with address-weighted percentages.
- Return type:
Recency analysis for codification.
Compares address presence across TIGER vintages to produce a construction timeline: when did these addresses first appear?
- class siege_utilities.geo.codification_recency.AddressVintage[source]
Bases:
objectEarliest known TIGER vintage for an address.
- class siege_utilities.geo.codification_recency.RecencyAnalysis[source]
Bases:
objectAddress vintage distribution for a codified area.
- __init__(total_analyzed=0, vintage_distribution=<factory>, vintage_pct=<factory>, new_construction_count=0, new_construction_pct=0.0, median_vintage=None)
- class siege_utilities.geo.codification_recency.AddressVintageProvider[source]
Bases:
ABCProtocol for determining address vintage from TIGER data.
- class siege_utilities.geo.codification_recency.DictAddressVintageProvider[source]
Bases:
AddressVintageProviderIn-memory vintage provider for testing.
- add(vintage)[source]
- Parameters:
vintage (AddressVintage)
- siege_utilities.geo.codification_recency.compute_recency(vintages)[source]
Compute recency analysis from address vintages.
- Parameters:
vintages (list[AddressVintage]) – List of AddressVintage with first_vintage set.
- Return type:
Summary portrait for codification.
Assembles headline metrics from block assignment, demographics, urbanicity, and recency into a structured area portrait.
- class siege_utilities.geo.codification_summary.AreaPortrait[source]
Bases:
objectStructured summary portrait of a codified area.
Combines headline metrics from all codification enrichment layers.
- spread: SpreadMetrics | None = None
- demographics: DemographicSignature | None = None
- urbanicity: UrbanicityDistribution | None = None
- recency: RecencyAnalysis | None = None
- top_blocks: list[BlockGroup]
- __init__(geocoding_summary=<factory>, spread=None, demographics=None, urbanicity=None, recency=None, top_blocks=<factory>)
- Parameters:
spread (SpreadMetrics | None)
demographics (DemographicSignature | None)
urbanicity (UrbanicityDistribution | None)
recency (RecencyAnalysis | None)
top_blocks (list[BlockGroup])
- Return type:
None
- siege_utilities.geo.codification_summary.build_portrait(codification, spread=None, demographics=None, urbanicity=None, recency=None, top_n=10)[source]
Build a structured area portrait from codification results.
- Parameters:
codification (CodificationResult) – CodificationResult from codify_area().
spread (SpreadMetrics | None) – Pre-computed spread metrics.
demographics (DemographicSignature | None) – Pre-computed demographic signature.
urbanicity (UrbanicityDistribution | None) – Pre-computed urbanicity distribution.
recency (RecencyAnalysis | None) – Pre-computed recency analysis.
top_n (int) – Number of top blocks to include.
- Return type:
Urbanicity enrichment for codification.
Classifies each matched block’s urbanicity using the NCES locale system and produces an address-weighted distribution.
- class siege_utilities.geo.codification_urbanicity.BlockLocale[source]
Bases:
objectNCES locale classification for a Census block.
- class siege_utilities.geo.codification_urbanicity.UrbanicityDistribution[source]
Bases:
objectAddress-weighted urbanicity distribution for a codified area.
- __init__(total_classified=0, distribution=<factory>, category_distribution=<factory>, dominant_locale='', dominant_category='')
- class siege_utilities.geo.codification_urbanicity.BlockLocaleProvider[source]
Bases:
ABCProtocol for fetching block urbanicity classifications.
- class siege_utilities.geo.codification_urbanicity.DictBlockLocaleProvider[source]
Bases:
BlockLocaleProviderIn-memory locale provider for testing.
- add(locale)[source]
- Parameters:
locale (BlockLocale)
Schemas
Pydantic schema layer for geographic models.
Provides validation schemas that mirror the Django models but without geometry (geometry handled via GeoDataFrame/WKT). Designed for: - API serialization/validation - Data pipeline intermediate representations - Round-trip conversion: GeoDataFrame ↔ Schema ↔ ORM
- Usage:
from siege_utilities.geo.schemas import StateSchema, CountySchema from siege_utilities.geo.schemas.converters import gdf_to_schemas, schemas_to_orm
- class siege_utilities.geo.schemas.TemporalGeographicFeatureSchema[source]
Bases:
BaseModelSchema for TemporalGeographicFeature fields.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.TemporalBoundarySchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for TemporalBoundary fields (no geometry).
- sync_ids()
- class siege_utilities.geo.schemas.CensusTIGERSchema[source]
Bases:
TemporalBoundarySchemaSchema for CensusTIGERBoundary fields.
- sync_from_geoid()
- class siege_utilities.geo.schemas.StateSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.CountySchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.TractSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.BlockGroupSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.BlockSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.PlaceSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.ZCTASchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.CongressionalDistrictSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.StateLegislativeUpperSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.StateLegislativeLowerSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.VTDSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.PrecinctSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.GADMBoundarySchema[source]
Bases:
TemporalBoundarySchema
- class siege_utilities.geo.schemas.GADMCountrySchema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.GADMAdmin1Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.GADMAdmin2Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.GADMAdmin3Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.GADMAdmin4Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.GADMAdmin5Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.SchoolDistrictBaseSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.SchoolDistrictElementarySchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.SchoolDistrictSecondarySchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.SchoolDistrictUnifiedSchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.NCESLocaleBoundarySchema[source]
Bases:
TemporalBoundarySchemaSchema for NCESLocaleBoundary.
- class siege_utilities.geo.schemas.SchoolLocationSchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for SchoolLocation (point feature, no geometry in schema).
- class siege_utilities.geo.schemas.NLRBRegionSchema[source]
Bases:
TemporalBoundarySchema
- class siege_utilities.geo.schemas.FederalJudicialDistrictSchema[source]
Bases:
TemporalBoundarySchema
- class siege_utilities.geo.schemas.BoundaryIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.CountyCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.VTDCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.TractCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.TemporalCrosswalkSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.DemographicVariableSchema[source]
Bases:
BaseModelReference schema for a Census variable.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.DemographicSnapshotSchema[source]
Bases:
BaseModelOne demographic snapshot for a boundary.
Uses boundary_type + boundary_id instead of Django’s generic FK.
- model_config = {'from_attributes': True}
- auto_populate_summaries()
Auto-populate summary fields from values, mirroring Django save().
- class siege_utilities.geo.schemas.DemographicTimeSeriesSchema[source]
Bases:
BaseModelPre-computed time series for a single variable on a single boundary.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.RedistrictingPlanSchema[source]
Bases:
BaseModelSchema for a redistricting plan with temporal resolution.
Supports court-ordered mid-cycle redistricting: multiple plans can exist for the same state/chamber/cycle with different effective date ranges. Use for_date(state_fips, chamber, date) on the Django model to find which plan was active on a given date.
- model_config = {'from_attributes': True}
- validate_effective_range()
Effective range must be valid if both dates are set.
- class siege_utilities.geo.schemas.PlanDistrictSchema[source]
Bases:
CensusTIGERSchemaSchema for a single district within a redistricting plan.
- class siege_utilities.geo.schemas.DistrictDemographicsSchema[source]
Bases:
BaseModelSchema for demographic profile of a plan district.
- model_config = {'from_attributes': True}
- auto_populate_race_from_values()
Auto-populate race fields from Census variable codes if present.
- class siege_utilities.geo.schemas.PrecinctElectionResultSchema[source]
Bases:
BaseModelSchema for precinct-level election results.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.CongressionalTermSchema[source]
Bases:
BaseModelSchema for CongressionalTerm.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.SeatSchema[source]
Bases:
BaseModelSchema for Seat.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.StateElectionCalendarSchema[source]
Bases:
BaseModelSchema for StateElectionCalendar.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.RaceSchema[source]
Bases:
BaseModelSchema for Race.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.RaceEventSchema[source]
Bases:
BaseModelSchema for RaceEvent.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.SpatioTemporalEventSchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for SpatioTemporalEvent.
- event_start: datetime
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.ReturnSnapshotSchema[source]
Bases:
BaseModelSchema for ReturnSnapshot.
- timestamp: datetime
- model_config = {'from_attributes': True}
- siege_utilities.geo.schemas.gdf_to_schemas(gdf, schema_class, column_map=None)[source]
Convert a GeoDataFrame to a list of Pydantic schema instances.
Geometry is dropped (schemas are non-spatial). Column names are mapped via column_map or auto-matched to schema field names.
- siege_utilities.geo.schemas.schemas_to_gdf(schemas, geometry_wkts=None, geometry_column='geometry', srid=4326, *, crs=None)[source]
Convert a list of Pydantic schema instances to a GeoDataFrame.
Complement to gdf_to_schemas(). No Django required.
- Parameters:
schemas (list['BaseModel']) – List of Pydantic schema instances
geometry_wkts (list[str] | None) – Optional list of WKT geometry strings (parallel to schemas)
geometry_column (str) – Name for the geometry column
srid (int) – SRID for the CRS (default 4326). Deprecated — use crs instead.
crs (str | None) – Output CRS (default
"EPSG:4326"). Overrides srid if provided.
- Returns:
GeoDataFrame with schema data and optional geometry in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.schemas.schemas_to_orm(schemas, model_class, geometry_wkts=None, srid=4326)[source]
Convert a list of Pydantic schemas to Django ORM model instances.
Does NOT call save() — returns unsaved instances for bulk_create.
- Parameters:
- Returns:
List of unsaved Django model instances
- Return type:
- siege_utilities.geo.schemas.orm_to_gdf(queryset, geometry_field='geometry', srid=4326, *, crs=None)[source]
Convert a Django QuerySet to a GeoDataFrame.
- Parameters:
- Returns:
GeoDataFrame with geometry and all non-geometry fields in crs.
- Return type:
gpd.GeoDataFrame
Base Pydantic schemas mirroring the abstract model hierarchy.
Geometry is NOT included — it’s handled via GeoDataFrame/WKT in converters.
- class siege_utilities.geo.schemas.base.CensusTIGERSchema[source]
Bases:
TemporalBoundarySchemaSchema for CensusTIGERBoundary fields.
- sync_from_geoid()
- class siege_utilities.geo.schemas.base.TemporalBoundarySchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for TemporalBoundary fields (no geometry).
- sync_ids()
- class siege_utilities.geo.schemas.base.TemporalGeographicFeatureSchema[source]
Bases:
BaseModelSchema for TemporalGeographicFeature fields.
- model_config = {'from_attributes': True}
Pydantic schemas for the 8 concrete Census TIGER boundary models.
- class siege_utilities.geo.schemas.boundaries.BlockGroupSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.BlockSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.CongressionalDistrictSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.CountySchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.PlaceSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.StateSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.TractSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.boundaries.ZCTASchema[source]
Bases:
CensusTIGERSchema
Conversion utilities between GeoDataFrame, Pydantic schemas, and Django ORM.
- Three conversion directions:
GeoDataFrame → Schema: gdf_to_schemas(gdf, SchemaClass) Schema → ORM: schemas_to_orm(schemas, ModelClass) ORM → GeoDataFrame: orm_to_gdf(queryset)
For Django models, from_schema()/to_schema() classmethods are the idiomatic way to do single-object conversions without importing converters directly.
- siege_utilities.geo.schemas.converters.gdf_to_schemas(gdf, schema_class, column_map=None)[source]
Convert a GeoDataFrame to a list of Pydantic schema instances.
Geometry is dropped (schemas are non-spatial). Column names are mapped via column_map or auto-matched to schema field names.
- siege_utilities.geo.schemas.converters.orm_to_gdf(queryset, geometry_field='geometry', srid=4326, *, crs=None)[source]
Convert a Django QuerySet to a GeoDataFrame.
- Parameters:
- Returns:
GeoDataFrame with geometry and all non-geometry fields in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.schemas.converters.schemas_to_gdf(schemas, geometry_wkts=None, geometry_column='geometry', srid=4326, *, crs=None)[source]
Convert a list of Pydantic schema instances to a GeoDataFrame.
Complement to gdf_to_schemas(). No Django required.
- Parameters:
schemas (list['BaseModel']) – List of Pydantic schema instances
geometry_wkts (list[str] | None) – Optional list of WKT geometry strings (parallel to schemas)
geometry_column (str) – Name for the geometry column
srid (int) – SRID for the CRS (default 4326). Deprecated — use crs instead.
crs (str | None) – Output CRS (default
"EPSG:4326"). Overrides srid if provided.
- Returns:
GeoDataFrame with schema data and optional geometry in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.schemas.converters.schemas_to_orm(schemas, model_class, geometry_wkts=None, srid=4326)[source]
Convert a list of Pydantic schemas to Django ORM model instances.
Does NOT call save() — returns unsaved instances for bulk_create.
- Parameters:
- Returns:
List of unsaved Django model instances
- Return type:
Pydantic schema for the TemporalCrosswalk model.
- class siege_utilities.geo.schemas.crosswalks.TemporalCrosswalkSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
Pydantic schemas for demographic data models.
Mirrors the Django models in geo/django/models/demographics.py but with plain strings (boundary_type + boundary_id) replacing Django’s generic foreign keys. No Django dependency required.
- class siege_utilities.geo.schemas.demographics.DemographicSnapshotSchema[source]
Bases:
BaseModelOne demographic snapshot for a boundary.
Uses boundary_type + boundary_id instead of Django’s generic FK.
- model_config = {'from_attributes': True}
- auto_populate_summaries()
Auto-populate summary fields from values, mirroring Django save().
- class siege_utilities.geo.schemas.demographics.DemographicTimeSeriesSchema[source]
Bases:
BaseModelPre-computed time series for a single variable on a single boundary.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.demographics.DemographicVariableSchema[source]
Bases:
BaseModelReference schema for a Census variable.
- model_config = {'from_attributes': True}
Pydantic schemas for NCES education models.
- class siege_utilities.geo.schemas.education.NCESLocaleBoundarySchema[source]
Bases:
TemporalBoundarySchemaSchema for NCESLocaleBoundary.
- class siege_utilities.geo.schemas.education.SchoolDistrictBaseSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.education.SchoolDistrictElementarySchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.education.SchoolDistrictSecondarySchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.education.SchoolDistrictUnifiedSchema[source]
Bases:
SchoolDistrictBaseSchema
- class siege_utilities.geo.schemas.education.SchoolLocationSchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for SchoolLocation (point feature, no geometry in schema).
Pydantic schemas for federal boundary models.
- class siege_utilities.geo.schemas.federal.FederalJudicialDistrictSchema[source]
Bases:
TemporalBoundarySchema
- class siege_utilities.geo.schemas.federal.NLRBRegionSchema[source]
Bases:
TemporalBoundarySchema
Pydantic schemas for GADM models.
- class siege_utilities.geo.schemas.gadm.GADMAdmin1Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMAdmin2Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMAdmin3Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMAdmin4Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMAdmin5Schema[source]
Bases:
GADMBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMBoundarySchema[source]
Bases:
TemporalBoundarySchema
- class siege_utilities.geo.schemas.gadm.GADMCountrySchema[source]
Bases:
GADMBoundarySchema
Pydantic schemas for intersection models.
- class siege_utilities.geo.schemas.intersections.BoundaryIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.intersections.CountyCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.intersections.TractCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.intersections.VTDCDIntersectionSchema[source]
Bases:
BaseModel- model_config = {'from_attributes': True}
Pydantic schemas for political Census models.
- class siege_utilities.geo.schemas.political.PrecinctSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.political.StateLegislativeLowerSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.political.StateLegislativeUpperSchema[source]
Bases:
CensusTIGERSchema
- class siege_utilities.geo.schemas.political.VTDSchema[source]
Bases:
CensusTIGERSchema
Pydantic schemas for redistricting models.
Mirrors the Django models in geo/django/models/redistricting.py but without Django dependency. Geometry handled via GeoDataFrame/WKT in converters.
- class siege_utilities.geo.schemas.redistricting.DistrictDemographicsSchema[source]
Bases:
BaseModelSchema for demographic profile of a plan district.
- model_config = {'from_attributes': True}
- auto_populate_race_from_values()
Auto-populate race fields from Census variable codes if present.
- class siege_utilities.geo.schemas.redistricting.PlanDistrictSchema[source]
Bases:
CensusTIGERSchemaSchema for a single district within a redistricting plan.
- class siege_utilities.geo.schemas.redistricting.PrecinctElectionResultSchema[source]
Bases:
BaseModelSchema for precinct-level election results.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.redistricting.RedistrictingPlanSchema[source]
Bases:
BaseModelSchema for a redistricting plan with temporal resolution.
Supports court-ordered mid-cycle redistricting: multiple plans can exist for the same state/chamber/cycle with different effective date ranges. Use for_date(state_fips, chamber, date) on the Django model to find which plan was active on a given date.
- model_config = {'from_attributes': True}
- validate_effective_range()
Effective range must be valid if both dates are set.
Pydantic schemas for temporal event models (Phase B).
Mirrors Django models in geo/django/models/temporal_events.py.
- class siege_utilities.geo.schemas.temporal_events.RaceEventSchema[source]
Bases:
BaseModelSchema for RaceEvent.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.temporal_events.RaceSchema[source]
Bases:
BaseModelSchema for Race.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.temporal_events.ReturnSnapshotSchema[source]
Bases:
BaseModelSchema for ReturnSnapshot.
- timestamp: datetime
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.temporal_events.SpatioTemporalEventSchema[source]
Bases:
TemporalGeographicFeatureSchemaSchema for SpatioTemporalEvent.
- event_start: datetime
- model_config = {'from_attributes': True}
Pydantic schemas for temporal political models (Phase A).
Mirrors Django models in geo/django/models/temporal_political.py.
- class siege_utilities.geo.schemas.temporal_political.CongressionalTermSchema[source]
Bases:
BaseModelSchema for CongressionalTerm.
- model_config = {'from_attributes': True}
- class siege_utilities.geo.schemas.temporal_political.SeatSchema[source]
Bases:
BaseModelSchema for Seat.
- model_config = {'from_attributes': True}
Temporal and Timeseries
Pure-Python temporal data management for geographic boundaries and demographics.
Provides persistence, querying, and time-series construction without Django/PostGIS. All submodules load on first attribute access via PEP 562 __getattr__.
- class siege_utilities.geo.temporal.TemporalDataStore[source]
Bases:
objectPure-Python persistence for temporal geographic data.
Follows the CrosswalkClient pattern: class with module-level convenience functions for the common case.
- SUPPORTED_FORMATS = ('parquet', 'gpkg')
- load_boundaries(geography_type, vintage_year, state_fips=None)[source]
Load boundaries for a geography type and vintage year.
- query_boundaries_at_date(geography_type, query_date, state_fips=None)[source]
Load the best-matching vintage for a given date.
If boundaries have valid_from/valid_to columns, those are checked. Otherwise, the nearest vintage_year <= query_date.year is used.
- list_available_vintages(geography_type)[source]
List vintage years available on disk for a geography type.
- save_demographics(snapshots, geography_type, dataset='acs5', year=None)[source]
Persist demographic snapshot data as Parquet.
- load_demographics(geography_type, year=None, dataset='acs5')[source]
Load demographic data. If year is None, loads all available years.
- siege_utilities.geo.temporal.get_temporal_store(root_dir=None, format='parquet')[source]
Get or create the singleton TemporalDataStore.
- Parameters:
- Return type:
- siege_utilities.geo.temporal.save_boundaries(gdf, geography_type, vintage_year, **kwargs)[source]
Save boundaries via the default store.
- siege_utilities.geo.temporal.load_boundaries(geography_type, vintage_year, state_fips=None, **kwargs)[source]
Load boundaries via the default store.
- siege_utilities.geo.temporal.query_boundaries_at_date(geography_type, query_date, state_fips=None, **kwargs)[source]
Query boundaries at a date via the default store.
- Parameters:
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.save_demographics(snapshots, geography_type, dataset='acs5', year=None, **kwargs)[source]
Save demographics via the default store.
- Parameters:
snapshots (pd.DataFrame) – DataFrame of demographic snapshot data.
geography_type (str) – Type of geography (e.g. “county”, “tract”).
dataset (str) – Dataset identifier (default “acs5”).
year (int | None) – Year of the snapshot. Inferred from DataFrame if omitted.
**kwargs – Forwarded to get_temporal_store(). Accepts root_dir and format.
- Return type:
- siege_utilities.geo.temporal.load_demographics(geography_type, year=None, dataset='acs5', **kwargs)[source]
Load demographics via the default store.
- Parameters:
- Return type:
pd.DataFrame
- siege_utilities.geo.temporal.temporal_filter(gdf, query_date, valid_from_col='valid_from', valid_to_col='valid_to', vintage_year_col='vintage_year')[source]
Filter a GeoDataFrame to rows valid at query_date.
First tries valid_from/valid_to date range filtering. Falls back to vintage_year if date columns are missing or all-null.
- Parameters:
- Returns:
Filtered GeoDataFrame
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.spatial_query(boundaries, points, predicate='intersects', *, crs=None)[source]
Spatial join between boundaries and points using geopandas.sjoin.
Aligns CRS before joining if both GeoDataFrames have a CRS set.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons (left)
points (gpd.GeoDataFrame) – GeoDataFrame of query geometries (right)
predicate (str) – Spatial predicate (intersects, within, contains, etc.)
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
Joined GeoDataFrame with columns from both inputs, in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.point_in_boundary(boundaries, lon, lat, predicate='intersects', *, crs=None)[source]
Find which boundaries contain a single point.
Convenience wrapper that creates a Point GeoDataFrame and calls spatial_query.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons
lon (float) – Longitude
lat (float) – Latitude
predicate (str) – Spatial predicate
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
GeoDataFrame of matching boundaries in crs.
- Return type:
gpd.GeoDataFrame
- class siege_utilities.geo.temporal.TemporalTimeseriesBuilder[source]
Bases:
objectPure-Python equivalent of django/services/timeseries_service.py.
Builds DemographicTimeSeriesSchema instances from snapshot DataFrames, computing the same statistics (mean, std_dev, CAGR, trend_direction) as the Django service.
Example
builder = TemporalTimeseriesBuilder(store=store, dataset=’acs5’) results = builder.build(
variables=[‘B01001_001E’], years=[2015, 2016, 2017, 2018, 2019, 2020], geography_type=’tract’,
)
- __init__(store=None, dataset='acs5')[source]
- Parameters:
store (Optional['TemporalDataStore'])
dataset (str)
- Return type:
None
- build(variables, years, geography_type, snapshots_df=None)[source]
Build time-series schemas from snapshot data.
- Parameters:
- Returns:
List of TimeseriesBuildResult (one per variable), with created DemographicTimeSeriesSchema instances accessible via result.series.
- Return type:
- class siege_utilities.geo.temporal.TemporalDemographicService[source]
Bases:
objectBuild demographic snapshots from Census API data.
Converts raw Census API DataFrames into DemographicSnapshotSchema instances and optionally persists them via TemporalDataStore.
- __init__(store=None, api_key=None)[source]
- Parameters:
store (Optional['TemporalDataStore'])
api_key (str | None)
- Return type:
None
- build_snapshots(df, geography_type, year, dataset='acs5', geoid_column='GEOID', variable_columns=None)[source]
Convert a Census API DataFrame to snapshot schema instances.
- Parameters:
df (pd.DataFrame) – DataFrame from Census API (columns: GEOID + variable codes)
geography_type (str) – Geography level name
year (int) – Census/ACS year
dataset (str) – Census dataset
geoid_column (str) – Column containing GEOIDs
variable_columns (list[str] | None) – Explicit list of variable columns. If None, uses all columns except geoid_column and ‘NAME’.
- Returns:
List of DemographicSnapshotSchema instances
- Return type:
- class siege_utilities.geo.temporal.TimeseriesBuildResult[source]
Bases:
objectResult of a time-series build run.
Pure-Python temporal and spatial query helpers.
Uses geopandas.sjoin for spatial queries (no PostGIS required).
- siege_utilities.geo.temporal.query.temporal_filter(gdf, query_date, valid_from_col='valid_from', valid_to_col='valid_to', vintage_year_col='vintage_year')[source]
Filter a GeoDataFrame to rows valid at query_date.
First tries valid_from/valid_to date range filtering. Falls back to vintage_year if date columns are missing or all-null.
- Parameters:
- Returns:
Filtered GeoDataFrame
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.query.spatial_query(boundaries, points, predicate='intersects', *, crs=None)[source]
Spatial join between boundaries and points using geopandas.sjoin.
Aligns CRS before joining if both GeoDataFrames have a CRS set.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons (left)
points (gpd.GeoDataFrame) – GeoDataFrame of query geometries (right)
predicate (str) – Spatial predicate (intersects, within, contains, etc.)
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
Joined GeoDataFrame with columns from both inputs, in crs.
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.query.point_in_boundary(boundaries, lon, lat, predicate='intersects', *, crs=None)[source]
Find which boundaries contain a single point.
Convenience wrapper that creates a Point GeoDataFrame and calls spatial_query.
- Parameters:
boundaries (gpd.GeoDataFrame) – GeoDataFrame of boundary polygons
lon (float) – Longitude
lat (float) – Latitude
predicate (str) – Spatial predicate
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
GeoDataFrame of matching boundaries in crs.
- Return type:
gpd.GeoDataFrame
Pure-Python services for building demographic time series.
Ports the core logic from django/services/timeseries_service.py to work with DataFrames and TemporalDataStore instead of Django ORM.
- class siege_utilities.geo.temporal.services.TimeseriesBuildResult[source]
Bases:
objectResult of a time-series build run.
- class siege_utilities.geo.temporal.services.TemporalTimeseriesBuilder[source]
Bases:
objectPure-Python equivalent of django/services/timeseries_service.py.
Builds DemographicTimeSeriesSchema instances from snapshot DataFrames, computing the same statistics (mean, std_dev, CAGR, trend_direction) as the Django service.
Example
builder = TemporalTimeseriesBuilder(store=store, dataset=’acs5’) results = builder.build(
variables=[‘B01001_001E’], years=[2015, 2016, 2017, 2018, 2019, 2020], geography_type=’tract’,
)
- __init__(store=None, dataset='acs5')[source]
- Parameters:
store (Optional['TemporalDataStore'])
dataset (str)
- Return type:
None
- build(variables, years, geography_type, snapshots_df=None)[source]
Build time-series schemas from snapshot data.
- Parameters:
- Returns:
List of TimeseriesBuildResult (one per variable), with created DemographicTimeSeriesSchema instances accessible via result.series.
- Return type:
- class siege_utilities.geo.temporal.services.TemporalDemographicService[source]
Bases:
objectBuild demographic snapshots from Census API data.
Converts raw Census API DataFrames into DemographicSnapshotSchema instances and optionally persists them via TemporalDataStore.
- __init__(store=None, api_key=None)[source]
- Parameters:
store (Optional['TemporalDataStore'])
api_key (str | None)
- Return type:
None
- build_snapshots(df, geography_type, year, dataset='acs5', geoid_column='GEOID', variable_columns=None)[source]
Convert a Census API DataFrame to snapshot schema instances.
- Parameters:
df (pd.DataFrame) – DataFrame from Census API (columns: GEOID + variable codes)
geography_type (str) – Geography level name
year (int) – Census/ACS year
dataset (str) – Census dataset
geoid_column (str) – Column containing GEOIDs
variable_columns (list[str] | None) – Explicit list of variable columns. If None, uses all columns except geoid_column and ‘NAME’.
- Returns:
List of DemographicSnapshotSchema instances
- Return type:
Pure-Python persistence for temporal boundaries and demographics.
- Storage layout (Parquet default):
{root}/boundaries/{geography_type}/{vintage_year}.parquet {root}/demographics/{geography_type}/{dataset}_{year}.parquet {root}/timeseries/{geography_type}/{variable_code}_{dataset}.parquet
GeoPackage is available as an alternative via format=”gpkg” (boundaries only).
- class siege_utilities.geo.temporal.store.TemporalDataStore[source]
Bases:
objectPure-Python persistence for temporal geographic data.
Follows the CrosswalkClient pattern: class with module-level convenience functions for the common case.
- SUPPORTED_FORMATS = ('parquet', 'gpkg')
- load_boundaries(geography_type, vintage_year, state_fips=None)[source]
Load boundaries for a geography type and vintage year.
- query_boundaries_at_date(geography_type, query_date, state_fips=None)[source]
Load the best-matching vintage for a given date.
If boundaries have valid_from/valid_to columns, those are checked. Otherwise, the nearest vintage_year <= query_date.year is used.
- list_available_vintages(geography_type)[source]
List vintage years available on disk for a geography type.
- save_demographics(snapshots, geography_type, dataset='acs5', year=None)[source]
Persist demographic snapshot data as Parquet.
- load_demographics(geography_type, year=None, dataset='acs5')[source]
Load demographic data. If year is None, loads all available years.
- siege_utilities.geo.temporal.store.get_temporal_store(root_dir=None, format='parquet')[source]
Get or create the singleton TemporalDataStore.
- Parameters:
- Return type:
- siege_utilities.geo.temporal.store.save_boundaries(gdf, geography_type, vintage_year, **kwargs)[source]
Save boundaries via the default store.
- siege_utilities.geo.temporal.store.load_boundaries(geography_type, vintage_year, state_fips=None, **kwargs)[source]
Load boundaries via the default store.
- siege_utilities.geo.temporal.store.query_boundaries_at_date(geography_type, query_date, state_fips=None, **kwargs)[source]
Query boundaries at a date via the default store.
- Parameters:
- Return type:
gpd.GeoDataFrame
- siege_utilities.geo.temporal.store.save_demographics(snapshots, geography_type, dataset='acs5', year=None, **kwargs)[source]
Save demographics via the default store.
- Parameters:
snapshots (pd.DataFrame) – DataFrame of demographic snapshot data.
geography_type (str) – Type of geography (e.g. “county”, “tract”).
dataset (str) – Dataset identifier (default “acs5”).
year (int | None) – Year of the snapshot. Inferred from DataFrame if omitted.
**kwargs – Forwarded to get_temporal_store(). Accepts root_dir and format.
- Return type:
- siege_utilities.geo.temporal.store.load_demographics(geography_type, year=None, dataset='acs5', **kwargs)[source]
Load demographics via the default store.
- Parameters:
- Return type:
pd.DataFrame
Time-series analysis for Census longitudinal data.
This module provides tools for analyzing Census data across multiple years, including change metrics, trend classification, and multi-year data fetching.
Key features: - Fetch data for multiple years with automatic boundary normalization - Calculate change metrics (absolute, percent, CAGR) - Classify trends into categories (rapid growth, stable, decline, etc.) - Support for various analysis approaches
- Example usage:
- from siege_utilities.geo.timeseries import (
get_longitudinal_data, calculate_change_metrics, classify_trends
)
# Fetch income data for multiple years df = get_longitudinal_data(
variables=’B19013_001E’, years=[2010, 2015, 2020], geography=’tract’, state=’California’, target_year=2020 # Normalize all to 2020 boundaries
)
# Calculate change metrics df = calculate_change_metrics(
df=df, value_column=’B19013_001E’, start_year=2010, end_year=2020, metrics=[‘absolute’, ‘percent’, ‘cagr’]
)
# Classify trends df = classify_trends(
df=df, change_column=’B19013_001E_pct_change_2010_2020’
)
- siege_utilities.geo.timeseries.get_longitudinal_data(variables, years, geography='tract', state=None, county_fips=None, target_year=2020, normalize_boundaries=True, include_moe=False, include_geometry=False, dataset='acs5', strict_years=True)[source]
Fetch Census data for multiple years and return in wide format.
This function: 1. Fetches data for each requested year 2. Optionally normalizes all data to a consistent boundary year 3. Merges into a wide-format DataFrame with year-suffixed columns
- Parameters:
variables (str | List[str]) – Census variable code(s) or predefined group name (e.g., ‘B19013_001E’, [‘B19013_001E’, ‘B01001_001E’], or ‘income’)
geography (str) – Geographic level (‘state’, ‘county’, ‘tract’, ‘block_group’)
state (str | None) – State identifier (name, abbreviation, or FIPS code)
county_fips (str | None) – County FIPS code (3 digits) for filtering
target_year (int) – Year to normalize boundaries to (default: 2020)
normalize_boundaries (bool) – If True, apply crosswalks to normalize all years to target_year boundaries. Required for accurate comparisons.
include_moe (bool) – Include margin of error columns
include_geometry (bool) – If True, include geometry from target year
dataset (str) – Census dataset (‘acs5’, ‘acs1’)
strict_years (bool) – If True (default), raise ValueError when any requested year is unavailable or fails to fetch. If False, log warnings and continue with available years.
- Returns:
GEOID: Geography identifier (in target_year vintage if normalized)
NAME: Geography name (from most recent year)
{variable}_{year}: Variable value for each year
- Return type:
Wide-format DataFrame with columns
Example
# Get income data for LA County tracts 2015-2020 df = get_longitudinal_data(
variables=’B19013_001E’, years=[2015, 2017, 2020], geography=’tract’, state=’CA’, county_fips=’037’
)
# Columns: GEOID, NAME, B19013_001E_2015, B19013_001E_2017, B19013_001E_2020
- siege_utilities.geo.timeseries.get_available_years(dataset='acs5', variable=None)[source]
Get list of available years for a Census dataset.
- siege_utilities.geo.timeseries.get_available_survey_years(dataset='acs5', variable=None)
Get list of available years for a Census dataset.
- siege_utilities.geo.timeseries.validate_longitudinal_years(years, dataset='acs5')[source]
Validate and filter years for longitudinal analysis.
- siege_utilities.geo.timeseries.calculate_change_metrics(df, value_column, start_year=None, end_year=None, metrics=None)[source]
Calculate change metrics for a variable across time.
This function expects wide-format data with year-suffixed columns (e.g., B19013_001E_2010, B19013_001E_2020).
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name (without year suffix)
start_year (int | None) – Start year for comparison. If None, uses earliest year found.
end_year (int | None) – End year for comparison. If None, uses latest year found.
metrics (List[str]) – List of metrics to calculate. Options: - ‘absolute’: Raw change (end - start) - ‘percent’: Percentage change ((end - start) / start * 100) - ‘cagr’: Compound Annual Growth Rate - ‘annualized’: Annualized change (absolute change / years) Default: [‘absolute’, ‘percent’, ‘cagr’]
- Returns:
{value_column}_change_{start}_{end}: Absolute change
{value_column}_pct_change_{start}_{end}: Percentage change
{value_column}_cagr_{start}_{end}: CAGR
- Return type:
DataFrame with original columns plus new metric columns
Example
- df = calculate_change_metrics(
df=income_data, value_column=’B19013_001E’, start_year=2010, end_year=2020, metrics=[‘absolute’, ‘percent’, ‘cagr’]
)
- siege_utilities.geo.timeseries.calculate_multi_period_changes(df, value_column, metrics=None)[source]
Calculate change metrics between all consecutive periods.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
- Returns:
DataFrame with changes between each pair of consecutive years
- Return type:
- siege_utilities.geo.timeseries.calculate_index(df, value_column, base_year=None, index_value=100.0)[source]
Calculate an index relative to a base year.
This is useful for comparing changes across geographies with different absolute values.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
base_year (int | None) – Year to use as index base. If None, uses earliest year.
index_value (float) – Value to set base year to (default: 100)
- Returns:
DataFrame with indexed columns (e.g., B19013_001E_index_2010)
- Return type:
Example
df = calculate_index(df, ‘B19013_001E’, base_year=2010) # Creates columns: B19013_001E_index_2010, B19013_001E_index_2015, …
- siege_utilities.geo.timeseries.get_change_summary(df, value_column, start_year=None, end_year=None)[source]
Get summary statistics for changes in a variable.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
start_year (int | None) – Start year
end_year (int | None) – End year
- Returns:
Dictionary with summary statistics
- Return type:
- class siege_utilities.geo.timeseries.TrendCategory[source]
Bases:
EnumStandard trend categories for classification.
- RAPID_GROWTH = 'rapid_growth'
- MODERATE_GROWTH = 'moderate_growth'
- STABLE = 'stable'
- MODERATE_DECLINE = 'moderate_decline'
- RAPID_DECLINE = 'rapid_decline'
- class siege_utilities.geo.timeseries.TrendThresholds[source]
Bases:
objectThreshold configuration for trend classification.
- rapid_decline
Values below this (or moderate_decline if None)
- siege_utilities.geo.timeseries.classify_trends(df, change_column, thresholds=None, output_column='trend_category')[source]
Classify changes into trend categories.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values (typically percentage change)
thresholds (TrendThresholds | str | Dict[str, float] | None) – Threshold configuration. Can be: - TrendThresholds object - Preset name (‘default’, ‘conservative’, ‘sensitive’, ‘housing’, ‘population’) - Dict with keys: rapid_growth, moderate_growth, stable_lower, moderate_decline - None (uses default thresholds)
output_column (str) – Name for the category column
- Returns:
DataFrame with new trend_category column
- Return type:
Example
- df = classify_trends(
df=income_changes, change_column=’B19013_001E_pct_change_2010_2020’, thresholds=’conservative’
)
- siege_utilities.geo.timeseries.classify_by_zscore(df, change_column, output_column='trend_zscore_category')[source]
Classify trends using z-scores (statistical approach).
This method classifies based on how many standard deviations a geography’s change is from the mean, providing a relative classification within the dataset.
Categories: - extreme_positive: z >= 2 - high_positive: 1 <= z < 2 - average: -1 < z < 1 - high_negative: -2 < z <= -1 - extreme_negative: z <= -2
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
output_column (str) – Name for the category column
- Returns:
DataFrame with z-score category column and z-score values
- Return type:
- siege_utilities.geo.timeseries.classify_by_quantiles(df, change_column, n_quantiles=5, labels=None, output_column='trend_quantile')[source]
Classify trends into quantile-based categories.
This method divides the data into equal-sized groups (quintiles, deciles, etc.) which is useful when you want balanced category sizes.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
n_quantiles (int) – Number of quantiles (default: 5 for quintiles)
labels (List[str] | None) – Custom labels for quantiles. If None, uses: - 5 quantiles: [‘lowest’, ‘low’, ‘middle’, ‘high’, ‘highest’] - Other: numeric labels 1 to n
output_column (str) – Name for the category column
- Returns:
DataFrame with quantile category column
- Return type:
- siege_utilities.geo.timeseries.get_trend_summary(df, trend_column='trend_category', geoid_column='GEOID')[source]
Get summary statistics for trend classification.
- Parameters:
df (pandas.DataFrame) – DataFrame with trend categories
trend_column (str) – Name of the trend category column
geoid_column (str) – Name of the GEOID column
- Returns:
Dictionary with summary statistics
- Return type:
- siege_utilities.geo.timeseries.identify_outliers(df, change_column, method='iqr', threshold=1.5)[source]
Identify outlier geographies based on change values.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
method (str) – Outlier detection method: - ‘iqr’: Interquartile range method - ‘zscore’: Z-score method
threshold (float) – Threshold for outlier detection: - For IQR: multiplier (default 1.5) - For zscore: number of standard deviations (default 1.5)
- Returns:
DataFrame with is_outlier and outlier_type columns
- Return type:
- siege_utilities.geo.timeseries.compare_trends(df1, df2, trend_column='trend_category', geoid_column='GEOID')[source]
Compare trend classifications between two DataFrames.
Useful for comparing trend patterns between different: - Time periods - Variables - Geographic subsets
- Parameters:
df1 (pandas.DataFrame) – First DataFrame with trend classification
df2 (pandas.DataFrame) – Second DataFrame with trend classification
trend_column (str) – Name of trend category column
geoid_column (str) – Name of GEOID column
- Returns:
DataFrame with comparison columns
- Return type:
- class siege_utilities.geo.timeseries.BoundaryStabilityMetrics[source]
Bases:
objectStability metrics for a set of boundaries between two vintages.
- __init__(source_year, target_year, geography_type, total_source=0, total_target=0, unchanged=0, split=0, merged=0, net_change=0, pct_unchanged=0.0, churn_rate=0.0)
- class siege_utilities.geo.timeseries.AllocationEfficiencyMetrics[source]
Bases:
objectMetrics describing how cleanly crosswalk weights allocate values.
- __init__(mean_weight=0.0, median_weight=0.0, std_weight=0.0, pct_above_90=0.0, pct_above_50=0.0, pct_below_10=0.0, n_relationships=0)
- class siege_utilities.geo.timeseries.ChainLink[source]
Bases:
objectA single step in a reallocation chain.
- class siege_utilities.geo.timeseries.ReallocationChain[source]
Bases:
objectA complete chain tracking one GEOID through multiple vintages.
- links
Ordered list of chain links.
- siege_utilities.geo.timeseries.compute_boundary_stability(crosswalk_df, source_year, target_year, geography_type='tract', unchanged_threshold=0.999)[source]
Compute stability metrics for boundaries between two vintages.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with columns: source_geoid, target_geoid, weight.
source_year (int) – Earlier vintage year.
target_year (int) – Later vintage year.
geography_type (str) – Geography level name.
unchanged_threshold (float) – Weight threshold to consider a boundary unchanged.
- Returns:
BoundaryStabilityMetrics with counts and rates.
- Return type:
- siege_utilities.geo.timeseries.compute_allocation_efficiency(crosswalk_df)[source]
Compute metrics describing crosswalk weight distribution.
High-quality crosswalks have most weights near 1.0 (clean mappings). Low-quality crosswalks have many small weights (fragmented boundaries).
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with a weight column.
- Returns:
AllocationEfficiencyMetrics.
- Return type:
- siege_utilities.geo.timeseries.build_reallocation_chain(crosswalk_dfs, origin_geoid, min_weight=0.01)[source]
Track a GEOID through multiple vintage transitions.
Given a sequence of crosswalk DataFrames (one per transition), follows the origin GEOID forward through each step, accumulating weights.
- Parameters:
- Returns:
ReallocationChain with links and terminal GEOIDs.
- Return type:
- siege_utilities.geo.timeseries.compare_vintage_stability(crosswalk_dfs, geography_type='tract')[source]
Compare boundary stability across multiple vintage transitions.
- Parameters:
- Returns:
DataFrame with one row per transition and stability metrics.
- Return type:
- siege_utilities.geo.timeseries.identify_volatile_boundaries(crosswalk_df, threshold=0.5, top_n=None)[source]
Identify source boundaries with the most fragmented mappings.
“Volatile” boundaries are those that split into many targets with small weights, indicating significant boundary changes.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with crosswalk relationships.
threshold (float) – Maximum weight to consider “fragmented” (default 0.5).
top_n (int | None) – Return only the top N most volatile (None for all).
- Returns:
source_geoid, n_targets, max_weight, min_weight, mean_weight, total_weight.
- Return type:
DataFrame with columns
Change metrics calculations for Census time-series analysis.
This module provides functions to calculate various change metrics from longitudinal Census data, including:
Absolute change
Percentage change
Compound Annual Growth Rate (CAGR)
Per-period change rates
- Example usage:
from siege_utilities.geo.timeseries import calculate_change_metrics
# Calculate metrics for income data df = calculate_change_metrics(
df=longitudinal_data, value_column=’B19013_001E’, metrics=[‘absolute’, ‘percent’, ‘cagr’]
)
- siege_utilities.geo.timeseries.change_metrics.calculate_change_metrics(df, value_column, start_year=None, end_year=None, metrics=None)[source]
Calculate change metrics for a variable across time.
This function expects wide-format data with year-suffixed columns (e.g., B19013_001E_2010, B19013_001E_2020).
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name (without year suffix)
start_year (int | None) – Start year for comparison. If None, uses earliest year found.
end_year (int | None) – End year for comparison. If None, uses latest year found.
metrics (List[str]) – List of metrics to calculate. Options: - ‘absolute’: Raw change (end - start) - ‘percent’: Percentage change ((end - start) / start * 100) - ‘cagr’: Compound Annual Growth Rate - ‘annualized’: Annualized change (absolute change / years) Default: [‘absolute’, ‘percent’, ‘cagr’]
- Returns:
{value_column}_change_{start}_{end}: Absolute change
{value_column}_pct_change_{start}_{end}: Percentage change
{value_column}_cagr_{start}_{end}: CAGR
- Return type:
DataFrame with original columns plus new metric columns
Example
- df = calculate_change_metrics(
df=income_data, value_column=’B19013_001E’, start_year=2010, end_year=2020, metrics=[‘absolute’, ‘percent’, ‘cagr’]
)
- siege_utilities.geo.timeseries.change_metrics.calculate_index(df, value_column, base_year=None, index_value=100.0)[source]
Calculate an index relative to a base year.
This is useful for comparing changes across geographies with different absolute values.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
base_year (int | None) – Year to use as index base. If None, uses earliest year.
index_value (float) – Value to set base year to (default: 100)
- Returns:
DataFrame with indexed columns (e.g., B19013_001E_index_2010)
- Return type:
Example
df = calculate_index(df, ‘B19013_001E’, base_year=2010) # Creates columns: B19013_001E_index_2010, B19013_001E_index_2015, …
- siege_utilities.geo.timeseries.change_metrics.calculate_multi_period_changes(df, value_column, metrics=None)[source]
Calculate change metrics between all consecutive periods.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
- Returns:
DataFrame with changes between each pair of consecutive years
- Return type:
- siege_utilities.geo.timeseries.change_metrics.get_change_summary(df, value_column, start_year=None, end_year=None)[source]
Get summary statistics for changes in a variable.
- Parameters:
df (pandas.DataFrame) – DataFrame with year-suffixed columns
value_column (str) – Base variable name
start_year (int | None) – Start year
end_year (int | None) – End year
- Returns:
Dictionary with summary statistics
- Return type:
Time-series analytics for geographic crosswalk data.
Analyzes how boundary relationships evolve across Census vintages: - Allocation efficiency: how cleanly do source boundaries map to targets? - Boundary stability: which geographies change the most between vintages? - Reallocation chains: tracking a GEOID through multiple vintage transitions.
Builds on TemporalCrosswalk model data and the crosswalk module.
- class siege_utilities.geo.timeseries.crosswalk_analytics.AllocationEfficiencyMetrics[source]
Bases:
objectMetrics describing how cleanly crosswalk weights allocate values.
- __init__(mean_weight=0.0, median_weight=0.0, std_weight=0.0, pct_above_90=0.0, pct_above_50=0.0, pct_below_10=0.0, n_relationships=0)
- class siege_utilities.geo.timeseries.crosswalk_analytics.BoundaryStabilityMetrics[source]
Bases:
objectStability metrics for a set of boundaries between two vintages.
- __init__(source_year, target_year, geography_type, total_source=0, total_target=0, unchanged=0, split=0, merged=0, net_change=0, pct_unchanged=0.0, churn_rate=0.0)
- class siege_utilities.geo.timeseries.crosswalk_analytics.ChainLink[source]
Bases:
objectA single step in a reallocation chain.
- class siege_utilities.geo.timeseries.crosswalk_analytics.ReallocationChain[source]
Bases:
objectA complete chain tracking one GEOID through multiple vintages.
- links
Ordered list of chain links.
- siege_utilities.geo.timeseries.crosswalk_analytics.build_reallocation_chain(crosswalk_dfs, origin_geoid, min_weight=0.01)[source]
Track a GEOID through multiple vintage transitions.
Given a sequence of crosswalk DataFrames (one per transition), follows the origin GEOID forward through each step, accumulating weights.
- Parameters:
- Returns:
ReallocationChain with links and terminal GEOIDs.
- Return type:
- siege_utilities.geo.timeseries.crosswalk_analytics.compare_vintage_stability(crosswalk_dfs, geography_type='tract')[source]
Compare boundary stability across multiple vintage transitions.
- Parameters:
- Returns:
DataFrame with one row per transition and stability metrics.
- Return type:
- siege_utilities.geo.timeseries.crosswalk_analytics.compute_allocation_efficiency(crosswalk_df)[source]
Compute metrics describing crosswalk weight distribution.
High-quality crosswalks have most weights near 1.0 (clean mappings). Low-quality crosswalks have many small weights (fragmented boundaries).
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with a weight column.
- Returns:
AllocationEfficiencyMetrics.
- Return type:
- siege_utilities.geo.timeseries.crosswalk_analytics.compute_boundary_stability(crosswalk_df, source_year, target_year, geography_type='tract', unchanged_threshold=0.999)[source]
Compute stability metrics for boundaries between two vintages.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with columns: source_geoid, target_geoid, weight.
source_year (int) – Earlier vintage year.
target_year (int) – Later vintage year.
geography_type (str) – Geography level name.
unchanged_threshold (float) – Weight threshold to consider a boundary unchanged.
- Returns:
BoundaryStabilityMetrics with counts and rates.
- Return type:
- siege_utilities.geo.timeseries.crosswalk_analytics.identify_volatile_boundaries(crosswalk_df, threshold=0.5, top_n=None)[source]
Identify source boundaries with the most fragmented mappings.
“Volatile” boundaries are those that split into many targets with small weights, indicating significant boundary changes.
- Parameters:
crosswalk_df (pandas.DataFrame) – DataFrame with crosswalk relationships.
threshold (float) – Maximum weight to consider “fragmented” (default 0.5).
top_n (int | None) – Return only the top N most volatile (None for all).
- Returns:
source_geoid, n_targets, max_weight, min_weight, mean_weight, total_weight.
- Return type:
DataFrame with columns
Longitudinal data fetching for Census time-series analysis.
This module provides functions to fetch Census data across multiple years and normalize them to a consistent boundary vintage for time-series analysis.
It integrates with: - CensusAPIClient for fetching demographic data - Crosswalk module for boundary normalization
- Example usage:
from siege_utilities.geo.timeseries import get_longitudinal_data
# Fetch median income for California tracts 2010-2020 df = get_longitudinal_data(
variables=’B19013_001E’, years=[2010, 2015, 2020], geography=’tract’, state=’California’, target_year=2020
)
# Result has columns: GEOID, B19013_001E_2010, B19013_001E_2015, B19013_001E_2020
# Multi-decade alignment with inflation adjustment from siege_utilities.geo.timeseries.longitudinal_data import LongitudinalAligner
aligner = LongitudinalAligner(target_vintage=2020) aligned = aligner.align(df_2010, source_vintage=2010, geography=’tract’) adjusted = aligner.adjust_inflation(aligned, dollar_columns=[‘B19013_001E’],
from_year=2010, to_year=2020)
- class siege_utilities.geo.timeseries.longitudinal_data.AlignmentResult[source]
Bases:
objectResult of a longitudinal alignment operation.
- data
Aligned DataFrame with GEOIDs in the target vintage.
- Type:
- data: pandas.DataFrame
- class siege_utilities.geo.timeseries.longitudinal_data.LongitudinalAligner[source]
Bases:
objectAlign Census data across vintage years with optional inflation adjustment.
Chains crosswalks for multi-decade transitions (e.g. 2000→2010→2020) and falls back to areal interpolation when crosswalk data is unavailable.
- Parameters:
Example
aligner = LongitudinalAligner(target_vintage=2020) result = aligner.align(df_2010, source_vintage=2010, geography='tract') adjusted = aligner.adjust_inflation( result.data, dollar_columns=['B19013_001E'], from_year=2010, to_year=2020, )
- align(df, source_vintage, geography=None, state_fips=None, geoid_column='GEOID')[source]
Align df from source_vintage boundaries to
target_vintage.- Parameters:
df (DataFrame) – Data with a GEOID column in source_vintage boundaries.
source_vintage (int) – Boundary vintage of df (2000, 2010, or 2020).
geography (str, optional) – Override instance geography.
state_fips (str, optional) – Override instance state_fips.
geoid_column (str) – Name of the GEOID column in df.
- Return type:
- siege_utilities.geo.timeseries.longitudinal_data.get_available_years(dataset='acs5', variable=None)[source]
Get list of available years for a Census dataset.
- siege_utilities.geo.timeseries.longitudinal_data.get_longitudinal_data(variables, years, geography='tract', state=None, county_fips=None, target_year=2020, normalize_boundaries=True, include_moe=False, include_geometry=False, dataset='acs5', strict_years=True)[source]
Fetch Census data for multiple years and return in wide format.
This function: 1. Fetches data for each requested year 2. Optionally normalizes all data to a consistent boundary year 3. Merges into a wide-format DataFrame with year-suffixed columns
- Parameters:
variables (str | List[str]) – Census variable code(s) or predefined group name (e.g., ‘B19013_001E’, [‘B19013_001E’, ‘B01001_001E’], or ‘income’)
geography (str) – Geographic level (‘state’, ‘county’, ‘tract’, ‘block_group’)
state (str | None) – State identifier (name, abbreviation, or FIPS code)
county_fips (str | None) – County FIPS code (3 digits) for filtering
target_year (int) – Year to normalize boundaries to (default: 2020)
normalize_boundaries (bool) – If True, apply crosswalks to normalize all years to target_year boundaries. Required for accurate comparisons.
include_moe (bool) – Include margin of error columns
include_geometry (bool) – If True, include geometry from target year
dataset (str) – Census dataset (‘acs5’, ‘acs1’)
strict_years (bool) – If True (default), raise ValueError when any requested year is unavailable or fails to fetch. If False, log warnings and continue with available years.
- Returns:
GEOID: Geography identifier (in target_year vintage if normalized)
NAME: Geography name (from most recent year)
{variable}_{year}: Variable value for each year
- Return type:
Wide-format DataFrame with columns
Example
# Get income data for LA County tracts 2015-2020 df = get_longitudinal_data(
variables=’B19013_001E’, years=[2015, 2017, 2020], geography=’tract’, state=’CA’, county_fips=’037’
)
# Columns: GEOID, NAME, B19013_001E_2015, B19013_001E_2017, B19013_001E_2020
- siege_utilities.geo.timeseries.longitudinal_data.inflation_factor(from_year, to_year)[source]
Return the CPI-U multiplier to convert dollars from from_year to to_year.
Raises
KeyErrorif either year is not in the CPI-U table.
- siege_utilities.geo.timeseries.longitudinal_data.validate_longitudinal_years(years, dataset='acs5')[source]
Validate and filter years for longitudinal analysis.
Trend classification for Census time-series analysis.
This module provides functions to classify and categorize trends in longitudinal Census data. It supports:
Threshold-based classification (rapid growth, stable, decline, etc.)
Statistical classification (z-score based)
Custom classification schemes
- Example usage:
from siege_utilities.geo.timeseries import classify_trends
# Classify income changes df = classify_trends(
df=income_data, change_column=’B19013_001E_pct_change_2010_2020’
)
# Result includes ‘trend_category’ column
- class siege_utilities.geo.timeseries.trend_classifier.TrendCategory[source]
Bases:
EnumStandard trend categories for classification.
- RAPID_GROWTH = 'rapid_growth'
- MODERATE_GROWTH = 'moderate_growth'
- STABLE = 'stable'
- MODERATE_DECLINE = 'moderate_decline'
- RAPID_DECLINE = 'rapid_decline'
- class siege_utilities.geo.timeseries.trend_classifier.TrendThresholds[source]
Bases:
objectThreshold configuration for trend classification.
- rapid_decline
Values below this (or moderate_decline if None)
- siege_utilities.geo.timeseries.trend_classifier.classify_by_quantiles(df, change_column, n_quantiles=5, labels=None, output_column='trend_quantile')[source]
Classify trends into quantile-based categories.
This method divides the data into equal-sized groups (quintiles, deciles, etc.) which is useful when you want balanced category sizes.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
n_quantiles (int) – Number of quantiles (default: 5 for quintiles)
labels (List[str] | None) – Custom labels for quantiles. If None, uses: - 5 quantiles: [‘lowest’, ‘low’, ‘middle’, ‘high’, ‘highest’] - Other: numeric labels 1 to n
output_column (str) – Name for the category column
- Returns:
DataFrame with quantile category column
- Return type:
- siege_utilities.geo.timeseries.trend_classifier.classify_by_zscore(df, change_column, output_column='trend_zscore_category')[source]
Classify trends using z-scores (statistical approach).
This method classifies based on how many standard deviations a geography’s change is from the mean, providing a relative classification within the dataset.
Categories: - extreme_positive: z >= 2 - high_positive: 1 <= z < 2 - average: -1 < z < 1 - high_negative: -2 < z <= -1 - extreme_negative: z <= -2
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
output_column (str) – Name for the category column
- Returns:
DataFrame with z-score category column and z-score values
- Return type:
- siege_utilities.geo.timeseries.trend_classifier.classify_trends(df, change_column, thresholds=None, output_column='trend_category')[source]
Classify changes into trend categories.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values (typically percentage change)
thresholds (TrendThresholds | str | Dict[str, float] | None) – Threshold configuration. Can be: - TrendThresholds object - Preset name (‘default’, ‘conservative’, ‘sensitive’, ‘housing’, ‘population’) - Dict with keys: rapid_growth, moderate_growth, stable_lower, moderate_decline - None (uses default thresholds)
output_column (str) – Name for the category column
- Returns:
DataFrame with new trend_category column
- Return type:
Example
- df = classify_trends(
df=income_changes, change_column=’B19013_001E_pct_change_2010_2020’, thresholds=’conservative’
)
- siege_utilities.geo.timeseries.trend_classifier.compare_trends(df1, df2, trend_column='trend_category', geoid_column='GEOID')[source]
Compare trend classifications between two DataFrames.
Useful for comparing trend patterns between different: - Time periods - Variables - Geographic subsets
- Parameters:
df1 (pandas.DataFrame) – First DataFrame with trend classification
df2 (pandas.DataFrame) – Second DataFrame with trend classification
trend_column (str) – Name of trend category column
geoid_column (str) – Name of GEOID column
- Returns:
DataFrame with comparison columns
- Return type:
- siege_utilities.geo.timeseries.trend_classifier.get_trend_summary(df, trend_column='trend_category', geoid_column='GEOID')[source]
Get summary statistics for trend classification.
- Parameters:
df (pandas.DataFrame) – DataFrame with trend categories
trend_column (str) – Name of the trend category column
geoid_column (str) – Name of the GEOID column
- Returns:
Dictionary with summary statistics
- Return type:
- siege_utilities.geo.timeseries.trend_classifier.identify_outliers(df, change_column, method='iqr', threshold=1.5)[source]
Identify outlier geographies based on change values.
- Parameters:
df (pandas.DataFrame) – DataFrame with change metrics
change_column (str) – Column containing change values
method (str) – Outlier detection method: - ‘iqr’: Interquartile range method - ‘zscore’: Z-score method
threshold (float) – Threshold for outlier detection: - For IQR: multiplier (default 1.5) - For zscore: number of standard deviations (default 1.5)
- Returns:
DataFrame with is_outlier and outlier_type columns
- Return type:
Other Modules
Shared admin-level area constants for H3 and S2 resolution selection.
The average areas below are rough midpoints across the entire US. The variance within a level (e.g. NY tract vs WY tract) is huge. The resolution returned by callers is a starting point; tune up or down by +/-1 if the polygons you actually have are systematically larger or smaller.
Choropleth map creation utilities for GeoDataFrames.
Provides standalone functions that accept GeoDataFrames and return matplotlib (fig, ax) tuples. Designed for notebooks, scripts, and interactive exploration.
For ReportLab PDF integration, see reporting.chart_generator.ChartGenerator which provides parallel implementations returning ReportLab Image objects.
- Functions:
create_choropleth – Single-variable choropleth create_choropleth_comparison – Multiple variables side by side create_classified_comparison – Same variable, different classification schemes create_bivariate_choropleth – Two-variable bivariate map with legend create_bivariate_crosstab – NxN count table for bivariate bins create_bivariate_companion_maps – Side-by-side monovariate maps with same bins verify_bivariate_classification – Data-level verification of classification create_bivariate_analysis – Orchestrator returning all artifacts save_map – Save figure to file with sensible defaults
- class siege_utilities.geo.choropleth.BivariateAnalysisResult[source]
Bases:
objectContainer for all bivariate choropleth analysis artifacts.
- bivariate_fig
Figure containing the bivariate choropleth map.
- Type:
matplotlib.figure.Figure
- bivariate_axes
Axes array [ax_map, ax_legend].
- Type:
numpy.ndarray
- crosstab
DataFrame of class-pair unit counts.
- Type:
- crosstab_fig
Figure of the rendered crosstab (colored grid).
- Type:
matplotlib.figure.Figure
- crosstab_ax
Axes for the rendered crosstab.
- Type:
matplotlib.pyplot.Axes
- companion_fig
Figure with two monovariate companion maps.
- Type:
matplotlib.figure.Figure
- companion_axes
Axes array [ax_var1, ax_var2].
- Type:
numpy.ndarray
- var1_breaks
Breakpoint array for variable 1.
- Type:
numpy.ndarray
- var2_breaks
Breakpoint array for variable 2.
- Type:
numpy.ndarray
- bivariate_fig: matplotlib.figure.Figure
- bivariate_axes: numpy.ndarray
- crosstab: pandas.DataFrame
- crosstab_fig: matplotlib.figure.Figure
- crosstab_ax: matplotlib.pyplot.Axes
- companion_fig: matplotlib.figure.Figure
- companion_axes: numpy.ndarray
- var1_breaks: numpy.ndarray
- var2_breaks: numpy.ndarray
- __init__(bivariate_fig, bivariate_axes, crosstab, crosstab_fig, crosstab_ax, companion_fig, companion_axes, verification, var1_breaks, var2_breaks)
- Parameters:
bivariate_fig (matplotlib.figure.Figure)
bivariate_axes (numpy.ndarray)
crosstab (pandas.DataFrame)
crosstab_fig (matplotlib.figure.Figure)
crosstab_ax (matplotlib.pyplot.Axes)
companion_fig (matplotlib.figure.Figure)
companion_axes (numpy.ndarray)
var1_breaks (numpy.ndarray)
var2_breaks (numpy.ndarray)
- Return type:
None
- siege_utilities.geo.choropleth.classify_choropleth(df, column, scheme='quantiles', k=5, cmap='YlOrRd')[source]
Classify a DataFrame column and add bin/color columns.
This is the non-GDAL entry point for choropleth classification. It produces the data layer (bin assignments + hex colors) that consumers render using their platform’s native visualization.
- Parameters:
df (pandas.DataFrame) – DataFrame (or GeoDataFrame) with a numeric column.
column (str) – Column name to classify.
scheme (str) – Classification scheme name.
k (int) – Number of classes.
cmap (str) – Matplotlib colormap name for color assignment. Requires matplotlib; omitted columns if unavailable.
- Returns:
_bin: int bin assignment (0..k-1, or -1 for NaN) _break_low: lower bound of the bin _break_high: upper bound of the bin _color: hex color string (if matplotlib available)
- Return type:
Copy of df with added columns
- Raises:
ValueError – If column not in df or values are empty.
- siege_utilities.geo.choropleth.classify_series(values, scheme='quantiles', k=5)[source]
Classify numeric values into k bins using the named scheme.
Backend dispatch: mapclassify (when installed) → pure numpy.
- Parameters:
values (numpy.ndarray | pandas.Series | list) – Numeric array, Series, or list.
scheme (str) – Classification scheme name (see AVAILABLE_SCHEMES).
k (int) – Number of classes. Some schemes (percentiles, boxplot) ignore k.
- Returns:
ClassificationResult with bin assignments, break points, and metadata.
- Raises:
ValueError – If scheme is unknown or values are empty.
- Return type:
ClassificationResult
- siege_utilities.geo.choropleth.create_bivariate_analysis(gdf, var1, var2, *, title='Bivariate Choropleth', n_classes=3, color_scheme='purple_blue', figsize=(14, 10), edgecolor='white', linewidth=0.5, legend_size_ratio=0.25, cmap1='YlOrRd', cmap2='YlGnBu', companion_figsize=(16, 8), crosstab_figsize=(6, 5))[source]
Create a complete bivariate choropleth analysis with all verification artifacts.
One call produces: - Bivariate choropleth map with magnitude legend - Rendered crosstab showing unit counts per class pair - Two companion monovariate maps (one per variable) - Verification dict confirming classification integrity
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and two numeric columns.
var1 (str) – Column name for first variable (x-axis).
var2 (str) – Column name for second variable (y-axis).
title (str) – Title for the bivariate map.
n_classes (int) – Number of quantile classes per variable.
color_scheme (str | List[str]) – Bivariate color scheme name or raw list.
figsize (Tuple[float, float]) – Size of the bivariate map figure.
edgecolor (str) – Polygon edge color on the bivariate map.
linewidth (float) – Polygon edge width on the bivariate map.
legend_size_ratio (float) – Legend panel width ratio.
cmap1 (str) – Colormap for var1 companion map.
cmap2 (str) – Colormap for var2 companion map.
companion_figsize (Tuple[float, float]) – Size of the companion maps figure.
crosstab_figsize (Tuple[float, float]) – Size of the rendered crosstab figure.
- Returns:
BivariateAnalysisResult dataclass with all artifacts.
- Return type:
- siege_utilities.geo.choropleth.create_bivariate_choropleth(gdf, var1, var2, *, title='Bivariate Choropleth', n_classes=3, color_scheme='purple_blue', figsize=(14, 10), edgecolor='white', linewidth=0.5, legend_size_ratio=0.25)[source]
Create a bivariate choropleth map showing two variables.
Bins each variable into quantile classes and combines them into a 2D color index, rendered with a proper NxN legend grid.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and two numeric columns.
var1 (str) – Column name for first variable (x-axis of legend).
var2 (str) – Column name for second variable (y-axis of legend).
title (str) – Map title.
n_classes (int) – Number of quantile classes per variable (default 3 = 9 colors).
color_scheme (str | List[str]) – Key into BIVARIATE_COLOR_SCHEMES or a raw list of hex colors.
figsize (Tuple[float, float]) – Figure size (width, height) in inches.
edgecolor (str) – Color of polygon edges.
linewidth (float) – Width of polygon edges.
legend_size_ratio (float) – Width ratio of the legend panel relative to the map.
- Returns:
(fig, axes) tuple where axes is an array [ax_map, ax_legend].
- Raises:
ValueError – If color scheme is unknown or has wrong number of colors.
- Return type:
Tuple[matplotlib.figure.Figure, numpy.ndarray]
- siege_utilities.geo.choropleth.create_bivariate_companion_maps(gdf, var1, var2, *, n_classes=3, cmap1='YlOrRd', cmap2='YlGnBu', figsize=(16, 8), edgecolor='black', linewidth=0.3)[source]
Create two side-by-side monovariate choropleths using the same quantile bins.
Useful as companion maps alongside a bivariate choropleth to let readers see each variable independently with the same classification.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and two numeric columns.
var1 (str) – Column name for first variable (left map).
var2 (str) – Column name for second variable (right map).
n_classes (int) – Number of quantile classes (same as bivariate map).
cmap1 (str) – Colormap for var1.
cmap2 (str) – Colormap for var2.
figsize (Tuple[float, float]) – Figure size (width, height) in inches.
edgecolor (str) – Edge color.
linewidth (float) – Edge width.
- Returns:
(fig, axes) tuple where axes is a numpy array of 2 Axes.
- Return type:
Tuple[matplotlib.figure.Figure, numpy.ndarray]
- siege_utilities.geo.choropleth.create_bivariate_crosstab(gdf, var1, var2, *, n_classes=3, color_scheme='purple_blue', render=False, figsize=(6, 5))[source]
Create a cross-tabulation of bivariate class counts.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with two numeric columns.
var1 (str) – Column name for first variable (columns of crosstab).
var2 (str) – Column name for second variable (rows of crosstab).
n_classes (int) – Number of quantile classes per variable.
color_scheme (str | List[str]) – Key into BIVARIATE_COLOR_SCHEMES or raw list (for render colors).
render (bool) – If True, also return a colored matplotlib figure.
figsize (Tuple[float, float]) – Figure size for rendered crosstab.
- Returns:
DataFrame with counts (n_classes rows × n_classes cols). If render=True: Tuple of (DataFrame, fig, ax).
- Return type:
If render=False
- siege_utilities.geo.choropleth.create_choropleth(gdf, column, *, title='', cmap='YlOrRd', figsize=(10, 12), legend=True, legend_kwds=None, edgecolor='black', linewidth=0.5, scheme=None, k=5, ax=None, missing_kwds=None)[source]
Create a single-variable choropleth map.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and a numeric column to visualize.
column (str) – Column name to color by.
title (str) – Map title.
cmap (str) – Matplotlib colormap name.
figsize (Tuple[float, float]) – Figure size (width, height) in inches. Ignored when ax is provided.
legend (bool) – Whether to show the color legend.
legend_kwds (Dict | None) – Dict passed to geopandas legend_kwds (e.g. label, shrink, format).
edgecolor (str) – Color of polygon edges.
linewidth (float) – Width of polygon edges.
scheme (str | None) – Classification scheme name (requires mapclassify). None for continuous.
k (int) – Number of classes when using a classification scheme.
ax (matplotlib.pyplot.Axes | None) – Optional matplotlib Axes to plot onto (for subplot embedding).
missing_kwds (Dict | None) – Dict for styling missing/NaN geometries.
- Returns:
(fig, ax) tuple.
- Raises:
ValueError – If scheme is unknown and no backend can handle it.
- Return type:
Tuple[matplotlib.figure.Figure, matplotlib.pyplot.Axes]
- siege_utilities.geo.choropleth.create_choropleth_comparison(gdf, columns, *, figsize=None, ncols=3, edgecolor='gray', linewidth=0.3)[source]
Create multiple choropleths side by side.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and data columns.
columns (List[Dict[str, str]]) – List of dicts, each with keys: - ‘column’ (required): Column name to visualize. - ‘title’ (optional): Subplot title. - ‘cmap’ (optional): Colormap name (default ‘YlOrRd’).
figsize (Tuple[float, float] | None) – Figure size. If None, auto-calculated from ncols.
ncols (int) – Number of columns in the subplot grid.
edgecolor (str) – Edge color for all subplots.
linewidth (float) – Edge width for all subplots.
- Returns:
(fig, axes) tuple where axes is a numpy array of Axes.
- Return type:
Tuple[matplotlib.figure.Figure, numpy.ndarray]
- siege_utilities.geo.choropleth.create_classified_comparison(gdf, column, schemes, *, k=5, cmap='YlOrRd', figsize=None, ncols=2, edgecolor='black', linewidth=0.3, titles=None)[source]
Compare classification schemes for the same variable.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with geometry and a numeric column.
column (str) – Column name to classify and visualize.
schemes (List[str]) – List of mapclassify scheme names (e.g. ‘quantiles’, ‘fisher_jenks’).
k (int) – Number of classes for each scheme.
cmap (str) – Colormap name applied to all subplots.
figsize (Tuple[float, float] | None) – Figure size. If None, auto-calculated.
ncols (int) – Number of columns in the subplot grid.
edgecolor (str) – Edge color.
linewidth (float) – Edge width.
titles (List[str] | None) – Custom titles for each subplot. If None, uses SCHEME_LABELS.
- Returns:
(fig, axes) tuple.
- Raises:
ValueError – If a scheme is unknown and no backend can handle it.
- Return type:
Tuple[matplotlib.figure.Figure, numpy.ndarray]
- siege_utilities.geo.choropleth.save_map(fig, path, *, dpi=150, bbox_inches='tight', facecolor='white', transparent=False)[source]
Save a map figure to file.
- Parameters:
- Returns:
Resolved Path object of the saved file.
- Return type:
- siege_utilities.geo.choropleth.verify_bivariate_classification(gdf, var1, var2, *, n_classes=3)[source]
Verify the integrity of a bivariate quantile classification.
Pure data function — no plotting. Checks that the classification is complete, breakpoints are monotonic, and counts sum correctly.
- Parameters:
gdf (geopandas.GeoDataFrame) – GeoDataFrame with two numeric columns.
var1 (str) – Column name for first variable.
var2 (str) – Column name for second variable.
n_classes (int) – Number of quantile classes per variable.
- Returns:
valid (bool): True if all checks pass. total_units (int): Total number of geographic units. classified_units (int): Number successfully classified. crosstab (pd.DataFrame): Raw count crosstab. var1_breaks (np.ndarray): Breakpoint values for var1. var2_breaks (np.ndarray): Breakpoint values for var2. errors (List[str]): List of error messages (empty if valid).
- Return type:
Dict with keys
Format detection and per-format attribute extraction for vector GIS files.
This module provides a small dispatcher that identifies a vector GIS file’s format (shapefile / GeoJSON / KML / KMZ / GeoPackage / DXF / SWMaps / unknown) and exposes per-format attribute extractors that tolerate missing fields the way OGR does (return None rather than raise).
Pattern lifted from a third-party tile-server audit
(common/utils/DataUtils.py:488-563 — _getDataFromSource and the
per-format _get*DataFromSource helpers). Patterns explicitly NOT carried
over from the salvage source:
Hardcoded Windows binary paths (
osgeo4WBinPath,ODA_FILE_CONVERTER_PATH) — the salvage shell-out via subprocess for DWG->DXF conversion is discarded entirely.osgeoPython bindings — the salvage source uses GDAL/OGR directly; SU declaresfiona(the high-level OGR wrapper) as the geo dep, so this module is fiona-shaped at the attribute-extraction layer.Azure Blob fetch coupling around the dispatch.
Application-schema-specific extractors (
map_job_id, etc.) — the generic extractors here accept candidate field-name lists; consumer code supplies the schema.
Optional dependencies are guarded; calling an extractor whose backing
library is not installed raises ImportError with the install hint.
- siege_utilities.geo.data_reception.detect_format(file_path)[source]
Return a format token for a vector GIS file.
- Parameters:
file_path (str) – Path to a vector GIS file on disk.
- Returns:
"shapefile","geojson","kml","kmz","geopackage","dxf","swmaps","unknown".- Return type:
One of
- Raises:
FileNotFoundError – If the file does not exist.
Detection algorithm:
Extension lookup (case-insensitive) against a small canonical map.
For ambiguous extensions (
.json,.sqlite,.db,.zip), a magic-byte / archive-peek fallback inspects the file body.
Directories return
"unknown"— walking is the caller’s responsibility.
- siege_utilities.geo.data_reception.extract_attributes_kml(kml_extended_data, field_names, missing='none')[source]
Extract one attribute from a KML extended-data mapping.
Mirrors the salvage-source helper
_getKMLDataFromSource(DataUtils.py:516-520). The salvage version was a one-liner dict lookup with no candidate fallback; this version adds the same candidate-list semantics asextract_attributes_ogr()for consistency across the dispatcher.
- siege_utilities.geo.data_reception.extract_attributes_ogr(feature, field_names, missing='none')[source]
Extract one attribute from a fiona-record-shaped feature.
Mirrors the candidate-list semantics of the salvage-source helper
_getOGRDataFromSource(DataUtils.py:489-513) which accepts a primary plus a fallback field name and returnsNonewhen neither is present. The salvage source used the GDAL/OGR Python bindings directly; this implementation is fiona-record-shaped (the high-level OGR wrapper that SU declares as a geo dep).- Parameters:
feature (Any) – A fiona-style record (
{"properties": {...}, ...}) OR a plaindict(e.g. the property dict directly).field_names (Iterable[str]) – Candidate field names, in priority order. First name present on the feature wins.
missing (str) – One of
"none"(default; returnNone),"raise"(KeyError), or"warn"(log + returnNone).
- Returns:
The attribute value or
None.- Return type:
- siege_utilities.geo.data_reception.extract_attributes_swmaps(db_path, feature_id, field_names, missing='none')[source]
Extract one attribute for a SWMaps feature by attribute-field name.
Queries the SWMaps SQLite
attribute_valuestable joined toattribute_fieldsfor the named field, scoped tofeature_id. This is the same join pattern as the salvage source’sswmapsAttributeQuery(DataUtils.py:693-698); the application-schema-specific mapper (swMapsAttributeMapperdict atDataUtils.py:672-686) is NOT lifted — that lives with the consumer.For full SWMaps feature iteration (geometry + all attributes), use
siege_utilities.geo.swmaps_readerinstead; this function is the thin dispatcher-friendly accessor.- Parameters:
- Returns:
The attribute value (as stored in
attribute_values.value) orNone.- Return type:
Databricks-oriented fallback planning for spatial ingestion workflows.
- class siege_utilities.geo.databricks_fallback.SpatialLoaderPlan[source]
Bases:
objectChosen loader with deterministic fallback ordering.
- siege_utilities.geo.databricks_fallback.build_census_ingest_targets(year, geography_levels, prefix='census')[source]
Build de-duplicated target table names for ingest planning.
- siege_utilities.geo.databricks_fallback.build_census_table_name(year, geography_level, prefix='census')[source]
Build a normalized census table name like census_2024_block_group.
- siege_utilities.geo.databricks_fallback.select_spatial_loader(ogr2ogr_available, sedona_available, native_spatial_available=False)[source]
Select a spatial loader strategy for Databricks-style environments.
Loader order: 1. databricks_native 2. ogr2ogr 3. sedona 4. python
- Parameters:
- Return type:
Deprecation shim — re-exports from siege_utilities.geo.providers.nces_download.
Moved during ELE-2438. Will be removed in v4.0.0.
- exception siege_utilities.geo.nces_download.NCESDownloadError[source]
Bases:
ExceptionRaised when an NCES download fails.
- class siege_utilities.geo.nces_download.NCESDownloader[source]
Bases:
objectDownload NCES locale boundaries, school locations, and district data.
Downloads from the NCES EDGE (Education Demographic and Geographic Estimates) program and returns data as GeoDataFrames.
- Parameters:
cache_dir – Directory for caching downloaded files. Defaults to a temporary directory.
timeout – HTTP request timeout in seconds.
Example:
downloader = NCESDownloader(cache_dir="./nces_cache") boundaries = downloader.download_locale_boundaries(2023) # GeoDataFrame with 12 locale territory polygons
- download_locale_boundaries(year=2023, *, crs=None)[source]
Download NCES locale boundary polygons.
Returns a GeoDataFrame with 12 rows — one polygon per locale territory (codes 11-43). Each polygon represents the geographic extent of that locale classification.
- Parameters:
year (int) – NCES publication year.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
locale_code, locale_category, locale_subcategory, name, geometry.
- Return type:
GeoDataFrame with columns
- download_school_locations(year=2023, state_abbr=None, *, crs=None)[source]
Download geocoded school locations.
Returns a GeoDataFrame of school point locations with NCES locale codes.
- Parameters:
year (int) – NCES publication year.
state_abbr (str | None) – Optional 2-letter state abbreviation to filter.
crs (str | None) – Output CRS. Defaults to
get_default_crs().
- Returns:
ncessch, school_name, lea_id, state_abbr, locale_code, locale_category, locale_subcategory, geometry.
- Return type:
GeoDataFrame with columns
- download_district_data(year=2023)[source]
Download district administrative data with locale codes.
Returns a DataFrame (not GeoDataFrame) of school district administrative records keyed by LEA ID.
- Parameters:
year (int) – NCES publication year.
- Returns:
lea_id, lea_name, state_abbr, locale_code, locale_category, locale_subcategory, survey_year.
- Return type:
DataFrame with columns
RDH dataset catalog and discovery.
Pre-indexes available Redistricting Data Hub datasets by state, year, chamber, dataset type, and format for browsable discovery without trial-and-error API calls.
- class siege_utilities.geo.rdh_catalog.CatalogEntry[source]
Bases:
objectIndexed metadata for a single RDH dataset.
- class siege_utilities.geo.rdh_catalog.CoverageCell[source]
Bases:
objectSingle cell in the coverage matrix.
- class siege_utilities.geo.rdh_catalog.DictRDHCatalogProvider[source]
Bases:
RDHCatalogProviderIn-memory catalog provider for testing.
- __init__(entries=None)[source]
- Parameters:
entries (list[CatalogEntry] | None)
- add(entry)[source]
- Parameters:
entry (CatalogEntry)
- class siege_utilities.geo.rdh_catalog.RDHCatalog[source]
Bases:
objectPre-indexed catalog of available RDH datasets.
Provides browsable discovery of datasets by state, year, chamber, dataset type, and format without trial-and-error API calls.
- Parameters:
provider – Source for fetching the full dataset inventory.
cache_dir – Directory for persisting the catalog index.
cache_ttl – Cache time-to-live in seconds (default 30 days).
- __init__(provider=None, cache_dir=None, cache_ttl=2592000)[source]
- Parameters:
provider (RDHCatalogProvider | None)
cache_dir (Path | None)
cache_ttl (int)
- property entries: list[CatalogEntry]
- search(state=None, year=None, chamber=None, dataset_type=None, format=None, query=None, limit=50)[source]
Search the catalog with ranked results.
At least one filter or query must be provided.
- Parameters:
state (str | None) – Two-letter state abbreviation.
year (str | None) – Publication year.
chamber (str | None) – Legislative chamber (congress, state_senate, state_house).
dataset_type (str | None) – Dataset category.
format (str | None) – File format (csv, shp).
query (str | None) – Free-text search against title.
limit (int) – Maximum results to return.
- Returns:
List of SearchResult sorted by relevance score (descending).
- Return type:
- coverage_matrix(dataset_type=None)[source]
Build a coverage matrix of state x year x dataset_type.
- Parameters:
dataset_type (str | None) – Optionally filter to a single dataset type.
- Returns:
List of CoverageCell entries with counts and available formats.
- Return type:
- class siege_utilities.geo.rdh_catalog.RDHCatalogProvider[source]
Bases:
ABCProtocol for fetching the full RDH dataset inventory.
- class siege_utilities.geo.rdh_catalog.SearchResult[source]
Bases:
objectRanked search result from the catalog.
- entry
The matching CatalogEntry.
- entry: CatalogEntry
- __init__(entry, score=1.0)
- Parameters:
entry (CatalogEntry)
score (float)
- Return type:
None
Reader for SWMaps .swmz archives and raw .sqlite field-data files.
SWMaps is a field-data-collection tool that exports .swmz archives
containing a SQLite database with three core tables:
features— one row per recorded feature (UUID, name, layer_id).points— one row per coordinate point (fid,seq,lat,lon,elv); rows sharefidand are ordered byseq.feature_layers— feature-type metadata (geom_type, group_name).
The geometry rule from the salvage source: one point per feature -> POINT, two or more points -> LINESTRING.
Pattern lifted from a third-party tile-server audit
(common/utils/DataUtils.py:669-783 — _readSWMapsSqlite).
Patterns NOT carried over:
Hardcoded application-schema attribute mapping (
map_job_id,map_group_id,map_feature_type_id,workorder_number). The consumer supplies its own attribute mapper.The PG-RPC
_getFeatureGroupAndTypeIdFromNamesand_getJobIdFromWorkOrderNumbercalls (Azure-coupled).Manual WKT string concatenation — replaced with shapely.
_siteMapLoggermacro — replaced with stdliblogging.
Typical usage:
from siege_utilities.geo.swmaps_reader import open_swmaps, read_features
with open_swmaps("/path/to/field-export.swmz") as archive:
for feature in read_features(archive):
print(feature["feature_type"], feature["geometry_wkt"])
- class siege_utilities.geo.swmaps_reader.SWMapsArchive[source]
Bases:
objectContext-manager handle to an opened SWMaps archive or raw SQLite file.
Use
open_swmaps()to construct. Callclose()(or use as awithblock) to clean up any extracted-archive temp directory.
- siege_utilities.geo.swmaps_reader.open_swmaps(path)[source]
Open a SWMaps archive (
.swmz/.zip) or raw SQLite file.- Parameters:
path (str) – Path to a
.swmzarchive, a.ziparchive containing a.sqlitemember, or a raw.sqlite/.dbfile.- Returns:
An
SWMapsArchivehandle. Use it as a context manager so any extracted temp directory is cleaned up on exit.- Raises:
FileNotFoundError – If
pathdoes not exist.ValueError – If an archive contains no SQLite member, or if the file extension is not one of the recognized forms.
- Return type:
- siege_utilities.geo.swmaps_reader.read_features(archive, mapper=None)[source]
Yield per-feature dicts from a SWMaps archive.
Each yielded dict has:
feature_id— SWMaps UUID.feature_type— fromfeature_layers.name.feature_group— fromfeature_layers.group_name.layer_id— fromfeature_layers.uuid.feature_name— fromfeatures.name.geometry_wkt— POINT (single coordinate) or LINESTRING (two or more coordinates), built viashapely. Z dimension included when every coordinate row carries a non-nullelv; dropped otherwise.attributes—{field_name: value}mapping pulled fromattribute_valuesjoined toattribute_fields. Keys can be renamed by passingmapper.
- Parameters:
archive (SWMapsArchive) – An
SWMapsArchiveopened viaopen_swmaps().mapper (dict[str, str] | None) – Optional
{source_name: consumer_name}rename map. WhenNone, attribute keys are the verbatim SWMaps field names. Unknown source keys are logged and skipped (kept verbatim).
- Yields:
Dicts as described above. Empty-features (zero coordinate rows) are logged at WARNING and skipped.
- Raises:
ImportError – If
shapelyis not installed.- Return type:
The geometry rule matches the salvage source (
DataUtils.py:749-768) but usesshapely.geometry.Point/shapely.geometry.LineStringwith.wktrather than manual string concatenation.