Skip to content

Commit 93569b5

Browse files
committed
raster landing page and reorder
1 parent 2d77ca6 commit 93569b5

19 files changed

+1694
-0
lines changed

_posts/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster01-subtract-rasters.md

Lines changed: 491 additions & 0 deletions
Large diffs are not rendered by default.

_posts/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster02-classify-raster.md

Lines changed: 653 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
---
2+
layout: single
3+
title: "Crop Spatial Raster Data With a Shapefile in Python"
4+
excerpt: "Sometimes a raster dataset covers a larger spatial extent than is needed for a particular purpose. In these cases, you can crop a raster file to a smaller extent. Learn how to crop raster data using a shapefile and export it as a new raster in open source Python"
5+
authors: ['Leah Wasser']
6+
dateCreated: 2018-02-05
7+
modified: 2020-02-14
8+
category: [courses]
9+
class-lesson: ['raster-processing-python']
10+
permalink: /courses/use-data-open-source-python/intro-raster-data-python/raster-data-processing/crop-raster-data-with-shapefile-in-python/
11+
nav-title: 'Crop Raster Data'
12+
week: 3
13+
course: 'intermediate-earth-data-science-textbook'
14+
sidebar:
15+
nav:
16+
author_profile: false
17+
comments: true
18+
order: 4
19+
topics:
20+
reproducible-science-and-programming: ['python']
21+
remote-sensing: ['lidar']
22+
spatial-data-and-gis: ['raster-data']
23+
redirect_from:
24+
- "/courses/earth-analytics-python/lidar-raster-data/crop-raster-data-with-shapefile-in-python/"
25+
---
26+
27+
{% include toc title="On This Page" icon="file-text" %}
28+
29+
<div class='notice--success' markdown="1">
30+
31+
## <i class="fa fa-graduation-cap" aria-hidden="true"></i> Learning Objectives
32+
33+
* Crop a raster dataset in **Python** using a vector extent object derived from a shapefile.
34+
* Open a shapefile in **Python**.
35+
36+
</div>
37+
38+
In previous lessons, you reclassified a raster in **Python**; however, the edges of your raster dataset were uneven.
39+
40+
In this lesson, you will learn how to crop a raster - to create a new raster
41+
object / file that you can share with colleagues and / or open in other tools such
42+
as a Desktop GIS tool like QGIS.
43+
44+
45+
## About Spatial Crop
46+
47+
Cropping (sometimes also referred to as clipping), is when you subset or make a dataset smaller,
48+
by removing all data outside of the crop area or spatial extent. In this case you have a large
49+
raster - but let's pretend that you only need to work with a smaller subset of the raster.
50+
51+
You can use the `crop_image` function to remove all of the data outside of your study area.
52+
This is useful as it:
53+
54+
1. Makes the data smaller and
55+
2. Makes processing and plotting faster
56+
57+
In general when you can, it's often a good idea to crop your raster data!
58+
59+
To begin let's load the libraries that you will need in this lesson.
60+
61+
62+
## Load Libraries
63+
64+
{:.input}
65+
```python
66+
import os
67+
import matplotlib.pyplot as plt
68+
import seaborn as sns
69+
import numpy as np
70+
from shapely.geometry import mapping
71+
import geopandas as gpd
72+
import rasterio as rio
73+
from rasterio.plot import plotting_extent
74+
from rasterio.mask import mask
75+
import earthpy as et
76+
import earthpy.spatial as es
77+
import earthpy.plot as ep
78+
79+
# Prettier plotting with seaborn
80+
sns.set(font_scale=1.5)
81+
82+
# Get data and set working directory
83+
et.data.get_data("colorado-flood")
84+
os.chdir(os.path.join(et.io.HOME, 'earth-analytics'))
85+
```
86+
87+
88+
## Open Raster and Vector Layers
89+
90+
In the previous lessons, you worked with a raster layer that looked like the one below. Notice that the data have an uneven edge on the left hand side. Let's pretend this edge is outside of your study area and you'd like to remove it or clip it off using your study area extent. You can do this using the `crop_image()` function in `earthpy.spatial`.
91+
92+
93+
{:.output}
94+
{:.display_data}
95+
96+
<figure>
97+
98+
<img src = "{{ site.url }}/images/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster03-crop-raster/2018-02-05-raster03-crop-raster_5_0.png" alt = "Canopy height model plot - uncropped.">
99+
<figcaption>Canopy height model plot - uncropped.</figcaption>
100+
101+
</figure>
102+
103+
104+
105+
106+
## Open Vector Layer
107+
108+
To begin your clip, open up a vector layer that contains the crop extent that you want
109+
to use to crop your data. To open a shapefile you use the `gpd.read_file()` function
110+
from geopandas. You will learn more about vector data in Python in a few weeks.
111+
112+
113+
{:.input}
114+
```python
115+
aoi = os.path.join("data", "colorado-flood", "spatial",
116+
"boulder-leehill-rd", "clip-extent.shp")
117+
118+
# Open crop extent (your study area extent boundary)
119+
crop_extent = gpd.read_file(aoi)
120+
```
121+
122+
Next, view the coordinate reference system (CRS) of both of your datasets.
123+
Remember that in order to perform any analysis with these two datasets together,
124+
they will need to be in the same CRS.
125+
126+
{:.input}
127+
```python
128+
print('crop extent crs: ', crop_extent.crs)
129+
print('lidar crs: ', lidar_chm.crs)
130+
```
131+
132+
{:.output}
133+
crop extent crs: {'init': 'epsg:32613'}
134+
lidar crs: EPSG:32613
135+
136+
137+
138+
139+
{:.input}
140+
```python
141+
# Plot the crop boundary layer
142+
# Note this is just an example so you can see what it looks like
143+
# You don't need to plot this layer in your homework!
144+
fig, ax = plt.subplots(figsize=(6, 6))
145+
146+
crop_extent.plot(ax=ax)
147+
148+
ax.set_title("Shapefile Crop Extent",
149+
fontsize=16)
150+
```
151+
152+
{:.output}
153+
{:.execute_result}
154+
155+
156+
157+
Text(0.5, 1, 'Shapefile Crop Extent')
158+
159+
160+
161+
162+
163+
{:.output}
164+
{:.display_data}
165+
166+
<figure>
167+
168+
<img src = "{{ site.url }}/images/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster03-crop-raster/2018-02-05-raster03-crop-raster_12_1.png" alt = "Plot of the shapefile that you will use to crop the CHM data.">
169+
<figcaption>Plot of the shapefile that you will use to crop the CHM data.</figcaption>
170+
171+
</figure>
172+
173+
174+
175+
176+
<figure>
177+
<a href="{{ site.url }}/images/earth-analytics/spatial-data/spatial-extent.png">
178+
<img src="{{ site.url }}/images/earth-analytics/spatial-data/spatial-extent.png" alt="The spatial extent of a shapefile the geographic edge or location that is the furthest north, south east and west."></a>
179+
<figcaption>The spatial extent of a shapefile represents the geographic "edge" or location that is the furthest north, south east and west. Thus is represents the overall geographic coverage of the spatial
180+
object. Image Source: Colin Williams, NEON.
181+
</figcaption>
182+
</figure>
183+
184+
185+
Now that you have imported the shapefile. You can use the `crop_image` function from `earthpy.spatial` to crop the raster data using the vector shapefile.
186+
187+
{:.input}
188+
```python
189+
fig, ax = plt.subplots(figsize=(10, 8))
190+
191+
ep.plot_bands(lidar_chm_im, cmap='terrain',
192+
extent=plotting_extent(lidar_chm),
193+
ax=ax,
194+
title="Raster Layer with Shapefile Overlayed",
195+
cbar=False)
196+
197+
crop_extent.plot(ax=ax, alpha=.8)
198+
199+
ax.set_axis_off()
200+
```
201+
202+
{:.output}
203+
{:.display_data}
204+
205+
<figure>
206+
207+
<img src = "{{ site.url }}/images/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster03-crop-raster/2018-02-05-raster03-crop-raster_14_0.png" alt = "Canopy height model with the crop shapefile overlayed. Note this image is just an illustration of what the two layers look like together. Below you will learn how to import the data and mask it rather than using the .read() method.">
208+
<figcaption>Canopy height model with the crop shapefile overlayed. Note this image is just an illustration of what the two layers look like together. Below you will learn how to import the data and mask it rather than using the .read() method.</figcaption>
209+
210+
</figure>
211+
212+
213+
214+
215+
216+
217+
## Crop Data Using the `crop_image` Function
218+
219+
If you want to crop the data you can use the `crop_image` function in `earthpy.spatial`. When you crop the data, you can then export it and share it with colleagues. Or use it in another analysis. IMPORTANT: You do not need to read the data in before cropping! Cropping the data can be your first step.
220+
221+
To perform the crop you:
222+
223+
1. Create a connection to the raster dataset that you wish to crop
224+
2. Open your shapefile as a geopandas object. This is what EarthPy needs to crop the data to the extent of your vector shapefile.
225+
3. Crop the data using the `crop_image()` function.
226+
227+
Without EarthPy, you would have to perform this with a Geojson object. Geojson is a format that is worth becoming familiar with. It's a text, structured format that is used in many online applications. We will discuss it in more detail later in the class. For now, have a look at the output below.
228+
229+
{:.input}
230+
```python
231+
lidar_chm_path = os.path.join("data", "colorado-flood", "spatial",
232+
"boulder-leehill-rd", "outputs", "lidar_chm.tif")
233+
234+
with rio.open(lidar_chm_path) as lidar_chm:
235+
lidar_chm_crop, lidar_chm_crop_meta = es.crop_image(lidar_chm,crop_extent)
236+
237+
lidar_chm_crop_affine = lidar_chm_crop_meta["transform"]
238+
239+
# Create spatial plotting extent for the cropped layer
240+
lidar_chm_extent = plotting_extent(lidar_chm_crop[0], lidar_chm_crop_affine)
241+
```
242+
243+
Finally, plot the cropped data. Does it look correct?
244+
245+
{:.input}
246+
```python
247+
# Plot your data
248+
ep.plot_bands(lidar_chm_crop[0],
249+
extent=lidar_chm_extent,
250+
cmap='Greys',
251+
title="Cropped Raster Dataset",
252+
scale=False)
253+
plt.show()
254+
```
255+
256+
{:.output}
257+
{:.display_data}
258+
259+
<figure>
260+
261+
<img src = "{{ site.url }}/images/courses/intermediate-earth-data-science-textbook/03-intro-raster/raster-processing-python/2018-02-05-raster03-crop-raster/2018-02-05-raster03-crop-raster_20_0.png" alt = "Final cropped canopy height model plot.">
262+
<figcaption>Final cropped canopy height model plot.</figcaption>
263+
264+
</figure>
265+
266+
267+
268+
269+
<div class="notice--info" markdown="1">
270+
## OPTIONAL -- Export Newly Cropped Raster
271+
272+
Once you have cropped your data, you may want to export it.
273+
In the subtract rasters lesson you exported a raster that had the same shape and transformation information as the parent rasters. However in this case, you have cropped your data. You will have to update several things to ensure your data export properly:
274+
275+
1. The width and height of the raster: You can get this information from the **shape** of the cropped numpy array and
276+
2. The transformation information of the affine object. The `crop_image()` function provides this inside the metadata object it returns!
277+
3. Finally you may want to update the `nodata` value.
278+
279+
In this case you don't have any `nodata` values in your raster. However you may have them in a future raster!
280+
</div>
281+
282+
{:.input}
283+
```python
284+
# Update with the new cropped affine info and the new width and height
285+
lidar_chm_meta.update({'transform': lidar_chm_crop_affine,
286+
'height': lidar_chm_crop.shape[1],
287+
'width': lidar_chm_crop.shape[2],
288+
'nodata': -999.99})
289+
lidar_chm_meta
290+
```
291+
292+
{:.output}
293+
{:.execute_result}
294+
295+
296+
297+
{'driver': 'GTiff', 'dtype': 'float64', 'nodata': -999.99, 'width': 3490, 'height': 2000, 'count': 1, 'crs': CRS.from_epsg(32613), 'transform': Affine(1.0, 0.0, 472510.0,
298+
0.0, -1.0, 4436000.0), 'tiled': False, 'compress': 'lzw', 'interleave': 'band'}
299+
300+
301+
302+
303+
304+
Once you have updated the metadata you can write our your new raster.
305+
306+
{:.input}
307+
```python
308+
# Write data
309+
path_out = os.path.join("data", "colorado-flood", "spatial",
310+
"outputs", "lidar_chm_cropped.tif")
311+
312+
with rio.open(path_out, 'w', **lidar_chm_meta) as ff:
313+
ff.write(lidar_chm_crop[0], 1)
314+
```
315+
316+
<div class="notice--warning" markdown="1">
317+
318+
## <i class="fa fa-pencil-square-o" aria-hidden="true"></i> Optional Challenge: Crop Change Over Time Layers
319+
320+
In the previous lesson, you created 2 plots:
321+
322+
1. A classified raster map that shows **positive and negative change** in the canopy
323+
height model before and after the flood. To do this you will need to calculate the
324+
difference between two canopy height models.
325+
2. A classified raster map that shows **positive and negative change** in terrain
326+
extracted from the pre and post flood Digital Terrain Models before and after the flood.
327+
328+
Create the same two plots except this time CROP each of the rasters that you plotted
329+
using the shapefile: `data/week-03/boulder-leehill-rd/crop_extent.shp`
330+
331+
For each plot, be sure to:
332+
333+
* Add a legend that clearly shows what each color in your classified raster represents.
334+
* Use proper colors.
335+
* Add a title to your plot.
336+
337+
You will include these plots in your final report due next week.
338+
</div>

0 commit comments

Comments
 (0)