Learning Objectives

  • Subset bands by index or name

  • Extract raster data by row and column number

  • Extract data by bounding window

  • Extract raster data by coordinates

  • Extract raster data by geometry (point, polygon)


Raster Data Extraction#

Raster data is often of little use unless we can extract and summarize the data. For instance, extracting raster to points by coordinates allows us to pass data to machine learning models for land cover classification or cloud masking.

Subsetting rasters#

We can subset sections of the data array in a number of ways. In this case we will create a slice based on row and column location to subset LandSat data using a rasterio.window.Window.

Either a rasterio.window.Window object or tuple can be used with geowombat.open.

import geowombat as gw
from geowombat.data import rgbn

from rasterio.windows import Window
w = Window(row_off=0, col_off=0, height=100, width=100)

with gw.open(rgbn,
                band_names=['blue', 'green', 'red'],
                num_workers=8,
                indexes=[1, 2, 3],
                window=w,
                out_dtype='float32') as src:
    print(src)
/home/mmann1123/miniconda3/envs/pygis/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
<xarray.DataArray 'array-031cc8ffc8116663509aa591950f4f3b' (band: 3, y: 100,
                                                            x: 100)> Size: 120kB
dask.array<array, shape=(3, 100, 100), dtype=float32, chunksize=(3, 64, 64), chunktype=numpy.ndarray>
Coordinates:
  * band     (band) <U5 60B 'blue' 'green' 'red'
  * y        (y) float64 800B 2.05e+06 2.05e+06 2.05e+06 ... 2.05e+06 2.05e+06
  * x        (x) float64 800B 7.93e+05 7.93e+05 7.93e+05 ... 7.935e+05 7.935e+05
Attributes:
    transform:           | 5.00, 0.00, 792988.00|\n| 0.00,-5.00, 2050382.00|\...
    crs:                 +init=epsg:32618
    res:                 (5.0, 5.0)
    is_tiled:            1
    nodatavals:          (nan, nan, nan, nan)
    offsets:             (0.0, 0.0, 0.0, 0.0)
    _data_are_separate:  0
    _data_are_stacked:   0

We can also slice a subset of data using a tuple of bounded coordinates.

bounds = (793475.76, 2049033.03, 794222.03, 2049527.24)

with gw.open(rgbn,
                band_names=['green', 'red', 'nir'],
                num_workers=8,
                indexes=[2, 3, 4],
                bounds=bounds,
                out_dtype='float32') as src:
    print(src)

The configuration manager provides an alternative method to subset rasters. See tutorial-config for more details.

with gw.config.update(ref_bounds=bounds):

    with gw.open(rgbn) as src:
        print(src)

By default, the subset will be returned by the upper left coordinates of the bounds, potentially shifting cell alignment with the reference raster. To subset a raster and align it to the same grid, use the ref_tar keyword. This is equivalent to a “snap raster” in ArcGIS.

with gw.config.update(ref_bounds=bounds, ref_tar=rgbn):

    with gw.open(rgbn) as src:
        print(src)

Extracting data by coordinates#

To extract values at a coordinate pair, translate the coordinates into array indices. For extraction by geometry, for instance with a shapefile, see extract by point geometry.

import geowombat as gw
from geowombat.data import l8_224078_20200518

# Coordinates in map projection units
y, x = -2823031.15, 761592.60

with gw.open(l8_224078_20200518) as src:
    # Transform the map coordinates to data indices
    j, i = gw.coords_to_indices(x, y, src)
    # Subset by index
    data = src[:, i, j].data.compute()

print(data.flatten())
[7448 6882 6090]

A latitude/longitude pair can be extracted after converting to the map projection.

import geowombat as gw
from geowombat.data import l8_224078_20200518

# Coordinates in latitude/longitude
lat, lon = -25.50142964, -54.39756038

with gw.open(l8_224078_20200518) as src:
    # Transform the coordinates to map units
    x, y = gw.lonlat_to_xy(lon, lat, src)
    # Transform the map coordinates to data indices
    j, i = gw.coords_to_indices(x, y, src)
    data = src[:, i, j].data.compute()

print(data.flatten())
[7448 6882 6090]

Extracting data with point geometry#

In the example below, ‘l8_224078_20200518_points’ is a GeoPackage of point locations, and the output df is a GeoPandas GeoDataFrame. To extract the raster values at the point locations, use geowombat.extract.

import geowombat as gw
from geowombat.data import l8_224078_20200518, l8_224078_20200518_points

with gw.open(l8_224078_20200518) as src:
    df = src.gw.extract(l8_224078_20200518_points)

print(df)
        name                         geometry  id     1     2     3
0      water  POINT (741522.314 -2811204.698)   0  7966  7326  6254
1       crop  POINT (736140.845 -2806478.364)   1  8030  7490  8080
2       tree  POINT (745919.508 -2805168.579)   2  7561  6874  6106
3  developed  POINT (739056.735 -2811710.662)   3  8302  8202  8111
4      water  POINT (737802.183 -2818016.412)   4  8277  7982  7341
5       tree   POINT (759209.443 -2828566.23)   5  7398  6711  6007

Note

The line df = src.gw.extract(l8_224078_20200518_points) could also have been written as df = gw.extract(src, l8_224078_20200518_points).

In the previous example, the point vector had a CRS that matched the raster (i.e., EPSG=32621, or UTM zone 21N). If the CRS had not matched, the geowombat.extract function transforms the CRS on-the-fly.

import geowombat as gw
from geowombat.data import l8_224078_20200518, l8_224078_20200518_points
import geopandas as gpd

point_df = gpd.read_file(l8_224078_20200518_points)
print(point_df.crs)

# Transform the CRS to WGS84 lat/lon
point_df = point_df.to_crs('epsg:4326')
print(point_df.crs)

with gw.open(l8_224078_20200518) as src:
    df = src.gw.extract(point_df)

print(df)
EPSG:32621
epsg:4326
        name                         geometry  id     1     2     3
0      water  POINT (741522.314 -2811204.698)   0  7966  7326  6254
1       crop  POINT (736140.845 -2806478.364)   1  8030  7490  8080
2       tree  POINT (745919.508 -2805168.579)   2  7561  6874  6106
3  developed  POINT (739056.735 -2811710.662)   3  8302  8202  8111
4      water  POINT (737802.183 -2818016.412)   4  8277  7982  7341
5       tree   POINT (759209.443 -2828566.23)   5  7398  6711  6007

Set the data band names using sensor = 'bgr', which assigns the band names blue, green, red.

import geowombat as gw
from geowombat.data import l8_224078_20200518, l8_224078_20200518_points

with gw.config.update(sensor='bgr'):
    with gw.open(l8_224078_20200518) as src:
        df = src.gw.extract(l8_224078_20200518_points,
                            band_names=src.band.values.tolist())

print(df)
        name                         geometry  id  blue  green   red
0      water  POINT (741522.314 -2811204.698)   0  7966   7326  6254
1       crop  POINT (736140.845 -2806478.364)   1  8030   7490  8080
2       tree  POINT (745919.508 -2805168.579)   2  7561   6874  6106
3  developed  POINT (739056.735 -2811710.662)   3  8302   8202  8111
4      water  POINT (737802.183 -2818016.412)   4  8277   7982  7341
5       tree   POINT (759209.443 -2828566.23)   5  7398   6711  6007

Extracting time series images by point geometry#

We can also easily extract a time series of raster images. Extracted pixel values are provided in ‘wide’ format with appropriate labels, for instance the column ‘t2_blue’ would be the blue band for the second time period

from geowombat.data import l8_224078_20200518, l8_224078_20200518_points

with gw.config.update(sensor='bgr'):
    with gw.open([l8_224078_20200518, l8_224078_20200518],
            time_names=['t1', 't2'],
            stack_dim='time') as src:

        # Extract and by point geometry
        df = src.gw.extract(l8_224078_20200518_points)

print(df)
        name                         geometry  id  t1_blue  t1_green  t1_red  \
0      water  POINT (741522.314 -2811204.698)   0     7966      7326    6254   
1       crop  POINT (736140.845 -2806478.364)   1     8030      7490    8080   
2       tree  POINT (745919.508 -2805168.579)   2     7561      6874    6106   
3  developed  POINT (739056.735 -2811710.662)   3     8302      8202    8111   
4      water  POINT (737802.183 -2818016.412)   4     8277      7982    7341   
5       tree   POINT (759209.443 -2828566.23)   5     7398      6711    6007   

   t2_blue  t2_green  t2_red  
0     7966      7326    6254  
1     8030      7490    8080  
2     7561      6874    6106  
3     8302      8202    8111  
4     8277      7982    7341  
5     7398      6711    6007  

Extracting data by polygon geometry#

To extract values within polygons, use the same geowombat.extract function.

from geowombat.data import l8_224078_20200518, l8_224078_20200518_polygons

with gw.config.update(sensor='bgr'):
    with gw.open(l8_224078_20200518) as src:
        df = src.gw.extract(l8_224078_20200518_polygons,
                            band_names=src.band.values.tolist())

    print(df)
     id  point                         geometry       name   blue  green  \
0     0      0  POINT (737559.502 -2795247.772)      water   7994   7423   
1     0      1  POINT (737589.502 -2795247.772)      water   8017   7428   
2     0      2  POINT (737619.502 -2795247.772)      water   8008   7446   
3     0      3  POINT (737649.502 -2795247.772)      water   8008   7412   
4     0      4  POINT (737679.502 -2795247.772)      water   8018   7398   
..   ..    ...                              ...        ...    ...    ...   
667   3    667  POINT (739038.667 -2811819.677)  developed   8567   8564   
668   3    668  POINT (739068.667 -2811819.677)  developed   8099   7676   
669   3    669  POINT (739098.667 -2811819.677)  developed  10151   9651   
670   3    670  POINT (739128.667 -2811819.677)  developed   8065   7735   
671   3    671  POINT (739158.667 -2811819.677)  developed   9343   8987   

       red  
0     6272  
1     6292  
2     6292  
3     6291  
4     6250  
..     ...  
667   8447  
668   7332  
669  10153  
670   7501  
671   9247  

[672 rows x 7 columns]

Calculate mean pixel value by polygon#

It is simple then to calculate the mean value of pixels within each polygon by using the polygon id column and pandas groupby function. You can easily calculate other statistics like min, max, median etc.

from geowombat.data import l8_224078_20200518, l8_224078_20200518_polygons

with gw.config.update(sensor='bgr'):
    with gw.open(l8_224078_20200518) as src:
        df = src.gw.extract(l8_224078_20200518_polygons,
                            band_names=src.band.values.tolist())
        # use pandas groupby to calc pixel mean  
        df.drop(columns=['geometry'], inplace=True)
        df = df.groupby(['id', 'name']).mean()
    print(df)
              point         blue        green          red
id name                                                   
0  water      103.5  7990.038462  7387.918269  6264.846154
1  crop       304.0  7692.481865  7037.419689  7571.207254
2  tree       497.0  7506.901554  6838.704663  6091.932642
3  developed  632.5  8698.397436  8328.294872  8365.487179