Skip to content
This repository was archived by the owner on Sep 5, 2023. It is now read-only.

Commit d9335bf

Browse files
add weather alert endpoint
1 parent 18d8a7d commit d9335bf

File tree

7 files changed

+262
-11
lines changed

7 files changed

+262
-11
lines changed

here_location_services/config/autosuggest_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class SearchCircle:
1313
defined by its center and radius(in meters).
1414
"""
1515

16-
def __init__(self, lat: str, lng: str, radius: int):
16+
def __init__(self, lat: float, lng: float, radius: int):
1717
self.lat = lat
1818
self.lng = lng
1919
self.radius = radius

here_location_services/config/dest_weather_config.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,81 @@ class DestWeatherUnits(Bunch):
6666
#: Use this config for ``units``` of Destination Weather API.
6767
#: Example: for ``metric`` units use ``DEST_WEATHER_UNITS.metric``.
6868
DEST_WEATHER_UNITS = DestWeatherUnits(**units)
69+
70+
71+
class WeatherSeverity(Bunch):
72+
"""A class to define the severity of the weather event
73+
74+
``insignificant``:
75+
Event doesn't have significance by nature
76+
77+
``no_alerts``:
78+
There are no alerts for the location
79+
80+
``minor``:
81+
Minor Severity, the event is potentially dangerous but not usual
82+
83+
``medium``:
84+
Medium Severity, the event is dangerous
85+
86+
``high``:
87+
High Severity, The event is very dangerous
88+
89+
``emergency``:
90+
Emergency. Take immediate action to protect life.
91+
"""
92+
93+
94+
weather_severity = {
95+
"insignificant": 0,
96+
"no_alerts": 1,
97+
"minor": 2,
98+
"medium": 3,
99+
"high": 4,
100+
"emergency": 5,
101+
}
102+
103+
#: Use this config for ``weather_severity``` of get weather alerts endpoint.
104+
#: Example: for ``high severity`` events use ``WEATHER_SEVERITY.high``.
105+
WEATHER_SEVERITY = WeatherSeverity(**weather_severity)
106+
107+
108+
class WeatherType(Bunch):
109+
"""A class to define the type of the weather event"""
110+
111+
112+
weather_type = {
113+
"extremely_high_temperature": 1,
114+
"extremely_low_temperature": 2,
115+
"fog": 3,
116+
"ice": 4,
117+
"rain": 5,
118+
"snow": 6,
119+
"thunderstorm": 7,
120+
"wind": 8,
121+
"air_quality": 9,
122+
"volcanic_ashfall": 10,
123+
"avalanche": 11,
124+
"tsunami": 12,
125+
"dust_storm": 13,
126+
"earthquake": 14,
127+
"fire_danger": 15,
128+
"flood": 16,
129+
"high_waves": 17,
130+
"gigh_uv_index": 18,
131+
"low_water": 19,
132+
"smoke": 20,
133+
"volcano": 21,
134+
"ice_in_waterway": 22,
135+
"coastal_event": 23,
136+
"civil_danger": 24,
137+
"evacuation": 25,
138+
"hazardous_material": 26,
139+
"radiological_hazard": 27,
140+
"shelter_in_place": 28,
141+
"warning": 29,
142+
}
143+
144+
#: Use this config for ``weather_type``` of get weather alerts endpoint of Destination Weather API.
145+
#: Example: for ``fog`` weather type use ``WEATHER_TYPE.fog``.
146+
WEATHER_TYPE = WeatherType(**weather_type)

here_location_services/destination_weather_api.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"""This module contains classes for accessing `HERE Destination Weather API <https://developer.here.com/documentation/destination-weather/dev_guide/topics/overview.html>`_.
55
""" # noqa E501
66

7+
import time
78
from datetime import datetime
8-
from typing import Dict, List, Optional
9+
from typing import Any, Dict, List, Optional
910

1011
from here_location_services.platform.auth import Auth
1112

@@ -32,7 +33,7 @@ def get_dest_weather(
3233
at: Optional[List] = None,
3334
query: Optional[str] = None,
3435
zipcode: Optional[str] = None,
35-
hourly_date: Optional[datetime] = None,
36+
hourly_date: Optional[Any] = None,
3637
one_observation: Optional[bool] = None,
3738
language: Optional[str] = None,
3839
units: Optional[str] = None,
@@ -83,3 +84,78 @@ def get_dest_weather(
8384
return resp
8485
else:
8586
raise ApiError(resp)
87+
88+
def get_weather_alerts(
89+
self,
90+
feature_type: str,
91+
geometry_type: str,
92+
geometry_coordinates: List,
93+
start_time: datetime,
94+
id: Optional[str] = None,
95+
weather_severity: Optional[int] = None,
96+
weather_type: Optional[str] = None,
97+
country: Optional[str] = None,
98+
end_time: Optional[datetime] = None,
99+
width: Optional[int] = 50000,
100+
):
101+
"""Retrieves weather reports, weather forecasts, severe weather alerts and moon and sun rise and set information.
102+
103+
See further information `Here Destination Weather API <https://developer.here.com/documentation/destination-weather/dev_guide/topics/overview.html>_`.
104+
105+
:param feature_type: String to define feature type
106+
:param geometry_type: Point or LineString or Polygon or MultiPolygon
107+
:param geometry_coordinates: Array of coordinates corressponding to type provided
108+
:param start_time: Start time of the event
109+
:param id: Unique weather alert id.
110+
:param weather_severity: Defines the severity of the weather event as defined
111+
in :class:`WeatherSeverity`.
112+
:param weather_type: Defines the type of the weather event as defined
113+
in :class:`WeatherType`.
114+
:param country: String for ISO-3166-1 2-letter country code.
115+
:param end_time: End time of the event. If not present, warning is valid until
116+
it is not removed from the feed by national weather institutes
117+
(valid until warning is present in the response)
118+
:param width: int. default 50000
119+
:return: :class:`requests.Response` object.
120+
:raises ApiError: If ``status_code`` of API response is not 200.
121+
""" # noqa E501
122+
123+
path = "v3/alerts"
124+
url = f"{self._base_url}/{path}"
125+
data: Dict[str, Any] = {
126+
"type": "FeatureCollection",
127+
}
128+
features: Dict[str, Any] = {
129+
"type": feature_type,
130+
}
131+
132+
if id:
133+
features["id"] = id
134+
135+
geometry: Dict[str, Any] = {"type": geometry_type, "coordinates": geometry_coordinates}
136+
137+
properties: Dict[str, Any] = {
138+
"width": width,
139+
}
140+
weather_warnings: Dict[str, Any] = {
141+
"startTime": time.mktime(start_time.timetuple()),
142+
}
143+
144+
if weather_severity:
145+
weather_warnings["severity"] = weather_severity
146+
if weather_type:
147+
weather_warnings["type"] = weather_type
148+
if country:
149+
weather_warnings["country"] = country
150+
if end_time:
151+
weather_warnings["endTime"] = time.mktime(end_time.timetuple())
152+
153+
properties["warnings"] = [weather_warnings]
154+
features["properties"] = properties
155+
features["geometry"] = geometry
156+
data["features"] = [features]
157+
resp = self.post(url, data=data)
158+
if resp.status_code == 200:
159+
return resp
160+
else:
161+
raise ApiError(resp)

here_location_services/ls.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import urllib.request
99
from datetime import datetime
1010
from time import sleep
11-
from typing import Dict, List, Optional, Tuple, Union
11+
from typing import Any, Dict, List, Optional, Tuple, Union
1212

1313
from here_location_services.config.routing_config import Scooter, Via
1414
from here_location_services.platform.apis.aaa_oauth2_api import AAAOauth2Api
@@ -320,7 +320,7 @@ def get_dest_weather(
320320
at: Optional[List] = None,
321321
query: Optional[str] = None,
322322
zipcode: Optional[str] = None,
323-
hourly_date: Optional[datetime] = None,
323+
hourly_date: Optional[Any] = None,
324324
one_observation: Optional[bool] = None,
325325
language: Optional[str] = None,
326326
units: Optional[str] = None,
@@ -368,6 +368,54 @@ def get_dest_weather(
368368
response = DestinationWeatherResponse.new(resp.json())
369369
return response
370370

371+
def get_weather_alerts(
372+
self,
373+
feature_type: str,
374+
geometry_type: str,
375+
geometry_coordinates: List,
376+
start_time: datetime,
377+
id: Optional[str] = None,
378+
weather_severity: Optional[int] = None,
379+
weather_type: Optional[str] = None,
380+
country: Optional[str] = None,
381+
end_time: Optional[datetime] = None,
382+
width: Optional[int] = 50000,
383+
) -> DestinationWeatherResponse:
384+
"""Retrieves weather reports, weather forecasts, severe weather alerts
385+
and moon and sun rise and set information.
386+
387+
:param feature_type: String to define feature type
388+
:param geometry_type: Point or LineString or Polygon or MultiPolygon
389+
:param geometry_coordinates: Array of coordinates corressponding to type provided
390+
:param start_time: Start time of the event
391+
:param id: Unique weather alert id.
392+
:param weather_severity: Defines the severity of the weather event as defined
393+
in :class:`WeatherSeverity`.
394+
:param weather_type: Defines the type of the weather event as defined
395+
in :class:`WeatherType`.
396+
:param country: String for ISO-3166-1 2-letter country code.
397+
:param end_time: End time of the event. If not present, warning is valid until
398+
it is not removed from the feed by national weather institutes
399+
(valid until warning is present in the response)
400+
:param width: int. default 50000
401+
:return: :class:`DestinationWeatherResponse` object.
402+
"""
403+
404+
resp = self.destination_weather_api.get_weather_alerts(
405+
feature_type=feature_type,
406+
geometry_type=geometry_type,
407+
geometry_coordinates=geometry_coordinates,
408+
id=id,
409+
weather_severity=weather_severity,
410+
weather_type=weather_type,
411+
country=country,
412+
start_time=start_time,
413+
end_time=end_time,
414+
width=width,
415+
)
416+
response = DestinationWeatherResponse.new(resp.json())
417+
return response
418+
371419
def discover(
372420
self,
373421
query: str,

here_location_services/responses.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,6 @@ class DestinationWeatherResponse(ApiResponse):
200200

201201
def __init__(self, **kwargs):
202202
super().__init__()
203-
self._filters = {
204-
"places": None,
205-
}
203+
self._filters = {"places": None, "features": None}
206204
for param, default in self._filters.items():
207205
setattr(self, param, kwargs.get(param, default))

tests/test_ls.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import json
5-
from datetime import datetime
5+
from datetime import datetime, timedelta
66

77
import pandas as pd
88
import pytest
@@ -21,6 +21,8 @@
2121
from here_location_services.config.dest_weather_config import (
2222
DEST_WEATHER_PRODUCT,
2323
DEST_WEATHER_UNITS,
24+
WEATHER_SEVERITY,
25+
WEATHER_TYPE,
2426
)
2527
from here_location_services.config.isoline_routing_config import (
2628
ISOLINE_ROUTING_AVOID_FEATURES,
@@ -56,6 +58,41 @@
5658
LS_API_KEY = get_apikey()
5759

5860

61+
@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.")
62+
def test_ls_weather_alerts():
63+
"""Test weather alerts endpoint of destination weather api."""
64+
ls = LS(api_key=LS_API_KEY)
65+
resp = ls.get_weather_alerts(
66+
feature_type="Feature",
67+
geometry_type="Point",
68+
geometry_coordinates=[15.256, 23.456],
69+
start_time=datetime.now(),
70+
width=3000,
71+
)
72+
assert resp
73+
74+
resp2 = ls.get_weather_alerts(
75+
feature_type="Feature",
76+
geometry_type="Point",
77+
geometry_coordinates=[15.256, 23.456],
78+
start_time=datetime.now(),
79+
weather_type=WEATHER_TYPE.ice,
80+
weather_severity=WEATHER_SEVERITY.high,
81+
country="US",
82+
end_time=datetime.now() + timedelta(days=7),
83+
)
84+
assert resp2
85+
86+
with pytest.raises(ApiError):
87+
ls2 = LS(api_key="dummy")
88+
ls2.get_weather_alerts(
89+
feature_type="Feature",
90+
geometry_type="Point",
91+
geometry_coordinates=[15.256, 23.456],
92+
start_time=datetime.now(),
93+
)
94+
95+
5996
@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.")
6097
def test_ls_dest_weather():
6198
"""Test destination weather api."""
@@ -121,7 +158,7 @@ def test_ls_autosuggest():
121158
assert resp.items
122159
assert len(resp.items) <= 5
123160

124-
search_in_circle1 = SearchCircle(lat="52.53", lng="13.38", radius="10000")
161+
search_in_circle1 = SearchCircle(lat=52.53, lng=13.38, radius="10000")
125162
search_in_bbox1 = ("13.08836", "52.33812", "13.761", "52.6755")
126163

127164
resp3 = ls.autosuggest(query="bar", limit=5, search_in_circle=search_in_circle1, lang=["en"])

tests/test_ls_apis.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
from argparse import Namespace
5+
from datetime import datetime
56

67
import pytest
78
import requests
@@ -20,7 +21,7 @@
2021

2122

2223
@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.")
23-
def test_destination(destination_weather_api):
24+
def test_destination_weather(destination_weather_api):
2425
"""Test Destination Weather api."""
2526
resp = destination_weather_api.get_dest_weather(
2627
products=[DEST_WEATHER_PRODUCT.observation], query="Chicago"
@@ -29,6 +30,19 @@ def test_destination(destination_weather_api):
2930
assert resp.status_code == 200
3031

3132

33+
@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.")
34+
def test_weather_alerts(destination_weather_api):
35+
"""Test Destination Weather api."""
36+
resp = destination_weather_api.get_weather_alerts(
37+
feature_type="Feature",
38+
geometry_type="Point",
39+
geometry_coordinates=[15.256, 23.456],
40+
start_time=datetime.now(),
41+
)
42+
assert type(resp) == requests.Response
43+
assert resp.status_code == 200
44+
45+
3246
@pytest.mark.skipif(not LS_API_KEY, reason="No api key found.")
3347
def test_autosuggest(autosuggest_api):
3448
"""Test autosuggest api."""

0 commit comments

Comments
 (0)