pvlib 0.12.0__py3-none-any.whl → 0.13.0a1__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.
pvlib/iotools/pvgis.py CHANGED
@@ -21,7 +21,7 @@ import requests
21
21
  import numpy as np
22
22
  import pandas as pd
23
23
  import pytz
24
- from pvlib.iotools import read_epw, parse_epw
24
+ from pvlib.iotools import read_epw
25
25
 
26
26
  URL = 'https://re.jrc.ec.europa.eu/api/'
27
27
 
@@ -57,6 +57,12 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
57
57
 
58
58
  PVGIS data is freely available at [1]_.
59
59
 
60
+ .. versionchanged:: 0.13.0
61
+ The function now returns two items ``(data,meta)``. Previous
62
+ versions of this function returned three elements
63
+ ``(data,inputs,meta)``. The ``inputs`` dictionary is now included in
64
+ ``meta``, which has changed structure to accommodate it.
65
+
60
66
  Parameters
61
67
  ----------
62
68
  latitude: float
@@ -130,8 +136,6 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
130
136
  -------
131
137
  data : pandas.DataFrame
132
138
  Time-series of hourly data, see Notes for fields
133
- inputs : dict
134
- Dictionary of the request input parameters
135
139
  metadata : dict
136
140
  Dictionary containing metadata
137
141
 
@@ -189,7 +193,7 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
189
193
  Examples
190
194
  --------
191
195
  >>> # Retrieve two years of irradiance data from PVGIS:
192
- >>> data, meta, inputs = pvlib.iotools.get_pvgis_hourly( # doctest: +SKIP
196
+ >>> data, meta = pvlib.iotools.get_pvgis_hourly( # doctest: +SKIP
193
197
  >>> latitude=45, longitude=8, start=2015, end=2016) # doctest: +SKIP
194
198
 
195
199
  References
@@ -241,28 +245,33 @@ def get_pvgis_hourly(latitude, longitude, start=None, end=None,
241
245
 
242
246
 
243
247
  def _parse_pvgis_hourly_json(src, map_variables):
244
- inputs = src['inputs']
245
- metadata = src['meta']
248
+ metadata = src['meta'].copy()
249
+ # Override the "inputs" in metadata
250
+ metadata['inputs'] = src['inputs']
251
+ # Re-add the inputs in metadata one-layer down
252
+ metadata['inputs']['descriptions'] = src['meta']['inputs']
246
253
  data = pd.DataFrame(src['outputs']['hourly'])
247
254
  data.index = pd.to_datetime(data['time'], format='%Y%m%d:%H%M', utc=True)
248
255
  data = data.drop('time', axis=1)
249
256
  data = data.astype(dtype={'Int': 'int'}) # The 'Int' column to be integer
250
257
  if map_variables:
251
258
  data = data.rename(columns=VARIABLE_MAP)
252
- return data, inputs, metadata
259
+ return data, metadata
253
260
 
254
261
 
255
262
  def _parse_pvgis_hourly_csv(src, map_variables):
256
263
  # The first 4 rows are latitude, longitude, elevation, radiation database
257
- inputs = {}
264
+ metadata = {'inputs': {}, 'descriptions': {}}
265
+ # 'location' metadata
258
266
  # 'Latitude (decimal degrees): 45.000\r\n'
259
- inputs['latitude'] = float(src.readline().split(':')[1])
267
+ metadata['inputs']['latitude'] = float(src.readline().split(':')[1])
260
268
  # 'Longitude (decimal degrees): 8.000\r\n'
261
- inputs['longitude'] = float(src.readline().split(':')[1])
269
+ metadata['inputs']['longitude'] = float(src.readline().split(':')[1])
262
270
  # Elevation (m): 1389.0\r\n
263
- inputs['elevation'] = float(src.readline().split(':')[1])
271
+ metadata['inputs']['elevation'] = float(src.readline().split(':')[1])
264
272
  # 'Radiation database: \tPVGIS-SARAH\r\n'
265
- inputs['radiation_database'] = src.readline().split(':')[1].strip()
273
+ metadata['inputs']['radiation_database'] = \
274
+ src.readline().split(':')[1].strip()
266
275
  # Parse through the remaining metadata section (the number of lines for
267
276
  # this section depends on the requested parameters)
268
277
  while True:
@@ -273,7 +282,7 @@ def _parse_pvgis_hourly_csv(src, map_variables):
273
282
  break
274
283
  # Only retrieve metadata from non-empty lines
275
284
  elif line.strip() != '':
276
- inputs[line.split(':')[0]] = line.split(':')[1].strip()
285
+ metadata['inputs'][line.split(':')[0]] = line.split(':')[1].strip()
277
286
  elif line == '': # If end of file is reached
278
287
  raise ValueError('No data section was detected. File has probably '
279
288
  'been modified since being downloaded from PVGIS')
@@ -295,16 +304,23 @@ def _parse_pvgis_hourly_csv(src, map_variables):
295
304
  # integer. It is necessary to convert to float, before converting to int
296
305
  data = data.astype(float).astype(dtype={'Int': 'int'})
297
306
  # Generate metadata dictionary containing description of parameters
298
- metadata = {}
307
+ metadata['descriptions'] = {}
299
308
  for line in src.readlines():
300
309
  if ':' in line:
301
- metadata[line.split(':')[0]] = line.split(':')[1].strip()
302
- return data, inputs, metadata
310
+ metadata['descriptions'][line.split(':')[0]] = \
311
+ line.split(':')[1].strip()
312
+ return data, metadata
303
313
 
304
314
 
305
315
  def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
306
316
  """Read a PVGIS hourly file.
307
317
 
318
+ .. versionchanged:: 0.13.0
319
+ The function now returns two items ``(data,meta)``. Previous
320
+ versions of this function returned three elements
321
+ ``(data,inputs,meta)``. The ``inputs`` dictionary is now included in
322
+ ``meta``, which has changed structure to accommodate it.
323
+
308
324
  Parameters
309
325
  ----------
310
326
  filename : str, pathlib.Path, or file-like buffer
@@ -323,8 +339,6 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
323
339
  -------
324
340
  data : pandas.DataFrame
325
341
  the time series data
326
- inputs : dict
327
- the inputs
328
342
  metadata : dict
329
343
  metadata
330
344
 
@@ -373,7 +387,7 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
373
387
  return _parse_pvgis_hourly_json(src, map_variables=map_variables)
374
388
 
375
389
  # CSV: use _parse_pvgis_hourly_csv()
376
- if outputformat == 'csv':
390
+ elif outputformat == 'csv':
377
391
  try:
378
392
  pvgis_data = _parse_pvgis_hourly_csv(
379
393
  filename, map_variables=map_variables)
@@ -383,11 +397,12 @@ def read_pvgis_hourly(filename, pvgis_format=None, map_variables=True):
383
397
  fbuf, map_variables=map_variables)
384
398
  return pvgis_data
385
399
 
386
- # raise exception if pvgis format isn't in ['csv', 'json']
387
- err_msg = (
388
- "pvgis format '{:s}' was unknown, must be either 'json' or 'csv'")\
389
- .format(outputformat)
390
- raise ValueError(err_msg)
400
+ else:
401
+ # raise exception if pvgis format isn't in ['csv', 'json']
402
+ err_msg = (
403
+ "pvgis format '{:s}' was unknown, must be either 'json' or 'csv'")\
404
+ .format(outputformat)
405
+ raise ValueError(err_msg)
391
406
 
392
407
 
393
408
  def _coerce_and_roll_tmy(tmy_data, tz, year):
@@ -410,18 +425,28 @@ def _coerce_and_roll_tmy(tmy_data, tz, year):
410
425
  np.roll(tmy_data, tz, axis=0),
411
426
  columns=tmy_data.columns,
412
427
  index=new_index)
428
+ # GH 2399
429
+ new_tmy_data = \
430
+ new_tmy_data.astype(dtype=dict(zip(tmy_data.columns, tmy_data.dtypes)))
413
431
  return new_tmy_data
414
432
 
415
433
 
416
434
  def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
417
435
  userhorizon=None, startyear=None, endyear=None,
418
436
  map_variables=True, url=URL, timeout=30,
419
- roll_utc_offset=None, coerce_year=None):
437
+ roll_utc_offset=None, coerce_year=1990):
420
438
  """
421
439
  Get TMY data from PVGIS.
422
440
 
423
441
  For more information see the PVGIS [1]_ TMY tool documentation [2]_.
424
442
 
443
+ .. versionchanged:: 0.13.0
444
+ The function now returns two items ``(data,meta)``. Previous
445
+ versions of this function returned four elements
446
+ ``(data,months_selected,inputs,meta)``. The ``inputs`` dictionary
447
+ and ``months_selected`` are now included in ``meta``, which has
448
+ changed structure to accommodate it.
449
+
425
450
  Parameters
426
451
  ----------
427
452
  latitude : float
@@ -429,7 +454,7 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
429
454
  longitude : float
430
455
  Longitude in degrees east
431
456
  outputformat : str, default 'json'
432
- Must be in ``['csv', 'basic', 'epw', 'json']``. See PVGIS TMY tool
457
+ Must be in ``['csv', 'epw', 'json']``. See PVGIS TMY tool
433
458
  documentation [2]_ for more info.
434
459
  usehorizon : bool, default True
435
460
  include effects of horizon
@@ -452,20 +477,18 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
452
477
  Use to specify a time zone other than the default UTC zero and roll
453
478
  dataframe by ``roll_utc_offset`` so it starts at midnight on January
454
479
  1st. Ignored if ``None``, otherwise will force year to ``coerce_year``.
455
- coerce_year: int, optional
456
- Use to force indices to desired year. Will default to 1990 if
457
- ``coerce_year`` is not specified, but ``roll_utc_offset`` is specified.
480
+ coerce_year: int, default 1990
481
+ Use to force indices to desired year. Defaults to 1990. Specify
482
+ ``None`` to return the actual indices used for the TMY. If
483
+ ``coerce_year`` is ``None``, but ``roll_utc_offset`` is specified,
484
+ then ``coerce_year`` will be set to the default.
458
485
 
459
486
  Returns
460
487
  -------
461
488
  data : pandas.DataFrame
462
489
  the weather data
463
- months_selected : list
464
- TMY year for each month, ``None`` for basic and EPW
465
- inputs : dict
466
- the inputs, ``None`` for basic and EPW
467
490
  metadata : list or dict
468
- file metadata, ``None`` for basic
491
+ file metadata
469
492
 
470
493
  Raises
471
494
  ------
@@ -509,24 +532,20 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
509
532
  else:
510
533
  raise requests.HTTPError(err_msg['message'])
511
534
  # initialize data to None in case API fails to respond to bad outputformat
512
- data = None, None, None, None
535
+ data = None, None
513
536
  if outputformat == 'json':
514
537
  src = res.json()
515
- data, months_selected, inputs, meta = _parse_pvgis_tmy_json(src)
538
+ data, meta = _parse_pvgis_tmy_json(src)
516
539
  elif outputformat == 'csv':
517
540
  with io.BytesIO(res.content) as src:
518
- data, months_selected, inputs, meta = _parse_pvgis_tmy_csv(src)
519
- elif outputformat == 'basic':
520
- with io.BytesIO(res.content) as src:
521
- data, months_selected, inputs, meta = _parse_pvgis_tmy_basic(src)
541
+ data, meta = _parse_pvgis_tmy_csv(src)
522
542
  elif outputformat == 'epw':
523
543
  with io.StringIO(res.content.decode('utf-8')) as src:
524
- data, meta = parse_epw(src)
525
- months_selected, inputs = None, None
526
- else:
527
- # this line is never reached because if outputformat is not valid then
528
- # the response is HTTP/1.1 400 BAD REQUEST which is handled earlier
529
- pass
544
+ data, meta = read_epw(src)
545
+ elif outputformat == 'basic':
546
+ err_msg = ("outputformat='basic' is no longer supported by pvlib, "
547
+ "please use outputformat='csv' instead.")
548
+ raise ValueError(err_msg)
530
549
 
531
550
  if map_variables:
532
551
  data = data.rename(columns=VARIABLE_MAP)
@@ -536,34 +555,37 @@ def get_pvgis_tmy(latitude, longitude, outputformat='json', usehorizon=True,
536
555
  coerce_year = coerce_year or 1990
537
556
  data = _coerce_and_roll_tmy(data, roll_utc_offset, coerce_year)
538
557
 
539
- return data, months_selected, inputs, meta
558
+ return data, meta
540
559
 
541
560
 
542
561
  def _parse_pvgis_tmy_json(src):
543
- inputs = src['inputs']
544
- meta = src['meta']
545
- months_selected = src['outputs']['months_selected']
562
+ meta = src['meta'].copy()
563
+ # Override the "inputs" in metadata
564
+ meta['inputs'] = src['inputs']
565
+ # Re-add the inputs in metadata one-layer down
566
+ meta['inputs']['descriptions'] = src['meta']['inputs']
567
+ meta['months_selected'] = src['outputs']['months_selected']
546
568
  data = pd.DataFrame(src['outputs']['tmy_hourly'])
547
569
  data.index = pd.to_datetime(
548
570
  data['time(UTC)'], format='%Y%m%d:%H%M', utc=True)
549
571
  data = data.drop('time(UTC)', axis=1)
550
- return data, months_selected, inputs, meta
572
+ return data, meta
551
573
 
552
574
 
553
575
  def _parse_pvgis_tmy_csv(src):
554
576
  # the first 3 rows are latitude, longitude, elevation
555
- inputs = {}
577
+ meta = {'inputs': {}, 'descriptions': {}}
556
578
  # 'Latitude (decimal degrees): 45.000\r\n'
557
- inputs['latitude'] = float(src.readline().split(b':')[1])
579
+ meta['inputs']['latitude'] = float(src.readline().split(b':')[1])
558
580
  # 'Longitude (decimal degrees): 8.000\r\n'
559
- inputs['longitude'] = float(src.readline().split(b':')[1])
581
+ meta['inputs']['longitude'] = float(src.readline().split(b':')[1])
560
582
  # Elevation (m): 1389.0\r\n
561
- inputs['elevation'] = float(src.readline().split(b':')[1])
583
+ meta['inputs']['elevation'] = float(src.readline().split(b':')[1])
562
584
 
563
585
  # TMY has an extra line here: Irradiance Time Offset (h): 0.1761\r\n
564
586
  line = src.readline()
565
587
  if line.startswith(b'Irradiance Time Offset'):
566
- inputs['irradiance time offset'] = float(line.split(b':')[1])
588
+ meta['inputs']['irradiance time offset'] = float(line.split(b':')[1])
567
589
  src.readline() # skip over the "month,year\r\n"
568
590
  else:
569
591
  # `line` is already the "month,year\r\n" line, so nothing to do
@@ -574,6 +596,7 @@ def _parse_pvgis_tmy_csv(src):
574
596
  for month in range(12):
575
597
  months_selected.append(
576
598
  {'month': month+1, 'year': int(src.readline().split(b',')[1])})
599
+ meta['months_selected'] = months_selected
577
600
  # then there's the TMY (typical meteorological year) data
578
601
  # first there's a header row:
579
602
  # time(UTC),T2m,RH,G(h),Gb(n),Gd(h),IR(h),WS10m,WD10m,SP
@@ -586,21 +609,25 @@ def _parse_pvgis_tmy_csv(src):
586
609
  data = pd.DataFrame(data, dtype=float)
587
610
  data.index = dtidx
588
611
  # finally there's some meta data
589
- meta = [line.decode('utf-8').strip() for line in src.readlines()]
590
- return data, months_selected, inputs, meta
591
-
592
-
593
- def _parse_pvgis_tmy_basic(src):
594
- data = pd.read_csv(src)
595
- data.index = pd.to_datetime(
596
- data['time(UTC)'], format='%Y%m%d:%H%M', utc=True)
597
- data = data.drop('time(UTC)', axis=1)
598
- return data, None, None, None
612
+ meta['descriptions'] = {}
613
+ for line in src.readlines():
614
+ line = line.decode('utf-8').strip()
615
+ if ':' in line:
616
+ meta['descriptions'][line.split(':')[0]] = \
617
+ line.split(':')[1].strip()
618
+ return data, meta
599
619
 
600
620
 
601
621
  def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
602
622
  """
603
- Read a file downloaded from PVGIS.
623
+ Read a TMY file downloaded from PVGIS.
624
+
625
+ .. versionchanged:: 0.13.0
626
+ The function now returns two items ``(data,meta)``. Previous
627
+ versions of this function returned four elements
628
+ ``(data,months_selected,inputs,meta)``. The ``inputs`` dictionary
629
+ and ``months_selected`` are now included in ``meta``, which has
630
+ changed structure to accommodate it.
604
631
 
605
632
  Parameters
606
633
  ----------
@@ -610,11 +637,9 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
610
637
  Format of PVGIS file or buffer. Equivalent to the ``outputformat``
611
638
  parameter in the PVGIS TMY API. If ``filename`` is a file and
612
639
  ``pvgis_format`` is not specified then the file extension will be used
613
- to determine the PVGIS format to parse. For PVGIS files from the API
614
- with ``outputformat='basic'``, please set ``pvgis_format`` to
615
- ``'basic'``.
640
+ to determine the PVGIS format to parse.
616
641
  If ``filename`` is a buffer, then ``pvgis_format`` is required and must
617
- be in ``['csv', 'epw', 'json', 'basic']``.
642
+ be in ``['csv', 'epw', 'json']``.
618
643
  map_variables: bool, default True
619
644
  When true, renames columns of the Dataframe to pvlib variable names
620
645
  where applicable. See variable :const:`VARIABLE_MAP`.
@@ -624,19 +649,15 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
624
649
  -------
625
650
  data : pandas.DataFrame
626
651
  the weather data
627
- months_selected : list
628
- TMY year for each month, ``None`` for basic and EPW
629
- inputs : dict
630
- the inputs, ``None`` for basic and EPW
631
652
  metadata : list or dict
632
- file metadata, ``None`` for basic
653
+ file metadata
633
654
 
634
655
  Raises
635
656
  ------
636
657
  ValueError
637
658
  if ``pvgis_format`` is not specified and the file extension is neither
638
659
  ``.csv``, ``.json``, nor ``.epw``, or if ``pvgis_format`` is provided
639
- as input but isn't in ``['csv', 'epw', 'json', 'basic']``
660
+ as input but isn't in ``['csv', 'epw', 'json']``
640
661
  TypeError
641
662
  if ``pvgis_format`` is not specified and ``filename`` is a buffer
642
663
 
@@ -652,18 +673,13 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
652
673
  outputformat = Path(filename).suffix[1:].lower()
653
674
  else:
654
675
  outputformat = pvgis_format
655
- # parse the pvgis file based on the output format, either 'epw', 'json',
656
- # 'csv', or 'basic'
676
+ # parse pvgis file based on outputformat, either 'epw', 'json', or 'csv'
657
677
 
658
678
  # EPW: use the EPW parser from the pvlib.iotools epw.py module
659
679
  if outputformat == 'epw':
660
- try:
661
- data, meta = parse_epw(filename)
662
- except AttributeError: # str/path has no .read() attribute
663
- data, meta = read_epw(filename)
664
- months_selected, inputs = None, None
680
+ data, meta = read_epw(filename)
665
681
 
666
- # NOTE: json, csv, and basic output formats have parsers defined as private
682
+ # NOTE: json and csv output formats have parsers defined as private
667
683
  # functions in this module
668
684
 
669
685
  # JSON: use Python built-in json module to convert file contents to a
@@ -675,32 +691,31 @@ def read_pvgis_tmy(filename, pvgis_format=None, map_variables=True):
675
691
  except AttributeError: # str/path has no .read() attribute
676
692
  with open(str(filename), 'r') as fbuf:
677
693
  src = json.load(fbuf)
678
- data, months_selected, inputs, meta = _parse_pvgis_tmy_json(src)
679
-
680
- # CSV or basic: use the correct parser from this module
681
- # eg: _parse_pvgis_tmy_csv() or _parse_pvgist_tmy_basic()
682
- elif outputformat in ['csv', 'basic']:
683
- # get the correct parser function for this output format from globals()
684
- pvgis_parser = globals()['_parse_pvgis_tmy_{:s}'.format(outputformat)]
685
- # NOTE: pvgis_parse() is a pvgis parser function from this module,
686
- # either _parse_pvgis_tmy_csv() or _parse_pvgist_tmy_basic()
694
+ data, meta = _parse_pvgis_tmy_json(src)
695
+
696
+ elif outputformat == 'csv':
687
697
  try:
688
- data, months_selected, inputs, meta = pvgis_parser(filename)
698
+ data, meta = _parse_pvgis_tmy_csv(filename)
689
699
  except AttributeError: # str/path has no .read() attribute
690
700
  with open(str(filename), 'rb') as fbuf:
691
- data, months_selected, inputs, meta = pvgis_parser(fbuf)
701
+ data, meta = _parse_pvgis_tmy_csv(fbuf)
702
+
703
+ elif outputformat == 'basic':
704
+ err_msg = "outputformat='basic' is no longer supported, please use " \
705
+ "outputformat='csv' instead."
706
+ raise ValueError(err_msg)
692
707
 
693
708
  else:
694
- # raise exception if pvgis format isn't in ['csv','basic','epw','json']
709
+ # raise exception if pvgis format isn't in ['csv','epw','json']
695
710
  err_msg = (
696
- "pvgis format '{:s}' was unknown, must be either 'epw', 'json', "
697
- "'csv', or 'basic'").format(outputformat)
711
+ "pvgis format '{:s}' was unknown, must be either 'json', 'csv',"
712
+ "or 'epw'").format(outputformat)
698
713
  raise ValueError(err_msg)
699
714
 
700
715
  if map_variables:
701
716
  data = data.rename(columns=VARIABLE_MAP)
702
717
 
703
- return data, months_selected, inputs, meta
718
+ return data, meta
704
719
 
705
720
 
706
721
  def get_pvgis_horizon(latitude, longitude, url=URL, **kwargs):
@@ -720,7 +735,7 @@ def get_pvgis_horizon(latitude, longitude, url=URL, **kwargs):
720
735
  Returns
721
736
  -------
722
737
  data : pd.Series
723
- Pandas Series of the retrived horizon elevation angles. Index is the
738
+ Pandas Series of the retrieved horizon elevation angles. Index is the
724
739
  corresponding horizon azimuth angles.
725
740
  metadata : dict
726
741
  Metadata returned by PVGIS.