Source code for reV.supply_curve.extent

# -*- coding: utf-8 -*-
"""
reV supply curve extent
"""

import logging

import numpy as np
import pandas as pd
from rex.utilities.utilities import get_chunk_ranges

from reV.handlers.exclusions import LATITUDE, LONGITUDE, ExclusionLayers
from reV.utilities import SupplyCurveField
from reV.utilities.exceptions import SupplyCurveError, SupplyCurveInputError

logger = logging.getLogger(__name__)


[docs]class SupplyCurveExtent: """Supply curve full extent framework. This class translates the 90m exclusion grid to the aggregated supply curve resolution.""" def __init__(self, f_excl, resolution=64): """ Parameters ---------- f_excl : str | list | tuple | ExclusionLayers File path(s) to the exclusions grid, or pre-initialized ExclusionLayers. The exclusions dictate the SC analysis extent. resolution : int Number of exclusion points per SC point along an axis. This number**2 is the total number of exclusion points per SC point. """ logger.debug( "Initializing SupplyCurveExtent with res {} from: {}".format( resolution, f_excl ) ) if not isinstance(resolution, int): raise SupplyCurveInputError( "Supply Curve resolution needs to be " "an integer but received: {}".format(type(resolution)) ) if isinstance(f_excl, (str, list, tuple)): self._excl_fpath = f_excl self._excls = ExclusionLayers(f_excl) elif isinstance(f_excl, ExclusionLayers): self._excl_fpath = f_excl.h5_file self._excls = f_excl else: raise SupplyCurveInputError( "SupplyCurvePoints needs an " "exclusions file path, or " "ExclusionLayers handler but " "received: {}".format(type(f_excl)) ) self._excl_shape = self.exclusions.shape # limit the resolution to the exclusion shape. self._res = int(np.min(list(self.excl_shape) + [resolution])) self._n_rows = None self._n_cols = None self._cols_of_excl = None self._rows_of_excl = None self._excl_row_slices = None self._excl_col_slices = None self._latitude = None self._longitude = None self._points = None self._sc_col_ind, self._sc_row_ind = np.meshgrid( np.arange(self.n_cols), np.arange(self.n_rows) ) self._sc_col_ind = self._sc_col_ind.flatten() self._sc_row_ind = self._sc_row_ind.flatten() logger.debug( "Initialized SupplyCurveExtent with shape {} from " "exclusions with shape {}".format(self.shape, self.excl_shape) ) def __len__(self): """Total number of supply curve points.""" return self.n_rows * self.n_cols def __enter__(self): return self def __exit__(self, type, value, traceback): self.close() if type is not None: raise def __getitem__(self, gid): """Get SC extent meta data corresponding to an SC point gid.""" if gid >= len(self): raise KeyError( "SC extent with {} points does not contain SC " "point gid {}.".format(len(self), gid) ) return self.points.loc[gid]
[docs] def close(self): """Close all file handlers.""" self._excls.close()
@property def shape(self): """Get the Supply curve shape tuple (n_rows, n_cols). Returns ------- shape : tuple 2-entry tuple representing the full supply curve extent. """ return (self.n_rows, self.n_cols) @property def exclusions(self): """Get the exclusions object. Returns ------- _excls : ExclusionLayers ExclusionLayers h5 handler object. """ return self._excls @property def resolution(self): """Get the 1D resolution. Returns ------- _res : int Number of exclusion points per SC point along an axis. This number**2 is the total number of exclusion points per SC point. """ return self._res @property def excl_shape(self): """Get the shape tuple of the exclusion file raster. Returns ------- tuple """ return self._excl_shape @property def excl_rows(self): """Get the unique row indices identifying the exclusion points. Returns ------- excl_rows : np.ndarray Array of exclusion row indices. """ return np.arange(self.excl_shape[0]) @property def excl_cols(self): """Get the unique column indices identifying the exclusion points. Returns ------- excl_cols : np.ndarray Array of exclusion column indices. """ return np.arange(self.excl_shape[1]) @property def rows_of_excl(self): """List representing the supply curve points rows and which exclusions rows belong to each supply curve row. Returns ------- _rows_of_excl : list List representing the supply curve points rows. Each list entry contains the exclusion row indices that are included in the sc point. """ if self._rows_of_excl is None: self._rows_of_excl = self._chunk_excl( self.excl_rows, self.resolution ) return self._rows_of_excl @property def cols_of_excl(self): """List representing the supply curve points columns and which exclusions columns belong to each supply curve column. Returns ------- _cols_of_excl : list List representing the supply curve points columns. Each list entry contains the exclusion column indices that are included in the sc point. """ if self._cols_of_excl is None: self._cols_of_excl = self._chunk_excl( self.excl_cols, self.resolution ) return self._cols_of_excl @property def excl_row_slices(self): """ List representing the supply curve points rows and which exclusions rows belong to each supply curve row. Returns ------- _excl_row_slices : list List representing the supply curve points rows. Each list entry contains the exclusion row slice that are included in the sc point. """ if self._excl_row_slices is None: self._excl_row_slices = self._excl_slices( self.excl_rows, self.resolution ) return self._excl_row_slices @property def excl_col_slices(self): """ List representing the supply curve points cols and which exclusions cols belong to each supply curve col. Returns ------- _excl_col_slices : list List representing the supply curve points cols. Each list entry contains the exclusion col slice that are included in the sc point. """ if self._excl_col_slices is None: self._excl_col_slices = self._excl_slices( self.excl_cols, self.resolution ) return self._excl_col_slices @property def n_rows(self): """Get the number of supply curve grid rows. Returns ------- n_rows : int Number of row entries in the full supply curve grid. """ if self._n_rows is None: self._n_rows = int(np.ceil(self.excl_shape[0] / self.resolution)) return self._n_rows @property def n_cols(self): """Get the number of supply curve grid columns. Returns ------- n_cols : int Number of column entries in the full supply curve grid. """ if self._n_cols is None: self._n_cols = int(np.ceil(self.excl_shape[1] / self.resolution)) return self._n_cols @property def latitude(self): """ Get supply curve point latitudes Returns ------- ndarray """ if self._latitude is None: lats = [] lons = [] sc_cols, sc_rows = np.meshgrid( np.arange(self.n_cols), np.arange(self.n_rows) ) for r, c in zip(sc_rows.flatten(), sc_cols.flatten()): r = self.excl_row_slices[r] c = self.excl_col_slices[c] lats.append(self.exclusions[LATITUDE, r, c].mean()) lons.append(self.exclusions[LONGITUDE, r, c].mean()) self._latitude = np.array(lats, dtype="float32") self._longitude = np.array(lons, dtype="float32") return self._latitude @property def longitude(self): """ Get supply curve point longitudes Returns ------- ndarray """ if self._longitude is None: lats = [] lons = [] sc_cols, sc_rows = np.meshgrid( np.arange(self.n_cols), np.arange(self.n_rows) ) for r, c in zip(sc_rows.flatten(), sc_cols.flatten()): r = self.excl_row_slices[r] c = self.excl_col_slices[c] lats.append(self.exclusions[LATITUDE, r, c].mean()) lons.append(self.exclusions[LONGITUDE, r, c].mean()) self._latitude = np.array(lats, dtype="float32") self._longitude = np.array(lons, dtype="float32") return self._longitude @property def lat_lon(self): """ 2D array of lat, lon coordinates for all sc points Returns ------- ndarray """ return np.dstack((self.latitude, self.longitude))[0] @property def row_indices(self): """Get a 1D array of row indices for every gid. That is, this property has length == len(gids) and row_indices[sc_gid] yields the row index of the target supply curve gid Returns ------- ndarray """ return self._sc_row_ind @property def col_indices(self): """Get a 1D array of col indices for every gid. That is, this property has length == len(gids) and col_indices[sc_gid] yields the col index of the target supply curve gid Returns ------- ndarray """ return self._sc_col_ind @property def points(self): """Get the summary dataframe of supply curve points. Returns ------- _points : pd.DataFrame Supply curve points with columns for attributes of each sc point. """ if self._points is None: self._points = pd.DataFrame( { "row_ind": self.row_indices.copy(), "col_ind": self.col_indices.copy(), } ) self._points.index.name = "gid" # sc_point_gid return self._points @staticmethod def _chunk_excl(arr, resolution): """Split an array into a list of arrays with len == resolution. Parameters ---------- arr : np.ndarray 1D array to be split into chunks. resolution : int Resolution of the chunks. Returns ------- chunks : list List of arrays, each with length equal to self.resolution (except for the last array in the list which is the remainder). """ chunks = get_chunk_ranges(len(arr), resolution) chunks = list(map(lambda i: np.arange(*i), chunks)) return chunks @staticmethod def _excl_slices(arr, resolution): """Split row or col ind into slices of excl rows or slices Parameters ---------- arr : np.ndarray 1D array to be split into slices resolution : int Resolution of the sc points Returns ------- slices : list List of arr slices, each with length equal to self.resolution (except for the last array in the list which is the remainder). """ slices = get_chunk_ranges(len(arr), resolution) slices = list(map(lambda i: slice(*i), slices)) return slices
[docs] def get_sc_row_col_ind(self, gid): """Get the supply curve grid row and column index values corresponding to a supply curve gid. Parameters ---------- gid : int Supply curve point gid. Returns ------- row_ind : int Row index that the gid is located at in the sc grid. col_ind : int Column index that the gid is located at in the sc grid. """ row_ind = self.points.loc[gid, "row_ind"] col_ind = self.points.loc[gid, "col_ind"] return row_ind, col_ind
[docs] def get_excl_slices(self, gid): """Get the row and column slices of the exclusions grid corresponding to the supply curve point gid. Parameters ---------- gid : int Supply curve point gid. Returns ------- row_slice : slice Exclusions grid row slice corresponding to the sc point gid. col_slice : slice Exclusions grid col slice corresponding to the sc point gid. """ if gid >= len(self): raise SupplyCurveError( 'Requested gid "{}" is out of bounds for ' 'supply curve points with length "{}".'.format(gid, len(self)) ) row_slice = self.excl_row_slices[self.row_indices[gid]] col_slice = self.excl_col_slices[self.col_indices[gid]] return row_slice, col_slice
[docs] def get_flat_excl_ind(self, gid): """Get the index values of the flattened exclusions grid corresponding to the supply curve point gid. Parameters ---------- gid : int Supply curve point gid. Returns ------- excl_ind : np.ndarray Index values of the flattened exclusions grid corresponding to the SC gid. """ row_slice, col_slice = self.get_excl_slices(gid) excl_ind = self.exclusions.iarr[row_slice, col_slice].flatten() return excl_ind
[docs] def get_excl_points(self, dset, gid): """Get the exclusions data corresponding to a supply curve gid. Parameters ---------- dset : str | int Used as the first arg in the exclusions __getitem__ slice. String can be "meta", integer can be layer number. gid : int Supply curve point gid. Returns ------- excl_points : pd.DataFrame Exclusions data reduced to just the exclusion points associated with the requested supply curve gid. """ row_slice, col_slice = self.get_excl_slices(gid) return self.exclusions[dset, row_slice, col_slice]
[docs] def get_coord(self, gid): """Get the centroid coordinate for the supply curve gid point. Parameters ---------- gid : int Supply curve point gid. Returns ------- coord : tuple Two entry coordinate tuple: (latitude, longitude) """ lat = self.latitude[gid] lon = self.longitude[gid] return (lat, lon)
[docs] def valid_sc_points(self, tm_dset): """ Determine which sc_point_gids contain resource gids and are thus valid supply curve points Parameters ---------- tm_dset : str Techmap dataset name Returns ------- valid_gids : ndarray Vector of valid sc_point_gids that contain resource gis """ logger.info('Getting valid SC points from "{}"...'.format(tm_dset)) valid_bool = np.zeros(self.n_rows * self.n_cols) tm = self._excls[tm_dset] gid = 0 for r in self.excl_row_slices: for c in self.excl_col_slices: if np.any(tm[r, c] != -1): valid_bool[gid] = 1 gid += 1 valid_gids = np.where(valid_bool == 1)[0].astype(np.uint32) logger.info( "Found {} valid SC points out of {} total possible " "(valid SC points that map to valid resource gids)".format( len(valid_gids), len(valid_bool) ) ) return valid_gids
[docs] def get_slice_lookup(self, sc_point_gids): """ Get exclusion slices for all requested supply curve point gids Parameters ---------- sc_point_gids : list | ndarray List or 1D array of sc_point_gids to get exclusion slices for Returns ------- dict lookup mapping sc_point_gid to exclusion slice """ return {g: self.get_excl_slices(g) for g in sc_point_gids}