A dependable and clean repository of Los Angeles administrative and physical boundary layers for reproducible analysis and easy mapping. Optionally enrich any layer with 2020 Census demographics. Pinpoint "your LA" with an API that feeds https://www.whatsmyla.com.
Provides clean, versioned, well-documented boundary layers for LA city & county with:
- Official sources from open data portals maintained by LA city and LA County
- Standardized outputs in WGS84 (EPSG:4326)
- Area calculations using California Albers (EPSG:3310) for accuracy
- Normalized schemas with consistent lower_snake_case naming
- Quality validation with geometry checks and expected feature counts
- Optional demographics from 2020 Census via block-level apportionment
All layers available as clean GeoJSON with standardized fields, area calculations, and validation.
The "Where do you live?" map for Los Angeles County
-
LA neighborhoods (270): Every city, unincorporated area, and LA City neighborhood in one layer
- Includes incorporated cities (Inglewood, Pasadena, Culver City)
- Unincorporated areas (Marina del Rey, East LA, Hacienda Heights)
- LA City neighborhoods (Venice, Silver Lake, North Hollywood)
- This is what people mean when they say "I live in Hollywood" or "I'm in Culver City"
- No, you don't live in the "City of North Hollywood"!
-
LA regions (16): Broad geographic regions of LA County
- Dissolves neighborhoods into larger areas: Westside, South LA, San Gabriel Valley, etc.
- Useful for high-level analysis and settling debates about which neighborhoods belong where
- See
docs/LA_REGIONS.mdfor complete region list and demographics
Source: LA Times Mapping LA project (archived)
Credit: Created by Ben Welsh and the LA Times Data Desk. Though the original project was deprecated a few years ago, I'm excited to keep these essential boundaries active — and easily enriched with demographic data — in my own way.
- LAPD Bureaus (4): Central, South, Valley, West
- LAPD Divisions (21): Pacific, Rampart, Central, etc.
- LAPD Reporting Districts (~1,191): Finest-grained LAPD geography
- LAPD Station Locations (21): Police station addresses and locations
- Sheriff & Municipal Police (TBD): Sheriff station and municipal police boundaries for areas outside LA City (Alhambra Police, Santa Monica Police, Inglewood Police, Lancaster Sheriff, etc.)
- City Boundary: Official city limits
- Neighborhoods (114): LA Times boundaries within LA City only (officially adopted)
- Neighborhood Councils: ~99 certified councils
- Council Districts: 15 city council districts
- Parks: 561 parks and recreation facilities
For the complete county-wide map including all cities and unincorporated areas, see "LA Neighborhoods (Comprehensive)" above.
- County boundary: LA County limits
- Cities & communities: 88 cities + unincorporated areas
- Supervisor Districts (5): LA County Board of Supervisors districts
- ZIP Codes (~313): ZIP Code Tabulation Areas for LA County
- School Districts: 85 school districts (Elementary, High School, and Unified)
- Parcels: 2.4 million property records on the county's GIS portal. Not included here because of its size
- LA County Fire - Station Boundaries (174): LA County Fire station service areas
- LA County Fire - Station Locations (174): LA County Fire station addresses
- LA Fire Dept - Station Boundaries (102): LAFD (city) station service areas
- Freeways: Interstates and state highways clipped to LA County
- Metro Lines: LA Metro rail lines and bus rapid transit (17 lines)
- Airport Locations (16): All LA County airports (4 commercial + 12 general aviation)
- Airport Noise Contours (35): CNEL noise zones (55-85 dB) around major airports
- Election Precincts (1,502): Current LA County election precincts (Sept 2025)
All polygon layers can be enriched with 2020 Census demographics (population, race/ethnicity, housing) through reproducible block-level apportionment.
Option 1: Download pre-computed demographics (fastest, no API key needed)
# Download all layers + demographics from S3
make s3-downloadOption 2: Compute demographics yourself (reproducible pipeline)
# Get free API key from census.gov, then:
export CENSUS_API_KEY="your-key"
make fetch-census
make apportion-censusOutput: Each layer gets a companion *_demographics.parquet file with population totals, race/ethnicity breakdowns, and housing counts from the 2020 Census.
→ See Census Demographics section below for details.
Query any coordinate to get all geographic information in one request - Find neighborhood, city, police division, fire station, council district, and more for any lat/lon point.
Live API endpoint: https://api.stilesdata.com/la-geography/lookup
# Example: What's at this location?
curl "https://api.stilesdata.com/la-geography/lookup?lat=34.0665304&lon=-118.3718048"{
"status": "success",
"query": {
"lat": 34.0665304,
"lon": -118.3718048
},
"results": {
"neighborhood": "Beverly Grove",
"city": "Los Angeles",
"region": "Central La",
"law_enforcement": {
"agency": "LAPD",
"division": "Wilshire",
"bureau": "West Bureau"
},
"fire": {
"agency": "LAFD",
"station": "Fire Station 61"
},
"representation": {
"type": "city_council",
"district": "5",
"representative": "Katy Yaroslavsky"
},
"zip_code": "90036",
"school_district": "Los Angeles USD",
"neighborhood_type": "segment-of-a-city",
"place_category": "la_city_neighborhood",
"neighborhood_demographics": {
"population": 22121,
"pop_hispanic": 2158,
"pop_white_nh": 15893,
"pop_black_nh": 836,
"pop_asian_nh": 1909,
"pop_other_nh": 1325
},
"city_demographics": {
"population": 3898787,
"pop_hispanic": 1830264,
"pop_white_nh": 1125894,
"pop_black_nh": 322537,
"pop_asian_nh": 454536,
"pop_other_nh": 165557
}
}
}Deploy your own API (AWS Lambda + API Gateway, ~$0.30/month for low traffic):
cd lambda/
sam build
sam deploy --guided→ See API Documentation and Deployment Guide for details.
# Set up environment
uv venv && source .venv/bin/activate
uv pip install -r requirements.txt
# Fetch all layers
make fetch
# Process and validate
make standardize validate
# Export final outputs
make exportProcessed layers and demographics are published to S3 for public access:
# Upload all layers + demographics to S3
make s3-upload
# Download layers + demographics from S3 (if you just need the data)
make s3-download
# List available layers in S3
make s3-list
# Upload a single layer (with its demographics if available)
python scripts/s3_sync.py upload --layer la_city_boundary
# Upload boundaries only (skip demographics)
python scripts/s3_sync.py upload --no-demographicsDirect download URLs:
All layers are publicly accessible via HTTPS. Click layer names to download:
| Layer | Size |
|---|---|
| LA neighborhoods (comprehensive) | 5.80 MB |
| LA County regions (Westside, San Fernando Valley, South Bay, etc.) | 1.47 MB |
| LAPD bureaus | 0.55 MB |
| LAPD divisions | 0.84 MB |
| LAPD reporting districts | 6.50 MB |
| LAPD station locations | 0.01 MB |
| LA County Sheriff & municipal police boundaries | TBD |
| LA city boundary | 0.40 MB |
| LA city neighborhoods | 0.95 MB |
| LA city neighborhood councils | 2.80 MB |
| LA city council districts | 1.40 MB |
| LA city parks | 5.00 MB |
| LA County boundary | 2.80 MB |
| LA County cities | 13.53 MB |
| LA County supervisor districts | TBD |
| LA County ZIP codes | TBD |
| LA County school districts | 4.30 MB |
| LA freeways | 1.62 MB |
| LA Metro lines | 0.44 MB |
| LA County Fire Dept station boundaries | 5.10 MB |
| LA County Fire Dept station locations | 0.10 MB |
| LA Fire Dept (city) station boundaries | 1.70 MB |
| Metadata | JSON |
Demographics files:
Each polygon layer also has a companion demographics file available:
- Pattern:
https://stilesdata.com/la-geography/{layer}_demographics.parquet - Example:
https://stilesdata.com/la-geography/lapd_divisions_demographics.parquet - Size: Typically < 50 KB per layer
Quick examples:
# Python with GeoPandas
import geopandas as gpd
import pandas as pd
# Load the comprehensive LA neighborhoods layer
neighborhoods = gpd.read_file('https://stilesdata.com/la-geography/la_neighborhoods_comprehensive.geojson')
# Load demographics (if available)
demographics = pd.read_parquet('https://stilesdata.com/la-geography/la_neighborhoods_comprehensive_demographics.parquet')
# Or load a simpler layer like city boundary
boundaries = gpd.read_file('https://stilesdata.com/la-geography/la_city_boundary.geojson')
# R with sf
library(sf)
library(arrow)
neighborhoods <- st_read('https://stilesdata.com/la-geography/la_neighborhoods_comprehensive.geojson')
demographics <- read_parquet('https://stilesdata.com/la-geography/la_neighborhoods_comprehensive_demographics.parquet')// JavaScript with D3
d3.json('https://stilesdata.com/la-geography/la_neighborhoods_comprehensive.geojson')
.then(data => {
const projection = d3.geoMercator().fitSize([width, height], data);
const path = d3.geoPath().projection(projection);
svg.selectAll('path').data(data.features)
.enter().append('path')
.attr('d', path)
.attr('class', d => d.properties.type); // Style by city/neighborhood type
});Environment setup: To upload layers, set these environment variables:
export MY_AWS_ACCESS_KEY_ID="your-key"
export MY_AWS_SECRET_ACCESS_KEY="your-secret"
export MY_PERSONAL_PROFILE="personal" # For clarity (optional)la-geography/
├── README.md # This file
├── PLANNING.md # Detailed planning document
├── config/
│ └── layers.yml # Layer endpoints and configurations
├── data/
│ ├── raw/ # Immutable source data
│ ├── standard/ # Cleaned and normalized outputs
│ ├── census/ # Census blocks and demographics
│ └── docs/ # Generated maps and quicklooks
├── docs/ # Documentation
│ ├── CENSUS_FIELDS.md # Census variable definitions
│ ├── CENSUS_SETUP.md # Census API key setup
│ ├── CENSUS_ANALYSIS.md # Analysis examples
│ └── CENSUS_TESTING.md # Testing guide
├── scripts/
│ ├── fetch_boundaries.py # Fetch boundary layers
│ ├── fetch_census.py # Fetch Census data
│ ├── apportion_census.py # Apportion demographics
│ ├── validate_apportionment.py # Validate results
│ ├── analyze_demographics.py # Analyze demographics (comprehensive)
│ ├── census_stats.py # Quick demographics stats
│ ├── process_raw.py # Process complex layers
│ ├── geo_utils.py # Shared utilities
│ └── s3_sync.py # Upload/download from S3
└── tests/
└── test_validate.py
All layers in data/standard/ include:
- Geometry: EPSG:4326 (WGS84)
- Format: GeoJSON and Parquet
- Fields:
geometry: Feature geometryarea_sqmi: Area in square miles (polygons only, calculated in EPSG:3310)source_url: Origin REST endpointfetched_at: ISO timestamp- Normalized field names in lower_snake_case
Layer endpoints are defined in config/layers.yml with:
- Source URL
- Expected feature count (for validation)
- Field mappings for normalization
- Geometry type
- Source organization
- LA City GeoHub: https://geohub.lacity.org/
- LA County GIS Hub: https://egis-lacounty.hub.arcgis.com/
- Caltrans: https://caltrans-gis.dot.ca.gov/
- LA Times Data Desk: https://github.com/datadesk/boundaries.latimes.com (archived)
All sources are official government portals or trusted journalism organizations with open data licenses.
- Storage & sharing: EPSG:4326 (WGS84 lat/lon)
- Area/distance calculations: EPSG:3310 (California Albers, equal-area)
This avoids Web Mercator distortion while maintaining compatibility with web mapping tools.
Each fetch includes automated checks:
- Non-empty features, no null geometries
- Valid geometries (auto-fixed with
buffer(0)) - Feature counts within expected tolerance
- Bounding box inside LA County extent
- LAPD hierarchy validation (districts ≥ divisions ≥ bureaus)
Data sources retain their original licenses (typically public domain or CC0). See data/standard/*.meta.json for per-layer licensing information.
- Python 3.10+
- Key dependencies: geopandas, shapely, pyproj, pyarrow, ezesri
- Managed with
uvfor reproducible environments
Enrich any polygon layer with 2020 Census demographics using reproducible, area-weighted block apportionment.
From the 2020 Decennial Census (hard counts, not estimates):
- Population totals by race and Hispanic/Latino ethnicity
- Housing units (total, occupied, vacant)
- Area-weighted apportionment from Census blocks to your target polygons
Available for all 13 polygon layers. See CENSUS_FIELDS.md for complete field documentation.
# 1. Get a free Census API key (one-time, 2 minutes)
# Visit: https://api.census.gov/data/key_signup.html
export CENSUS_API_KEY="your-key-here"
# 2. Fetch LA County Census blocks (~2-3 minutes, ~254K blocks)
make fetch-census
# 3. Test with LAPD bureaus (fast, 4 features)
make apportion-census-test
# 4. Apportion to all layers (~5-10 minutes)
make apportion-census
# 5. Validate results
make validate-censusDemographics are stored as companion Parquet files (separate from GeoJSON boundaries) for several good reasons:
- Size efficiency: Demographics are ~50 KB vs 1-15 MB for geometries. Parquet is extremely compact for tabular data.
- Optional enrichment: Users who only need boundaries (for basemaps, spatial joins, etc.) don't download or process demographics.
- Update independence: Re-run census apportionment without re-fetching boundaries.
- Data type optimization: GeoJSON for geometry (human-readable, universal), Parquet for demographics (binary, columnar, fast).
- Clear provenance: Separation makes it explicit that demographics are derived, not intrinsic properties.
Option 1: Helper function (recommended)
The easiest way to load layers with demographics:
from scripts.data_loader import load_layer_with_demographics
# Load boundaries + demographics in one line
enriched = load_layer_with_demographics('lapd_divisions')
# Works with local files or S3 URLs
enriched = load_layer_with_demographics(
'lapd_divisions',
base_url='https://stilesdata.com/la-geography'
)
# Map or analyze
enriched.plot(column='pop_total', legend=True, figsize=(12, 10))Option 2: Manual join (full control)
For transparency or custom joins:
import pandas as pd
import geopandas as gpd
# Load files separately
boundaries = gpd.read_file('data/standard/lapd_divisions.geojson')
demographics = pd.read_parquet('data/standard/lapd_divisions_demographics.parquet')
# Join on ID field (varies by layer - see config/layers.yml)
joined = boundaries.merge(demographics, on='prec')
# Calculate percentages
joined['pct_hispanic'] = joined['pop_hispanic'] / joined['pop_total'] * 100Option 3: Boundaries only
Many use cases don't need demographics:
import geopandas as gpd
from scripts.data_loader import load_layer
# Just the boundaries
boundaries = load_layer('lapd_divisions')
# Or from S3
boundaries = gpd.read_file('https://stilesdata.com/la-geography/lapd_divisions.geojson')R users:
library(sf)
library(arrow)
library(dplyr)
# Load and join
boundaries <- st_read('https://stilesdata.com/la-geography/lapd_divisions.geojson')
demographics <- read_parquet('https://stilesdata.com/la-geography/lapd_divisions_demographics.parquet')
enriched <- boundaries %>% left_join(demographics, by = 'prec')Each *_demographics.parquet file includes:
pop_total,pop_hispanic,pop_white_nh,pop_black_nh,pop_asian_nh,pop_nhpi_nh,pop_aian_nh,pop_other_nh,pop_two_or_more_nhhousing_total,housing_occupied,housing_vacantsource_blocks_count,apportioned_at,census_vintage
Note: These are 2020 Census hard counts (no margins of error). For income, education, or median age, you'll need American Community Survey (ACS) data.
Quick statistics for any layer:
# Show demographics summary
python scripts/census_stats.py lapd_divisions
# Top 10 features by population
python scripts/census_stats.py la_city_neighborhoods --top 10Comprehensive analysis:
# Analyze all layers
python scripts/analyze_demographics.py
# Analyze specific layer
python scripts/analyze_demographics.py --layer lapd_bureaus
# Generate markdown report
python scripts/analyze_demographics.py --save-report- Setup guide: CENSUS_SETUP.md
- Field definitions: CENSUS_FIELDS.md
- Analysis examples: CENSUS_ANALYSIS.md
- Testing guide: CENSUS_TESTING.md