pythonmappingplotly-dashcontour

How to display drawdown contours calculated with the Theis equation to display on an interactive map in Dash with Python?


I have created a Dash app using Python with an interactive map using mapbox. I want to generate drawdown contours at 10-ft intervals out to a 1-ft drawdown around a specified lat/lon using the Theis equation. I just started learning to code a few months ago so I'm sure there's a simple fix but I'm really struggling with this one. I would greatly appreciate any assistance on this!

So far, my map displays the drawdown contours, but they aren't true circles. The code currently generates points in a circular pattern around the lat/lon at 10-ft intervals out to 10,000 ft, calculates drawdown at each point using Theis, then connects the points using straight lines and plots them on the map with Scattermapbox.

The problem is, I don't want to have to plug in that exact distance each time to calculate drawdown out to. What I really need it to do is generate concentric circles around the lat/lon out to a 1-ft drawdown contour, no matter how far that drawdown circle needs to be away from the original lat/lon point. Some advice I was given was to look at using matplotlib instead of plotly but I'm not sure if this is possible in Dash.

Above the @app.callback section I'm using this:

# Contour lines
distance = np.append(np.arange(1,1000,10), np.append(np.arange(100, 2500, 100), np.arange(2500,10000,250)))
distance = np.append(distance, np.arange(10000,105000,1000)) # distance, feet
ci = 10 # default contour interval, feet

def generate_contour_points(lat, lon, distance, angles):
    """
    Generate points in a circular pattern around a given lat/lon for the contour lines.
    Parameters:
        - lat: Latitude of the center point (decimal degrees).
        - lon: Longitude of the center point (decimal degrees).
        - distance: Array of radial distances for contour levels (in feet).
        - angles: Array of angles (in degrees) to calculate the radial points.
    Returns:
        - List of points representing contour lines.
    """
    contour_points = []
    
    for i in distance:  # Loop over each distance (for each contour level)
        contour_level_points = []  # List to store points for each contour level
        
        for angle in angles:  # Loop over each angle
            # Convert angle to radians
            angle_rad = np.deg2rad(angle)
            
            # Calculate the new x, y coordinates using polar to Cartesian conversion
            x_offset = i * np.sin(angle_rad)
            y_offset = i * np.cos(angle_rad)
            
            # Convert to geographic coordinates (lat/lon) using distance
            lat_offset = x_offset / 364000.0  # Approximate conversion from feet to degrees latitude
            lon_offset = y_offset / (364000.0 * np.cos(np.radians(lat)))  # Approximate conversion for longitude
            
            contour_level_points.append([lat + lat_offset, lon + lon_offset])
        
        contour_points.append(contour_level_points)
    
    return contour_points

def calculate_drawdown_at_points(lat, lon, distance, angles, transmissivity, storativity, discharge, time):
    """
    Calculate the drawdown at each point generated by generate_contour_points.
    """
    contour_points = generate_contour_points(lat, lon, distance, angles)
    drawdowns = []

    for contour_level_points in contour_points:
        drawdown_level = []
        for point in contour_level_points:
            r = np.sqrt((point[0] - lat)**2 + (point[1] - lon)**2) * 3963.0  # Convert to miles
            drawdown = theis_con(transmissivity, storativity, r, discharge, time)
            drawdown_level.append(drawdown)
        drawdowns.append(drawdown_level)

    return drawdowns, contour_points

def plot_contours_on_map(contour_points, drawdowns):
    """
    Plot the contour lines on the map.
    """
    contour_traces = []
    
    for contour_level, drawdown_level in zip(contour_points, drawdowns):
        contour_traces.append(go.Scattermapbox(
            lat=[point[0] for point in contour_level],
            lon=[point[1] for point in contour_level],
            mode='lines',
            line=dict(width=2, color='blue'),
            name=f"Drawdown: {min(drawdown_level)} to {max(drawdown_level)} ft"
        ))
    
    return contour_traces

In my @app.callback section I'm currently using this:

    inc = 20
    angles = np.arange(inc,360+inc, inc)

    # Generate points for contour lines
    distanceone = np.arange(10, 10000, 1000)  # Example distance array (1 ft to 1000 ft)
    angles = np.linspace(0, 360, 36)  # Angles from 0° to 360° (every 10 degrees)
    
    # Calculate drawdowns at the generated points
    drawdowns, contour_points = calculate_drawdown_at_points(lat, lon, distanceone, angles, transmissivity, storativity, discharge, time=365)
    
    # Plot contour lines on the map
    contour_traces = plot_contours_on_map(contour_points, drawdowns)

Solution

  • GeoJSON was effective in producing the contours I needed.