krz/omaha-metro-blotter
Archive of police activity and ALPR surveillance across the Omaha metro.
clone: git clone https://gitbay.org/krz/omaha-metro-blotter.git
1e8c059eeeafa07ff220a9e9f874d879cefff113
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-08-23T21:37:15Z
README.nfo | 13 +++++++------ fetch_boundaries.py | 2 +- ingest.py | 4 ++-- site_template.html | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) @@ -1,11 +1,12 @@ ┌──────────────────────────────────────────────────────────────┐ -│ O M A H A I N C I D E N T S [ KRZ ] krz.sh │ +│ O M A H A M E T R O B L O T T E R [ KRZ ] krz.sh │ └──────────────────────────────────────────────────────────────┘ WHAT - police activity across the omaha metro, pulled from the agencies' - own feeds and joined against alpr camera locations from - openstreetmap. + an archive of police activity and alpr surveillance across the + omaha metro, pulled from the agencies' own feeds. sarpy county, + council bluffs and the flock portals all serve rolling windows and + delete what falls outside them; this keeps it. COVERAGE omaha pd dcgis arcgis view, 2022-01-01 onward, @@ -42,7 +43,7 @@ USE .venv/bin/python ingest.py --full # first run, backfill .venv/bin/python ingest.py # daily, last 30 days .venv/bin/python ingest.py cbpd sarpy # one source at a time - .venv/bin/python ingest.py opd_csv # 2015-2023 csv archive + .venv/bin/python ingest.py opd_archive # omaha 2015-2021 backfill .venv/bin/python app.py SITE @@ -91,7 +92,7 @@ ARCHIVE to run the pull locally instead: - 0 6 * * * cd /path/to/omaha-incidents && .venv/bin/python ingest.py + 0 6 * * * cd /path/to/omaha-metro-blotter && .venv/bin/python ingest.py RAW raw_records keeps the feed's own json for every version of every @@ -38,7 +38,7 @@ def fetch(layer): "maxAllowableOffset": TOLERANCE, "f": "geojson"} url = layer["url"] + "/query?" + urllib.parse.urlencode(params) # Sarpy's MapServer 403s the default Python-urllib agent - req = urllib.request.Request(url, headers={"User-Agent": "omaha-incidents/1.0"}) + req = urllib.request.Request(url, headers={"User-Agent": "omaha-metro-blotter/1.0"}) with urllib.request.urlopen(req, timeout=120, context=CTX) as r: return json.load(r) @@ -128,7 +128,7 @@ def get(url, params, retries=4): for attempt in range(retries): try: req = urllib.request.Request(url, data=body, - headers={"User-Agent": "omaha-incidents/1.0"}) + headers={"User-Agent": "omaha-metro-blotter/1.0"}) with urllib.request.urlopen(req, timeout=120, context=SSL_CTX) as r: payload = json.load(r) except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e: @@ -321,7 +321,7 @@ def ingest_opd_archive(conn, _since): rows, seen = [], {} for year in OPD_ARCHIVE_YEARS: url = OPD_ARCHIVE.format(y=year) - req = urllib.request.Request(url, headers={"User-Agent": "omaha-incidents/1.0"}) + req = urllib.request.Request(url, headers={"User-Agent": "omaha-metro-blotter/1.0"}) with urllib.request.urlopen(req, timeout=180, context=SSL_CTX) as r: text = r.read().decode("utf-8-sig", "replace") reader = csv.DictReader(text.splitlines()) @@ -152,7 +152,7 @@ footer a { color: var(--text-secondary); } <footer> <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> - <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> + <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> </footer> </div>