env-canada 0.6.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
env_canada/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
- from .ec_aqhi import *
2
- from .ec_historical import *
3
- from .ec_hydro import *
4
- from .ec_radar import *
5
- from .ec_weather import *
1
+ from .ec_aqhi import ECAirQuality
2
+ from .ec_historical import ECHistorical, ECHistoricalRange
3
+ from .ec_hydro import ECHydro
4
+ from .ec_radar import ECRadar
5
+ from .ec_weather import ECWeather
env_canada/constants.py CHANGED
@@ -1 +1 @@
1
- USER_AGENT = "env_canada/0.6.1"
1
+ USER_AGENT = "env_canada/0.7.0"
env_canada/ec_radar.py CHANGED
@@ -1,17 +1,18 @@
1
- from aiohttp.client_exceptions import ClientConnectorError
2
1
  import asyncio
3
2
  import datetime
4
- from io import BytesIO
5
3
  import logging
6
4
  import math
7
5
  import os
8
- from PIL import Image, ImageDraw, ImageFont
6
+ from io import BytesIO
9
7
 
10
- from .ec_cache import CacheClientSession as ClientSession
11
8
  import dateutil.parser
12
9
  import defusedxml.ElementTree as et
13
10
  import imageio.v2 as imageio
14
11
  import voluptuous as vol
12
+ from aiohttp.client_exceptions import ClientConnectorError
13
+ from PIL import Image, ImageDraw, ImageFont
14
+
15
+ from .ec_cache import CacheClientSession as ClientSession
15
16
 
16
17
  ATTRIBUTION = {
17
18
  "english": "Data provided by Environment Canada",
@@ -156,16 +157,12 @@ class ECRadar(object):
156
157
  # Get overlay parameters
157
158
 
158
159
  self.show_legend = kwargs["legend"]
159
- if self.show_legend:
160
- self.legend_layer = None
161
- self.legend_image = None
162
- self.legend_position = None
160
+ self.legend_layer = None
161
+ self.legend_image = None
162
+ self.legend_position = None
163
163
 
164
164
  self.show_timestamp = kwargs["timestamp"]
165
- if self.show_timestamp:
166
- self.font = ImageFont.load(
167
- os.path.join(os.path.dirname(__file__), "10x20.pil")
168
- )
165
+ self.font = None
169
166
 
170
167
  @property
171
168
  def precip_type(self):
@@ -197,22 +194,20 @@ class ECRadar(object):
197
194
  async with ClientSession(raise_for_status=True) as session:
198
195
  response = await session.get(url=basemap_url, params=basemap_params)
199
196
  base_bytes = await response.read()
200
- self.map_image = Image.open(BytesIO(base_bytes)).convert("RGBA")
201
197
 
202
198
  except ClientConnectorError as e:
203
199
  logging.warning("NRCan base map could not be retrieved: %s" % e)
204
-
205
200
  try:
206
201
  async with ClientSession(raise_for_status=True) as session:
207
202
  response = await session.get(
208
203
  url=backup_map_url, params=basemap_params
209
204
  )
210
205
  base_bytes = await response.read()
211
- self.map_image = Image.open(BytesIO(base_bytes)).convert("RGBA")
212
206
  except ClientConnectorError:
213
207
  logging.warning("Mapbox base map could not be retrieved")
208
+ return None
214
209
 
215
- return
210
+ return base_bytes
216
211
 
217
212
  async def _get_legend(self):
218
213
  """Fetch legend image."""
@@ -221,13 +216,13 @@ class ECRadar(object):
221
216
  layer=precip_layers[self.layer_key], style=legend_style[self.layer_key]
222
217
  )
223
218
  )
224
- async with ClientSession(raise_for_status=True) as session:
225
- response = await session.get(url=geomet_url, params=legend_params)
226
- legend_bytes = await response.read()
227
- self.legend_image = Image.open(BytesIO(legend_bytes)).convert("RGB")
228
- legend_width = self.legend_image.size[0]
229
- self.legend_position = (self.width - legend_width, 0)
230
- self.legend_layer = self.layer_key
219
+ try:
220
+ async with ClientSession(raise_for_status=True) as session:
221
+ response = await session.get(url=geomet_url, params=legend_params)
222
+ return await response.read()
223
+ except ClientConnectorError:
224
+ logging.warning("Legend could not be retrieved")
225
+ return None
231
226
 
232
227
  async def _get_dimensions(self):
233
228
  """Get time range of available data."""
@@ -255,54 +250,73 @@ class ECRadar(object):
255
250
  async def _combine_layers(self, radar_bytes, frame_time):
256
251
  """Add radar overlay to base layer and add timestamp."""
257
252
 
258
- radar = Image.open(BytesIO(radar_bytes)).convert("RGBA")
259
-
260
- # Add transparency to radar
261
-
262
- if self.radar_opacity < 100:
263
- alpha = round((self.radar_opacity / 100) * 255)
264
- radar_copy = radar.copy()
265
- radar_copy.putalpha(alpha)
266
- radar.paste(radar_copy, radar)
267
-
268
- # Overlay radar on basemap
269
-
253
+ base_bytes = None
270
254
  if not self.map_image:
271
- await self._get_basemap()
272
- if self.map_image:
273
- frame = Image.alpha_composite(self.map_image, radar)
274
- else:
275
- frame = radar
276
-
277
- # Add legend
255
+ base_bytes = await self._get_basemap()
278
256
 
257
+ legend_bytes = None
279
258
  if self.show_legend:
280
259
  if not self.legend_image or self.legend_layer != self.layer_key:
281
- await self._get_legend()
282
- frame.paste(self.legend_image, self.legend_position)
260
+ legend_bytes = await self._get_legend()
283
261
 
284
- # Add timestamp
262
+ # All the synchronous PIL stuff here
263
+ def _create_image():
264
+ radar = Image.open(BytesIO(radar_bytes)).convert("RGBA")
285
265
 
286
- if self.show_timestamp:
287
- timestamp = (
288
- timestamp_label[self.layer_key][self.language]
289
- + " @ "
290
- + frame_time.astimezone().strftime("%H:%M")
291
- )
292
- text_box = Image.new("RGBA", self.font.getbbox(timestamp)[2:], "white")
293
- box_draw = ImageDraw.Draw(text_box)
294
- box_draw.text(xy=(0, 0), text=timestamp, fill=(0, 0, 0), font=self.font)
295
- double_box = text_box.resize((text_box.width * 2, text_box.height * 2))
296
- frame.paste(double_box)
297
- frame = frame.quantize()
298
-
299
- # Return frame as PNG bytes
300
-
301
- img_byte_arr = BytesIO()
302
- frame.save(img_byte_arr, format="PNG")
303
- frame_bytes = img_byte_arr.getvalue()
266
+ if base_bytes:
267
+ self.map_image = Image.open(BytesIO(base_bytes)).convert("RGBA")
304
268
 
305
- return frame_bytes
269
+ if legend_bytes:
270
+ self.legend_image = Image.open(BytesIO(legend_bytes)).convert("RGB")
271
+ legend_width = self.legend_image.size[0]
272
+ self.legend_position = (self.width - legend_width, 0)
273
+ self.legend_layer = self.layer_key
274
+
275
+ # Add transparency to radar
276
+ if self.radar_opacity < 100:
277
+ alpha = round((self.radar_opacity / 100) * 255)
278
+ radar_copy = radar.copy()
279
+ radar_copy.putalpha(alpha)
280
+ radar.paste(radar_copy, radar)
281
+
282
+ if self.show_timestamp and not self.font:
283
+ self.font = ImageFont.load(
284
+ os.path.join(os.path.dirname(__file__), "10x20.pil")
285
+ )
286
+
287
+ # Overlay radar on basemap
288
+ if self.map_image:
289
+ frame = Image.alpha_composite(self.map_image, radar)
290
+ else:
291
+ frame = radar
292
+
293
+ # Add legend
294
+ if self.show_legend and self.legend_image:
295
+ frame.paste(self.legend_image, self.legend_position)
296
+
297
+ # Add timestamp
298
+ if self.show_timestamp and self.font:
299
+ timestamp = (
300
+ timestamp_label[self.layer_key][self.language]
301
+ + " @ "
302
+ + frame_time.astimezone().strftime("%H:%M")
303
+ )
304
+ text_box = Image.new("RGBA", self.font.getbbox(timestamp)[2:], "white")
305
+ box_draw = ImageDraw.Draw(text_box)
306
+ box_draw.text(xy=(0, 0), text=timestamp, fill=(0, 0, 0), font=self.font)
307
+ double_box = text_box.resize((text_box.width * 2, text_box.height * 2))
308
+ frame.paste(double_box)
309
+ frame = frame.quantize()
310
+
311
+ # Return frame as PNG bytes
312
+ img_byte_arr = BytesIO()
313
+ frame.save(img_byte_arr, format="PNG")
314
+ frame_bytes = img_byte_arr.getvalue()
315
+
316
+ return frame_bytes
317
+
318
+ # Since PIL is synchronous, run it all in another thread
319
+ return await asyncio.get_event_loop().run_in_executor(None, _create_image)
306
320
 
307
321
  async def _get_radar_image(self, session, frame_time):
308
322
  params = dict(
@@ -331,6 +345,17 @@ class ECRadar(object):
331
345
  async def get_loop(self, fps=5):
332
346
  """Build an animated GIF of recent radar images."""
333
347
 
348
+ def build_image():
349
+ gif_frames = [imageio.imread(f, mode="RGBA") for f in frames]
350
+ gif_bytes = imageio.mimwrite(
351
+ imageio.RETURN_BYTES,
352
+ gif_frames,
353
+ format="GIF",
354
+ duration=duration,
355
+ subrectangles=True,
356
+ )
357
+ return gif_bytes
358
+
334
359
  """Build list of frame timestamps."""
335
360
  start, end = await self._get_dimensions()
336
361
  frame_times = [start]
@@ -361,12 +386,6 @@ class ECRadar(object):
361
386
  """Assemble animated GIF."""
362
387
  duration = 1000 / fps
363
388
 
364
- gif_frames = [imageio.imread(f, mode="RGBA") for f in frames]
365
- gif_bytes = imageio.mimwrite(
366
- imageio.RETURN_BYTES,
367
- gif_frames,
368
- format="GIF",
369
- duration=duration,
370
- subrectangles=True,
371
- )
389
+ loop = asyncio.get_running_loop()
390
+ gif_bytes = await loop.run_in_executor(None, build_image)
372
391
  return gif_bytes
env_canada/ec_weather.py CHANGED
@@ -137,24 +137,6 @@ conditions_meta = {
137
137
  "english": "Icon Code",
138
138
  "french": "Code icône",
139
139
  },
140
- "high_temp_yesterday": {
141
- "xpath": './yesterdayConditions/temperature[@class="high"]',
142
- "type": "float",
143
- "english": "High Temperature Yesterday",
144
- "french": "Haute température d'hier",
145
- },
146
- "low_temp_yesterday": {
147
- "xpath": './yesterdayConditions/temperature[@class="low"]',
148
- "type": "float",
149
- "english": "Low Temperature Yesterday",
150
- "french": "Basse température d'hier",
151
- },
152
- "precip_yesterday": {
153
- "xpath": "./yesterdayConditions/precip",
154
- "type": "float",
155
- "english": "Precipitation Yesterday",
156
- "french": "Précipitation d'hier",
157
- },
158
140
  "normal_high": {
159
141
  "xpath": './forecastGroup/regionalNormals/temperature[@class="high"]',
160
142
  "type": "int",
@@ -183,7 +165,7 @@ conditions_meta = {
183
165
  "xpath": "./currentConditions/dateTime/timeStamp",
184
166
  "type": "timestamp",
185
167
  "english": "Observation Time",
186
- "french": "Temps d'observation"
168
+ "french": "Temps d'observation",
187
169
  },
188
170
  }
189
171
 
@@ -278,7 +260,6 @@ def closest_site(site_list, lat, lon):
278
260
 
279
261
 
280
262
  class ECWeather(object):
281
-
282
263
  """Get weather data from Environment Canada."""
283
264
 
284
265
  def __init__(self, **kwargs):
@@ -432,7 +413,9 @@ class ECWeather(object):
432
413
  self.conditions[c].update(get_condition(meta))
433
414
 
434
415
  # Update station metadata
435
- self.metadata["station"] = weather_tree.find("./currentConditions/station").text
416
+ self.metadata["station"] = weather_tree.find(
417
+ "./currentConditions/station"
418
+ ).text
436
419
 
437
420
  # Update text summary
438
421
  period = get_condition(summary_meta["forecast_period"])["value"]
@@ -494,7 +477,9 @@ class ECWeather(object):
494
477
  "temperature": int(f.findtext("./temperature") or 0),
495
478
  "icon_code": f.findtext("./iconCode"),
496
479
  "precip_probability": int(f.findtext("./lop") or "0"),
497
- "wind_speed": int(wind_speed_text if wind_speed_text.isnumeric() else 0),
480
+ "wind_speed": int(
481
+ wind_speed_text if wind_speed_text.isnumeric() else 0
482
+ ),
498
483
  "wind_direction": f.findtext("./wind/direction"),
499
484
  }
500
485
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
- Name: env-canada
3
- Version: 0.6.1
2
+ Name: env_canada
3
+ Version: 0.7.0
4
4
  Summary: A package to access meteorological data from Environment Canada
5
5
  Home-page: https://github.com/michaeldavie/env_canada
6
6
  Author: Michael Davie
@@ -25,7 +25,7 @@ Requires-Dist: voluptuous
25
25
  # Environment Canada (env_canada)
26
26
 
27
27
  [![PyPI version](https://badge.fury.io/py/env-canada.svg)](https://badge.fury.io/py/env-canada)
28
- [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.6.1?utm_source=badge)
28
+ [![Snyk rating](https://snyk-widget.herokuapp.com/badge/pip/env-canada/badge.svg)](https://snyk.io/vuln/pip:env-canada@0.7.0?utm_source=badge)
29
29
 
30
30
  This package provides access to various data sources published by [Environment and Climate Change Canada](https://www.canada.ca/en/environment-climate-change.html).
31
31
 
@@ -116,7 +116,8 @@ Once updated asynchronously, historical weather data is contained with the `stat
116
116
  ```python
117
117
  import asyncio
118
118
 
119
- from env_canada import ECHistorical, get_historical_stations
119
+ from env_canada import ECHistorical
120
+ from env_canada.ec_historical import get_historical_stations
120
121
 
121
122
  # search for stations, response contains station_ids
122
123
  coordinates = [53.916944, -122.749444] # [lat, long]
@@ -165,7 +166,8 @@ For example :
165
166
  ```python
166
167
  import pandas as pd
167
168
  import asyncio
168
- from env_canada import ECHistoricalRange, get_historical_stations
169
+ from env_canada import ECHistoricalRange
170
+ from env_canada.ec_historical import get_historical_stations
169
171
  from datetime import datetime
170
172
 
171
173
  coordinates = ['48.508333', '-68.467667']
@@ -205,4 +207,4 @@ of `datetime(2022, 7, 1, 12, 12)`
205
207
 
206
208
  # License
207
209
 
208
- The code is available under terms of [MIT License](LICENSE.md)
210
+ The code is available under terms of [MIT License](https://github.com/michaeldavie/env_canada/tree/master/LICENSE.md)
@@ -0,0 +1,16 @@
1
+ env_canada/10x20.pbm,sha256=ClKTs2WUmhUhTHAQzPuGwPTICGVBzCvos5l-vHRBE5M,2463
2
+ env_canada/10x20.pil,sha256=Oki6-TD7b0xFtfm6vxCKsmpEpsZ5Jaia_0v_aDz8bfE,5143
3
+ env_canada/__init__.py,sha256=wEx1BCwVUH__GoosSlhNMHuUKCKNZAvv5uuSa5ZWq_g,187
4
+ env_canada/constants.py,sha256=xUcfT4fpHqhAnvC1GZ_gpYYrJYozfitwFiOBKN65xXg,32
5
+ env_canada/ec_aqhi.py,sha256=kJQ8xEgFnujGMYdxRXpoEK17B5e-ya-Y7rK0vLo_-w0,7768
6
+ env_canada/ec_cache.py,sha256=qoFxmO-kOBT8jhgPeNWtVBRmguXcARIIOI54OaDh-20,1171
7
+ env_canada/ec_exc.py,sha256=SBJwzmLf94lTx7KYVLfQYrMXYNYUoIxeVXc-BLkuXoE,67
8
+ env_canada/ec_historical.py,sha256=slHaFwsoyW16uCVtE3_-IF3_BFhFD4IuWl7rpIRsCm4,15901
9
+ env_canada/ec_hydro.py,sha256=LBsWreTlaTKec6ObjI0ih8-zOKBNjD02oiXKTyUa1EQ,4898
10
+ env_canada/ec_radar.py,sha256=gcLa2z5T_CkrY-NLEJRqaLDHODJRcO5unW5MGxjKxF8,13115
11
+ env_canada/ec_weather.py,sha256=M7nPeZIKLirRIcCENB8z2B8aBDZHrjltzMYPgRz9lz0,16789
12
+ env_canada-0.7.0.dist-info/LICENSE,sha256=c037dTHQWAgRgDqZNN-5d-CZvcteSYN37u39SNklO0I,1072
13
+ env_canada-0.7.0.dist-info/METADATA,sha256=IueyWyN-i8vtUD8G4p8kMumCncfOUyPUrUS_TMEfw7w,10707
14
+ env_canada-0.7.0.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
15
+ env_canada-0.7.0.dist-info/top_level.txt,sha256=fw7Pcl9ULBXYvqnAdyBdmwPXW8GSRFmhO0sLZWVfOCc,11
16
+ env_canada-0.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (70.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +0,0 @@
1
- env_canada/10x20.pbm,sha256=ClKTs2WUmhUhTHAQzPuGwPTICGVBzCvos5l-vHRBE5M,2463
2
- env_canada/10x20.pil,sha256=Oki6-TD7b0xFtfm6vxCKsmpEpsZ5Jaia_0v_aDz8bfE,5143
3
- env_canada/__init__.py,sha256=wQpbGVmwROrIR06RWinatD-qX7fbJvxBFOd6U0xoek4,126
4
- env_canada/constants.py,sha256=IsaGrjrg-YGadCHWyT0ZkBjbTDaF14W1gBPGQylncUY,32
5
- env_canada/ec_aqhi.py,sha256=kJQ8xEgFnujGMYdxRXpoEK17B5e-ya-Y7rK0vLo_-w0,7768
6
- env_canada/ec_cache.py,sha256=qoFxmO-kOBT8jhgPeNWtVBRmguXcARIIOI54OaDh-20,1171
7
- env_canada/ec_exc.py,sha256=SBJwzmLf94lTx7KYVLfQYrMXYNYUoIxeVXc-BLkuXoE,67
8
- env_canada/ec_historical.py,sha256=slHaFwsoyW16uCVtE3_-IF3_BFhFD4IuWl7rpIRsCm4,15901
9
- env_canada/ec_hydro.py,sha256=LBsWreTlaTKec6ObjI0ih8-zOKBNjD02oiXKTyUa1EQ,4898
10
- env_canada/ec_radar.py,sha256=WaJHtNnuQa3XoidvymOjJxNHqRLORTcy6_mczjIwd_A,12217
11
- env_canada/ec_weather.py,sha256=uBY6qd0-hVyZDhqPcpipfMDImXpJGiNIzMOjIzqNBfo,17358
12
- env_canada-0.6.1.dist-info/LICENSE,sha256=c037dTHQWAgRgDqZNN-5d-CZvcteSYN37u39SNklO0I,1072
13
- env_canada-0.6.1.dist-info/METADATA,sha256=yy2_dg0TKwYxaIwyc1UfBHiuIQpt3hyETSkzvnRPGU0,10580
14
- env_canada-0.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
- env_canada-0.6.1.dist-info/top_level.txt,sha256=fw7Pcl9ULBXYvqnAdyBdmwPXW8GSRFmhO0sLZWVfOCc,11
16
- env_canada-0.6.1.dist-info/RECORD,,