Search code examples
pythonpolygongeojsoncontourcontourf

Converting Matplotlib's filled contour plot (contourf_plot) to GeoJSON


I am working on a project where I have successfully generated filled contour plots using plt.contourf in Matplotlib in a Google Colab environment. Now, I am attempting to convert these filled contour polygons into GeoJSON for seamless integration with Folium.

import numpy as np
import folium
from folium import plugins
import matplotlib.pyplot as plt
from matplotlib.colors import to_hex, Normalize

# Specify the URL of the NetCDF file
url = "https://www.star.nesdis.noaa.gov/socd/mecb/sar/AKDEMO_products/APL_winds/tropical/2024/SH052024_BELAL/STAR_SAR_20240116013937_SH052024_05S_MERGED_FIX_3km.nc"

# Download the NetCDF file content
response = requests.get(url)
nc_content = BytesIO(response.content)
# Open the NetCDF file using xarray
dataset = xr.open_dataset(nc_content)

# Access the 'sar_wind' variable
sar_wind = dataset['sar_wind'].values

# Access the 'latitude' and 'longitude' variables
latitude = dataset['latitude'].values
longitude = dataset['longitude'].values
# Set iso values for filled contours
iso_values_filled = np.linspace(np.min(sar_wind), np.max(sar_wind), 11)  # One extra value for filling the background

# Create a filled contour plot
contourf_plot = plt.contourf(longitude, latitude, sar_wind, levels=iso_values_filled, cmap='viridis')

# Convert filled contour polygons to GeoJSON
geojson_data_polygon = {"type": "FeatureCollection", "features": []}

# Normalize iso values for colormap mapping
norm = Normalize(vmin=np.min(iso_values_filled), vmax=np.max(iso_values_filled))

for level, collection in zip(iso_values_filled, contourf_plot.collections):
    for path in collection.get_paths():
        coordinates = path.vertices.tolist()
        # Close the polygon by repeating the first vertex
        coordinates.append(coordinates[0])
        color_hex = to_hex(plt.cm.viridis(norm(level)))  # Map normalized iso value to colormap
        geojson_data_polygon["features"].append({
            "type": "Feature",
            "geometry": {
                "type": "Polygon",
                "coordinates": [coordinates]
            },
            "properties": {
                "level": level,
                "color": color_hex
            }
        })

# Create a Folium map centered on the average latitude and longitude
center_lat, center_lon = np.mean(latitude), np.mean(longitude)
mymap_polygon = folium.Map(location=[center_lat, center_lon], zoom_start=8)

# Add filled contour polygons as GeoJSON overlay with colored areas
folium.GeoJson(
    geojson_data_polygon,
    style_function=lambda feature: {
        'fillColor': feature['properties']['color'],
        'color': feature['properties']['color'],
        'weight': 2,
        'fillOpacity': 0.7
    }
).add_to(mymap_polygon)

# Display the map with filled contour polygons
mymap_polygon

Issue:

contourf_plot gives this map, which the desired result. enter image description here

but the folium gives:

enter image description here

We can see that the polygons are not built.

Objective: My goal is to convert the filled contour polygons from contourf_plot into GeoJSON format so that I can display them in Folium.


Solution

  • If you plot the contours lines on your first figure, you can find the contours lines are not properly arranged. enter image description here You need to process the contour coordinates, I recommend using geojsoncontour.

    Just change the codes after the line contourf_plot = ... to :

    import geojsoncontour
    import branca
    
    # Create geogson strings
    geojson = geojsoncontour.contourf_to_geojson(
        contourf=contourf_plot,
        min_angle_deg=3.0,
        ndigits=5,
        stroke_width=1,
        fill_opacity=0.7)
    
    # Normalize iso values for colormap mapping
    norm = Normalize(vmin=np.min(iso_values_filled), vmax=np.max(iso_values_filled))
    # Covert float to hex colors
    color_hex = []
    for level in iso_values_filled: 
        color_hex.append( to_hex(plt.cm.viridis(norm(level))))
    
    # Create a Folium map centered on the average latitude and longitude
    center_lat, center_lon = np.mean(latitude), np.mean(longitude)
    mymap_polygon = folium.Map(location=[center_lat, center_lon], zoom_start=8)
    
    # Creat colormap
    cm = branca.colormap.LinearColormap(color_hex)
    
    # Add filled contour polygons as GeoJSON overlay with colored areas
    folium.GeoJson(
        geojson,
        style_function=lambda x: {
            'color':     x['properties']['stroke'],
            'weight':    x['properties']['stroke-width'],
            'fillColor': x['properties']['fill'],
            'fillOpacity':   0.7,
        }).add_to(mymap_polygon)
    
    # Update color map 
    mymap_polygon.add_child(cm)
    

    You will get result mymap_polygon:

    enter image description here