Search code examples
pythonplotlymapboxhaversine

How to make a circle shape radius plot on scatter mapbox (Plotly Python)


I want to retrieve the nearest coordinate point based on circle radius in kilometers and plot the result on scatter mapbox. But I dont know how to plot the circle shape radius. Anyone knows how to plot the circle shape radius?

So far my result just only data in circle radius without its circle shape. This is my code

import plotly.graph_objects as go
import plotly.express as px
from math import radians, cos, sin, asin, sqrt

def haversine(lon1, lat1, lon2, lat2):

    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6371
    return c * r

center_point = pd.DataFrame({'Longitude':[long_input], 'Latitude':[lat_input]})
test_point = df_latlong[['Longitude','Latitude']].reset_index().drop('index',axis=1)

lat1 = center_point['Latitude'][0]
lon1 = center_point['Longitude'][0]

df_nearest = []
for i, (index, row) in enumerate(df_latlong.iterrows()):
    lat2 = test_point['Latitude'][i]
    lon2 = test_point['Longitude'][i]
    a = haversine(lon1, lat1, lon2, lat2)
    df_nearest.append(a)

df_latlong['Distance'] = df_nearest
df_radius = df_latlong[df_latlong['Distance'] <= 5] #5 kilometers for radius 

fig_map3 = px.scatter_mapbox(df_radius, lon=df_radius['Longitude'], lat=df_radius['Latitude'],
                             hover_name='#WELL', zoom=9, width=300, height=500)

fig_map3.update_layout(mapbox_style='open-street-map', margin={'r':0, 't':0, 'l':0, 'b':0})

fig_map3.show()

it has circle shape radius based on centroid that I have defined in center_point. How can I plot black circle like that? The radius has 5 kilometers. Any idea? Thanks!


Solution

  • First I created some sample data in the same geographic region as the data you showed in your screenshots.

    Then, we can calculate the (latitude, longitude) points for a circle with radius r, center point, and number of sample points using the approximation given by @Stéphane (for your question, the 5 km radius you want around your center point is small enough that this approximation holds well).

    Note: I tried to [unsuccessfully] calculate the exact coordinates of the circle using geog.propogate as outlined here, but it wasn't generating the correct coordinates for a circle around the center coordinate.

    Then you can pass the (latitude, longitude) points of the circle to go.Scattermapbox, set the mode to 'lines', and add the trace to your figure.

    import numpy as np
    import pandas as pd
    import plotly.graph_objects as go
    import plotly.express as px
    from math import radians, cos, sin, asin, sqrt, pi
    
    def haversine(lon1, lat1, lon2, lat2):
    
        lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
    
        dlon = lon2 - lon1 
        dlat = lat2 - lat1 
        a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
        c = 2 * asin(sqrt(a)) 
        r = 6371
        return c * r
    
    ## create a sample dataframe to reproduce your data
    lat_input, long_input = -0.622929, 117.575513,
    
    lat_min, lat_max = -0.59, -0.68
    long_min, long_max = 117.543, 117.614
    
    np.random.seed(42)
    df_latlong = pd.DataFrame({
        'Latitude': np.random.uniform(lat_min, lat_max, size=20),
        'Longitude': np.random.uniform(long_min, long_max, size=20),
        '#WELL': ["TN-test"]*20
    })
    
    center_point = pd.DataFrame({'Longitude':[long_input], 'Latitude':[lat_input]})
    test_point = df_latlong[['Longitude','Latitude']].reset_index().drop('index',axis=1)
    
    lat1 = center_point['Latitude'][0]
    lon1 = center_point['Longitude'][0]
    
    df_nearest = []
    for i, (index, row) in enumerate(df_latlong.iterrows()):
        lat2 = test_point['Latitude'][i]
        lon2 = test_point['Longitude'][i]
        a = haversine(lon1, lat1, lon2, lat2)
        df_nearest.append(a)
    
    df_latlong['Distance'] = df_nearest
    df_radius = df_latlong[df_latlong['Distance'] <= 5] #5 kilometers for radius 
    
    fig_map3 = px.scatter_mapbox(df_radius, lon=df_radius['Longitude'], lat=df_radius['Latitude'],
                                 hover_name='#WELL', zoom=9, width=300, height=500)
    
    radius = 5 * 1000 # m - the following code is an approximation that stays reasonably accurate for distances < 100km
    
    # parameters
    N = 360 # number of discrete sample points to be generated along the circle
    
    # generate points
    circle_lats, circle_lons = [], []
    for k in range(N):
        # compute
        angle = pi*2*k/N
        dx = radius*cos(angle)
        dy = radius*sin(angle)
        circle_lats.append(lat1 + (180/pi)*(dy/6378137))
        circle_lons.append(lon1 + (180/pi)*(dx/6378137)/cos(lat1*pi/180))
    circle_lats.append(circle_lats[0])
    circle_lons.append(circle_lons[0])
    
    fig_map3.add_trace(go.Scattermapbox(
        lat=circle_lats,
        lon=circle_lons,
        mode='lines',
        marker=go.scattermapbox.Marker(
            size=1, color="BlueViolet"
        ),
    ))
    
    fig_map3.update_layout(mapbox_style='open-street-map', margin={'r':0, 't':0, 'l':0, 'b':0}, width=500)
    
    fig_map3.show()