|
25 | 25 | "\n", |
26 | 26 | "You can [🔗try it right now in JupyterLite](https://jupytergis.readthedocs.io/en/latest/lite/lab/)!\n", |
27 | 27 | "\n", |
28 | | - "Let's explore some functionality together (based on [🔗Carl Boettiger](https://ourenvironment.berkeley.edu/people/carl-boettiger)'s [🔗ESPM-288 course](https://espm-288.carlboettiger.info/)). We'll explore whether neighborhoods that were highly-rated (A) under the disciminatory 1930s practice of [🔗redlining](https://en.wikipedia.org/wiki/Redlining) are greener today than neighborhoods graded D?" |
| 28 | + "Let's explore some functionality together (based on [🔗Carl Boettiger](https://ourenvironment.berkeley.edu/people/carl-boettiger)'s [🔗ESPM-288 course](https://espm-288.carlboettiger.info/)). We'll explore the question: are neighborhoods that were highly-rated (A) under the disciminatory 1930s practice of [🔗redlining](https://en.wikipedia.org/wiki/Redlining) are greener today than neighborhoods graded D?\n", |
| 29 | + "\n", |
| 30 | + ":::{warning}\n", |
| 31 | + "Please be aware that JupyterGIS is a young project and it's likely you'll run in to bugs.\n", |
| 32 | + "We encourage you to try to break it and [report issues you find on GitHub](https://github.com/geojupyter/jupytergis/issues)!\n", |
| 33 | + ":::" |
29 | 34 | ] |
30 | 35 | }, |
31 | 36 | { |
|
72 | 77 | "source": [ |
73 | 78 | "from jupytergis import GISDocument\n", |
74 | 79 | "\n", |
75 | | - "jgis_project = GISDocument()\n", |
| 80 | + "jgis_project = GISDocument(\"new_haven_redlining_analysis.jGIS\")\n", |
76 | 81 | "jgis_project.add_raster_layer(\n", |
77 | | - " url=\"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n", |
| 82 | + " url=\"https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}@2x.png\",\n", |
78 | 83 | " name=\"Basemap\",\n", |
79 | 84 | ")\n", |
80 | 85 | "\n", |
| 86 | + "# Open this in a new panel on the right\n", |
81 | 87 | "jgis_project.sidecar()" |
82 | 88 | ] |
83 | 89 | }, |
| 90 | + { |
| 91 | + "cell_type": "markdown", |
| 92 | + "id": "40c7da62-6405-4bb7-8c81-ff950db59ff3", |
| 93 | + "metadata": {}, |
| 94 | + "source": [ |
| 95 | + "You should see a map on the right with a simple dark basemap.\n", |
| 96 | + "\n", |
| 97 | + "Try closing or shrinking the file browser on the left to make more room!" |
| 98 | + ] |
| 99 | + }, |
84 | 100 | { |
85 | 101 | "cell_type": "markdown", |
86 | 102 | "id": "b2133145-ef92-4d5e-84b8-a53546ef1fe1", |
|
108 | 124 | " con\n", |
109 | 125 | " .read_geo(\"/vsicurl/http://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\")\n", |
110 | 126 | " .filter(_.city == \"New Haven\", _.residential)\n", |
| 127 | + " # Add a numeric grade to provide a scalar value for JupyterGIS symbology\n", |
| 128 | + " # \"A\" is ascii value 65, so we subtract 64 to start with 1.\n", |
| 129 | + " # TODO: Do this in Pandas not DuckDB! (.astype('category').cat.codes)\n", |
| 130 | + " # Or: https://pandas.pydata.org/docs/reference/api/pandas.Series.map.html#pandas.Series.map\n", |
| 131 | + " .mutate(numeric_grade=_.grade.ascii_str() - 64)\n", |
111 | 132 | ")\n", |
112 | 133 | "\n", |
113 | 134 | "new_haven_redlining = redlines.execute().set_crs(\"EPSG:4326\")\n", |
114 | | - "# TODO: Set a numeric grade for graduated symbology\n", |
| 135 | + "new_haven_bbox = new_haven_redlining.total_bounds\n", |
115 | 136 | "\n", |
116 | 137 | "new_haven_redlining.to_file(INEQUALITY_GEOJSON_FILE, engine=\"fiona\")\n", |
| 138 | + "new_haven_redlining.head()" |
| 139 | + ] |
| 140 | + }, |
| 141 | + { |
| 142 | + "cell_type": "markdown", |
| 143 | + "id": "d5b8285d-b3c3-4dc8-b6dd-6c2c654ea806", |
| 144 | + "metadata": {}, |
| 145 | + "source": [ |
| 146 | + "You should notice that 5 rows of data are shown above. They're all residential neighborhoods with `grade` \"A\", meaning the neighborhoods were mostly occupied by white citizens. This grade was used to discriminate against non-white people seeking home loans.\n", |
| 147 | + "\n", |
| 148 | + "There is a `fill` column containing a hexadecimal color code, and a `geom` column containing polygon shapes.\n", |
117 | 149 | "\n", |
118 | | - "new_haven_bbox = new_haven_redlining.total_bounds" |
| 150 | + "There's an additional `numeric_grade` column which contains a number-encoded version of the `grade` column." |
119 | 151 | ] |
120 | 152 | }, |
121 | 153 | { |
|
125 | 157 | "source": [ |
126 | 158 | "#### Explore the data\n", |
127 | 159 | "\n", |
128 | | - "Let's explore the data a little bit. After running the cell below, **right-click the \"New Haven neighborhood redlining\" layer** in the JupyterGIS interface, and **select \"Zoom to layer\"**. " |
| 160 | + "Let's explore the data a little bit in JupyterGIS. First, add the data to the map:" |
129 | 161 | ] |
130 | 162 | }, |
131 | 163 | { |
|
141 | 173 | ");" |
142 | 174 | ] |
143 | 175 | }, |
| 176 | + { |
| 177 | + "cell_type": "markdown", |
| 178 | + "id": "d10bc86d-5c57-4b81-b52b-731bcd153059", |
| 179 | + "metadata": {}, |
| 180 | + "source": [ |
| 181 | + "You should notice that the layers panel on the left of the JupyterGIS UI contains a new layer: \"New Haven neighborhood redlining\"." |
| 182 | + ] |
| 183 | + }, |
144 | 184 | { |
145 | 185 | "cell_type": "markdown", |
146 | 186 | "id": "7663ab54-2d69-479b-b8ce-cbdc0bc13143", |
147 | 187 | "metadata": {}, |
148 | 188 | "source": [ |
| 189 | + ":::{important} In the JupyterGIS UI...\n", |
| 190 | + "After running the cell above, **right-click the \"New Haven neighborhood redlining\" layer** in the JupyterGIS interface, and **select \"Zoom to layer\"**. \n", |
| 191 | + "\n", |
149 | 192 | "Now, with the \"New Haven neighborhood redlining\" layer selected, **click the `i` (identify) icon in the toolbar** at the top of the JupyterGIS interface.\n", |
150 | 193 | "\n", |
151 | 194 | "Select some neighborhoods and view their \"Grade\" and \"Category\" attributes.\n", |
| 195 | + ":::" |
| 196 | + ] |
| 197 | + }, |
| 198 | + { |
| 199 | + "cell_type": "markdown", |
| 200 | + "id": "37e363b6-8fbc-4cf0-aa77-1d099872fca7", |
| 201 | + "metadata": {}, |
| 202 | + "source": [ |
| 203 | + "#### Configure symbology\n", |
| 204 | + "\n", |
| 205 | + "This dataset has a \"fill\" attribute which determines which color each polygon should be rendered with based on its grade.\n", |
| 206 | + "\n", |
| 207 | + "::::::{important} In the JupyterGIS UI...\n", |
152 | 208 | "\n", |
153 | | - "TODO: Symbologize on numeric grade" |
| 209 | + ":::{warning} Beware of bugs!\n", |
| 210 | + "The symbology menu is a bit fragile. Stick to the instructions and you'll be OK! If anything goes wrong, try removing the layer (right-click) and re-adding it.\n", |
| 211 | + ":::\n", |
| 212 | + "\n", |
| 213 | + "Right-click the layer \"New Haven neighborhood redlining\" and select **Edit Symbology**. This symbology mode renders each feature based on a data attribute containing a color.\n", |
| 214 | + "\n", |
| 215 | + "**Select \"Canonical\"** from the \"Render Type\" menu. The \"Value\" field should contain the \"fill\" attribute already, so **click OK**.\n", |
| 216 | + "\n", |
| 217 | + "You should notice that the neighborhoods ranked \"A\" are green, \"B\" are blue, \"C\" are yellow, and \"D\" are red.\n", |
| 218 | + "::::::" |
154 | 219 | ] |
155 | 220 | }, |
156 | 221 | { |
|
198 | 263 | " bbox=new_haven_bbox,\n", |
199 | 264 | " resolution=10,\n", |
200 | 265 | " groupby=\"solar_day\",\n", |
201 | | - " chunks = {}, # this tells odc to use dask\n", |
| 266 | + " chunks = {},\n", |
202 | 267 | ")\n", |
203 | 268 | "data" |
204 | 269 | ] |
205 | 270 | }, |
| 271 | + { |
| 272 | + "cell_type": "markdown", |
| 273 | + "id": "bb7cfddd-8a32-447c-97f2-823a86b5531f", |
| 274 | + "metadata": {}, |
| 275 | + "source": [ |
| 276 | + "You should notice that the two requested data variables (near-infrared and red) are present in the xarray DataSet." |
| 277 | + ] |
| 278 | + }, |
206 | 279 | { |
207 | 280 | "cell_type": "markdown", |
208 | 281 | "id": "81a2b33a-117d-411b-8f73-4dd873252738", |
|
252 | 325 | " \"EPSG:4326\",\n", |
253 | 326 | ").rio.to_raster(\n", |
254 | 327 | " raster_path=NDVI_GEOTIFF_FILE, \n", |
255 | | - " driver=\"COG\",\n", |
| 328 | + " driver=\"COG\", # Cloud-Optimized GeoTIFF\n", |
256 | 329 | ")" |
257 | 330 | ] |
258 | 331 | }, |
|
264 | 337 | "#### Explore the data\n", |
265 | 338 | "\n", |
266 | 339 | "Let's explore the data in JupyterGIS again.\n", |
267 | | - "This time, we'll add the layer with the GUI.\n", |
| 340 | + "This time, we'll add the NDVI data as a layer with the GUI.\n", |
| 341 | + "\n", |
| 342 | + "::::::{important} In the JupyterGIS UI...\n", |
| 343 | + "\n", |
| 344 | + ":::{warning} Beware of bugs!\n", |
| 345 | + "The symbology menu is a bit fragile. You may see some things that look a little weird, but stick to the instructions and you'll be OK! If anything goes wrong, try removing the layer (right-click) and re-adding it.\n", |
| 346 | + ":::\n", |
268 | 347 | "\n", |
269 | 348 | "If the \"identify\" tool is still active, click the `i` icon in the toolbar again to disable it.\n", |
270 | 349 | "\n", |
|
281 | 360 | "\n", |
282 | 361 | "Scroll down to **input the layer name as \"NDVI\"**.\n", |
283 | 362 | "\n", |
| 363 | + "**Click \"OK\"** to add the layer to the map.\n", |
| 364 | + "\n", |
| 365 | + "You should notice that the data doesn't look right -- it's entirely black-and-white.\n", |
| 366 | + "\n", |
| 367 | + "We need to set up the symbology now.\n", |
| 368 | + "\n", |
284 | 369 | "Finally, **right-click the \"NDVI\" layer** and **select \"Edit Symbology\"**. The symbology menu may take a moment to load. Be patient! **Select \"Classify\" then click \"OK\".**\n", |
285 | 370 | "\n", |
286 | 371 | "The brighter areas have a higher NDVI value, and the darker areas have a lower one.\n", |
287 | 372 | "\n", |
288 | | - "We can use the identify tool (`i` icon in the toolbar) to explore the raw values." |
| 373 | + "We can use the identify tool (`i` icon in the toolbar) to explore the raw values.\n", |
| 374 | + "\n", |
| 375 | + "You might notice that the colored polygons show through in areas where the NDVI is `NaN`. You can hide that layer with the 👁️ icon in the JupyterGIS layers panel.\n", |
| 376 | + "::::::" |
289 | 377 | ] |
290 | 378 | }, |
291 | 379 | { |
292 | 380 | "cell_type": "markdown", |
293 | 381 | "id": "41a3f550-04da-4170-9e69-b4131d428fdd", |
294 | 382 | "metadata": {}, |
295 | 383 | "source": [ |
296 | | - "### Calculating mean NDVI for each New Haven neighborhood\n", |
| 384 | + "### Zonal Statistics: Calculating mean NDVI for each New Haven neighborhood\n", |
297 | 385 | "\n", |
298 | | - "To find out whether neighborhoods graded \"A\" are greener than neighborhoods graded \"D\", we'll calculate the mean NDVI for each neighborhood using [🔗exactextract](https://isciences.github.io/exactextract/background.html), which is known for its capability to include fractional grid cells in its calculation (as opposed to other tools, where a cell is binary, either in or out)." |
| 386 | + "To find out whether neighborhoods graded \"A\" are greener than neighborhoods graded \"D\", we'll calculate the mean NDVI for each neighborhood using [🔗exactextract](https://isciences.github.io/exactextract/background.html), which is known for its capability to include fractional grid cells in its calculation (as opposed to other tools, where a cell is binary, either inside or outside the polygon)." |
299 | 387 | ] |
300 | 388 | }, |
301 | 389 | { |
|
310 | 398 | "new_haven_redlining_and_ndvi = exact_extract(\n", |
311 | 399 | " NDVI_GEOTIFF_FILE,\n", |
312 | 400 | " new_haven_redlining,\n", |
313 | | - " \"mean_ndvi=mean\",\n", |
| 401 | + " \"mean_ndvi=mean\", # Give the column a human-readable name!\n", |
314 | 402 | " include_geom = True,\n", |
315 | 403 | " include_cols=[\"label\", \"grade\", \"city\", \"fill\"],\n", |
316 | 404 | " output=\"pandas\",\n", |
317 | 405 | ")\n", |
318 | 406 | "\n", |
319 | 407 | "new_haven_redlining_and_ndvi.set_crs(\n", |
320 | 408 | " \"EPSG:4326\"\n", |
321 | | - ").to_file(INEQUALITY_NDVI_GEOJSON_FILE, engine=\"fiona\")" |
| 409 | + ").to_file(INEQUALITY_NDVI_GEOJSON_FILE, engine=\"fiona\")\n", |
| 410 | + "\n", |
| 411 | + "new_haven_redlining_and_ndvi.head()" |
| 412 | + ] |
| 413 | + }, |
| 414 | + { |
| 415 | + "cell_type": "markdown", |
| 416 | + "id": "2d9542f4-131a-4fb4-9328-7b7a2981510b", |
| 417 | + "metadata": {}, |
| 418 | + "source": [ |
| 419 | + "You should notice that the dataset now includes a `mean_ndvi` column with the results of our zonal statistics calculation! 🎉" |
322 | 420 | ] |
323 | 421 | }, |
324 | 422 | { |
|
328 | 426 | "metadata": {}, |
329 | 427 | "outputs": [], |
330 | 428 | "source": [ |
331 | | - "# jgis_project.add_geojson_layer(\n", |
332 | | - "# path=INEQUALITY_NDVI_GEOJSON_FILE,\n", |
333 | | - "# name=\"New Haven neighborhood redlining w/ NDVI\",\n", |
334 | | - "# ) # TODO" |
| 429 | + "jgis_project.add_geojson_layer(\n", |
| 430 | + " path=INEQUALITY_NDVI_GEOJSON_FILE,\n", |
| 431 | + " name=\"New Haven neighborhood redlining w/ NDVI\",\n", |
| 432 | + ")" |
| 433 | + ] |
| 434 | + }, |
| 435 | + { |
| 436 | + "cell_type": "markdown", |
| 437 | + "id": "7a1d4e12-9363-4625-9001-bc6aef655a7f", |
| 438 | + "metadata": {}, |
| 439 | + "source": [ |
| 440 | + "Notice that there are now two copies of the New Haven neighborhoods polygon layer. The new one is titled the same as the old one with \"w/ NDVI\" appended.\n", |
| 441 | + "\n", |
| 442 | + "::::::{important} In the JupyterGIS UI...\n", |
| 443 | + "\n", |
| 444 | + ":::{warning} Beware of bugs!\n", |
| 445 | + "If the new redlining w/ NDVI layer disappears, remove it from the map by right-clicking it in the JupyterGIS layers panel and selecting \"Remove Layer\". Then re-run the cell above.\n", |
| 446 | + ":::\n", |
| 447 | + "\n", |
| 448 | + "Temporarily hide the \"NDVI\" raster layer with the 👁️ icon in the JupyterGIS layers panel so you can more clearly see the new layer.\n", |
| 449 | + "\n", |
| 450 | + "Hide the \"old\" copy of the redlining layer (\"New Haven neighborhood redlining\") with the 👁️ icon in the JupyterGIS layers panel.\n", |
| 451 | + ":::::::\n", |
| 452 | + "\n" |
| 453 | + ] |
| 454 | + }, |
| 455 | + { |
| 456 | + "cell_type": "markdown", |
| 457 | + "id": "0e66de07-3861-414e-b2e0-d3d320d4673c", |
| 458 | + "metadata": {}, |
| 459 | + "source": [ |
| 460 | + "### Visualizing correlation\n", |
| 461 | + "\n", |
| 462 | + "Now that we have neighborhood grades and mean NDVI for each neighborhood, how do we visualize any potential correlation?\n", |
| 463 | + "\n", |
| 464 | + "TODO: Bivariate choropleth?\n", |
| 465 | + "\n", |
| 466 | + "TODO: ?" |
335 | 467 | ] |
336 | 468 | }, |
337 | 469 | { |
|
0 commit comments