meteofrance-api 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +56 -0
- data/.travis.yml +7 -0
- data/CHANGELOG.md +0 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/meteofrance_api/client.rb +275 -0
- data/lib/meteofrance_api/constants.rb +86 -0
- data/lib/meteofrance_api/helpers.rb +119 -0
- data/lib/meteofrance_api/models/place.rb +38 -0
- data/lib/meteofrance_api/models/rain/forecast.rb +11 -0
- data/lib/meteofrance_api/models/rain.rb +44 -0
- data/lib/meteofrance_api/models/warning/current.rb +44 -0
- data/lib/meteofrance_api/models/warning/full.rb +67 -0
- data/lib/meteofrance_api/models/warning/phenomenon.rb +9 -0
- data/lib/meteofrance_api/models/warning.rb +5 -0
- data/lib/meteofrance_api/models/weather/daily_forecast.rb +35 -0
- data/lib/meteofrance_api/models/weather/forecast.rb +63 -0
- data/lib/meteofrance_api/models/weather/hazard_forecast.rb +17 -0
- data/lib/meteofrance_api/models/weather.rb +89 -0
- data/lib/meteofrance_api/models.rb +4 -0
- data/lib/meteofrance_api/version.rb +3 -0
- data/lib/meteofrance_api.rb +10 -0
- data/meteofrance-api.gemspec +44 -0
- metadata +131 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
require "meteofrance_api/constants"
|
2
|
+
|
3
|
+
module MeteofranceApi::Helpers
|
4
|
+
# Convert the color code in readable text.
|
5
|
+
#
|
6
|
+
# Args:
|
7
|
+
# color_code: Color status in int. Value expected between 1 and 4.
|
8
|
+
# lang(Optional): If language is equal :fr (default value) results will
|
9
|
+
# be in French. All other value will give results in English.
|
10
|
+
|
11
|
+
#Returns:
|
12
|
+
#Color status in text. French or English according to the lang parameter.
|
13
|
+
def color_code_to_str(
|
14
|
+
code,
|
15
|
+
lang = :fr
|
16
|
+
)
|
17
|
+
colors = MeteofranceApi::ALERT_COLORS[lang] || MeteofranceApi::ALERT_COLORS[:en]
|
18
|
+
|
19
|
+
colors[code]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Convert the phenomenom code in readable text (Hepler).
|
24
|
+
#
|
25
|
+
# Args:
|
26
|
+
# code: ID of the phenomenom in int. Value expected between 1 and 9.
|
27
|
+
# lang: Optional; If language is equal :fr (default value) results will
|
28
|
+
# be in French. All other value will give results in English.
|
29
|
+
#
|
30
|
+
# Returns:
|
31
|
+
# Phenomenom in text. French or English according to the lang parameter.
|
32
|
+
def alert_code_to_str(
|
33
|
+
code,
|
34
|
+
lang = :fr
|
35
|
+
)
|
36
|
+
alert_types = MeteofranceApi::ALERT_TYPES[lang] || MeteofranceApi::ALERT_TYPES[:en]
|
37
|
+
|
38
|
+
alert_types[code]
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Identify when a second bulletin is availabe for coastal risks (Helper).
|
43
|
+
#
|
44
|
+
# Args:
|
45
|
+
# department_number: Department number on 2 characters
|
46
|
+
#
|
47
|
+
# Returns:
|
48
|
+
# True if the department have an additional coastal bulletin. False otherwise.
|
49
|
+
#
|
50
|
+
def is_coastal_department?(department_number)
|
51
|
+
MeteofranceApi::COASTAL_DEPARTMENTS.include?(department_number)
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Identify if there is a weather alert bulletin for this department (Helper).
|
56
|
+
#
|
57
|
+
# Weather alert buletins are available only for metropolitan France and Andorre.
|
58
|
+
#
|
59
|
+
# Args:
|
60
|
+
# department_number: Department number on 2 characters.
|
61
|
+
#
|
62
|
+
# Returns:
|
63
|
+
# True if a department is metropolitan France or Andorre.
|
64
|
+
#
|
65
|
+
def is_department?(department_number)
|
66
|
+
MeteofranceApi::VALID_DEPARTMENTS.include?(department_number)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Compute distance in meters between to GPS coordinates using Harvesine formula.
|
70
|
+
#
|
71
|
+
# source: https://janakiev.com/blog/gps-points-distance-python/
|
72
|
+
#
|
73
|
+
# Args:
|
74
|
+
# coord1: Tuple with latitude and longitude in degrees for first point
|
75
|
+
# coord2: Tuple with latitude and longitude in degrees for second point
|
76
|
+
#
|
77
|
+
# Returns:
|
78
|
+
# Distance in meters between the two points
|
79
|
+
def haversine(coord1, coord2)
|
80
|
+
to_radians = ->(v) { v * (Math::PI / 180) }
|
81
|
+
radius = 6372800 # Earth radius in meters
|
82
|
+
|
83
|
+
lat1, lon1 = coord1
|
84
|
+
lat2, lon2 = coord2
|
85
|
+
|
86
|
+
phi1, phi2 = to_radians.call(lat1), to_radians.call(lat2)
|
87
|
+
dphi = to_radians.call(lat2 - lat1)
|
88
|
+
dlambda = to_radians.call(lon2 - lon1)
|
89
|
+
|
90
|
+
a = (
|
91
|
+
Math.sin(dphi / 2) ** 2
|
92
|
+
+ Math.cos(phi1) * Math.cos(phi2) * Math.sin(dlambda / 2) ** 2
|
93
|
+
)
|
94
|
+
|
95
|
+
return 2 * radius * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
# Order list of places according to the distance to a reference coordinates.
|
100
|
+
#
|
101
|
+
# Note: this helper is compensating the bad results of the API. Results in the API
|
102
|
+
# are generally sorted, but lot of cases identified where the order is inconsistent
|
103
|
+
# (example: Montréal)
|
104
|
+
#
|
105
|
+
# Args:
|
106
|
+
# list_places: List of Place instances to be ordered
|
107
|
+
# gps_coord: Tuple with latitude and longitude in degrees for the reference point
|
108
|
+
#
|
109
|
+
# Returns:
|
110
|
+
# List of Place instances ordered by distance to the reference point (nearest
|
111
|
+
# first)
|
112
|
+
#
|
113
|
+
def sort_places_versus_distance_from_coordinates(
|
114
|
+
places,
|
115
|
+
gps_coord
|
116
|
+
)
|
117
|
+
places.sort_by {|place| haversine(place.latitude.to_i, place.longitude.to_i, gps_coord)}
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class MeteofranceApi::Place
|
2
|
+
|
3
|
+
# INSEE ID of the place.
|
4
|
+
attr_reader :insee
|
5
|
+
# name of the place.
|
6
|
+
attr_reader :name
|
7
|
+
# latitude of the place.
|
8
|
+
attr_reader :latitude
|
9
|
+
# longitude of the place.
|
10
|
+
attr_reader :longitude
|
11
|
+
# country code of the place.
|
12
|
+
attr_reader :country
|
13
|
+
# Province/Region of the place.
|
14
|
+
attr_reader :province
|
15
|
+
# Province/Region's numerical code of the place.
|
16
|
+
attr_reader :province_code
|
17
|
+
# postal code of the place.
|
18
|
+
attr_reader :postal_code
|
19
|
+
|
20
|
+
def initialize(data)
|
21
|
+
@insee = data["insee"]
|
22
|
+
@name = data["name"]
|
23
|
+
@latitude = data["lat"]
|
24
|
+
@longitude = data["lon"]
|
25
|
+
@country = data["country"]
|
26
|
+
@province = data["admin"]
|
27
|
+
@province_code = data["admin2"]
|
28
|
+
@postal_code = data["postCode"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
if country == "FR"
|
33
|
+
"#{name} - #{admin} (#{admin2}) - #{country}"
|
34
|
+
else
|
35
|
+
"#{name} - #{admin} - #{country}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class MeteofranceApi::Rain::Forecast
|
2
|
+
attr_reader :time
|
3
|
+
attr_reader :intensity
|
4
|
+
attr_reader :description
|
5
|
+
|
6
|
+
def intensity(data)
|
7
|
+
@time = Time.parse(data["time"])
|
8
|
+
@intensity = data["rain_intensity"]
|
9
|
+
@description = data["rain_intensity_description"]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class MeteofranceApi::Rain
|
2
|
+
# update time of the rain forecast.
|
3
|
+
attr_reader :updated_on
|
4
|
+
# position information of the rain forecast ([latitude, longitude]).
|
5
|
+
attr_reader :place_name
|
6
|
+
attr_reader :position
|
7
|
+
attr_reader :altitude
|
8
|
+
attr_reader :french_department
|
9
|
+
attr_reader :timezone
|
10
|
+
# the rain forecast.
|
11
|
+
attr_reader :forecasts
|
12
|
+
# quality of the rain forecast.
|
13
|
+
attr_reader :confidence
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
@updated_on = Time.at(data["updated_time"]).utc
|
17
|
+
@position = data["geometry"]["coordinates"]
|
18
|
+
@place_name = data["properties"]["name"]
|
19
|
+
@altitude = data["properties"]["altitude"]
|
20
|
+
@french_department = data["properties"]["french_department"]
|
21
|
+
@timezone = data["properties"]["timezone"]
|
22
|
+
@forecasts = data["properties"]["forecast"].map {|d| MeteofranceApi::Rain::Forecast.new(d)}
|
23
|
+
@confidence = data["properties"]["confidence"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_locale_timezone(time)
|
27
|
+
time.in_time_zone(position["timezone"])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Estimate the date of the next rain.
|
31
|
+
#
|
32
|
+
# Returns:
|
33
|
+
# A datetime instance representing the date estimation of the next rain within
|
34
|
+
# the next hour.
|
35
|
+
# If no rain is expected in the following hour nil is returned.
|
36
|
+
def next_rain_date(use_position_timezone)
|
37
|
+
# search first cadran with rain
|
38
|
+
next_rain = forecasts.find {|f| f.intensity > 1}
|
39
|
+
|
40
|
+
next_rain&.time
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require "meteofrance_api/models/rain/forecast"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Class to access the results of a `warning/currentPhenomenons` REST API request.
|
2
|
+
#
|
3
|
+
# For coastal department two bulletins are avalaible corresponding to two different
|
4
|
+
# domains.
|
5
|
+
class MeteofranceApi::Warning::Current
|
6
|
+
# update time of the phenomenoms.
|
7
|
+
attr_reader :update_time
|
8
|
+
# end of validty time of the phenomenoms.
|
9
|
+
attr_reader :end_validity_time
|
10
|
+
# domain ID of the phenomenons. Value is 'France' or a department number
|
11
|
+
attr_reader :domain_id
|
12
|
+
# List of Phenomenon
|
13
|
+
attr_reader :phenomenons
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
@update_time = Time.at(data["update_time"])
|
17
|
+
@end_validity_time = Time.at(data["end_validity_time"])
|
18
|
+
@domain_id = data["domain_id"]
|
19
|
+
@phenomenons = (data["phenomenons_max_colors"] || []).map {|i| Warning::Phenomenon.new(i)}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Merge the classical phenomenoms bulleting with the coastal one.
|
23
|
+
|
24
|
+
# Extend the phenomenomes_max_colors property with the content of the coastal
|
25
|
+
# weather alert bulletin.
|
26
|
+
|
27
|
+
# Args:
|
28
|
+
# coastal_phenomenoms: WarningCurrentPhenomenons instance corresponding to the
|
29
|
+
# coastal weather alert bulletin.
|
30
|
+
#
|
31
|
+
def merge_with_coastal_phenomenons!(coastal_phenomenons)
|
32
|
+
# TODO: Add consitency check
|
33
|
+
@phenomenons += coastal_phenomenoms.phenomenons
|
34
|
+
end
|
35
|
+
|
36
|
+
# Get the maximum level of alert of a given domain (class helper).
|
37
|
+
# Returns:
|
38
|
+
# An integer corresponding to the status code representing the maximum alert.
|
39
|
+
def get_domain_max_color
|
40
|
+
phenomenons.map {|item| item.max_color_id}.max
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require "meteofrance_api/models/warning/phenomenon"
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# This class allows to access the results of a `warning/full` API command.
|
2
|
+
#
|
3
|
+
# For a given domain we can access the maximum alert, a timelaps of the alert
|
4
|
+
# evolution for the next 24 hours, and a list of alerts.
|
5
|
+
#
|
6
|
+
# For coastal department two bulletins are avalaible corresponding to two different
|
7
|
+
# domains.
|
8
|
+
class MeteofranceApi::Warning::Full
|
9
|
+
# update time of the full bulletin.
|
10
|
+
attr_reader :update_time
|
11
|
+
# end of validty time of the full bulletin.
|
12
|
+
attr_reader :end_validity_time
|
13
|
+
# domain ID of the the full bulletin. Value is 'France' or a department number.
|
14
|
+
attr_reader :domain_id
|
15
|
+
# color max of the domain.
|
16
|
+
attr_reader :color_max
|
17
|
+
# timelaps of each phenomenom for the domain. A list of Hash corresponding to the schedule of each phenomenons in the next 24 hours
|
18
|
+
attr_reader :timelaps
|
19
|
+
# phenomenom list of the domain.
|
20
|
+
attr_reader :phenomenons
|
21
|
+
|
22
|
+
attr_reader :advices
|
23
|
+
attr_reader :consequences
|
24
|
+
attr_reader :max_count_items
|
25
|
+
attr_reader :comments
|
26
|
+
attr_reader :text
|
27
|
+
attr_reader :text_avalanche
|
28
|
+
|
29
|
+
def initialize(data)
|
30
|
+
@update_time = Time.at(data["update_time"])
|
31
|
+
@end_validity_time = Time.at(data["end_validity_time"])
|
32
|
+
@domain_id = data["domain_id"]
|
33
|
+
@color_max = data["color_max"]
|
34
|
+
@timelaps = data["timelaps"]
|
35
|
+
@phenomenons = (data["phenomenons_items"] || []).map {|i| MeteofranceApi::Warning::Phenomenon.new(i)}
|
36
|
+
|
37
|
+
@advices = data["advices"]
|
38
|
+
@consequences = data["consequences"]
|
39
|
+
@max_count_items = data["max_count_items"]
|
40
|
+
@comments = data["comments"]
|
41
|
+
@text = data["text"]
|
42
|
+
@text["text_avalanche"]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Merge the classical phenomenon bulletin with the coastal one.
|
46
|
+
|
47
|
+
# Extend the color_max, timelaps and phenomenons properties with the content
|
48
|
+
# of the coastal weather alert bulletin.
|
49
|
+
|
50
|
+
# Args:
|
51
|
+
# coastal_phenomenoms: Full instance corresponding to the coastal weather
|
52
|
+
# alert bulletin.
|
53
|
+
def merge_with_coastal_phenomenons!(coastal_phenomenons)
|
54
|
+
# TODO: Add consitency check
|
55
|
+
# TODO: Check if other data need to be merged
|
56
|
+
|
57
|
+
@color_max = [color_max, coastal_phenomenons.color_max].max
|
58
|
+
|
59
|
+
# Merge timelaps
|
60
|
+
@timelaps += coastal_phenomenons.timelaps
|
61
|
+
|
62
|
+
# Merge phenomenons
|
63
|
+
@phenomenons += coastal_phenomenons.phenomenons
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
require "meteofrance_api/models/warning/phenomenon"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class MeteofranceApi::Weather::DailyForecast
|
2
|
+
attr_reader :time
|
3
|
+
|
4
|
+
attr_reader :T_max
|
5
|
+
attr_reader :T_min
|
6
|
+
attr_reader :T_sea
|
7
|
+
|
8
|
+
attr_reader :relative_humidity_max
|
9
|
+
attr_reader :relative_humidity_min
|
10
|
+
|
11
|
+
attr_reader :daily_weather_description
|
12
|
+
attr_reader :daily_weather_icon
|
13
|
+
|
14
|
+
attr_reader :sunrise_time
|
15
|
+
attr_reader :sunset_time
|
16
|
+
|
17
|
+
attr_reader :total_precipitation_24h
|
18
|
+
|
19
|
+
attr_reader :uv_index
|
20
|
+
|
21
|
+
def initialize(data)
|
22
|
+
@time = Time.parse(data["time"])
|
23
|
+
@temperature_max = data["T_max"]
|
24
|
+
@temperature_min = data["T_min"]
|
25
|
+
@temperature_sea = data["T_sea"]
|
26
|
+
@relative_humidity_max = data["relative_humidity_max"]
|
27
|
+
@relative_humidity_min = data["relative_humidity_min"]
|
28
|
+
@weather_description = data["daily_weather_description"]
|
29
|
+
@weather_icon = data["daily_weather_icon"]
|
30
|
+
@sunrise_time = Time.parse(data["sunrise_time"])
|
31
|
+
@sunset_time = Time.parse(data["sunset_time"])
|
32
|
+
@total_precipitation_24h = data["total_precipitation_24h"]
|
33
|
+
@uv_index = data["uv_index"]
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class MeteofranceApi::Weather::Forecast
|
2
|
+
attr_reader :time
|
3
|
+
|
4
|
+
attr_reader :temperature
|
5
|
+
attr_reader :temperature_windchill
|
6
|
+
attr_reader :relative_humidity
|
7
|
+
|
8
|
+
attr_reader :total_cloud_cover
|
9
|
+
|
10
|
+
attr_reader :weather_description
|
11
|
+
attr_reader :weather_icon
|
12
|
+
|
13
|
+
attr_reader :wind_direction
|
14
|
+
attr_reader :wind_icon
|
15
|
+
attr_reader :wind_speed
|
16
|
+
attr_reader :wind_speed_gust
|
17
|
+
|
18
|
+
attr_reader :rain_1h
|
19
|
+
attr_reader :rain_3h
|
20
|
+
attr_reader :rain_6h
|
21
|
+
attr_reader :rain_12h
|
22
|
+
attr_reader :rain_24h
|
23
|
+
|
24
|
+
attr_reader :rain_snow_limit
|
25
|
+
|
26
|
+
attr_reader :snow_1h
|
27
|
+
attr_reader :snow_3h
|
28
|
+
attr_reader :snow_6h
|
29
|
+
attr_reader :snow_12h
|
30
|
+
attr_reader :snow_24h
|
31
|
+
|
32
|
+
attr_reader :P_sea
|
33
|
+
|
34
|
+
attr_reader :iso0
|
35
|
+
|
36
|
+
|
37
|
+
def initialize(data)
|
38
|
+
@time = Time.parse(data["time"])
|
39
|
+
@temperature = data["T"]
|
40
|
+
@temperature_windchill = data["T_windchill"]
|
41
|
+
@relative_humidity = data["relative_humidity"]
|
42
|
+
@P_sea = data["P_sea"]
|
43
|
+
@wind_speed = data["wind_speed"]
|
44
|
+
@wind_speed_gust = data["wind_speed_gust"]
|
45
|
+
@wind_direction = data["wind_direction"]
|
46
|
+
@wind_icon = data["wind_icon"]
|
47
|
+
@rain_1h = data["rain_1h"]
|
48
|
+
@rain_3h = data["rain_3h"]
|
49
|
+
@rain_6h = data["rain_6h"]
|
50
|
+
@rain_12h = data["rain_12h"]
|
51
|
+
@rain_24h = data["rain_24h"]
|
52
|
+
@snow_1h = data["snow_1h"]
|
53
|
+
@snow_3h = data["snow_3h"]
|
54
|
+
@snow_6h = data["snow_6h"]
|
55
|
+
@snow_12h = data["snow_12h"]
|
56
|
+
@snow_24h = data["snow_24h"]
|
57
|
+
@iso0 = data["iso0"]
|
58
|
+
@rain_snow_limit = data["rain_snow_limit"]
|
59
|
+
@total_cloud_cover = data["total_cloud_cover"]
|
60
|
+
@weather_icon = data["weather_icon"]
|
61
|
+
@weather_description = data["weather_description"]
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class MeteofranceApi::Weather::HazardForecast
|
2
|
+
attr_reader :rain_hazard_3h
|
3
|
+
attr_reader :rain_hazard_6h
|
4
|
+
attr_reader :snow_hazard_3h
|
5
|
+
attr_reader :snow_hazard_6h
|
6
|
+
attr_reader :freezing_hazard
|
7
|
+
attr_reader :storm_hazard
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@rain_hazard_3h = data["rain_hazard_3h"]
|
11
|
+
@rain_hazard_6h = data["rain_hazard_6h"]
|
12
|
+
@snow_hazard_3h = data["snow_hazard_3h"]
|
13
|
+
@snow_hazard_6h = data["snow_hazard_6h"]
|
14
|
+
@freezing_hazard = data["freezing_hazard"]
|
15
|
+
@storm_hazard = data["storm_hazard"]
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
class MeteofranceApi::Weather
|
2
|
+
# update Time of the forecast.
|
3
|
+
attr_reader :updated_at
|
4
|
+
|
5
|
+
# position information of the forecast. An Array [longitude, latitude].
|
6
|
+
attr_reader :position
|
7
|
+
# Altitude of the position (in meter)
|
8
|
+
attr_reader :altitude
|
9
|
+
# Name of the position
|
10
|
+
attr_reader :name
|
11
|
+
# Country of the position
|
12
|
+
attr_reader :country
|
13
|
+
attr_reader :timezone
|
14
|
+
attr_reader :insee
|
15
|
+
attr_reader :french_department_code
|
16
|
+
|
17
|
+
attr_reader :bulletin_cote
|
18
|
+
|
19
|
+
# daily forecast for the following days.
|
20
|
+
# A list of Hash to describe the daily forecast for the next 15 days.
|
21
|
+
attr_reader :daily_forecasts
|
22
|
+
# hourly forecast.
|
23
|
+
# A list of Hash to describe the hourly forecast for the next day
|
24
|
+
attr_reader :forecasts
|
25
|
+
# wheather event forecast.
|
26
|
+
# A list of object to describe the event probability forecast (rain, snow, freezing) for next 10 days.
|
27
|
+
attr_reader :hazard_forecasts
|
28
|
+
|
29
|
+
|
30
|
+
def initialize(data)
|
31
|
+
@updated_on = Time.at(data["updated_time"])
|
32
|
+
|
33
|
+
@position = data["geometry"]["coordinates"]
|
34
|
+
@altitude = data["properties"]["altitude"]
|
35
|
+
@name = data["properties"]["name"]
|
36
|
+
@country = data["properties"]["country"]
|
37
|
+
@timezone = data["properties"]["timezone"]
|
38
|
+
@insee = data["properties"]["insee"]&.to_i
|
39
|
+
@french_department_code = data["properties"]["french_department_code"]&.to_i
|
40
|
+
|
41
|
+
@bulletin_cote = data["properties"]["bulletin_cote"]
|
42
|
+
|
43
|
+
@daily_forecasts = data.dig("daily_forecast", []).map {|d| MeteofranceApi::Weather::DailyForecast.new(d)}
|
44
|
+
@forecasts = data.dig("forecast", []).map {|d| MeteofranceApi::Weather::Forecast.new(d)}
|
45
|
+
@hazard_forecasts = data.dig("probability_forecast", []).map {|d| MeteofranceApi::Weather::HazardForecast.new(d)}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the forecast for today. A Hash corresponding to the daily forecast for the current day
|
49
|
+
def today_forecast
|
50
|
+
self.daily_forecasts.first
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return the nearest hourly forecast. A Hash corresponding to the nearest hourly forecast.
|
54
|
+
def nearest_forecast
|
55
|
+
# get timestamp for current time
|
56
|
+
now_timestamp = Time.now
|
57
|
+
|
58
|
+
# sort list of forecast by distance between current timestamp and
|
59
|
+
# forecast timestamp
|
60
|
+
sorted_forecasts = self.forecasts.sort_by {|f| (f.time - now_timestamp).abs }
|
61
|
+
|
62
|
+
sorted_forecasts.first
|
63
|
+
end
|
64
|
+
|
65
|
+
# Return the forecast of the current hour. A Hash corresponding to the hourly forecast for the current hour
|
66
|
+
def current_forecast
|
67
|
+
# Get the timestamp for the current hour.
|
68
|
+
current_hour = Time.now.utc.to_a
|
69
|
+
current_hour[0] = 0
|
70
|
+
current_hour[1] = 0
|
71
|
+
current_hour_timestamp = Time.utc.new(*current_hour).to_i
|
72
|
+
|
73
|
+
# create a Hash using timestamp as keys
|
74
|
+
forecasts_by_datetime = self.forecasts.map {|f| [f.time.to_i, f]}.to_h
|
75
|
+
|
76
|
+
# Return the forecast corresponding to the timestamp of the current hour if
|
77
|
+
# exists.
|
78
|
+
forecasts_by_datetime[current_hour_timestamp]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Convert time in the forecast location timezone
|
82
|
+
def to_locale_time(time)
|
83
|
+
time.in_time_zone(timezone)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
require "meteofrance_api/models/weather/forecast"
|
88
|
+
require "meteofrance_api/models/weather/daily_forecast"
|
89
|
+
require "meteofrance_api/models/weather/hazard_forecast"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module MeteofranceApi
|
2
|
+
class Error < StandardError; end
|
3
|
+
# Your code goes here...
|
4
|
+
end
|
5
|
+
|
6
|
+
require "meteofrance_api/client"
|
7
|
+
require "meteofrance_api/constants"
|
8
|
+
require "meteofrance_api/helpers"
|
9
|
+
require "meteofrance_api/models"
|
10
|
+
require "meteofrance_api/version"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "meteofrance_api/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "meteofrance-api"
|
8
|
+
spec.version = MeteofranceApi::VERSION
|
9
|
+
spec.authors = ["Thomas Kuntz"]
|
10
|
+
spec.email = ["thomaskuntz67@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Wrapper around meteofrance.com (french weather website) API.}
|
13
|
+
spec.description = %q{Wrapper around meteofrance.com non-public API. Based on Python lib https://github.com/hacf-fr/meteofrance-api}
|
14
|
+
spec.homepage = "https://github.com/Haerezis/meteofrance-api"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
#spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
|
22
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
23
|
+
spec.metadata["source_code_uri"] = "https://github.com/Haerezis/meteofrance-api"
|
24
|
+
spec.metadata["changelog_uri"] = "https://github.com/Haerezis/meteofrance-api/blob/main/CHANGELOG.md"
|
25
|
+
else
|
26
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
27
|
+
"public gem pushes."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
32
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
34
|
+
end
|
35
|
+
spec.bindir = "exe"
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
|
+
spec.require_paths = ["lib"]
|
38
|
+
|
39
|
+
spec.add_dependency "faraday", "~> 2.3.0"
|
40
|
+
|
41
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
42
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
43
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
44
|
+
end
|