def from_string(prjs): """Turn a PROJ.4 string into a mapping of parameters. Bare parameters like "+no_defs" are given a value of ``True``. All keys are checked against the ``all_proj_keys`` list. EPSG:nnnn is allowed. JSON text-encoded strings are allowed. """ if '{' in prjs: # may be json, try to decode it try: val = json.loads(prjs, strict=False) except ValueError: raise CRSError('crs appears to be JSON but is not valid') if not val: raise CRSError("crs is empty JSON") else: return val if prjs.strip().upper().startswith('EPSG:'): return from_epsg(prjs.split(':')[1]) parts = [o.lstrip('+') for o in prjs.strip().split()] def parse(v): if v in ('True', 'true'): return True elif v in ('False', 'false'): return False else: try: return int(v) except ValueError: pass try: return float(v) except ValueError: return v items = map( lambda kv: len(kv) == 2 and (kv[0], parse(kv[1])) or (kv[0], True), (p.split('=') for p in parts)) out = dict((k, v) for k, v in items if k in all_proj_keys) if not out: raise CRSError("crs is empty or invalid: {}".format(prjs)) return out
def from_string(cls, string, morph_from_esri_dialect=False): """Make a CRS from an EPSG, PROJ, or WKT string Parameters ---------- string : str An EPSG, PROJ, or WKT string. morph_from_esri_dialect : bool, optional If True, items in the input using Esri's dialect of WKT will be replaced by OGC standard equivalents. Returns ------- CRS """ try: string = string.strip() except AttributeError: pass if not string: raise CRSError("CRS is empty or invalid: {!r}".format(string)) elif string.upper().startswith('EPSG:'): auth, val = string.split(':') if not val: raise CRSError("Invalid CRS: {!r}".format(string)) return cls.from_epsg(val) elif string.startswith('{') or string.startswith('['): # may be json, try to decode it try: val = json.loads(string, strict=False) except ValueError: raise CRSError('CRS appears to be JSON but is not valid') if not val: raise CRSError("CRS is empty JSON") else: return cls.from_dict(**val) elif string.endswith("]"): return cls.from_wkt( string, morph_from_esri_dialect=morph_from_esri_dialect) elif "=" in string: return cls.from_proj4(string) obj = cls() obj._crs = _CRS.from_user_input( string, morph_from_esri_dialect=morph_from_esri_dialect) return obj
def from_user_input(cls, value, morph_from_esri_dialect=False): """Make a CRS from various input Dispatches to from_epsg, from_proj, or from_string Parameters ---------- value : obj A Python int, dict, or str. morph_from_esri_dialect : bool, optional If True, items in the input using Esri's dialect of WKT will be replaced by OGC standard equivalents. Returns ------- CRS """ if isinstance(value, cls): return value elif hasattr(value, "to_wkt") and callable(value.to_wkt): return cls.from_wkt( value.to_wkt(), morph_from_esri_dialect=morph_from_esri_dialect, ) elif isinstance(value, int): return cls.from_epsg(value) elif isinstance(value, dict): return cls(**value) elif isinstance(value, string_types): return cls.from_string( value, morph_from_esri_dialect=morph_from_esri_dialect) else: raise CRSError("CRS is invalid: {!r}".format(value))
def to_dict(self, projjson=False): """Convert CRS to a PROJ dict .. note:: If there is a corresponding EPSG code, it will be used when returning PROJ parameter dict. .. versionadded:: 1.3.0 Parameters ---------- projjson: bool, default=False If True, will convert to PROJ JSON dict (Requites GDAL 3.1+ and PROJ 6.2+). If False, will convert to PROJ parameter dict. Returns ------- dict """ if self._crs is None: raise CRSError("Undefined CRS has no dict representation") if projjson: text = self._crs.projjson() return json.loads(text) if text else {} epsg_code = self.to_epsg() if epsg_code: return {'init': 'epsg:{}'.format(epsg_code)} else: try: return self._crs.to_dict() except CRSError: return {}
def from_any(cls, proj: dict or str or int): """ Makes CRS from any supported system Parameters ---------- proj: dict or str or int Projection in PROJ, EPSG, WKT or CF. Returns ------- CRS: XCRS """ if isinstance(proj, dict): try: return cls.from_dict(proj) except CRSError: return cls.from_cf_dict(proj) except Exception: raise CRSError("{} is neither a PROJ4 or CF dict".format(proj)) elif isinstance(proj, str): for method in ['from_string', 'from_proj4', 'from_wkt']: try: return getattr(cls, method)(proj) except CRSError: pass raise CRSError( "{} is neither a EPSG, PROJ4 or WKT string".format(proj)) elif isinstance(proj, int): try: return cls.from_epsg(proj) except CRSError: raise CRSError("{} is not valid EPSG code".format(proj)) elif isinstance(proj, cls): return proj else: raise IOError( "{} is not valid data type. Only dictionary, string or integer are supported" .format(proj))
def _to_crs_str(value): # After rasterio=1.0.14 WKT is backbone so try it first try: crs_ = CRS.from_wkt(value) crs_.is_valid except CRSError as err: logger.debug('Could not parse CRS as WKT', err) try: crs_ = CRS.from_string(value) crs_.is_valid except CRSError as err: logger.debug('Could not parse CRS as Proj4', err) raise CRSError('Could not interpret CRS input as ' 'either WKT or Proj4') return crs_
def _parse_crs(crs): """Parse a coordinate reference system from a variety of representations. Parameters ---------- crs : {str, dict, int, CRS} Must be either a rasterio CRS object, a proj-string, rasterio supported dictionary, WKT string, or EPSG integer. Returns ------- rasterio.crs.CRS The parsed CRS. Raises ------ CRSError Raises an error if the input cannot be parsed. """ # # NOTE: This doesn't currently throw an error if the EPSG code is invalid. # parsed = None if isinstance(crs, CRS): parsed = crs elif isinstance(crs, str): try: # proj-string or wkt parsed = CRS.from_string(crs) except CRSError: # wkt parsed = CRS.from_wkt(crs) elif isinstance(crs, dict): parsed = CRS(crs) elif isinstance(crs, int): parsed = CRS.from_epsg(crs) elif isinstance(crs, pyproj.Proj): parsed = CRS.from_proj4(crs.proj4_init) if parsed is None or not parsed.is_valid: raise CRSError('Could not parse CRS: {}'.format(crs)) return parsed
def reproject(self, dst_crs): """ Reprojects all the features to the new crs Args: dst_crs: rasterio.CRS or any acceptable by your rasterio version input (str, dict, epsg code), ot 'utm' Returns: new reprojected FeatureCollection """ if isinstance(dst_crs, str) and dst_crs == 'utm': lon1, lat1, lon2, lat2 = self.index.bounds dst_crs = _utm_zone((lat1 + lat2)/2, (lon1 + lon2)/2) else: dst_crs = dst_crs if isinstance(dst_crs, CRS) else CRS.from_user_input(dst_crs) # Old rasterio compatibility: a separate check for validity if not dst_crs.is_valid: raise CRSError('Invalid CRS {} given'.format(dst_crs)) features = [f.reproject(dst_crs) for f in self.features] return FeatureCollection(features, dst_crs)
def get_extent(ds): """Extract the extent (bounding box) from the dataset. Parameters ---------- ds : xarray.Dataset The input dataset Returns ------- tuple The extent (left, bottom, right, top) in latitude and longitude coordinates. """ # # Check if latitude and longitude are stored as coordinates. # if 'lon' in ds.coords and 'lat' in ds.coords: return BoundingBox(left=ds.lon.values.min(), bottom=ds.lat.values.min(), right=ds.lon.values.max(), top=ds.lat.values.max()) # # Otherwise, get extent from projection information # by projecting the corner coordinates onto EPSG:4326 # to obtain the latitude and longitude at the four corners. # src_crs = get_crs(ds) if src_crs is None: raise CRSError('Could not determine the CRS.') dst_crs = CRS(init='epsg:4326') proj_bounds = get_bounds(ds) bounds = rasterio.warp.transform_bounds(src_crs, dst_crs, **proj_bounds._asdict()) return BoundingBox(*bounds)
def to_dict(self): """Convert CRS to a PROJ4 dict Notes ----- If there is a corresponding EPSG code, it will be used. Returns ------- dict """ if self._crs is None: raise CRSError("Undefined CRS has no dict representation") else: epsg_code = self.to_epsg() if epsg_code: return {'init': 'epsg:{}'.format(epsg_code)} else: try: return self._crs.to_dict() except CRSError: return {}
def _reproject(ds, src_crs=None, dst_crs=None, dst_transform=None, width=None, height=None, res=None, extent=None, **kwargs): """Reproject a Dataset or DataArray. Parameters ---------- ds : xarray.Dataset or xarray.DataArray The input dataset src_crs : CRS-like, optional An object that can be parsed into a CRS. By default, try to infer from input dataset. dst_crs : CRS-like, optional An object that can be parsed into a CRS. By default, use the same CRS as the input dataset. dst_transform : affine.Affine, optional The geometric transform of the output dataset. width : int, optional The width of the output dataset. height : int, optional The height of the output dataset. res : tuple (float, float), optional The resolution of the output dataset. extent : tuple, optional The output extent. By default this is determined from the input data. **kwargs : dict, optional Extra keyword arguments for ``rasterio.warp.reproject``. Returns ------- xarray.Dataset or xarray.DataArray The projected dataset. """ if src_crs is None: src_crs = get_crs(ds) if src_crs is None: raise CRSError('Could not infer projection from input data. ' 'Please provide the parameter `src_crs`.') src_bounds = get_bounds(ds) if extent is not None: extent = BoundingBox(*extent) # # Only allow inferring of width or height from aspect ratio # if the CRS is not changed. # if dst_crs is None: dst_crs = src_crs if width is None and height is not None: width = int(ncols(ds) * height / nrows(ds)) elif height is None and width is not None: height = int(nrows(ds) * width / ncols(ds)) # Given: transform, shape # Given: transform, extent # Given: res, extent # Given: shape, res # Given: shape, extent if dst_transform is not None: # # If the transform is given, we also need the width and height or # the extent. # if width is not None and height is not None: pass elif extent is not None: # Calculate width and height from extent width = int(abs( (extent.right - extent.left) / dst_transform.a)) + 1 height = int(abs( (extent.top - extent.bottom) / dst_transform.e)) + 1 else: raise ValueError('Not enough information provided.') elif extent is not None: # # Transform can be calculated from extent, if either width and height # or the resolution are given. # if res is not None: width = int(abs((extent.right - extent.left) / res[0])) + 1 height = int(abs((extent.top - extent.bottom) / res[1])) + 1 # The following doesn't give the correct result. dst_transform = rasterio.transform.from_bounds(*extent, width=width - 1, height=height - 1) else: # # If neither the transform nor the extent are given, infer the best # possible parameters from the width, height, and the resolution. # dst_transform, width, height = \ rasterio.warp.calculate_default_transform( src_crs, dst_crs, ncols(ds), nrows(ds), resolution=res, dst_width=width, dst_height=height, **src_bounds._asdict()) src_transform = get_transform(ds) src_dims = get_dims(ds) dst_crs = _parse_crs(dst_crs) # # Prepare new x and y coordinate arrays # dst_x, _ = dst_transform * (np.arange(width), np.zeros(width, dtype=int)) _, dst_y = dst_transform * (np.zeros(height, dtype=int), np.arange(height)) dst_coords = {'x': dst_x, 'y': dst_y} # # Handle the case where there are extra dimensions, e.g. 'time' # or 'band' # extra_dims = set(src_dims) - {'y', 'x'} for c in extra_dims: dst_coords[c] = ds.coords[c] def _reproject_da(da, shape): # # Reproject a single data array # coord_dims = tuple(c for c in ('y', 'x') if c in da.dims) extra_dims = set(da.dims) - set(coord_dims) # Preserve original dimension order orig_dim_order = get_dims(da) ordered_extra_dims = \ tuple(d for d in orig_dim_order if d in extra_dims) dim_order = ordered_extra_dims + coord_dims # Determine best resampling method from data type if np.issubdtype(da.dtype, np.integer): nodata = 0 default_resampling = rasterio.warp.Resampling.nearest else: nodata = np.nan default_resampling = rasterio.warp.Resampling.bilinear if 'resampling' not in kwargs: kwargs['resampling'] = default_resampling # Get values as numpy array such that last two axes are # y and x values = da.transpose(*dim_order, transpose_coords=True).values # Flatten multidimensional data to ONE extra dimension if values.ndim > 2: output_shape = values.shape[:-2] + shape values = values.reshape((-1, ) + values.shape[-2:]) output_shape_flat = (values.shape[0], ) + shape else: output_shape = shape output_shape_flat = shape # rasterio cannot deal with float16 if da.dtype == np.float16: values = values.astype(np.float32) # Integers cannot be set to nan if np.issubdtype(values.dtype, np.integer): values = values.astype(np.float32) output = np.zeros(output_shape_flat, dtype=values.dtype) output[:] = np.nan rasterio.warp.reproject(values, output, src_transform=src_transform, src_crs=src_crs, dst_transform=dst_transform, dst_crs=dst_crs, dst_nodata=nodata, **kwargs) if da.dtype == np.float16: output = output.astype(np.float16) # Final reshape in case the input was one-dimensional return output.reshape(output_shape) if isinstance(ds, xr.Dataset): result = xr.Dataset(coords=dst_coords) # # Also reproject coordinate arrays that are defined over # x and y # for v in ds.coords: # # If the projection is the same (i.e. resampling), # also reproject coordinate arrays # that are defined over only one variable. # if dst_crs == src_crs and v not in ds.dims: if len(ds.coords[v].dims) == 0: result.coords[v] = (ds.coords[v].dims, ds.coords[v].data) else: expanded = _expand_var_to_xy(ds.coords[v], ds.coords) if ds.coords[v].dims == ('x', ): result.coords[v] = (('y', 'x'), _reproject_da( expanded, (height, width))) elif ds.coords[v].dims == ('y', ): result.coords[v] = (('y', 'x'), _reproject_da( expanded, (height, width))) # Remove redundant dimensions coords = _collapse_coords(result.coords[v]) result.coords[v] = (coords.dims, coords) if not set(ds.coords[v].dims).issuperset({'x', 'y'}): continue shape = (height, width) result.coords[v] = (('y', 'x'), _reproject_da(ds.coords[v], shape)) # # Reproject the actual data # for v in ds.data_vars: vdims = _get_projection_dim_order(ds[v]) common = set(vdims).intersection(ds[v].dims) shape = (height, width) if set(ds[v].dims) == set(vdims) or set(ds[v].dims) == {'y', 'x'}: result[v] = (vdims, _reproject_da(ds[v], shape)) # Reorder dimensions of each variable to match original. result[v] = result[v].transpose(*get_dims(ds[v]), transpose_coords=True) elif common == {'x'} or common == {'y'}: # Does the data contain either x or y dimension? # Expand to grid and then reproject as two dimensional. result[v] = (vdims, _reproject_da(_expand_var_to_xy(ds[v], ds.coords), shape)) else: # The variable doesn't contain either y or x dimension. result[v] = (ds[v].dims, ds[v].data) # # Create lat and lon coordinates # # if 'lat' in ds.coords and 'lon' in ds.coords: # lon, lat = rasterio.warp.transform( # src_crs, dst_crs, ds.coords['x'], ds.coords['y']) # result.coords['lon'] = (('x',), lon) # result.coords['lat'] = (('y',), lat) elif isinstance(ds, xr.DataArray): shape = (height, width) dst_dims = _get_projection_dim_order(ds) result = xr.DataArray(_reproject_da(ds, shape), dims=dst_dims, coords=dst_coords, name=ds.name) # Reorder dimensions to match original. result = result.transpose(*get_dims(ds), transpose_coords=True) # # Add metadata # result.attrs = ds.attrs # Serialize transform to tuple and store in metadata result.attrs['transform'] = dst_transform[:6] # Store CRS info in metadata result.attrs['crs'] = dst_crs.to_string() result.attrs['coordinate_system_string'] = dst_crs.wkt # Store new data shape in metadata result.attrs['lines'] = nrows(result) result.attrs['samples'] = ncols(result) _add_latlon(result) return result