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
- Call the DEFRA flood API
- Convert the flood geoJSONs into geopandas boundaries
- Map the floods
- 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()
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
In the Github code this is also done for UDPRN (address point) data.