4

I have a big (2GB) landcover shapefile I want to convert to a GeoTIFF file. For this purpose, I am using the gdal.Rasterize() function with the following options:

rasterize_options = gdal.RasterizeOptions(
    outputType=gdal.GDT_Byte,
    xRes=1000,  # 1km
    yRes=1000,  # 1km
    noData=255,
    allTouched=True,
    attribute='CODE'  # Shapefile field with each landcover class
)
gdal.Rasterize(dst, src, options=rasterize_options)

Although this works, it is not clear what cell assignment method the function is using to assign the output value to each pixel. Here is an example of the input shapefile and the output raster (with each color representing a different class):

enter image description here enter image description here

I want to specify a cell assignment method where the output value in a given pixel corresponds to the class of the polygons that occupy most of the area in the pixel. Thus, the output raster shown above would end up being mostly orange and probably just a few pixels, if any, would be green.

This seems to be possible in other tools such as ArcGIS's Polygon to Raster, where one can specify the cell_assignment parameter:

The method to determine how the cell will be assigned a value when more than one feature falls within a cell.

  • CELL_CENTER —The polygon that overlaps the center of the cell yields the attribute to assign to the cell.
  • MAXIMUM_AREA —The single feature with the largest area within the cell yields the attribute to assign to the cell.
  • MAXIMUM_COMBINED_AREA —If there is more than one feature in a cell with the same value, the areas of these features will be combined. The combined feature with the largest area within the cell will determine the value to assign to the cell.

However, after reading the documentation, GDAL does not seem to have the option to specify the cell assignment method. I also took a look at the rasterio.features.rasterize() function but my best guess is that this function is just a wrapper of GDAL's rasterize function and does not offer the option to specify the method either.

As I don't have access to ArcGIS and would rather use open source tools for this, the only workaround I have thought of is rasterizing the shapefile to a smaller cell size (30m maybe as the shapefile was produced using Landsat imagery) and then resampling the output using a mode algorithm. However, this does not seem like a good option given the extent of the original shapefile. What are my options here?

2
+25

If I understand your core problem correctly you want to rasterize your shapefile and each pixel should be assign to the landcover class occupying the largest amount of area underneath each pixel.

I can present a solution which will not be the most elegant or cost effective solution processing wise (compared to the mentioned "array" solution), but with some "gridding" and multiprocessing (I will leave that part up to you) it might improve and its open source and it works.

It is a vector based solution mainly in geopandas and then rasterized in the end as a formality. I will explain some pseudo steps with code snippets.

Step 1: Create a vector grid over the extent of your LC shapefile (assuming you are working in a projected coordinate system with meters) in your required pixel size (e.g. 10 by 10m). Assign a GRID_ID column to the grid.

import numpy as np
from shapely.geometry import Polygon
import geopandas as gpd 

lc_shp_df = gpd.read_file('/path/to/your/LC/shape.shp') # with column e.g. 'class_code' for LC
xmin, ymin, xmax, ymax = lc_shp_df.unary_union.bounds

length= 10  # e.g. 10 by 10 meters
wide=   10
cols = list(range(int(np.floor(xmin)), int(np.ceil(xmax)), wide))
rows = list(range(int(np.floor(ymin+wide)), int(np.ceil(ymax+wide)), length))
rows.reverse()

polygons = []
for x in cols:
  for y in rows:
    polygons.append( Polygon([(x,y), (x+wide, y), (x+wide, y-length), (x, y-length)]) )
Grid_gdf = gpd.GeoDataFrame({'geometry':polygons})
Grid_gdf['GRID_ID'] = Grid_gdf.index

Step 2: Intersect the gird with the land cover shape and calculate the area of the intersection (note: ideally the land cover shape only has a landcover class column, this will make the process simpler, for the example I will use the column name class_code to represent the landcover class)

intersection = gpd.overlay(lc_shp_df, Grid_gdf, how='intersection')
intersection['area_msqr'] = intersection.area

In your created gird, for each GRID_ID you now have the area_msqr for each class_code

Step 3: Convert gpd to pd and get the class_code with the maximum area_msqr for each unique GRID_ID. Then join back to the original grid gpd and save as a shape.

import pandas as pd
intersection = pd.DataFrame(intersection)

# Find maximum class_code for each GIRD_ID
intersection_max = intersection.groupby('GRID_ID').idxmax()
class_code_max= intersection['class_code'].iloc[intersection_max['area_msqr']].reset_index()
class_code_max= class_code_max.rename(columns={'class_code':'max_cls_cd'})

# Join back to original gird dataframe
gird_crs = Grid_gdf.crs
Grid_gdf = pd.DataFrame(Grid_gdf)
Grid_gdf = Grid_gdf.set_index('GRID_ID')
LC_df = Grid_gdf.join(class_code_max['max_cls_cd'],how='left')
LC_df = gpd.GeoDataFrame(LC_df)
LC_df.crs = gird_crs 
LC_df.to_file('/path/to/your/Max_LC/shape.shp')

Step 4: Rasterize your new shape with the same geometrical properties (e.g. x,y boundaries, x y shape and 10 by 10 m) on the max_cls_cd attribute and you should have what you are looking for.

Since you have a 2GB LC vector file, I suggest to split this into parts and then batch execute. Hope that helps.

| improve this answer | |
1

Often times, people (including myself) use gdal.rasterize() to distinguish data from no-data. It is a way to pluck a polygon or line which is in vector format out from there and into a raster. Essentially telling the raster where the alpha layer should be strong (transparent).

Now, you can still use this tool to do what you are wanting to do but if you want amazing control on the actual averaging of the precise data you have, you might want to consider using an array as an intermediate. Essentially, you take the vector data and push it into an array. Then you downgrade the size of the area to the corresponding granularity you want for the raster (1kmx1km per pixel) and then transform the array into a raster.

Since you are working with distinct values for the shapes, you don't want to mix these numbers together (fading green into pink) - you want hard boundaries. To do this, you can just write specific rules which propagate upwards (essentially mimicking the above method you want(which seems like you want the Maximum area method)). This is more work but it allows you to know exactly how the output is created and be able to back this up in writing.

I did take a quick look around and could not see anything concrete. Looking at this page from the GDAL FAQs, it looks like there used to be a dev page which explaining the tool in more detail but now this page throws a 404 (maybe it moved and you can find it?). However, right below that they do say

As Chris Barker suggests, GDAL's rasterization capability is not sophisticated from a render styling point of view. Other tools may be more appropriate if you want to do anything more sophisticated than rasterize the polygons in a single solid color.

And link to some possible other tools if you are interested.

I would say, if you are not afraid of working with arrays and writing simple rules to consolidate those arrays into a new size then take this route as you will have the most control

| improve this answer | |
  • Mind expanding a little bit on the idea on working with arrays? RIght now I am using a smaller pixel size and then using the mode (which can be thought of as an equivalent of maximum area) to resample to my desired pixel size (1km x 1km). Is this what you mean when you say "you can just write specific rules which propagate upwards"? The issue with the intermediate array is that I have ~ 1 million 1km pixels in my study area; using a smaller pixel size increases this value drastically. – Marcelo Villa-Piñeros Jul 21 at 16:38
  • Yeah so what I mean by "propagate upwards" is essentially going up the zoom pyramid and getting less pixels. gdal_translate can do this easily and allow you to choose a method but if you have an array, writing rules would be like saying "okay for these 9 pixels (a center and all of its neighbors) whichever color is most present, is what this one pixel becomes. This turns every 9 pixels into 1. It is downsampling. Does that make sense? See this video for a more matrix heavy example youtube.com/watch?v=AqscP7rc8_M – Sven Jul 23 at 12:40
  • That's exactly what I'm doing at the moment as I don't have any better alternative. However, as I said, it involves creating an intermediary raster/array that is too big and that's what I want to avoid. – Marcelo Villa-Piñeros Jul 23 at 13:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.