Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nav 183 make viewer app responsive #20

Merged
merged 5 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 2 additions & 19 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions public_transit_viewer/components/logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import streamlit as st
import base64

from public_transit_viewer import LOGO_PATH

def show_logo(max_width: int = 150, padding: int = 15) -> None:

image = LOGO_PATH.read_bytes()
base64_image = base64.b64encode(image).decode()

st.html(
f"""
<style>
.logo-image {{
max-width: {max_width}px;
width: 100%;
padding: {padding}px;
}}
</style>
<div style="text-align: center;">
<img src="data:image/png;base64,{base64_image}" class="logo-image"/>
</div>
"""
)
155 changes: 155 additions & 0 deletions public_transit_viewer/components/map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import streamlit.components.v1 as components
import html
from folium import Map, Marker, Circle, PolyLine, Popup, Tooltip, Icon # type: ignore
from typing import Any


def display_map(map: Map, height: int = 400) -> None:

zoom: int = map.options.get("zoom", 10) # type: ignore

children = _create_children_js(map)

components.html(
f"""
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/python-visualization/folium/folium/templates/leaflet.awesome.rotate.min.css">
<style>
#map {{
height: {height}px;
width: 100%;
}}
</style>
<div id="map"></div>
<script>
const map = L.map('map').setView([{map.location[0]}, {map.location[1]}], {zoom});

const tiles = L.tileLayer(
"https://{{s}}.basemaps.cartocdn.com/light_all/{{z}}/{{x}}/{{y}}{{r}}.png",
{{
"attribution": '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
"detectRetina": false,
"maxNativeZoom": 20,
"maxZoom": 20,
"minZoom": 0,
"noWrap": false,
"opacity": 1,
"subdomains": "abcd",
"tms": false
}}
).addTo(map);

{children}

</script>
""",
height=height + 10, # for margin
)


def _create_children_js(map: Map) -> str:

children = ""

for name, child in map._children.items(): # type: ignore
# PolyLine is Child of Marker!!
if isinstance(child, PolyLine):
children += _create_polyline_js(child, name) # type: ignore
elif isinstance(child, Circle):
children += _create_circle_js(child, name) # type: ignore
elif isinstance(child, Marker):
children += _create_marker_js(child, name) # type: ignore

return children


def _create_marker_js(marker: Marker, name: str) -> str:
lat = marker.location[0] # type: ignore
lon = marker.location[1] # type: ignore
options = _convert_dict_to_options_string(marker.options) # type: ignore
js = f"var {name} = L.marker([{lat}, {lon}], {options}).addTo(map);\n\n"
js += _get_object_children_js(marker, name)
return js


def _create_circle_js(circle: Circle, name: str) -> str:
lat = circle.location[0] # type: ignore
lon = circle.location[1] # type: ignore
options = _convert_dict_to_options_string(circle.options) # type: ignore
js = f"var {name} = L.circle([{lat}, {lon}], {options}).addTo(map);\n\n"
js += _get_object_children_js(circle, name)
return js


def _create_polyline_js(polyline: PolyLine, name: str) -> str:
# build coordinates string
coordinates = "["
for coord in polyline.locations:
coordinates += _convert_coordinates_to_string(coord) + "," # type: ignore
coordinates += "]"

options = _convert_dict_to_options_string(polyline.options) # type: ignore

js = f"var {name} = L.polyline({coordinates}, {options}).addTo(map);\n\n"
js += _get_object_children_js(polyline, name)
return js


def _convert_dict_to_options_string(options: dict[str, Any]) -> str:
options_list: list[str] = []
for key, value in options.items():
if isinstance(value, str):
options_list.append(f"{key}: '{value}'")
elif isinstance(value, bool):
options_list.append(f"{key}: {str(value).lower()}")
elif isinstance(value, float) or isinstance(value, int):
options_list.append(f"{key}: {value}")
return "{" + ", ".join(options_list) + "}"


def _convert_coordinates_to_string(coordinates: list[float]) -> str:
return f"[{coordinates[0]}, {coordinates[1]}]"


def _get_object_children_js(obj: Marker | Circle | PolyLine, parent: str) -> str:
children = ""
for name, child in obj._children.items(): # type: ignore
if isinstance(child, Popup):
children += _create_popup_js(child, parent) # type: ignore
elif isinstance(child, Icon):
children += _create_icon_js(child, name, parent) # type: ignore
elif isinstance(child, Tooltip):
children += _create_tooltip_js(child, parent) # type: ignore
return children


def _create_popup_js(popup: Popup, parent: str) -> str:
if len(popup.html._children.values()) == 0: # type: ignore
return ""
content = _escape_js_string(list(popup.html._children.values())[0].data) # type: ignore
maybe_show = ".openPopup()" if popup.show else ""
return f"{parent}.bindPopup('{content}'){maybe_show};\n\n"


def _create_icon_js(icon: Icon, name: str, parent: str) -> str:
icon_options = _convert_dict_to_options_string(icon.options) # type: ignore
js = f"var {name} = L.AwesomeMarkers.icon({icon_options});\n"
js += f"{parent}.setIcon({name});\n\n"
return js


def _create_tooltip_js(tooltip: Tooltip, parent: str) -> str:
return f"{parent}.bindTooltip('{_escape_js_string(tooltip.text)}');" # type: ignore


def _escape_js_string(string: str) -> str:
# Escapes quotes and other necessary characters for JavaScript strings
return html.escape(string).replace("'", "\\'").replace('"', '\\"')
7 changes: 5 additions & 2 deletions public_transit_viewer/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from public_transit_client.model import Connection
from streamlit_searchbox import st_searchbox # type: ignore

from public_transit_viewer import LOGO_PATH, ICON_PATH
from public_transit_viewer import ICON_PATH
from public_transit_viewer.components.form_components import (
query_config_expandable,
time_form_row,
)
from public_transit_viewer.components.logo import show_logo
from public_transit_viewer.utils.client import get_connections, get_stop_suggestions
from public_transit_viewer.utils.connection import output_connection

Expand All @@ -21,7 +22,9 @@

header_col1, header_col2 = st.columns([1, 4])

header_col1.image(str(LOGO_PATH), use_column_width=True)
with header_col1:
show_logo(max_width=150)

header_col2.title("Naviqore")
header_col2.write("Search and visualize connections between stops") # type: ignore

Expand Down
11 changes: 7 additions & 4 deletions public_transit_viewer/pages/isolines.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
import streamlit as st
from dotenv import dotenv_values
from public_transit_client.model import Stop, TimeType
from streamlit_folium import st_folium # type: ignore
from streamlit_searchbox import st_searchbox # type: ignore

from public_transit_viewer import LOGO_PATH, ICON_PATH
from public_transit_viewer import ICON_PATH
from public_transit_viewer.components.form_components import (
query_config_expandable,
time_form_row,
)
from public_transit_viewer.components.logo import show_logo
from public_transit_viewer.components.map import display_map
from public_transit_viewer.utils.client import get_isolines, get_stop_suggestions
from public_transit_viewer.utils.color import get_color_map_hex_value

Expand All @@ -23,7 +24,9 @@

header_col1, header_col2 = st.columns([1, 4])

header_col1.image(str(LOGO_PATH), use_column_width=True)
with header_col1:
show_logo(max_width=150)

header_col2.title("Naviqore")
header_col2.write("Visualize isolines from a source stop") # type: ignore

Expand Down Expand Up @@ -215,4 +218,4 @@ def show_remaining_distance_circles(
else:
show_remaining_distance_circles(m, row, filter_value, walking_speed) # type: ignore

st_folium(m, use_container_width=True) # type: ignore
display_map(m, height=600)
8 changes: 4 additions & 4 deletions public_transit_viewer/utils/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import folium # type: ignore
import streamlit as st
from public_transit_client.model import Connection, Leg
from streamlit_folium import folium_static # type: ignore
from public_transit_viewer.components.map import display_map

stop_icon_args = {
"prefix": "fa",
Expand Down Expand Up @@ -113,7 +113,7 @@ def show_leg(leg: Leg):
if leg.is_walk:
walk_duration = leg.duration // 60
st.markdown(
f"{walk_duration}' Walk",
f"{walk_duration} Minute Walk",
help=f"Distance: {float(leg.distance) / 1000:.2f} km",
)
elif leg.trip is not None:
Expand Down Expand Up @@ -235,7 +235,7 @@ def show_map(connection: Connection):
line_args["dash_array"] = "5"

walk_duration = leg.duration // 60
label = f"{walk_duration}' Walk"
label = f"{walk_duration} Minute Walk"

if leg.trip is not None:
trip = leg.trip
Expand All @@ -247,4 +247,4 @@ def show_map(connection: Connection):

with st.expander("Show map"): # type: ignore
st.write("Map of the connection") # type: ignore
folium_static(m, width=600, height=400) # type: ignore
display_map(m)
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pydantic = "^2.8.2"
streamlit = "^1.34,<1.35"
streamlit-searchbox = "^0.1.14"
folium = "^0.17.0"
streamlit-folium = "^0.22.0"
watchdog = "^4.0.2"
public-transit-client = "^0.5.2"

Expand Down
Loading