44from pathlib import Path
55from typing import List , Optional
66
7+ import fastkml .geometry
78import fastkml .kml
9+ import fastkml .views
810from geopy import Point
911from geopy .distance import great_circle
1012
@@ -40,7 +42,7 @@ class KmlPlaceNameTag(Tag):
4042 def __init__ (self ):
4143 self .log = logging .getLogger (self .__class__ .__name__ )
4244
43- def configure (
45+ def configure ( # type: ignore[override]
4446 self ,
4547 kml : str ,
4648 use_look_at : bool = True ,
@@ -68,7 +70,7 @@ def configure(
6870 with open (to_csv , "w" ) as csv_file :
6971 csv_file .writelines (
7072 [
71- f"{ p .latitude } , { p .longitude } , { p .radius / 1000 } , { p .name } \n "
73+ f"{ p .latitude } , { p .longitude } , { p .radius / 1000 if p . radius else 0 } , { p .name } \n "
7274 for p in self .places
7375 ]
7476 )
@@ -81,7 +83,17 @@ def _load_file(
8183
8284 def _collect_places (kml , prefix : List [str ]) -> List [Place ]:
8385 if isinstance (kml , fastkml .containers .Folder ):
84- if use_folders and kml .view is not None :
86+ if (
87+ use_folders
88+ and kml .view is not None
89+ and isinstance (kml .view , fastkml .views .LookAt )
90+ ):
91+ assert kml .name is not None , "Folder name is required"
92+ assert kml .view .latitude is not None , "Folder latitude is required"
93+ assert (
94+ kml .view .longitude is not None
95+ ), "Folder longitude is required"
96+ assert kml .view .range is not None , "Folder range is required"
8597 folder_place = Place (
8698 "/" .join (prefix + [kml .name ]),
8799 kml .view .latitude ,
@@ -94,9 +106,22 @@ def _collect_places(kml, prefix: List[str]) -> List[Place]:
94106 [_collect_places (f , prefix ) for f in kml .features ]
95107 )
96108 )
109+ assert kml .name is not None , "Folder name is required"
97110 prefix = prefix + [kml .name ]
98111 elif isinstance (kml , fastkml .containers .Placemark ):
99- if use_look_at and kml .view is not None :
112+ if (
113+ use_look_at
114+ and kml .view is not None
115+ and isinstance (kml .view , fastkml .views .LookAt )
116+ ):
117+ assert kml .name is not None , "Placemark name is required"
118+ assert (
119+ kml .view .latitude is not None
120+ ), "Placemark latitude is required"
121+ assert (
122+ kml .view .longitude is not None
123+ ), "Placemark longitude is required"
124+ assert kml .view .range is not None , "Placemark range is required"
100125 return [
101126 Place (
102127 "/" .join (prefix + [kml .name ]),
@@ -106,9 +131,25 @@ def _collect_places(kml, prefix: List[str]) -> List[Place]:
106131 )
107132 ]
108133 else :
109- latitude = kml .kml_geometry .kml_coordinates .coords [0 ][0 ]
110- longitude = kml .kml_geometry .kml_coordinates .coords [0 ][1 ]
111- radius = kml .view .range if kml .view / 2 is not None else None
134+ assert kml .name is not None , "Placemark name is required"
135+ assert (
136+ kml .kml_geometry is not None
137+ ), "Placemark geometry is required"
138+ assert isinstance (
139+ kml .kml_geometry , fastkml .geometry .Point
140+ ), "Only Point geometry is supported"
141+ assert (
142+ kml .kml_geometry .kml_coordinates is not None
143+ ), "Point coordinates are required"
144+ latitude = kml .kml_geometry .kml_coordinates .coords [0 ][1 ]
145+ longitude = kml .kml_geometry .kml_coordinates .coords [0 ][0 ]
146+ radius = (
147+ kml .view .range / 2
148+ if kml .view is not None
149+ and isinstance (kml .view , fastkml .views .LookAt )
150+ and kml .view .range is not None
151+ else None
152+ )
112153 return [
113154 Place (
114155 "/" .join (prefix + [kml .name ]),
@@ -146,15 +187,17 @@ def process(self, file: File, context: Optional[str]) -> str:
146187 def _find_best_match (places : List [Place ], point : Point ) -> Optional [Place ]:
147188 gc = great_circle ()
148189
149- places_in_range = filter (
150- lambda p : gc .measure (p .position , point ) < p .radius , places
151- )
190+ places_in_range = [
191+ p
192+ for p in places
193+ if p .radius is not None and gc .measure (p .position , point ) < p .radius
194+ ]
195+
196+ if not places_in_range :
197+ return None
152198
153- # FIXME: Fix the types
154- smallest_place_in_range = next (
155- sorted (places_in_range , key = lambda p : p .radius ), None
156- )
157- return smallest_place_in_range
199+ # Return place with smallest radius
200+ return min (places_in_range , key = lambda p : p .radius or float ("inf" ))
158201
159202 # min_distance: float = -1
160203 # min_distance_place: Optional[Place] = None
0 commit comments