1

I want to get a list of indices (row,col) for all raster cells that fall within or are intersected by a polygon feature. Looking for a solution in python, ideally with gdal/ogr modules.

Other posts have suggested rasterizing the polygon, but I would rather have direct access to the cell indices if possible.

corvus
  • 556
  • 7
  • 18

3 Answers3

3

Since you don't provide a working example, it's bit unclear what your starting point is. I made a dataset with 1 polygon, if you have a dataset with multiple but only want to target a specific polygon you can add SQLStatement or where to the gdal.Rasterize call.

Sample polygon

geojson = """{"type":"FeatureCollection",
"name":"test",
"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}},
"features":[
{"type":"Feature","properties":{},"geometry":{"type":"MultiPolygon","coordinates":[[[[-110.254,44.915],[-114.176,37.644],[-105.729,36.41],[-105.05,43.318],[-110.254,44.915]]]]}}
]}"""

Rasterizing

Rasterizing can be done with gdal.Rasterize. You need to specify the properties of the target grid. If there is no predefined grid these could be extracted from the polygon itself

ds = gdal.Rasterize('/vsimem/tmpfile', geojson, xRes=1, yRes=-1, allTouched=True,
                    outputBounds=[-120, 30, -100, 50], burnValues=1, 
                    outputType=gdal.GDT_Byte)
mask = ds.ReadAsArray()
ds = None
gdal.Unlink('/vsimem/tmpfile')

Converting to indices

Retrieving the indices from the rasterized polygon can be done with Numpy:

y_ind, x_ind = np.where(mask==1)
Rutger Kassies
  • 61,630
  • 17
  • 112
  • 97
  • OK, you win! This is about twice as fast as my method, and much less code required. – corvus Dec 10 '17 at 04:16
  • Can you suggest a way to get burn values from a polygon (shapefile) attribute field and then pass these to the numpy array along with the coordinates? – corvus Dec 10 '17 at 05:46
  • Instead of the `burnValues=1` use `attribute="name"`, if you want to extract individual polygons the value of the attribute has to be unique for each feature (or do it with a separate step). – Rutger Kassies Dec 12 '17 at 10:53
  • In my case there is no need to keep individual features separate. Each feature belongs to a class, and I just need to make sure that the class IDs stay attached to the cell indices. Thanks for your help! – corvus Dec 12 '17 at 21:43
  • Do you know if there is a way to do a similar operation using `gdal.RasterizeLayer` instead (that is, rasterizing to a temp file)? I've just been revisiting this code after some months & I'm realizing it would be convenient if I could do it this way since then I can apply layer attribute filters before rasterizing. – corvus Feb 08 '18 at 05:27
  • Its possible, but takes a bit more code to create the output dataset, opening the input dataset and layer, setting the filter etc. You should be able to achieve a the same result by adding the `where` or `SQLStatement` keywords to `gdal.Rasterize`. – Rutger Kassies Feb 08 '18 at 07:41
  • Hi - I'm trying to do the same thing, but getting the following error: TypeError: in method 'wrapper_GDALRasterizeDestName', argument 2 of type 'GDALDatasetShadow *'. Could you please clarify what the arguments you're passing to gdal.Rasterize mean, where you got those values, and what exactly your code is doing so that it's easier to troubleshoot? Thank you! – Takver Feb 28 '19 at 09:58
1

Clearly Rutger's solution above is the way to go with this, however I will leave my solution up. I developed a script that accomplished what I needed with the following:

  1. Get the bounding box for each vector feature I want to check
  2. Use the bounding box to limit the computational window (determine what portion of the raster could potentially have intersections)
  3. Iterate over the cells within this part of the raster and construct a polygon geometry for each cell
  4. Use ogr.Geometry.Intersects() to check if the cell intersects with the polygon feature

Note that I have only defined the methods, but I think implementation should be pretty clear -- just call match_cells with the appropriate arguments (ogr.Geometry object and geotransform matrix). Code below:

from osgeo import ogr

# Convert projected coordinates to raster cell indices
def parse_coords(x,y,gt):
    row,col = None,None
    if x:
        col = int((x - gt[0]) // gt[1])
        # If only x coordinate is provided, return column index
        if not y:
            return col
    if y:
        row = int((y - gt[3]) // gt[5])
        # If only x coordinate is provided, return column index
        if not x:
            return row
    return (row,col)

# Construct polygon geometry from raster cell
def build_cell((row,col),gt):
    xres,yres = gt[1],gt[5]
    x_0,y_0 = gt[0],gt[3]
    top = (yres*row) + y_0
    bottom = (yres*(row+1)) + y_0
    right = (xres*col) + x_0
    left = (xres*(col+1)) + x_0
    # Create ring topology
    ring = ogr.Geometry(ogr.wkbLinearRing)
    ring.AddPoint(left,bottom)
    ring.AddPoint(right,bottom)
    ring.AddPoint(right,top)
    ring.AddPoint(left,top)
    ring.AddPoint(left,bottom)
    # Create polygon
    box = ogr.Geometry(ogr.wkbPolygon)
    box.AddGeometry(ring)
    return box

# Iterate over feature geometries & check for intersection
def match_cells(inputGeometry,gt):
    matched_cells = []
    for f,feature in enumerate(inputGeometry):
        geom = feature.GetGeometryRef()
        bbox = geom.GetEnvelope()
        xmin,xmax = [parse_coords(x,None,gt) for x in bbox[:2]]
        ymin,ymax = [parse_coords(None,y,gt) for y in bbox[2:]]
        for cell_row in range(ymax,ymin+1):
            for cell_col in range(xmin,xmax+1):
                cell_box = build_cell((cell_row,cell_col),gt)
                if cell_box.Intersects(geom):
                    matched_cells += [[(cell_row,cell_col)]]
    return matched_cells 
corvus
  • 556
  • 7
  • 18
  • Yup, in most cases it's not a cheap operation to do. – Monza Nov 23 '17 at 04:06
  • 1
    Understandably so. Luckily for my case, while the rasters I will be processing are large, the polygons are small and relatively few. So, this code executes pretty quickly for the tasks I need it to perform. – corvus Nov 23 '17 at 05:58
  • Why don't you use rasterize? It would probably a lot faster and give exactly the same result. – Rutger Kassies Nov 24 '17 at 13:02
  • Because I need the cell indices as a list, not as a raster product. Unless I am misunderstanding how it works, I don't think rasterize can provide this functionality. – corvus Nov 24 '17 at 16:05
0

if you want to do this manually you'll need to test each cell for: Square v Polygon intersection and Square v Line intersection.

If you treat each square as a 2d point this becomes easier - it's now a Point v Polygon problem. Check in Game Dev forums for collision algorithms.

Good luck!

Monza
  • 745
  • 4
  • 12
  • Do you know of any good resources describing how to do this? If I could use the bounding box of the polygon to limit the number of raster cells I have to check, this might be feasible. – corvus Nov 21 '17 at 04:45
  • The polygon might not suit a bounding box. Use a variation of point-in-triangle. There's plenty out there for you to find. Make sure to put a couple of unit tests around whatever algo you choose to verify that it's correct! – Monza Nov 21 '17 at 12:18
  • This sounds more sophisticated than my solution, but unfortunately I don't have time to work on it right now. I'll look into this at some point though. Thanks for your suggestions! – corvus Nov 22 '17 at 21:39