Search code examples
pythonleafletplotlydata-visualizationfolium

plot heading direction with folium onto an object


I have plotted a heatmap with the following data.

enter image description here

I have thousands of rows. Its just a sample. I also wanted to see the google map view of that coordinate. So I did something like this.

import folium
from folium.plugins import HeatMap
from folium.plugins import FastMarkerCluster


default_location=[11.1657, 45.4515]
m = folium.Map(location=default_location, zoom_start=13)


heat_data = [[row['lat'],row['lon']] for index, row in test.iterrows()]

# Plot it on the map
HeatMap(heat_data).add_to(m)

callback = ('function (row) {' 
                'var marker = L.marker(new L.LatLng(row[0], row[1]), {color: "red"});'
                'var icon = L.AwesomeMarkers.icon({'
                "icon: 'info-sign',"
                "iconColor: 'white',"
                "markerColor: 'green',"
                "prefix: 'glyphicon',"
                "extraClasses: 'fa-rotate-0'"
                    '});'
                'marker.setIcon(icon);'
                "var popup = L.popup({maxWidth: '300'});"
                "const display_text = {text1: row[0], text2: row[1]};"
                "var mytext = $(`<div id='mytext' class='display_text' style='width: 100.0%; height: 100.0%;'>\
                <a href=https://https://www.google.com/maps?ll=${display_text.text1},${display_text.text2} target='_blank'>Open Google Maps</a></div>`)[0];"
                "popup.setContent(mytext);"
                "marker.bindPopup(popup);"
                'return marker};')
            
m.add_child(FastMarkerCluster(heat_data, callback=callback))


# Display the map
m

Now for every gps coordinate I want to plot a small arrow or few small arrows in the angle of heading_direction and if possible show the distance_of_item in that angle from the gps coordinate. The expected outcome may be something like this.

enter image description here

In the above image, the location pointer is the gps coordinate, the direction and angle would be according to heading direction angle and there is a little star plotted which is the object. The object should be placed at a distance(in meters) mentioned in the dataset. I am not sure how to achieve that. Any lead or suggestions are most welcome. Thanks!


Solution

    • given your sample data is an image, have used alternate GPS data (UK hospitals) then added distance and direction columns as random values
    • given requirement is to plot a marker at location defined by distance and direction, first step is to calculate GPS co-ordinates of this.
      1. use UTM CRS so that distance is meaningful
      2. use high school maths to calculate x and y in UTM CRS
      3. convert CRS back to WSG 84 so that have GPS co-ordinates
    • you have tagged question as plotly so I have used mapbox line and scatter traces to demonstrate building a tiled map
    • sample data is 1200+ hospitals, performance is decent
    • geopandas data frame could also be used to build folium tiles / markers. Key step is calculating the GPS co-ordinates
    import geopandas as gpd
    import pandas as pd
    import numpy as np
    import shapely
    import math
    import plotly.express as px
    import plotly.graph_objects as go
    import io, requests
    # get some public addressess - hospitals.  data that has GPS lat / lon
    dfhos = pd.read_csv(io.StringIO(requests.get("http://media.nhschoices.nhs.uk/data/foi/Hospital.csv").text),
        sep="¬",engine="python",).loc[:, ["OrganisationName", "Latitude", "Longitude"]]
    
    # debug with fewer records
    # df = dfhos.loc[0:500]
    df = dfhos
    
    # to use CRS transformations use geopandas, initial data is WSG 84, transform to UTM geometry
    # directions and distances are random
    gdf = gpd.GeoDataFrame(
        data=df.assign(
            heading_direction=lambda d: np.random.randint(0, 360, len(d)),
            distance_of_item=lambda d: np.random.randint(10 ** 3, 10 ** 4, len(d)),
        ),
        geometry=df.loc[:, ["Longitude", "Latitude"]].apply(
            lambda r: shapely.geometry.Point(r["Longitude"], r["Latitude"]), axis=1
        ),
        crs="EPSG:4326",
    ).pipe(lambda d: d.to_crs(d.estimate_utm_crs()))
    
    # standard high school geometry...
    def new_point(point, d, alpha):
        alpha = math.radians(alpha)
        return shapely.geometry.Point(
            point.x + (d * math.cos(alpha)),
            point.y + (d * math.sin(alpha)),
        )
    
    # calculate points based on direction and distance in UTM CRS.  Then convert back to WSG 84 CRS
    gdf["geometry2"] = gpd.GeoSeries(
        gdf.apply(
            lambda r: new_point(
                r["geometry"], r["distance_of_item"], r["heading_direction"]
            ),
            axis=1,
        ),
        crs=gdf.geometry.crs,
    ).to_crs("EPSG:4326")
    gdf = gdf.to_crs("EPSG:4326")
    
    
    # plot lines to show start point and direct.  plot markers of destinations for text of distance, etc
    fig = px.line_mapbox(
        lon=np.stack(
            [gdf.geometry.x.values, gdf.geometry2.x.values, np.full(len(gdf), np.nan)],
            axis=1,
        ).reshape([1, len(gdf) * 3])[0],
        lat=np.stack(
            [gdf.geometry.y.values, gdf.geometry2.y.values, np.full(len(gdf), np.nan)],
            axis=1,
        ).reshape([1, len(gdf) * 3])[0],
    ).add_traces(
        px.scatter_mapbox(
            gdf,
            lat=gdf.geometry2.y,
            lon=gdf.geometry2.x,
            hover_data=["distance_of_item", "OrganisationName"],
        ).data
    )
    # c = gdf.loc[]
    fig.update_layout(mapbox={"style": "open-street-map", "zoom": 8, 'center': {'lat': 52.2316838387109, 'lon': -1.4577750831062155}}, margin={"l":0,"r":0,"t":0,"r":0})
    

    enter image description here