apyefa 0.0.6__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apyefa might be problematic. Click here for more details.

apyefa/client.py CHANGED
@@ -1,27 +1,39 @@
1
1
  import logging
2
+ from datetime import date, datetime
3
+ from typing import Final
2
4
 
3
5
  import aiohttp
4
6
 
5
7
  from apyefa.commands import (
6
8
  Command,
9
+ CommandCoord,
7
10
  CommandDepartures,
11
+ CommandGeoObject,
12
+ CommandLineList,
13
+ CommandLineStop,
8
14
  CommandServingLines,
9
15
  CommandStopFinder,
16
+ CommandStopList,
10
17
  CommandSystemInfo,
18
+ CommandTrip,
11
19
  )
12
20
  from apyefa.data_classes import (
13
21
  CoordFormat,
14
22
  Departure,
23
+ Jorney,
15
24
  Line,
16
25
  LineRequestType,
17
26
  Location,
18
27
  LocationFilter,
19
28
  LocationType,
29
+ PointTypeFilter,
20
30
  SystemInfo,
21
31
  )
22
- from apyefa.exceptions import EfaConnectionError
32
+ from apyefa.exceptions import EfaConnectionError, EfaFormatNotSupported
33
+ from apyefa.helpers import is_date
23
34
 
24
- _LOGGER = logging.getLogger(__name__)
35
+ _LOGGER: Final = logging.getLogger(__name__)
36
+ QUERY_TIMEOUT: Final = 30 # seconds
25
37
 
26
38
 
27
39
  class EfaClient:
@@ -32,57 +44,88 @@ class EfaClient:
32
44
  async def __aexit__(self, *args, **kwargs):
33
45
  await self._client_session.__aexit__(*args, **kwargs)
34
46
 
35
- def __init__(self, url: str, debug: bool = False):
47
+ def __init__(self, url: str, debug: bool = False, format: str = "rapidJSON"):
36
48
  """Create a new instance of client.
37
49
 
38
50
  Args:
39
- url (str): url string to EFA endpoint
51
+ url(str): EFA endpoint url
52
+ format(str, optional): Format of the response. Defaults to "rapidJSON".
40
53
 
41
54
  Raises:
42
- ValueError: No url provided
55
+ ValueError: If no url provided
56
+ EfaFormatNotSupported: If format is not supported
43
57
  """
44
58
  if not url:
45
59
  raise ValueError("No EFA endpoint url provided")
46
60
 
61
+ if format != "rapidJSON":
62
+ raise EfaFormatNotSupported(f"Format {format} is not supported")
63
+
47
64
  self._debug: bool = debug
65
+ self._format: str = format
48
66
  self._base_url: str = url if url.endswith("/") else f"{url}/"
49
67
 
50
68
  async def info(self) -> SystemInfo:
51
- """Get system info used by EFA endpoint.
69
+ """Get EFA endpoint system info.
52
70
 
53
71
  Returns:
54
72
  SystemInfo: info object
55
73
  """
56
74
  _LOGGER.info("Request system info")
57
75
 
58
- command = CommandSystemInfo()
76
+ command = CommandSystemInfo(self._format)
77
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
78
+
79
+ command.validate_params()
80
+
59
81
  response = await self._run_query(self._build_url(command))
60
82
 
61
83
  return command.parse(response)
62
84
 
63
85
  async def locations_by_name(
64
- self, name: str, *, filters: list[LocationFilter] = [], limit: int = 30
86
+ self,
87
+ name: str,
88
+ *,
89
+ filters: list[LocationFilter] = [],
90
+ limit: int = 30,
91
+ search_nearbly_stops: bool = False,
65
92
  ) -> list[Location]:
66
- """Find location(s) by provided `name`.
93
+ """
94
+ Asynchronously search for locations by name with optional filters and limits.
67
95
 
68
96
  Args:
69
- name (str): Name or ID of location to search (case insensitive)
70
- e.g. "Plärrer", "Nordostbanhof" or "de:09564:704"
71
- filters (list[LocationFilter], optional): List of filters to apply for search. Defaults to empty.
72
- limit (int, optional): Max size of returned list. Defaults to 30.
97
+ name (str): The name or ID of the location to search for.
98
+ filters (list[LocationFilter], optional): A list of filters to apply to the search. Defaults to an empty list.
99
+ limit (int, optional): The maximum number of locations to return. Defaults to 30.
100
+ search_nearbly_stops (bool, optional): Whether to include nearby stops in the search. Defaults to False.
101
+
102
+ Raises:
103
+ ValueError: If no name is provided.
73
104
 
74
105
  Returns:
75
- list[Location]: List of location(s) returned by endpoint. List is sorted by match quality.
106
+ list[Location]: A list of locations matching the search criteria.
76
107
  """
77
108
  _LOGGER.info(f"Request location search by name/id: {name}")
78
109
  _LOGGER.debug(f"filters: {filters}")
79
110
  _LOGGER.debug(f"limit: {limit}")
111
+ _LOGGER.debug(f"search_nearbly_stops: {search_nearbly_stops}")
112
+
113
+ if not name:
114
+ raise ValueError("No name provided")
80
115
 
81
- command = CommandStopFinder("any", name)
116
+ command = CommandStopFinder(self._format)
117
+
118
+ command.add_param("locationServerActive", 1)
119
+ command.add_param("type_sf", "any")
120
+ command.add_param("name_sf", name)
121
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
122
+ command.add_param("doNotSearchForStops_sf", not search_nearbly_stops)
82
123
 
83
124
  if filters:
84
125
  command.add_param("anyObjFilter_sf", sum(filters))
85
126
 
127
+ command.validate_params()
128
+
86
129
  response = await self._run_query(self._build_url(command))
87
130
 
88
131
  return command.parse(response)[:limit]
@@ -91,94 +134,355 @@ class EfaClient:
91
134
  self,
92
135
  coord_x: float,
93
136
  coord_y: float,
137
+ *,
94
138
  format: CoordFormat = CoordFormat.WGS84,
95
139
  limit: int = 10,
140
+ search_nearbly_stops: bool = False,
96
141
  ) -> Location:
97
- """Find location(s) by provided `coordinates`.
142
+ """
143
+ Asynchronously fetches location information based on given coordinates.
98
144
 
99
145
  Args:
100
- coord_x (float): X coordinate
101
- coord_y (float): Y coordinate
102
- format (CoordFormat, optional): Coordinate format. Defaults to CoordFormat.WGS84.
103
- limit (int, optional): Max size of returned list. Defaults to 10.
146
+ coord_x (float): The X coordinate (longitude).
147
+ coord_y (float): The Y coordinate (latitude).
148
+ format (CoordFormat, optional): The coordinate format. Defaults to CoordFormat.WGS84.
149
+ limit (int, optional): The maximum number of locations to return. Defaults to 10.
150
+ search_nearbly_stops (bool, optional): Whether to search for nearby stops. Defaults to False.
104
151
 
105
152
  Returns:
106
- Location: List of location(s) returned by endpoint. List is sorted by match quality.
153
+ Location: The location information based on the provided coordinates.
107
154
  """
108
155
  _LOGGER.info("Request location search by coordinates")
109
156
  _LOGGER.debug(f"coord_x: {coord_x}")
110
157
  _LOGGER.debug(f"coord_y: {coord_y}")
111
158
  _LOGGER.debug(f"format: {format}")
112
159
  _LOGGER.debug(f"limit: {limit}")
160
+ _LOGGER.debug(f"search_nearbly_stops: {search_nearbly_stops}")
161
+
162
+ command = CommandStopFinder(self._format)
163
+ command.add_param("locationServerActive", 1)
164
+ command.add_param("type_sf", "coord")
165
+ command.add_param("name_sf", f"{coord_x}:{coord_y}:{format}")
166
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
167
+ command.add_param("doNotSearchForStops_sf", not search_nearbly_stops)
113
168
 
114
- command = CommandStopFinder("coord", f"{coord_x}:{coord_y}:{format}")
169
+ command.validate_params()
115
170
 
116
171
  response = await self._run_query(self._build_url(command))
117
172
 
118
173
  return command.parse(response)[:limit]
119
174
 
120
- async def trip(self):
121
- raise NotImplementedError
175
+ async def list_lines(
176
+ self,
177
+ branch_code: str | None = None,
178
+ net_branch_code: str | None = None,
179
+ sub_network: str | None = None,
180
+ list_omc: str | None = None,
181
+ mixed_lines: bool = False,
182
+ merge_directions: bool = True,
183
+ req_types: list[LineRequestType] = [],
184
+ ) -> list[Line]:
185
+ """
186
+ Asynchronously retrieves a list of lines based on the provided parameters.
187
+
188
+ Args:
189
+ branch_code (str | None): The branch code to filter lines.
190
+ net_branch_code (str | None): The Network and optionally the code of the branch separated by colon.
191
+ sub_network (str | None): The sub-network to filter lines.
192
+ list_omc (str | None): The OMC(Open Method of Coordination) list to filter lines.
193
+ mixed_lines (bool): Activates the search of composed services. Defaults to False.
194
+ merge_directions (bool): Merges the inbound and outbound service. Thus only inbound services are listed. Defaults to True.
195
+ req_type (list[LineRequestType]): The request types to filter lines. Defaults to an empty list.
196
+
197
+ Returns:
198
+ list[Line]: A list of Line objects representing the lines.
199
+ """
200
+ _LOGGER.info("Request lines")
201
+ _LOGGER.debug(f"branch_code: {branch_code}")
202
+ _LOGGER.debug(f"net_branch_code: {net_branch_code}")
203
+ _LOGGER.debug(f"sub_network: {sub_network}")
204
+ _LOGGER.debug(f"list_omc: {list_omc}")
205
+ _LOGGER.debug(f"mixed_lines: {mixed_lines}")
206
+ _LOGGER.debug(f"merge_directions: {merge_directions}")
207
+ _LOGGER.debug(f"req_types: {req_types}")
208
+
209
+ command = CommandLineList(self._format)
210
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
211
+
212
+ if branch_code:
213
+ command.add_param("lineListBranchCode", branch_code)
214
+ if net_branch_code:
215
+ command.add_param("lineListNetBranchCode", net_branch_code)
216
+ if sub_network:
217
+ command.add_param("lineListSubnetwork", sub_network)
218
+ if list_omc:
219
+ command.add_param("lineListOMC", list_omc)
220
+ if mixed_lines:
221
+ command.add_param("lineListMixedLines", mixed_lines)
222
+ if not merge_directions:
223
+ command.add_param("mergeDir", merge_directions)
224
+ if req_types:
225
+ command.add_param("lineReqType", sum(req_types))
226
+
227
+ command.validate_params()
228
+
229
+ response = await self._run_query(self._build_url(command))
230
+
231
+ return command.parse(response)
232
+
233
+ async def list_stops(
234
+ self,
235
+ omc: str | None = None,
236
+ place_id: str | None = None,
237
+ omc_place_id: str | None = None,
238
+ rtn: str | None = None,
239
+ sub_network: str | None = None,
240
+ from_stop: str | None = None,
241
+ to_stop: str | None = None,
242
+ serving_lines: bool = True,
243
+ serving_lines_mot_type: bool = True,
244
+ serving_lines_mot_types: bool = False,
245
+ tarif_zones: bool = True,
246
+ ) -> list[Location]:
247
+ """
248
+ Asynchronously retrieves a list of stops based on the provided parameters.
249
+
250
+ Args:
251
+ omc (str | None): Optional. The OMC (Operational Management Center) code.
252
+ place_id (str | None): Optional. ID of the place. Can be combined with `omc`.
253
+ omc_place_id (str | None): Optional. Combination of `omc` and `place_id`. OMC and ID of the place are separated by colon.
254
+ rtn (str | None): Optional. Only stops within the network given by parameter value.
255
+ sub_network (str | None): Optional. Only stops served by services from the network given by parameter value.
256
+ from_stop (str | None): Optional. Only stops with IDs within the intervall restricted by these parameters. Must be combined with `to_stop`.
257
+ to_stop (str | None): Optional. Only stops with IDs within the intervall restricted by these parameters. Must be combined with `from_stop`.
258
+ serving_lines (bool): Optional. Services of each stop. Default is True.
259
+ serving_lines_mot_type (bool): Optional. Mayor means of transport of each stop. The combination with `serving_lines_mot_types=True` is not possible. Default is True.
260
+ serving_lines_mot_types (bool): Optional. All means of transport of each stop. Separated by comma. The combination with `serving_lines_mot_type=True` is not possible. Default is False.
261
+ tarif_zones (bool): Optional. Tariff zone of each stop. Default is True.
262
+
263
+ Returns:
264
+ The parsed response from the command execution.
265
+ """
266
+ command = CommandStopList(self._format)
267
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
268
+
269
+ if omc:
270
+ command.add_param("stopListOMC", omc)
271
+ if place_id:
272
+ command.add_param("stopListPlaceId", place_id)
273
+ if omc_place_id:
274
+ command.add_param("stopListOMCPlaceId", omc_place_id)
275
+ if rtn:
276
+ command.add_param("rTN", rtn)
277
+ if sub_network:
278
+ command.add_param("stopListSubnetwork", sub_network)
279
+ if from_stop:
280
+ command.add_param("fromstop", from_stop)
281
+ if to_stop:
282
+ command.add_param("tostop", to_stop)
283
+
284
+ command.add_param("servingLines", serving_lines)
285
+ command.add_param("servingLinesMOTType", serving_lines_mot_type)
286
+ command.add_param("servingLinesMOTTypes", serving_lines_mot_types)
287
+ command.add_param("tariffZones", tarif_zones)
288
+
289
+ command.validate_params()
290
+
291
+ response = await self._run_query(self._build_url(command))
292
+
293
+ return command.parse(response)
294
+
295
+ async def trip(
296
+ self,
297
+ origin: Location | str,
298
+ destination: Location | str,
299
+ trip_departure: bool = True,
300
+ trip_datetime: str | datetime | date | None = None,
301
+ ) -> list[Jorney]:
302
+ """
303
+ Asynchronously calculates a trip between an origin and a destination.
304
+
305
+ Args:
306
+ origin (Location | str): The starting point of the trip, either as a Location object or a string identifier.
307
+ destination (Location | str): The endpoint of the trip, either as a Location object or a string identifier.
308
+ trip_departure (bool, optional): Indicates whether the trip is based on departure time (True) or arrival time (False). Defaults to True.
309
+ trip_datetime (str | datetime | date | None, optional): The date and time of the departure/arrival. Can be a string, datetime object, date object, or None. Defaults to None.
310
+
311
+ Returns:
312
+ list[Jorney]: A list of Jorney objects representing the calculated trip.
313
+
314
+ Raises:
315
+ ValueError: If the parameters are invalid or the trip cannot be calculated.
316
+ """
317
+ if isinstance(origin, Location):
318
+ origin = origin.id
319
+
320
+ if isinstance(destination, Location):
321
+ destination = destination.id
322
+
323
+ command = CommandTrip(self._format)
324
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
325
+ command.add_param("locationServerActive", True)
326
+ command.add_param("deleteAssignedStops_origin", True)
327
+ command.add_param("deleteAssignedStops_destination", True)
328
+ command.add_param("genC", False)
329
+ command.add_param("genP", False)
330
+ command.add_param("genMaps", False)
331
+ command.add_param("useRealtime", True)
332
+ command.add_param("useUT", True)
333
+ command.add_param("allInterchangesAsLegs", True)
334
+ command.add_param("calcOneDirection", True)
335
+ command.add_param("changeSpeed", "normal")
336
+ command.add_param("coordOutputDistance", True)
337
+
338
+ if trip_departure:
339
+ command.add_param("itdTripDateTimeDepArr", "dep")
340
+ else:
341
+ command.add_param("itdTripDateTimeDepArr", "arr")
342
+
343
+ if trip_datetime:
344
+ command.add_param_datetime(trip_datetime)
345
+
346
+ command.add_param("type_origin", "any")
347
+ command.add_param("name_origin", origin)
348
+ command.add_param("type_destination", "any")
349
+ command.add_param("name_destination", destination)
350
+
351
+ command.validate_params()
352
+
353
+ response = await self._run_query(self._build_url(command))
354
+
355
+ return command.parse(response)
122
356
 
123
357
  async def departures_by_location(
124
358
  self,
125
- stop: Location | str,
359
+ location: Location | str,
360
+ *,
126
361
  limit=40,
127
- date: str | None = None,
362
+ arg_date: str | datetime | date | None = None,
363
+ realtime: bool = True,
128
364
  ) -> list[Departure]:
129
- _LOGGER.info(f"Request departures for stop {stop}")
365
+ """
366
+ Fetches departures for a given location.
367
+
368
+ Args:
369
+ location (Location | str): The location object or location ID as a string.
370
+ limit (int, optional): The maximum number of departures to return. Defaults to 40.
371
+ arg_date (str | datetime | date | None, optional): The date for which to fetch departures. Can be a string, datetime, date, or None. Defaults to None.
372
+ realtime (bool, optional): Whether to use real-time data. Defaults to True.
373
+
374
+ Returns:
375
+ list[Departure]: A list of Departure objects.
376
+
377
+ Raises:
378
+ ValueError: If no location is provided.
379
+ """
380
+ _LOGGER.info(f"Request departures for location {location}")
130
381
  _LOGGER.debug(f"limit: {limit}")
131
- _LOGGER.debug(f"date: {date}")
382
+ _LOGGER.debug(f"date: {arg_date}")
383
+
384
+ if not location:
385
+ raise ValueError("No location provided")
132
386
 
133
- if isinstance(stop, Location):
134
- stop = stop.id
387
+ if isinstance(location, Location):
388
+ location = location.id
135
389
 
136
- command = CommandDepartures(stop)
390
+ command = CommandDepartures(self._format)
137
391
 
138
392
  # add parameters
139
- command.add_param("limit", limit)
140
- command.add_param_datetime(date)
393
+ command.add_param("locationServerActive", 1)
394
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
395
+ command.add_param("name_dm", location)
396
+ command.add_param("type_dm", "any")
397
+
398
+ if self._format == "rapidJSON":
399
+ command.add_param("mode", "direct")
400
+ command.add_param("useProxFootSearch", "0")
401
+ else:
402
+ command.add_param("mode", "any")
403
+
404
+ command.add_param("useAllStops", "1")
405
+ command.add_param("lsShowTrainsExplicit", "1")
406
+ command.add_param("useRealtime", realtime)
407
+
408
+ command.add_param_datetime(arg_date)
409
+
410
+ command.validate_params()
141
411
 
142
412
  response = await self._run_query(self._build_url(command))
143
413
 
144
- return command.parse(response)
414
+ return command.parse(response)[:limit]
145
415
 
146
- async def lines_by_name(self, line: str) -> list[Line]:
147
- """Search lines by name. e.g. subway `U3` or bus `65`
416
+ async def lines_by_name(
417
+ self,
418
+ line: str,
419
+ *,
420
+ merge_directions: bool = False,
421
+ show_trains_explicit: bool = False,
422
+ ) -> list[Line]:
423
+ """
424
+ Asynchronously fetches lines by name.
148
425
 
149
426
  Args:
150
- line (str): Line name to search
427
+ line (str): The name of the line to search for.
428
+ merge_directions (bool, optional): Whether to merge directions. Defaults to False.
429
+ show_trains_explicit (bool, optional): Whether to explicitly show trains. Defaults to False.
430
+
431
+ Raises:
432
+ ValueError: If no line is provided.
151
433
 
152
434
  Returns:
153
- list[Transport]: List of lines
435
+ list[Line]: A list of Line objects matching the search criteria.
154
436
  """
155
437
  _LOGGER.info("Request lines by name")
156
438
  _LOGGER.debug(f"line:{line}")
157
439
 
158
- command = CommandServingLines("line", line)
440
+ if not line:
441
+ raise ValueError("No line provided")
442
+
443
+ command = CommandServingLines(self._format)
444
+ command.add_param("mode", "line")
445
+ command.add_param("lineName", line)
446
+ command.add_param("locationServerActive", 1)
447
+ command.add_param("mergeDir", merge_directions)
448
+ command.add_param("lsShowTrainsExplicit", show_trains_explicit)
449
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
450
+
451
+ command.validate_params()
159
452
 
160
453
  response = await self._run_query(self._build_url(command))
161
454
 
162
455
  return command.parse(response)
163
456
 
164
457
  async def lines_by_location(
165
- self, location: str | Location, req_types: list[LineRequestType] = []
458
+ self,
459
+ location: str | Location,
460
+ *,
461
+ req_types: list[LineRequestType] = [],
462
+ merge_directions: bool = False,
463
+ show_trains_explicit: bool = False,
166
464
  ) -> list[Line]:
167
- """Search for lines that pass `location`. Location can be location ID like `de:08111:6221` or a `Location` object
465
+ """
466
+ Fetches lines by location.
168
467
 
169
468
  Args:
170
- location (str | Location): Location
171
- req_types (list[LineRequestType], optional): List of types for the request. Defaults to empty.
172
-
173
- Raises:
174
- ValueError: Wrong location type provided e.g. LocationType.POI or LocationType.ADDRESS
469
+ location (str | Location): The location identifier or Location object.
470
+ req_types (list[LineRequestType], optional): List of request types for lines. Defaults to [].
471
+ merge_directions (bool, optional): Whether to merge directions. Defaults to False.
472
+ show_trains_explicit (bool, optional): Whether to explicitly show trains. Defaults to False.
175
473
 
176
474
  Returns:
177
- list[Transport]: List of lines
475
+ list[Line]: A list of Line objects.
476
+
477
+ Raises:
478
+ ValueError: If the location is a Location object and its type is not STOP.
178
479
  """
179
480
  _LOGGER.info("Request lines by location")
180
481
  _LOGGER.debug(f"location:{location}")
181
- _LOGGER.debug(f"filters :{req_types}")
482
+ _LOGGER.debug(f"req_types :{req_types}")
483
+
484
+ if not location:
485
+ raise ValueError("No location provided")
182
486
 
183
487
  if isinstance(location, Location):
184
488
  if location.loc_type != LocationType.STOP:
@@ -187,22 +491,209 @@ class EfaClient:
187
491
  )
188
492
  location = location.id
189
493
 
190
- command = CommandServingLines("odv", location)
494
+ command = CommandServingLines(self._format)
495
+ command.add_param("mode", "odv")
496
+ command.add_param("locationServerActive", 1)
497
+ command.add_param("type_sl", "stopID")
498
+ command.add_param("name_sl", location)
499
+ command.add_param("mergeDir", merge_directions)
500
+ command.add_param("lsShowTrainsExplicit", show_trains_explicit)
501
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
191
502
 
192
503
  if req_types:
193
504
  command.add_param("lineReqType", sum(req_types))
194
505
 
506
+ command.validate_params()
507
+
508
+ response = await self._run_query(self._build_url(command))
509
+
510
+ return command.parse(response)
511
+
512
+ async def line_stops(
513
+ self, line_name: str, additional_info: bool = False
514
+ ) -> list[Location]:
515
+ """
516
+ Retrieve the stops for a given line.
517
+
518
+ Args:
519
+ line_name (str): The name of the line for which to retrieve stops.
520
+ additional_info (bool, optional): Whether to include additional stop information. Defaults to False.
521
+
522
+ Returns:
523
+ list[Location]: A list of Location objects representing the stops for the specified line.
524
+ """
525
+ _LOGGER.info("Request lise stops")
526
+ _LOGGER.debug(f"line_name: {line_name}")
527
+
528
+ if not line_name:
529
+ raise ValueError("No line name provided")
530
+
531
+ command = CommandLineStop(self._format)
532
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
533
+ command.add_param("line", line_name)
534
+ command.add_param("allStopInfo", additional_info)
535
+
536
+ command.validate_params()
537
+
538
+ response = await self._run_query(self._build_url(command))
539
+
540
+ return command.parse(response)
541
+
542
+ async def coord_bounding_box(
543
+ self,
544
+ left_upper: tuple[float, float],
545
+ right_lower: tuple[float, float],
546
+ filters: list[PointTypeFilter],
547
+ limit: int = 10,
548
+ ) -> list[Location]:
549
+ """
550
+ Asynchronously request object coordinates within a bounding box.
551
+
552
+ Args:
553
+ left_upper (tuple[float, float]): The coordinates(WGS84 format) of the left upper corner of the bounding box (latitude, longitude).
554
+ right_lower (tuple[float, float]): The coordinates(WGS84 format) of the right lower corner of the bounding box (latitude, longitude).
555
+ filters (list[PointTypeFilter]): A list of filters to apply to the points within the bounding box.
556
+ limit (int, optional): The maximum number of locations to return. Defaults to 10.
557
+
558
+ Returns:
559
+ list[Location]: A list of Location objects that fall within the specified bounding box and match the given filters.
560
+ """
561
+ _LOGGER.info("Request object(s) coordinates by bounding box")
562
+ _LOGGER.debug(f"left_upper: {left_upper}")
563
+ _LOGGER.debug(f"right_lower: {right_lower}")
564
+ _LOGGER.debug(f"filters: {filters}")
565
+
566
+ command = CommandCoord(self._format)
567
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
568
+ command.add_param("boundingBox", True)
569
+ command.add_param(
570
+ "boundingBoxLU",
571
+ f"{left_upper[0]}:{left_upper[1]}:{CoordFormat.WGS84.value}",
572
+ )
573
+ command.add_param(
574
+ "boundingBoxRL",
575
+ f"{right_lower[0]}:{right_lower[1]}:{CoordFormat.WGS84.value}",
576
+ )
577
+ command.add_param("inclFilter", True)
578
+ command.add_param("max", limit)
579
+
580
+ for index, f in enumerate(filters):
581
+ command.add_param(f"type_{index + 1}", f.value)
582
+
583
+ command.validate_params()
584
+
585
+ response = await self._run_query(self._build_url(command))
586
+
587
+ return command.parse(response)
588
+
589
+ async def coord_radial(
590
+ self,
591
+ coord: tuple[float, float],
592
+ filters: list[PointTypeFilter],
593
+ radius: list[int],
594
+ limit: int = 10,
595
+ ) -> list[Location]:
596
+ """
597
+ Asynchronously request object coordinates by radius.
598
+
599
+ Args:
600
+ coord (tuple[float, float]): A tuple containing the latitude and longitude of the coordinate(WGS84 format).
601
+ filters (list[PointTypeFilter]): A list of PointTypeFilter objects to filter the results.
602
+ radius (list[int]): A list of radii corresponding to each filter.
603
+ limit (int, optional): The maximum number of locations to return. Defaults to 10.
604
+
605
+ Returns:
606
+ list[Location]: A list of Location objects that match the given filters and radius.
607
+
608
+ Raises:
609
+ ValueError: If the length of radius and filters do not match.
610
+ """
611
+ _LOGGER.info("Request object(s) coordinates by radius")
612
+ _LOGGER.debug(f"coord: {coord}")
613
+ _LOGGER.debug(f"filters: {filters}")
614
+ _LOGGER.debug(f"radius: {radius}")
615
+
616
+ if len(radius) != len(filters):
617
+ raise ValueError("Radius and filters must have the same length")
618
+
619
+ command = CommandCoord(self._format)
620
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
621
+ command.add_param("inclFilter", True)
622
+ command.add_param("coord", f"{coord[0]}:{coord[1]}:{CoordFormat.WGS84.value}")
623
+ command.add_param("max", limit)
624
+
625
+ for index, f in enumerate(filters):
626
+ command.add_param(f"type_{index + 1}", f.value)
627
+ command.add_param(f"radius_{index + 1}", radius[index])
628
+
629
+ command.validate_params()
630
+
195
631
  response = await self._run_query(self._build_url(command))
196
632
 
197
633
  return command.parse(response)
198
634
 
199
- async def locations_by_line(self, line: str | Line) -> list[Location]:
200
- raise NotImplementedError
635
+ async def geo_object(
636
+ self,
637
+ line: str,
638
+ filter_date: date | str | None = None,
639
+ left_upper: tuple[float, float] | None = None,
640
+ right_lower: tuple[float, float] | None = None,
641
+ ) -> list[Line]:
642
+ """
643
+ Asynchronously generate a sequence of coordinates and all passed stops of a provided line.
644
+
645
+ Args:
646
+ line (str): The line parameter to be used in the query.
647
+ filter_date (date | str | None, optional): The date to filter results by. Can be a date object or a string in the format 'YYYYMMDD'. Defaults to None.
648
+ left_upper (tuple[float, float] | None, optional): The left upper coordinate(WGS84 format) of the bounding box. Defaults to None.
649
+ right_lower (tuple[float, float] | None, optional): The right lower coordinate(WGS84 format) of the bounding box. Defaults to None.
650
+
651
+ Returns:
652
+ list[Line]: A list of Line objects that match the query parameters.
653
+
654
+ Raises:
655
+ ValueError: If the filter_date is not a valid date object or string in the format 'YYYYMMDD'.
656
+ """
657
+ command = CommandGeoObject(self._format)
658
+ command.add_param("coordOutputFormat", CoordFormat.WGS84.value)
659
+ command.add_param("line", line)
660
+
661
+ if left_upper and right_lower:
662
+ command.add_param("boundingBox", True)
663
+
664
+ command.add_param(
665
+ "boundingBoxLU",
666
+ f"{left_upper[0]}:{left_upper[1]}:{CoordFormat.WGS84.value}",
667
+ )
668
+ command.add_param(
669
+ "boundingBoxRL",
670
+ f"{right_lower[0]}:{right_lower[1]}:{CoordFormat.WGS84.value}",
671
+ )
672
+
673
+ if filter_date:
674
+ if isinstance(filter_date, date):
675
+ filter_date = filter_date.strftime("%Y%m%d")
676
+ elif is_date(filter_date):
677
+ filter_date = filter_date
678
+ else:
679
+ raise ValueError(
680
+ "Invalid date format. Expected a date object or string in the format 'YYYYMMDD'"
681
+ )
682
+
683
+ command.add_param("filterDate", filter_date)
684
+
685
+ command.validate_params()
686
+
687
+ response = await self._run_query(self._build_url(command))
688
+
689
+ return command.parse(response)
201
690
 
202
691
  async def _run_query(self, query: str) -> str:
203
692
  _LOGGER.info(f"Run query {query}")
204
693
 
205
- async with self._client_session.get(query, ssl=False) as response:
694
+ async with self._client_session.get(
695
+ query, ssl=False, timeout=QUERY_TIMEOUT
696
+ ) as response:
206
697
  _LOGGER.debug(f"Response status: {response.status}")
207
698
 
208
699
  if response.status == 200: