Skip to content

Commit bacf764

Browse files
committed
enhance structure
1 parent e3a4150 commit bacf764

File tree

5 files changed

+94
-27
lines changed

5 files changed

+94
-27
lines changed

pretty_gpx/rendering_modes/city/data/roads.py

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from dataclasses import dataclass
55
from enum import auto
66
from enum import Enum
7+
from typing import Any
78

89
from tqdm import tqdm
910

@@ -29,13 +30,46 @@ class CityRoadType(Enum):
2930
ACCESS_ROAD = auto()
3031

3132

33+
class RoadPrecisionLevel:
34+
"""City road precision level."""
35+
def __init__(self, name: str, priority: int):
36+
self.name = name
37+
self.priority = priority
38+
39+
def __str__(self) -> str:
40+
return self.name
41+
42+
def __eq__(self, other: Any) -> bool:
43+
if isinstance(other, RoadPrecisionLevel):
44+
return self.priority == other.priority
45+
return False
46+
47+
def __lt__(self, other: 'RoadPrecisionLevel') -> bool:
48+
if isinstance(other, RoadPrecisionLevel):
49+
return self.priority < other.priority
50+
return NotImplemented
51+
52+
53+
class CityRoadPrecision(Enum):
54+
"""Enum defining different road precision levels."""
55+
VERY_HIGH = RoadPrecisionLevel(name="Very-High", priority=3)
56+
HIGH = RoadPrecisionLevel(name="High", priority=2)
57+
MEDIUM = RoadPrecisionLevel(name="Medium", priority=1)
58+
LOW = RoadPrecisionLevel(name="Low", priority=0)
59+
60+
@classmethod
61+
def from_string(cls, precision_name: str) -> 'CityRoadPrecision':
62+
"""Convert a string representation to a CityRoadPrecision enum value."""
63+
for precision in cls:
64+
if precision.value.name.lower() == precision_name.lower():
65+
return precision
66+
raise ValueError(f"Invalid precision level: {precision_name}")
67+
68+
@property
69+
def priority(self) -> int:
70+
"""Get the priority value for the precision level."""
71+
return self.value.priority
3272

33-
ROAD_PRECISION_LEVEL = {
34-
"Low":0,
35-
"Medium":1,
36-
"High":2,
37-
"Very High": 3
38-
}
3973

4074
@dataclass(frozen=True)
4175
class RoadTypeData:
@@ -57,16 +91,19 @@ class RoadTypeData:
5791
key=lambda road_type: ROAD_TYPE_DATA[road_type].priority)
5892

5993

60-
def get_city_roads_with_priority_better_than(x: int) -> list[CityRoadType]:
94+
def get_city_roads_with_priority_better_than(precision: CityRoadPrecision) -> list[CityRoadType]:
6195
"""Returns a list of CityRoadType with a priority better than the given x."""
6296
# Filter ROAD_TYPE_DATA to get only those with priority less than x
63-
return [road_type for road_type, data in ROAD_TYPE_DATA.items() if data.priority <= x]
64-
97+
return [
98+
road_type
99+
for road_type, data in ROAD_TYPE_DATA.items()
100+
if data.priority <= precision.priority
101+
]
65102

66103
@profile
67104
def prepare_download_city_roads(query: OverpassQuery,
68105
bounds: GpxBounds,
69-
road_precision: str) -> list[CityRoadType]:
106+
road_precision: CityRoadPrecision) -> list[CityRoadType]:
70107
"""Download roads map from OpenStreetMap.
71108
72109
Args:
@@ -79,10 +116,9 @@ def prepare_download_city_roads(query: OverpassQuery,
79116
"""
80117
cache_pkl = ROADS_CACHE.get_path(bounds)
81118

82-
assert road_precision in ROAD_PRECISION_LEVEL.keys()
119+
logger.debug(f"Road precision: {road_precision.name}")
83120

84-
logger.debug(f"Road precision : {road_precision}")
85-
roads_to_plot: list[CityRoadType] = get_city_roads_with_priority_better_than(ROAD_PRECISION_LEVEL[road_precision])
121+
roads_to_plot: list[CityRoadType] = get_city_roads_with_priority_better_than(road_precision)
86122

87123
if os.path.isfile(cache_pkl):
88124
cityroads_cache: dict[CityRoadType, list[ListLonLat]] = read_pickle(file_path=cache_pkl)
@@ -112,9 +148,9 @@ def prepare_download_city_roads(query: OverpassQuery,
112148
def process_city_roads(query: OverpassQuery,
113149
bounds: GpxBounds,
114150
city_roads_downloaded: list[CityRoadType],
115-
road_precision: str) -> dict[CityRoadType,list[ListLonLat]]:
151+
road_precision: CityRoadPrecision) -> dict[CityRoadType,list[ListLonLat]]:
116152
"""Query the overpass API to get the roads of a city."""
117-
roads_to_plot: list[CityRoadType] = get_city_roads_with_priority_better_than(ROAD_PRECISION_LEVEL[road_precision])
153+
roads_to_plot: list[CityRoadType] = get_city_roads_with_priority_better_than(road_precision)
118154

119155
if len(city_roads_downloaded) > 0:
120156
# We need to process some downloaded road types.

pretty_gpx/rendering_modes/city/drawing/city_background.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pretty_gpx.rendering_modes.city.data.forests import process_city_forests
1717
from pretty_gpx.rendering_modes.city.data.rivers import prepare_download_city_rivers
1818
from pretty_gpx.rendering_modes.city.data.rivers import process_city_rivers
19+
from pretty_gpx.rendering_modes.city.data.roads import CityRoadPrecision
1920
from pretty_gpx.rendering_modes.city.data.roads import CityRoadType
2021
from pretty_gpx.rendering_modes.city.data.roads import prepare_download_city_roads
2122
from pretty_gpx.rendering_modes.city.data.roads import process_city_roads
@@ -51,7 +52,7 @@ class CityBackground:
5152
@staticmethod
5253
@profile
5354
def from_union_bounds(union_bounds: GpxBounds,
54-
road_precision: str) -> 'CityBackground':
55+
road_precision: CityRoadPrecision) -> 'CityBackground':
5556
"""Initialize the City Background from the Union Bounds."""
5657
total_query = OverpassQuery()
5758
roads_downloaded = prepare_download_city_roads(total_query, union_bounds, road_precision)

pretty_gpx/rendering_modes/city/drawing/city_params.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pretty_gpx.common.drawing.utils.fonts import FontEnum
1313
from pretty_gpx.common.drawing.utils.plt_marker import MarkerType
1414
from pretty_gpx.common.drawing.utils.scatter_point import ScatterPointCategory
15+
from pretty_gpx.rendering_modes.city.data.roads import CityRoadPrecision
1516
from pretty_gpx.rendering_modes.city.data.roads import CityRoadType
1617
from pretty_gpx.rendering_modes.city.drawing.city_colors import CITY_COLOR_THEMES
1718

@@ -48,7 +49,7 @@ class CityParams:
4849
user_title: str | None = None
4950
user_uphill_m: int | None = None
5051
user_dist_km: float | None = None
51-
user_road_precision: str = "Medium"
52+
user_road_precision: CityRoadPrecision = CityRoadPrecision.MEDIUM
5253

5354
@staticmethod
5455
def default() -> "CityParams":

pretty_gpx/ui/pages/city/page.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from dataclasses import dataclass
44

55
from pretty_gpx.common.drawing.utils.scatter_point import ScatterPointCategory
6+
from pretty_gpx.rendering_modes.city.data.roads import CityRoadPrecision
67
from pretty_gpx.rendering_modes.city.drawing.city_colors import CITY_COLOR_THEMES
78
from pretty_gpx.rendering_modes.city.drawing.city_drawer import _update_city_background
89
from pretty_gpx.rendering_modes.city.drawing.city_drawer import CityDrawer
910
from pretty_gpx.rendering_modes.city.drawing.city_params import CityParams
10-
from pretty_gpx.ui.pages.template.ui_input import UiDropdownStr
11+
from pretty_gpx.ui.pages.template.ui_input import UiDropdownGeneric
1112
from pretty_gpx.ui.pages.template.ui_input import UiInputInt
1213
from pretty_gpx.ui.pages.template.ui_manager import UiManager
1314
from pretty_gpx.ui.utils.run import run_cpu_bound
@@ -22,7 +23,7 @@ def city_page() -> None:
2223
class CityUiManager(UiManager[CityDrawer]):
2324
"""City Ui Manager."""
2425
uphill: UiInputInt
25-
road_precision: UiDropdownStr
26+
road_precision: UiDropdownGeneric
2627

2728
def __init__(self) -> None:
2829
drawer = CityDrawer(params=CityParams.default(), top_ratio=0.18, bot_ratio=0.22, margin_ratio=0.1)
@@ -34,11 +35,12 @@ def __init__(self) -> None:
3435
self.uphill = UiInputInt.create(label='D+ (m)', value="", on_enter=self.on_click_update,
3536
tooltip="Press Enter to override elevation from GPX")
3637

37-
self.road_precision = UiDropdownStr.create(label='Road precision',
38-
discrete_val=["Low", "Medium", "High", "Very High"],
39-
default_idx=1,
40-
on_change=self.background_update,
41-
tooltip="Change the roads level of details")
38+
precision_dropdown_values = {precision: precision.name for precision in CityRoadPrecision}
39+
self.road_precision = UiDropdownGeneric.create(label='Road precision',
40+
discrete_val=precision_dropdown_values,
41+
default_val=CityRoadPrecision.MEDIUM,
42+
on_change=self.background_update,
43+
tooltip="Change the roads level of details")
4244

4345

4446
@staticmethod

pretty_gpx/ui/pages/template/ui_input.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
33
from collections.abc import Awaitable
44
from collections.abc import Callable
55
from dataclasses import dataclass
6+
from typing import Any
67
from typing import Self
78

89
from nicegui import ui
10+
from typing_extensions import TypedDict
911

1012
from pretty_gpx.common.utils.utils import safe
1113

1214

15+
class DiscreteValue(TypedDict):
16+
"""Structured representation of discrete values for dropdowns."""
17+
name: str
18+
priority: Any
19+
20+
1321
@dataclass
1422
class UiInput:
1523
"""NiceGUI Input Wrapper."""
@@ -43,14 +51,19 @@ class UiDropdown:
4351
def create(cls,
4452
*,
4553
label: str,
46-
discrete_val: list[str],
47-
default_idx: int,
54+
discrete_val: list[str] | dict[Any, str],
55+
default_val: Any,
4856
tooltip: str,
4957
on_change: Callable[[], Awaitable[None]]) -> Self:
5058
"""Create NiceGUI Dropdown select element and add a tooltip."""
59+
if isinstance(discrete_val, list):
60+
discrete_val = {val: val for val in discrete_val}
61+
62+
assert isinstance(discrete_val, dict)
63+
assert default_val in list(discrete_val.keys())
5164
with ui.select(discrete_val,
5265
label=label,
53-
value=discrete_val[default_idx]).on('update:modelValue', on_change).style('width:100%') as input:
66+
value=default_val).on('update:modelValue', on_change).style('width:100%') as input:
5467
ui.tooltip(tooltip)
5568
return cls(input)
5669

@@ -60,6 +73,12 @@ def _value_str(self) -> str:
6073
val = str(safe(self.input.value))
6174
return val
6275

76+
@property
77+
def _value_raw(self) -> Any:
78+
"""Return the str value."""
79+
val: Any = safe(self.input.value)
80+
return val
81+
6382

6483
@dataclass
6584
class UiDropdownStr(UiDropdown):
@@ -70,6 +89,14 @@ def value(self) -> str:
7089
"""Return the value."""
7190
return self._value_str
7291

92+
@dataclass
93+
class UiDropdownGeneric(UiDropdown):
94+
"""NiceGUI Str Dropdown Wrapper."""
95+
96+
@property
97+
def value(self) -> Any:
98+
"""Return the value."""
99+
return self._value_raw
73100

74101
@dataclass
75102
class UiInputStr(UiInput):

0 commit comments

Comments
 (0)