#
# Copyright (c) 2014-2023 National Technology and Engineering
# Solutions of Sandia, LLC. Under the terms of Contract DE-NA0003525
# with National Technology and Engineering Solutions of Sandia, LLC,
# the U.S. Government retains certain rights in this software.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
tracktable.render.folium - render trajectories in using the folium backend
"""
import itertools
import logging
from datetime import datetime, timedelta
import matplotlib
from matplotlib.colors import ListedColormap, hsv_to_rgb, rgb2hex
from tracktable.core.geomath import compute_bounding_box
from tracktable.info import airports, borders, ports, rivers, shorelines
from tracktable.render.map_decoration import coloring
from tracktable.render.map_processing import common_processing
from tracktable.render.backends import folium_proxy
fol = folium_proxy.import_folium()
fol_heat_map = folium_proxy.import_folium("plugins.heat_map")
logger = logging.getLogger(__name__)
# later can add multiple layers and switch between with:
# folium.TileLayer('cartodbdark_matter', attr='.').add_to(map)
# folium.TileLayer('CartoDBPositron', attr='.').add_to(map)
# folium.LayerControl().add_to(map)
# TODO add region specifications
# TODO could have opacity, and point size genertor?
# TODO color_scale, can we remove min,max?
# TODO what if color map but no generator or vice versa
# TODO add point_color_map?
# TODO could customize choice of mapping hues to trajs
[docs]
def timedelta_to_iso8601_duration(td: timedelta) -> str:
"""Convert a datetime.timedelta to ISO8601 Duration format
This function converts a duration specified as a ``datetime.timedelta``
to ISO 8601 string format. Fractional seconds will be rounded to the nearest second.
Arguments:
td (datetime.timedelta): Interval to convert
Returns:
Duration represented as a string in ISO8601 format (without fractional seconds)
"""
#Adapted from isodate's _strfduration method.
ret = []
usecs = abs(
(td.days * 24 * 60 * 60 + td.seconds) * 1000000 + td.microseconds
)
seconds, usecs = divmod(usecs, 1000000)
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
days, hours = divmod(hours, 24)
if days:
ret.append("%sD" % days)
if hours or minutes or seconds or usecs:
ret.append("T")
if hours:
ret.append("%sH" % hours)
if minutes:
ret.append("%sM" % minutes)
if seconds or usecs:
if usecs:
seconds = seconds + int(round(usecs/1000000)) #we don't want fractional seconds
ret.append("%dS" % seconds)
if len(ret) == 0: # at least one component has to be there, if not 0D
return "P0D"
else:
return "P"+"".join(ret)
[docs]
def render_trajectories(trajectories,
#common arguments
map_canvas = None,
obj_ids = [],
map_bbox = None,
show_lines = True,
gradient_hue = None,
color_map = '',
line_color = '',
linewidth = 2.4,
show_points = False,
point_size = 0.6,
point_color = '',
show_dot = True,
dot_size = 0.7,
dot_color = 'white',
trajectory_scalar_generator = common_processing.path_length_fraction_generator,
trajectory_linewidth_generator = None,
color_scale = matplotlib.colors.Normalize(vmin=0, vmax=1),
show = False,
save = False,
filename = '',
# folium specific args
tiles = 'cartodbdark_matter',
attr = ".",
crs = "EPSG3857",
point_popup_properties = [],
show_distance_geometry = False,
distance_geometry_depth = 4,
zoom_frac = [0,1], #undocumented feature, for now
show_scale = True,
max_zoom = 22,
fast = False,
animate = False,
anim_display_update_interval=timedelta(microseconds=200000),
anim_timestamp_update_step=timedelta(minutes=1),
anim_trail_duration=None,
anim_loop=True,
# Airport and poirt specific args
draw_airports=False,
draw_ports=False,
airport_color='red',
port_color='blue',
airport_dot_size = 1,
port_dot_size = 1,
use_shapefile=False,
use_markers=False,
popup_width=250,
airport_list=[],
port_list=[],
airport_bounding_box=None,
port_country=None,
port_water_body=None,
port_wpi_region=None,
port_bounding_box=None,
port_and_country_seperate=False,
prefer_canvas=False,
# Shoreline, River and Border specific args
draw_shorelines=False,
draw_rivers=False,
draw_borders=False,
shoreline_color='red',
river_color='blue',
border_color='green',
shoreline_fill_polygon=True,
shoreline_fill_color='red',
shoreline_list=[],
river_list=[],
border_list=[],
shoreline_bounding_box=None,
river_bounding_box=None,
border_bounding_box=None,
shoreline_resolution="low",
shoreline_level="L1",
river_resolution="low",
river_level="L01",
border_resolution="low",
border_level="L1",
**kwargs):
"""Render a list of trajectories using the folium backend
For documentation on the parameters, please see render_trajectories
"""
if not fast:
trajectories, line_color, color_map, gradient_hue \
= common_processing.common_processing(trajectories,
obj_ids,
line_color,
color_map,
gradient_hue)
if not trajectories:
return
if map_canvas == None:
map_canvas = fol.Map(tiles=tiles,
attr=attr,
crs=crs,
control_scale=show_scale,
max_zoom=max_zoom,
prefer_canvas=prefer_canvas)
if animate:
fol_tsgj = folium_proxy.import_folium("plugins.timestamped_geo_json") # to not require it always. Reconsider moving to top?
segments = []
anim_points = []
render_airports_and_ports(map_canvas,
draw_airports=draw_airports,
draw_ports=draw_ports,
airport_color=airport_color,
port_color=port_color,
airport_dot_size = airport_dot_size,
port_dot_size = port_dot_size,
use_shapefile=use_shapefile,
use_markers=use_markers,
popup_width=popup_width,
airport_list=airport_list,
port_list=port_list,
airport_bounding_box=airport_bounding_box,
port_country=port_country,
port_water_body=port_water_body,
port_wpi_region=port_wpi_region,
port_bounding_box=port_bounding_box,
port_and_country_seperate=port_and_country_seperate)
render_shorelines_rivers_borders(map_canvas,
draw_shorelines=draw_shorelines,
draw_rivers=draw_rivers,
draw_borders=draw_borders,
shoreline_color=shoreline_color,
river_color=river_color,
border_color=border_color,
shoreline_fill_polygon=shoreline_fill_polygon,
shoreline_fill_color=shoreline_fill_color,
shoreline_list=shoreline_list,
river_list=river_list,
border_list=border_list,
shoreline_bounding_box=shoreline_bounding_box,
river_bounding_box=river_bounding_box,
border_bounding_box=border_bounding_box,
shoreline_resolution=shoreline_resolution,
shoreline_level=shoreline_level,
river_resolution=river_resolution,
river_level=river_level,
border_resolution=border_resolution,
border_level=border_level)
for i, trajectory in enumerate(trajectories):
coordinates = [(point[1], point[0]) for point in trajectory]
if animate:
times = [point.timestamp.strftime('%Y-%m-%d %H:%M:%S') for point in trajectory]
if not fast:
# set up generators
if trajectory_scalar_generator:
scalars = trajectory_scalar_generator(trajectory)
if trajectory_linewidth_generator:
# NOTE: lines invisible below about .37
widths = trajectory_linewidth_generator(trajectory)
current_color_map, current_point_cmap, mapper, point_mapper = \
coloring.setup_colors(line_color, color_map, gradient_hue,
point_color, color_scale,
trajectory[0].object_id, i,
trajectory_linewidth_generator)
else:
rgb = hsv_to_rgb([common_processing.hash_short_md5(trajectory[0].object_id), 1.0, 1.0])
current_color_map = ListedColormap([rgb2hex(rgb)])
solid = (type(current_color_map) is ListedColormap \
and len(current_color_map.colors) == 1 \
and trajectory_linewidth_generator == None)
if show_lines:
popup_str = str(trajectory[0].object_id)+'<br>'+ \
trajectory[0].timestamp.strftime('%Y-%m-%d %H:%M:%S')+ \
'<br> to <br>'+ \
trajectory[-1].timestamp.strftime('%Y-%m-%d %H:%M:%S')
tooltip_str = str(trajectory[0].object_id)
if fast or (solid and not animate): # Polyline ok
fol.PolyLine(coordinates,
color=current_color_map.colors[0],
weight=linewidth, opacity=1,
tooltip=tooltip_str,
popup=popup_str).add_to(map_canvas)
else: # mapped color (not solid) or animate
last_pos = coordinates[0]
for i, pos in enumerate(coordinates[1:]):
weight = linewidth
if trajectory_linewidth_generator:
weight = widths[i]
if solid:
segment_color = current_color_map.colors[0]
else:
segment_color = rgb2hex(mapper.to_rgba(scalars[i]))
if animate:
segments.append({'coordinates': [[last_pos[1], last_pos[0]], [pos[1], pos[0]]],
'times': [times[i], times[i+1]], #i is off by one, so in first iter times[0] is the previous time
'color': segment_color,
'weight': weight
})
else:
fol.PolyLine([last_pos,pos],
color=segment_color, weight=weight,
opacity=1, tooltip=tooltip_str,
popup=popup_str).add_to(map_canvas)
last_pos = pos
if show_points:
for coord_ind, c in enumerate(coordinates[:-1]): # all but last (dot)
point_radius = point_size
if type(current_point_cmap) is ListedColormap \
and len(current_point_cmap.colors) == 1: # one color
current_point_color = current_point_cmap.colors[0]
else:
current_point_color = \
rgb2hex(point_mapper.to_rgba(scalars[coord_ind]))
if animate:
anim_points.append({'coordinates': [c[1],c[0]], 'time': times[coord_ind], 'color': current_point_color, 'radius': point_radius, 'popup_str': common_processing.point_popup(trajectory[coord_ind], point_popup_properties)}) #swap lat/lon c[1],c[0]
else:
render_point(trajectory[coord_ind],
point_popup_properties, c,
point_radius,
current_point_color, map_canvas)
if show_dot:
render_point(trajectory[-1],
point_popup_properties,
coordinates[-1], dot_size,
dot_color, map_canvas)
if show_distance_geometry:
common_processing.render_distance_geometry('folium', distance_geometry_depth,
trajectory, map_canvas)
if animate:
#linestrings/track
features = [{"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": seg["coordinates"],
},
"properties": {
"times": seg["times"],
"style": {
"color": seg["color"],
"weight": seg["weight"]
},
},
} for seg in segments ]
if show_points:
features+=[{"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": point["coordinates"],
},
"properties": {
"time": point["time"],
"popup": point["popup_str"],
"icon": 'circle',
"style": {
"color": point["color"],
},
'iconstyle': {
'fillOpacity': 1.0,
'radius': point['radius'],
},
},
} for point in anim_points ]
if anim_trail_duration != None:
anim_trail_duration = timedelta_to_iso8601_duration(anim_trail_duration)
anim_timestamp_update_step = timedelta_to_iso8601_duration(anim_timestamp_update_step)
fol_tsgj.TimestampedGeoJson({"type":"FeatureCollection",
"features": features}, add_last_point=False,
transition_time=anim_display_update_interval.microseconds // 1000,
period=anim_timestamp_update_step,
duration=anim_trail_duration,
time_slider_drag_update=True,
loop_button=True, loop=anim_loop).add_to(map_canvas)# need to set period automatically or allow users to set.
if map_bbox:
map_canvas.fit_bounds([(map_bbox[1], map_bbox[0]),
(map_bbox[3], map_bbox[2])])
else:
if zoom_frac != [0,1]:
sub_trajs = common_processing.sub_trajs_from_frac(trajectories, zoom_frac)
map_canvas.fit_bounds(bounding_box_for_folium(sub_trajs))
else:
map_canvas.fit_bounds(bounding_box_for_folium(trajectories))
if save: # saves as .html document
if not filename:
datetime_str = datetime.now().strftime("%Y-%m-%dT%H%M%S-%f")
filename = "trajs-"+datetime_str+'.html'
map_canvas.save(filename)
if show:
display(map_canvas)
return map_canvas
# ----------------------------------------------------------------------
[docs]
def render_heatmap(points,
trajectories=None,
weights=None,
color_map='viridis',
tiles='cartodbdark_matter',
attr='.',
crs="EPSG3857",
show = False,
save = False,
filename = '',
show_scale = True,
max_zoom = 22,
# Airport and poirt specific args
draw_airports=False,
draw_ports=False,
airport_color='red',
port_color='blue',
airport_dot_size = 1,
port_dot_size = 1,
use_shapefile=False,
use_markers=False,
popup_width=250,
airport_list=[],
port_list=[],
airport_bounding_box=None,
port_country=None,
port_water_body=None,
port_wpi_region=None,
port_bounding_box=None,
port_and_country_seperate=False,
prefer_canvas=False,
# Shoreline, River and Border specific args
draw_shorelines=False,
draw_rivers=False,
draw_borders=False,
shoreline_color='red',
river_color='blue',
border_color='green',
shoreline_fill_polygon=True,
shoreline_fill_color='red',
shoreline_list=[],
river_list=[],
border_list=[],
shoreline_bounding_box=None,
river_bounding_box=None,
border_bounding_box=None,
shoreline_resolution="low",
shoreline_level="L1",
river_resolution="low",
river_level="L01",
border_resolution="low",
border_level="L1",
**kwargs):
"""Creates an interactive heatmap visualization using the folium backend
For documentation on the parameters, please see render_heatmap
"""
# lat, long, (optional weight) of points to render
if weights is None:
display_points = [[point[1], point[0]] for point in points]
else:
display_points = [[point[1], point[0], weight] for point, weight in zip(points, weights)]
# create the heat map
heat_map = fol.Map(tiles=tiles,
zoom_start=4,
attr=attr,
crs=crs,
control_scale=show_scale,
max_zoom=max_zoom,
prefer_canvas=prefer_canvas)
render_airports_and_ports(heat_map,
draw_airports=draw_airports,
draw_ports=draw_ports,
airport_color=airport_color,
port_color=port_color,
airport_dot_size = airport_dot_size,
port_dot_size = port_dot_size,
use_shapefile=use_shapefile,
use_markers=use_markers,
popup_width=popup_width,
airport_list=airport_list,
port_list=port_list,
airport_bounding_box=airport_bounding_box,
port_country=port_country,
port_water_body=port_water_body,
port_wpi_region=port_wpi_region,
port_bounding_box=port_bounding_box,
port_and_country_seperate=port_and_country_seperate)
render_shorelines_rivers_borders(heat_map,
draw_shorelines=draw_shorelines,
draw_rivers=draw_rivers,
draw_borders=draw_borders,
shoreline_color=shoreline_color,
river_color=river_color,
border_color=border_color,
shoreline_fill_polygon=shoreline_fill_polygon,
shoreline_fill_color=shoreline_fill_color,
shoreline_list=shoreline_list,
river_list=river_list,
border_list=border_list,
shoreline_bounding_box=shoreline_bounding_box,
river_bounding_box=river_bounding_box,
border_bounding_box=border_bounding_box,
shoreline_resolution=shoreline_resolution,
shoreline_level=shoreline_level,
river_resolution=river_resolution,
river_level=river_level,
border_resolution=border_resolution,
border_level=border_level)
gradient = coloring.matplotlib_cmap_to_dict(color_map)
if trajectories:
heat_map = render_trajectories(trajectories, map_canvas=heat_map,
line_color='grey', linewidth=0.5,
tiles=tiles, attr=attr, crs=crs,
prefer_canvas=prefer_canvas)
fol_heat_map.HeatMap(display_points, gradient=gradient).add_to(heat_map)
if save: # saves as .html document
if not filename:
datetime_str = datetime.now().strftime("%Y-%m-%dT%H%M%S-%f")
filename = "heatmap-"+datetime_str+'.html'
heat_map.save(filename)
if show:
display(heat_map)
return heat_map
# ----------------------------------------------------------------------
[docs]
def render_point(current_point,
point_popup_properties, coord, point_radius,
point_color, map_canvas):
"""Renders a point to the folium map
Args:
current_point (point): Current point of the trajectory
point_popup_properties (list): Point properties
coord (tuple): Coordinates to render point
point_radius (int): Size of the point to render
point_color (str): Color of the point to render
map (Basemap): Folium map
Returns:
No return value
"""
tooltip_str_point = common_processing.point_tooltip(current_point)
popup_point = fol.Popup(common_processing.point_popup(current_point,
point_popup_properties),
max_width=450)
fol.CircleMarker(coord, radius=point_radius, fill=True,
fill_opacity=1.0,
fill_color=point_color,
color=point_color,
tooltip=tooltip_str_point,
popup=popup_point).add_to(map_canvas)
# ----------------------------------------------------------------------
[docs]
def render_airports_and_ports(map_canvas,
draw_airports=False,
draw_ports=False,
airport_color='red',
port_color='blue',
airport_dot_size = 1,
port_dot_size = 1,
use_shapefile=False, # Undocumented for now
use_markers=False,
popup_width=250,
airport_list=[],
port_list=[],
airport_bounding_box=None,
port_country=None,
port_water_body=None,
port_wpi_region=None,
port_bounding_box=None,
port_and_country_seperate=False,
**kwargs):
"""Renders airports and/or ports to the folium map
Args:
map_canvas (folium map object): Canvas to draw the airports/ports on
Keyword Arguments:
draw_airports (bool): Whether or not to draw airports (Default: False)
draw_ports (bool): Whether or not to draw ports (Default: False)
airport_color (name of standard color as string, hex color string or
matplotlib color object): Color of the airport dot or marker (Default: 'red')
port_color (name of standard color as string, hex color string or
matplotlib color object): Color of the port dot or marker (Default: 'blue')
airport_dot_size (float): Radius of a airport dot (Default: 1)
port_dot_size (float): radius of a port dot (Default: 1)
use_markers (bool): Bool for using marker object instead of dots for airports and prots,
used for Folium rendering only. (Default: False)
popup_width (int): Size of the popup window that displays airport/port information, used for Folium rendering only (Default: 250)
airport_list (list(str)): IATA code of airports to render onto the map (Default: [])
port_list (list(str)): Name or WPI index number of ports to render onto the map (Default: [])
airport_bounding_box (BoundingBox or tuple/list of points): bounding box for
rendering airports within. (Default: None)
port_bounding_box (BoundingBox or tuple/list of points): bounding box for
rendering ports within. (Default: None)
port_country (str): Name of country to render ports in. (Default: None)
port_water_body (str): Name of body of water to render ports on. (Default: None)
port_wpi_region (str): Name of WPI region to render ports in. (Default: None)
port_and_country_seperate (bool): Bool for searching the ports database for a port and not considering it's
country to see if it's rendered. i.e. You want to render a port in the U.K. while rendering all ports in
Japan. (Default: False)
Returns:
No return value
"""
if use_shapefile:
logger.warning("Using a shapefile is currently unsupported for rendering airports and ports.")
if use_markers:
logger.warning("Using `markers` causes a considerable performance decrease, ensure `prefer_canvas=True` is set for the Folium map to help performance.")
if draw_airports:
display_all_airports = True
all_airports = []
if airport_bounding_box:
display_all_airports = False
for airport_name, airport in airports.all_airports_within_bounding_box(airport_bounding_box).items():
all_airports.append(airport)
if len(airport_list) > 0:
display_all_airports = False
for airport in airport_list:
all_airports.append(airports.airport_information(airport))
if display_all_airports:
all_airports = airports.all_airports()
else:
# Remove duplicates since there is a chance you'll double up on ports with how this code is structured
all_airports = list(set(all_airports))
for airport in all_airports:
airport_properties = "Airport = " + airport.name + \
"<br>City = " + airport.city + \
"<br>Country = " + airport.country + \
"<br>Lat = " + str(airport.position[1]) + \
"<br>Lon = " + str(airport.position[0]) + \
"<br>Alt = " + str(airport.position[2]) + " ft | " + str(round(airport.position[2]/3.281, 2)) + " m" + \
"<br>IATA Code = " + str(airport.iata_code) + \
"<br>ICAO Code = " + str(airport.icao_code) + \
"<br>UTC Offset = " + str(airport.utc_offset) + \
"<br>Daylight Savings = " + airport.daylight_savings
if use_markers:
fol.Marker(location=[airport.position[1], airport.position[0]], tooltip=airport.name,
popup=fol.Popup(airport_properties, max_width=popup_width, min_width=popup_width), icon=fol.Icon(color=airport_color, icon='fa-plane', prefix='fa')).add_to(map_canvas)
else:
fol.CircleMarker((airport.position[1], airport.position[0]), radius=airport_dot_size, fill=True,
fill_opacity=1.0,
fill_color=airport_color,
color=airport_color,
tooltip=airport.name,
popup=fol.Popup(airport_properties, max_width=popup_width, min_width=popup_width)).add_to(map_canvas)
if draw_ports:
display_all_ports = True
all_ports = []
if port_water_body:
display_all_ports = False
for port_index, port in ports.all_ports_by_water_body(port_water_body).items():
all_ports.append(port)
if port_wpi_region:
display_all_ports = False
for port_index, port in ports.all_ports_by_wpi_region(port_wpi_region).items():
all_ports.append(port)
if port_bounding_box:
display_all_ports = False
for port_index, port in ports.all_ports_within_bounding_box(port_bounding_box).items():
all_ports.append(port)
if len(port_list) > 0:
display_all_ports = False
if port_and_country_seperate:
if port_country:
for port_index, port in ports.all_ports_by_country(port_country).items():
all_ports.append(port)
else:
logger.info("No `port_country` specified only ports listed in `port_list` will be rendered.")
for port in port_list:
all_ports.append(ports.port_information(port))
else:
for port in port_list:
all_ports.append(ports.port_information(port, country=port_country))
flatten_all_ports = []
for port in all_ports: # Since port_information can return lists we need to flatten the all_ports list
if type(port) is list:
for i in port:
flatten_all_ports.append(i)
else:
flatten_all_ports.append(port)
all_ports = flatten_all_ports
if len(port_list) == 0 and port_country:
display_all_ports = False
for port_index, port in ports.all_ports_by_country(port_country).items():
all_ports.append(port)
if display_all_ports:
all_ports = ports.all_ports()
else:
# Remove duplicates since there is a chance you'll double up on ports with how this code is structured
all_ports = list(set(all_ports))
for port in all_ports:
port_properties = "Port = " + port.name + \
"<br>Alternate Port Name = " + port.alternate_name + \
"<br>Country = " + port.country + \
"<br>Lat = " + str(port.position[1]) + \
"<br>Lon = " + str(port.position[0]) + \
"<br>Region = " + port.region + \
"<br>Water Body = " + port.water_body + \
"<br>UN/LOCODE = " + str(port.un_locode) + \
"<br>World Port Index Number = " + str(port.world_port_index_number)
# TODO (mjfadem): It appears that the WPI.shp file is made up of coordinates and not polygon lists so
# it's no different then the CSV data we use as a baseline. This needs a bit more investigation.
# if use_shapefile:
# sf = shapefile.Reader("src/Python/tracktable/info/data/WPI.shp")
# for record in sf.shapeRecords():
# fol.GeoJson(shape(record.shape.__geo_interface__)).add_to(map_canvas)
if use_markers:
fol.Marker(location=[port.position[1], port.position[0]], tooltip=port.name,
popup=fol.Popup(port_properties, max_width=popup_width, min_width=popup_width),
icon=fol.Icon(color=port_color, icon='fa-ship', prefix='fa')).add_to(map_canvas)
else:
fol.CircleMarker((port.position[1], port.position[0]), radius=port_dot_size, fill=True,
fill_opacity=1.0,
fill_color=port_color,
color=port_color,
tooltip=port.name,
popup=fol.Popup(port_properties, max_width=popup_width, min_width=popup_width)).add_to(map_canvas)
# ----------------------------------------------------------------------
[docs]
def render_shorelines_rivers_borders(map_canvas,
draw_shorelines=False,
draw_rivers=False,
draw_borders=False,
shoreline_color='red',
river_color='blue',
border_color='green',
shoreline_fill_polygon=True,
shoreline_fill_color='red',
popup_width=375,
shoreline_list=[],
river_list=[],
border_list=[],
shoreline_bounding_box=None,
river_bounding_box=None,
border_bounding_box=None,
shoreline_resolution="low",
shoreline_level="L1",
river_resolution="low",
river_level="L01",
border_resolution="low",
border_level="L1",
display_polygon_tooltip=True,
**kwargs):
"""Renders shorelines, rivers and/or borders to the folium map
Args:
map_canvas (folium map object): Canvas to draw the shorelines/rivers/borders on
Keyword Arguments:
draw_shorelines (bool): Whether or not to draw shorelines (Default: False)
draw_rivers (bool): Whether or not to draw rivers (Default: False)
draw_borders (bool): Whether or not to draw borders (Default: False)
shoreline_color (name of standard color as string, hex color string or
matplotlib color object): Color of the shoreline (Default: 'red')
river_color_color (name of standard color as string, hex color string or
matplotlib color object): Color of the river (Default: 'blue')
border_color (name of standard color as string, hex color string or
matplotlib color object): Color of the border (Default: 'green')
shoreline_fill_polygon (bool): Whether or not to fill in the inside of the shoreline polygon (Default: True)
shoreline_fill_color (name of standard color as string, hex color string or
matplotlib color object): Fill color of the shoreline (Default: 'red')
popup_width (int): Size of the popup window that displays airport/port information, used for Folium rendering only (Default: 375)
shoreline_list (list(int)): GSHHS index number of the shoreline polygons to render (Default: [])
river_list (list(int)): WDBII index number of the river polygons to render (Default: [])
border_list (list(int)): WDBII index number of the border polygons to render (Default: [])
shoreline_bounding_box (BoundingBox): bounding box for
rendering shorelines within. (Default: None)
river_bounding_box (BoundingBox): bounding box for
rendering rivers within. (Default: None)
border_bounding_box (BoundingBox): bounding box for
rendering borders within. (Default: None)
shoreline_resolution (string): Resolution of the shapes to pull from the shapefile. (Default: "low")
shoreline_level (string): See the docstring for build_shoreline_dict() for more information about levels. (Default: "L1")
river_resolution (string): Resolution of the shapes to pull from the shapefile. (Default: "low")
river_level (string): See the docstring for build_river_dict() for more information about levels. (Default: "L01")
border_resolution (string): Resolution of the shapes to pull from the shapefile. (Default: "low")
border_level (string): See the docstring for build_border_dict() for more information about levels. (Default: "L1")
display_polygon_tooltip (bool): Whether or not to display the tooltip when hovering over a polygon. (Default: True)
Returns:
No return value
"""
if draw_shorelines:
display_all_shorelines = True
all_shorelines = []
if shoreline_bounding_box:
display_all_shorelines = False
for shoreline_index, shoreline in shorelines.all_shorelines_within_bounding_box(shoreline_bounding_box, resolution=shoreline_resolution, level=shoreline_level).items():
all_shorelines.append(shoreline)
if len(shoreline_list) > 0:
display_all_shorelines = False
for shoreline in shoreline_list:
all_shorelines.append(shorelines.shoreline_information(shoreline, resolution=shoreline_resolution, level=shoreline_level))
if display_all_shorelines:
all_shorelines = shorelines.all_shorelines(resolution=shoreline_resolution, level=shoreline_level)
else:
# Remove duplicates since there is a chance you'll double up on shorelines with how this code is structured
all_shorelines = list(set(all_shorelines))
for shoreline in all_shorelines:
shoreline_properties = f"GSHHS Index = {shoreline.index} <br>Bounding Box = {shoreline.shape_bbox} <br>Centroid = {shoreline.shape_centroid} <br>GSHHS Level = {shoreline.level} <br>Resolution = {shoreline.resolution}"
if shoreline_fill_polygon:
if display_polygon_tooltip:
fol_geojson_obj = fol.GeoJson(data=shoreline.geojson,
tooltip=shoreline.index,
style_function=lambda x: {'fillColor': shoreline_fill_color, 'color': shoreline_color})
else:
fol_geojson_obj = fol.GeoJson(data=shoreline.geojson,
style_function=lambda x: {'fillColor': shoreline_fill_color, 'color': shoreline_color})
else:
if display_polygon_tooltip:
fol_geojson_obj = fol.GeoJson(data=shoreline.geojson,
tooltip=shoreline.index,
style_function=lambda x: {'fillColor': 'none', 'color': shoreline_color})
else:
fol_geojson_obj = fol.GeoJson(data=shoreline.geojson,
style_function=lambda x: {'fillColor': 'none', 'color': shoreline_color})
fol.Popup(shoreline_properties, max_width=popup_width, min_width=popup_width).add_to(fol_geojson_obj)
fol_geojson_obj.add_to(map_canvas)
if draw_rivers:
display_all_rivers = True
all_rivers = []
if river_bounding_box:
display_all_rivers = False
for river_index, river in rivers.all_rivers_within_bounding_box(river_bounding_box, resolution=river_resolution, level=river_level).items():
all_rivers.append(river)
if len(river_list) > 0:
display_all_rivers = False
for river in river_list:
all_rivers.append(rivers.river_information(river, resolution=river_resolution, level=river_level))
if display_all_rivers:
all_rivers = rivers.all_rivers(resolution=river_resolution, level=river_level)
else:
# Remove duplicates since there is a chance you'll double up on rivers with how this code is structured
all_rivers = list(set(all_rivers))
for river in all_rivers:
river_properties = f"WBDII River Index = {river.index} <br>Bounding Box = {river.shape_bbox} <br>Centroid = {river.shape_centroid} <br>WBDII Level = {river.level} <br>Resolution = {river.resolution}"
if display_polygon_tooltip:
fol_geojson_obj = fol.GeoJson(data=river.geojson,
tooltip=river.index,
style_function=lambda x: {'fillColor': 'none', 'color': river_color})
else:
fol_geojson_obj = fol.GeoJson(data=river.geojson,
style_function=lambda x: {'fillColor': 'none', 'color': river_color})
fol.Popup(river_properties, max_width=popup_width, min_width=popup_width).add_to(fol_geojson_obj)
fol_geojson_obj.add_to(map_canvas)
if draw_borders:
display_all_borders = True
all_borders = []
if border_bounding_box:
display_all_borders = False
for border_index, border in borders.all_borders_within_bounding_box(border_bounding_box, resolution=border_resolution, level=border_level).items():
all_borders.append(border)
if len(border_list) > 0:
display_all_borders = False
for border in border_list:
all_borders.append(borders.border_information(border, resolution=border_resolution, level=border_level))
if display_all_borders:
all_borders = borders.all_borders(resolution=border_resolution, level=border_level)
else:
# Remove duplicates since there is a chance you'll double up on borders with how this code is structured
all_borders = list(set(all_borders))
for border in all_borders:
border_properties = f"WBDII Border Index = {border.index} <br>Bounding Box = {border.shape_bbox} <br>Centroid = {border.shape_centroid} <br>WBDII Level = {border.level} <br>Resolution = {border.resolution}"
if display_polygon_tooltip:
fol_geojson_obj = fol.GeoJson(data=border.geojson,
tooltip=border.index,
style_function=lambda x: {'fillColor': 'none', 'color': border_color})
else:
fol_geojson_obj = fol.GeoJson(data=border.geojson,
style_function=lambda x: {'fillColor': 'none', 'color': border_color})
fol.Popup(border_properties, max_width=popup_width, min_width=popup_width).add_to(fol_geojson_obj)
fol_geojson_obj.add_to(map_canvas)
# ----------------------------------------------------------------------
[docs]
def bounding_box_for_folium(trajectories):
"""Translates a computed bounding box to the format needed by folium
"""
raw_bbox = compute_bounding_box(itertools.chain(*trajectories))
# folium needs two corner points [sw, ne], in (lat,lon) order
box_for_folium = [(raw_bbox.min_corner[1], raw_bbox.min_corner[0]),
(raw_bbox.max_corner[1], raw_bbox.max_corner[0])]
return box_for_folium