NREL-reV 0.8.7__py3-none-any.whl → 0.8.9__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.8.9.dist-info}/METADATA +12 -10
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/RECORD +38 -38
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/WHEEL +1 -1
- reV/SAM/SAM.py +182 -133
- reV/SAM/econ.py +18 -14
- reV/SAM/generation.py +608 -419
- reV/SAM/windbos.py +93 -79
- reV/bespoke/bespoke.py +690 -445
- reV/bespoke/place_turbines.py +6 -6
- reV/config/project_points.py +220 -140
- reV/econ/econ.py +165 -113
- reV/econ/economies_of_scale.py +57 -34
- reV/generation/base.py +310 -183
- reV/generation/generation.py +298 -190
- reV/handlers/exclusions.py +16 -15
- reV/handlers/multi_year.py +12 -9
- reV/handlers/outputs.py +6 -5
- reV/hybrids/hybrid_methods.py +28 -30
- reV/hybrids/hybrids.py +304 -188
- 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 +536 -309
- reV/supply_curve/sc_aggregation.py +366 -225
- reV/supply_curve/supply_curve.py +505 -308
- reV/supply_curve/tech_mapping.py +144 -82
- reV/utilities/__init__.py +199 -16
- reV/utilities/pytest_utils.py +8 -4
- reV/version.py +1 -1
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/LICENSE +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.dist-info}/entry_points.txt +0 -0
- {NREL_reV-0.8.7.dist-info → NREL_reV-0.8.9.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,33 @@ 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 = cls._parse_sites(
|
625
|
+
if SiteDataField.CONFIG not in df.columns:
|
626
|
+
df = cls._parse_sites(
|
627
|
+
df[SiteDataField.GID].values, res_file=res_file
|
628
|
+
)
|
597
629
|
|
598
|
-
gids = df[
|
630
|
+
gids = df[SiteDataField.GID].values
|
599
631
|
if not np.array_equal(np.sort(gids), gids):
|
600
|
-
msg = (
|
601
|
-
|
602
|
-
|
632
|
+
msg = (
|
633
|
+
"WARNING: points are not in sequential order and will be "
|
634
|
+
"sorted! The original order is being preserved under "
|
635
|
+
'column "points_order"'
|
636
|
+
)
|
603
637
|
logger.warning(msg)
|
604
638
|
warn(msg)
|
605
|
-
df[
|
606
|
-
df = df.sort_values(
|
639
|
+
df["points_order"] = df.index.values
|
640
|
+
df = df.sort_values(SiteDataField.GID).reset_index(drop=True)
|
607
641
|
|
608
642
|
return df
|
609
643
|
|
@@ -628,16 +662,16 @@ class ProjectPoints:
|
|
628
662
|
if isinstance(sam_config, SAMConfig):
|
629
663
|
return sam_config
|
630
664
|
|
665
|
+
if isinstance(sam_config, dict):
|
666
|
+
config_dict = sam_config
|
667
|
+
elif isinstance(sam_config, str):
|
668
|
+
config_dict = {sam_config: sam_config}
|
631
669
|
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)))
|
670
|
+
raise ValueError(
|
671
|
+
"Cannot parse SAM configs from {}".format(type(sam_config))
|
672
|
+
)
|
639
673
|
|
640
|
-
|
674
|
+
return SAMConfig(config_dict)
|
641
675
|
|
642
676
|
@staticmethod
|
643
677
|
def _parse_curtailment(curtailment_input):
|
@@ -670,10 +704,12 @@ class ProjectPoints:
|
|
670
704
|
|
671
705
|
else:
|
672
706
|
curtailment = None
|
673
|
-
warn(
|
674
|
-
|
675
|
-
|
676
|
-
|
707
|
+
warn(
|
708
|
+
"Curtailment inputs not recognized. Received curtailment "
|
709
|
+
'input of type: "{}". Expected None, dict, str, or '
|
710
|
+
"Curtailment object. Defaulting to no curtailment.",
|
711
|
+
ConfigWarning,
|
712
|
+
)
|
677
713
|
|
678
714
|
return curtailment
|
679
715
|
|
@@ -691,13 +727,15 @@ class ProjectPoints:
|
|
691
727
|
ind : int
|
692
728
|
Row index of gid in the project points dataframe.
|
693
729
|
"""
|
694
|
-
if gid not in self._df[
|
695
|
-
e = (
|
696
|
-
|
730
|
+
if gid not in self._df[SiteDataField.GID].values:
|
731
|
+
e = (
|
732
|
+
"Requested resource gid {} is not present in the project "
|
733
|
+
"points dataframe. Cannot return row index.".format(gid)
|
734
|
+
)
|
697
735
|
logger.error(e)
|
698
736
|
raise ConfigError(e)
|
699
737
|
|
700
|
-
ind = np.where(self._df[
|
738
|
+
ind = np.where(self._df[SiteDataField.GID] == gid)[0][0]
|
701
739
|
|
702
740
|
return ind
|
703
741
|
|
@@ -707,21 +745,24 @@ class ProjectPoints:
|
|
707
745
|
(sam_config_obj) are compatible. Update as necessary or break
|
708
746
|
"""
|
709
747
|
# Extract unique config refences from project_points DataFrame
|
710
|
-
df_configs = self.df[
|
748
|
+
df_configs = self.df[SiteDataField.CONFIG].unique()
|
711
749
|
sam_configs = self.sam_inputs
|
712
750
|
|
713
751
|
# Checks to make sure that the same number of SAM config files
|
714
752
|
# as references in project_points DataFrame
|
715
753
|
if len(df_configs) > len(sam_configs):
|
716
|
-
msg = (
|
717
|
-
|
718
|
-
|
754
|
+
msg = (
|
755
|
+
"Points references {} configs while only "
|
756
|
+
"{} SAM configs were provided!".format(
|
757
|
+
len(df_configs), len(sam_configs)
|
758
|
+
)
|
759
|
+
)
|
719
760
|
logger.error(msg)
|
720
761
|
raise ConfigError(msg)
|
721
762
|
|
722
763
|
if len(df_configs) == 1 and df_configs[0] is None:
|
723
|
-
self._df[
|
724
|
-
df_configs = self.df[
|
764
|
+
self._df[SiteDataField.CONFIG] = list(sam_configs)[0]
|
765
|
+
df_configs = self.df[SiteDataField.CONFIG].unique()
|
725
766
|
|
726
767
|
# Check to see if config references in project_points DataFrame
|
727
768
|
# are valid file paths, if compare with SAM configs
|
@@ -733,21 +774,25 @@ class ProjectPoints:
|
|
733
774
|
elif config in sam_configs:
|
734
775
|
configs[config] = sam_configs[config]
|
735
776
|
else:
|
736
|
-
msg =
|
737
|
-
|
777
|
+
msg = "{} does not map to a valid configuration file".format(
|
778
|
+
config
|
779
|
+
)
|
738
780
|
logger.error(msg)
|
739
781
|
raise ConfigError(msg)
|
740
782
|
|
741
783
|
# If configs has any keys that are not in sam_configs then
|
742
784
|
# something really weird happened so raise an error.
|
743
785
|
if any(set(configs) - set(sam_configs)):
|
744
|
-
msg = (
|
745
|
-
|
746
|
-
|
786
|
+
msg = (
|
787
|
+
"A wild config has appeared! Requested config keys for "
|
788
|
+
"ProjectPoints are {} and previous config keys are {}".format(
|
789
|
+
list(configs), list(sam_configs)
|
790
|
+
)
|
791
|
+
)
|
747
792
|
logger.error(msg)
|
748
793
|
raise ConfigError(msg)
|
749
794
|
|
750
|
-
def join_df(self, df2, key=
|
795
|
+
def join_df(self, df2, key=SiteDataField.GID):
|
751
796
|
"""Join new df2 to the _df attribute using the _df's gid as pkey.
|
752
797
|
|
753
798
|
This can be used to add site-specific data to the project_points,
|
@@ -767,8 +812,15 @@ class ProjectPoints:
|
|
767
812
|
"""
|
768
813
|
# ensure df2 doesnt have any duplicate columns for suffix reasons.
|
769
814
|
df2_cols = [c for c in df2.columns if c not in self._df or c == key]
|
770
|
-
self._df = pd.merge(
|
771
|
-
|
815
|
+
self._df = pd.merge(
|
816
|
+
self._df,
|
817
|
+
df2[df2_cols],
|
818
|
+
how="left",
|
819
|
+
left_on=SiteDataField.GID,
|
820
|
+
right_on=key,
|
821
|
+
copy=False,
|
822
|
+
validate="1:1",
|
823
|
+
)
|
772
824
|
|
773
825
|
def get_sites_from_config(self, config):
|
774
826
|
"""Get a site list that corresponds to a config key.
|
@@ -784,7 +836,9 @@ class ProjectPoints:
|
|
784
836
|
List of sites associated with the requested configuration ID. If
|
785
837
|
the configuration ID is not recognized, an empty list is returned.
|
786
838
|
"""
|
787
|
-
sites = self.df.loc[
|
839
|
+
sites = self.df.loc[
|
840
|
+
(self.df[SiteDataField.CONFIG] == config), SiteDataField.GID
|
841
|
+
].values
|
788
842
|
|
789
843
|
return list(sites)
|
790
844
|
|
@@ -817,32 +871,39 @@ class ProjectPoints:
|
|
817
871
|
# Extract DF subset with only index values between i0 and i1
|
818
872
|
n = len(project_points)
|
819
873
|
if i0 > n or i1 > n:
|
820
|
-
raise ValueError(
|
821
|
-
|
874
|
+
raise ValueError(
|
875
|
+
"{} and {} must be within the range of "
|
876
|
+
"project_points (0 - {})".format(i0, i1, n - 1)
|
877
|
+
)
|
822
878
|
|
823
879
|
points_df = project_points.df.iloc[i0:i1]
|
824
880
|
|
825
881
|
# make a new instance of ProjectPoints with subset DF
|
826
|
-
sub = cls(
|
827
|
-
|
828
|
-
|
829
|
-
|
882
|
+
sub = cls(
|
883
|
+
points_df,
|
884
|
+
project_points.sam_config_obj,
|
885
|
+
project_points.tech,
|
886
|
+
curtailment=project_points.curtailment,
|
887
|
+
)
|
830
888
|
|
831
889
|
return sub
|
832
890
|
|
833
891
|
@staticmethod
|
834
892
|
def _parse_lat_lons(lat_lons):
|
835
|
-
msg = (
|
836
|
-
|
893
|
+
msg = (
|
894
|
+
"Expecting a pair or multiple pairs of latitude and "
|
895
|
+
"longitude coordinates!"
|
896
|
+
)
|
837
897
|
if isinstance(lat_lons, str):
|
838
898
|
lat_lons = parse_table(lat_lons)
|
839
|
-
cols = [
|
840
|
-
|
899
|
+
cols = [
|
900
|
+
c for c in lat_lons if c.lower().startswith(("lat", "lon"))
|
901
|
+
]
|
841
902
|
lat_lons = lat_lons[sorted(cols)].values
|
842
903
|
elif isinstance(lat_lons, (list, tuple)):
|
843
904
|
lat_lons = np.array(lat_lons)
|
844
905
|
elif isinstance(lat_lons, (int, float)):
|
845
|
-
msg +=
|
906
|
+
msg += " Recieved a single coordinate value!"
|
846
907
|
logger.error(msg)
|
847
908
|
raise ValueError(msg)
|
848
909
|
|
@@ -850,15 +911,16 @@ class ProjectPoints:
|
|
850
911
|
lat_lons = np.expand_dims(lat_lons, axis=0)
|
851
912
|
|
852
913
|
if lat_lons.shape[1] != 2:
|
853
|
-
msg +=
|
914
|
+
msg += " Received {} coordinate values!".format(lat_lons.shape[1])
|
854
915
|
logger.error(msg)
|
855
916
|
raise ValueError(msg)
|
856
917
|
|
857
918
|
return lat_lons
|
858
919
|
|
859
920
|
@classmethod
|
860
|
-
def lat_lon_coords(
|
861
|
-
|
921
|
+
def lat_lon_coords(
|
922
|
+
cls, lat_lons, res_file, sam_configs, tech=None, curtailment=None
|
923
|
+
):
|
862
924
|
"""
|
863
925
|
Generate ProjectPoints for gids nearest to given latitude longitudes
|
864
926
|
|
@@ -903,11 +965,13 @@ class ProjectPoints:
|
|
903
965
|
res_kwargs = {}
|
904
966
|
else:
|
905
967
|
res_cls = ResourceX
|
906
|
-
res_kwargs = {
|
968
|
+
res_kwargs = {"hsds": hsds}
|
907
969
|
|
908
|
-
logger.info(
|
909
|
-
|
910
|
-
|
970
|
+
logger.info(
|
971
|
+
"Converting latitude longitude coordinates into nearest "
|
972
|
+
"ProjectPoints"
|
973
|
+
)
|
974
|
+
logger.debug("- (lat, lon) pairs:\n{}".format(lat_lons))
|
911
975
|
with res_cls(res_file, **res_kwargs) as f:
|
912
976
|
gids = f.lat_lon_gid(lat_lons) # pylint: disable=no-member
|
913
977
|
|
@@ -915,37 +979,46 @@ class ProjectPoints:
|
|
915
979
|
gids = [gids]
|
916
980
|
else:
|
917
981
|
if len(gids) != len(np.unique(gids)):
|
918
|
-
uniques, pos, counts = np.unique(
|
919
|
-
|
982
|
+
uniques, pos, counts = np.unique(
|
983
|
+
gids, return_counts=True, return_inverse=True
|
984
|
+
)
|
920
985
|
duplicates = {}
|
921
986
|
for idx in np.where(counts > 1)[0]:
|
922
987
|
duplicate_lat_lons = lat_lons[np.where(pos == idx)[0]]
|
923
988
|
duplicates[uniques[idx]] = duplicate_lat_lons
|
924
989
|
|
925
|
-
msg = (
|
926
|
-
|
927
|
-
|
990
|
+
msg = (
|
991
|
+
"reV Cannot currently handle duplicate Resource gids! "
|
992
|
+
"The given latitude and longitudes map to the same "
|
993
|
+
"gids:\n{}".format(duplicates)
|
994
|
+
)
|
928
995
|
logger.error(msg)
|
929
996
|
raise RuntimeError(msg)
|
930
997
|
|
931
998
|
gids = gids.tolist()
|
932
999
|
|
933
|
-
logger.debug(
|
1000
|
+
logger.debug("- Resource gids:\n{}".format(gids))
|
934
1001
|
|
935
|
-
pp = cls(
|
936
|
-
|
1002
|
+
pp = cls(
|
1003
|
+
gids,
|
1004
|
+
sam_configs,
|
1005
|
+
tech=tech,
|
1006
|
+
res_file=res_file,
|
1007
|
+
curtailment=curtailment,
|
1008
|
+
)
|
937
1009
|
|
938
|
-
if
|
939
|
-
lat_lons = lat_lons[pp.df[
|
1010
|
+
if "points_order" in pp.df:
|
1011
|
+
lat_lons = lat_lons[pp.df["points_order"].values]
|
940
1012
|
|
941
|
-
pp._df[
|
942
|
-
pp._df[
|
1013
|
+
pp._df["latitude"] = lat_lons[:, 0]
|
1014
|
+
pp._df["longitude"] = lat_lons[:, 1]
|
943
1015
|
|
944
1016
|
return pp
|
945
1017
|
|
946
1018
|
@classmethod
|
947
|
-
def regions(
|
948
|
-
|
1019
|
+
def regions(
|
1020
|
+
cls, regions, res_file, sam_configs, tech=None, curtailment=None
|
1021
|
+
):
|
949
1022
|
"""
|
950
1023
|
Generate ProjectPoints for gids nearest to given latitude longitudes
|
951
1024
|
|
@@ -989,28 +1062,35 @@ class ProjectPoints:
|
|
989
1062
|
else:
|
990
1063
|
res_cls = ResourceX
|
991
1064
|
|
992
|
-
logger.info(
|
1065
|
+
logger.info("Extracting ProjectPoints for desired regions")
|
993
1066
|
points = []
|
994
1067
|
with res_cls(res_file, hsds=hsds) as f:
|
995
1068
|
meta = f.meta
|
996
1069
|
for region, region_col in regions.items():
|
997
|
-
logger.debug(
|
1070
|
+
logger.debug("- {}: {}".format(region_col, region))
|
998
1071
|
# pylint: disable=no-member
|
999
1072
|
gids = f.region_gids(region, region_col=region_col)
|
1000
|
-
logger.debug(
|
1073
|
+
logger.debug("- Resource gids:\n{}".format(gids))
|
1001
1074
|
if points:
|
1002
1075
|
duplicates = np.intersect1d(gids, points).tolist()
|
1003
1076
|
if duplicates:
|
1004
|
-
msg = (
|
1005
|
-
|
1006
|
-
|
1077
|
+
msg = (
|
1078
|
+
"reV Cannot currently handle duplicate "
|
1079
|
+
"Resource gids! The given regions containg the "
|
1080
|
+
"same gids:\n{}".format(duplicates)
|
1081
|
+
)
|
1007
1082
|
logger.error(msg)
|
1008
1083
|
raise RuntimeError(msg)
|
1009
1084
|
|
1010
1085
|
points.extend(gids.tolist())
|
1011
1086
|
|
1012
|
-
pp = cls(
|
1013
|
-
|
1087
|
+
pp = cls(
|
1088
|
+
points,
|
1089
|
+
sam_configs,
|
1090
|
+
tech=tech,
|
1091
|
+
res_file=res_file,
|
1092
|
+
curtailment=curtailment,
|
1093
|
+
)
|
1014
1094
|
|
1015
1095
|
meta = meta.loc[pp.sites]
|
1016
1096
|
cols = list(set(regions.values()))
|