krz/omaha-metro-blotter

Archive of police activity and ALPR surveillance across the Omaha metro. alpr archive omaha police surveillance

Commit 1e8c059eee

1e8c059eeeafa07ff220a9e9f874d879cefff113

parent: b9daf53bf9

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-23 21:37 UTC

Rename the project to omaha-metro-blotter

The repo covers six agencies across two states, ALPR camera locations
and Flock search audits, not just Omaha incidents. Updates the user
agent, the site footer link, the nfo banner and a stale opd_csv
reference in the usage block.

Layout: unified · split

README.nfo +7 −6
@@ -1,11 +1,12 @@
1┌──────────────────────────────────────────────────────────────┐ 1┌──────────────────────────────────────────────────────────────┐
2│ O M A H A I N C I D E N T S [ KRZ ] krz.sh │ 2│ O M A H A M E T R O B L O T T E R [ KRZ ] krz.sh │
3└──────────────────────────────────────────────────────────────┘ 3└──────────────────────────────────────────────────────────────┘
4 4
5WHAT 5WHAT
6 police activity across the omaha metro, pulled from the agencies' 6 an archive of police activity and alpr surveillance across the
7 own feeds and joined against alpr camera locations from 7 omaha metro, pulled from the agencies' own feeds. sarpy county,
8 openstreetmap. 8 council bluffs and the flock portals all serve rolling windows and
9 delete what falls outside them; this keeps it.
9 10
10COVERAGE 11COVERAGE
11 omaha pd dcgis arcgis view, 2022-01-01 onward, 12 omaha pd dcgis arcgis view, 2022-01-01 onward,
@@ -42,7 +43,7 @@ USE
42 .venv/bin/python ingest.py --full # first run, backfill 43 .venv/bin/python ingest.py --full # first run, backfill
43 .venv/bin/python ingest.py # daily, last 30 days 44 .venv/bin/python ingest.py # daily, last 30 days
44 .venv/bin/python ingest.py cbpd sarpy # one source at a time 45 .venv/bin/python ingest.py cbpd sarpy # one source at a time
45 .venv/bin/python ingest.py opd_csv # 2015-2023 csv archive 46 .venv/bin/python ingest.py opd_archive # omaha 2015-2021 backfill
46 .venv/bin/python app.py 47 .venv/bin/python app.py
47 48
48SITE 49SITE
@@ -91,7 +92,7 @@ ARCHIVE
91 92
92 to run the pull locally instead: 93 to run the pull locally instead:
93 94
94 0 6 * * * cd /path/to/omaha-incidents && .venv/bin/python ingest.py 95 0 6 * * * cd /path/to/omaha-metro-blotter && .venv/bin/python ingest.py
95 96
96RAW 97RAW
97 raw_records keeps the feed's own json for every version of every 98 raw_records keeps the feed's own json for every version of every
fetch_boundaries.py +1 −1
@@ -38,7 +38,7 @@ def fetch(layer):
38 "maxAllowableOffset": TOLERANCE, "f": "geojson"} 38 "maxAllowableOffset": TOLERANCE, "f": "geojson"}
39 url = layer["url"] + "/query?" + urllib.parse.urlencode(params) 39 url = layer["url"] + "/query?" + urllib.parse.urlencode(params)
40 # Sarpy's MapServer 403s the default Python-urllib agent 40 # Sarpy's MapServer 403s the default Python-urllib agent
41 req = urllib.request.Request(url, headers={"User-Agent": "omaha-incidents/1.0"}) 41 req = urllib.request.Request(url, headers={"User-Agent": "omaha-metro-blotter/1.0"})
42 with urllib.request.urlopen(req, timeout=120, context=CTX) as r: 42 with urllib.request.urlopen(req, timeout=120, context=CTX) as r:
43 return json.load(r) 43 return json.load(r)
44 44
ingest.py +2 −2
@@ -128,7 +128,7 @@ def get(url, params, retries=4):
128 for attempt in range(retries): 128 for attempt in range(retries):
129 try: 129 try:
130 req = urllib.request.Request(url, data=body, 130 req = urllib.request.Request(url, data=body,
131 headers={"User-Agent": "omaha-incidents/1.0"}) 131 headers={"User-Agent": "omaha-metro-blotter/1.0"})
132 with urllib.request.urlopen(req, timeout=120, context=SSL_CTX) as r: 132 with urllib.request.urlopen(req, timeout=120, context=SSL_CTX) as r:
133 payload = json.load(r) 133 payload = json.load(r)
134 except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e: 134 except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e:
@@ -321,7 +321,7 @@ def ingest_opd_archive(conn, _since):
321 rows, seen = [], {} 321 rows, seen = [], {}
322 for year in OPD_ARCHIVE_YEARS: 322 for year in OPD_ARCHIVE_YEARS:
323 url = OPD_ARCHIVE.format(y=year) 323 url = OPD_ARCHIVE.format(y=year)
324 req = urllib.request.Request(url, headers={"User-Agent": "omaha-incidents/1.0"}) 324 req = urllib.request.Request(url, headers={"User-Agent": "omaha-metro-blotter/1.0"})
325 with urllib.request.urlopen(req, timeout=180, context=SSL_CTX) as r: 325 with urllib.request.urlopen(req, timeout=180, context=SSL_CTX) as r:
326 text = r.read().decode("utf-8-sig", "replace") 326 text = r.read().decode("utf-8-sig", "replace")
327 reader = csv.DictReader(text.splitlines()) 327 reader = csv.DictReader(text.splitlines())
site_template.html +1 −1
@@ -152,7 +152,7 @@ footer a { color: var(--text-secondary); }
152 152
153<footer> 153<footer>
154 <p>Built from the agencies' own published feeds: the Omaha Police incident view on DCGIS, the Sarpy County PublicCrimeMap, the Council Bluffs public calls-for-service feed, and the Flock Safety transparency portals. Camera locations from OpenStreetMap contributors, <a href="https://opendatacommons.org/licenses/odbl/">ODbL</a>.</p> 154 <p>Built from the agencies' own published feeds: the Omaha Police incident view on DCGIS, the Sarpy County PublicCrimeMap, the Council Bluffs public calls-for-service feed, and the Flock Safety transparency portals. Camera locations from OpenStreetMap contributors, <a href="https://opendatacommons.org/licenses/odbl/">ODbL</a>.</p>
155 <p>The Sarpy County and Council Bluffs feeds keep a rolling twelve months and drop what falls outside it. This archive keeps what they drop. Source and the full database: <a href="https://github.com/krazywarez/omaha-incidents">github.com/krazywarez/omaha-incidents</a>.</p> 155 <p>The Sarpy County and Council Bluffs feeds keep a rolling twelve months and drop what falls outside it. This archive keeps what they drop. Source and the full database: <a href="https://github.com/krazywarez/omaha-metro-blotter">github.com/krazywarez/omaha-metro-blotter</a>.</p>
156</footer> 156</footer>
157</div> 157</div>
158 158