From 0b88a77b151854ff7469b586d36e5a52761a6183 Mon Sep 17 00:00:00 2001 From: Chandler Swift Date: Tue, 25 Jul 2023 21:34:19 -0500 Subject: [PATCH] Add US Census Bureau layers --- layers/census-bureau/README.md | 38 +++++++++++++++++ layers/census-bureau/cleanup_data.py | 47 +++++++++++++++++++++ layers/census-bureau/get_data.sh | 17 ++++++++ layers/census-bureau/index.js | 63 ++++++++++++++++++++++++++++ layers/index.js | 4 +- 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 layers/census-bureau/README.md create mode 100644 layers/census-bureau/cleanup_data.py create mode 100755 layers/census-bureau/get_data.sh create mode 100644 layers/census-bureau/index.js diff --git a/layers/census-bureau/README.md b/layers/census-bureau/README.md new file mode 100644 index 0000000..d317691 --- /dev/null +++ b/layers/census-bureau/README.md @@ -0,0 +1,38 @@ +from the US Census Bureau: + +https://www.census.gov/geographies/mapping-files/time-series/geo/cartographic-boundary.html + +more description here: + +https://www.census.gov/programs-surveys/geography/technical-documentation/naming-convention/cartographic-boundary-file.html + +Including the following: + +> # File Naming Convention +> ## 2013 to Present Files +> +> The cartographic boundary files are named cb_yyyy_ss__rr.zip where: +> +> * yyyy = 4 digit year +> * ss = state FIPS code or 'us' for a national level file +> * entity = the entity name +> * rr = resolution level +> * 500k = 1:500,000 +> * 5m = 1:5,000,000 +> * 20m = 1:20,000,000 + +I prefer the KML options when available since OpenLayers doesn't [citation +needed?] have built-in support for shapefiles (.shp)? That said, we also do +transform KML to GeoJSON to be able to edit more easily (and it's sometimes good +for a smaller file size). + +For more information, see e.g.: + +https://indicatrix.org/post/shapefiles-in-openlayers/ + +Census Bureau data appears to be using a different (US-centric, possibly?) +projection; this may require more work to be accurate going forward. + +https://gis.stackexchange.com/a/310949 + +https://epsg.io/4269 diff --git a/layers/census-bureau/cleanup_data.py b/layers/census-bureau/cleanup_data.py new file mode 100644 index 0000000..dd63190 --- /dev/null +++ b/layers/census-bureau/cleanup_data.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 + +import sys +import json + +with open(sys.argv[1]) as f: + data = json.load(f) + +# { +# "type": "FeatureCollection", +# "name": "cb_2022_us_county_20m", +# "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +# "features": [ +# { +# "type": "Feature", +# "properties": { +# "Name": "Autauga", +# "Description": "
Attributes
STATEFP 01
COUNTYFP 001
COUNTYNS 00161526
AFFGEOID 0500000US01001
GEOID 01001
NAME Autauga
NAMELSAD Autauga County
STUSPS AL
STATE_NAME Alabama
LSAD 06
ALAND 1539631461
AWATER 25677536
" +# }, +# "geometry": { +# "type": "Polygon", +# "coordinates": [ +# [ +# [ +# -86.917595, +# 32.664169, +# 0.0 +# ], +# ... +# ] +# ] +# } +# }, +# [...] +# ] +# } + +# len("") == 15 +# len("") == 12 +# "Autauga"[15:-12] == "Autauga" + +for feature in data['features']: + # del feature['properties']['Description'] + feature['properties']['Name'] = feature['properties']['Name'][15:-12] + +with open(sys.argv[1], 'w') as f: + json.dump(data, f) diff --git a/layers/census-bureau/get_data.sh b/layers/census-bureau/get_data.sh new file mode 100755 index 0000000..003a489 --- /dev/null +++ b/layers/census-bureau/get_data.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +for resolution in 20m 5m; do + curl --silent --remote-name https://www2.census.gov/geo/tiger/GENZ2022/shp/cb_2022_us_county_${resolution}.zip + unzip cb_2022_us_county_${resolution}.zip + ogr2ogr -f GeoJSON us-counties-${resolution}.geojson cb_2022_us_county_${resolution}.shp + sed -i '/^"crs":/d' us-counties-${resolution}.geojson # TODO: handle this projection properly + rm cb_2022_us_county_${resolution}.* + # python cleanup_data.py us-counties-${resolution}.geojson # Only needed for KML files +done + +curl --silent --remote-name https://www2.census.gov/geo/tiger/GENZ2022/shp/cb_2022_us_unsd_500k.zip +unzip cb_2022_us_unsd_500k.zip +ogr2ogr -f GeoJSON us-school-districts.geojson cb_2022_us_unsd_500k.shp +sed -i '/^"crs":/d' us-school-districts.geojson # TODO: handle this projection properly +rm cb_2022_us_unsd_500k.* +# TODO: some kind of cleanup to save space? diff --git a/layers/census-bureau/index.js b/layers/census-bureau/index.js new file mode 100644 index 0000000..fbcf7d9 --- /dev/null +++ b/layers/census-bureau/index.js @@ -0,0 +1,63 @@ +import GeoJSON from 'ol/format/GeoJSON.js'; +import VectorLayer from 'ol/layer/Vector.js'; +import VectorSource from 'ol/source/Vector.js'; + +import counties20m from './us-counties-20m.geojson?url'; +import counties5m from './us-counties-5m.geojson?url'; +import schoolDistricts from './us-school-districts.geojson?url'; + +import { Fill, Stroke, Style, Text } from 'ol/style.js'; + +function style(feature){ + return new Style({ + text: new Text({ + text: feature.get('NAME'), + }), + fill: new Fill({ + color: 'rgba(255,255,255,0.4)', + }), + stroke: new Stroke({ + color: '#3399CC', + width: 1.25, + }), + }); +} + +const layers = { + name: "US Census Bureau Data", + layers: [ + { + name: "All Counties (2022; low-res)", + layer: new VectorLayer({ + source: new VectorSource({ + url: counties20m, + format: new GeoJSON(), + }), + style: style, + }), + }, + { + name: "All Counties (2022; medium-res)", + layer: new VectorLayer({ + source: new VectorSource({ + url: counties5m, + format: new GeoJSON(), + }), + style: style, + }), + }, + { + name: "School Districts (2022; unreasonably large)", + layer: new VectorLayer({ + source: new VectorSource({ + url: schoolDistricts, + format: new GeoJSON(), + // TODO: this probably uses projection 'EPSG:4326' + }), + style: style, + }), + }, + ], +}; + +export default layers; diff --git a/layers/index.js b/layers/index.js index 496b86a..f7a0da9 100644 --- a/layers/index.js +++ b/layers/index.js @@ -7,6 +7,7 @@ import amtrakLayer from './amtrak/layer.js'; import arenasLayer from './nhl-arenas/layer.js'; import bikepackingLayer from './bikepacking/layer.js'; import chains from './chains/index.js'; +import census_bureau from './census-bureau/index.js'; const layerCategories = [ { // Base maps @@ -70,7 +71,8 @@ const layerCategories = [ }, ] }, - chains + chains, + census_bureau, ]; export default layerCategories;