Geo Django

GeoDjango integration: models, services, management commands, and serializers for the spatial platform.

GeoDjango integration for temporal geographic feature data.

This module provides Django models for storing and querying geographic boundaries with full GeoDjango spatial support and temporal validity tracking.

Requirements:
  • Django >= 4.2

  • djangorestframework-gis >= 1.0

  • PostGIS database

Usage:

Add ‘siege_utilities.geo.django’ to INSTALLED_APPS.

Example

>>> from siege_utilities.geo.django.models import State, County, Tract
>>> from siege_utilities.geo.django.services import BoundaryPopulationService

# Fetch boundaries containing a point >>> from django.contrib.gis.geos import Point # doctest: +SKIP >>> point = Point(-122.4194, 37.7749, srid=4326) # doctest: +SKIP >>> tract = Tract.objects.filter(geometry__contains=point).first() # doctest: +SKIP

# Populate boundaries from TIGER/Line >>> service = BoundaryPopulationService() # doctest: +SKIP >>> service.populate_counties(year=2020, state_fips=’06’) # doctest: +SKIP

siege_utilities.geo.django.get_model(model_name)[source]

Lazily import a model by name.

Parameters:

model_name (str) – Name of the model (e.g., ‘State’, ‘County’, ‘Tract’)

Returns:

The Django model class

Raises:
  • ImportError – If Django/GeoDjango is not available

  • ValueError – If the model name is not recognized

siege_utilities.geo.django.get_service(service_name)[source]

Lazily import a service by name.

Parameters:

service_name (str) – Name of the service (e.g., ‘BoundaryPopulationService’)

Returns:

The service class

Raises:

ImportError – If Django/GeoDjango is not available

Models

Abstract base models for temporal geographic features.

Hierarchy:

TemporalGeographicFeature (root abstract — no geometry, pure temporal identity) ├── TemporalBoundary (abstract — MultiPolygon, area-based features) │ └── CensusTIGERBoundary (abstract — GEOID, FIPS codes, TIGER metadata) ├── TemporalLinearFeature (abstract — MultiLineString, e.g. roads, rivers) └── TemporalPointFeature (abstract — Point, e.g. addresses, facilities)

class siege_utilities.geo.django.models.base.CensusTIGERBoundary[source]

Bases: TemporalBoundary

Abstract model for US Census TIGER/Line boundaries.

Adds GEOID-based identity and TIGER-specific metadata fields that are common to ALL Census geography types (states, counties, tracts, etc.).

The geoid is the authoritative identifier; feature_id and boundary_id are automatically synced from it in save().

GEOID Format by Geography Type:

State: 2 digits (e.g., “06” for California) County: 5 digits = state (2) + county (3) (e.g., “06037”) Tract: 11 digits = state (2) + county (3) + tract (6) Block Group: 12 digits = tract (11) + block group (1) Block: 15 digits = tract (11) + block (4) Place: 7 digits = state (2) + place (5) ZCTA: 5 digits CD: 4 digits = state (2) + district (2)

class Meta[source]

Bases: object

abstract = True
ordering = ['geoid']
save(*args, **kwargs)[source]

Sync feature_id and boundary_id from geoid.

classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.base.TemporalBoundary[source]

Bases: TemporalGeographicFeature

Abstract model for area/polygon-based geographic features.

All boundary types (Census, GADM, NLRB regions, judicial districts, etc.) share these fields. The geometry is always stored as MultiPolygon in WGS 84 (SRID 4326) for maximum interoperability.

Fields inherited from TemporalGeographicFeature:

feature_id, name, vintage_year, valid_from, valid_to, source

Fields added here:
boundary_id: Alias for feature_id, kept for readability in boundary contexts.

Automatically synced from feature_id in save().

geometry: MultiPolygon, SRID 4326. area_land / area_water: Area in square meters (from TIGER ALAND/AWATER). internal_point: Representative point for labeling (INTPTLAT/INTPTLON).

class Meta[source]

Bases: object

abstract = True
ordering = ['feature_id']
property total_area: int

Total area (land + water) in square meters.

property land_percentage: float

Percentage of total area that is land.

save(*args, **kwargs)[source]

Sync boundary_id from feature_id.

class siege_utilities.geo.django.models.base.TemporalGeographicFeature[source]

Bases: Model

Root abstract model for all temporal geographic features.

Every geographic feature — whether a polygon boundary, a road centerline, or a point address — has a temporal dimension: it was valid during some period and belongs to some vintage/edition of its source dataset.

This base provides the identity, temporal, and metadata fields shared by ALL geographic feature types. Geometry is intentionally omitted — each geometry-type subclass (TemporalBoundary, TemporalLinearFeature, TemporalPointFeature) adds the appropriate geometry field.

Fields:
feature_id: Stable identifier unique within (feature_id, vintage_year).

Polygons sync this from geoid; points from address hash; etc.

name: Human-readable label. vintage_year: The edition year of the source dataset (e.g. 2020 TIGER). valid_from / valid_to: Date range during which this feature was in effect.

NULL means open-ended (still valid / always valid).

source: Provenance string (e.g. “TIGER/Line”, “GADM”, “OpenStreetMap”).

class Meta[source]

Bases: object

abstract = True
ordering = ['feature_id']
property is_current: bool

True if this feature has no valid_to date (still in effect).

class siege_utilities.geo.django.models.base.TemporalLinearFeature[source]

Bases: TemporalGeographicFeature

Abstract model for linear geographic features (roads, rivers, rail lines).

These features have temporal validity — a highway may not have existed before a certain date, a road may be reclassified, a rail line may be decommissioned.

Concrete subclasses (future): Road, RailLine, Waterway, etc.

class Meta[source]

Bases: object

abstract = True
ordering = ['feature_id']
class siege_utilities.geo.django.models.base.TemporalPointFeature[source]

Bases: TemporalGeographicFeature

Abstract model for point geographic features (addresses, facilities, landmarks).

These features have temporal validity — a building may be demolished, a new address may be created, a facility may relocate.

Concrete subclasses (future): Address, Facility, Landmark, etc.

class Meta[source]

Bases: object

abstract = True
ordering = ['feature_id']

Concrete Census TIGER/Line boundary models.

Each model represents a specific Census geography type with appropriate parent relationships and GEOID parsing. All inherit from CensusTIGERBoundary, which provides: geoid, state_fips, lsad, mtfcc, funcstat, vintage_year, valid_from/valid_to, geometry, area_land, area_water, internal_point, source.

class siege_utilities.geo.django.models.boundaries.Block[source]

Bases: CensusTIGERBoundary

Census Block boundary.

GEOID: 15 digits = state (2) + county (3) + tract (6) + block (4) Example: “060371011001001” for block 1001 in tract 1011.00, LA County, CA

Note: Blocks are very numerous (millions per state). Consider using a separate table or partitioning for large-scale deployments.

state

alias of State

county

alias of County

tract

alias of Tract

block_group_ref

alias of BlockGroup

class Meta[source]

Bases: object

verbose_name = 'Census Block'
verbose_name_plural = 'Census Blocks'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

property block_group: str

Block group derived from block code (first digit).

class siege_utilities.geo.django.models.boundaries.BlockGroup[source]

Bases: CensusTIGERBoundary

Census Block Group boundary.

GEOID: 12 digits = state (2) + county (3) + tract (6) + block group (1) Example: “060371011001” for block group 1 in tract 1011.00, LA County, CA

state

alias of State

county

alias of County

tract

alias of Tract

class Meta[source]

Bases: object

verbose_name = 'Block Group'
verbose_name_plural = 'Block Groups'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.boundaries.CongressionalDistrict[source]

Bases: CensusTIGERBoundary

Congressional District boundary.

GEOID: 4 digits = state (2) + district (2) Example: “0614” for California’s 14th district

Note: District numbers change after reapportionment (every 10 years). Use vintage_year to track which Congress the districts represent.

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'Congressional District'
verbose_name_plural = 'Congressional Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

property is_at_large: bool

True if this is an at-large district (single representative).

class siege_utilities.geo.django.models.boundaries.County[source]

Bases: CensusTIGERBoundary

County or county-equivalent boundary.

GEOID: 5 digits = state (2) + county (3) Example: “06037” for Los Angeles County, CA

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'County'
verbose_name_plural = 'Counties'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.boundaries.Place[source]

Bases: CensusTIGERBoundary

Census Place (city, town, CDP) boundary.

GEOID: 7 digits = state (2) + place (5) Example: “0644000” for Los Angeles city, CA

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'Place'
verbose_name_plural = 'Places'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.boundaries.State[source]

Bases: CensusTIGERBoundary

US State or Territory boundary.

GEOID: 2 digits (state FIPS code) Example: “06” for California

class Meta[source]

Bases: object

verbose_name = 'State'
verbose_name_plural = 'States'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.boundaries.Tract[source]

Bases: CensusTIGERBoundary

Census Tract boundary.

GEOID: 11 digits = state (2) + county (3) + tract (6) Example: “06037101100” for tract 1011.00 in LA County, CA

state

alias of State

county

alias of County

class Meta[source]

Bases: object

verbose_name = 'Census Tract'
verbose_name_plural = 'Census Tracts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

property tract_number: str

Human-readable tract number (e.g., ‘1011.00’).

property urbanicity_category

NCES locale category (city, suburban, town, rural) or None.

property urbanicity_subcategory

NCES locale subcategory (e.g., city_large, rural_remote) or None.

class siege_utilities.geo.django.models.boundaries.ZCTA[source]

Bases: CensusTIGERBoundary

ZIP Code Tabulation Area boundary.

GEOID: 5 digits (the ZCTA code, similar to ZIP) Example: “90210” for Beverly Hills area

Note: ZCTAs approximate but do not exactly match USPS ZIP codes. They are defined by Census blocks and may cross state lines.

class Meta[source]

Bases: object

verbose_name = 'ZCTA'
verbose_name_plural = 'ZCTAs'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

Extended Census boundary models — non-nesting geographies.

CBSA, UrbanArea, PUMA, TribalArea, and CountySubdivision are Census-defined boundaries that either don’t nest in the standard state→county→tract hierarchy or belong to parallel hierarchy branches. All inherit from CensusTIGERBoundary.

class siege_utilities.geo.django.models.census_extended.CBSA[source]

Bases: CensusTIGERBoundary

Core Based Statistical Area (Metropolitan/Micropolitan).

CBSAs are defined by OMB around urban cores of 10,000+ population. They consist of one or more counties (or county-equivalents). CBSAs do NOT nest in the Census hierarchy — they can span states.

GEOID: 5 digits (CBSA FIPS code) Example: “31080” for Los Angeles-Long Beach-Anaheim, CA

CBSA_TYPE_CHOICES = [('metro', 'Metropolitan Statistical Area'), ('micro', 'Micropolitan Statistical Area')]
class Meta[source]

Bases: object

verbose_name = 'CBSA'
verbose_name_plural = 'CBSAs'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.census_extended.UrbanArea[source]

Bases: CensusTIGERBoundary

Census Urban Area boundary.

Urban Areas represent densely developed territory. Two types: - Urbanized Area (UA): 50,000+ population - Urban Cluster (UC): 2,500–49,999 population

Like CBSAs, Urban Areas can span county and state boundaries.

GEOID: 5 digits (Urban Area code) Example: “51445” for Los Angeles—Long Beach—Anaheim, CA UA

UA_TYPE_CHOICES = [('UA', 'Urbanized Area (50,000+)'), ('UC', 'Urban Cluster (2,500-49,999)')]
class Meta[source]

Bases: object

verbose_name = 'Urban Area'
verbose_name_plural = 'Urban Areas'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.census_extended.PUMA[source]

Bases: CensusTIGERBoundary

Public Use Microdata Area boundary.

PUMAs are non-overlapping, statistical geographic areas of contiguous counties or census tracts containing ~100,000+ people. Used by ACS public-use microdata samples (PUMS) and small-area estimates.

GEOID: 7 digits = state (2) + PUMA (5) Example: “0603701” for PUMA 03701 in California (Los Angeles area)

class Meta[source]

Bases: object

verbose_name = 'PUMA'
verbose_name_plural = 'PUMAs'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.census_extended.TribalArea[source]

Bases: CensusTIGERBoundary

American Indian / Alaska Native / Native Hawaiian Area boundary.

TIGER layer: tl_YYYY_us_aiannh (national file, not per-state). GEOIDs vary in length (typically 4–5 digits); no fixed-length constraint. Tribal areas can span state boundaries.

GEOID example: “0010” for Agua Caliente Indian Reservation

class Meta[source]

Bases: object

verbose_name = 'Tribal Area'
verbose_name_plural = 'Tribal Areas'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.census_extended.CountySubdivision[source]

Bases: CensusTIGERBoundary

County Subdivision (MCD/CCD) boundary.

Minor Civil Divisions (MCDs) or Census County Divisions (CCDs) are the primary legal or statistical subdivision of a county. MCDs are used in 28 states + DC; CCDs fill in where MCDs don’t exist.

TIGER layer: tl_YYYY_SS_cousub GEOID: 10 digits = state (2) + county (3) + cousub (5) Example: “2500109000” for Abington town, Plymouth County, MA

state

alias of State

county

alias of County

class Meta[source]

Bases: object

verbose_name = 'County Subdivision'
verbose_name_plural = 'County Subdivisions'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

Demographic data models for Census boundaries.

These models store demographic snapshots that can be attached to any Census boundary type using Django’s contenttypes framework.

class siege_utilities.geo.django.models.demographics.DemographicVariable[source]

Bases: Model

Reference table for Census variables.

Stores metadata about Census variables for documentation and validation purposes.

DATASET_CHOICES = [('acs5', 'American Community Survey 5-Year'), ('acs1', 'American Community Survey 1-Year'), ('dec', 'Decennial Census'), ('dec_pl', 'Decennial Census P.L. 94-171')]
class Meta[source]

Bases: object

verbose_name = 'Demographic Variable'
verbose_name_plural = 'Demographic Variables'
unique_together = [('code', 'dataset')]
indexes = [django.db.models.Index]
property is_estimate: bool

True if this is an estimate variable (ends in E).

property is_moe: bool

True if this is a margin of error variable (ends in M).

property base_code: str

Return the code without the E/M suffix.

class siege_utilities.geo.django.models.demographics.DemographicSnapshot[source]

Bases: Model

Demographic data snapshot for a Census boundary.

Uses generic foreign key to attach to any boundary type. Stores values as JSON to allow flexible variable sets.

Example

>>> snapshot = DemographicSnapshot.objects.create(
...     content_object=tract,
...     year=2022,
...     dataset='acs5',
...     values={'B01001_001E': 4521, 'B19013_001E': 75000},
...     moe_values={'B01001_001E': 150, 'B19013_001E': 5000}
... )
DATASET_CHOICES = [('acs5', 'American Community Survey 5-Year'), ('acs1', 'American Community Survey 1-Year'), ('dec', 'Decennial Census'), ('dec_pl', 'Decennial Census P.L. 94-171')]
content_object = 'content_type'
class Meta[source]

Bases: object

verbose_name = 'Demographic Snapshot'
verbose_name_plural = 'Demographic Snapshots'
unique_together = [('content_type', 'object_id', 'year', 'dataset')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
get_value(variable_code, default=None)[source]

Get a specific variable value.

Parameters:
  • variable_code (str) – Census variable code (e.g., ‘B01001_001E’)

  • default – Value to return if variable not present

Returns:

The variable value or default

get_moe(variable_code, default=None)[source]

Get margin of error for a specific variable.

Parameters:
  • variable_code (str) – Census variable code (without M suffix)

  • default – Value to return if MOE not present

Returns:

The margin of error or default

save(*args, **kwargs)[source]

Auto-populate summary fields from values if present.

class siege_utilities.geo.django.models.demographics.DemographicTimeSeries[source]

Bases: Model

Pre-computed time series for frequently-accessed demographic trends.

This model stores aggregated time series data for performance, avoiding repeated joins across multiple DemographicSnapshot records.

content_object = 'content_type'
class Meta[source]

Bases: object

verbose_name = 'Demographic Time Series'
verbose_name_plural = 'Demographic Time Series'
unique_together = [('content_type', 'object_id', 'variable_code', 'dataset', 'start_year', 'end_year')]
indexes = [django.db.models.Index, django.db.models.Index]

NCES education models.

School district boundaries from Census TIGER/Line, enhanced with NCES locale classification data (12-code urban-centric system). Plus NCES-specific models for locale territory polygons and school locations.

School districts inherit from CensusTIGERBoundary. NCESLocaleBoundary inherits from TemporalBoundary (not Census-specific). SchoolLocation inherits from TemporalPointFeature.

class siege_utilities.geo.django.models.education.SchoolDistrictBase[source]

Bases: CensusTIGERBoundary

Abstract base for NCES school district boundaries.

Adds LEA (Local Education Agency) identifier and NCES locale classification fields. Locale codes are denormalized here for query performance — the authoritative classification logic lives in siege_utilities.geo.locale.NCESLocaleClassifier.

GEOID: 7 digits = state (2) + LEA (5)

state

alias of State

class Meta[source]

Bases: object

abstract = True
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.education.SchoolDistrictElementary[source]

Bases: SchoolDistrictBase

Elementary school district boundary.

class Meta[source]

Bases: object

verbose_name = 'School District (Elementary)'
verbose_name_plural = 'School Districts (Elementary)'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
class siege_utilities.geo.django.models.education.SchoolDistrictSecondary[source]

Bases: SchoolDistrictBase

Secondary school district boundary.

class Meta[source]

Bases: object

verbose_name = 'School District (Secondary)'
verbose_name_plural = 'School Districts (Secondary)'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
class siege_utilities.geo.django.models.education.SchoolDistrictUnified[source]

Bases: SchoolDistrictBase

Unified school district boundary (serves all grades).

class Meta[source]

Bases: object

verbose_name = 'School District (Unified)'
verbose_name_plural = 'School Districts (Unified)'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
class siege_utilities.geo.django.models.education.NCESLocaleBoundary[source]

Bases: TemporalBoundary

NCES locale territory polygon.

NCES publishes 12 locale territory polygons per year — one for each code (11=City-Large through 43=Rural-Remote). These are pre-classified boundaries that can be used for spatial joins without recomputing.

Not a CensusTIGERBoundary because these are NCES-published, not TIGER.

class Meta[source]

Bases: object

verbose_name = 'NCES Locale Boundary'
verbose_name_plural = 'NCES Locale Boundaries'
unique_together = [('locale_code', 'nces_year')]
indexes = [django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Persist the model, auto-populating feature_id and source.

*args/**kwargs: Forwarded to super().save().

class siege_utilities.geo.django.models.education.SchoolLocation[source]

Bases: TemporalPointFeature

Individual school geocoded point location.

Downloaded from NCES EDGE school location data. Each row represents a school with its geocoded coordinates and NCES locale classification.

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'School Location'
verbose_name_plural = 'School Locations'
unique_together = [('ncessch', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Persist the model, auto-populating feature_id and source.

*args/**kwargs: Forwarded to super().save().

Federal boundary models (non-Census).

These models inherit directly from TemporalBoundary (not CensusTIGERBoundary) because they don’t use Census GEOIDs or TIGER metadata.

class siege_utilities.geo.django.models.federal.NLRBRegion[source]

Bases: TemporalBoundary

National Labor Relations Board regional boundary.

The NLRB divides the US into 26 regions (numbered, with some gaps). Boundaries change infrequently but do shift over time.

class Meta[source]

Bases: object

verbose_name = 'NLRB Region'
verbose_name_plural = 'NLRB Regions'
unique_together = [('feature_id', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Persist the model, auto-populating feature_id and source.

*args/**kwargs: Forwarded to super().save().

class siege_utilities.geo.django.models.federal.FederalJudicialDistrict[source]

Bases: TemporalBoundary

US Federal Judicial District boundary.

94 districts organized into 12 regional circuits (plus DC and Federal). District boundaries follow state lines and sometimes county lines.

class Meta[source]

Bases: object

verbose_name = 'Federal Judicial District'
verbose_name_plural = 'Federal Judicial Districts'
unique_together = [('feature_id', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Persist the model, auto-populating feature_id and source.

*args/**kwargs: Forwarded to super().save().

GADM (Global Administrative Areas Database) boundary models.

Six-level administrative hierarchy from country through Admin5. All inherit from TemporalBoundary (NOT CensusTIGERBoundary, since GADM doesn’t use Census GEOIDs).

Preserves the enterprise dual-FK pattern: each child has both a Django FK to the parent model and a string gid_N field for parallel bulk loading (FK resolved after load via populate_parent_relationships()).

class siege_utilities.geo.django.models.gadm.GADMBoundary[source]

Bases: TemporalBoundary

Abstract base for all GADM boundaries.

GADM uses its own identifier scheme: GID_0 through GID_5. Source defaults to “GADM”.

class Meta[source]

Bases: object

abstract = True
ordering = ['gid']
save(*args, **kwargs)[source]

Sync feature_id from gid; default source to GADM.

class siege_utilities.geo.django.models.gadm.GADMCountry[source]

Bases: GADMBoundary

GADM Level 0 — Country.

Example: GID_0 = “USA”, NAME_0 = “United States”

class Meta[source]

Bases: object

verbose_name = 'GADM Country'
verbose_name_plural = 'GADM Countries'
unique_together = [('gid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.gadm.GADMAdmin1[source]

Bases: GADMBoundary

GADM Level 1 — First-level subdivision (state, province, region).

Example: GID_1 = “USA.5_1”, NAME_1 = “California”

country

alias of GADMCountry

class Meta[source]

Bases: object

verbose_name = 'GADM Admin Level 1'
verbose_name_plural = 'GADM Admin Level 1'
unique_together = [('gid', 'vintage_year')]
populate_parent_relationships()[source]

Resolve country FK from gid_0_string.

class siege_utilities.geo.django.models.gadm.GADMAdmin2[source]

Bases: GADMBoundary

GADM Level 2 — Second-level subdivision (county, department, district).

Example: GID_2 = “USA.5.37_1”, NAME_2 = “Los Angeles”

admin1

alias of GADMAdmin1

class Meta[source]

Bases: object

verbose_name = 'GADM Admin Level 2'
verbose_name_plural = 'GADM Admin Level 2'
unique_together = [('gid', 'vintage_year')]
populate_parent_relationships()[source]

Resolve admin1 FK from gid_1_string.

class siege_utilities.geo.django.models.gadm.GADMAdmin3[source]

Bases: GADMBoundary

GADM Level 3 — Third-level subdivision.

Example: GID_3 = “USA.5.37.1_1”

admin2

alias of GADMAdmin2

class Meta[source]

Bases: object

verbose_name = 'GADM Admin Level 3'
verbose_name_plural = 'GADM Admin Level 3'
unique_together = [('gid', 'vintage_year')]
populate_parent_relationships()[source]

Resolve admin2 FK from gid_2_string.

class siege_utilities.geo.django.models.gadm.GADMAdmin4[source]

Bases: GADMBoundary

GADM Level 4 — Fourth-level subdivision.

admin3

alias of GADMAdmin3

class Meta[source]

Bases: object

verbose_name = 'GADM Admin Level 4'
verbose_name_plural = 'GADM Admin Level 4'
unique_together = [('gid', 'vintage_year')]
populate_parent_relationships()[source]

Resolve admin3 FK from gid_3_string.

class siege_utilities.geo.django.models.gadm.GADMAdmin5[source]

Bases: GADMBoundary

GADM Level 5 — Fifth-level subdivision (finest granularity).

admin4

alias of GADMAdmin4

class Meta[source]

Bases: object

verbose_name = 'GADM Admin Level 5'
verbose_name_plural = 'GADM Admin Level 5'
unique_together = [('gid', 'vintage_year')]
populate_parent_relationships()[source]

Resolve admin4 FK from gid_4_string.

Boundary intersection models — pre-computed spatial overlaps.

Generic BoundaryIntersection stores any pair of overlapping boundaries. Typed convenience models (CountyCDIntersection, VTDCDIntersection, TractCDIntersection) add FK constraints and dominant-boundary tracking for the most commonly-queried intersection pairs.

class siege_utilities.geo.django.models.intersections.BoundaryIntersection[source]

Bases: Model

Generic intersection between any two boundary types.

Records the area overlap (intersection geometry is NOT stored — too expensive for millions of rows; use PostGIS ST_Intersection on the fly). Source/target types are string-typed for maximum flexibility.

Example

BoundaryIntersection(

source_type=’county’, source_boundary_id=’06037’, target_type=’cd’, target_boundary_id=’0614’, vintage_year=2020, intersection_area=12345678, pct_of_source=0.45, pct_of_target=0.72,

)

class Meta[source]

Bases: object

verbose_name = 'Boundary Intersection'
verbose_name_plural = 'Boundary Intersections'
unique_together = [('source_type', 'source_boundary_id', 'target_type', 'target_boundary_id', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index, django.db.models.Index]
class siege_utilities.geo.django.models.intersections.CountyCDIntersection[source]

Bases: Model

Pre-computed County ↔ Congressional District intersection.

Typed model with FKs for the most commonly-queried pair. Matches the enterprise pattern: pct_of_county, pct_of_cd, is_dominant.

county

alias of County

congressional_district

alias of CongressionalDistrict

class Meta[source]

Bases: object

verbose_name = 'County-CD Intersection'
verbose_name_plural = 'County-CD Intersections'
unique_together = [('county', 'congressional_district', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.intersections.VTDCDIntersection[source]

Bases: Model

Pre-computed VTD ↔ Congressional District intersection.

vtd

alias of VTD

congressional_district

alias of CongressionalDistrict

class Meta[source]

Bases: object

verbose_name = 'VTD-CD Intersection'
verbose_name_plural = 'VTD-CD Intersections'
unique_together = [('vtd', 'congressional_district', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.intersections.TractCDIntersection[source]

Bases: Model

Pre-computed Tract ↔ Congressional District intersection.

tract

alias of Tract

congressional_district

alias of CongressionalDistrict

class Meta[source]

Bases: object

verbose_name = 'Tract-CD Intersection'
verbose_name_plural = 'Tract-CD Intersections'
unique_together = [('tract', 'congressional_district', 'vintage_year')]
indexes = [django.db.models.Index]

Isochrone result model.

Stores computed isochrone polygons (travel-time contours) in PostGIS for caching, spatial joins, and historical analysis. Each record represents a single isochrone polygon for a given origin point, travel time, profile, and provider.

class siege_utilities.geo.django.models.isochrone.IsochroneResult[source]

Bases: TemporalBoundary

Cached isochrone polygon from an external routing provider.

Avoids repeated API calls by storing computed isochrones in PostGIS. Supports spatial queries like “which tracts are within 15 min of X?”

Data source: OpenRouteService, Valhalla, or other isochrone providers.

Example

>>> iso = IsochroneResult.objects.get(
...     travel_minutes=15,
...     profile="driving-car",
... )
>>> iso.geometry  # MultiPolygon of the 15-min driving contour
class Meta[source]

Bases: object

verbose_name = 'Isochrone Result'
verbose_name_plural = 'Isochrone Results'
unique_together = [('origin_point', 'travel_minutes', 'profile', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Sync boundary_id from feature_id.

NLRB case, election, ULP, and bargaining unit Django models.

Non-spatial models for NLRB case data. These link to NLRBRegion via FK but don’t carry their own geometry.

class siege_utilities.geo.django.models.nlrb_cases.NLRBCase[source]

Bases: Model

An NLRB case (representation petition, ULP charge, etc.).

region = 'NLRBRegion'
class Meta[source]

Bases: object

verbose_name = 'NLRB Case'
verbose_name_plural = 'NLRB Cases'
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
to_record()[source]

Convert to NLRBCaseRecord dataclass.

classmethod from_record(record)[source]

Create model instance from NLRBCaseRecord dataclass.

class siege_utilities.geo.django.models.nlrb_cases.ElectionResult[source]

Bases: Model

Election tally result for an NLRB representation case.

case

alias of NLRBCase

class Meta[source]

Bases: object

verbose_name = 'Election Result'
verbose_name_plural = 'Election Results'
indexes = [django.db.models.Index]
to_record()[source]
class siege_utilities.geo.django.models.nlrb_cases.ULPCharge[source]

Bases: Model

Unfair labor practice charge filed with the NLRB.

case

alias of NLRBCase

class Meta[source]

Bases: object

verbose_name = 'ULP Charge'
verbose_name_plural = 'ULP Charges'
indexes = [django.db.models.Index]
to_record()[source]
class siege_utilities.geo.django.models.nlrb_cases.BargainingUnit[source]

Bases: Model

Bargaining unit description with industry/occupation codes.

case

alias of NLRBCase

class Meta[source]

Bases: object

verbose_name = 'Bargaining Unit'
verbose_name_plural = 'Bargaining Units'
indexes = [django.db.models.Index]

Political Census boundary models.

State legislative districts (upper/lower chambers), voting tabulation districts (VTDs), and precincts. All inherit from CensusTIGERBoundary.

class siege_utilities.geo.django.models.political.StateLegislativeUpper[source]

Bases: CensusTIGERBoundary

State Legislative District — Upper Chamber (Senate).

GEOID: 5 digits = state (2) + district (3) Example: “06001” for California State Senate District 1

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'State Legislative District (Upper)'
verbose_name_plural = 'State Legislative Districts (Upper)'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.political.StateLegislativeLower[source]

Bases: CensusTIGERBoundary

State Legislative District — Lower Chamber (House/Assembly).

GEOID: 5 digits = state (2) + district (3) Example: “06001” for California State Assembly District 1

state

alias of State

class Meta[source]

Bases: object

verbose_name = 'State Legislative District (Lower)'
verbose_name_plural = 'State Legislative Districts (Lower)'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

class siege_utilities.geo.django.models.political.VTD[source]

Bases: CensusTIGERBoundary

Voting Tabulation District (VTD) boundary.

VTDs are the smallest geographic units for which Census tabulates voting-age population data. They approximate but don’t exactly match election precincts.

GEOID: 11 digits = state (2) + county (3) + VTD (6) Example: “06037001234” for VTD 001234 in LA County, CA

Enhanced fields support enterprise-compatible workflows: - precinct_name/precinct_code for election data linkage - registered_voters for turnout analysis - data_source for provenance tracking - populate_parent_relationships() for FK resolution

state

alias of State

county

alias of County

congressional_district

alias of CongressionalDistrict

class Meta[source]

Bases: object

verbose_name = 'Voting Tabulation District'
verbose_name_plural = 'Voting Tabulation Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
constraints = [django.db.models.CheckConstraint]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

clean()[source]
populate_parent_relationships()[source]

Resolve parent FKs from FIPS codes.

Call after bulk loading to link VTDs to their parent State, County, and (optionally) CongressionalDistrict records.

class siege_utilities.geo.django.models.political.Precinct[source]

Bases: CensusTIGERBoundary

Election Precinct boundary.

Precincts are the fundamental unit of election administration. They may not align perfectly with Census VTDs. Stored as TIGER boundaries when sourced from Census, but precinct_source tracks actual provenance.

GEOID format varies by state; typically state (2) + county (3) + precinct code.

state

alias of State

county

alias of County

class Meta[source]

Bases: object

verbose_name = 'Precinct'
verbose_name_plural = 'Precincts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index, django.db.models.Index]
classmethod get_geoid_length()[source]

Return the expected GEOID length for this geography type.

Return type:

int

classmethod parse_geoid(geoid)[source]

Parse a GEOID into its component parts.

Parameters:

geoid (str)

Return type:

dict

Per-kind special-district boundary models.

Per downstream socialwarehouse design SW#207 Q2 = “one model per kind” (no parent SpecialDistrict model). Each Census special-district function gets its own concrete model. Downstream consumers cache the geoid in a per-kind field on Address (SW already shipped those caches via SW#191’s C-medium batch).

GEOID format varies by district type but is at most 10 digits in Census Special Districts file outputs. We use a shared 10-char CharField with a permissive regex.

The Census-published “function” code distinguishes kinds within the Special Districts file; this module realizes those into separate Django models for type-safety + reverse-accessor clarity from downstream consumers (an Address’s fire_protection_districts is unambiguous about which kind it returns).

class siege_utilities.geo.django.models.special_districts.SpecialDistrictBase[source]

Bases: CensusTIGERBoundary

Shared shape for all per-kind special-district models.

Abstract: each concrete kind below inherits and gets its own table.

class Meta[source]

Bases: object

abstract = True
class siege_utilities.geo.django.models.special_districts.FireProtectionDistrict[source]

Bases: SpecialDistrictBase

Fire protection special district. Census function code typically ‘24’.

class Meta[source]

Bases: object

verbose_name = 'Fire Protection District'
verbose_name_plural = 'Fire Protection Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.WaterSupplyDistrict[source]

Bases: SpecialDistrictBase

Water supply special district. Census function code typically ‘91’.

class Meta[source]

Bases: object

verbose_name = 'Water Supply District'
verbose_name_plural = 'Water Supply Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.HospitalDistrict[source]

Bases: SpecialDistrictBase

Hospital special district. Census function code typically ‘40’.

class Meta[source]

Bases: object

verbose_name = 'Hospital District'
verbose_name_plural = 'Hospital Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.LibraryDistrict[source]

Bases: SpecialDistrictBase

Library special district. Census function code typically ‘52’.

class Meta[source]

Bases: object

verbose_name = 'Library District'
verbose_name_plural = 'Library Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.CemeteryDistrict[source]

Bases: SpecialDistrictBase

Cemetery special district. Census function code typically ‘05’.

class Meta[source]

Bases: object

verbose_name = 'Cemetery District'
verbose_name_plural = 'Cemetery Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.MosquitoAbatementDistrict[source]

Bases: SpecialDistrictBase

Mosquito abatement special district. Census function code typically ‘63’.

class Meta[source]

Bases: object

verbose_name = 'Mosquito Abatement District'
verbose_name_plural = 'Mosquito Abatement Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]
class siege_utilities.geo.django.models.special_districts.OtherSpecialDistrict[source]

Bases: SpecialDistrictBase

Catch-all for special-district kinds not represented by a dedicated model. Downstream consumers can filter by function_code to scope to a specific kind that hasn’t graduated to its own model yet.

class Meta[source]

Bases: object

verbose_name = 'Other Special District'
verbose_name_plural = 'Other Special Districts'
unique_together = [('geoid', 'vintage_year')]
indexes = [django.db.models.Index]

Timezone boundary model.

Stores IANA timezone boundaries from the timezone-boundary-builder (https://github.com/evansiroky/timezone-boundary-builder) as PostGIS geometries with DST offset metadata.

class siege_utilities.geo.django.models.timezone.TimezoneGeometry[source]

Bases: TemporalBoundary

IANA timezone boundary geometry.

Data source: timezone-boundary-builder GeoJSON releases. Each record represents a single IANA timezone polygon for a given vintage year.

Example

>>> tz = TimezoneGeometry.objects.get(timezone_id='America/New_York')
>>> tz.utc_offset_std
Decimal('-5.0')
class Meta[source]

Bases: object

verbose_name = 'Timezone Geometry'
verbose_name_plural = 'Timezone Geometries'
unique_together = [('timezone_id', 'vintage_year')]
indexes = [django.db.models.Index, django.db.models.Index]
save(*args, **kwargs)[source]

Persist the model, auto-populating feature_id and source.

*args/**kwargs: Forwarded to super().save().

Managers

Custom managers for Census boundary models.

class siege_utilities.geo.django.managers.BoundaryManager[source]

Bases: Manager

Custom manager for temporal boundary models.

Provides convenient methods for common spatial queries.

Example

>>> from siege_utilities.geo.django.models import Tract
>>> point = Point(-122.4194, 37.7749, srid=4326)
>>> tract = Tract.objects.containing_point(point).for_year(2020).first()
get_queryset()[source]
for_year(year)[source]

Filter boundaries by vintage year.

Parameters:

year (int)

for_state(state_fips)[source]

Filter boundaries by state FIPS code.

Parameters:

state_fips (str)

current()[source]

Filter to currently-valid boundaries.

valid_on(date)[source]

Filter to boundaries valid on a specific date.

containing_point(point, srid=4326)[source]

Find boundaries containing a point.

Parameters:

srid (int)

intersecting(geometry)[source]

Find boundaries intersecting a geometry.

within_distance(point, distance_meters)[source]

Find boundaries within a distance of a point.

Parameters:

distance_meters (float)

nearest(point, max_distance_m=None, srid=4326)[source]

Find boundaries ordered by distance from a point.

Parameters:

srid (int)

get_by_geoid(geoid, year=None)[source]

Get a single boundary by GEOID.

Parameters:
  • geoid (str) – The full GEOID

  • year (int) – Optional vintage year (returns most recent if not specified)

Returns:

The boundary object

Raises:

DoesNotExist – If no matching boundary found

get_children(parent_geoid, year)[source]

Get child boundaries for a parent GEOID.

For example, get all counties in a state or all tracts in a county.

Parameters:
  • parent_geoid (str) – GEOID of the parent boundary

  • year (int) – Vintage year

Returns:

QuerySet of child boundaries

bulk_create_from_geodataframe(gdf, year, batch_size=1000, srid=4326)[source]

Bulk create boundaries from a GeoPandas GeoDataFrame.

Parameters:
  • gdf – GeoDataFrame with GEOID, NAME, geometry, and optionally ALAND/AWATER

  • year (int) – Vintage year for these boundaries

  • batch_size (int) – Number of records per batch

  • srid (int) – Spatial reference ID for the geometry (default: 4326 WGS 84)

Returns:

List of created boundary objects

class siege_utilities.geo.django.managers.BoundaryQuerySet[source]

Bases: QuerySet

Custom QuerySet for boundary models with spatial queries.

for_year(year)[source]

Filter boundaries by vintage year.

Parameters:

year (int)

for_state(state_fips)[source]

Filter boundaries by state FIPS code.

Parameters:

state_fips (str)

current()[source]

Filter to currently-valid boundaries (valid_to is NULL).

valid_on(date)[source]

Filter to boundaries valid on a specific date.

containing_point(point, srid=4326)[source]

Find boundaries containing a point.

Parameters:
  • point (django.contrib.gis.geos.Point) – A GEOS Point object or (lon, lat) tuple

  • srid (int) – Spatial reference ID (default: 4326 WGS 84)

Returns:

QuerySet filtered to boundaries containing the point

intersecting(geometry)[source]

Find boundaries intersecting a geometry.

Parameters:

geometry (django.contrib.gis.geos.GEOSGeometry) – Any GEOS geometry

Returns:

QuerySet filtered to intersecting boundaries

within_distance(point, distance_meters)[source]

Find boundaries within a distance of a point.

Uses geodesic distance via PostGIS ST_DWithin(geography).

Parameters:
  • point (django.contrib.gis.geos.Point) – A GEOS Point object

  • distance_meters (float) – Distance in meters

Returns:

QuerySet filtered to boundaries within distance

nearest(point, max_distance_m=None, srid=4326)[source]

Find boundaries ordered by distance from a point (nearest first).

Uses geodesic distance via PostGIS ST_Distance(geography).

Parameters:
  • point – A GEOS Point object or (lon, lat) tuple

  • max_distance_m – Optional max distance in meters to filter results

  • srid (int) – Spatial reference ID for tuple input (default: 4326 WGS 84)

Returns:

QuerySet annotated with ‘distance’ and ordered nearest-first

with_area()[source]

Annotate each boundary with its calculated area.

Returns:

QuerySet with ‘calculated_area’ annotation (square meters)

by_land_area(ascending=True)[source]

Order boundaries by land area.

Parameters:

ascending (bool) – True for smallest first, False for largest first

Returns:

Ordered QuerySet

by_population(year=None, ascending=False)[source]

Order boundaries by population.

Requires DemographicSnapshot records to be joined.

Parameters:
  • year (int) – Census year for population data

  • ascending (bool) – True for smallest first, False for largest first

Returns:

Ordered QuerySet (may be empty if no demographics loaded)

geojson()[source]

Return boundaries as a GeoJSON FeatureCollection dict.

GeoJSON is always WGS 84 (EPSG:4326) per RFC 7946. The global default CRS set via set_default_crs() does not apply here — use isochrone_to_geodataframe() or convert the result to a GeoDataFrame if you need reprojection.

Returns:

4326).

Return type:

GeoJSON FeatureCollection dict (always EPSG

Custom manager for temporal boundary models with spatial query helpers.

class siege_utilities.geo.django.managers.boundary_manager.BoundaryQuerySet[source]

Bases: QuerySet

Custom QuerySet for boundary models with spatial queries.

for_year(year)[source]

Filter boundaries by vintage year.

Parameters:

year (int)

for_state(state_fips)[source]

Filter boundaries by state FIPS code.

Parameters:

state_fips (str)

current()[source]

Filter to currently-valid boundaries (valid_to is NULL).

valid_on(date)[source]

Filter to boundaries valid on a specific date.

containing_point(point, srid=4326)[source]

Find boundaries containing a point.

Parameters:
  • point (django.contrib.gis.geos.Point) – A GEOS Point object or (lon, lat) tuple

  • srid (int) – Spatial reference ID (default: 4326 WGS 84)

Returns:

QuerySet filtered to boundaries containing the point

intersecting(geometry)[source]

Find boundaries intersecting a geometry.

Parameters:

geometry (django.contrib.gis.geos.GEOSGeometry) – Any GEOS geometry

Returns:

QuerySet filtered to intersecting boundaries

within_distance(point, distance_meters)[source]

Find boundaries within a distance of a point.

Uses geodesic distance via PostGIS ST_DWithin(geography).

Parameters:
  • point (django.contrib.gis.geos.Point) – A GEOS Point object

  • distance_meters (float) – Distance in meters

Returns:

QuerySet filtered to boundaries within distance

nearest(point, max_distance_m=None, srid=4326)[source]

Find boundaries ordered by distance from a point (nearest first).

Uses geodesic distance via PostGIS ST_Distance(geography).

Parameters:
  • point – A GEOS Point object or (lon, lat) tuple

  • max_distance_m – Optional max distance in meters to filter results

  • srid (int) – Spatial reference ID for tuple input (default: 4326 WGS 84)

Returns:

QuerySet annotated with ‘distance’ and ordered nearest-first

with_area()[source]

Annotate each boundary with its calculated area.

Returns:

QuerySet with ‘calculated_area’ annotation (square meters)

by_land_area(ascending=True)[source]

Order boundaries by land area.

Parameters:

ascending (bool) – True for smallest first, False for largest first

Returns:

Ordered QuerySet

by_population(year=None, ascending=False)[source]

Order boundaries by population.

Requires DemographicSnapshot records to be joined.

Parameters:
  • year (int) – Census year for population data

  • ascending (bool) – True for smallest first, False for largest first

Returns:

Ordered QuerySet (may be empty if no demographics loaded)

geojson()[source]

Return boundaries as a GeoJSON FeatureCollection dict.

GeoJSON is always WGS 84 (EPSG:4326) per RFC 7946. The global default CRS set via set_default_crs() does not apply here — use isochrone_to_geodataframe() or convert the result to a GeoDataFrame if you need reprojection.

Returns:

4326).

Return type:

GeoJSON FeatureCollection dict (always EPSG

class siege_utilities.geo.django.managers.boundary_manager.BoundaryManager[source]

Bases: Manager

Custom manager for temporal boundary models.

Provides convenient methods for common spatial queries.

Example

>>> from siege_utilities.geo.django.models import Tract
>>> point = Point(-122.4194, 37.7749, srid=4326)
>>> tract = Tract.objects.containing_point(point).for_year(2020).first()
get_queryset()[source]
for_year(year)[source]

Filter boundaries by vintage year.

Parameters:

year (int)

for_state(state_fips)[source]

Filter boundaries by state FIPS code.

Parameters:

state_fips (str)

current()[source]

Filter to currently-valid boundaries.

valid_on(date)[source]

Filter to boundaries valid on a specific date.

containing_point(point, srid=4326)[source]

Find boundaries containing a point.

Parameters:

srid (int)

intersecting(geometry)[source]

Find boundaries intersecting a geometry.

within_distance(point, distance_meters)[source]

Find boundaries within a distance of a point.

Parameters:

distance_meters (float)

nearest(point, max_distance_m=None, srid=4326)[source]

Find boundaries ordered by distance from a point.

Parameters:

srid (int)

get_by_geoid(geoid, year=None)[source]

Get a single boundary by GEOID.

Parameters:
  • geoid (str) – The full GEOID

  • year (int) – Optional vintage year (returns most recent if not specified)

Returns:

The boundary object

Raises:

DoesNotExist – If no matching boundary found

get_children(parent_geoid, year)[source]

Get child boundaries for a parent GEOID.

For example, get all counties in a state or all tracts in a county.

Parameters:
  • parent_geoid (str) – GEOID of the parent boundary

  • year (int) – Vintage year

Returns:

QuerySet of child boundaries

bulk_create_from_geodataframe(gdf, year, batch_size=1000, srid=4326)[source]

Bulk create boundaries from a GeoPandas GeoDataFrame.

Parameters:
  • gdf – GeoDataFrame with GEOID, NAME, geometry, and optionally ALAND/AWATER

  • year (int) – Vintage year for these boundaries

  • batch_size (int) – Number of records per batch

  • srid (int) – Spatial reference ID for the geometry (default: 4326 WGS 84)

Returns:

List of created boundary objects

Services

Services for populating Census boundary and NCES education data.

Imports are deferred to avoid requiring Django setup at import time. Use: from siege_utilities.geo.django.services.nces_service import NCESPopulationService

Service for populating boundary crosswalk data.

Uses the existing siege_utilities.geo.crosswalk module to load crosswalk relationships into Django models.

class siege_utilities.geo.django.services.crosswalk_service.CrosswalkPopulationResult[source]

Bases: object

Result of a crosswalk population operation.

geography_type: str
source_year: int
target_year: int
state_fips: str | None
records_created: int
records_updated: int
records_skipped: int
errors: list[str]
property total_processed: int
property success: bool
__init__(geography_type, source_year, target_year, state_fips, records_created, records_updated, records_skipped, errors)
Parameters:
  • geography_type (str)

  • source_year (int)

  • target_year (int)

  • state_fips (str | None)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list[str])

Return type:

None

class siege_utilities.geo.django.services.crosswalk_service.CrosswalkPopulationService[source]

Bases: object

Service for populating boundary crosswalk data.

This service integrates with the existing siege_utilities.geo.crosswalk module to load crosswalk relationships into TemporalCrosswalk models.

Example

>>> service = CrosswalkPopulationService()
>>> result = service.populate(
...     geography_type='tract',
...     source_year=2010,
...     target_year=2020,
...     state_fips='06'
... )
>>> print(f"Created {result.records_created} crosswalk records")
__init__(cache_dir=None)[source]

Initialize the service.

Parameters:

cache_dir (str | None) – Directory for caching downloaded crosswalk files

populate(geography_type, source_year, target_year, state_fips=None, weight_type='population', update_existing=False, batch_size=1000)

Populate crosswalk data for a geography type.

Parameters:
  • geography_type (str) – Type of geography (tract, blockgroup, etc.)

  • source_year (int) – Source Census year (earlier)

  • target_year (int) – Target Census year (later)

  • state_fips (str | None) – Optional state filter

  • weight_type (str) – Type of weight (population, housing, area)

  • update_existing (bool) – If True, update existing records; otherwise skip

  • batch_size (int) – Number of records per database batch

Returns:

CrosswalkPopulationResult with statistics

Return type:

CrosswalkPopulationResult

populate_tract_crosswalk(source_year=2010, target_year=2020, state_fips=None, **kwargs)[source]

Populate tract crosswalk data.

Parameters:
  • source_year (int)

  • target_year (int)

  • state_fips (str | None)

Return type:

CrosswalkPopulationResult

populate_block_group_crosswalk(source_year=2010, target_year=2020, state_fips=None, **kwargs)[source]

Populate block group crosswalk data.

Parameters:
  • source_year (int)

  • target_year (int)

  • state_fips (str | None)

Return type:

CrosswalkPopulationResult

populate_county_crosswalk(source_year=2010, target_year=2020, state_fips=None, **kwargs)[source]

Populate county crosswalk data.

Parameters:
  • source_year (int)

  • target_year (int)

  • state_fips (str | None)

Return type:

CrosswalkPopulationResult

Service for populating demographic data from Census API.

Uses the existing siege_utilities.geo.census_api_client module to fetch demographic data, then loads it into Django models.

class siege_utilities.geo.django.services.demographic_service.DemographicPopulationResult[source]

Bases: object

Result of a demographic population operation.

geography_type: str
year: int
dataset: str
state_fips: str | None
variable_group: str
records_created: int
records_updated: int
records_skipped: int
errors: list[str]
property total_processed: int
property success: bool
__init__(geography_type, year, dataset, state_fips, variable_group, records_created, records_updated, records_skipped, errors)
Parameters:
  • geography_type (str)

  • year (int)

  • dataset (str)

  • state_fips (str | None)

  • variable_group (str)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list[str])

Return type:

None

class siege_utilities.geo.django.services.demographic_service.DemographicPopulationService[source]

Bases: object

Service for populating demographic data from Census API.

This service integrates with the existing siege_utilities.geo.census_api_client to fetch demographic data and store it in DemographicSnapshot models.

Example

>>> service = DemographicPopulationService(api_key='your-key')
>>> result = service.populate(
...     geography_type='tract',
...     year=2022,
...     state_fips='06',
...     variable_group='income'
... )
>>> print(f"Created {result.records_created} demographic snapshots")
CENSUS_GEOGRAPHIES = {'blockgroup': 'block group:*', 'county': 'county:*', 'place': 'place:*', 'state': 'state:*', 'tract': 'tract:*'}
__init__(api_key=None, cache_dir=None)[source]

Initialize the service.

Parameters:
  • api_key (str | None) – Census API key (or use CENSUS_API_KEY env var)

  • cache_dir (str | None) – Directory for caching API responses

property client

Lazy-load the Census API client.

populate(geography_type, year, state_fips, variable_group='demographics_basic', dataset='acs5', update_existing=False, batch_size=500)

Populate demographic data for boundaries.

Parameters:
  • geography_type (str) – Type of geography (state, county, tract, etc.)

  • year (int) – ACS/Census year

  • state_fips (str) – State FIPS code

  • variable_group (str) – Variable group name (income, education, housing, etc.)

  • dataset (str) – Census dataset (acs5, acs1, dec)

  • update_existing (bool) – If True, update existing records; otherwise skip

  • batch_size (int) – Number of records per database batch

Returns:

DemographicPopulationResult with statistics

Return type:

DemographicPopulationResult

populate_income(geography_type, year, state_fips, **kwargs)[source]

Populate income demographic data.

Parameters:
  • geography_type (str)

  • year (int)

  • state_fips (str)

Return type:

DemographicPopulationResult

populate_education(geography_type, year, state_fips, **kwargs)[source]

Populate education demographic data.

Parameters:
  • geography_type (str)

  • year (int)

  • state_fips (str)

Return type:

DemographicPopulationResult

populate_housing(geography_type, year, state_fips, **kwargs)[source]

Populate housing demographic data.

Parameters:
  • geography_type (str)

  • year (int)

  • state_fips (str)

Return type:

DemographicPopulationResult

populate_race_ethnicity(geography_type, year, state_fips, **kwargs)[source]

Populate race/ethnicity demographic data.

Parameters:
  • geography_type (str)

  • year (int)

  • state_fips (str)

Return type:

DemographicPopulationResult

populate_all_groups(geography_type, year, state_fips, groups=None, **kwargs)[source]

Populate multiple demographic variable groups.

Parameters:
  • geography_type (str) – Type of geography

  • year (int) – ACS year

  • state_fips (str) – State FIPS code

  • groups (list[str] | None) – List of variable groups (defaults to all standard groups)

  • **kwargs – Additional arguments passed to populate()

Returns:

List of results for each group

Return type:

list[DemographicPopulationResult]

Service for computing and caching isochrone results in PostGIS.

Wraps the siege_utilities.geo.isochrones module to fetch isochrone polygons from routing providers and store them as IsochroneResult model instances for spatial queries and historical analysis.

class siege_utilities.geo.django.services.isochrone_service.IsochroneComputeResult[source]

Bases: object

Result of an isochrone compute-and-store operation.

records_created: int = 0
records_cached: int = 0
errors: list
property success: bool
__init__(records_created=0, records_cached=0, errors=<factory>)
Parameters:
  • records_created (int)

  • records_cached (int)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.isochrone_service.IsochroneComputeService[source]

Bases: object

Compute isochrones via external providers and cache in PostGIS.

Example

>>> service = IsochroneComputeService()
>>> result = service.compute_and_store(
...     latitude=41.8781, longitude=-87.6298,
...     travel_minutes=15, profile="driving-car",
... )
>>> print(f"Created: {result.records_created}, Cached: {result.records_cached}")
compute_and_store(latitude, longitude, travel_minutes, profile='driving-car', provider='openrouteservice', vintage_year=None, api_key=None, base_url=None, max_age_days=None, **provider_kwargs)[source]

Fetch an isochrone from a provider and store it in PostGIS.

If a cached result exists within max_age_days, returns the cached record without making an API call.

Parameters:
  • latitude (float) – Origin latitude.

  • longitude (float) – Origin longitude.

  • travel_minutes (int) – Travel time in minutes.

  • profile (str) – Routing profile (e.g. “driving-car”, “foot-walking”).

  • provider (str) – Isochrone provider (“openrouteservice” or “valhalla”).

  • vintage_year (int | None) – Year to associate with this isochrone (default: current year).

  • api_key (str | None) – API key for the provider (if required).

  • base_url (str | None) – Custom base URL for self-hosted providers.

  • max_age_days (int | None) – If set, return cached result if younger than this many days.

  • **provider_kwargs – Extra parameters forwarded to get_isochrone().

Returns:

IsochroneComputeResult with created/cached counts.

Return type:

IsochroneComputeResult

get_cached(latitude, longitude, travel_minutes, profile='driving-car', max_age_days=None)[source]

Retrieve a cached isochrone result if available.

Returns the IsochroneResult instance or None.

Parameters:
  • latitude (float)

  • longitude (float)

  • travel_minutes (int)

  • profile (str)

  • max_age_days (int | None)

Service for populating NCES education data into Django models.

Downloads NCES locale boundaries, school locations, and district data using NCESDownloader, then loads into NCESLocaleBoundary, SchoolLocation, and enriches existing SchoolDistrict* records with locale codes.

class siege_utilities.geo.django.services.nces_service.NCESPopulationResult[source]

Bases: object

Result of an NCES population operation.

action: str
year: int
records_created: int = 0
records_updated: int = 0
records_skipped: int = 0
errors: list[str]
property total_processed: int
property success: bool
__init__(action, year, records_created=0, records_updated=0, records_skipped=0, errors=<factory>)
Parameters:
  • action (str)

  • year (int)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list[str])

Return type:

None

class siege_utilities.geo.django.services.nces_service.NCESPopulationService[source]

Bases: object

Service for downloading and populating NCES education data.

Three actions: 1. populate_locale_boundaries — download 12 territory polygons per year 2. populate_school_locations — download geocoded school points 3. enrich_school_districts — match NCES data to existing district records

Example:

service = NCESPopulationService(cache_dir="/data/nces")
result = service.populate_locale_boundaries(year=2023)
print(f"Created {result.records_created} locale boundaries")
__init__(cache_dir=None)[source]
Parameters:

cache_dir (str | None)

populate_locale_boundaries(year=2023, update_existing=False, batch_size=100)

Download and populate NCESLocaleBoundary records.

Parameters:
  • year (int) – NCES publication year.

  • update_existing (bool) – If True, update existing records.

  • batch_size (int) – Records per database batch.

Returns:

NCESPopulationResult with statistics.

Return type:

NCESPopulationResult

populate_school_locations(year=2023, state_fips=None, update_existing=False, batch_size=500)

Download and populate SchoolLocation records.

Parameters:
  • year (int) – NCES publication year.

  • state_fips (str | None) – 2-digit FIPS code or 2-letter abbreviation to filter.

  • update_existing (bool) – If True, update existing records.

  • batch_size (int) – Records per database batch.

Returns:

NCESPopulationResult with statistics.

Return type:

NCESPopulationResult

enrich_school_districts(year=2023, update_existing=False, batch_size=500)[source]

Enrich existing SchoolDistrict* records with NCES locale codes.

Matches NCES district administrative data to existing Django SchoolDistrictElementary/Secondary/Unified records by LEA ID, then updates locale_code, locale_category, and locale_subcategory.

Parameters:
  • year (int) – NCES publication year.

  • update_existing (bool) – If True, overwrite existing locale codes.

  • batch_size (int)

Returns:

NCESPopulationResult with statistics.

Return type:

NCESPopulationResult

Service for populating NLRB (National Labor Relations Board) region data.

NLRB divides the US into 26 active regions (numbered 1-31 with gaps). Boundaries are county-based and change infrequently. Since the NLRB does not publish shapefiles, this service builds region geometries by dissolving county boundaries for the counties assigned to each region.

Data source: NLRB region-to-county mapping (manual maintenance).

class siege_utilities.geo.django.services.nlrb_service.NLRBPopulationResult[source]

Bases: object

Result of an NLRB region population run.

records_created: int = 0
records_updated: int = 0
records_skipped: int = 0
errors: list
property success: bool
__init__(records_created=0, records_updated=0, records_skipped=0, errors=<factory>)
Parameters:
  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.nlrb_service.NLRBPopulationService[source]

Bases: object

Populate NLRBRegion model instances from the built-in region registry.

Since the NLRB does not publish GIS shapefiles, this service creates region records with state-level metadata. When county boundaries are available in the database, it can optionally dissolve them into region geometries.

Example

>>> service = NLRBPopulationService()
>>> result = service.populate(vintage_year=2024)
>>> print(f"Created {result.records_created} regions")
populate(vintage_year=2024, update_existing=False, dissolve_counties=False)[source]

Populate NLRBRegion records from the built-in registry.

Parameters:
  • vintage_year (int) – Vintage year for the records.

  • update_existing (bool) – If True, update existing records instead of skipping.

  • dissolve_counties (bool) – If True, attempt to build region geometry by dissolving county boundaries (requires populated County model).

Returns:

NLRBPopulationResult with counts.

Return type:

NLRBPopulationResult

Service for populating Census boundary data from TIGER/Line shapefiles.

Uses the existing siege_utilities.geo.spatial_data module to download boundaries, then loads them into Django models.

class siege_utilities.geo.django.services.population_service.PopulationResult[source]

Bases: object

Result of a boundary population operation.

geography_type: str
year: int
state_fips: str | None
records_created: int
records_updated: int
records_skipped: int
errors: list[str]
property total_processed: int
property success: bool
__init__(geography_type, year, state_fips, records_created, records_updated, records_skipped, errors)
Parameters:
  • geography_type (str)

  • year (int)

  • state_fips (str | None)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list[str])

Return type:

None

class siege_utilities.geo.django.services.population_service.BoundaryPopulationService[source]

Bases: object

Service for populating Census boundary models from TIGER/Line data.

This service integrates with the existing siege_utilities.geo module to download TIGER/Line shapefiles and load them into Django models.

Example

>>> service = BoundaryPopulationService()
>>> result = service.populate_counties(year=2020, state_fips='06')
>>> print(f"Created {result.records_created} counties")
>>> # Or use the generic method
>>> result = service.populate(
...     geography_type='tract',
...     year=2020,
...     state_fips='06'
... )
GEOGRAPHY_MODELS = {'block': 'Block', 'blockgroup': 'BlockGroup', 'cbsa': 'CBSA', 'cd': 'CongressionalDistrict', 'county': 'County', 'elsd': 'SchoolDistrictElementary', 'place': 'Place', 'precinct': 'Precinct', 'scsd': 'SchoolDistrictSecondary', 'sldl': 'StateLegislativeLower', 'sldu': 'StateLegislativeUpper', 'state': 'State', 'tract': 'Tract', 'unsd': 'SchoolDistrictUnified', 'urbanarea': 'UrbanArea', 'vtd': 'VTD', 'zcta': 'ZCTA'}
TIGER_TYPES = {'block': 'TABBLOCK', 'blockgroup': 'BG', 'cbsa': 'CBSA', 'cd': 'CD', 'county': 'COUNTY', 'elsd': 'ELSD', 'place': 'PLACE', 'scsd': 'SCSD', 'sldl': 'SLDL', 'sldu': 'SLDU', 'state': 'STATE', 'tract': 'TRACT', 'unsd': 'UNSD', 'urbanarea': 'UA', 'vtd': 'VTD', 'zcta': 'ZCTA5'}
__init__(cache_dir=None)[source]

Initialize the service.

Parameters:

cache_dir (str | None) – Directory for caching downloaded shapefiles

populate(geography_type, year, state_fips=None, update_existing=False, batch_size=500)

Populate boundary data for a geography type.

Parameters:
  • geography_type (str) – Type of geography (state, county, tract, etc.)

  • year (int) – Census year

  • state_fips (str | None) – State FIPS code (required for sub-state geographies)

  • update_existing (bool) – If True, update existing records; otherwise skip

  • batch_size (int) – Number of records per database batch

Returns:

PopulationResult with statistics

Return type:

PopulationResult

populate_states(year, **kwargs)[source]

Populate state boundaries.

Parameters:

year (int)

Return type:

PopulationResult

populate_counties(year, state_fips=None, **kwargs)[source]

Populate county boundaries.

Parameters:
  • year (int)

  • state_fips (str | None)

Return type:

PopulationResult

populate_tracts(year, state_fips, **kwargs)[source]

Populate tract boundaries (requires state).

Parameters:
Return type:

PopulationResult

populate_block_groups(year, state_fips, **kwargs)[source]

Populate block group boundaries (requires state).

Parameters:
Return type:

PopulationResult

populate_blocks(year, state_fips, **kwargs)[source]

Populate block boundaries (requires state).

Parameters:
Return type:

PopulationResult

populate_places(year, state_fips=None, **kwargs)[source]

Populate place boundaries.

Parameters:
  • year (int)

  • state_fips (str | None)

Return type:

PopulationResult

populate_zctas(year, **kwargs)[source]

Populate ZCTA boundaries.

Parameters:

year (int)

Return type:

PopulationResult

populate_congressional_districts(year, state_fips=None, **kwargs)[source]

Populate congressional district boundaries.

Parameters:
  • year (int)

  • state_fips (str | None)

Return type:

PopulationResult

Link child boundaries to their parent boundaries.

For example, link counties to states, tracts to counties, etc.

Parameters:
  • geography_type (str) – Type of child geography

  • year (int) – Census year

  • state_fips (str | None) – Optional state filter

Returns:

Number of relationships linked

Return type:

int

Service for loading Redistricting Data Hub data into Django models.

Orchestrates fetching data from RDH (via the data client), computing compactness scores, overlaying demographics, and persisting to the redistricting Django models.

class siege_utilities.geo.django.services.rdh_loader.RDHLoadResult[source]

Bases: object

Result of an RDH load operation.

operation: str
records_created: int = 0
records_updated: int = 0
records_skipped: int = 0
errors: list[str]
property total_processed: int
property success: bool
__init__(operation, records_created=0, records_updated=0, records_skipped=0, errors=<factory>)
Parameters:
  • operation (str)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list[str])

Return type:

None

class siege_utilities.geo.django.services.rdh_loader.RDHLoaderService[source]

Bases: object

Loads RDH data into Django redistricting models.

Example:

from siege_utilities.geo.django.services.rdh_loader import RDHLoaderService

service = RDHLoaderService()
plan = service.load_enacted_plan("VA", "congress", 2020)
service.compute_plan_compactness(plan)
count = service.load_demographics_for_plan(plan, acs_year=2022)
__init__(client=None)[source]
Parameters:

client (RDHClient, optional) – Pre-configured RDH client. Created from env vars if omitted.

load_enacted_plan(state, chamber, cycle_year, year=None)

Fetch and load an enacted redistricting plan with all districts.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • chamber (str) – "congress", "state_senate", or "state_house".

  • cycle_year (int) – Redistricting cycle (e.g. 2020).

  • year (str, optional) – Year filter for the RDH query.

Returns:

The created or updated plan instance.

Return type:

RedistrictingPlan

load_precinct_results(state, election_year, year=None, plan=None)

Fetch and load precinct election results.

Parameters:
  • state (str) – Two-letter state abbreviation.

  • election_year (int) – Election year.

  • year (str, optional) – Year filter for RDH query.

  • plan (RedistrictingPlan, optional) – Link results to a specific plan.

Return type:

RDHLoadResult

load_cvap_for_plan(plan, year)

Overlay CVAP data onto plan districts via spatial join.

Parameters:
Return type:

RDHLoadResult

load_demographics_for_plan(plan, acs_year, census_gdf=None)

Overlay ACS demographics onto plan districts via spatial join.

Parameters:
  • plan (RedistrictingPlan) – Plan to populate.

  • acs_year (int) – ACS 5-year data year.

  • census_gdf (GeoDataFrame, optional) – Pre-loaded Census data. If omitted, fetches from RDH.

Return type:

RDHLoadResult

compute_plan_compactness(plan)[source]

Compute and store compactness scores for all districts in a plan.

Parameters:

plan (RedistrictingPlan) – Plan whose districts will be scored.

Return type:

None

Service for demographic roll-up aggregation across geography levels.

Aggregates demographic data from a finer geography (e.g., tracts) to a coarser geography (e.g., counties) using GEOID prefix hierarchy or temporal crosswalk mappings. Supports sum, avg, and weighted_avg (population-weighted) operations.

class siege_utilities.geo.django.services.rollup_service.RollupResult[source]

Bases: object

Result of a rollup aggregation run.

source_level: str
target_level: str
variable_code: str
records_created: int = 0
records_skipped: int = 0
coverage_ratio: float = 1.0
errors: list
property success: bool
__init__(source_level, target_level, variable_code, records_created=0, records_skipped=0, coverage_ratio=1.0, errors=<factory>)
Parameters:
  • source_level (str)

  • target_level (str)

  • variable_code (str)

  • records_created (int)

  • records_skipped (int)

  • coverage_ratio (float)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.rollup_service.DemographicRollupService[source]

Bases: object

Aggregate demographic values from child geographies to parent geographies.

Uses GEOID prefix hierarchy for parent resolution: - Block (15) → Tract (11) → County (5) → State (2) - BlockGroup (12) → Tract (11) → County (5) → State (2)

Supports operations: - sum: Sum of child values (e.g., population) - avg: Simple average of child values - weighted_avg: Population-weighted average (e.g., median income)

Example

service = DemographicRollupService() result = service.rollup(

source_level=’tract’, target_level=’county’, year=2022, variables=[‘B01001_001E’], operation=’sum’,

)

__init__(dataset='acs5')[source]
Parameters:

dataset (str)

GEOID_PREFIX_LENGTHS = {'block': 15, 'block_group': 12, 'county': 5, 'state': 2, 'tract': 11}
rollup(source_level, target_level, year, variables, operation='sum', state_fips=None, batch_size=500, crosswalk_year=None, min_coverage=0.5)[source]

Aggregate demographic data from source to target geography level.

Parameters:
  • source_level (str) – Source (finer) geography (‘tract’, ‘block_group’, etc.)

  • target_level (str) – Target (coarser) geography (‘county’, ‘state’, etc.)

  • year (int) – Census year for the demographic data

  • variables (List[str]) – List of variable codes to aggregate

  • operation (str) – Aggregation operation (‘sum’, ‘avg’, ‘weighted_avg’)

  • state_fips (str | None) – Optional state filter

  • batch_size (int) – Bulk create batch size

  • crosswalk_year (int | None) – When set, use TemporalCrosswalk mappings from crosswalk_yearyear instead of GEOID prefix truncation. Enables rollup across boundary changes.

  • min_coverage (float) – Minimum coverage ratio (0-1, default 0.5). When a target geography has data for fewer than min_coverage of its expected children, a warning is logged.

Returns:

List of RollupResult (one per variable)

Return type:

List[RollupResult]

Service for populating DemographicTimeSeries from multi-year Census data.

Fetches Census data across multiple years for a given geography level, computes trend statistics (CAGR, std_dev, trend direction), and bulk-creates DemographicTimeSeries records.

class siege_utilities.geo.django.services.timeseries_service.TimeseriesResult[source]

Bases: object

Result of a timeseries population run.

variable_code: str
geography_level: str
records_created: int = 0
records_updated: int = 0
records_skipped: int = 0
errors: list
property total_processed: int
property success: bool
__init__(variable_code, geography_level, records_created=0, records_updated=0, records_skipped=0, errors=<factory>)
Parameters:
  • variable_code (str)

  • geography_level (str)

  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.timeseries_service.TimeseriesService[source]

Bases: object

Populate DemographicTimeSeries from multi-year Census snapshots.

This service reads existing DemographicSnapshot records for a boundary, assembles time series, computes statistics, and stores the result as a DemographicTimeSeries record.

Example

service = TimeseriesService() result = service.populate_timeseries(

variables=[‘B01001_001E’], years=range(2015, 2023), geography_level=’tract’, state_fips=’06’,

)

__init__(dataset='acs5')[source]
Parameters:

dataset (str) – Census dataset identifier (acs5, acs1, dec)

populate_timeseries(variables, years, geography_level, state_fips=None, update_existing=False, batch_size=500)[source]

Build DemographicTimeSeries records from existing DemographicSnapshots.

Parameters:
  • variables (List[str]) – List of Census variable codes (e.g., [‘B01001_001E’])

  • years (List[int]) – List of years to include in the series

  • geography_level (str) – Geography level (‘tract’, ‘county’, etc.)

  • state_fips (str | None) – Optional state FIPS to filter boundaries

  • update_existing (bool) – If True, update existing series; else skip

  • batch_size (int) – Bulk create batch size

Returns:

List of TimeseriesResult (one per variable)

Return type:

List[TimeseriesResult]

Service for populating TimezoneGeometry from timezone-boundary-builder GeoJSON.

Data source: https://github.com/evansiroky/timezone-boundary-builder Releases provide GeoJSON files with IANA timezone boundaries.

class siege_utilities.geo.django.services.timezone_service.TimezonePopulationResult[source]

Bases: object

Result of a timezone population run.

records_created: int = 0
records_updated: int = 0
records_skipped: int = 0
errors: list
property success: bool
__init__(records_created=0, records_updated=0, records_skipped=0, errors=<factory>)
Parameters:
  • records_created (int)

  • records_updated (int)

  • records_skipped (int)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.timezone_service.TimezonePopulationService[source]

Bases: object

Populate TimezoneGeometry model from timezone-boundary-builder GeoJSON.

Example

>>> service = TimezonePopulationService()
>>> result = service.populate_from_geojson('/path/to/timezones.geojson')
>>> print(f"Created {result.records_created} timezone boundaries")
populate_from_geojson(geojson_path, vintage_year=2024, update_existing=False, batch_size=100)[source]

Populate TimezoneGeometry records from a GeoJSON file.

Parameters:
  • geojson_path (str | Path) – Path to the timezone-boundary-builder GeoJSON file.

  • vintage_year (int) – Vintage year for the records.

  • update_existing (bool) – If True, update existing records.

  • batch_size (int) – Bulk create batch size.

Returns:

TimezonePopulationResult with counts.

Return type:

TimezonePopulationResult

Urbanicity classification service for Census tracts.

Computes NCES-style locale codes (11-43) for tracts in years where official NCES locale data is not available. Uses tract population (from DemographicSnapshot) and distance to the nearest Census Urban Area boundary to apply the NCES classification thresholds.

Usage:

from siege_utilities.geo.django.services import UrbanicityClassificationService

service = UrbanicityClassificationService()
result = service.classify(year=2020, state_fips="06")
class siege_utilities.geo.django.services.urbanicity_service.ClassificationResult[source]

Bases: object

Result of an urbanicity classification run.

classified: int = 0
skipped_no_population: int = 0
skipped_no_urban_area: int = 0
skipped_no_point: int = 0
errors: list
property total_processed: int
__init__(classified=0, skipped_no_population=0, skipped_no_urban_area=0, skipped_no_point=0, errors=<factory>)
Parameters:
  • classified (int)

  • skipped_no_population (int)

  • skipped_no_urban_area (int)

  • skipped_no_point (int)

  • errors (list)

Return type:

None

class siege_utilities.geo.django.services.urbanicity_service.UrbanicityClassificationService[source]

Bases: object

Classify Census tracts into NCES locale codes using population and distance-to-urban-area.

Algorithm:
  1. For each tract, look up total population from DemographicSnapshot.

  2. Compute geodesic distance from the tract’s internal_point to the nearest UrbanArea boundary.

  3. Pass (population, distance_miles) to classify_urbanicity() to get the subcategory string (e.g. "city_large").

  4. Convert subcategory to the numeric NCES code (e.g. 11).

  5. Bulk-update Tract.urbanicity_code.

classify(year, state_fips=None, urban_area_year=None, dataset='acs5', batch_size=500, overwrite=False, srid=4326)[source]

Classify tracts for the given vintage year.

Parameters:
  • year (int) – Census vintage year for tracts and demographics.

  • state_fips (str | None) – Limit to a single state (2-digit FIPS). If None, classifies all states.

  • urban_area_year (int | None) – Vintage year for UrbanArea boundaries. Defaults to year.

  • dataset (str) – Census dataset used for the DemographicSnapshot (default "acs5").

  • batch_size (int) – Number of tracts to update per bulk_update call.

  • overwrite (bool) – If False (default), skip tracts that already have a non-null urbanicity_code.

  • srid (int) – SRID for spatial operations (default 4326).

Returns:

ClassificationResult with counts.

Return type:

ClassificationResult

Serializers

Management Commands

Django management commands for Census boundary data.

App Configuration

Django app configuration for siege_utilities.geo.django.

class siege_utilities.geo.django.apps.SiegeGeoConfig[source]

Bases: AppConfig

Django app configuration for Census geographic data models.

name = 'siege_utilities.geo.django'
label = 'siege_geo'
verbose_name = 'Siege Utilities - Geography'
default_auto_field = 'django.db.models.BigAutoField'
ready()[source]

Perform initialization when the app is ready.