def __init__(self, raster, geo_grid: GeoGrid = None, no_data_value=None, crs=None): """ RasterMap constructor :param raster: raster file or numpy array :param geo_grid: GeoGrid instance :param no_data_value: data to be regarded as "no_data" :param crs: projection used for the raster :Example: >>> """ check_type(raster, (str, np.ndarray)) if type(raster) == np.ndarray: raster_file = None try: test_fit = geo_grid.data_fits(raster) crs = pyproj.CRS(crs) # crs = proj4_from(crs) if not test_fit: raise RasterMapError("Input geo grid does not fit raster") except AttributeError: raise RasterMapError("Geo grid argument has not been set") except (ValueError, TypeError): raise RasterMapError("Invalid projection: crs='{}'".format(crs)) else: raster_file = raster try: geo_grid = GeoGrid.from_raster_file(raster) crs = crs_from_raster(raster) raster = raster_to_array(raster) except RuntimeError: raise RasterMapError("Invalid/unknown file '%s'" % raster_file) # Set attributes self._raster_file = raster_file self._geo_grid = geo_grid self._raster_array = np.array(raster, dtype='float64') # Ensure compatibility with NaNs self._crs = crs self._res = self._geo_grid.res self._x_origin = self._geo_grid.geo_transform[0] self._y_origin = self._geo_grid.geo_transform[3] self._x_size = self._geo_grid.num_x self._y_size = self._geo_grid.num_y self._shape = self._raster_array.shape self._no_data_value = no_data_value if no_data_value is not None: self._raster_array[self._raster_array == no_data_value] = np.nan # Use attribute (raster_array) rather than # instance (self == ...) to avoid 'recursion' error with decorator above # Available filters self._filters = {"majority_filter": self._majority_filter, "sieve": self._gdal_sieve}
def _apply_operator(self, other, operator_function, operator_str): if isinstance(other, RasterMap): if self.geo_grid == other.geo_grid: return self.raster_array.__getattribute__(operator_function)(other.raster_array) else: raise RasterMapError("Raster maps are not defined on the same geo grid") else: try: return self.raster_array.__getattribute__(operator_function)(other) except TypeError: raise RasterMapError("Unsupported operand type(s) for {}: '{}' and '{}'".format(operator_str, type(self).__name__, type(other).__name__)) except ValueError: raise RasterMapError("No match for operand {} between '{}' and '{}'".format(operator_str, type(self).__name__, type(other).__name__)) except Exception as e: raise RuntimeError("Unexpected error when applying {} between '{}' and '{}': {}" .format(operator_str, type(self).__name__, type(other).__name__, e))
def __setitem__(self, key, value): if type(key) == type(self): self.raster_array.__setitem__(key.raster_array, value) else: try: self.raster_array.__setitem__(key, value) except IndexError: raise RasterMapError("Invalid indexing") except Exception as e: raise RuntimeError("Unknown error while setting data in raster map: {}".format(e)) return self
def __getitem__(self, key): if key.__class__ == type(self): key = key.raster_array if key.__class__ == slice: key = (key, key) if key.__class__ == tuple: if key[0].__class__ == int and key[1].__class__ == int: return self.raster_array.__getitem__(key) else: try: return self.__class__(self.raster_array.__getitem__(key), self.geo_grid.__getitem__(key), no_data_value=self.no_data_value, crs=self.crs) except IndexError: raise RasterMapError("Invalid indexing") except Exception as e: raise RuntimeError("Unknown error while getting data in raster map: {}".format(e)) elif key.__class__ == np.ndarray: return self.raster_array.__getitem__(key) else: raise RasterMapError("Invalid indexing")
def to_crs(self, crs): """ Reproject raster onto new CRS :param crs: :return: """ try: if self.crs != crs: srs = srs_from(crs) return self._gdal_warp(srs) else: return self.copy() except ValueError: raise RasterMapError("Invalid CRS '%s'" % crs)
def _apply_comparison(self, other, operator_function, operator_str): valid_values = np.full(self.raster_array.shape, False) if isinstance(other, RasterMap): other = other.raster_array try: valid_values[(~np.isnan(self.raster_array)) & (~np.isnan(other))] = True valid_values[valid_values] = self.raster_array[valid_values].__getattribute__(operator_function)( other[valid_values]) return valid_values except (TypeError, IndexError): valid_values[~np.isnan(self.raster_array)] = True valid_values[valid_values] = self.raster_array[valid_values].__getattribute__(operator_function)(other) return valid_values except Exception as e: raise RasterMapError("Comparison for '{}' has failed ({})".format(operator_str, e))
def to_file(self, raster_file, data_type=None): """ Save raster to file :param raster_file: :param data_type: data type :return: """ if data_type is None: dtype = self._numpy_to_gdal_type[self.data_type] else: try: dtype = self._numpy_to_gdal_type[data_type] except KeyError: raise RasterMapError("Invalid data type '%s'" % data_type) out = array_to_raster(raster_file, self.raster_array_without_nans, self.geo_grid, self.crs, datatype=dtype, no_data_value=self.no_data_value) return out
def get_raster_at(self, layer=None, ll_point=None, ur_point=None): """ Extract sub-raster in current raster map Extract new raster from current raster map by giving either a geo lines_ or a new geo-square defined by lower-left point (ll_point) and upper right point (ur_point) such as for geo grids. :param layer: GeoLayer instance :param ll_point: tuple of 2 values (lat, lon) :param ur_point: tuple of 2 values (lat, lon) :return: RasterMap """ if layer is not None: check_proj(layer.crs, self.crs) ll_point = (layer.bounds[1], layer.bounds[0]) # Warning: (lat, lon) in that order ! ur_point = (layer.bounds[3], layer.bounds[2]) try: ll_point_r, ll_point_c = self._geo_grid.latlon_to_2d_index(ll_point[0], ll_point[1]) ur_point_r, ur_point_c = self._geo_grid.latlon_to_2d_index(ur_point[0], ur_point[1]) return self.raster_array[ur_point_r:ll_point_r + 1, ll_point_c:ur_point_c + 1], \ self.geo_grid[ur_point_r:ll_point_r + 1, ll_point_c:ur_point_c + 1] except GeoGridError: raise RasterMapError("Lower left or/and upper right points have not been rightly defined")
def disaggregate(self, factor: int = 1, method: str = 'nearest', no_limit=False): """ Disaggregate raster cells :param factor: scale factor for disaggregation (number of cells) :param method: 'linear' or 'nearest' (default = 'nearest') :param no_limit: no limit for disaggregation (default=False) :return: """ from scipy.interpolate import RegularGridInterpolator upper_limit = np.inf if no_limit else 10**8 / (self.geo_grid.num_x * self.geo_grid.num_y) if 1 < factor <= upper_limit: new_geo_grid = self.geo_grid.to_res(self.res/factor) try: interpolator = RegularGridInterpolator((self.geo_grid.lats, self.geo_grid.lons), self.raster_array[::-1, :], bounds_error=False, method=method) except ValueError: raise RasterMapError("Method should be 'linear' or 'nearest' but is {}".format(method)) return interpolator((new_geo_grid.latitude, new_geo_grid.longitude)), new_geo_grid else: warnings.warn("Invalid factor, factor = 1, or exceeded limit (set no_limit=True). Return copy of object") return self.copy()