
This is part of the Scicloj Clojure Data Scrapbook. |
Seattle Parks and Neighborhoods - DRAFT
Timothy Prately and Daniel Slutsky
(Probably, this notebook will be divided into a few book chapters.)
Choosing where to live depends on many factors such as job opportunities and cost of living. I like walking, so one factor that is important to me is access to parks. In this analysis we’ll rank neighborhoods by park area proportional to total area. This article demonstrates how to prepare the geospatial data, calculate the value we want, and how to explore the meaning behind the numbers.
ns index
(:require [geo
(:as geohash]
[geohash :as jts]
[jts :as spatial]
[spatial :as geoio]
[io :as crs]]
[crs :as fun]
[tech.v3.datatype.functional :as tc]
[tablecloth.api :as kind]
[scicloj.kindly.v4.kind :as kindly]
[scicloj.kindly.v4.api :as hiccup]
[hiccup.core :as charred]
[charred.api :as color]
[clojure2d.color :as str])
[clojure.string :import (org.locationtech.jts.index.strtree STRtree)
(
(org.locationtech.jts.geom Geometry Point Polygon Coordinate)
(org.locationtech.jts.geom.prep PreparedGeometry
PreparedLineString
PreparedPolygon
PreparedGeometryFactory) (java.util TreeMap)))
Gathering geospatial data
Both the neighborhood geometry and park geometry can be downloaded from Seattle GeoData:
I’ve saved a snapshot in the data
directory.
The data format is gzipped GeoJSON. Java has a built-in class for handling gzip streams, and we’ll use the factual/geojson library to parse the string representation.
defn slurp-gzip
("Read a gzipped file into a string"
path]
[with-open [in (java.util.zip.GZIPInputStream. (clojure.java.io/input-stream path))]
(slurp in))) (
Now we can conveniently load the data files we downloaded previously.
defonce neighborhoods-geojson
("data/Seattle/Neighborhood_Map_Atlas_Neighborhoods.geojson.gz")) (slurp-gzip
def neighborhoods-features
( (geoio/read-geojson neighborhoods-geojson))
Let’s check that we got some data.
count neighborhoods-features) (
94
This seems like a reasonable number of neighborhoods.
Each member of the dataset is called a Feature. Here is one:
-> neighborhoods-features
(first
kind/pprint)
:properties
{:OBJECTID 27,
{:L_HOOD "Ballard",
:S_HOOD "Loyal Heights",
:S_HOOD_ALT_NAMES nil,
:Shape__Area 2.13206555455933E7,
:Shape__Length 18831.00959637},
:geometry
0x734a2288 "POLYGON ((-122.376336564723 47.6759176989664, -122.376707907517 47.6759982290459, -122.377903057631 47.6759978849021, -122.379988625303 47.6759893429385, -122.38182788443 47.675992079457, -122.383602752477 47.675979810424, -122.385435721425 47.6759587956618, -122.387087434206 47.6759521343769, -122.387702391216 47.6759476875929, -122.388955759168 47.6759424488678, -122.390449346263 47.6759259868868, -122.391773109273 47.6759158131504, -122.392827172066 47.6759153143353, -122.392956550545 47.6759141859541, -122.392956303464 47.6763287099834, -122.392959618409 47.676929558447, -122.392961263359 47.6775085790216, -122.392971502986 47.6781034200237, -122.392975009807 47.6792517755972, -122.392983771129 47.6798142464259, -122.39300195772 47.6811303942788, -122.393014530791 47.6824285808909, -122.393016158048 47.6837341155494, -122.393022827056 47.685021586284, -122.393030577589 47.6863450742964, -122.393032746621 47.687668638629, -122.393046187318 47.6889956475812, -122.393010665051 47.6902980913372, -122.393002415356 47.6905813205566, -122.392432373275 47.6905826938111, -122.390347910813 47.6905631204492, -122.388192979124 47.6905805029145, -122.385832831602 47.6905766041982, -122.383275316007 47.690593164597, -122.383201215024 47.6905936585447, -122.381453020656 47.6905978829044, -122.379876313408 47.6905988541749, -122.379103841332 47.6906041929746, -122.377117910432 47.6906029639028, -122.376810344766 47.6906051591463, -122.376810264834 47.6905341242701, -122.376810989127 47.6900117143006, -122.376803067488 47.688976146724, -122.376794751014 47.6879443550321, -122.376800402808 47.6870068457338, -122.376784403749 47.6860923366759, -122.376785260609 47.6851813698577, -122.376780535885 47.6842704786057, -122.376787310842 47.6833707854178, -122.376777423242 47.6822860591882, -122.376767820487 47.6811938310584, -122.376770442503 47.680154307228, -122.376770065639 47.6792017979916, -122.376769882216 47.6780678404081, -122.376759750881 47.6775387646826, -122.376761985459 47.676862137338, -122.376772171763 47.6764361697665, -122.376772451106 47.6764114022001, -122.376770700102 47.6763866189683, -122.37676692258 47.6763619488292, -122.376761120573 47.6763374777451, -122.376753802121 47.6763131984825, -122.376744969247 47.6762891966531, -122.376733608705 47.6762655291531, -122.376721246367 47.6762423037704, -122.376706358898 47.6762194979699, -122.376690473449 47.6761972626929, -122.376672575492 47.6761756116819, -122.376653693551 47.6761550023704, -122.376620811506 47.6761243856628, -122.37659791092 47.6761051588887, -122.376573006221 47.6760868163511, -122.376547112434 47.6760693435982, -122.376519722058 47.6760527482053, -122.376336564723 47.6759176989664))"]} #object[org.locationtech.jts.geom.Polygon
Each feature, in our case, represents a geographic region with a geometry and some properties.
And similarly for the parks:
defonce parks-geojson
("data/Seattle/Park_Boundary_(details).geojson.gz")) (slurp-gzip
def parks-features
( (geoio/read-geojson parks-geojson))
count parks-features) (
2809
There are more parks than neighborhoods, which sounds right.
delay
(-> parks-features
(first
kind/pprint))
:properties
{:SE_ANNO_CAD_DATA "",
{:NAMEFLAG 9,
:ADDRESS " ",
:MAINT "DPR",
:LEASE "N",
:PMA_NAME "East Duwamish GS: S Chicago St",
:SUBPARCEL 9851,
:PMA 442,
:REVIEW_DATE "2004-04-08T00:00:00Z",
:GlobalID "{D4B7025E-1135-4232-88AB-CBF72932E6AE}",
:OBJECTID 207882,
:AMWOID "PROPERTY-EDUWSC",
:SDQL "QL-D1",
:USE_ nil,
:PIN "4006000485",
:GIS_EDT_DT "2024-01-19T14:07:23Z",
:SHAPE_Area 1.4691943312522366E-6,
:SHAPE_Length 0.005215887160444538,
:GIS_CRT_DT "2024-01-19T14:07:23Z",
:NAME "EAST DUWAMISH GREENBELT",
:OWNER "DPR",
:ACQ_DATE "1997-10-21T00:00:00Z"},
:geometry
0x1788033d "MULTIPOLYGON (((-122.28235899499998 47.525165833000074, -122.28168484899999 47.525164389000054, -122.28168307199996 47.52516438600003, -122.28145050399996 47.52516388600003, -122.281450582 47.525147437000044, -122.28092621899998 47.525146310000025, -122.28092721899998 47.52433074500004, -122.28129411599997 47.52433142700005, -122.28247185299995 47.52433360800006, -122.28270306299999 47.52433403400005, -122.28270162899997 47.52516656600005, -122.28235899499998 47.525165833000074)))"]} #object[org.locationtech.jts.geom.MultiPolygon
And the parks are defined as geographic regions.
Drawing a map
def Seattle-center
(47.608013 -122.335167]) [
The map we will create is A choropleth, though for now, we will use a fixed color, which is not so informative.
We will enrich every feature (e.g., neighborhood) with data relevant for its visual representation.
defn enrich-feature [{:as feature :keys [geometry]}
(:keys [tooltip-keys
{
style]}]-> feature
(update :properties
(fn [properties]
(-> properties
(assoc :tooltip (-> properties
(select-keys tooltip-keys)
(->> (map (fn [[k v]]
(:p [:b k] ": " v]))
[into [:div]))
(
hiccup/html):style style))))))
def neighborhoods-enriched-features
(-> neighborhoods-geojson
(:key-fn keyword})
(charred/read-json {:features
->> (mapv (fn [feature]
(-> feature
(
(enrich-feature:tooltip-keys [:L_HOOD :S_HOOD]
{:style {:opacity 0.3
:fillOpacity 0.1
:color "purple"
:fillColor "purple"}})))))))
Here is how we may generate a Choroplet map in Leaflet:
defn choropleth-map [details]
(delay
(
(kind/reagentfn [{:keys [provider
['(
center
enriched-features]}]:div
[:style {:height "900px"}
{:ref (fn [el]
let [m (-> js/L
(map el)
(.
(.setView (clj->js center)11))]
-> js/L
(
.-tileLayer
(.provider provider)
(.addTo m))-> js/L
(
(.geoJson (clj->js enriched-features):style (fn [feature]
(clj->js {-> feature
(
.-properties
.-style))}))fn [layer]
(.bindTooltip (-> layer
(
.-feature
.-properties
.-tooltip)))
(.addTo m))))}])
details]:reagent/deps [:leaflet]}))) {
We pick a tile layer provider from leaflet-providers.
defn Seattle-choropleth-map [enriched-features]
(
(choropleth-map:provider "OpenStreetMap.Mapnik"
{:center Seattle-center
:enriched-features enriched-features}))
For our basic neighborhoods map:
delay
(
(Seattle-choropleth-map neighborhoods-enriched-features))