Mapping Environment Agency flood data from their API using Python to find at-risk postcodes

Given the recent storms and floods it seemed a good time to try something slightly new in python, calling an API.

Specifically, I want to be able to call the Environment Agency flood warning API then map all areas with a flood warning and then extract a list of postcodes within those flood areas.

Code can be found in the “investigations” folder of my GitHub repo https://github.com/richardkappa/GeoData with the name call_api.

This will be done using these steps

  1. Call the DEFRA flood API
  2. Convert the flood geoJSONs into geopandas boundaries
  3. Map the floods
  4. Find the at-risk postcodes

Note that as this is Environment Agency data, it’s England only. Wales and Scotland may have similar things, but that’s not covered here.

Call the DEFRA flood API

The Environment Agency flood API guide is here. The key bits I need are in the “items” part of the JSON. Then from in this the elements are “message”, “severityLevel” & “floodArea.polygon”.

The API is called using the package “requests”, and my initial call I only want severities of 3 or worse

api_url = "http://environment.data.gov.uk/flood-monitoring/id/floods?min-severity=3"
response = requests.get(api_url)
flood_warnings = pd.json_normalize(response.json()['items'])

I now have a pandas dataframe of all currently live flood warnings

Convert the geoJSON boundaries into geopandas boundaries

The column “floodArea.polygon” is just a url which then contains the geojson. Geopandas makes it easy to convert this into a geopandas dataframe using “read_file”. This can then be repeated with a for loop for all weather warnings

boundaries = pd.DataFrame(columns=gpd.read_file(flood_warnings["floodArea.polygon"][0]).columns)
for i in flood_warnings["floodArea.polygon"]:
    bdry = gpd.read_file(i)
    boundaries = pd.concat([boundaries, bdry], ignore_index=True)
    boundaries_gdf = gpd.GeoDataFrame(boundaries, crs=4326, geometry="geometry").to_crs(Main_CRS)

Map the floods

In previous posts we’ve got maps of the UK, I’ve just reused the maps from here to draw the outline of the UK and to get the list of postcodes and UDPRNs in the next step

fig, ax = plt.subplots(1, figsize=(15,30))
gdf.loc[gdf["Type"]=="All_GB",:].plot(ax=ax, color='none', edgecolor='black', linewidth=0.5, zorder=1)
boundaries_gdf.plot(ax=ax, color='blue', edgecolor='blue', zorder=2)
plt.show()
Where the floods currently are in England

At this point I could colour by severity, but as this was just a check to see if my code was working I’m not going to.

Find the at-risk postcodes

The final step is to cycle over the different flood zones to extract the postcodes within each flood boundary

#Find the postcodes within the flood areas (based on postcode centriods)
pc_df = pd.DataFrame(columns=["Postcodes", "message", "severityLevel"])

for i in range(0,len(flood_warnings)):
    pc_list = Postcodes[Postcodes.within(boundaries_gdf['geometry'][i])]["Name"].to_list()
    pc_df_tmp = pd.DataFrame(pc_list,columns=["Postcodes"])
    pc_df_tmp["message"] = flood_warnings.loc[:,["message"]].iloc[i].values[0]
    pc_df_tmp["severityLevel"] = flood_warnings.loc[:,["severityLevel"]].iloc[i].values[0]
    pc_df = pd.concat([pc_df, pc_df_tmp], ignore_index=True)

#remove duplicates, if a postcode is in a severity 1 and 2 risk. only keep severity 1 risk
pc_df = pc_df.sort_values(by=["Postcodes","severityLevel"])
pc_df = pc_df.drop_duplicates(subset=['Postcodes', 'severityLevel'], keep='first')

There is a chance that a postcode will lie in within more than one flood zone. We don’t want this in our final output, so I only keep a single version of the highest risk flood

Flood postcodes with their severity level

In the Github code this is also done for UDPRN (address point) data.

Leave a Comment

Your email address will not be published. Required fields are marked *