def test_from_string(): wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') assert wgs84_crs.to_dict() == {'init': 'epsg:4326'} # Make sure this doesn't get handled using the from_epsg() even though 'epsg' is in the string epsg_init_crs = CRS.from_string('+init=epsg:26911') assert epsg_init_crs.to_dict() == {'init': 'epsg:26911'}
def test_from_epsg_string(): crs_dict = CRS.from_string('epsg:4326') assert crs_dict['init'].lower() == 'epsg:4326' # Test with invalid EPSG code with pytest.raises(ValueError): assert CRS.from_string('epsg:xyz')
def test_from_string(): wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') assert wgs84_crs.to_dict() == {'no_defs': True, 'ellps': 'WGS84', 'datum': 'WGS84', 'proj': 'longlat'} # Make sure this doesn't get handled using the from_epsg() even though 'epsg' is in the string epsg_init_crs = CRS.from_string('+units=m +init=epsg:26911 +no_defs=True') assert epsg_init_crs.to_dict() == {'units': 'm', 'init': 'epsg:26911', 'no_defs': True}
def test_is_projected(): assert CRS({'init': 'EPSG:3857'}).is_projected is True lcc_crs = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert CRS(lcc_crs).is_projected is True wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') assert CRS(wgs84_crs).is_projected is False
def test_from_proj4_json(): json_str = '{"proj": "longlat", "ellps": "WGS84", "datum": "WGS84"}' crs_dict = CRS.from_string(json_str) assert crs_dict == json.loads(json_str) # Test with invalid JSON code with pytest.raises(ValueError): assert CRS.from_string('{foo: bar}')
def test_is_geographic_from_string(): wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') assert wgs84_crs.is_geographic is True nad27_crs = CRS.from_string('+proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs') assert nad27_crs.is_geographic is True lcc_crs = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert lcc_crs.is_geographic is False
def test_bare_parameters(): """ Make sure that bare parameters (e.g., no_defs) are handled properly, even if they come in with key=True. This covers interaction with pyproj, which makes presents bare parameters as key=<bool>.""" # Example produced by pyproj crs_dict = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert crs_dict.get('no_defs', False) is True crs_dict = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=False +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert crs_dict.get('no_defs', True) is False
def test_symmetric_proj4(tmpdir): """ Test writing and reading proj4 string as attribute of variable """ ds = Dataset(str(tmpdir.join('test.nc')), 'w') proj4 = '+proj=stere +units=m +datum=WGS84 +lat_ts=60 +lat_0=90 +lon_0=263 +lat_1=60 +x_0=3475000 +y_0=7475000' ds.createVariable('data', 'S1') set_crs(ds, 'data', Proj(proj4), set_proj4_att=True) out_proj4 = get_crs(ds, 'data') out_data = CRS.from_string(out_proj4).to_dict() assert len(out_data) == 9 # There should be 9 parameters assert CRS.from_string(proj4).to_dict() == out_data
def test_is_same_crs(): crs1 = CRS({'init': 'EPSG:4326'}) crs2 = CRS({'init': 'EPSG:3857'}) assert crs1 == crs1 assert crs1 != crs2 wgs84_crs = CRS.from_string('+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs') assert crs1 == wgs84_crs # Make sure that same projection with different parameter are not equal lcc_crs1 = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') lcc_crs2 = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc +x_0=0 +units=m +lat_2=77 +lat_1=45 +lat_0=0') assert lcc_crs1 != lcc_crs2
def test_get_crs(tmpdir): """ Test reading proj4 string from CF convention parameters """ ds = Dataset(str(tmpdir.join('test.nc')), 'w') data_var = ds.createVariable('data', 'S1') data_var.setncattr('grid_mapping', 'crs_Lambert') crs_var = ds.createVariable('crs_Lambert', 'S1') in_proj4 = '+proj=lcc +units=m +lat_1=30 +lat_2=60 +lat_0=47.5 +lon_0=-97 +x_0=3825000 +y_0=3200000' # These parameters match the above proj4 string ncatts = dict() ncatts['grid_mapping_name'] = 'lambert_conformal_conic' ncatts['latitude_of_projection_origin'] = 47.5 ncatts['longitude_of_central_meridian'] = -97 ncatts['standard_parallel'] = [30, 60] ncatts['false_northing'] = 3200000 ncatts['false_easting'] = 3825000 set_ncattrs(crs_var, ncatts) out_proj4 = get_crs(ds, 'data') assert out_proj4 is not None out_data = CRS.from_string(out_proj4).to_dict() assert len(out_data) == 8 # There should be 8 parameters assert CRS.from_string(in_proj4).to_dict() == out_data # Test WGS84 lat/long data_var = ds.createVariable('data2', 'S1') data_var.setncattr('grid_mapping', 'crs_latlong') crs_var = ds.createVariable('crs_latlong', 'S1') in_proj4 = '+proj=latlong +a={0} +rf={1}'.format(pj_ellps['WGS84']['a'], pj_ellps['WGS84']['rf']) # These parameters match the above proj4 string ncatts = dict() ncatts['grid_mapping_name'] = 'latitude_longitude' ncatts['semi_major_axis'] = 6378137.0 ncatts['inverse_flattening'] = 298.257223563 set_ncattrs(crs_var, ncatts) out_proj4 = get_crs(ds, 'data2') assert out_proj4 is not None out_data = CRS.from_string(out_proj4).to_dict() assert len(out_data) == 4 # There should be 4 parameters # Note: pyproj adds units=m even for latlong, which is incorrect but not our problem assert CRS.from_string(in_proj4 + ' +units=m').to_dict() == out_data
def index(self, *args, **kwargs): target_srs = self.crs # Annotations are always in WGS84 source_srs = CRS.from_string("EPSG:4326") args = transform_coordinates(source_srs, target_srs, [args[0]], [args[1]]) return self.dataset.index(*args, **kwargs)
def test_warped_vrt(path_rgb_byte_tif): """A VirtualVRT has the expected VRT properties.""" with rasterio.open(path_rgb_byte_tif) as src: vrt = WarpedVRT(src, crs=DST_CRS) assert vrt.dst_crs == CRS.from_string(DST_CRS) assert vrt.src_nodata == 0.0 assert vrt.dst_nodata == 0.0 assert vrt.tolerance == 0.125 assert vrt.resampling == Resampling.nearest assert vrt.warp_extras == {"init_dest": "NO_DATA"} assert vrt.mask_flag_enums == ([MaskFlags.nodata],) * 3
def get_band_ix(self, indexes, x, y): # Reproject to native data coordinates target_srs = self.crs # Annotations are always in WGS84 source_srs = CRS.from_string("EPSG:4326") transformed_x, transformed_y = transform_coordinates(source_srs, target_srs, [x], [y]) return list(self.dataset.sample([(transformed_x, transformed_y)], indexes=indexes))[0]
def test_empty_json(): with pytest.raises(CRSError): CRS.from_string('{}') with pytest.raises(CRSError): CRS.from_string('[]') with pytest.raises(CRSError): CRS.from_string('')
def retrieve_tilespecs(): """ Retrieve default tile specifications packaged within ``tilezilla`` Returns: dict: default tilespecs packaged within ``tilezilla`` as TileSpec objects """ tilespecs = json.loads(pkgutil.get_data('tilezilla', 'data/tile_specs.json').decode()) for key in tilespecs: tilespecs[key]['crs'] = CRS.from_string(tilespecs[key]['crs']) tilespecs[key] = TileSpec(desc=key, **tilespecs[key]) return tilespecs
def test_wrap_file(path_rgb_byte_tif): """A VirtualVRT has the expected dataset properties.""" with rasterio.open(path_rgb_byte_tif) as src: vrt = WarpedVRT(src, crs=DST_CRS) assert vrt.crs == CRS.from_string(DST_CRS) assert tuple(round(x, 1) for x in vrt.bounds) == ( -8789636.7, 2700460.0, -8524406.4, 2943560.2 ) assert vrt.name.startswith("WarpedVRT(") assert vrt.name.endswith("tests/data/RGB.byte.tif)") assert vrt.indexes == (1, 2, 3) assert vrt.nodatavals == (0, 0, 0) assert vrt.dtypes == ("uint8", "uint8", "uint8") assert vrt.read().shape == (3, 736, 803)
def test_warped_vrt_add_alpha(path_rgb_byte_tif): """A VirtualVRT has the expected VRT properties.""" with rasterio.open(path_rgb_byte_tif) as src: vrt = WarpedVRT(src, crs=DST_CRS, add_alpha=True) assert vrt.dst_crs == CRS.from_string(DST_CRS) assert vrt.src_nodata == 0.0 assert vrt.dst_nodata is None assert vrt.tolerance == 0.125 assert vrt.resampling == Resampling.nearest assert vrt.warp_extras == {"init_dest": "NO_DATA"} assert vrt.count == 4 assert vrt.mask_flag_enums == ( [MaskFlags.per_dataset, MaskFlags.alpha], ) * 3 + ( [MaskFlags.all_valid], )
def test_warped_vrt_msk_nodata(path_rgb_msk_byte_tif, caplog): """Specifying dst nodata also works for source with .msk""" with rasterio.open(path_rgb_msk_byte_tif) as src: vrt = WarpedVRT(src, crs=DST_CRS, nodata=0.0) assert vrt.dst_crs == CRS.from_string(DST_CRS) assert vrt.src_nodata is None assert vrt.dst_nodata == 0.0 assert vrt.count == 3 assert vrt.mask_flag_enums == ([MaskFlags.nodata],) * 3 caplog.set_level(logging.DEBUG) with rasterio.Env(CPL_DEBUG=True): masks = vrt.read_masks() assert masks[0, 0, 0] == 0 assert masks[0].mean() > 0 assert "RGB2.byte.tif.msk" in caplog.text
def crs_handler(ctx, param, value): """Get crs value from a template file or command line.""" retval = options.from_like_context(ctx, param, value) if retval is None and value: try: retval = json.loads(value) except ValueError: retval = value try: if isinstance(retval, dict): retval = CRS(retval) else: retval = CRS.from_string(retval) except CRSError: raise click.BadParameter( "'%s' is not a recognized CRS." % retval, param=param, param_hint='crs') return retval
def __init__(self, ul, crs, res, size, desc=None): self.ul = ul if isinstance(crs, six.string_types): self.crs = CRS.from_string(crs) elif isinstance(crs, int): self.crs = CRS.from_epsg(crs) else: self.crs = crs if not self.crs.is_valid: raise ValueError('Could not parse coordinate reference system ' 'string to a valid projection ({})'.format(crs)) self.crs_str = self.crs.to_string() self.res = res self.size = size self.desc = desc or 'unnamed' self._tiles = {}
def test_utm(tmpdir): ds = Dataset(str(tmpdir.join('test.nc')), 'w') proj4 = '+init=epsg:3157' # UTM Zone 10 ds.createVariable('data', 'S1') set_crs(ds, 'data', Proj(proj4), set_proj4_att=True) out_proj4 = get_crs(ds, 'data') out_data = CRS.from_string(out_proj4).to_dict() # ESPG will have been converted to long form assert len(out_data) == 6 expected = { u'zone': 10, u'ellps': u'GRS80', u'no_defs': True, u'proj': u'utm', u'units': u'm', u'towgs84': u'0,0,0,0,0,0,0' } assert expected == out_data
def test_warped_vrt_dimensions(path_rgb_byte_tif): """ A WarpedVRT with target dimensions has the expected dataset properties. """ with rasterio.open(path_rgb_byte_tif) as src: extent = (-20037508.34, 20037508.34) size = (2 ** 16) * 256 resolution = (extent[1] - extent[0]) / size dst_transform = affine.Affine( resolution, 0.0, extent[0], 0.0, -resolution, extent[1] ) vrt = WarpedVRT( src, crs=DST_CRS, width=size, height=size, transform=dst_transform ) assert vrt.dst_crs == CRS.from_string(DST_CRS) assert vrt.src_nodata == 0.0 assert vrt.dst_nodata == 0.0 assert vrt.resampling == Resampling.nearest assert vrt.width == size assert vrt.height == size assert vrt.transform == dst_transform assert vrt.warp_extras == {"init_dest": "NO_DATA"}
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds, dst_bounds, res, resampling, src_nodata, dst_nodata, threads, check_invert_proj, overwrite, creation_options, target_aligned_pixels): """ Warp a raster dataset. If a template raster is provided using the --like option, the coordinate reference system, affine transform, and dimensions of that raster will be used for the output. In this case --dst-crs, --bounds, --res, and --dimensions options are not applicable and an exception will be raised. \b $ rio warp input.tif output.tif --like template.tif The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn string, \b --dst-crs EPSG:4326 --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84' or a JSON text-encoded PROJ.4 object. \b --dst-crs '{"proj": "utm", "zone": 18, ...}' If --dimensions are provided, --res and --bounds are not applicable and an exception will be raised. Resolution is calculated based on the relationship between the raster bounds in the target coordinate system and the dimensions, and may produce rectangular rather than square pixels. \b $ rio warp input.tif output.tif --dimensions 100 200 \\ > --dst-crs EPSG:4326 If --bounds are provided, --res is required if --dst-crs is provided (defaults to source raster resolution otherwise). \b $ rio warp input.tif output.tif \\ > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326 """ output, files = resolve_inout( files=files, output=output, overwrite=overwrite) resampling = Resampling[resampling] # get integer code for method if not len(res): # Click sets this as an empty tuple if not provided res = None else: # Expand one value to two if needed res = (res[0], res[0]) if len(res) == 1 else res if target_aligned_pixels: if not res: raise click.BadParameter( '--target-aligned-pixels requires a specified resolution') if src_bounds or dst_bounds: raise click.BadParameter( '--target-aligned-pixels cannot be used with ' '--src-bounds or --dst-bounds') # Check invalid parameter combinations if like: invalid_combos = (dimensions, dst_bounds, dst_crs, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--like cannot be used with any of --dimensions, --bounds, " "--dst-crs, or --res") elif dimensions: invalid_combos = (dst_bounds, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--dimensions cannot be used with --bounds or --res") with ctx.obj['env']: setenv(CHECK_WITH_INVERT_PROJ=check_invert_proj) with rasterio.open(files[0]) as src: l, b, r, t = src.bounds out_kwargs = src.profile.copy() out_kwargs['driver'] = driver # Sort out the bounds options. if src_bounds and dst_bounds: raise click.BadParameter( "--src-bounds and destination --bounds may not be " "specified simultaneously.") if like: with rasterio.open(like) as template_ds: dst_crs = template_ds.crs dst_transform = template_ds.transform dst_height = template_ds.height dst_width = template_ds.width elif dst_crs is not None: try: dst_crs = CRS.from_string(dst_crs) except ValueError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') if dimensions: # Calculate resolution appropriate for dimensions # in target. dst_width, dst_height = dimensions try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src.bounds) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') dst_transform = Affine( (xmax - xmin) / float(dst_width), 0, xmin, 0, (ymin - ymax) / float(dst_height), ymax ) elif src_bounds or dst_bounds: if not res: raise click.BadParameter( "Required when using --bounds.", param='res', param_hint='res') if src_bounds: try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src_bounds) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') else: xmin, ymin, xmax, ymax = dst_bounds dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) else: try: if src.transform.is_identity and src.gcps: src_crs = src.gcps[1] kwargs = {'gcps': src.gcps[0]} else: src_crs = src.crs kwargs = src.bounds._asdict() dst_transform, dst_width, dst_height = calcdt( src_crs, dst_crs, src.width, src.height, resolution=res, **kwargs) except CRSError as err: raise click.BadParameter( str(err), param='dst_crs', param_hint='dst_crs') elif dimensions: # Same projection, different dimensions, calculate resolution. dst_crs = src.crs dst_width, dst_height = dimensions dst_transform = Affine( (r - l) / float(dst_width), 0, l, 0, (b - t) / float(dst_height), t ) elif src_bounds or dst_bounds: # Same projection, different dimensions and possibly # different resolution. if not res: res = (src.transform.a, -src.transform.e) dst_crs = src.crs xmin, ymin, xmax, ymax = (src_bounds or dst_bounds) dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution. dst_crs = src.crs dst_transform = Affine(res[0], 0, l, 0, -res[1], t) dst_width = max(int(ceil((r - l) / res[0])), 1) dst_height = max(int(ceil((t - b) / res[1])), 1) else: dst_crs = src.crs dst_transform = src.transform dst_width = src.width dst_height = src.height if target_aligned_pixels: dst_transform, dst_width, dst_height = aligned_target(dst_transform, dst_width, dst_height, res) # If src_nodata is not None, update the dst metadata NODATA # value to src_nodata (will be overridden by dst_nodata if it is not None if src_nodata is not None: # Update the dst nodata value out_kwargs.update({ 'nodata': src_nodata }) # Validate a manually set destination NODATA value # against the input datatype. if dst_nodata is not None: if src_nodata is None and src.meta['nodata'] is None: raise click.BadParameter( "--src-nodata must be provided because dst-nodata is not None") else: # Update the dst nodata value out_kwargs.update({'nodata': dst_nodata}) # When the bounds option is misused, extreme values of # destination width and height may result. if (dst_width < 0 or dst_height < 0 or dst_width > MAX_OUTPUT_WIDTH or dst_height > MAX_OUTPUT_HEIGHT): raise click.BadParameter( "Invalid output dimensions: {0}.".format( (dst_width, dst_height))) out_kwargs.update({ 'crs': dst_crs, 'transform': dst_transform, 'width': dst_width, 'height': dst_height }) # Adjust block size if necessary. if ('blockxsize' in out_kwargs and dst_width < out_kwargs['blockxsize']): del out_kwargs['blockxsize'] if ('blockysize' in out_kwargs and dst_height < out_kwargs['blockysize']): del out_kwargs['blockysize'] out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as dst: reproject( source=rasterio.band(src, list(range(1, src.count + 1))), destination=rasterio.band( dst, list(range(1, src.count + 1))), src_transform=src.transform, src_crs=src.crs, src_nodata=src_nodata, dst_transform=out_kwargs['transform'], dst_crs=out_kwargs['crs'], dst_nodata=dst_nodata, resampling=resampling, num_threads=threads)
def test_epsg__no_code_available(): lcc_crs = CRS.from_string('+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc ' '+x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert lcc_crs.to_epsg() is None
def test_crs_OSR_no_equivalence(): crs1 = CRS.from_string('+proj=longlat +datum=WGS84 +no_defs') crs2 = CRS.from_string('+proj=longlat +datum=NAD27 +no_defs') assert crs1 != crs2
def test_convert_crs(string): test = CRS.from_string(string) assert convert.GIS_TO_STR['crs'](test) == string assert convert.STR_TO_GIS['crs'](string) == test
def test_empty_json(arg): with Env(), pytest.raises(CRSError): CRS.from_string(arg)
def __init__(self, tile_id: str, grid: Grid, layer: Layer) -> None: self.grid: Grid = grid self.layer: Layer = layer self.local_dst: Dict[str, RasterSource] = dict() self.tile_id: str = tile_id self.bounds: BoundingBox = grid.get_tile_bounds(tile_id) gdal_profile = { "driver": "GTiff", "width": grid.cols, "height": grid.rows, "count": self.layer.band_count, "transform": rasterio.transform.from_origin(self.bounds.left, self.bounds.top, grid.xres, grid.yres), "crs": CRS.from_string(grid.crs.to_string( )), # Need to convert from ProjPy CRS to RasterIO CRS "sparse_ok": "TRUE", "interleave": "BAND", } if layer.photometric: gdal_profile[ "photometric"] = layer.photometric.value # need value, not just Enum! gdal_profile.update(self.layer.dst_profile) LOGGER.debug(f"GDAL Profile for tile {self.tile_id}: {gdal_profile}") # Drop GDAL specific optimizations which might not be readable by other applications geotiff_profile = copy.deepcopy(gdal_profile) geotiff_profile.pop("nbits", None) geotiff_profile.pop("sparse_ok", None) geotiff_profile.pop("interleave", None) geotiff_profile["compress"] = "DEFLATE" LOGGER.debug( f"GEOTIFF Profile for tile {self.tile_id}: {geotiff_profile}") self.dst: Dict[str, Destination] = { DstFormat.gdal_geotiff: Destination( uri=os.path.join(layer.prefix, DstFormat.gdal_geotiff, f"{self.tile_id}.tif"), profile=gdal_profile, bounds=self.bounds, ), DstFormat.geotiff: Destination( uri=os.path.join(layer.prefix, DstFormat.geotiff, f"{self.tile_id}.tif"), profile=geotiff_profile, bounds=self.bounds, ), } self.work_dir = create_dir(os.path.join(os.getcwd(), tile_id)) self.tmp_dir = create_dir(os.path.join(self.work_dir, "tmp")) self.default_format = GLOBALS.default_dst_format self.status = "pending" self.metadata: Dict[str, Dict] = dict()
def mask( input, output, variable, like, netcdf3, all_touched, invert, zip): """ Create a NetCDF mask from a shapefile. Values are equivalent to a numpy mask: 0 for unmasked areas, and 1 for masked areas. Template NetCDF dataset must have a valid projection defined or be inferred from dimensions (e.g., lat / long) """ with Dataset(like) as template_ds: template_varname = data_variables(template_ds).keys()[0] template_variable = template_ds.variables[template_varname] template_crs = get_crs(template_ds, template_varname) if template_crs: template_crs = CRS.from_string(template_crs) elif is_geographic(template_ds, template_varname): template_crs = CRS({'init': 'EPSG:4326'}) else: raise click.UsageError('template dataset must have a valid projection defined') spatial_dimensions = template_variable.dimensions[-2:] mask_shape = template_variable.shape[-2:] template_y_name, template_x_name = spatial_dimensions coords = SpatialCoordinateVariables.from_dataset( template_ds, x_name=template_x_name, y_name=template_y_name, projection=Proj(**template_crs.to_dict()) ) with fiona.open(input, 'r') as shp: transform_required = CRS(shp.crs) != template_crs # Project bbox for filtering bbox = coords.bbox if transform_required: bbox = bbox.project(Proj(**shp.crs), edge_points=21) geometries = [] for f in shp.filter(bbox=bbox.as_list()): geom = f['geometry'] if transform_required: geom = transform_geom(shp.crs, template_crs, geom) geometries.append(geom) click.echo('Converting {0} features to mask'.format(len(geometries))) if invert: fill_value = 0 default_value = 1 else: fill_value = 1 default_value = 0 with rasterio.drivers(): # Rasterize features to 0, leaving background as 1 mask = rasterize( geometries, out_shape=mask_shape, transform=coords.affine, all_touched=all_touched, fill=fill_value, default_value=default_value, dtype=numpy.uint8 ) format = 'NETCDF3_CLASSIC' if netcdf3 else 'NETCDF4' dtype = 'int8' if netcdf3 else 'uint8' with Dataset(output, 'w', format=format) as out: coords.add_to_dataset(out, template_x_name, template_y_name) out_var = out.createVariable(variable, dtype, dimensions=spatial_dimensions, zlib=zip, fill_value=get_fill_value(dtype)) out_var[:] = mask
def __init__( self, array, bands, crs, transform, nodataval, driver="GTiff", rio_ds=None, ): if rasterio is None: msg = ("Raster(): error " + 'importing rasterio - try "pip install rasterio"') raise ImportError(msg) else: from rasterio.crs import CRS if affine is None: msg = ("Raster(): error " + 'importing affine - try "pip install affine"') raise ImportError(msg) self._array = array self._bands = bands meta = {"driver": driver, "nodata": nodataval} # create metadata dictionary if array.dtype in Raster.FLOAT32: dtype = "float32" elif array.dtype in Raster.FLOAT64: dtype = "float64" elif array.dtype in Raster.INT8: dtype = "int8" elif array.dtype in Raster.INT16: dtype = "int16" elif array.dtype in Raster.INT32: dtype = "int32" elif array.dtype in Raster.INT64: dtype = "int64" else: raise TypeError("dtype cannot be determined from Raster") meta["dtype"] = dtype if isinstance(crs, CRS): pass elif isinstance(crs, int): crs = CRS.from_epsg(crs) elif isinstance(crs, str): crs = CRS.from_string(crs) else: TypeError("crs type not understood, provide an epsg or proj4") meta["crs"] = crs count, height, width = array.shape meta["count"] = count meta["height"] = height meta["width"] = width if not isinstance(transform, affine.Affine): raise TypeError("Transform must be defined by an Affine object") meta["transform"] = transform self._meta = meta self._dataset = None self.__arr_dict = { self._bands[b]: arr for b, arr in enumerate(self._array) } self.__xcenters = None self.__ycenters = None if isinstance(rio_ds, rasterio.io.DatasetReader): self._dataset = rio_ds
def test_epsg__no_code_available(): lcc_crs = CRS.from_string( '+lon_0=-95 +ellps=GRS80 +y_0=0 +no_defs=True +proj=lcc ' '+x_0=0 +units=m +lat_2=77 +lat_1=49 +lat_0=0') assert lcc_crs.to_epsg() is None
def test_crs_OSR_equivalence(): crs1 = CRS.from_string('+proj=longlat +datum=WGS84 +no_defs') crs2 = CRS.from_string('+proj=latlong +datum=WGS84 +no_defs') crs3 = CRS({'init': 'EPSG:4326'}) assert crs1 == crs2 assert crs1 == crs3
def test_epsg(): assert CRS({'init': 'EPSG:4326'}).to_epsg() == 4326 assert CRS.from_string( '+proj=longlat +datum=WGS84 +no_defs').to_epsg() == 4326
def read_data_grid(file_name, output_format='data_array', output_dtype='float32'): try: dset = rasterio.open(file_name) bounds = dset.bounds res = dset.res transform = dset.transform data = dset.read() if dset.crs is None: crs = CRS.from_string(proj_epsg_default) else: crs = dset.crs values = data[0, :, :] decimal_round = 7 center_right = bounds.right - (res[0] / 2) center_left = bounds.left + (res[0] / 2) center_top = bounds.top - (res[1] / 2) center_bottom = bounds.bottom + (res[1] / 2) lon = np.arange(center_left, center_right + np.abs(res[0] / 2), np.abs(res[0]), float) lat = np.arange(center_bottom, center_top + np.abs(res[0] / 2), np.abs(res[1]), float) lons, lats = np.meshgrid(lon, lat) min_lon_round = round(np.min(lons), decimal_round) max_lon_round = round(np.max(lons), decimal_round) min_lat_round = round(np.min(lats), decimal_round) max_lat_round = round(np.max(lats), decimal_round) center_right_round = round(center_right, decimal_round) center_left_round = round(center_left, decimal_round) center_bottom_round = round(center_bottom, decimal_round) center_top_round = round(center_top, decimal_round) assert min_lon_round == center_left_round assert max_lon_round == center_right_round assert min_lat_round == center_bottom_round assert max_lat_round == center_top_round lats = np.flipud(lats) if output_format == 'data_array': data_obj = create_darray_2d(values, lons[0, :], lats[:, 0], coord_name_x='west_east', coord_name_y='south_north', dim_name_x='west_east', dim_name_y='south_north') elif output_format == 'dictionary': data_obj = {'values': values, 'longitude': lons[0, :], 'latitude': lats[:, 0], 'transform': transform, 'crs': crs, 'bbox': [bounds.left, bounds.bottom, bounds.right, bounds.top], 'bb_left': bounds.left, 'bb_right': bounds.right, 'bb_top': bounds.top, 'bb_bottom': bounds.bottom, 'res_lon': res[0], 'res_lat': res[1]} else: log_stream.error(' ===> File static "' + file_name + '" output format not allowed') raise NotImplementedError('Case not implemented yet') except IOError as io_error: data_obj = None log_stream.warning(' ===> File static in ascii grid was not correctly open with error "' + str(io_error) + '"') log_stream.warning(' ===> Filename "' + os.path.split(file_name)[1] + '"') return data_obj
def warp(ctx, files, output, driver, like, dst_crs, dimensions, src_bounds, dst_bounds, res, resampling, src_nodata, dst_nodata, threads, check_invert_proj, force_overwrite, creation_options): """ Warp a raster dataset. If a template raster is provided using the --like option, the coordinate reference system, affine transform, and dimensions of that raster will be used for the output. In this case --dst-crs, --bounds, --res, and --dimensions options are not applicable and an exception will be raised. \b $ rio warp input.tif output.tif --like template.tif The output coordinate reference system may be either a PROJ.4 or EPSG:nnnn string, \b --dst-crs EPSG:4326 --dst-crs '+proj=longlat +ellps=WGS84 +datum=WGS84' or a JSON text-encoded PROJ.4 object. \b --dst-crs '{"proj": "utm", "zone": 18, ...}' If --dimensions are provided, --res and --bounds are not applicable and an exception will be raised. Resolution is calculated based on the relationship between the raster bounds in the target coordinate system and the dimensions, and may produce rectangular rather than square pixels. \b $ rio warp input.tif output.tif --dimensions 100 200 \\ > --dst-crs EPSG:4326 If --bounds are provided, --res is required if --dst-crs is provided (defaults to source raster resolution otherwise). \b $ rio warp input.tif output.tif \\ > --bounds -78 22 -76 24 --res 0.1 --dst-crs EPSG:4326 """ output, files = resolve_inout(files=files, output=output, force_overwrite=force_overwrite) resampling = Resampling[resampling] # get integer code for method if not len(res): # Click sets this as an empty tuple if not provided res = None else: # Expand one value to two if needed res = (res[0], res[0]) if len(res) == 1 else res # Check invalid parameter combinations if like: invalid_combos = (dimensions, dst_bounds, dst_crs, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--like cannot be used with any of --dimensions, --bounds, " "--dst-crs, or --res") elif dimensions: invalid_combos = (dst_bounds, res) if any(p for p in invalid_combos if p is not None): raise click.BadParameter( "--dimensions cannot be used with --bounds or --res") with ctx.obj['env']: setenv(CHECK_WITH_INVERT_PROJ=check_invert_proj) with rasterio.open(files[0]) as src: l, b, r, t = src.bounds out_kwargs = src.profile.copy() out_kwargs['driver'] = driver # Sort out the bounds options. if src_bounds and dst_bounds: raise click.BadParameter( "--src-bounds and destination --bounds may not be " "specified simultaneously.") if like: with rasterio.open(like) as template_ds: dst_crs = template_ds.crs dst_transform = template_ds.transform dst_height = template_ds.height dst_width = template_ds.width elif dst_crs is not None: try: dst_crs = CRS.from_string(dst_crs) except ValueError as err: raise click.BadParameter(str(err), param='dst_crs', param_hint='dst_crs') if dimensions: # Calculate resolution appropriate for dimensions # in target. dst_width, dst_height = dimensions try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src.bounds) except CRSError as err: raise click.BadParameter(str(err), param='dst_crs', param_hint='dst_crs') dst_transform = Affine( (xmax - xmin) / float(dst_width), 0, xmin, 0, (ymin - ymax) / float(dst_height), ymax) elif src_bounds or dst_bounds: if not res: raise click.BadParameter( "Required when using --bounds.", param='res', param_hint='res') if src_bounds: try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *src_bounds) except CRSError as err: raise click.BadParameter(str(err), param='dst_crs', param_hint='dst_crs') else: xmin, ymin, xmax, ymax = dst_bounds dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) else: try: if src.transform.is_identity and src.gcps: src_crs = src.gcps[1] kwargs = {'gcps': src.gcps[0]} else: src_crs = src.crs kwargs = src.bounds._asdict() dst_transform, dst_width, dst_height = calcdt( src_crs, dst_crs, src.width, src.height, resolution=res, **kwargs) except CRSError as err: raise click.BadParameter(str(err), param='dst_crs', param_hint='dst_crs') elif dimensions: # Same projection, different dimensions, calculate resolution. dst_crs = src.crs dst_width, dst_height = dimensions dst_transform = Affine((r - l) / float(dst_width), 0, l, 0, (b - t) / float(dst_height), t) elif src_bounds or dst_bounds: # Same projection, different dimensions and possibly # different resolution. if not res: res = (src.transform.a, -src.transform.e) dst_crs = src.crs xmin, ymin, xmax, ymax = (src_bounds or dst_bounds) dst_transform = Affine(res[0], 0, xmin, 0, -res[1], ymax) dst_width = max(int(ceil((xmax - xmin) / res[0])), 1) dst_height = max(int(ceil((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution. dst_crs = src.crs dst_transform = Affine(res[0], 0, l, 0, -res[1], t) dst_width = max(int(ceil((r - l) / res[0])), 1) dst_height = max(int(ceil((t - b) / res[1])), 1) else: dst_crs = src.crs dst_transform = src.transform dst_width = src.width dst_height = src.height # If src_nodata is not None, update the dst metadata NODATA # value to src_nodata (will be overridden by dst_nodata if it is not None if src_nodata is not None: # Update the dst nodata value out_kwargs.update({'nodata': src_nodata}) # Validate a manually set destination NODATA value # against the input datatype. if dst_nodata is not None: if src_nodata is None and src.meta['nodata'] is None: raise click.BadParameter( "--src-nodata must be provided because dst-nodata is not None" ) else: # Update the dst nodata value out_kwargs.update({'nodata': dst_nodata}) # When the bounds option is misused, extreme values of # destination width and height may result. if (dst_width < 0 or dst_height < 0 or dst_width > MAX_OUTPUT_WIDTH or dst_height > MAX_OUTPUT_HEIGHT): raise click.BadParameter( "Invalid output dimensions: {0}.".format( (dst_width, dst_height))) out_kwargs.update({ 'crs': dst_crs, 'transform': dst_transform, 'width': dst_width, 'height': dst_height }) # Adjust block size if necessary. if ('blockxsize' in out_kwargs and dst_width < out_kwargs['blockxsize']): del out_kwargs['blockxsize'] if ('blockysize' in out_kwargs and dst_height < out_kwargs['blockysize']): del out_kwargs['blockysize'] out_kwargs.update(**creation_options) with rasterio.open(output, 'w', **out_kwargs) as dst: reproject(source=rasterio.band(src, list(range(1, src.count + 1))), destination=rasterio.band( dst, list(range(1, src.count + 1))), src_transform=src.transform, src_crs=src.crs, src_nodata=src_nodata, dst_transform=out_kwargs['transform'], dst_crs=out_kwargs['crs'], dst_nodata=dst_nodata, resampling=resampling, num_threads=threads)
def get(self, request, pk=None, project_pk=None, tile_type=""): """ Get the metadata for this tasks's asset type """ task = self.get_and_check_task(request, pk) formula = self.request.query_params.get('formula') bands = self.request.query_params.get('bands') defined_range = self.request.query_params.get('range') boundaries_feature = self.request.query_params.get('boundaries') if formula == '': formula = None if bands == '': bands = None if defined_range == '': defined_range = None if boundaries_feature == '': boundaries_feature = None if boundaries_feature is not None: boundaries_feature = json.loads(boundaries_feature) try: expr, hrange = lookup_formula(formula, bands) if defined_range is not None: new_range = tuple(map(float, defined_range.split(",")[:2])) #Validate rescaling range if hrange is not None and (new_range[0] < hrange[0] or new_range[1] > hrange[1]): pass else: hrange = new_range except ValueError as e: raise exceptions.ValidationError(str(e)) pmin, pmax = 2.0, 98.0 raster_path = get_raster_path(task, tile_type) if not os.path.isfile(raster_path): raise exceptions.NotFound() try: with COGReader(raster_path) as src: band_count = src.metadata()['count'] if boundaries_feature is not None: boundaries_cutline = create_cutline(src.dataset, boundaries_feature, CRS.from_string('EPSG:4326')) boundaries_bbox = featureBounds(boundaries_feature) else: boundaries_cutline = None boundaries_bbox = None if has_alpha_band(src.dataset): band_count -= 1 nodata = None # Workaround for https://github.com/OpenDroneMap/WebODM/issues/894 if tile_type == 'orthophoto': nodata = 0 histogram_options = {"bins": 255, "range": hrange} if expr is not None: if boundaries_cutline is not None: data, mask = src.preview(expression=expr, vrt_options={'cutline': boundaries_cutline}) else: data, mask = src.preview(expression=expr) data = numpy.ma.array(data) data.mask = mask == 0 stats = { str(b + 1): raster_stats(data[b], percentiles=(pmin, pmax), bins=255, range=hrange) for b in range(data.shape[0]) } stats = {b: ImageStatistics(**s) for b, s in stats.items()} metadata = RioMetadata(statistics=stats, **src.info().dict()) else: if (boundaries_cutline is not None) and (boundaries_bbox is not None): metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata , bounds=boundaries_bbox, vrt_options={'cutline': boundaries_cutline}) else: metadata = src.metadata(pmin=pmin, pmax=pmax, hist_options=histogram_options, nodata=nodata) info = json.loads(metadata.json()) except IndexError as e: # Caught when trying to get an invalid raster metadata raise exceptions.ValidationError("Cannot retrieve raster metadata: %s" % str(e)) # Override min/max if hrange: for b in info['statistics']: info['statistics'][b]['min'] = hrange[0] info['statistics'][b]['max'] = hrange[1] cmap_labels = { "viridis": "Viridis", "jet": "Jet", "terrain": "Terrain", "gist_earth": "Earth", "rdylgn": "RdYlGn", "rdylgn_r": "RdYlGn (Reverse)", "spectral": "Spectral", "spectral_r": "Spectral (Reverse)", "discrete_ndvi": "Contrast NDVI", "better_discrete_ndvi": "Custom NDVI Index", "rplumbo": "Rplumbo (Better NDVI)", "pastel1": "Pastel", } colormaps = [] algorithms = [] if tile_type in ['dsm', 'dtm']: colormaps = ['viridis', 'jet', 'terrain', 'gist_earth', 'pastel1'] elif formula and bands: colormaps = ['rdylgn', 'spectral', 'rdylgn_r', 'spectral_r', 'rplumbo', 'discrete_ndvi', 'better_discrete_ndvi'] algorithms = *get_algorithm_list(band_count), info['color_maps'] = [] info['algorithms'] = algorithms if colormaps: for cmap in colormaps: try: info['color_maps'].append({ 'key': cmap, 'color_map': colormap.get(cmap).values(), 'label': cmap_labels.get(cmap, cmap) }) except FileNotFoundError: raise exceptions.ValidationError("Not a valid color_map value: %s" % cmap) info['name'] = task.name info['scheme'] = 'xyz' info['tiles'] = [get_tile_url(task, tile_type, self.request.query_params)] if info['maxzoom'] < info['minzoom']: info['maxzoom'] = info['minzoom'] info['maxzoom'] += ZOOM_EXTRA_LEVELS info['minzoom'] -= ZOOM_EXTRA_LEVELS info['bounds'] = {'value': src.bounds, 'crs': src.dataset.crs} return Response(info)
def set_crs(dataset, variable_name, projection, set_proj4_att=False): """ Set the projection information into a grid_mapping variable and reference it from the data variable. :param dataset: dataset open in write or append mode :param variable_name: name of data variable to attach projection to :param projection: pyproj.Proj projection object :param set_proj4_att: if True, set the 'proj4' attribute on the variable """ if not isinstance(projection, Proj): raise ValueError('Projection must be instance of pyproj.Proj') variable = dataset.variables[variable_name] if 'epsg:' in projection.srs: proj_string = epsg_to_proj4(re.search('(?<=epsg:)\d+', projection.srs).group()) else: proj_string = projection.srs if set_proj4_att: variable.setncattr(PROJ4_KEY, proj_string) proj = CRS.from_string(proj_string) proj_data = proj.to_dict() proj_key = 'latlong' if not proj.is_projected else proj_data['proj'] if not proj_key in PROJ4_CF_PARAM_MAP.keys(): raise ValueError('CF Convention mapping is not yet available for projection {0}'.format(proj_key)) crs_variable_name = 'crs_{0}'.format(pj_list[proj_key].replace(' ', '_').replace('/', '')) if not crs_variable_name in dataset.variables: crs_variable = dataset.createVariable(crs_variable_name, 'S1') ncatts = {'grid_mapping_name': PROJ4_CF_NAMES[proj_key]} out_proj_params = PROJ4_CF_PARAM_MAP[proj_key] for param in out_proj_params: if param.count('{'): # Special case - standard parallel keys = [param.format(i) for i in (1, 2)] values = [proj_data[key] for key in keys if key in proj_data] if values: if len(values) == 1: values = values[0] ncatts[out_proj_params[param]] = values elif param in proj_data: ncatts[out_proj_params[param]] = proj_data[param] if 'datum' in proj_data and not 'ellps' in proj_data: # Not all datums link to available pj_ellps keys, some had to be added manually here if proj_data['datum'] in pj_ellps: proj_data['ellps'] = proj_data['datum'] elif proj_data['datum'] == 'NAD83': proj_data['ellps'] = 'GRS80' elif proj_data['datum'] == 'NAD27': proj_data['ellps'] = 'clrk66' else: raise ValueError('projection ellipsoid must be specified, datum {0}' 'does not match a known ellipsoid'.format(proj_data['datum'])) # Extract out parameters of known ellipsoids if 'ellps' in proj_data: if not proj_data['ellps'] in pj_ellps: raise ValueError('projection ellipsoid does not match a known ellipsoid') ellipsoid_params = pj_ellps[proj_data['ellps']] for param in set(PROJ4_CF_ELLIPSOID_MAP.keys()).intersection(ellipsoid_params): proj_data[param] = ellipsoid_params[param] for param in set(PROJ4_CF_ELLIPSOID_MAP.keys()).intersection(proj_data): ncatts[PROJ4_CF_ELLIPSOID_MAP[param]] = proj_data[param] set_ncattrs(crs_variable, ncatts) variable.setncattr('grid_mapping', crs_variable_name)
def write_geotiff_from_xr(tif_path, dataset, bands=[], no_data=-9999, crs="EPSG:4326"): """Write a geotiff from an xarray dataset. Args: tif_path: path for the tif to be written to. dataset: xarray dataset bands: list of strings representing the bands in the order they should be written no_data: nodata value for the dataset crs: requested crs. Affine(a,b,c,d,e,f) a = width of a pixel b = row rotation (typically zero) c = x-coordinate of the upper-left corner of the upper-left pixel d = column rotation (typically zero) e = height of a pixel (typically negative) f = y-coordinate of the of the upper-left corner of the upper-left pixel """ from rasterio.crs import CRS as CRS_rasterio bands = list(dataset.data_vars.keys()) assert isinstance(bands, list), "Bands must a list of strings" assert len(bands) > 0 and isinstance( bands[0], str), "You must supply at least one band." logging.info('write_geotiff_from_xr: dataset {} - {}'.format( type(dataset), dataset)) #print(dataset.crs) #if dataset.crs is not None: # if isinstance(dataset.crs, xr.DataArray): # print(type(dataset.crs)) # crs_dict = dataset.crs.to_dict() # crs = CRS_rasterio.from_wkt(crs_dict['attrs']['crs_wkt']) # print(crs_dict['attrs']) # geobox = calculate_bounds_geotransform(dataset) # bounds = BoundingBox(left=geobox['left'], bottom=geobox['bottom'], right=geobox['right'], top=geobox['top']) # else: # crs = dataset.crs.crs_str #else: # print("no entra") # transform = _get_transform_from_xr(dataset) #transform = _get_transform_from_xr(dataset) if isinstance(dataset.crs, xr.DataArray): crs_dict = dataset.crs.to_dict() crs = CRS_rasterio.from_wkt(crs_dict['attrs']['crs_wkt']) elif isinstance(dataset.crs, datacube.utils.geometry._base.CRS): crs = CRS_rasterio.from_string(dataset.crs.crs_str) else: raise Exception( 'dataset.crs datatype not know (please check calculate_bounds_geotransform)' ) geobox = calculate_bounds_geotransform(dataset) bounds = BoundingBox(left=geobox['left'], bottom=geobox['bottom'], right=geobox['right'], top=geobox['top']) transform = _get_transform_from_xr(dataset) with rasterio.open( tif_path, 'w', driver='GTiff', height=dataset.dims['latitude'], width=dataset.dims['longitude'], count=len(bands), dtype=dataset[bands[0]].dtype, #str(dataset[bands[0]].dtype), crs=crs, transform=transform, bounds=bounds, nodata=no_data) as dst: for index, band in enumerate(bands): print(dataset[band].dtype) dst.write_band( index + 1, dataset[band].values.astype(dataset[bands[0]].dtype), ) tag = {'Band_' + str(index + 1): bands[index]} dst.update_tags(**tag) dst.set_band_description(index + 1, band) dst.close()
def test_crs_proj_json__from_string(): aeqd_crs = CRS(proj="aeqd", lon_0=-80, lat_0=40.5) assert CRS.from_string(json.dumps( aeqd_crs.to_dict(projjson=True))) == aeqd_crs
def extract_chips_from_raster( raster, rescale_mode=None, rescale_range=None, bands=None, type="tif", write_geojson=True, labels=None, label_property="class", mask_type="class", classes=None, crs=None, skip_existing=True, within=False, aoi_poly=None, polys_dict=None, dry_run=False, *, size, step_size, output_dir, ): basename, _ = os.path.splitext(os.path.basename(raster)) masks_folder = os.path.join(output_dir, "masks") image_folder = os.path.join(output_dir, "images") with rasterio.open(raster) as ds: _logger.info("Raster size: %s", (ds.width, ds.height)) if any(b > ds.count for b in bands): raise RuntimeError( f"Raster has {ds.count} bands, but you asked to use {bands} band indexes" ) if bands is None: bands = list(range(1, min(ds.count, 3) + 1)) _logger.info("Building windows") win_size = (size, size) win_step_size = (step_size, step_size) windows = list( sliding_windows(win_size, win_step_size, ds.width, ds.height, whole=True)) _logger.info("Total windows: %d", len(windows)) _logger.info("Building window shapes") window_shapes = [ box(*rasterio.windows.bounds(w, ds.transform)) for w, _ in windows ] window_and_shapes = zip(windows, window_shapes) # Filter windows by AOI shape if aoi_poly: _logger.info("Filtering windows by AOI") _logger.info("Using \"%s\" function", 'within' if within else 'intersects') filter_fn = lambda w, aoi: w.within( aoi) if within else w.intersects(aoi) window_and_shapes = [(w, s) for w, s in window_and_shapes if filter_fn(s, aoi_poly)] _logger.info("Total windows after filtering: %d", len(window_and_shapes)) meta = ds.meta.copy() if crs: meta["crs"] = CRS.from_string(crs) chips = [] for c, ((window, (i, j)), win_shape) in tqdm(list(enumerate(window_and_shapes))): _logger.debug("%s %s", window, (i, j)) img_path = os.path.join(image_folder, f"{basename}_{i}_{j}.{type}") mask_path = os.path.join(masks_folder, f"{basename}_{i}_{j}.{type}") # Store chip window for generating GeoJSON later chip = (win_shape, (c, i, j)) chips.append(chip) if dry_run: continue if (skip_existing and os.path.exists(img_path) and (not labels or os.path.exists(mask_path))): continue # Extract chip image from original image img = ds.read(window=window) img = np.nan_to_num(img) img = np.array([img[b - 1, :, :] for b in bands]) # Rescale intensity (if needed) if rescale_mode: img = rescale_intensity(img, rescale_mode, rescale_range) # Write chip image if type == "tif": image_was_saved = write_tif( img, img_path, window=window, meta=meta.copy(), transform=ds.transform, bands=bands, ) else: image_was_saved = write_image(img, img_path) # If there are labels, and chip was extracted succesfully, generate a mask if image_was_saved and labels: if mask_type == "class": keys = classes if classes is not None else polys_dict.keys( ) multiband_chip_mask_by_classes( classes=keys, transform=ds.transform, window=window, window_shape=win_shape, polys_dict=polys_dict, metadata=meta, mask_path=mask_path, label_property=label_property, ) if write_geojson: geojson_path = os.path.join(output_dir, "{}.geojson".format(basename)) write_chips_geojson(geojson_path, chips, type=type, crs=str(meta["crs"]), basename=basename)
def test_tmerc_no_match(): """Should not match an authority, see issue #2293.""" s = "+proj=tmerc +lat_0=0 +lon_0=10.7584 +k=0.9996 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs" crs = CRS.from_string(s) assert crs.to_epsg() is None
def set_crs(dataset, variable_name, projection, set_proj4_att=False): """ Set the projection information into a grid_mapping variable and reference it from the data variable. :param dataset: dataset open in write or append mode :param variable_name: name of data variable to attach projection to :param projection: pyproj.Proj projection object :param set_proj4_att: if True, set the 'proj4' attribute on the variable """ if not isinstance(projection, Proj): raise ValueError('Projection must be instance of pyproj.Proj') variable = dataset.variables[variable_name] if 'epsg:' in projection.srs: proj_string = epsg_to_proj4(re.search('(?<=epsg:)\d+', projection.srs).group()) else: proj_string = projection.srs if set_proj4_att: variable.setncattr(PROJ4_KEY, proj_string) proj_data = CRS.from_string(proj_string).to_dict() proj_key = proj_data['proj'] if not proj_key in PROJ4_CF_PARAM_MAP.keys(): raise ValueError('CF Convention mapping is not yet available for projection {0}'.format(proj_key)) crs_variable_name = 'crs_{0}'.format(pj_list[proj_key].replace(' ', '_').replace('/', '')) if not crs_variable_name in dataset.variables: crs_variable = dataset.createVariable(crs_variable_name, 'S1') ncatts = {'grid_mapping_name': PROJ4_CF_NAMES[proj_key]} out_proj_params = PROJ4_CF_PARAM_MAP[proj_key] for param in out_proj_params: if param.count('{'): # Special case - standard parallel keys = [param.format(i) for i in (1, 2)] values = [proj_data[key] for key in keys if key in proj_data] if values: if len(values) == 1: values = values[0] ncatts[out_proj_params[param]] = values elif param in proj_data: ncatts[out_proj_params[param]] = proj_data[param] if 'datum' in proj_data and not 'ellps' in proj_data: # Not all datums link to available pj_ellps keys, some had to be added manually here if proj_data['datum'] in pj_ellps: proj_data['ellps'] = proj_data['datum'] elif proj_data['datum'] == 'NAD83': proj_data['ellps'] = 'GRS80' elif proj_data['datum'] == 'NAD27': proj_data['ellps'] = 'clrk66' else: raise ValueError('projection ellipsoid must be specified, datum {0}' 'does not match a known ellipsoid'.format(proj_data['datum'])) # Extract out parameters of known ellipsoids if 'ellps' in proj_data: if not proj_data['ellps'] in pj_ellps: raise ValueError('projection ellipsoid does not match a known ellipsoid') ellipsoid_params = pj_ellps[proj_data['ellps']] for param in set(PROJ4_CF_ELLIPSOID_MAP.keys()).intersection(ellipsoid_params): proj_data[param] = ellipsoid_params[param] for param in set(PROJ4_CF_ELLIPSOID_MAP.keys()).intersection(proj_data): ncatts[PROJ4_CF_ELLIPSOID_MAP[param]] = proj_data[param] set_ncattrs(crs_variable, ncatts) variable.setncattr('grid_mapping', crs_variable_name)
def write_data_grid(file_name, file_data, file_ancillary=None): if 'bb_left' in list(file_ancillary.keys()): bb_left = file_ancillary['bb_left'] else: log_stream.error( ' ===> Geographical info "bb_left" for writing ascii grid file is undefined.' ) raise IOError( 'Geographical info is mandatory. Check your static datasets.') if 'bb_bottom' in list(file_ancillary.keys()): bb_bottom = file_ancillary['bb_bottom'] else: log_stream.error( ' ===> Geographical info "bb_bottom" for writing ascii grid file is undefined.' ) raise IOError( 'Geographical info is mandatory. Check your static datasets.') if 'res_lon' in list(file_ancillary.keys()): res_lon = file_ancillary['res_lon'] else: log_stream.error( ' ===> Geographical info "res_lon" for writing ascii grid file is undefined.' ) raise IOError( 'Geographical info is mandatory. Check your static datasets.') if 'res_lat' in list(file_ancillary.keys()): res_lat = file_ancillary['res_lat'] else: log_stream.error( ' ===> Geographical info "res_lat" for writing ascii grid file is undefined.' ) raise IOError( 'Geographical info is mandatory. Check your static datasets.') if 'transform' in list(file_ancillary.keys()): transform = file_ancillary['transform'] else: log_stream.error( ' ===> Geographical info "transform" for writing ascii grid file is undefined.' ) raise IOError( 'Geographical info is mandatory. Check your static datasets.') if 'no_data' in list(file_ancillary.keys()): no_data = file_ancillary['no_data'] else: no_data = -9999 if 'espg' in list(file_ancillary.keys()): epsg = file_ancillary['epsg'] else: epsg = proj_epsg_default if 'decimal_precision' in list(file_ancillary.keys()): decimal_precision = int(file_ancillary['decimal_precision']) else: decimal_num = Decimal(str(file_data[0][0])) decimal_precision = abs(decimal_num.as_tuple().exponent) if isinstance(epsg, int): crs = CRS.from_epsg(epsg) elif isinstance(epsg, str): crs = CRS.from_string(epsg) else: log_stream.error( ' ===> Geographical info "epsg" defined by using an unsupported format.' ) raise IOError( 'Geographical EPSG must be in string format "EPSG:4326" or integer format "4326".' ) dset_meta = dict(driver='AAIGrid', height=file_data.shape[0], width=file_data.shape[1], crs=crs, count=1, dtype=str(file_data.dtype), transform=transform, nodata=no_data, decimal_precision=decimal_precision) with rasterio.open(file_name, 'w', **dset_meta) as dset_handle: dset_handle.write(file_data, 1)
def render_netcdf(filename_pattern, variable, output_directory, renderer_file, save_file, renderer_type, colormap, fill, colorspace, palette, palette_stretch, scale, id_variable, lh, legend_breaks, legend_ticks, legend_precision, format, src_crs, dst_crs, res, resampling, anchors, interactive_map, mask_path): """ Render netcdf files to images. colormap is ignored if renderer_file is provided --dst-crs is ignored if using --map option (always uses EPSG:3857 If no colormap or palette is provided, a default palette may be chosen based on the name of the variable. If provided, mask must be 1 for areas to be masked out, and 0 otherwise. It must be in the same CRS as the input datasets, and have the same spatial dimensions. """ # Parameter overrides if interactive_map: dst_crs = 'EPSG:3857' filenames = glob.glob(filename_pattern) if not filenames: raise click.BadParameter('No files found matching that pattern', param='filename_pattern', param_hint='FILENAME_PATTERN') if not os.path.exists(output_directory): os.makedirs(output_directory) mask = get_mask(mask_path) if mask_path is not None else None if renderer_file is not None and not save_file: if not os.path.exists(renderer_file): raise click.BadParameter('does not exist', param='renderer_file', param_hint='renderer_file') # see https://bitbucket.org/databasin/ncdjango/wiki/Home for format renderer_dict = json.loads(open(renderer_file).read()) if variable in renderer_dict and not 'colors' in renderer_dict: renderer_dict = renderer_dict[variable] renderer_type = renderer_dict['type'] if renderer_type == 'stretched': colors = ','.join([str(c[0]) for c in renderer_dict['colors']]) if 'min' in colors or 'max' in colors or 'mean' in colors: statistics = collect_statistics(filenames, (variable, ), mask=mask)[variable] for entry in renderer_dict['colors']: if isinstance(entry[0], basestring): if entry[0] in ('min', 'max', 'mean'): entry[0] = statistics[entry[0]] elif '*' in entry[0]: rel_value, statistic = entry[0].split('*') entry[0] = float(rel_value) * statistics[statistic] renderer = renderer_from_dict(renderer_dict) else: if renderer_type == 'stretched': if palette is not None: renderer = palette_to_stretched_renderer(palette, palette_stretch, filenames, variable, fill_value=fill, mask=mask) elif colormap is None and variable in DEFAULT_PALETTES: palette, palette_stretch = DEFAULT_PALETTES[variable] renderer = palette_to_stretched_renderer(palette, palette_stretch, filenames, variable, fill_value=fill, mask=mask) else: if colormap is None: colormap = 'min:#000000,max:#FFFFFF' renderer = colormap_to_stretched_renderer(colormap, colorspace, filenames, variable, fill_value=fill, mask=mask) elif renderer_type == 'classified': if not palette: raise click.BadParameter( 'palette required for classified (for now)', param='--palette', param_hint='--palette') renderer = palette_to_classified_renderer( palette, filenames, variable, method='equal', fill_value=fill, mask=mask) # TODO: other methods if save_file: if os.path.exists(save_file): with open(save_file, 'r+') as output_file: data = json.loads(output_file.read()) output_file.seek(0) output_file.truncate() data[variable] = renderer.serialize() output_file.write(json.dumps(data, indent=4)) else: with open(save_file, 'w') as output_file: output_file.write(json.dumps({variable: renderer.serialize()})) if renderer_type == 'stretched': if legend_ticks is not None and not legend_breaks: legend_ticks = [float(v) for v in legend_ticks.split(',')] legend = renderer.get_legend( image_height=lh, breaks=legend_breaks, ticks=legend_ticks, max_precision=legend_precision)[0].to_image() elif renderer_type == 'classified': legend = composite_elements(renderer.get_legend()) legend.save( os.path.join(output_directory, '{0}_legend.png'.format(variable))) with Dataset(filenames[0]) as ds: var_obj = ds.variables[variable] dimensions = var_obj.dimensions shape = var_obj.shape num_dimensions = len(shape) if num_dimensions == 3: if id_variable: if shape[0] != ds.variables[id_variable][:].shape[0]: raise click.BadParameter( 'must be same dimensionality as 3rd dimension of {0}'. format(variable), param='--id_variable', param_hint='--id_variable') else: # Guess from the 3rd dimension guess = dimensions[0] if guess in ds.variables and ds.variables[guess][:].shape[ 0] == shape[0]: id_variable = guess ds_crs = get_crs(ds, variable) if not ds_crs and is_geographic(ds, variable): ds_crs = 'EPSG:4326' # Assume all geographic data is WGS84 src_crs = CRS.from_string(ds_crs) if ds_crs else CRS( {'init': src_crs}) if src_crs else None # get transforms, assume last 2 dimensions on variable are spatial in row, col order y_dim, x_dim = dimensions[-2:] coords = SpatialCoordinateVariables.from_dataset( ds, x_dim, y_dim, projection=Proj(src_crs.to_dict()) if src_crs else None) if mask is not None and not mask.shape == shape[-2:]: # Will likely break before this if collecting statistics raise click.BadParameter( 'mask variable shape does not match shape of input spatial dimensions', param='--mask', param_hint='--mask') flip_y = False reproject_kwargs = None if dst_crs is not None: if not src_crs: raise click.BadParameter('must provide src_crs to reproject', param='--src-crs', param_hint='--src-crs') dst_crs = CRS.from_string(dst_crs) src_height, src_width = coords.shape dst_transform, dst_width, dst_height = calculate_default_transform( src_crs, dst_crs, src_width, src_height, *coords.bbox.as_list(), resolution=res) reproject_kwargs = { 'src_crs': src_crs, 'src_transform': coords.affine, 'dst_crs': dst_crs, 'dst_transform': dst_transform, 'resampling': getattr(Resampling, resampling), 'dst_shape': (dst_height, dst_width) } else: dst_transform = coords.affine dst_height, dst_width = coords.shape dst_crs = src_crs if coords.y.is_ascending_order(): # Only needed if we are not already reprojecting the data, since that will flip it automatically flip_y = True if anchors or interactive_map: if not (dst_crs or src_crs): raise click.BadParameter( 'must provide at least src_crs to get Leaflet anchors or interactive map', param='--src-crs', param_hint='--src-crs') leaflet_anchors = get_leaflet_anchors( BBox.from_affine( dst_transform, dst_width, dst_height, projection=Proj(dst_crs) if dst_crs else None)) if anchors: click.echo('Anchors: {0}'.format(leaflet_anchors)) layers = {} for filename in filenames: with Dataset(filename) as ds: click.echo('Processing {0}'.format(filename)) filename_root = os.path.split(filename)[1].replace('.nc', '') if not variable in ds.variables: raise click.BadParameter( 'variable {0} was not found in file: {1}'.format( variable, filename), param='variable', param_hint='VARIABLE') var_obj = ds.variables[variable] if not var_obj.dimensions == dimensions: raise click.ClickException( 'All datasets must have the same dimensions for {0}'. format(variable)) if num_dimensions == 2: data = var_obj[:] if mask is not None: data = numpy.ma.masked_array(data, mask=mask) image_filename = os.path.join( output_directory, '{0}_{1}.{2}'.format(filename_root, variable, format)) if reproject_kwargs: data = warp_array(data, **reproject_kwargs) render_image(renderer, data, image_filename, scale, flip_y=flip_y, format=format) local_filename = os.path.split(image_filename)[1] layers[os.path.splitext(local_filename)[0]] = local_filename elif num_dimensions == 3: for index in range(shape[0]): id = ds.variables[id_variable][ index] if id_variable is not None else index image_filename = os.path.join( output_directory, '{0}_{1}__{2}.{3}'.format(filename_root, variable, id, format)) data = var_obj[index] if mask is not None: data = numpy.ma.masked_array(data, mask=mask) if reproject_kwargs: data = warp_array(data, **reproject_kwargs) render_image(renderer, data, image_filename, scale, flip_y=flip_y, format=format) local_filename = os.path.split(image_filename)[1] layers[os.path.splitext(local_filename) [0]] = local_filename # TODO: not tested recently. Make sure still correct # else: # # Assume last 2 components of shape are lat & lon, rest are iterated over # id_variables = None # if id_variable is not None: # id_variables = id_variable.split(',') # for index, name in enumerate(id_variables): # if name: # assert data.shape[index] == ds.variables[name][:].shape[0] # # ranges = [] # for dim in data.shape[:-2]: # ranges.append(range(0, dim)) # for combined_index in product(*ranges): # id_parts = [] # for index, dim_index in enumerate(combined_index): # if id_variables is not None and index < len(id_variables) and id_variables[index]: # id = ds.variables[id_variables[index]][dim_index] # # if not isinstance(id, basestring): # if isinstance(id, Iterable): # id = '_'.join((str(i) for i in id)) # else: # id = str(id) # # id_parts.append(id) # # else: # id_parts.append(str(dim_index)) # # combined_id = '_'.join(id_parts) # image_filename = os.path.join(output_directory, '{0}__{1}.{2}'.format(filename_root, combined_id, format)) # if reproject_kwargs: # data = warp_array(data, **reproject_kwargs) # NOTE: lack of index will break this # render_image(renderer, data[combined_index], image_filename, scale, flip_y=flip_y, format=format) # # local_filename = os.path.split(image_filename)[1] # layers[os.path.splitext(local_filename)[0]] = local_filename if interactive_map: index_html = os.path.join(output_directory, 'index.html') with open(index_html, 'w') as out: template = Environment( loader=PackageLoader('trefoil.cli')).get_template('map.html') out.write( template.render(layers=json.dumps(layers), bounds=str(leaflet_anchors), variable=variable)) webbrowser.open(index_html)
def test_esri_auth__from_string(): assert CRS.from_string('ESRI:54009').to_string() == 'ESRI:54009'
def test_epsg(): assert CRS({'init': 'epsg:4326'}).to_epsg() == 4326 assert CRS.from_string('+proj=longlat +datum=WGS84 +no_defs').to_epsg() == 4326
def map_eems( eems_file, # output_directory, scale, format, src_crs, resampling): """ Render a NetCDF EEMS model to a web map. """ from EEMSBasePackage import EEMSCmd, EEMSProgram model = EEMSProgram(eems_file) # For each data producing command, store the netcdf file that contains it file_vars = dict() raw_variables = set() for cmd in model.orderedCmds: # This is bottom up, may want to invert filename = None variable = None if cmd.HasResultName(): filename = cmd.GetParam('OutFileName') variable = cmd.GetResultName() elif cmd.IsReadCmd(): filename = cmd.GetParam('OutFileName') variable = cmd.GetParam('NewFieldName') raw_variables.add(variable) if filename and variable: if not filename in file_vars: file_vars[filename] = [] file_vars[filename].append(variable) filenames = file_vars.keys() for filename in filenames: if not os.path.exists(filename): raise click.ClickException( 'Could not find data file from EEMS model: {0}'.format( filename)) dst_crs = 'EPSG:3857' output_directory = tempfile.mkdtemp() click.echo('Using temp directory: {0}'.format(output_directory)) # if not os.path.exists(output_directory): # os.makedirs(output_directory) # Since fuzzy renderer is hardcoded, we can output it now fuzzy_renderer = palette_to_stretched_renderer(DEFAULT_PALETTES['fuzzy'], '1,-1') fuzzy_renderer.get_legend(image_height=150)[0].to_image().save( os.path.join(output_directory, 'fuzzy_legend.png')) template_filename = filenames[0] template_var = file_vars[template_filename][0] with Dataset(template_filename) as ds: var_obj = ds.variables[template_var] dimensions = var_obj.dimensions shape = var_obj.shape num_dimensions = len(shape) if num_dimensions != 2: raise click.ClickException( 'Only 2 dimensions are allowed on data variables for now') ds_crs = get_crs(ds, template_var) if not ds_crs and is_geographic(ds, template_var): ds_crs = 'EPSG:4326' # Assume all geographic data is WGS84 src_crs = CRS.from_string(ds_crs) if ds_crs else CRS( {'init': src_crs}) if src_crs else None # get transforms, assume last 2 dimensions on variable are spatial in row, col order y_dim, x_dim = dimensions[-2:] coords = SpatialCoordinateVariables.from_dataset( ds, x_dim, y_dim, projection=Proj(src_crs) if src_crs else None) # # if mask is not None and not mask.shape == shape[-2:]: # # Will likely break before this if collecting statistics # raise click.BadParameter( # 'mask variable shape does not match shape of input spatial dimensions', # param='--mask', param_hint='--mask' # ) # if not src_crs: raise click.BadParameter('must provide src_crs to reproject', param='--src-crs', param_hint='--src-crs') dst_crs = CRS.from_string(dst_crs) src_height, src_width = coords.shape dst_transform, dst_width, dst_height = calculate_default_transform( src_crs, dst_crs, src_width, src_height, *coords.bbox.as_list()) reproject_kwargs = { 'src_crs': src_crs, 'src_transform': coords.affine, 'dst_crs': dst_crs, 'dst_transform': dst_transform, 'resampling': getattr(Resampling, resampling), 'dst_shape': (dst_height, dst_width) } if not (dst_crs or src_crs): raise click.BadParameter( 'must provide valid src_crs to get interactive map', param='--src-crs', param_hint='--src-crs') leaflet_anchors = get_leaflet_anchors( BBox.from_affine(dst_transform, dst_width, dst_height, projection=Proj(dst_crs) if dst_crs else None)) layers = {} for filename in filenames: with Dataset(filename) as ds: click.echo('Processing dataset {0}'.format(filename)) for variable in file_vars[filename]: click.echo('Processing variable {0}'.format(variable)) if not variable in ds.variables: raise click.ClickException( 'variable {0} was not found in file: {1}'.format( variable, filename)) var_obj = ds.variables[variable] if not var_obj.dimensions == dimensions: raise click.ClickException( 'All datasets must have the same dimensions for {0}'. format(variable)) data = var_obj[:] # if mask is not None: # data = numpy.ma.masked_array(data, mask=mask) if variable in raw_variables: palette = DEFAULT_PALETTES['raw'] palette_stretch = '{0},{1}'.format(data.max(), data.min()) renderer = palette_to_stretched_renderer( palette, palette_stretch) renderer.get_legend( image_height=150, max_precision=2)[0].to_image().save( os.path.join(output_directory, '{0}_legend.png'.format(variable))) else: renderer = fuzzy_renderer image_filename = os.path.join( output_directory, '{0}.{1}'.format(variable, format)) data = warp_array(data, **reproject_kwargs) render_image(renderer, data, image_filename, scale=scale, format=format) local_filename = os.path.split(image_filename)[1] layers[variable] = local_filename index_html = os.path.join(output_directory, 'index.html') with open(index_html, 'w') as out: template = Environment( loader=PackageLoader('trefoil.cli')).get_template('eems_map.html') out.write( template.render(layers=json.dumps(layers), bounds=str(leaflet_anchors), tree=[[cmd, depth] for (cmd, depth) in model.GetCmdTree()], raw_variables=list(raw_variables))) webbrowser.open(index_html)
def test_crs_OSR_equivalence(): crs1 = CRS.from_string('+proj=longlat +datum=WGS84 +no_defs') crs2 = CRS.from_string('+proj=latlong +datum=WGS84 +no_defs') crs3 = CRS({'init': 'epsg:4326'}) assert crs1 == crs2 assert crs1 == crs3
def __init__(self, array, bands, crs, transform, nodataval, driver="GTiff", rio_ds=None): import rasterio import affine from rasterio.crs import CRS self._array = array self._bands = bands meta = {"driver": driver, "nodata": nodataval} # create metadata dictionary if array.dtype in Raster.FLOAT32: dtype = "float32" elif array.dtype in Raster.FLOAT64: dtype = "float64" elif array.dtype in Raster.INT8: dtype = "int8" elif array.dtype in Raster.INT16: dtype = "int16" elif array.dtype in Raster.INT32: dtype = "int32" elif array.dtype in Raster.INT64: dtype = "int64" else: raise TypeError("dtype cannot be determined from Raster") meta['dtype'] = dtype if isinstance(crs, CRS): pass elif isinstance(crs, int): crs = CRS.from_epsg(crs) elif isinstance(crs, str): crs = CRS.from_string(crs) else: TypeError("crs type not understood, provide an epsg or proj4") meta['crs'] = crs count, height, width = array.shape meta['count'] = count meta['height'] = height meta['width'] = width if not isinstance(transform, affine.Affine): raise TypeError("Transform must be defined by an Affine object") meta['transform'] = transform self._meta = meta self._dataset = None self.__arr_dict = {self._bands[b]: arr for b, arr in enumerate(self._array)} self.__xcenters = None self.__ycenters = None if isinstance(rio_ds, rasterio.io.DatasetReader): self._dataset = rio_ds
def test_from_wkt(): wgs84 = CRS.from_string('+proj=longlat +datum=WGS84 +no_defs') from_wkt = CRS.from_wkt(wgs84.wkt) assert wgs84.wkt == from_wkt.wkt
def subset(self, raster_data, **kwargs): # It is possible our user has drawn a polygon where part of the # shape is outside the dataset, intersect with the rasterdata # shape to make sure we don't try to select/mask data that is # outside the bounds of our dataset. # Convert the image corner coordinates to WGS84 trgt_srs = CRS.from_string("EPSG:4326") src_srs = raster_data.crs transformed = [transform_coordinates(src_srs, trgt_srs, [i[0]], [i[1]]) for i in raster_data.shape.exterior.coords] reprojected_data_extent = sPolygon(transformed) clipped = self.intersection(reprojected_data_extent) # Polygon is completely outside the dataset, return whatever # would have been returned by get_data() if not bool(clipped): ul = raster_data.index(self.bounds[0], self.bounds[1]) lr = raster_data.index(self.bounds[2], self.bounds[3]) window = self.get_data_window(ul[0], ul[1], lr[0], lr[1]) return raster_data.get_data(window=window, **kwargs) ul = raster_data.index(clipped.bounds[0], clipped.bounds[1]) lr = raster_data.index(clipped.bounds[2], clipped.bounds[3]) window = self.get_data_window(ul[0], ul[1], lr[0], lr[1]) data = raster_data.get_data(window=window, **kwargs) # out_shape must be determined from data's shape, get_data # may have returned a bounding box of data that is smaller than # the implicit shape of the window we passed. e.g. if the window # is partially outside the extent of the raster data. Note that # we index with negative numbers here because we may or may not # have a time dimension. num_bands = len(raster_data.band_indexes) if num_bands > 1: out_shape = data.shape[-3], data.shape[-2] else: out_shape = data.shape[-2], data.shape[-1] coordinates = [] for lat, lon in clipped.exterior.coords: x, y = raster_data.index(lat, lon) coordinates.append((y - window[0][1], x - window[0][0])) # Mask the final polygon mask = rasterize( [({'type': 'Polygon', 'coordinates': [coordinates]}, 0)], out_shape=out_shape, fill=1, all_touched=True, dtype=np.uint8) # If we have more than one band, expand the mask so it includes # A "channel" dimension (e.g. shape is now (lat, lon, channel)) if num_bands > 1: mask = mask[..., np.newaxis] * np.ones(num_bands) # Finally broadcast mask to data because data may be from a # Raster data collection and include a time component # (e.g. shape could be (time, lat, lon), or even # (time, lat, lon, channels)) _, mask = np.broadcast_arrays(data, mask) data[mask.astype(bool)] = raster_data.nodata return np.ma.masked_equal(data, raster_data.nodata)
def get(self, request, pk=None, project_pk=None, tile_type="", z="", x="", y="", scale=1): """ Get a tile image """ task = self.get_and_check_task(request, pk) z = int(z) x = int(x) y = int(y) scale = int(scale) ext = "png" driver = "jpeg" if ext == "jpg" else ext indexes = None nodata = None rgb_tile = None formula = self.request.query_params.get('formula') bands = self.request.query_params.get('bands') rescale = self.request.query_params.get('rescale') color_map = self.request.query_params.get('color_map') hillshade = self.request.query_params.get('hillshade') boundaries_feature = self.request.query_params.get('boundaries') if boundaries_feature == '': boundaries_feature = None if boundaries_feature is not None: try: boundaries_feature = json.loads(boundaries_feature) except json.JSONDecodeError: raise exceptions.ValidationError("Invalid boundaries parameter") if formula == '': formula = None if bands == '': bands = None if rescale == '': rescale = None if color_map == '': color_map = None if hillshade == '' or hillshade == '0': hillshade = None try: expr, _ = lookup_formula(formula, bands) except ValueError as e: raise exceptions.ValidationError(str(e)) if tile_type in ['dsm', 'dtm'] and rescale is None: rescale = "0,1000" if tile_type == 'orthophoto' and rescale is None: rescale = "0,255" if tile_type in ['dsm', 'dtm'] and color_map is None: color_map = "gray" if tile_type == 'orthophoto' and formula is not None: if color_map is None: color_map = "gray" if rescale is None: rescale = "-1,1" if nodata is not None: nodata = np.nan if nodata == "nan" else float(nodata) tilesize = scale * 256 url = get_raster_path(task, tile_type) if not os.path.isfile(url): raise exceptions.NotFound() with COGReader(url) as src: if not src.tile_exists(z, x, y): raise exceptions.NotFound("Outside of bounds") with COGReader(url) as src: minzoom, maxzoom = get_zoom_safe(src) has_alpha = has_alpha_band(src.dataset) if z < minzoom - ZOOM_EXTRA_LEVELS or z > maxzoom + ZOOM_EXTRA_LEVELS: raise exceptions.NotFound() if boundaries_feature is not None: try: boundaries_cutline = create_cutline(src.dataset, boundaries_feature, CRS.from_string('EPSG:4326')) except: raise exceptions.ValidationError("Invalid boundaries") else: boundaries_cutline = None # Handle N-bands datasets for orthophotos (not plant health) if tile_type == 'orthophoto' and expr is None: ci = src.dataset.colorinterp # More than 4 bands? if len(ci) > 4: # Try to find RGBA band order if ColorInterp.red in ci and \ ColorInterp.green in ci and \ ColorInterp.blue in ci: indexes = (ci.index(ColorInterp.red) + 1, ci.index(ColorInterp.green) + 1, ci.index(ColorInterp.blue) + 1,) else: # Fallback to first three indexes = (1, 2, 3,) elif has_alpha: indexes = non_alpha_indexes(src.dataset) # Workaround for https://github.com/OpenDroneMap/WebODM/issues/894 if nodata is None and tile_type == 'orthophoto': nodata = 0 resampling = "nearest" padding = 0 if tile_type in ["dsm", "dtm"]: resampling = "bilinear" padding = 16 try: with COGReader(url) as src: if expr is not None: if boundaries_cutline is not None: tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, expression=expr, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling) else: if boundaries_cutline is not None: tile = src.tile(x, y, z, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling, vrt_options={'cutline': boundaries_cutline}) else: tile = src.tile(x, y, z, indexes=indexes, tilesize=tilesize, nodata=nodata, padding=padding, resampling_method=resampling) except TileOutsideBounds: raise exceptions.NotFound("Outside of bounds") if color_map: try: colormap.get(color_map) except InvalidColorMapName: raise exceptions.ValidationError("Not a valid color_map value") intensity = None try: rescale_arr = list(map(float, rescale.split(","))) except ValueError: raise exceptions.ValidationError("Invalid rescale value") options = img_profiles.get(driver, {}) if hillshade is not None: try: hillshade = float(hillshade) if hillshade <= 0: hillshade = 1.0 except ValueError: raise exceptions.ValidationError("Invalid hillshade value") if tile.data.shape[0] != 1: raise exceptions.ValidationError( "Cannot compute hillshade of non-elevation raster (multiple bands found)") delta_scale = (maxzoom + ZOOM_EXTRA_LEVELS + 1 - z) * 4 dx = src.dataset.meta["transform"][0] * delta_scale dy = -src.dataset.meta["transform"][4] * delta_scale ls = LightSource(azdeg=315, altdeg=45) # Hillshading is not a local tile operation and # requires neighbor tiles to be rendered seamlessly elevation = get_elevation_tiles(tile.data[0], url, x, y, z, tilesize, nodata, resampling, padding) intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade) intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2] if intensity is not None: rgb = tile.post_process(in_range=(rescale_arr,)) if colormap: rgb, _ = apply_cmap(rgb.data, colormap.get(color_map)) if rgb.data.shape[0] != 3: raise exceptions.ValidationError( "Cannot process tile: intensity image provided, but no RGB data was computed.") intensity = intensity * 255.0 rgb = hsv_blend(rgb, intensity) if rgb is not None: return HttpResponse( render(rgb, tile.mask, img_format=driver, **options), content_type="image/{}".format(ext) ) if color_map is not None: return HttpResponse( tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, colormap=colormap.get(color_map), **options), content_type="image/{}".format(ext) ) return HttpResponse( tile.post_process(in_range=(rescale_arr,)).render(img_format=driver, **options), content_type="image/{}".format(ext) )
def rasterize(ctx, files, output, driver, like, bounds, dimensions, res, src_crs, all_touched, default_value, fill, prop, overwrite, nodata, creation_options): """Rasterize GeoJSON into a new or existing raster. If the output raster exists, rio-rasterize will rasterize feature values into all bands of that raster. The GeoJSON is assumed to be in the same coordinate reference system as the output unless --src-crs is provided. --default_value or property values when using --property must be using a data type valid for the data type of that raster. If a template raster is provided using the --like option, the affine transform and data type from that raster will be used to create the output. Only a single band will be output. The GeoJSON is assumed to be in the same coordinate reference system unless --src-crs is provided. --default_value or property values when using --property must be using a data type valid for the data type of that raster. --driver, --bounds, --dimensions, --res, --nodata are ignored when output exists or --like raster is provided If the output does not exist and --like raster is not provided, the input GeoJSON will be used to determine the bounds of the output unless provided using --bounds. --dimensions or --res are required in this case. If --res is provided, the bottom and right coordinates of bounds are ignored. Note ---- The GeoJSON is not projected to match the coordinate reference system of the output or --like rasters at this time. This functionality may be added in the future. """ from rasterio.crs import CRS from rasterio.features import rasterize from rasterio.features import bounds as calculate_bounds output, files = resolve_inout(files=files, output=output, overwrite=overwrite) bad_param = click.BadParameter('invalid CRS. Must be an EPSG code.', ctx, param=src_crs, param_hint='--src_crs') has_src_crs = src_crs is not None try: src_crs = CRS.from_string(src_crs) if has_src_crs else CRS.from_string( 'EPSG:4326') except CRSError: raise bad_param # If values are actually meant to be integers, we need to cast them # as such or rasterize creates floating point outputs if default_value == int(default_value): default_value = int(default_value) if fill == int(fill): fill = int(fill) with ctx.obj['env']: def feature_value(feature): if prop and 'properties' in feature: return feature['properties'].get(prop, default_value) return default_value with click.open_file(files.pop(0) if files else '-') as gj_f: geojson = json.loads(gj_f.read()) if 'features' in geojson: geometries = [] for f in geojson['features']: geometries.append((f['geometry'], feature_value(f))) elif 'geometry' in geojson: geometries = ((geojson['geometry'], feature_value(geojson)), ) else: raise click.BadParameter('Invalid GeoJSON', param=input, param_hint='input') geojson_bounds = geojson.get('bbox', calculate_bounds(geojson)) if rasterio.shutil.exists(output): with rasterio.open(output, 'r+') as out: if has_src_crs and src_crs != out.crs: raise click.BadParameter( 'GeoJSON does not match crs of ' 'existing output raster', param='input', param_hint='input') if disjoint_bounds(geojson_bounds, out.bounds): click.echo( "GeoJSON outside bounds of existing output " "raster. Are they in different coordinate " "reference systems?", err=True) meta = out.meta result = rasterize(geometries, out_shape=(meta['height'], meta['width']), transform=meta.get('affine', meta['transform']), all_touched=all_touched, dtype=meta.get('dtype', None), default_value=default_value, fill=fill) for bidx in range(1, meta['count'] + 1): data = out.read(bidx, masked=True) # Burn in any non-fill pixels, and update mask accordingly ne = result != fill data[ne] = result[ne] if data.mask.any(): data.mask[ne] = False out.write(data, indexes=bidx) else: if like is not None: template_ds = rasterio.open(like) if has_src_crs and src_crs != template_ds.crs: raise click.BadParameter( 'GeoJSON does not match crs of ' '--like raster', param='input', param_hint='input') if disjoint_bounds(geojson_bounds, template_ds.bounds): click.echo( "GeoJSON outside bounds of --like raster. " "Are they in different coordinate reference " "systems?", err=True) kwargs = template_ds.profile kwargs['count'] = 1 kwargs['transform'] = template_ds.transform template_ds.close() else: bounds = bounds or geojson_bounds if src_crs.is_geographic: if (bounds[0] < -180 or bounds[2] > 180 or bounds[1] < -80 or bounds[3] > 80): raise click.BadParameter( "Bounds are beyond the valid extent for " "EPSG:4326.", ctx, param=bounds, param_hint='--bounds') if dimensions: width, height = dimensions res = ((bounds[2] - bounds[0]) / float(width), (bounds[3] - bounds[1]) / float(height)) else: if not res: raise click.BadParameter( 'pixel dimensions are required', ctx, param=res, param_hint='--res') elif len(res) == 1: res = (res[0], res[0]) width = max( int(ceil((bounds[2] - bounds[0]) / float(res[0]))), 1) height = max( int(ceil((bounds[3] - bounds[1]) / float(res[1]))), 1) kwargs = { 'count': 1, 'crs': src_crs, 'width': width, 'height': height, 'transform': Affine(res[0], 0, bounds[0], 0, -res[1], bounds[3]), 'driver': driver } if driver: kwargs["driver"] = driver kwargs.update(**creation_options) if nodata is not None: kwargs['nodata'] = nodata result = rasterize(geometries, out_shape=(kwargs['height'], kwargs['width']), transform=kwargs['transform'], all_touched=all_touched, dtype=kwargs.get('dtype', None), default_value=default_value, fill=fill) if 'dtype' not in kwargs: kwargs['dtype'] = result.dtype with rasterio.open(output, 'w', **kwargs) as out: out.write(result, indexes=1)
def __init__( self, data, index_column=None, x_column=None, y_column=None, crs=None, res=None, **df_init_kws, ): """ Initialize the land data frame instance. The default parameters are defined to work with SFSO data out-of-the-box, but they can be modified through the following keyword arguments: Parameters ---------- data : ndarray (structured or homogeneous), Iterable, dict or DataFrame Data that will be passed to the initialization method of `pd.DataFrame`. index_column : str, optional Label of the index column. If `None` is provided, the value set in `settings.DEFAULT_INDEX_COLUMN` will be taken. x_column : str, optional Label of the x-coordinates column. If `None` is provided, the value set in `settings.DEFAULT_X_COLUMN` will be taken. y_column : str, optional Label of the y-coordinates column. If `None` is provided, the value set in `settings.DEFAULT_Y_COLUMN` will be taken. crs : str or rasterio CRS, optional Coordinate reference system, as string or as rasterio CRS object. If a string is provided, it will be passed to `rasterio.crs.CRS.from_string` to instantiatie a rasterio CRS object. If `None` is provided, the value set in `settings.DEFAULT_CRS` will be taken. res : tuple, optional The (x, y) resolution of the dataset. If `None` is provided, the value set in `settings.DEFAULT_RES` will be taken. """ # init the pandas dataframe super(LandDataFrame, self).__init__(data, **df_init_kws) # set the index if index_column is None: index_column = settings.DEFAULT_INDEX_COLUMN if self.index.name != index_column: self.set_index(index_column, inplace=True) # set the rest of attributes if x_column is None: x_column = settings.DEFAULT_X_COLUMN if y_column is None: y_column = settings.DEFAULT_Y_COLUMN if crs is None: crs = CRS.from_string(settings.DEFAULT_CRS) elif isinstance(crs, str): crs = CRS.from_string(crs) if res is None: res = settings.DEFAULT_RES self.x_column = x_column self.y_column = y_column self.crs = crs self.res = res
def test_deprecated_param(path_rgb_byte_tif): """dst_crs is deprecated""" with rasterio.open(path_rgb_byte_tif) as src: with pytest.warns(RasterioDeprecationWarning): vrt = WarpedVRT(src, dst_crs=DST_CRS) assert vrt.dst_crs == CRS.from_string(DST_CRS)
def quicklook_rgb_xr( xarr: xr.Dataset, rgb_varlist: List[str], scale_factor: Union[float, int], dwnscale_factor: float = 3, mask_nodata: bool = False, ) -> Tuple[np.ndarray, dict]: """ Generate a quicklook RGB from an xarray object Parameters ---------- xarr : xr.Dataset xarray dataset at a given instance, e.g. xarr = parent_xarr.isel(time=XX) where XX is the user specified time index rgb_varlist : list A list of variable names used to generate the RGB, e.g. rgb_varlist = ["lmbskyg_red", "lmbskyg_green", "lmbskyg_blue"] scale_factor : float or int scale factor to convert integer values to reflectances dwnscale_factor : float >= 1 The downscaling factor. If dwnscale_factor = 3 then the spatial resolution of the quicklook RGB will be reduced by a factor of three from the native resolution of the sensors' RGB bands. If dwnscale_factor = 1 then no downscaling is performed, thus the native resolution of the RGB bands are used. mask_nodata : bool Whether to set nodata pixels [< 0 or > scale_factor] to grey in the quicklook RGB Returns ------- rgb_im : numpy.ndarray RGB image with the following dimensions [nrows, ncols, 3] for the three channels rgb_meta : dict Metadata dictionary taken from rasterio Raises ------ ValueError * if len(rgb_varlist) != 3 * if dwnscale_factor < 1 """ if len(rgb_varlist) != 3: raise ValueError("rgb_bandlist must have three elements") src_trans = xarr.affine src_crs = CRS.from_string(xarr.attrs["crs"]) # compute the downsample transform matrix dst_trans = src_trans * src_trans.scale(dwnscale_factor, dwnscale_factor) dst_nrows = int(xarr.dims["y"] / dwnscale_factor) dst_ncols = int(xarr.dims["x"] / dwnscale_factor) # create a rasterio-style metadata dict for destination # image. This will be needed in creating a shapefile # from the matplotlib polygon var_dtype = xarr.variables[rgb_varlist[0]].dtype nodata = xarr.variables[rgb_varlist[0]].attrs["nodata"] dst_meta = { "transform": dst_trans, "crs": src_crs, "height": dst_nrows, "width": dst_ncols, "dtype": var_dtype.name, "nodata": nodata, "count": 3, "driver": "GTiff", # dummy } if dwnscale_factor > 1: refl_im = np.zeros([3, dst_nrows, dst_ncols], order="C", dtype=var_dtype) for z, varname in enumerate(rgb_varlist): var_ds = xarr.variables[varname] # resample reproject( source=var_ds.values, destination=refl_im[z, :, :], src_transform=src_trans, src_crs=src_crs, src_nodata=nodata, dst_transform=dst_trans, dst_crs=src_crs, # keep the same projection dst_nodata=nodata, resampling=Resampling.nearest, ) else: # dwnscale_factor = 1. This is likely to be memory intensive refl_im = np.array( [ xarr.variables[rgb_varlist[0]].values, xarr.variables[rgb_varlist[1]].values, xarr.variables[rgb_varlist[2]].values, ], order="C", ) # use NASA-OBPG SeaDAS's transformation to create a very pretty RGB rgb_im = seadas_style_rgb( refl_img=refl_im, rgb_ix=[0, 1, 2], scale_factor=scale_factor, mask_nodata=mask_nodata, ) return rgb_im, dst_meta
def test_crs_compound_epsg(): assert CRS.from_string("EPSG:4326+3855").to_wkt().startswith("COMPD")