Is it possible to draw lines with arrowheads in a Folium map?
Asked Answered
E

3

11

I am running Folium 0.2.1' with Python 2.7.11 on Jupyter Notebook Server 4.2.1

I am trying to plot lines on a map, which have a arrowhead to convey direction

import folium

#DFW, LGA coordinates
coordinates=[(32.900908, -97.040335),(40.768571, -73.861603)]

m = folium.Map(location=[32.900908, -97.040335], zoom_start=4)

#line going from dfw to lga
aline=folium.PolyLine(locations=coordinates,weight=2,color = 'blue')
m.add_children(aline)

enter image description here Is there a way to add an arrowhead to the line?

Evocation answered 23/8, 2016 at 5:47 Comment(0)
I
9

You could use a regular polygon marker to draw a triangle at the end point...

folium.RegularPolygonMarker(location=(32.900908, -97.040335), fill_color='blue', number_of_sides=3, radius=10, rotation=???).add_to(m)

You'll have to use some trigonometry to calculate the angle of rotation for the triangle to point in the correct direction. The initial point of any such marker points due east.

Infection answered 4/9, 2017 at 15:21 Comment(2)
Definitely a good idea! I finished this with ``` from math import atan # returns radians rotation = 90*atan(latitude_difference/longitude_difference) ```Masseter
@SergioLucero, I think you mean rotation = math.atan(latitude_difference / longitude_difference * math.cos(avg_latitude * math.pi/180)) * 180/math.pi. And then the rotation must be corrected to work at all 360 degrees by: rotation = rotation if longitude_difference>0 else rotation+180. The resulting rotation can then be fed directly into ´RegularPolygonMarker´.Tolerance
H
6

I may be a little late to the party, but I have another suggestions for other people bothered by this problem. I would suggest to use the pyproj package's Geod class, which can do geodetic and great circle calculations. We can use it to get forward and backward azimuth of a piece of a LineString. Then for each piece we add a small polygon marker(or something similar) on one end.

from pyproj import Geod
# loop your lines
for line in lines.itertuples():
    # format coordinates and draw line
    loc = [[j for j in reversed(i)] for i in line.geometry.coords]
    folium.PolyLine(loc, color="red").add_to(m)
    # get pieces of the line
    pairs = [(loc[idx], loc[idx-1]) for idx, val in enumerate(loc) if idx != 0]
    # get rotations from forward azimuth of the line pieces and add an offset of 90°
    geodesic = Geod(ellps='WGS84')
    rotations = [geodesic.inv(pair[0][1], pair[0][0], pair[1][1], pair[1][0])[0]+90 for pair in pairs]
    # create your arrow
    for pair, rot in zip(pairs, rotations):
        folium.RegularPolygonMarker(location=pair[0], color='red', fill=True, fill_color='red', fill_opacity=1,
                                    number_of_sides=3, rotation=rot).add_to(m)

I hope someone will find this snippet helpful. Have a great day! =)

Hydrocortisone answered 18/8, 2021 at 14:43 Comment(1)
This worked for me, but instead of putting the triangles at the ends of the lines, I put them in the centers with: centers = [((locations[idx][0] + locations[idx-1][0])/2, (locations[idx][1] + locations[idx-1][1])/2) for idx, val in enumerate(locations) if idx != 0] and making the location= in the RegularPolygonMarker the center of each pair.Mcnair
B
1

I found a solution for this issue: I calculate my own arrow points with trigonometry and then I call polyline function using that points.

    def arrow_points_calculate(self, ini_lat, ini_long, heading):
    lenght_scale = 0.00012
    sides_scale = 0.000025
    sides_angle = 25

    latA= ini_lat
    longA = ini_long

    latB = lenght_scale * math.cos(math.radians(heading)) + latA
    longB = lenght_scale * math.sin(math.radians(heading)) + longA

    latC = sides_scale * math.cos(math.radians(heading + 180 - sides_angle)) + latB
    longC = sides_scale * math.sin(math.radians(heading + 180 - sides_angle)) + longB

    latD = sides_scale * math.cos(math.radians(heading + 180 + sides_angle)) + latB
    longD = sides_scale * math.sin(math.radians(heading + 180 + sides_angle)) + longB

    pointA = (latA, longA)
    pointB = (latB, longB)
    pointC = (latC, longC)
    pointD = (latD, longD)

    point = [pointA, pointB, pointC, pointD, pointB]
    return point


folium.PolyLine(locations=points, color="purple").add_to(
                        position_plot)

Lenght_scale and Side_scale variables must be modified depends of the arrow size you want. If you have the coords of start and finish of the arrow, just use the final coords as point B and calculate Side Scale relative to the length between that points (20% of the length between points is a correct scale on my opinion).

Example of the result: Position plot + heading arrows

Hope it could help

Balikpapan answered 16/3, 2022 at 11:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.