⚠️ Our 0.1 release refactored several early-development functions for long-term stability, to update your code see here. ⚠️
Future changes will come with deprecation warnings! 🙂

Feature overview#

The following presents a descriptive example show-casing all core features of GeoUtils.

For more details, refer to the Fundamentals, Rasters or Vectors pages.

Tip

All pages of this documentation containing code cells can be run interactively online without the need of setting up your own environment. Simply click the top launch button! (MyBinder can be a bit capricious: you might have to be patient, or restart it after the build is done the first time 😅)

Alternatively, start your own notebook to test GeoUtils at Binder.

The core Raster and Vector classes#

In GeoUtils, geospatial handling is object-based and revolves around Raster and Vector. These link to either on-disk or in-memory datasets, opened by calling the object with a file path.

import geoutils as gu

# Examples files: infrared band of Landsat and glacier outlines
filename_rast = gu.examples.get_path("everest_landsat_b4")
filename_vect = gu.examples.get_path("everest_rgi_outlines")

# Open files by calling Raster and Vector
rast = gu.Raster(filename_rast)
vect = gu.Vector(filename_vect)

A Raster is an object with four main attributes: a numpy.ma.MaskedArray as data, a pyproj.crs.CRS as crs, an affine.Affine as transform, and a float or int as nodata.

# The opened raster
rast
Raster(
  data=not_loaded; shape on disk (1, 655, 800); will load (655, 800)
  transform=| 30.00, 0.00, 478000.00|
            | 0.00,-30.00, 3108140.00|
            | 0.00, 0.00, 1.00|
  crs=EPSG:32645
  nodata=None)

Important

When a file exists on disk, Raster is linked to a rasterio.io.DatasetReader object for loading the metadata. The array will be loaded in-memory implicitly when data is required by an operation.

See Implicit lazy loading for more details.

A Vector is an object with a single main attribute: a GeoDataFrame as ds, for which most methods are wrapped directly into Vector.

# The opened vector
vect
Vector(
  ds=             RGIId         GLIMSId   BgnDate   EndDate     CenLon     CenLat  \
       0   RGI60-15.03410  G086932E27925N  20001030  -9999999  86.932083  27.924758   
       1   RGI60-15.03411  G086939E27924N  20001030  -9999999  86.939035  27.923919   
       2   RGI60-15.03412  G086852E27949N  20001030  -9999999  86.851810  27.948727   
       3   RGI60-15.03413  G086848E27952N  20001030  -9999999  86.848051  27.951937   
       4   RGI60-15.03414  G086849E27966N  20001030  -9999999  86.848799  27.966443   
       ..             ...             ...       ...       ...        ...        ...   
       81  RGI60-15.10070  G086956E28089N  20100409  -9999999  86.956000  28.089000   
       82  RGI60-15.10075  G086960E28045N  20100409  -9999999  86.960000  28.045000   
       83  RGI60-15.10077  G086963E28079N  20100409  -9999999  86.963000  28.079000   
       84  RGI60-15.10078  G086966E28051N  20100409  -9999999  86.966000  28.051000   
       85  RGI60-15.10079  G086969E28097N  20100409  -9999999  86.969000  28.097000   
       
          O1Region O2Region    Area  Zmin  ...  Aspect  Lmax  Status  Connect  Form  \
       0        15        2   0.980  5196  ...     310  1184       0        0     0   
       1        15        2   0.064  5831  ...      78   309       0        0     0   
       2        15        2   0.059  5509  ...      29   269       0        0     0   
       3        15        2   0.176  5400  ...     341   790       0        0     0   
       4        15        2   0.477  5280  ...     284   845       0        0     0   
       ..      ...      ...     ...   ...  ...     ...   ...     ...      ...   ...   
       81       15        2  12.646  5940  ...     266  5413       0        0     1   
       82       15        2   0.143  6530  ...     262   426       0        0     0   
       83       15        2   0.051  6420  ...     307   395       0        0     0   
       84       15        2   0.181  6559  ...     321   461       0        0     0   
       85       15        2   0.136  6538  ...     203   571       0        0     0   
       
           TermType  Surging  Linkages                               Name  \
       0          0        9         9                               None   
       1          0        9         9                               None   
       2          0        9         9                               None   
       3          0        9         9                               None   
       4          0        9         9                               None   
       ..       ...      ...       ...                                ...   
       81         0        9         9  CN5O193B0118 East Rongbuk Glacier   
       82         0        9         9  CN5O193B0118 East Rongbuk Glacier   
       83         0        9         9  CN5O193B0118 East Rongbuk Glacier   
       84         0        9         9  CN5O193B0118 East Rongbuk Glacier   
       85         0        9         9  CN5O193B0118 East Rongbuk Glacier   
       
                                                    geometry  
       0   POLYGON ((86.92842 27.92877, 86.92866 27.92930...  
       1   POLYGON ((86.94077 27.92375, 86.94059 27.92295...  
       2   POLYGON ((86.85344 27.94752, 86.85340 27.94752...  
       3   POLYGON ((86.84776 27.94845, 86.84776 27.94849...  
       4   POLYGON ((86.84567 27.96906, 86.84567 27.96910...  
       ..                                                ...  
       81  POLYGON ((86.94112 28.11455, 86.94129 28.11452...  
       82  POLYGON ((86.95861 28.04669, 86.95847 28.04701...  
       83  POLYGON ((86.96454 28.07843, 86.96445 28.07814...  
       84  POLYGON ((86.96302 28.04915, 86.96304 28.04920...  
       85  POLYGON ((86.97074 28.09496, 86.97039 28.09488...  
       
       [86 rows x 23 columns]
  crs=EPSG:4326
  bounds=BoundingBox(left=86.70393205700003, bottom=27.847778695000045, right=87.11409458600008, top=28.14549759700003))

All other attributes are derivatives of those main attributes, or of the filename on disk. Attributes of Raster and Vector update with geospatial operations on themselves.

Handling and match-reference#

In GeoUtils, geospatial handling operations are based on class methods, such as crop() or reproject().

For convenience and consistency, nearly all of these methods can be passed solely another Raster or Vector as a reference to match during the operation. A reference Vector enforces a matching of bounds and/or crs, while a reference Raster can also enforce a matching of res, depending on the nature of the operation.

# Print initial bounds of the vector
print(vect.bounds)
# Crop vector to raster's extent, and add clipping option (otherwise keeps all intersecting features)
vect_cropped = vect.crop(rast, clip=True)
# Print bounds of cropped + clipped vector
print(vect_cropped.bounds)
BoundingBox(left=86.70393205700003, bottom=27.847778695000045, right=87.11409458600008, top=28.14549759700003)
BoundingBox(left=86.77604281922927, bottom=27.921168931302088, right=87.02035980673882, top=28.098736524870052)

Additionally, in GeoUtils, methods that apply to the same georeferencing attributes have consistent naming1 across Raster and Vector.

A reproject() involves a change in crs or transform, while a crop() only involves a change in bounds. Using polygonize() allows to generate a Vector from a Raster, and the other way around for rasterize().

Class

Raster

Vector

Warping/Reprojection

reproject()

reproject()

Cropping/Clipping

crop()

crop()

Rasterize/Polygonize

polygonize()

rasterize()

All methods can also be passed any number of georeferencing arguments such as shape or res, and will naturally deduce others from the input Raster or Vector, much as in GDAL’s command line.

Higher-level analysis tools#

GeoUtils also implements higher-level geospatial analysis tools for both Rasters and Vectors. For example, one can compute the distance to a Vector geometry, or to target pixels of a Raster, using proximity().

As with the geospatial handling functions previously listed, many analysis functions can take a Raster or Vector as a reference to utilize during the operation. In the case of proximity(), passing a Raster serves as a reference for the georeferenced grid on which to compute the distances.

# Compute proximity to vector on raster's grid
rast_proximity_to_vec = vect.proximity(rast)

Note

Right now, the array data of rast is still not loaded. Applying crop() does not yet require loading, and rast’s metadata is sufficient to provide a georeferenced grid for proximity(). The array will only be loaded when necessary.

Quick plotting#

To facilitate the analysis process, GeoUtils includes quick plotting tools that support multiple colorbars and implicitly add layers to the current axis. Those are build on top of rasterio.plot.show() and geopandas.GeoDataFrame.plot(), and relay any argument passed.

See also

GeoUtils’ plotting tools only aim to smooth out the most common hassles when quickly plotting raster and vectors.

For advanced plotting tools to create “publication-quality” figures, see Cartopy or GeoPlot.

The plotting functionality is named plot() everywhere, for consistency. Here again, a Raster or Vector can be passed as a reference to match to ensure all data is displayed on the same grid and projection.

# Plot proximity to vector
rast_proximity_to_vec = vect.proximity(rast)
rast_proximity_to_vec.plot(cbar_title="Distance to glacier outline")
vect.plot(rast_proximity_to_vec, fc="none")
_images/cc9094a17aba3b5403794ddc60e05213c528daf7d159c63def6254337e9b159e.png

Tip

To quickly visualize a raster directly from a terminal, without opening a Python console/notebook, check out our tool geoviewer.py in the Command line interface documentation.

Pythonic arithmetic and NumPy interface#

All Raster objects support Python arithmetic (+, -, /, //, *, **, %) with any other Raster, ndarray or number. With another Raster, the georeferencing must match, while only the shape with a ndarray.

# Add 1 to the raster array
rast += 1

Additionally, the Raster object possesses a NumPy masked-array interface that allows to apply to it any NumPy universal function and most other NumPy array functions, while logically casting dtype and respecting nodata values.

# Apply a normalization to the raster
import numpy as np
rast = (rast - np.min(rast)) / (np.max(rast) - np.min(rast))

Casting to Mask, indexing and overload#

All Raster objects also support Python logical comparison operators (==, !=, >=, >, <=, <), or more complex NumPy logical functions. Those operations automatically casts them into a Mask, a boolean raster that inherits all methods from Raster.

# Get mask of an AOI: infrared index above 0.6, at least 200 m from glaciers
mask_aoi = np.logical_and(rast > 0.6, rast_proximity_to_vec > 200)

Masks can then be used for indexing a Raster, which returns a MaskedArray of indexed values.

# Index raster with mask to extract a 1-D array
values_aoi = rast[mask_aoi]

Masks also have simplified, overloaded Raster methods due to their boolean dtype. Using polygonize() with a Mask is straightforward, for instance, to retrieve a Vector of the area-of-interest:

# Polygonize areas where mask is True
vect_aoi = mask_aoi.polygonize()
# Plot result
rast.plot(cmap='Reds', cbar_title='Normalized infrared')
vect_aoi.plot(fc='none', ec='k', lw=0.75)
_images/717d5bb304d4b201c36e6bd985e79989ec6db87f7937efbd9a2422576f756090.png

Saving to file#

Finally, for saving a Raster or Vector to file, simply call the save() function.

# Save our AOI vector
vect_aoi.save("myaoi.gpkg")

Parsing metadata with SatelliteImage#

In our case, rast would be better opened using the Raster object SatelliteImage instead, which tentatively parses metadata recognized from the filename or auxiliary files.

# Name of the image we used
import os
print(os.path.basename(filename_rast))
LE71400412000304SGS00_B4.tif
# Open while parsing metadata
rast = gu.SatelliteImage(filename_rast, silent=False)
From filename: setting satellite as Landsat 7
From filename: setting sensor as ETM+
From filename: setting tile_name as 140041
From filename: setting datetime as 2000-10-30 00:00:00

Wrap-up

In a few lines, we:

  • easily handled georeferencing operations on rasters and vectors,

  • performed numerical calculations inherently respecting invalid data,

  • casted to a mask implicitly from a logical operation on raster, and

  • vectorized a mask without need for any additional metadata, simply using the nature of the mask object!

Our result: a vector of high infrared absorption indexes at least 200 meters away from glaciers near Everest, which likely corresponds to perennial snowfields.

Otherwise, for more hands-on examples, explore GeoUtils’ gallery of examples!