meteo-lt-pkg 0.5.0b0__tar.gz → 0.5.2__tar.gz
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.
- {meteo_lt_pkg-0.5.0b0/meteo_lt_pkg.egg-info → meteo_lt_pkg-0.5.2}/PKG-INFO +228 -10
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/README.md +227 -9
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/__init__.py +2 -2
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/api.py +27 -13
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/client.py +13 -38
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/const.py +2 -1
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/models.py +11 -10
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/utils.py +3 -4
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt/warnings.py +46 -25
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2/meteo_lt_pkg.egg-info}/PKG-INFO +228 -10
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/pyproject.toml +1 -1
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/LICENSE +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/MANIFEST.in +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt_pkg.egg-info/SOURCES.txt +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt_pkg.egg-info/dependency_links.txt +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt_pkg.egg-info/requires.txt +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/meteo_lt_pkg.egg-info/top_level.txt +0 -0
- {meteo_lt_pkg-0.5.0b0 → meteo_lt_pkg-0.5.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meteo_lt-pkg
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A library to fetch weather data from api.meteo.lt
|
|
5
5
|
Author-email: Brunas <brunonas@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Brunas/meteo_lt-pkg
|
|
@@ -228,6 +228,66 @@ asyncio.run(fetch_warnings())
|
|
|
228
228
|
asyncio.run(fetch_warnings_for_area())
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
+
### Fetching Hydrological Warnings
|
|
232
|
+
|
|
233
|
+
To get hydrological warnings (water-related warnings like floods, high water levels):
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
async def fetch_hydro_warnings():
|
|
237
|
+
async with MeteoLtAPI() as api:
|
|
238
|
+
# Get all hydrological warnings
|
|
239
|
+
hydro_warnings = await api.get_hydro_warnings()
|
|
240
|
+
print(f"Total active hydro warnings: {len(hydro_warnings)}")
|
|
241
|
+
|
|
242
|
+
for warning in hydro_warnings:
|
|
243
|
+
print(f"Hydro Warning: {warning.warning_type} in {warning.county}")
|
|
244
|
+
print(f"Severity: {warning.severity}")
|
|
245
|
+
print(f"Description: {warning.description}")
|
|
246
|
+
print("-" * 50)
|
|
247
|
+
|
|
248
|
+
async def fetch_hydro_warnings_for_area():
|
|
249
|
+
async with MeteoLtAPI() as api:
|
|
250
|
+
# Get hydro warnings for specific administrative division
|
|
251
|
+
warnings = await api.get_hydro_warnings("Vilniaus miesto")
|
|
252
|
+
print(f"Hydro warnings for Vilnius: {len(warnings)}")
|
|
253
|
+
|
|
254
|
+
asyncio.run(fetch_hydro_warnings())
|
|
255
|
+
asyncio.run(fetch_hydro_warnings_for_area())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Fetching All Warnings (Weather + Hydrological)
|
|
259
|
+
|
|
260
|
+
To get both weather and hydrological warnings combined:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
async def fetch_all_warnings():
|
|
264
|
+
async with MeteoLtAPI() as api:
|
|
265
|
+
# Get all warnings (weather + hydrological)
|
|
266
|
+
all_warnings = await api.get_all_warnings()
|
|
267
|
+
print(f"Total active warnings (all types): {len(all_warnings)}")
|
|
268
|
+
|
|
269
|
+
for warning in all_warnings:
|
|
270
|
+
print(f"{warning.warning_type} in {warning.county}")
|
|
271
|
+
print(f"Severity: {warning.severity}")
|
|
272
|
+
print(f"Description: {warning.description}")
|
|
273
|
+
print("-" * 50)
|
|
274
|
+
|
|
275
|
+
async def fetch_all_warnings_for_area():
|
|
276
|
+
async with MeteoLtAPI() as api:
|
|
277
|
+
# Get all warnings for specific administrative division
|
|
278
|
+
warnings = await api.get_all_warnings("Vilniaus miesto")
|
|
279
|
+
print(f"All warnings for Vilnius: {len(warnings)}")
|
|
280
|
+
|
|
281
|
+
# Separate by type if needed
|
|
282
|
+
weather_count = sum(1 for w in warnings if w.category == "weather")
|
|
283
|
+
hydro_count = sum(1 for w in warnings if w.category == "hydro")
|
|
284
|
+
print(f" Weather warnings: {weather_count}")
|
|
285
|
+
print(f" Hydro warnings: {hydro_count}")
|
|
286
|
+
|
|
287
|
+
asyncio.run(fetch_all_warnings())
|
|
288
|
+
asyncio.run(fetch_all_warnings_for_area())
|
|
289
|
+
```
|
|
290
|
+
|
|
231
291
|
## Data Models
|
|
232
292
|
|
|
233
293
|
The package includes several data models to represent the API responses:
|
|
@@ -250,8 +310,15 @@ Represents a place with associated metadata.
|
|
|
250
310
|
```python
|
|
251
311
|
from meteo_lt import Place
|
|
252
312
|
|
|
253
|
-
place = Place(
|
|
313
|
+
place = Place(
|
|
314
|
+
code="vilnius",
|
|
315
|
+
name="Vilnius",
|
|
316
|
+
administrative_division="Vilniaus miesto",
|
|
317
|
+
country_code="LT",
|
|
318
|
+
coordinates=coords
|
|
319
|
+
)
|
|
254
320
|
print(place.latitude, place.longitude)
|
|
321
|
+
print(place.counties) # List of counties this place belongs to
|
|
255
322
|
```
|
|
256
323
|
|
|
257
324
|
### ForecastTimestamp
|
|
@@ -274,7 +341,10 @@ forecast_timestamp = ForecastTimestamp(
|
|
|
274
341
|
humidity=60,
|
|
275
342
|
precipitation=0
|
|
276
343
|
)
|
|
277
|
-
print(forecast_timestamp.
|
|
344
|
+
print(f"Temperature: {forecast_timestamp.temperature}°C")
|
|
345
|
+
print(f"Condition: {forecast_timestamp.condition_code}")
|
|
346
|
+
# Warnings list is automatically populated when using get_forecast_with_warnings()
|
|
347
|
+
print(f"Warnings: {len(forecast_timestamp.warnings)}")
|
|
278
348
|
```
|
|
279
349
|
|
|
280
350
|
### Forecast
|
|
@@ -286,28 +356,176 @@ from meteo_lt import Forecast
|
|
|
286
356
|
|
|
287
357
|
forecast = Forecast(
|
|
288
358
|
place=place,
|
|
289
|
-
forecast_created=
|
|
359
|
+
forecast_created="2024-07-23T12:00:00+00:00",
|
|
360
|
+
current_conditions=forecast_timestamp,
|
|
290
361
|
forecast_timestamps=[forecast_timestamp]
|
|
291
362
|
)
|
|
292
|
-
|
|
363
|
+
# current_conditions is automatically set to the current hour's forecast
|
|
364
|
+
print(f"Current temperature: {forecast.current_conditions.temperature}°C")
|
|
365
|
+
# forecast_timestamps are automatically filtered to exclude past hours
|
|
366
|
+
print(f"Future forecasts: {len(forecast.forecast_timestamps)}")
|
|
293
367
|
```
|
|
294
368
|
|
|
295
|
-
###
|
|
369
|
+
### MeteoWarning
|
|
296
370
|
|
|
297
|
-
Represents a
|
|
371
|
+
Represents a meteorological warning (weather or hydrological) for a specific area.
|
|
298
372
|
|
|
299
373
|
```python
|
|
300
|
-
from meteo_lt import
|
|
374
|
+
from meteo_lt import MeteoWarning
|
|
301
375
|
|
|
302
|
-
warning
|
|
376
|
+
# Weather warning example
|
|
377
|
+
weather_warning = MeteoWarning(
|
|
303
378
|
county="Vilniaus apskritis",
|
|
304
379
|
warning_type="frost",
|
|
305
380
|
severity="Moderate",
|
|
306
381
|
description="Ground surface frost 0-5 degrees in many places",
|
|
382
|
+
category="weather",
|
|
307
383
|
start_time="2024-07-23T12:00:00Z",
|
|
308
384
|
end_time="2024-07-23T18:00:00Z"
|
|
309
385
|
)
|
|
310
|
-
print(f"Warning for {
|
|
386
|
+
print(f"Warning for {weather_warning.county}: {weather_warning.description}")
|
|
387
|
+
print(f"Category: {weather_warning.category}") # "weather" or "hydro"
|
|
388
|
+
|
|
389
|
+
# Hydrological warning example
|
|
390
|
+
hydro_warning = MeteoWarning(
|
|
391
|
+
county="Kauno apskritis",
|
|
392
|
+
warning_type="flood",
|
|
393
|
+
severity="High",
|
|
394
|
+
description="High water levels expected",
|
|
395
|
+
category="hydro",
|
|
396
|
+
start_time="2024-07-23T12:00:00Z",
|
|
397
|
+
end_time="2024-07-24T12:00:00Z"
|
|
398
|
+
)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### HydroStation
|
|
402
|
+
|
|
403
|
+
Represents a hydrological observation station with water body information.
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
from meteo_lt import HydroStation
|
|
407
|
+
|
|
408
|
+
station = HydroStation(
|
|
409
|
+
code="klaipedos-juru-uosto-vms",
|
|
410
|
+
name="Klaipėdos jūrų uosto VMS",
|
|
411
|
+
water_body="Baltijos jūra",
|
|
412
|
+
coordinates=coords
|
|
413
|
+
)
|
|
414
|
+
print(f"Station: {station.name} on {station.water_body}")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### HydroObservation
|
|
418
|
+
|
|
419
|
+
Represents a single hydrological observation with water measurements.
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
from meteo_lt import HydroObservation
|
|
423
|
+
|
|
424
|
+
observation = HydroObservation(
|
|
425
|
+
observation_datetime="2024-07-23T12:00:00+00:00",
|
|
426
|
+
water_level=481.8,
|
|
427
|
+
water_temperature=15.5,
|
|
428
|
+
water_discharge=100.0
|
|
429
|
+
)
|
|
430
|
+
print(f"Water level: {observation.water_level} cm")
|
|
431
|
+
print(f"Water temperature: {observation.water_temperature}°C")
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### HydroObservationData
|
|
435
|
+
|
|
436
|
+
Represents a collection of hydrological observations for a specific station.
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
from meteo_lt import HydroObservationData
|
|
440
|
+
|
|
441
|
+
data = HydroObservationData(
|
|
442
|
+
station=station,
|
|
443
|
+
observations=[observation],
|
|
444
|
+
observations_data_range={"from": "2024-07-01", "to": "2024-07-23"}
|
|
445
|
+
)
|
|
446
|
+
print(f"Station: {data.station.name}")
|
|
447
|
+
print(f"Observations: {len(data.observations)}")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Hydrological Data
|
|
451
|
+
|
|
452
|
+
The package provides access to hydrological data from Lithuanian water monitoring stations.
|
|
453
|
+
|
|
454
|
+
### Fetching Hydrological Stations
|
|
455
|
+
|
|
456
|
+
To get the list of all hydrological stations:
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
async def fetch_hydro_stations():
|
|
460
|
+
async with MeteoLtAPI() as api:
|
|
461
|
+
stations = await api.fetch_hydro_stations()
|
|
462
|
+
for station in stations:
|
|
463
|
+
print(f"{station.name} ({station.code}) - Water body: {station.water_body}")
|
|
464
|
+
|
|
465
|
+
asyncio.run(fetch_hydro_stations())
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Finding the Nearest Hydrological Station
|
|
469
|
+
|
|
470
|
+
You can find the nearest hydrological station using coordinates:
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
async def find_nearest_hydro_station():
|
|
474
|
+
async with MeteoLtAPI() as api:
|
|
475
|
+
# Example coordinates for Klaipėda, Lithuania
|
|
476
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
477
|
+
print(f"Nearest station: {nearest_station.name}")
|
|
478
|
+
print(f"Water body: {nearest_station.water_body}")
|
|
479
|
+
print(f"Distance: approximately {nearest_station.latitude}, {nearest_station.longitude}")
|
|
480
|
+
|
|
481
|
+
asyncio.run(find_nearest_hydro_station())
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Fetching Hydrological Observations
|
|
485
|
+
|
|
486
|
+
To get water level and temperature observations for a specific station:
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
async def fetch_hydro_observations():
|
|
490
|
+
async with MeteoLtAPI() as api:
|
|
491
|
+
# Get observations for a station
|
|
492
|
+
hydro_data = await api.get_hydro_observation_data("klaipedos-juru-uosto-vms")
|
|
493
|
+
|
|
494
|
+
print(f"Station: {hydro_data.station.name}")
|
|
495
|
+
print(f"Water body: {hydro_data.station.water_body}")
|
|
496
|
+
print(f"\nRecent observations:")
|
|
497
|
+
|
|
498
|
+
for observation in hydro_data.observations[:5]:
|
|
499
|
+
print(f" {observation.observation_datetime}")
|
|
500
|
+
print(f" Water level: {observation.water_level} cm")
|
|
501
|
+
print(f" Water temperature: {observation.water_temperature}°C")
|
|
502
|
+
if observation.water_discharge:
|
|
503
|
+
print(f" Water discharge: {observation.water_discharge} m³/s")
|
|
504
|
+
|
|
505
|
+
asyncio.run(fetch_hydro_observations())
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Getting Observations for Nearest Station
|
|
509
|
+
|
|
510
|
+
Combine location finding with observation fetching:
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
async def get_nearest_station_observations():
|
|
514
|
+
async with MeteoLtAPI() as api:
|
|
515
|
+
# Find nearest hydro station to coordinates
|
|
516
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
517
|
+
|
|
518
|
+
# Get observations for the nearest station
|
|
519
|
+
hydro_data = await api.get_hydro_observation_data(nearest_station.code)
|
|
520
|
+
|
|
521
|
+
print(f"Latest observations from {hydro_data.station.name}:")
|
|
522
|
+
if hydro_data.observations:
|
|
523
|
+
latest = hydro_data.observations[0]
|
|
524
|
+
print(f" Time: {latest.observation_datetime}")
|
|
525
|
+
print(f" Water level: {latest.water_level} cm")
|
|
526
|
+
print(f" Temperature: {latest.water_temperature}°C")
|
|
527
|
+
|
|
528
|
+
asyncio.run(get_nearest_station_observations())
|
|
311
529
|
```
|
|
312
530
|
|
|
313
531
|
## Contributing
|
|
@@ -201,6 +201,66 @@ asyncio.run(fetch_warnings())
|
|
|
201
201
|
asyncio.run(fetch_warnings_for_area())
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
+
### Fetching Hydrological Warnings
|
|
205
|
+
|
|
206
|
+
To get hydrological warnings (water-related warnings like floods, high water levels):
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
async def fetch_hydro_warnings():
|
|
210
|
+
async with MeteoLtAPI() as api:
|
|
211
|
+
# Get all hydrological warnings
|
|
212
|
+
hydro_warnings = await api.get_hydro_warnings()
|
|
213
|
+
print(f"Total active hydro warnings: {len(hydro_warnings)}")
|
|
214
|
+
|
|
215
|
+
for warning in hydro_warnings:
|
|
216
|
+
print(f"Hydro Warning: {warning.warning_type} in {warning.county}")
|
|
217
|
+
print(f"Severity: {warning.severity}")
|
|
218
|
+
print(f"Description: {warning.description}")
|
|
219
|
+
print("-" * 50)
|
|
220
|
+
|
|
221
|
+
async def fetch_hydro_warnings_for_area():
|
|
222
|
+
async with MeteoLtAPI() as api:
|
|
223
|
+
# Get hydro warnings for specific administrative division
|
|
224
|
+
warnings = await api.get_hydro_warnings("Vilniaus miesto")
|
|
225
|
+
print(f"Hydro warnings for Vilnius: {len(warnings)}")
|
|
226
|
+
|
|
227
|
+
asyncio.run(fetch_hydro_warnings())
|
|
228
|
+
asyncio.run(fetch_hydro_warnings_for_area())
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Fetching All Warnings (Weather + Hydrological)
|
|
232
|
+
|
|
233
|
+
To get both weather and hydrological warnings combined:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
async def fetch_all_warnings():
|
|
237
|
+
async with MeteoLtAPI() as api:
|
|
238
|
+
# Get all warnings (weather + hydrological)
|
|
239
|
+
all_warnings = await api.get_all_warnings()
|
|
240
|
+
print(f"Total active warnings (all types): {len(all_warnings)}")
|
|
241
|
+
|
|
242
|
+
for warning in all_warnings:
|
|
243
|
+
print(f"{warning.warning_type} in {warning.county}")
|
|
244
|
+
print(f"Severity: {warning.severity}")
|
|
245
|
+
print(f"Description: {warning.description}")
|
|
246
|
+
print("-" * 50)
|
|
247
|
+
|
|
248
|
+
async def fetch_all_warnings_for_area():
|
|
249
|
+
async with MeteoLtAPI() as api:
|
|
250
|
+
# Get all warnings for specific administrative division
|
|
251
|
+
warnings = await api.get_all_warnings("Vilniaus miesto")
|
|
252
|
+
print(f"All warnings for Vilnius: {len(warnings)}")
|
|
253
|
+
|
|
254
|
+
# Separate by type if needed
|
|
255
|
+
weather_count = sum(1 for w in warnings if w.category == "weather")
|
|
256
|
+
hydro_count = sum(1 for w in warnings if w.category == "hydro")
|
|
257
|
+
print(f" Weather warnings: {weather_count}")
|
|
258
|
+
print(f" Hydro warnings: {hydro_count}")
|
|
259
|
+
|
|
260
|
+
asyncio.run(fetch_all_warnings())
|
|
261
|
+
asyncio.run(fetch_all_warnings_for_area())
|
|
262
|
+
```
|
|
263
|
+
|
|
204
264
|
## Data Models
|
|
205
265
|
|
|
206
266
|
The package includes several data models to represent the API responses:
|
|
@@ -223,8 +283,15 @@ Represents a place with associated metadata.
|
|
|
223
283
|
```python
|
|
224
284
|
from meteo_lt import Place
|
|
225
285
|
|
|
226
|
-
place = Place(
|
|
286
|
+
place = Place(
|
|
287
|
+
code="vilnius",
|
|
288
|
+
name="Vilnius",
|
|
289
|
+
administrative_division="Vilniaus miesto",
|
|
290
|
+
country_code="LT",
|
|
291
|
+
coordinates=coords
|
|
292
|
+
)
|
|
227
293
|
print(place.latitude, place.longitude)
|
|
294
|
+
print(place.counties) # List of counties this place belongs to
|
|
228
295
|
```
|
|
229
296
|
|
|
230
297
|
### ForecastTimestamp
|
|
@@ -247,7 +314,10 @@ forecast_timestamp = ForecastTimestamp(
|
|
|
247
314
|
humidity=60,
|
|
248
315
|
precipitation=0
|
|
249
316
|
)
|
|
250
|
-
print(forecast_timestamp.
|
|
317
|
+
print(f"Temperature: {forecast_timestamp.temperature}°C")
|
|
318
|
+
print(f"Condition: {forecast_timestamp.condition_code}")
|
|
319
|
+
# Warnings list is automatically populated when using get_forecast_with_warnings()
|
|
320
|
+
print(f"Warnings: {len(forecast_timestamp.warnings)}")
|
|
251
321
|
```
|
|
252
322
|
|
|
253
323
|
### Forecast
|
|
@@ -259,28 +329,176 @@ from meteo_lt import Forecast
|
|
|
259
329
|
|
|
260
330
|
forecast = Forecast(
|
|
261
331
|
place=place,
|
|
262
|
-
forecast_created=
|
|
332
|
+
forecast_created="2024-07-23T12:00:00+00:00",
|
|
333
|
+
current_conditions=forecast_timestamp,
|
|
263
334
|
forecast_timestamps=[forecast_timestamp]
|
|
264
335
|
)
|
|
265
|
-
|
|
336
|
+
# current_conditions is automatically set to the current hour's forecast
|
|
337
|
+
print(f"Current temperature: {forecast.current_conditions.temperature}°C")
|
|
338
|
+
# forecast_timestamps are automatically filtered to exclude past hours
|
|
339
|
+
print(f"Future forecasts: {len(forecast.forecast_timestamps)}")
|
|
266
340
|
```
|
|
267
341
|
|
|
268
|
-
###
|
|
342
|
+
### MeteoWarning
|
|
269
343
|
|
|
270
|
-
Represents a
|
|
344
|
+
Represents a meteorological warning (weather or hydrological) for a specific area.
|
|
271
345
|
|
|
272
346
|
```python
|
|
273
|
-
from meteo_lt import
|
|
347
|
+
from meteo_lt import MeteoWarning
|
|
274
348
|
|
|
275
|
-
warning
|
|
349
|
+
# Weather warning example
|
|
350
|
+
weather_warning = MeteoWarning(
|
|
276
351
|
county="Vilniaus apskritis",
|
|
277
352
|
warning_type="frost",
|
|
278
353
|
severity="Moderate",
|
|
279
354
|
description="Ground surface frost 0-5 degrees in many places",
|
|
355
|
+
category="weather",
|
|
280
356
|
start_time="2024-07-23T12:00:00Z",
|
|
281
357
|
end_time="2024-07-23T18:00:00Z"
|
|
282
358
|
)
|
|
283
|
-
print(f"Warning for {
|
|
359
|
+
print(f"Warning for {weather_warning.county}: {weather_warning.description}")
|
|
360
|
+
print(f"Category: {weather_warning.category}") # "weather" or "hydro"
|
|
361
|
+
|
|
362
|
+
# Hydrological warning example
|
|
363
|
+
hydro_warning = MeteoWarning(
|
|
364
|
+
county="Kauno apskritis",
|
|
365
|
+
warning_type="flood",
|
|
366
|
+
severity="High",
|
|
367
|
+
description="High water levels expected",
|
|
368
|
+
category="hydro",
|
|
369
|
+
start_time="2024-07-23T12:00:00Z",
|
|
370
|
+
end_time="2024-07-24T12:00:00Z"
|
|
371
|
+
)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### HydroStation
|
|
375
|
+
|
|
376
|
+
Represents a hydrological observation station with water body information.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from meteo_lt import HydroStation
|
|
380
|
+
|
|
381
|
+
station = HydroStation(
|
|
382
|
+
code="klaipedos-juru-uosto-vms",
|
|
383
|
+
name="Klaipėdos jūrų uosto VMS",
|
|
384
|
+
water_body="Baltijos jūra",
|
|
385
|
+
coordinates=coords
|
|
386
|
+
)
|
|
387
|
+
print(f"Station: {station.name} on {station.water_body}")
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### HydroObservation
|
|
391
|
+
|
|
392
|
+
Represents a single hydrological observation with water measurements.
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
from meteo_lt import HydroObservation
|
|
396
|
+
|
|
397
|
+
observation = HydroObservation(
|
|
398
|
+
observation_datetime="2024-07-23T12:00:00+00:00",
|
|
399
|
+
water_level=481.8,
|
|
400
|
+
water_temperature=15.5,
|
|
401
|
+
water_discharge=100.0
|
|
402
|
+
)
|
|
403
|
+
print(f"Water level: {observation.water_level} cm")
|
|
404
|
+
print(f"Water temperature: {observation.water_temperature}°C")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### HydroObservationData
|
|
408
|
+
|
|
409
|
+
Represents a collection of hydrological observations for a specific station.
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
from meteo_lt import HydroObservationData
|
|
413
|
+
|
|
414
|
+
data = HydroObservationData(
|
|
415
|
+
station=station,
|
|
416
|
+
observations=[observation],
|
|
417
|
+
observations_data_range={"from": "2024-07-01", "to": "2024-07-23"}
|
|
418
|
+
)
|
|
419
|
+
print(f"Station: {data.station.name}")
|
|
420
|
+
print(f"Observations: {len(data.observations)}")
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Hydrological Data
|
|
424
|
+
|
|
425
|
+
The package provides access to hydrological data from Lithuanian water monitoring stations.
|
|
426
|
+
|
|
427
|
+
### Fetching Hydrological Stations
|
|
428
|
+
|
|
429
|
+
To get the list of all hydrological stations:
|
|
430
|
+
|
|
431
|
+
```python
|
|
432
|
+
async def fetch_hydro_stations():
|
|
433
|
+
async with MeteoLtAPI() as api:
|
|
434
|
+
stations = await api.fetch_hydro_stations()
|
|
435
|
+
for station in stations:
|
|
436
|
+
print(f"{station.name} ({station.code}) - Water body: {station.water_body}")
|
|
437
|
+
|
|
438
|
+
asyncio.run(fetch_hydro_stations())
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Finding the Nearest Hydrological Station
|
|
442
|
+
|
|
443
|
+
You can find the nearest hydrological station using coordinates:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
async def find_nearest_hydro_station():
|
|
447
|
+
async with MeteoLtAPI() as api:
|
|
448
|
+
# Example coordinates for Klaipėda, Lithuania
|
|
449
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
450
|
+
print(f"Nearest station: {nearest_station.name}")
|
|
451
|
+
print(f"Water body: {nearest_station.water_body}")
|
|
452
|
+
print(f"Distance: approximately {nearest_station.latitude}, {nearest_station.longitude}")
|
|
453
|
+
|
|
454
|
+
asyncio.run(find_nearest_hydro_station())
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Fetching Hydrological Observations
|
|
458
|
+
|
|
459
|
+
To get water level and temperature observations for a specific station:
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
async def fetch_hydro_observations():
|
|
463
|
+
async with MeteoLtAPI() as api:
|
|
464
|
+
# Get observations for a station
|
|
465
|
+
hydro_data = await api.get_hydro_observation_data("klaipedos-juru-uosto-vms")
|
|
466
|
+
|
|
467
|
+
print(f"Station: {hydro_data.station.name}")
|
|
468
|
+
print(f"Water body: {hydro_data.station.water_body}")
|
|
469
|
+
print(f"\nRecent observations:")
|
|
470
|
+
|
|
471
|
+
for observation in hydro_data.observations[:5]:
|
|
472
|
+
print(f" {observation.observation_datetime}")
|
|
473
|
+
print(f" Water level: {observation.water_level} cm")
|
|
474
|
+
print(f" Water temperature: {observation.water_temperature}°C")
|
|
475
|
+
if observation.water_discharge:
|
|
476
|
+
print(f" Water discharge: {observation.water_discharge} m³/s")
|
|
477
|
+
|
|
478
|
+
asyncio.run(fetch_hydro_observations())
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Getting Observations for Nearest Station
|
|
482
|
+
|
|
483
|
+
Combine location finding with observation fetching:
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
async def get_nearest_station_observations():
|
|
487
|
+
async with MeteoLtAPI() as api:
|
|
488
|
+
# Find nearest hydro station to coordinates
|
|
489
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
490
|
+
|
|
491
|
+
# Get observations for the nearest station
|
|
492
|
+
hydro_data = await api.get_hydro_observation_data(nearest_station.code)
|
|
493
|
+
|
|
494
|
+
print(f"Latest observations from {hydro_data.station.name}:")
|
|
495
|
+
if hydro_data.observations:
|
|
496
|
+
latest = hydro_data.observations[0]
|
|
497
|
+
print(f" Time: {latest.observation_datetime}")
|
|
498
|
+
print(f" Water level: {latest.water_level} cm")
|
|
499
|
+
print(f" Temperature: {latest.water_temperature}°C")
|
|
500
|
+
|
|
501
|
+
asyncio.run(get_nearest_station_observations())
|
|
284
502
|
```
|
|
285
503
|
|
|
286
504
|
## Contributing
|
|
@@ -7,7 +7,7 @@ from .models import (
|
|
|
7
7
|
Place,
|
|
8
8
|
ForecastTimestamp,
|
|
9
9
|
Forecast,
|
|
10
|
-
|
|
10
|
+
MeteoWarning,
|
|
11
11
|
HydroStation,
|
|
12
12
|
HydroObservation,
|
|
13
13
|
HydroObservationData,
|
|
@@ -20,7 +20,7 @@ __all__ = [
|
|
|
20
20
|
"Place",
|
|
21
21
|
"ForecastTimestamp",
|
|
22
22
|
"Forecast",
|
|
23
|
-
"
|
|
23
|
+
"MeteoWarning",
|
|
24
24
|
"HydroStation",
|
|
25
25
|
"HydroObservation",
|
|
26
26
|
"HydroObservationData",
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"""Main API class script"""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
from typing import List, Optional
|
|
4
5
|
|
|
5
6
|
from .models import (
|
|
6
7
|
Forecast,
|
|
7
8
|
Place,
|
|
8
|
-
|
|
9
|
+
MeteoWarning,
|
|
9
10
|
HydroStation,
|
|
10
11
|
HydroObservationData,
|
|
11
12
|
)
|
|
12
13
|
from .utils import find_nearest_location
|
|
13
14
|
from .client import MeteoLtClient
|
|
14
|
-
from .warnings import
|
|
15
|
+
from .warnings import WarningsProcessor
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MeteoLtAPI:
|
|
@@ -20,7 +21,8 @@ class MeteoLtAPI:
|
|
|
20
21
|
def __init__(self, session=None):
|
|
21
22
|
self.places = []
|
|
22
23
|
self.client = MeteoLtClient(session)
|
|
23
|
-
self.warnings_processor =
|
|
24
|
+
self.warnings_processor = WarningsProcessor(self.client, category="weather")
|
|
25
|
+
self.hydro_warnings_processor = WarningsProcessor(self.client, category="hydro")
|
|
24
26
|
|
|
25
27
|
async def __aenter__(self):
|
|
26
28
|
"""Async context manager entry"""
|
|
@@ -56,7 +58,7 @@ class MeteoLtAPI:
|
|
|
56
58
|
longitude: Optional[float] = None,
|
|
57
59
|
place_code: Optional[str] = None,
|
|
58
60
|
) -> Forecast:
|
|
59
|
-
"""Get forecast with
|
|
61
|
+
"""Get forecast with all warnings (weather and hydrological) for a location"""
|
|
60
62
|
if place_code is None:
|
|
61
63
|
if latitude is None or longitude is None:
|
|
62
64
|
raise ValueError("Either place_code or both latitude and longitude must be provided")
|
|
@@ -65,7 +67,7 @@ class MeteoLtAPI:
|
|
|
65
67
|
|
|
66
68
|
return await self.get_forecast(place_code, include_warnings=True)
|
|
67
69
|
|
|
68
|
-
async def get_forecast(self, place_code: str, include_warnings: bool =
|
|
70
|
+
async def get_forecast(self, place_code: str, include_warnings: bool = False) -> Forecast:
|
|
69
71
|
"""Retrieves forecast data from API"""
|
|
70
72
|
forecast = await self.client.fetch_forecast(place_code)
|
|
71
73
|
|
|
@@ -74,27 +76,39 @@ class MeteoLtAPI:
|
|
|
74
76
|
|
|
75
77
|
return forecast
|
|
76
78
|
|
|
77
|
-
async def get_weather_warnings(self, administrative_division: str = None) -> List[
|
|
79
|
+
async def get_weather_warnings(self, administrative_division: str = None) -> List[MeteoWarning]:
|
|
78
80
|
"""Fetches weather warnings from meteo.lt JSON API"""
|
|
79
|
-
return await self.warnings_processor.
|
|
81
|
+
return await self.warnings_processor.get_warnings(administrative_division)
|
|
82
|
+
|
|
83
|
+
async def get_hydro_warnings(self, administrative_division: str = None) -> List[MeteoWarning]:
|
|
84
|
+
"""Fetches hydrological warnings from meteo.lt JSON API"""
|
|
85
|
+
return await self.hydro_warnings_processor.get_warnings(administrative_division)
|
|
86
|
+
|
|
87
|
+
async def get_all_warnings(self, administrative_division: str = None) -> List[MeteoWarning]:
|
|
88
|
+
"""Fetches both weather and hydrological warnings from meteo.lt JSON API"""
|
|
89
|
+
weather_task = self.warnings_processor.get_warnings(administrative_division)
|
|
90
|
+
hydro_task = self.hydro_warnings_processor.get_warnings(administrative_division)
|
|
91
|
+
weather_warnings, hydro_warnings = await asyncio.gather(weather_task, hydro_task)
|
|
92
|
+
return weather_warnings + hydro_warnings
|
|
80
93
|
|
|
81
94
|
async def _enrich_forecast_with_warnings(self, forecast: Forecast) -> None:
|
|
82
|
-
"""Enrich forecast timestamps with relevant weather
|
|
95
|
+
"""Enrich forecast timestamps with all relevant warnings (weather and hydrological)"""
|
|
83
96
|
if not forecast or not forecast.place or not forecast.place.administrative_division:
|
|
84
97
|
return
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
all_warnings = await self.get_all_warnings(forecast.place.administrative_division)
|
|
87
100
|
|
|
88
|
-
if
|
|
89
|
-
|
|
101
|
+
if all_warnings:
|
|
102
|
+
# Use weather processor to enrich (logic is the same for both)
|
|
103
|
+
self.warnings_processor.enrich_forecast_with_warnings(forecast, all_warnings)
|
|
90
104
|
|
|
91
|
-
async def
|
|
105
|
+
async def fetch_hydro_stations(self) -> List[HydroStation]:
|
|
92
106
|
"""Get list of all hydrological stations"""
|
|
93
107
|
return await self.client.fetch_hydro_stations()
|
|
94
108
|
|
|
95
109
|
async def get_nearest_hydro_station(self, latitude: float, longitude: float) -> Optional[HydroStation]:
|
|
96
110
|
"""Find the nearest hydrological station to given coordinates"""
|
|
97
|
-
stations = await self.
|
|
111
|
+
stations = await self.fetch_hydro_stations()
|
|
98
112
|
if not stations:
|
|
99
113
|
return None
|
|
100
114
|
return find_nearest_location(latitude, longitude, stations)
|
|
@@ -12,7 +12,7 @@ from .models import (
|
|
|
12
12
|
HydroObservationData,
|
|
13
13
|
HydroObservation,
|
|
14
14
|
)
|
|
15
|
-
from .const import BASE_URL,
|
|
15
|
+
from .const import BASE_URL, TIMEOUT, ENCODING
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class MeteoLtClient:
|
|
@@ -71,12 +71,12 @@ class MeteoLtClient:
|
|
|
71
71
|
response_json = await response.json()
|
|
72
72
|
return Forecast.from_dict(response_json)
|
|
73
73
|
|
|
74
|
-
async def
|
|
75
|
-
"""Fetches raw
|
|
74
|
+
async def fetch_warnings(self, warnings_url: str) -> Dict[str, Any]:
|
|
75
|
+
"""Fetches raw warnings data from meteo.lt JSON API"""
|
|
76
76
|
session = await self._get_session()
|
|
77
77
|
|
|
78
78
|
# Get the latest warnings file
|
|
79
|
-
async with session.get(
|
|
79
|
+
async with session.get(warnings_url) as response:
|
|
80
80
|
file_list = await response.json()
|
|
81
81
|
|
|
82
82
|
if not file_list:
|
|
@@ -93,35 +93,23 @@ class MeteoLtClient:
|
|
|
93
93
|
session = await self._get_session()
|
|
94
94
|
async with session.get(f"{BASE_URL}/hydro-stations") as resp:
|
|
95
95
|
if resp.status == 200:
|
|
96
|
+
resp.encoding = ENCODING
|
|
96
97
|
response = await resp.json()
|
|
97
98
|
stations = []
|
|
98
99
|
for station_data in response:
|
|
99
|
-
stations.append(
|
|
100
|
-
HydroStation(
|
|
101
|
-
code=station_data.get("code"),
|
|
102
|
-
name=station_data.get("name"),
|
|
103
|
-
water_body=station_data.get("waterBody"),
|
|
104
|
-
coordinates=station_data.get("coordinates", {}),
|
|
105
|
-
)
|
|
106
|
-
)
|
|
100
|
+
stations.append(HydroStation.from_dict(station_data))
|
|
107
101
|
return stations
|
|
108
|
-
|
|
109
|
-
raise Exception(f"API returned status {resp.status}")
|
|
102
|
+
raise aiohttp.ClientError(f"API returned status {resp.status}")
|
|
110
103
|
|
|
111
104
|
async def fetch_hydro_station(self, station_code: str) -> HydroStation:
|
|
112
105
|
"""Get information about a specific hydrological station."""
|
|
113
106
|
session = await self._get_session()
|
|
114
107
|
async with session.get(f"{BASE_URL}/hydro-stations/{station_code}") as resp:
|
|
115
108
|
if resp.status == 200:
|
|
109
|
+
resp.encoding = ENCODING
|
|
116
110
|
response = await resp.json()
|
|
117
|
-
return HydroStation(
|
|
118
|
-
|
|
119
|
-
name=response.get("name"),
|
|
120
|
-
water_body=response.get("waterBody"),
|
|
121
|
-
coordinates=response.get("coordinates", {}),
|
|
122
|
-
)
|
|
123
|
-
else:
|
|
124
|
-
raise Exception(f"API returned status {resp.status}")
|
|
111
|
+
return HydroStation.from_dict(response)
|
|
112
|
+
raise aiohttp.ClientError(f"API returned status {resp.status}")
|
|
125
113
|
|
|
126
114
|
async def fetch_hydro_observation_data(
|
|
127
115
|
self,
|
|
@@ -136,28 +124,15 @@ class MeteoLtClient:
|
|
|
136
124
|
) as resp:
|
|
137
125
|
if resp.status == 200:
|
|
138
126
|
response = await resp.json()
|
|
139
|
-
station = HydroStation(
|
|
140
|
-
code=response["station"].get("code"),
|
|
141
|
-
name=response["station"].get("name"),
|
|
142
|
-
water_body=response["station"].get("waterBody"),
|
|
143
|
-
coordinates=response["station"].get("coordinates", {}),
|
|
144
|
-
)
|
|
127
|
+
station = HydroStation.from_dict(response.get("station"))
|
|
145
128
|
|
|
146
129
|
observations = []
|
|
147
130
|
for obs_data in response.get("observations", []):
|
|
148
|
-
observations.append(
|
|
149
|
-
HydroObservation(
|
|
150
|
-
observation_datetime=obs_data.get("observationTimeUtc"),
|
|
151
|
-
water_level=obs_data.get("waterLevel"),
|
|
152
|
-
water_temperature=obs_data.get("waterTemperature"),
|
|
153
|
-
water_discharge=obs_data.get("waterDischarge"),
|
|
154
|
-
)
|
|
155
|
-
)
|
|
131
|
+
observations.append(HydroObservation.from_dict(obs_data))
|
|
156
132
|
|
|
157
133
|
return HydroObservationData(
|
|
158
134
|
station=station,
|
|
159
135
|
observations_data_range=response.get("observationsDataRange"),
|
|
160
136
|
observations=observations,
|
|
161
137
|
)
|
|
162
|
-
|
|
163
|
-
raise Exception(f"API returned status {resp.status}")
|
|
138
|
+
raise aiohttp.ClientError(f"API returned status {resp.status}")
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""const.py"""
|
|
2
2
|
|
|
3
3
|
BASE_URL = "https://api.meteo.lt/v1"
|
|
4
|
-
WARNINGS_URL = "https://www.meteo.lt/app/mu-plugins/Meteo/Components/
|
|
4
|
+
WARNINGS_URL = "https://www.meteo.lt/app/mu-plugins/Meteo/Components/WeatherWarningsNew/list_JSON.php"
|
|
5
|
+
HYDRO_WARNINGS_URL = "https://www.meteo.lt/app/mu-plugins/Meteo/Components/HydroWarnings/list_JSON.php"
|
|
5
6
|
TIMEOUT = 30
|
|
6
7
|
ENCODING = "utf-8"
|
|
7
8
|
|
|
@@ -50,13 +50,14 @@ class Place(LocationBase):
|
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
@dataclass
|
|
53
|
-
class
|
|
54
|
-
"""
|
|
53
|
+
class MeteoWarning:
|
|
54
|
+
"""Meteorological Warning (includes both weather and hydrological warnings)"""
|
|
55
55
|
|
|
56
56
|
county: str
|
|
57
57
|
warning_type: str
|
|
58
58
|
severity: str
|
|
59
59
|
description: str
|
|
60
|
+
category: str = "weather" # "weather" or "hydro"
|
|
60
61
|
start_time: Optional[str] = None
|
|
61
62
|
end_time: Optional[str] = None
|
|
62
63
|
|
|
@@ -65,17 +66,17 @@ class WeatherWarning:
|
|
|
65
66
|
class HydroStation(LocationBase):
|
|
66
67
|
"""Hydrological station data."""
|
|
67
68
|
|
|
68
|
-
water_body: str
|
|
69
|
+
water_body: str = field(metadata={"json_key": "waterBody"})
|
|
69
70
|
|
|
70
71
|
|
|
71
72
|
@dataclass
|
|
72
73
|
class HydroObservation:
|
|
73
74
|
"""Single hydrological observation."""
|
|
74
75
|
|
|
75
|
-
observation_datetime: Optional[str] = None
|
|
76
|
-
water_level: Optional[float] = None # cm
|
|
77
|
-
water_temperature: Optional[float] = None # °C
|
|
78
|
-
water_discharge: Optional[float] = None # m3/s
|
|
76
|
+
observation_datetime: Optional[str] = field(default=None, metadata={"json_key": "observationTimeUtc"})
|
|
77
|
+
water_level: Optional[float] = field(default=None, metadata={"json_key": "waterLevel"}) # cm
|
|
78
|
+
water_temperature: Optional[float] = field(default=None, metadata={"json_key": "waterTemperature"}) # °C
|
|
79
|
+
water_discharge: Optional[float] = field(default=None, metadata={"json_key": "waterDischarge"}) # m3/s
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
@dataclass
|
|
@@ -83,7 +84,7 @@ class HydroObservationData:
|
|
|
83
84
|
"""Observation data response."""
|
|
84
85
|
|
|
85
86
|
station: HydroStation
|
|
86
|
-
observations_data_range: Optional[dict] = None
|
|
87
|
+
observations_data_range: Optional[dict] = field(default=None, metadata={"json_key": "observationsDataRange"})
|
|
87
88
|
observations: List[HydroObservation] = field(default_factory=list)
|
|
88
89
|
|
|
89
90
|
|
|
@@ -102,7 +103,7 @@ class ForecastTimestamp:
|
|
|
102
103
|
pressure: float = field(metadata={"json_key": "seaLevelPressure"})
|
|
103
104
|
humidity: float = field(metadata={"json_key": "relativeHumidity"})
|
|
104
105
|
precipitation: float = field(metadata={"json_key": "totalPrecipitation"})
|
|
105
|
-
warnings: List[
|
|
106
|
+
warnings: List[MeteoWarning] = field(default_factory=list, init=False)
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
@dataclass
|
|
@@ -169,7 +170,7 @@ Coordinates.from_dict = classmethod(from_dict)
|
|
|
169
170
|
Place.from_dict = classmethod(from_dict)
|
|
170
171
|
ForecastTimestamp.from_dict = classmethod(from_dict)
|
|
171
172
|
Forecast.from_dict = classmethod(from_dict)
|
|
172
|
-
|
|
173
|
+
MeteoWarning.from_dict = classmethod(from_dict)
|
|
173
174
|
HydroStation.from_dict = classmethod(from_dict)
|
|
174
175
|
HydroObservation.from_dict = classmethod(from_dict)
|
|
175
176
|
HydroObservationData.from_dict = classmethod(from_dict)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"""utils.py"""
|
|
2
2
|
|
|
3
3
|
from math import radians, sin, cos, sqrt, atan2
|
|
4
|
-
from typing import
|
|
5
|
-
|
|
6
|
-
LocationT = TypeVar("LocationT") # Type variable for location objects with latitude/longitude
|
|
4
|
+
from typing import List
|
|
5
|
+
from meteo_lt.models import LocationBase
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
def haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
|
@@ -20,7 +19,7 @@ def haversine(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
|
|
20
19
|
return r * c
|
|
21
20
|
|
|
22
21
|
|
|
23
|
-
def find_nearest_location(latitude: float, longitude: float, locations: List[
|
|
22
|
+
def find_nearest_location(latitude: float, longitude: float, locations: List[LocationBase]) -> LocationBase:
|
|
24
23
|
"""Find the nearest location from a list of locations based on the given latitude and longitude."""
|
|
25
24
|
nearest_location = None
|
|
26
25
|
min_distance = float("inf")
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Unified warnings processor for handling weather and hydrological warning-related logic"""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
-
from typing import List, Optional, Dict, Any
|
|
5
|
+
from typing import List, Optional, Dict, Any, Literal
|
|
6
6
|
|
|
7
|
-
from .models import Forecast,
|
|
8
|
-
from .const import COUNTY_MUNICIPALITIES
|
|
7
|
+
from .models import Forecast, MeteoWarning
|
|
8
|
+
from .const import COUNTY_MUNICIPALITIES, WARNINGS_URL, HYDRO_WARNINGS_URL
|
|
9
9
|
from .client import MeteoLtClient
|
|
10
10
|
|
|
11
|
+
WarningCategory = Literal["weather", "hydro"]
|
|
11
12
|
|
|
12
|
-
class WeatherWarningsProcessor:
|
|
13
|
-
"""Processes weather warnings data and handles warning-related logic"""
|
|
14
13
|
|
|
15
|
-
|
|
14
|
+
class WarningsProcessor:
|
|
15
|
+
"""Processes weather and hydrological warnings data and handles warning-related logic"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: MeteoLtClient, category: WarningCategory = "weather"):
|
|
16
18
|
self.client = client
|
|
19
|
+
self.category = category
|
|
20
|
+
|
|
21
|
+
async def get_warnings(self, administrative_division: str = None) -> List[MeteoWarning]:
|
|
22
|
+
"""Fetches and processes warnings (weather or hydro based on category)"""
|
|
23
|
+
# Determine URL based on category
|
|
24
|
+
warnings_url = HYDRO_WARNINGS_URL if self.category == "hydro" else WARNINGS_URL
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
warnings_data = await self.client.fetch_weather_warnings()
|
|
26
|
+
# Fetch warnings data
|
|
27
|
+
warnings_data = await self.client.fetch_warnings(warnings_url)
|
|
21
28
|
warnings = self._parse_warnings_data(warnings_data)
|
|
22
29
|
|
|
23
30
|
# Filter by administrative division if specified
|
|
@@ -26,8 +33,8 @@ class WeatherWarningsProcessor:
|
|
|
26
33
|
|
|
27
34
|
return warnings
|
|
28
35
|
|
|
29
|
-
def _parse_warnings_data(self, warnings_data: Optional[Dict[str, Any]]) -> List[
|
|
30
|
-
"""Parse raw warnings data into
|
|
36
|
+
def _parse_warnings_data(self, warnings_data: Optional[Dict[str, Any]]) -> List[MeteoWarning]:
|
|
37
|
+
"""Parse raw warnings data into MeteoWarning objects"""
|
|
31
38
|
warnings = []
|
|
32
39
|
|
|
33
40
|
# Handle empty response (list instead of dict)
|
|
@@ -36,8 +43,12 @@ class WeatherWarningsProcessor:
|
|
|
36
43
|
|
|
37
44
|
# Parse the warnings data
|
|
38
45
|
for phenomenon_group in warnings_data.get("phenomenon_groups", []):
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
phenomenon_category = phenomenon_group.get("phenomenon_category")
|
|
47
|
+
|
|
48
|
+
# Filter based on category
|
|
49
|
+
if self.category == "hydro" and phenomenon_category != "hydrological":
|
|
50
|
+
continue
|
|
51
|
+
if self.category == "weather" and phenomenon_category == "hydrological":
|
|
41
52
|
continue
|
|
42
53
|
|
|
43
54
|
for area_group in phenomenon_group.get("area_groups", []):
|
|
@@ -54,8 +65,8 @@ class WeatherWarningsProcessor:
|
|
|
54
65
|
|
|
55
66
|
return warnings
|
|
56
67
|
|
|
57
|
-
def _create_warning_from_alert(self, alert: Dict[str, Any], area: Dict[str, Any]) ->
|
|
58
|
-
"""Create a
|
|
68
|
+
def _create_warning_from_alert(self, alert: Dict[str, Any], area: Dict[str, Any]) -> MeteoWarning:
|
|
69
|
+
"""Create a MeteoWarning from alert data"""
|
|
59
70
|
county = area.get("name", "Unknown")
|
|
60
71
|
phenomenon = alert.get("phenomenon", "")
|
|
61
72
|
severity = alert.get("severity", "Minor")
|
|
@@ -72,16 +83,17 @@ class WeatherWarningsProcessor:
|
|
|
72
83
|
if instruction:
|
|
73
84
|
full_description += f"\n\nRecommendations: {instruction}"
|
|
74
85
|
|
|
75
|
-
return
|
|
86
|
+
return MeteoWarning(
|
|
76
87
|
county=county,
|
|
77
88
|
warning_type=warning_type,
|
|
78
89
|
severity=severity,
|
|
79
90
|
description=full_description,
|
|
91
|
+
category=self.category,
|
|
80
92
|
start_time=alert.get("t_from"),
|
|
81
93
|
end_time=alert.get("t_to"),
|
|
82
94
|
)
|
|
83
95
|
|
|
84
|
-
def _warning_affects_area(self, warning:
|
|
96
|
+
def _warning_affects_area(self, warning: MeteoWarning, administrative_division: str) -> bool:
|
|
85
97
|
"""Check if warning affects specified administrative division"""
|
|
86
98
|
admin_lower = administrative_division.lower().replace(" savivaldybė", "").replace(" sav.", "")
|
|
87
99
|
|
|
@@ -99,22 +111,31 @@ class WeatherWarningsProcessor:
|
|
|
99
111
|
|
|
100
112
|
return False
|
|
101
113
|
|
|
102
|
-
def enrich_forecast_with_warnings(self, forecast: Forecast, warnings: List[
|
|
103
|
-
"""Enrich forecast timestamps with relevant
|
|
114
|
+
def enrich_forecast_with_warnings(self, forecast: Forecast, warnings: List[MeteoWarning]) -> None:
|
|
115
|
+
"""Enrich forecast timestamps with relevant warnings
|
|
116
|
+
|
|
117
|
+
All warnings (weather and hydro) are added to 'warnings' attribute
|
|
118
|
+
"""
|
|
104
119
|
if not warnings:
|
|
105
120
|
return
|
|
106
121
|
|
|
107
122
|
# For each forecast timestamp, find applicable warnings
|
|
108
123
|
for timestamp in forecast.forecast_timestamps:
|
|
109
|
-
|
|
124
|
+
# Initialize warnings list if it doesn't exist
|
|
125
|
+
if not hasattr(timestamp, "warnings"):
|
|
126
|
+
timestamp.warnings = []
|
|
127
|
+
# Get warnings for this timestamp and extend the list
|
|
128
|
+
applicable_warnings = self._get_warnings_for_timestamp(timestamp.datetime, warnings)
|
|
129
|
+
timestamp.warnings.extend(applicable_warnings)
|
|
110
130
|
|
|
111
131
|
# Also add warnings to current conditions if available
|
|
112
132
|
if hasattr(forecast, "current_conditions") and forecast.current_conditions:
|
|
113
|
-
forecast.current_conditions
|
|
114
|
-
forecast.current_conditions.
|
|
115
|
-
)
|
|
133
|
+
if not hasattr(forecast.current_conditions, "warnings"):
|
|
134
|
+
forecast.current_conditions.warnings = []
|
|
135
|
+
applicable_warnings = self._get_warnings_for_timestamp(forecast.current_conditions.datetime, warnings)
|
|
136
|
+
forecast.current_conditions.warnings.extend(applicable_warnings)
|
|
116
137
|
|
|
117
|
-
def _get_warnings_for_timestamp(self, timestamp_str: str, warnings: List[
|
|
138
|
+
def _get_warnings_for_timestamp(self, timestamp_str: str, warnings: List[MeteoWarning]) -> List[MeteoWarning]:
|
|
118
139
|
"""Get warnings that are active for a specific timestamp"""
|
|
119
140
|
try:
|
|
120
141
|
timestamp = datetime.fromisoformat(timestamp_str).replace(tzinfo=timezone.utc)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meteo_lt-pkg
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
4
4
|
Summary: A library to fetch weather data from api.meteo.lt
|
|
5
5
|
Author-email: Brunas <brunonas@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Brunas/meteo_lt-pkg
|
|
@@ -228,6 +228,66 @@ asyncio.run(fetch_warnings())
|
|
|
228
228
|
asyncio.run(fetch_warnings_for_area())
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
+
### Fetching Hydrological Warnings
|
|
232
|
+
|
|
233
|
+
To get hydrological warnings (water-related warnings like floods, high water levels):
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
async def fetch_hydro_warnings():
|
|
237
|
+
async with MeteoLtAPI() as api:
|
|
238
|
+
# Get all hydrological warnings
|
|
239
|
+
hydro_warnings = await api.get_hydro_warnings()
|
|
240
|
+
print(f"Total active hydro warnings: {len(hydro_warnings)}")
|
|
241
|
+
|
|
242
|
+
for warning in hydro_warnings:
|
|
243
|
+
print(f"Hydro Warning: {warning.warning_type} in {warning.county}")
|
|
244
|
+
print(f"Severity: {warning.severity}")
|
|
245
|
+
print(f"Description: {warning.description}")
|
|
246
|
+
print("-" * 50)
|
|
247
|
+
|
|
248
|
+
async def fetch_hydro_warnings_for_area():
|
|
249
|
+
async with MeteoLtAPI() as api:
|
|
250
|
+
# Get hydro warnings for specific administrative division
|
|
251
|
+
warnings = await api.get_hydro_warnings("Vilniaus miesto")
|
|
252
|
+
print(f"Hydro warnings for Vilnius: {len(warnings)}")
|
|
253
|
+
|
|
254
|
+
asyncio.run(fetch_hydro_warnings())
|
|
255
|
+
asyncio.run(fetch_hydro_warnings_for_area())
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Fetching All Warnings (Weather + Hydrological)
|
|
259
|
+
|
|
260
|
+
To get both weather and hydrological warnings combined:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
async def fetch_all_warnings():
|
|
264
|
+
async with MeteoLtAPI() as api:
|
|
265
|
+
# Get all warnings (weather + hydrological)
|
|
266
|
+
all_warnings = await api.get_all_warnings()
|
|
267
|
+
print(f"Total active warnings (all types): {len(all_warnings)}")
|
|
268
|
+
|
|
269
|
+
for warning in all_warnings:
|
|
270
|
+
print(f"{warning.warning_type} in {warning.county}")
|
|
271
|
+
print(f"Severity: {warning.severity}")
|
|
272
|
+
print(f"Description: {warning.description}")
|
|
273
|
+
print("-" * 50)
|
|
274
|
+
|
|
275
|
+
async def fetch_all_warnings_for_area():
|
|
276
|
+
async with MeteoLtAPI() as api:
|
|
277
|
+
# Get all warnings for specific administrative division
|
|
278
|
+
warnings = await api.get_all_warnings("Vilniaus miesto")
|
|
279
|
+
print(f"All warnings for Vilnius: {len(warnings)}")
|
|
280
|
+
|
|
281
|
+
# Separate by type if needed
|
|
282
|
+
weather_count = sum(1 for w in warnings if w.category == "weather")
|
|
283
|
+
hydro_count = sum(1 for w in warnings if w.category == "hydro")
|
|
284
|
+
print(f" Weather warnings: {weather_count}")
|
|
285
|
+
print(f" Hydro warnings: {hydro_count}")
|
|
286
|
+
|
|
287
|
+
asyncio.run(fetch_all_warnings())
|
|
288
|
+
asyncio.run(fetch_all_warnings_for_area())
|
|
289
|
+
```
|
|
290
|
+
|
|
231
291
|
## Data Models
|
|
232
292
|
|
|
233
293
|
The package includes several data models to represent the API responses:
|
|
@@ -250,8 +310,15 @@ Represents a place with associated metadata.
|
|
|
250
310
|
```python
|
|
251
311
|
from meteo_lt import Place
|
|
252
312
|
|
|
253
|
-
place = Place(
|
|
313
|
+
place = Place(
|
|
314
|
+
code="vilnius",
|
|
315
|
+
name="Vilnius",
|
|
316
|
+
administrative_division="Vilniaus miesto",
|
|
317
|
+
country_code="LT",
|
|
318
|
+
coordinates=coords
|
|
319
|
+
)
|
|
254
320
|
print(place.latitude, place.longitude)
|
|
321
|
+
print(place.counties) # List of counties this place belongs to
|
|
255
322
|
```
|
|
256
323
|
|
|
257
324
|
### ForecastTimestamp
|
|
@@ -274,7 +341,10 @@ forecast_timestamp = ForecastTimestamp(
|
|
|
274
341
|
humidity=60,
|
|
275
342
|
precipitation=0
|
|
276
343
|
)
|
|
277
|
-
print(forecast_timestamp.
|
|
344
|
+
print(f"Temperature: {forecast_timestamp.temperature}°C")
|
|
345
|
+
print(f"Condition: {forecast_timestamp.condition_code}")
|
|
346
|
+
# Warnings list is automatically populated when using get_forecast_with_warnings()
|
|
347
|
+
print(f"Warnings: {len(forecast_timestamp.warnings)}")
|
|
278
348
|
```
|
|
279
349
|
|
|
280
350
|
### Forecast
|
|
@@ -286,28 +356,176 @@ from meteo_lt import Forecast
|
|
|
286
356
|
|
|
287
357
|
forecast = Forecast(
|
|
288
358
|
place=place,
|
|
289
|
-
forecast_created=
|
|
359
|
+
forecast_created="2024-07-23T12:00:00+00:00",
|
|
360
|
+
current_conditions=forecast_timestamp,
|
|
290
361
|
forecast_timestamps=[forecast_timestamp]
|
|
291
362
|
)
|
|
292
|
-
|
|
363
|
+
# current_conditions is automatically set to the current hour's forecast
|
|
364
|
+
print(f"Current temperature: {forecast.current_conditions.temperature}°C")
|
|
365
|
+
# forecast_timestamps are automatically filtered to exclude past hours
|
|
366
|
+
print(f"Future forecasts: {len(forecast.forecast_timestamps)}")
|
|
293
367
|
```
|
|
294
368
|
|
|
295
|
-
###
|
|
369
|
+
### MeteoWarning
|
|
296
370
|
|
|
297
|
-
Represents a
|
|
371
|
+
Represents a meteorological warning (weather or hydrological) for a specific area.
|
|
298
372
|
|
|
299
373
|
```python
|
|
300
|
-
from meteo_lt import
|
|
374
|
+
from meteo_lt import MeteoWarning
|
|
301
375
|
|
|
302
|
-
warning
|
|
376
|
+
# Weather warning example
|
|
377
|
+
weather_warning = MeteoWarning(
|
|
303
378
|
county="Vilniaus apskritis",
|
|
304
379
|
warning_type="frost",
|
|
305
380
|
severity="Moderate",
|
|
306
381
|
description="Ground surface frost 0-5 degrees in many places",
|
|
382
|
+
category="weather",
|
|
307
383
|
start_time="2024-07-23T12:00:00Z",
|
|
308
384
|
end_time="2024-07-23T18:00:00Z"
|
|
309
385
|
)
|
|
310
|
-
print(f"Warning for {
|
|
386
|
+
print(f"Warning for {weather_warning.county}: {weather_warning.description}")
|
|
387
|
+
print(f"Category: {weather_warning.category}") # "weather" or "hydro"
|
|
388
|
+
|
|
389
|
+
# Hydrological warning example
|
|
390
|
+
hydro_warning = MeteoWarning(
|
|
391
|
+
county="Kauno apskritis",
|
|
392
|
+
warning_type="flood",
|
|
393
|
+
severity="High",
|
|
394
|
+
description="High water levels expected",
|
|
395
|
+
category="hydro",
|
|
396
|
+
start_time="2024-07-23T12:00:00Z",
|
|
397
|
+
end_time="2024-07-24T12:00:00Z"
|
|
398
|
+
)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### HydroStation
|
|
402
|
+
|
|
403
|
+
Represents a hydrological observation station with water body information.
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
from meteo_lt import HydroStation
|
|
407
|
+
|
|
408
|
+
station = HydroStation(
|
|
409
|
+
code="klaipedos-juru-uosto-vms",
|
|
410
|
+
name="Klaipėdos jūrų uosto VMS",
|
|
411
|
+
water_body="Baltijos jūra",
|
|
412
|
+
coordinates=coords
|
|
413
|
+
)
|
|
414
|
+
print(f"Station: {station.name} on {station.water_body}")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### HydroObservation
|
|
418
|
+
|
|
419
|
+
Represents a single hydrological observation with water measurements.
|
|
420
|
+
|
|
421
|
+
```python
|
|
422
|
+
from meteo_lt import HydroObservation
|
|
423
|
+
|
|
424
|
+
observation = HydroObservation(
|
|
425
|
+
observation_datetime="2024-07-23T12:00:00+00:00",
|
|
426
|
+
water_level=481.8,
|
|
427
|
+
water_temperature=15.5,
|
|
428
|
+
water_discharge=100.0
|
|
429
|
+
)
|
|
430
|
+
print(f"Water level: {observation.water_level} cm")
|
|
431
|
+
print(f"Water temperature: {observation.water_temperature}°C")
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### HydroObservationData
|
|
435
|
+
|
|
436
|
+
Represents a collection of hydrological observations for a specific station.
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
from meteo_lt import HydroObservationData
|
|
440
|
+
|
|
441
|
+
data = HydroObservationData(
|
|
442
|
+
station=station,
|
|
443
|
+
observations=[observation],
|
|
444
|
+
observations_data_range={"from": "2024-07-01", "to": "2024-07-23"}
|
|
445
|
+
)
|
|
446
|
+
print(f"Station: {data.station.name}")
|
|
447
|
+
print(f"Observations: {len(data.observations)}")
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Hydrological Data
|
|
451
|
+
|
|
452
|
+
The package provides access to hydrological data from Lithuanian water monitoring stations.
|
|
453
|
+
|
|
454
|
+
### Fetching Hydrological Stations
|
|
455
|
+
|
|
456
|
+
To get the list of all hydrological stations:
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
async def fetch_hydro_stations():
|
|
460
|
+
async with MeteoLtAPI() as api:
|
|
461
|
+
stations = await api.fetch_hydro_stations()
|
|
462
|
+
for station in stations:
|
|
463
|
+
print(f"{station.name} ({station.code}) - Water body: {station.water_body}")
|
|
464
|
+
|
|
465
|
+
asyncio.run(fetch_hydro_stations())
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Finding the Nearest Hydrological Station
|
|
469
|
+
|
|
470
|
+
You can find the nearest hydrological station using coordinates:
|
|
471
|
+
|
|
472
|
+
```python
|
|
473
|
+
async def find_nearest_hydro_station():
|
|
474
|
+
async with MeteoLtAPI() as api:
|
|
475
|
+
# Example coordinates for Klaipėda, Lithuania
|
|
476
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
477
|
+
print(f"Nearest station: {nearest_station.name}")
|
|
478
|
+
print(f"Water body: {nearest_station.water_body}")
|
|
479
|
+
print(f"Distance: approximately {nearest_station.latitude}, {nearest_station.longitude}")
|
|
480
|
+
|
|
481
|
+
asyncio.run(find_nearest_hydro_station())
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Fetching Hydrological Observations
|
|
485
|
+
|
|
486
|
+
To get water level and temperature observations for a specific station:
|
|
487
|
+
|
|
488
|
+
```python
|
|
489
|
+
async def fetch_hydro_observations():
|
|
490
|
+
async with MeteoLtAPI() as api:
|
|
491
|
+
# Get observations for a station
|
|
492
|
+
hydro_data = await api.get_hydro_observation_data("klaipedos-juru-uosto-vms")
|
|
493
|
+
|
|
494
|
+
print(f"Station: {hydro_data.station.name}")
|
|
495
|
+
print(f"Water body: {hydro_data.station.water_body}")
|
|
496
|
+
print(f"\nRecent observations:")
|
|
497
|
+
|
|
498
|
+
for observation in hydro_data.observations[:5]:
|
|
499
|
+
print(f" {observation.observation_datetime}")
|
|
500
|
+
print(f" Water level: {observation.water_level} cm")
|
|
501
|
+
print(f" Water temperature: {observation.water_temperature}°C")
|
|
502
|
+
if observation.water_discharge:
|
|
503
|
+
print(f" Water discharge: {observation.water_discharge} m³/s")
|
|
504
|
+
|
|
505
|
+
asyncio.run(fetch_hydro_observations())
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Getting Observations for Nearest Station
|
|
509
|
+
|
|
510
|
+
Combine location finding with observation fetching:
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
async def get_nearest_station_observations():
|
|
514
|
+
async with MeteoLtAPI() as api:
|
|
515
|
+
# Find nearest hydro station to coordinates
|
|
516
|
+
nearest_station = await api.get_nearest_hydro_station(55.6872, 21.2797)
|
|
517
|
+
|
|
518
|
+
# Get observations for the nearest station
|
|
519
|
+
hydro_data = await api.get_hydro_observation_data(nearest_station.code)
|
|
520
|
+
|
|
521
|
+
print(f"Latest observations from {hydro_data.station.name}:")
|
|
522
|
+
if hydro_data.observations:
|
|
523
|
+
latest = hydro_data.observations[0]
|
|
524
|
+
print(f" Time: {latest.observation_datetime}")
|
|
525
|
+
print(f" Water level: {latest.water_level} cm")
|
|
526
|
+
print(f" Temperature: {latest.water_temperature}°C")
|
|
527
|
+
|
|
528
|
+
asyncio.run(get_nearest_station_observations())
|
|
311
529
|
```
|
|
312
530
|
|
|
313
531
|
## Contributing
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|