NREL-reV 0.8.7__py3-none-any.whl → 0.9.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.
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/METADATA +13 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/RECORD +43 -43
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +217 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +611 -422
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +681 -377
- reV/bespoke/cli_bespoke.py +2 -0
- reV/bespoke/place_turbines.py +187 -43
- reV/config/output_request.py +2 -1
- reV/config/project_points.py +218 -140
- reV/econ/econ.py +166 -114
- reV/econ/economies_of_scale.py +91 -45
- reV/generation/base.py +331 -184
- reV/generation/generation.py +326 -200
- reV/generation/output_attributes/lcoe_fcr_inputs.json +38 -3
- reV/handlers/__init__.py +0 -1
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +57 -26
- reV/handlers/outputs.py +6 -5
- reV/handlers/transmission.py +44 -27
- reV/hybrids/hybrid_methods.py +30 -30
- reV/hybrids/hybrids.py +305 -189
- reV/nrwal/nrwal.py +262 -168
- reV/qa_qc/cli_qa_qc.py +14 -10
- reV/qa_qc/qa_qc.py +217 -119
- reV/qa_qc/summary.py +228 -146
- reV/rep_profiles/rep_profiles.py +349 -230
- reV/supply_curve/aggregation.py +349 -188
- reV/supply_curve/competitive_wind_farms.py +90 -48
- reV/supply_curve/exclusions.py +138 -85
- reV/supply_curve/extent.py +75 -50
- reV/supply_curve/points.py +735 -390
- reV/supply_curve/sc_aggregation.py +357 -248
- reV/supply_curve/supply_curve.py +604 -347
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +274 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.9.0.dist-info}/top_level.txt +0 -0
reV/config/project_points.py
CHANGED
@@ -2,28 +2,33 @@
|
|
2
2
|
"""
|
3
3
|
reV Project Points Configuration
|
4
4
|
"""
|
5
|
+
|
5
6
|
import copy
|
6
7
|
import logging
|
7
|
-
import numpy as np
|
8
8
|
import os
|
9
|
-
import pandas as pd
|
10
9
|
from warnings import warn
|
11
10
|
|
11
|
+
import numpy as np
|
12
|
+
import pandas as pd
|
13
|
+
from rex.multi_file_resource import MultiFileResource
|
14
|
+
from rex.resource import Resource
|
15
|
+
from rex.resource_extraction.resource_extraction import (
|
16
|
+
MultiFileResourceX,
|
17
|
+
ResourceX,
|
18
|
+
)
|
19
|
+
from rex.utilities import check_res_file, parse_table
|
20
|
+
|
12
21
|
from reV.config.curtailment import Curtailment
|
13
22
|
from reV.config.sam_config import SAMConfig
|
23
|
+
from reV.utilities import SiteDataField, SupplyCurveField
|
14
24
|
from reV.utilities.exceptions import ConfigError, ConfigWarning
|
15
25
|
|
16
|
-
from rex.resource import Resource
|
17
|
-
from rex.multi_file_resource import MultiFileResource
|
18
|
-
from rex.resource_extraction.resource_extraction import (ResourceX,
|
19
|
-
MultiFileResourceX)
|
20
|
-
from rex.utilities import check_res_file, parse_table
|
21
|
-
|
22
26
|
logger = logging.getLogger(__name__)
|
23
27
|
|
24
28
|
|
25
29
|
class PointsControl:
|
26
30
|
"""Class to manage and split ProjectPoints."""
|
31
|
+
|
27
32
|
def __init__(self, project_points, sites_per_split=100):
|
28
33
|
"""
|
29
34
|
Parameters
|
@@ -46,9 +51,12 @@ class PointsControl:
|
|
46
51
|
last_site = 0
|
47
52
|
ilim = len(self.project_points)
|
48
53
|
|
49
|
-
logger.debug(
|
50
|
-
|
51
|
-
|
54
|
+
logger.debug(
|
55
|
+
"PointsControl iterator initializing with sites "
|
56
|
+
"{} through {}".format(
|
57
|
+
self.project_points.sites[0], self.project_points.sites[-1]
|
58
|
+
)
|
59
|
+
)
|
52
60
|
|
53
61
|
# pre-initialize all iter objects
|
54
62
|
while True:
|
@@ -59,14 +67,19 @@ class PointsControl:
|
|
59
67
|
|
60
68
|
last_site = i1
|
61
69
|
|
62
|
-
new = self.split(
|
63
|
-
|
70
|
+
new = self.split(
|
71
|
+
i0,
|
72
|
+
i1,
|
73
|
+
self.project_points,
|
74
|
+
sites_per_split=self.sites_per_split,
|
75
|
+
)
|
64
76
|
new._split_range = [i0, i1]
|
65
77
|
self._iter_list.append(new)
|
66
78
|
|
67
|
-
logger.debug(
|
68
|
-
|
69
|
-
|
79
|
+
logger.debug(
|
80
|
+
"PointsControl stopped iteration at attempted "
|
81
|
+
"index of {}. Length of iterator is: {}".format(i1, len(self))
|
82
|
+
)
|
70
83
|
return self
|
71
84
|
|
72
85
|
def __next__(self):
|
@@ -85,17 +98,22 @@ class PointsControl:
|
|
85
98
|
# No more points controllers left in initialized list
|
86
99
|
raise StopIteration
|
87
100
|
|
88
|
-
logger.debug(
|
89
|
-
|
90
|
-
|
91
|
-
|
101
|
+
logger.debug(
|
102
|
+
"PointsControl passing site project points "
|
103
|
+
"with indices {} to {} on iteration #{} ".format(
|
104
|
+
next_pc.split_range[0], next_pc.split_range[1], self._i
|
105
|
+
)
|
106
|
+
)
|
92
107
|
self._i += 1
|
93
108
|
return next_pc
|
94
109
|
|
95
110
|
def __repr__(self):
|
96
|
-
msg =
|
97
|
-
|
98
|
-
|
111
|
+
msg = "{} with {} sites from gid {} through {}".format(
|
112
|
+
self.__class__.__name__,
|
113
|
+
len(self.project_points),
|
114
|
+
self.sites[0],
|
115
|
+
self.sites[-1],
|
116
|
+
)
|
99
117
|
return msg
|
100
118
|
|
101
119
|
def __len__(self):
|
@@ -210,8 +228,9 @@ class ProjectPoints:
|
|
210
228
|
>>> h_list = pp.h
|
211
229
|
"""
|
212
230
|
|
213
|
-
def __init__(
|
214
|
-
|
231
|
+
def __init__(
|
232
|
+
self, points, sam_configs, tech=None, res_file=None, curtailment=None
|
233
|
+
):
|
215
234
|
"""
|
216
235
|
Parameters
|
217
236
|
----------
|
@@ -269,22 +288,25 @@ class ProjectPoints:
|
|
269
288
|
names (keys) and values.
|
270
289
|
"""
|
271
290
|
|
272
|
-
site_bool =
|
291
|
+
site_bool = self.df[SiteDataField.GID] == site
|
273
292
|
try:
|
274
|
-
config_id = self.df.loc[site_bool,
|
293
|
+
config_id = self.df.loc[site_bool, SiteDataField.CONFIG].values[0]
|
275
294
|
except (KeyError, IndexError) as ex:
|
276
|
-
msg = (
|
277
|
-
|
278
|
-
|
295
|
+
msg = (
|
296
|
+
"Site {} not found in this instance of "
|
297
|
+
"ProjectPoints. Available sites include: {}".format(
|
298
|
+
site, self.sites
|
299
|
+
)
|
300
|
+
)
|
279
301
|
logger.exception(msg)
|
280
302
|
raise KeyError(msg) from ex
|
281
303
|
|
282
304
|
return config_id, copy.deepcopy(self.sam_inputs[config_id])
|
283
305
|
|
284
306
|
def __repr__(self):
|
285
|
-
msg =
|
286
|
-
|
287
|
-
|
307
|
+
msg = "{} with {} sites from gid {} through {}".format(
|
308
|
+
self.__class__.__name__, len(self), self.sites[0], self.sites[-1]
|
309
|
+
)
|
288
310
|
return msg
|
289
311
|
|
290
312
|
def __len__(self):
|
@@ -299,7 +321,7 @@ class ProjectPoints:
|
|
299
321
|
-------
|
300
322
|
_df : pd.DataFrame
|
301
323
|
Table of sites and corresponding SAM configuration IDs.
|
302
|
-
Has columns
|
324
|
+
Has columns "gid" and 'config'.
|
303
325
|
"""
|
304
326
|
return self._df
|
305
327
|
|
@@ -384,7 +406,7 @@ class ProjectPoints:
|
|
384
406
|
List of integer sites (resource file gids) belonging to this
|
385
407
|
instance of ProjectPoints.
|
386
408
|
"""
|
387
|
-
return self.df[
|
409
|
+
return self.df[SiteDataField.GID].values.tolist()
|
388
410
|
|
389
411
|
@property
|
390
412
|
def sites_as_slice(self):
|
@@ -425,7 +447,7 @@ class ProjectPoints:
|
|
425
447
|
solarwaterheat, troughphysicalheat, lineardirectsteam)
|
426
448
|
The string should be lower-cased with spaces and _ removed.
|
427
449
|
"""
|
428
|
-
return
|
450
|
+
return "windpower" if "wind" in self._tech.lower() else self._tech
|
429
451
|
|
430
452
|
@property
|
431
453
|
def h(self):
|
@@ -437,9 +459,9 @@ class ProjectPoints:
|
|
437
459
|
Hub heights corresponding to each site, taken from the sam config
|
438
460
|
for each site. This is None if the technology is not wind.
|
439
461
|
"""
|
440
|
-
h_var =
|
462
|
+
h_var = "wind_turbine_hub_ht"
|
441
463
|
if self._h is None:
|
442
|
-
if
|
464
|
+
if "wind" in self.tech:
|
443
465
|
# wind technology, get a list of h values
|
444
466
|
self._h = [self[site][1][h_var] for site in self.sites]
|
445
467
|
|
@@ -456,9 +478,9 @@ class ProjectPoints:
|
|
456
478
|
the sam config for each site. This is None if the technology
|
457
479
|
is not geothermal.
|
458
480
|
"""
|
459
|
-
d_var =
|
481
|
+
d_var = "resource_depth"
|
460
482
|
if self._d is None:
|
461
|
-
if
|
483
|
+
if "geothermal" in self.tech:
|
462
484
|
if d_var in self.df:
|
463
485
|
self._d = list(self.df[d_var])
|
464
486
|
else:
|
@@ -485,8 +507,8 @@ class ProjectPoints:
|
|
485
507
|
Parameters
|
486
508
|
----------
|
487
509
|
fname : str
|
488
|
-
Project points .csv file (with path). Must have 'gid' and
|
489
|
-
column names.
|
510
|
+
Project points .csv file (with path). Must have 'gid' and
|
511
|
+
'config' column names.
|
490
512
|
|
491
513
|
Returns
|
492
514
|
-------
|
@@ -494,12 +516,13 @@ class ProjectPoints:
|
|
494
516
|
DataFrame mapping sites (gids) to SAM technology (config)
|
495
517
|
"""
|
496
518
|
fname = fname.strip()
|
497
|
-
if fname.endswith(
|
519
|
+
if fname.endswith(".csv"):
|
498
520
|
df = pd.read_csv(fname)
|
499
521
|
else:
|
500
|
-
raise ValueError(
|
501
|
-
|
502
|
-
|
522
|
+
raise ValueError(
|
523
|
+
"Config project points file must be "
|
524
|
+
".csv, but received: {}".format(fname)
|
525
|
+
)
|
503
526
|
|
504
527
|
return df
|
505
528
|
|
@@ -522,7 +545,7 @@ class ProjectPoints:
|
|
522
545
|
df : pd.DataFrame
|
523
546
|
DataFrame mapping sites (gids) to SAM technology (config)
|
524
547
|
"""
|
525
|
-
df = pd.DataFrame(columns=[
|
548
|
+
df = pd.DataFrame(columns=[SiteDataField.GID, SiteDataField.CONFIG])
|
526
549
|
if isinstance(points, int):
|
527
550
|
points = [points]
|
528
551
|
if isinstance(points, (list, tuple, np.ndarray)):
|
@@ -532,14 +555,16 @@ class ProjectPoints:
|
|
532
555
|
logger.error(msg)
|
533
556
|
raise RuntimeError(msg)
|
534
557
|
|
535
|
-
df[
|
558
|
+
df[SiteDataField.GID] = points
|
536
559
|
elif isinstance(points, slice):
|
537
560
|
stop = points.stop
|
538
561
|
if stop is None:
|
539
562
|
if res_file is None:
|
540
|
-
raise ValueError(
|
541
|
-
|
542
|
-
|
563
|
+
raise ValueError(
|
564
|
+
"Must supply a resource file if "
|
565
|
+
"points is a slice of type "
|
566
|
+
" slice(*, None, *)"
|
567
|
+
)
|
543
568
|
|
544
569
|
multi_h5_res, _ = check_res_file(res_file)
|
545
570
|
if multi_h5_res:
|
@@ -547,13 +572,14 @@ class ProjectPoints:
|
|
547
572
|
else:
|
548
573
|
stop = Resource(res_file).shape[1]
|
549
574
|
|
550
|
-
df[
|
575
|
+
df[SiteDataField.GID] = list(range(*points.indices(stop)))
|
551
576
|
else:
|
552
|
-
raise TypeError(
|
553
|
-
|
554
|
-
|
577
|
+
raise TypeError(
|
578
|
+
"Project Points sites needs to be set as a list, "
|
579
|
+
"tuple, or slice, but was set as: {}".format(type(points))
|
580
|
+
)
|
555
581
|
|
556
|
-
df[
|
582
|
+
df[SiteDataField.CONFIG] = None
|
557
583
|
|
558
584
|
return df
|
559
585
|
|
@@ -585,25 +611,31 @@ class ProjectPoints:
|
|
585
611
|
elif isinstance(points, pd.DataFrame):
|
586
612
|
df = points
|
587
613
|
else:
|
588
|
-
raise ValueError(
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
614
|
+
raise ValueError(
|
615
|
+
"Cannot parse Project points data from {}".format(type(points))
|
616
|
+
)
|
617
|
+
df = df.rename(SupplyCurveField.map_to(SiteDataField), axis=1)
|
618
|
+
if SiteDataField.GID not in df.columns:
|
619
|
+
raise KeyError(
|
620
|
+
"Project points data must contain "
|
621
|
+
f"{SiteDataField.GID} column."
|
622
|
+
)
|
593
623
|
|
594
624
|
# pylint: disable=no-member
|
595
|
-
if
|
596
|
-
df
|
625
|
+
if SiteDataField.CONFIG not in df.columns:
|
626
|
+
df[SiteDataField.CONFIG] = None
|
597
627
|
|
598
|
-
gids = df[
|
628
|
+
gids = df[SiteDataField.GID].values
|
599
629
|
if not np.array_equal(np.sort(gids), gids):
|
600
|
-
msg = (
|
601
|
-
|
602
|
-
|
630
|
+
msg = (
|
631
|
+
"WARNING: points are not in sequential order and will be "
|
632
|
+
"sorted! The original order is being preserved under "
|
633
|
+
'column "points_order"'
|
634
|
+
)
|
603
635
|
logger.warning(msg)
|
604
636
|
warn(msg)
|
605
|
-
df[
|
606
|
-
df = df.sort_values(
|
637
|
+
df["points_order"] = df.index.values
|
638
|
+
df = df.sort_values(SiteDataField.GID).reset_index(drop=True)
|
607
639
|
|
608
640
|
return df
|
609
641
|
|
@@ -628,16 +660,16 @@ class ProjectPoints:
|
|
628
660
|
if isinstance(sam_config, SAMConfig):
|
629
661
|
return sam_config
|
630
662
|
|
663
|
+
if isinstance(sam_config, dict):
|
664
|
+
config_dict = sam_config
|
665
|
+
elif isinstance(sam_config, str):
|
666
|
+
config_dict = {sam_config: sam_config}
|
631
667
|
else:
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
config_dict = {sam_config: sam_config}
|
636
|
-
else:
|
637
|
-
raise ValueError('Cannot parse SAM configs from {}'
|
638
|
-
.format(type(sam_config)))
|
668
|
+
raise ValueError(
|
669
|
+
"Cannot parse SAM configs from {}".format(type(sam_config))
|
670
|
+
)
|
639
671
|
|
640
|
-
|
672
|
+
return SAMConfig(config_dict)
|
641
673
|
|
642
674
|
@staticmethod
|
643
675
|
def _parse_curtailment(curtailment_input):
|
@@ -670,10 +702,12 @@ class ProjectPoints:
|
|
670
702
|
|
671
703
|
else:
|
672
704
|
curtailment = None
|
673
|
-
warn(
|
674
|
-
|
675
|
-
|
676
|
-
|
705
|
+
warn(
|
706
|
+
"Curtailment inputs not recognized. Received curtailment "
|
707
|
+
'input of type: "{}". Expected None, dict, str, or '
|
708
|
+
"Curtailment object. Defaulting to no curtailment.",
|
709
|
+
ConfigWarning,
|
710
|
+
)
|
677
711
|
|
678
712
|
return curtailment
|
679
713
|
|
@@ -691,13 +725,15 @@ class ProjectPoints:
|
|
691
725
|
ind : int
|
692
726
|
Row index of gid in the project points dataframe.
|
693
727
|
"""
|
694
|
-
if gid not in self._df[
|
695
|
-
e = (
|
696
|
-
|
728
|
+
if gid not in self._df[SiteDataField.GID].values:
|
729
|
+
e = (
|
730
|
+
"Requested resource gid {} is not present in the project "
|
731
|
+
"points dataframe. Cannot return row index.".format(gid)
|
732
|
+
)
|
697
733
|
logger.error(e)
|
698
734
|
raise ConfigError(e)
|
699
735
|
|
700
|
-
ind = np.where(self._df[
|
736
|
+
ind = np.where(self._df[SiteDataField.GID] == gid)[0][0]
|
701
737
|
|
702
738
|
return ind
|
703
739
|
|
@@ -707,21 +743,24 @@ class ProjectPoints:
|
|
707
743
|
(sam_config_obj) are compatible. Update as necessary or break
|
708
744
|
"""
|
709
745
|
# Extract unique config refences from project_points DataFrame
|
710
|
-
df_configs = self.df[
|
746
|
+
df_configs = self.df[SiteDataField.CONFIG].unique()
|
711
747
|
sam_configs = self.sam_inputs
|
712
748
|
|
713
749
|
# Checks to make sure that the same number of SAM config files
|
714
750
|
# as references in project_points DataFrame
|
715
751
|
if len(df_configs) > len(sam_configs):
|
716
|
-
msg = (
|
717
|
-
|
718
|
-
|
752
|
+
msg = (
|
753
|
+
"Points references {} configs while only "
|
754
|
+
"{} SAM configs were provided!".format(
|
755
|
+
len(df_configs), len(sam_configs)
|
756
|
+
)
|
757
|
+
)
|
719
758
|
logger.error(msg)
|
720
759
|
raise ConfigError(msg)
|
721
760
|
|
722
761
|
if len(df_configs) == 1 and df_configs[0] is None:
|
723
|
-
self._df[
|
724
|
-
df_configs = self.df[
|
762
|
+
self._df[SiteDataField.CONFIG] = list(sam_configs)[0]
|
763
|
+
df_configs = self.df[SiteDataField.CONFIG].unique()
|
725
764
|
|
726
765
|
# Check to see if config references in project_points DataFrame
|
727
766
|
# are valid file paths, if compare with SAM configs
|
@@ -733,21 +772,25 @@ class ProjectPoints:
|
|
733
772
|
elif config in sam_configs:
|
734
773
|
configs[config] = sam_configs[config]
|
735
774
|
else:
|
736
|
-
msg =
|
737
|
-
|
775
|
+
msg = "{} does not map to a valid configuration file".format(
|
776
|
+
config
|
777
|
+
)
|
738
778
|
logger.error(msg)
|
739
779
|
raise ConfigError(msg)
|
740
780
|
|
741
781
|
# If configs has any keys that are not in sam_configs then
|
742
782
|
# something really weird happened so raise an error.
|
743
783
|
if any(set(configs) - set(sam_configs)):
|
744
|
-
msg = (
|
745
|
-
|
746
|
-
|
784
|
+
msg = (
|
785
|
+
"A wild config has appeared! Requested config keys for "
|
786
|
+
"ProjectPoints are {} and previous config keys are {}".format(
|
787
|
+
list(configs), list(sam_configs)
|
788
|
+
)
|
789
|
+
)
|
747
790
|
logger.error(msg)
|
748
791
|
raise ConfigError(msg)
|
749
792
|
|
750
|
-
def join_df(self, df2, key=
|
793
|
+
def join_df(self, df2, key=SiteDataField.GID):
|
751
794
|
"""Join new df2 to the _df attribute using the _df's gid as pkey.
|
752
795
|
|
753
796
|
This can be used to add site-specific data to the project_points,
|
@@ -767,8 +810,15 @@ class ProjectPoints:
|
|
767
810
|
"""
|
768
811
|
# ensure df2 doesnt have any duplicate columns for suffix reasons.
|
769
812
|
df2_cols = [c for c in df2.columns if c not in self._df or c == key]
|
770
|
-
self._df = pd.merge(
|
771
|
-
|
813
|
+
self._df = pd.merge(
|
814
|
+
self._df,
|
815
|
+
df2[df2_cols],
|
816
|
+
how="left",
|
817
|
+
left_on=SiteDataField.GID,
|
818
|
+
right_on=key,
|
819
|
+
copy=False,
|
820
|
+
validate="1:1",
|
821
|
+
)
|
772
822
|
|
773
823
|
def get_sites_from_config(self, config):
|
774
824
|
"""Get a site list that corresponds to a config key.
|
@@ -784,7 +834,9 @@ class ProjectPoints:
|
|
784
834
|
List of sites associated with the requested configuration ID. If
|
785
835
|
the configuration ID is not recognized, an empty list is returned.
|
786
836
|
"""
|
787
|
-
sites = self.df.loc[
|
837
|
+
sites = self.df.loc[
|
838
|
+
(self.df[SiteDataField.CONFIG] == config), SiteDataField.GID
|
839
|
+
].values
|
788
840
|
|
789
841
|
return list(sites)
|
790
842
|
|
@@ -817,32 +869,39 @@ class ProjectPoints:
|
|
817
869
|
# Extract DF subset with only index values between i0 and i1
|
818
870
|
n = len(project_points)
|
819
871
|
if i0 > n or i1 > n:
|
820
|
-
raise ValueError(
|
821
|
-
|
872
|
+
raise ValueError(
|
873
|
+
"{} and {} must be within the range of "
|
874
|
+
"project_points (0 - {})".format(i0, i1, n - 1)
|
875
|
+
)
|
822
876
|
|
823
877
|
points_df = project_points.df.iloc[i0:i1]
|
824
878
|
|
825
879
|
# make a new instance of ProjectPoints with subset DF
|
826
|
-
sub = cls(
|
827
|
-
|
828
|
-
|
829
|
-
|
880
|
+
sub = cls(
|
881
|
+
points_df,
|
882
|
+
project_points.sam_config_obj,
|
883
|
+
project_points.tech,
|
884
|
+
curtailment=project_points.curtailment,
|
885
|
+
)
|
830
886
|
|
831
887
|
return sub
|
832
888
|
|
833
889
|
@staticmethod
|
834
890
|
def _parse_lat_lons(lat_lons):
|
835
|
-
msg = (
|
836
|
-
|
891
|
+
msg = (
|
892
|
+
"Expecting a pair or multiple pairs of latitude and "
|
893
|
+
"longitude coordinates!"
|
894
|
+
)
|
837
895
|
if isinstance(lat_lons, str):
|
838
896
|
lat_lons = parse_table(lat_lons)
|
839
|
-
cols = [
|
840
|
-
|
897
|
+
cols = [
|
898
|
+
c for c in lat_lons if c.lower().startswith(("lat", "lon"))
|
899
|
+
]
|
841
900
|
lat_lons = lat_lons[sorted(cols)].values
|
842
901
|
elif isinstance(lat_lons, (list, tuple)):
|
843
902
|
lat_lons = np.array(lat_lons)
|
844
903
|
elif isinstance(lat_lons, (int, float)):
|
845
|
-
msg +=
|
904
|
+
msg += " Recieved a single coordinate value!"
|
846
905
|
logger.error(msg)
|
847
906
|
raise ValueError(msg)
|
848
907
|
|
@@ -850,15 +909,16 @@ class ProjectPoints:
|
|
850
909
|
lat_lons = np.expand_dims(lat_lons, axis=0)
|
851
910
|
|
852
911
|
if lat_lons.shape[1] != 2:
|
853
|
-
msg +=
|
912
|
+
msg += " Received {} coordinate values!".format(lat_lons.shape[1])
|
854
913
|
logger.error(msg)
|
855
914
|
raise ValueError(msg)
|
856
915
|
|
857
916
|
return lat_lons
|
858
917
|
|
859
918
|
@classmethod
|
860
|
-
def lat_lon_coords(
|
861
|
-
|
919
|
+
def lat_lon_coords(
|
920
|
+
cls, lat_lons, res_file, sam_configs, tech=None, curtailment=None
|
921
|
+
):
|
862
922
|
"""
|
863
923
|
Generate ProjectPoints for gids nearest to given latitude longitudes
|
864
924
|
|
@@ -903,11 +963,13 @@ class ProjectPoints:
|
|
903
963
|
res_kwargs = {}
|
904
964
|
else:
|
905
965
|
res_cls = ResourceX
|
906
|
-
res_kwargs = {
|
966
|
+
res_kwargs = {"hsds": hsds}
|
907
967
|
|
908
|
-
logger.info(
|
909
|
-
|
910
|
-
|
968
|
+
logger.info(
|
969
|
+
"Converting latitude longitude coordinates into nearest "
|
970
|
+
"ProjectPoints"
|
971
|
+
)
|
972
|
+
logger.debug("- (lat, lon) pairs:\n{}".format(lat_lons))
|
911
973
|
with res_cls(res_file, **res_kwargs) as f:
|
912
974
|
gids = f.lat_lon_gid(lat_lons) # pylint: disable=no-member
|
913
975
|
|
@@ -915,37 +977,46 @@ class ProjectPoints:
|
|
915
977
|
gids = [gids]
|
916
978
|
else:
|
917
979
|
if len(gids) != len(np.unique(gids)):
|
918
|
-
uniques, pos, counts = np.unique(
|
919
|
-
|
980
|
+
uniques, pos, counts = np.unique(
|
981
|
+
gids, return_counts=True, return_inverse=True
|
982
|
+
)
|
920
983
|
duplicates = {}
|
921
984
|
for idx in np.where(counts > 1)[0]:
|
922
985
|
duplicate_lat_lons = lat_lons[np.where(pos == idx)[0]]
|
923
986
|
duplicates[uniques[idx]] = duplicate_lat_lons
|
924
987
|
|
925
|
-
msg = (
|
926
|
-
|
927
|
-
|
988
|
+
msg = (
|
989
|
+
"reV Cannot currently handle duplicate Resource gids! "
|
990
|
+
"The given latitude and longitudes map to the same "
|
991
|
+
"gids:\n{}".format(duplicates)
|
992
|
+
)
|
928
993
|
logger.error(msg)
|
929
994
|
raise RuntimeError(msg)
|
930
995
|
|
931
996
|
gids = gids.tolist()
|
932
997
|
|
933
|
-
logger.debug(
|
998
|
+
logger.debug("- Resource gids:\n{}".format(gids))
|
934
999
|
|
935
|
-
pp = cls(
|
936
|
-
|
1000
|
+
pp = cls(
|
1001
|
+
gids,
|
1002
|
+
sam_configs,
|
1003
|
+
tech=tech,
|
1004
|
+
res_file=res_file,
|
1005
|
+
curtailment=curtailment,
|
1006
|
+
)
|
937
1007
|
|
938
|
-
if
|
939
|
-
lat_lons = lat_lons[pp.df[
|
1008
|
+
if "points_order" in pp.df:
|
1009
|
+
lat_lons = lat_lons[pp.df["points_order"].values]
|
940
1010
|
|
941
|
-
pp._df[
|
942
|
-
pp._df[
|
1011
|
+
pp._df["latitude"] = lat_lons[:, 0]
|
1012
|
+
pp._df["longitude"] = lat_lons[:, 1]
|
943
1013
|
|
944
1014
|
return pp
|
945
1015
|
|
946
1016
|
@classmethod
|
947
|
-
def regions(
|
948
|
-
|
1017
|
+
def regions(
|
1018
|
+
cls, regions, res_file, sam_configs, tech=None, curtailment=None
|
1019
|
+
):
|
949
1020
|
"""
|
950
1021
|
Generate ProjectPoints for gids nearest to given latitude longitudes
|
951
1022
|
|
@@ -989,28 +1060,35 @@ class ProjectPoints:
|
|
989
1060
|
else:
|
990
1061
|
res_cls = ResourceX
|
991
1062
|
|
992
|
-
logger.info(
|
1063
|
+
logger.info("Extracting ProjectPoints for desired regions")
|
993
1064
|
points = []
|
994
1065
|
with res_cls(res_file, hsds=hsds) as f:
|
995
1066
|
meta = f.meta
|
996
1067
|
for region, region_col in regions.items():
|
997
|
-
logger.debug(
|
1068
|
+
logger.debug("- {}: {}".format(region_col, region))
|
998
1069
|
# pylint: disable=no-member
|
999
1070
|
gids = f.region_gids(region, region_col=region_col)
|
1000
|
-
logger.debug(
|
1071
|
+
logger.debug("- Resource gids:\n{}".format(gids))
|
1001
1072
|
if points:
|
1002
1073
|
duplicates = np.intersect1d(gids, points).tolist()
|
1003
1074
|
if duplicates:
|
1004
|
-
msg = (
|
1005
|
-
|
1006
|
-
|
1075
|
+
msg = (
|
1076
|
+
"reV Cannot currently handle duplicate "
|
1077
|
+
"Resource gids! The given regions containg the "
|
1078
|
+
"same gids:\n{}".format(duplicates)
|
1079
|
+
)
|
1007
1080
|
logger.error(msg)
|
1008
1081
|
raise RuntimeError(msg)
|
1009
1082
|
|
1010
1083
|
points.extend(gids.tolist())
|
1011
1084
|
|
1012
|
-
pp = cls(
|
1013
|
-
|
1085
|
+
pp = cls(
|
1086
|
+
points,
|
1087
|
+
sam_configs,
|
1088
|
+
tech=tech,
|
1089
|
+
res_file=res_file,
|
1090
|
+
curtailment=curtailment,
|
1091
|
+
)
|
1014
1092
|
|
1015
1093
|
meta = meta.loc[pp.sites]
|
1016
1094
|
cols = list(set(regions.values()))
|