Calculate exposed ratio with overlapping hazards

Hello,

I am using 2 hazard layers in my pipeline, both are flooding and both have some overlapping sections. If a building has half exposed to each of the hazards but with a portion overlapping how can I calculate this correctly?
In the image below the black is the building and the orange and blue are the hazard. The current output I’m getting is

buildingid,hazardid,exposed_ratio,sum_exposed_ratio
1,1,[0.25,0.25],0.5
1,2,[0.25,0.25],0.5

and I would like

buildingid,sum_exposed_ratio
1,0.75

is this possible?

Hi John,

I think it is possible, but the overlapping hazards makes it tricky.

What you could do is combine the intersecting hazards into a single polygon, and then take the exposed ratio of the combined polygon. That’s the tricky part, because we don’t currently support combining polygons like that in the RiskScape Engine, but you could use Python to do that. For example, adding a combine_polygons() Python function like this:

from shapely.ops import unary_union
from shapely import wkb

# geom_result_all_coverages type here would be: list(list(geometry))
def function(geom_result_all_coverages):
    polygons = []
    # NB: assumption here is all geometry data is in the same CRS
    srid = 0

    for sample_geom in geom_result_all_coverages:
        for geom in sample_geom:
            # note the RS geometry type gets passed through as a Python tuple of WKB
            bytes, srid = geom
            polygons.append(wkb.loads(bytes))
        
    return unary_union(polygons).wkb, srid

Then in your pipeline, you could turn your hazard data into a list, so you have one list of hazard coverages per building. And then do something like this to sample the coverages, combine the geometry result, and measure the exposed ratio:

# do the spatial sampling across the list of coverages
select({ *, map(hazard_coverages, coverage -> sample(exposure, coverage)) as sample_result_all_coverages })
 ->
# pull out the geometry from the sample(coverage) results
select({ *, map(sample_result_all_coverages, result -> map(result, hv -> hv.geometry)) as geom_result_all_coverages })
 ->
# turn the sampled results into a single polygon and measure its area
select({ *, measure(combine_polygons(geom_result_all_coverages)) / orig_geom_size as exposed_ratio })

Another alternative to using a list of coverages would be to aggregate the data by building and use to_list(sampled_geom) to build the combined list of intersecting geometry. That approach might be a bit more memory-intensive depending on the size of your exposure dataset.

I’ve also assumed you have N hazard maps here. If you only ever have 2 hazard maps, then you could simplify things slightly, e.g.

# do the spatial sampling across the list of coverages
select({
         *,
         sample(exposure, coverage1) as sampled1,
         sample(exposure, coverage2) as sampled2,
       })
 ->
# pull out the geometry from the sample(coverage) results
select({
         *,
         concat(
                map(sampled1, hv -> hv.geometry),
                map(sampled2, hv -> hv.geometry),
               ) as sample_geom
      })
 ->
# turn the sampled results into a single polygon and measure its area
select({ *, measure(combine_polygons(sample_geom)) / orig_geom_size as exposed_ratio })

You could then remove the outer geom_result_all_coverages Python loop and just pass a list(geometry) to Python.

Hope that helps.
Tim