Classified Lidar Pre/Post-Flood Canopy and Terrain Height Difference Models

Daniel Fewell

Question 1

In [41]:
import rioxarray as rxr
import xarray as xr
import geopandas as gpd
import earthpy.plot as ep
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.colors import ListedColormap, BoundaryNorm
import earthpy as et
import numpy as np

import seaborn as sns
sns.set(font_scale=1.5, style="whitegrid")

Importing necessary libraries for project.

In [42]:
myTif = rxr.open_rasterio('../earth-analytics/data/colorado-flood/spatial/outputs/pre-flood-chm.tif',
                          masked=True).squeeze()
myTif
Out[42]:
<xarray.DataArray (y: 2000, x: 4000)>
[8000000 values with dtype=float32]
Coordinates:
    band         int64 1
  * x            (x) float64 4.72e+05 4.72e+05 4.72e+05 ... 4.76e+05 4.76e+05
  * y            (y) float64 4.436e+06 4.436e+06 ... 4.434e+06 4.434e+06
    spatial_ref  int64 0
Attributes:
    scale_factor:  1.0
    add_offset:    0.0

Opening pre-flood canopy height model and viewing data array.

In [43]:
print("The CRS for this data is:", myTif.rio.crs)
print("The no data value is:", myTif.rio.nodata)
print("The spatial extent is:", myTif.rio.bounds())
The CRS for this data is: EPSG:32613
The no data value is: None
The spatial extent is: (472000.0, 4434000.0, 476000.0, 4436000.0)

The CRS is correct.

In [44]:
myTif.plot(cmap=plt.cm.BuPu_r)
plt.show()
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1445: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  and not np.issubdtype(x.dtype, str)
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1460: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  and not np.issubdtype(y.dtype, str)

Final plot with blue-purple color bar.

Question 2A

In [45]:
pre_DTM = rxr.open_rasterio('../earth-analytics/data/colorado-flood/spatial/boulder-leehill-rd/pre-flood/lidar/pre_DTM.tif',
                          masked=True).squeeze()
pre_DSM = rxr.open_rasterio('../earth-analytics/data/colorado-flood/spatial/boulder-leehill-rd/pre-flood/lidar/pre_DSM.tif',
                          masked=True).squeeze()

post_DTM = rxr.open_rasterio('../earth-analytics/data/colorado-flood/spatial/boulder-leehill-rd/post-flood/lidar/post_DTM.tif',
                          masked=True).squeeze()

post_DSM = rxr.open_rasterio('../earth-analytics/data/colorado-flood/spatial/boulder-leehill-rd/post-flood/lidar/post_DSM.tif',
                          masked=True).squeeze()

This cell loads the terrain and surface models from before and after the flood.

In [46]:
# canopy height models (CHM)
# pre
pre_CHM = pre_DSM - pre_DTM

#post
post_CHM = post_DSM - post_DTM

This cell creates two canopy height models: one from before the flood and one from after.

In [47]:
canopy_difference = post_CHM - pre_CHM

This cell creates a data array of the canopy height difference by subtracting the height of the canopy before the flood from the height of the canopy after the flood.

In [48]:
print('min value:', np.nanmin(canopy_difference))
print('max value:', np.nanmax(canopy_difference))
min value: -23.429932
max value: 24.45996

Here I am checking the min and max values of the data which I will need to know when creating the bins in the next step.

In [49]:
class_bins = [-25, 0, 25]

canopy_difference_class = xr.apply_ufunc(np.digitize,
                                     canopy_difference,
                                     class_bins)

In this cell I am creating a classified version of the data. The value '0' will be included in the category 'Positive Change'.

In [50]:
canopy_difference_class_ma = canopy_difference_class.where(canopy_difference_class != 3)

In this cell, I am creating a new data array object by selecting all data except for the data classified as'nodata'.

In [51]:
class_labels = ["Negative Change",
                "Positive Change"]

colors = ['maroon',
          'gold']

cmap = ListedColormap(colors)

class_bins = [.5, 1.5, 2.5]

norm = BoundaryNorm(class_bins,
                    len(colors))

# Plot newly classified and masked raster
f, ax = plt.subplots(figsize=(10, 5))
im = canopy_difference_class_ma.plot.imshow(cmap=cmap,
                                        norm=norm,
                                        # Turn off colorbar
                                        add_colorbar=False)
# Add legend using earthpy
ep.draw_legend(im,
               titles=class_labels)
ax.set(title="Classified Lidar Pre/Post-Flood Canopy Height Difference Model \n Derived from NEON AOP Data")
ax.set_axis_off()
plt.show()
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1344: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  if np.issubdtype(x.dtype, str):
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1395: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  if np.issubdtype(v.dtype, str):

In this cell I set the class labels, colors and bins for plotting the classified and masked data. Then I plotted it. The reason I included 0 height difference in the 'Positive Change' category is because I think that this would be considered a positive outcome in a real world situation. I also added a legend and a title, and turned off the axis.

Question 2B

In [52]:
terrain_change = post_DTM - pre_DTM

This cell creates a data array of the terrain height difference by subtracting the height of the terrain before the flood from the height of the terrain after the flood.

In [53]:
print('min value:', np.nanmin(terrain_change))
print('max value:', np.nanmax(terrain_change))
min value: -19.609985
max value: 47.100098

Here I am checking the min and max values of the data which I will need to know when creating the bins in the next step.

In [54]:
class_bins = [-20, 0, 50]

terrain_change_class = xr.apply_ufunc(np.digitize,
                                     terrain_change,
                                     class_bins)

In this cell I am creating a classified version of the data. The value '0' will be included in the category 'Positive Change'.

In [55]:
terrain_change_class_ma = terrain_change_class.where(terrain_change_class != 3)

In this cell, I am creating a new data array object by selecting all data except for the data classified as 'nodata'.

In [56]:
class_labels = ["Negative Change",
                "Positive Change"]

colors = ['maroon',
          'gold']

cmap = ListedColormap(colors)

class_bins = [.5, 1.5, 2.5]

norm = BoundaryNorm(class_bins,
                    len(colors))

# Plot newly classified and masked raster
f, ax = plt.subplots(figsize=(10, 5))
im = terrain_change_class_ma.plot.imshow(cmap=cmap,
                                        norm=norm,
                                        # Turn off colorbar
                                        add_colorbar=False)
# Add legend using earthpy
ep.draw_legend(im,
               titles=class_labels)
ax.set(title="Classified Lidar Pre/Post-Flood Terrain Change Model \n Derived from NEON AOP Data")
ax.set_axis_off()
plt.show()
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1344: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  if np.issubdtype(x.dtype, str):
/opt/conda/lib/python3.7/site-packages/xarray/plot/plot.py:1395: FutureWarning: Conversion of the second argument of issubdtype from `str` to `str` is deprecated. In future, it will be treated as `np.str_ == np.dtype(str).type`.
  if np.issubdtype(v.dtype, str):

In this cell I set the class labels, colors and bins for plotting the classified and masked data. Then I plotted it. Again, the reason I included 0 height difference in the 'Positive Change' category is because I think that this would be considered a positive outcome in a real world situation. I also added a legend and a title, and turned off the axis.