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_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_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_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_issue_1446(): """Confirm resolution of #1446""" g = transform_geom( CRS.from_epsg(4326), CRS.from_epsg(32610), {"type": "Point", "coordinates": (-122.51403808499907, 38.06106733107932)}, ) assert round(g["coordinates"][0], 1) == 542630.9 assert round(g["coordinates"][1], 1) == 4212702.1
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_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 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 main(): samplefile = r'bxk1-d-ck.idf' tiffile = samplefile.replace('.idf', '.geotiff') dtype = rasterio.float64 driver = 'AAIGrid' crs = CRS.from_epsg(28992) # read data from idf file idffile = idfpy.IdfFile(filepath=samplefile, mode='rb') geotransform = idffile.geotransform height = idffile.header['nrow'] width = idffile.header['ncol'] nodata = idffile.header['nodata'] transform = Affine.from_gdal(*geotransform) # write data from idf file to geotiff with rasterio profile = { 'width': width, 'height': height, 'count': 1, 'dtype': dtype, 'driver': driver, 'crs': crs, 'transform': transform, 'nodata': nodata, } # the default profile would be sufficient for the example, however the profile dict shows how to make the export # profile idffile.to_raster(tiffile, **profile)
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 __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_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_reproject_init_dest_nodata(): """No pixels should transfer over""" crs = CRS.from_epsg(4326) transform = Affine.identity() source = np.zeros((1, 100, 100)) destination = np.ones((1, 100, 100)) reproject( source, destination, src_crs=crs, src_transform=transform, dst_crs=crs, dst_transform=transform, src_nodata=0, init_dest_nodata=False ) assert destination.all()
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_linear_units_factor(): """CRS linear units can be had""" assert CRS.from_epsg(3857).linear_units_factor[0] == 'metre' assert CRS.from_epsg(3857).linear_units_factor[1] == 1.0 assert CRS.from_epsg(2261).linear_units_factor[0] == 'US survey foot' assert CRS.from_epsg(2261).linear_units_factor[1] == pytest.approx(0.3048006096012192) with pytest.raises(CRSError): CRS.from_epsg(4326).linear_units_factor
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_get_area_def_from_raster(self): from pyresample import utils from rasterio.crs import CRS from affine import Affine x_size = 791 y_size = 718 transform = Affine(300.0379266750948, 0.0, 101985.0, 0.0, -300.041782729805, 2826915.0) crs = CRS(init='epsg:3857') source = tmptiff(x_size, y_size, transform, crs=crs) area_id = 'area_id' proj_id = 'proj_id' name = 'name' area_def = utils._rasterio.get_area_def_from_raster( source, area_id=area_id, name=name, proj_id=proj_id) self.assertEqual(area_def.area_id, area_id) self.assertEqual(area_def.proj_id, proj_id) self.assertEqual(area_def.name, name) self.assertEqual(area_def.width, x_size) self.assertEqual(area_def.height, y_size) self.assertDictEqual(crs.to_dict(), area_def.proj_dict) self.assertTupleEqual(area_def.area_extent, (transform.c, transform.f + transform.e * y_size, transform.c + transform.a * x_size, transform.f))
def feature_to_mercator(feature): '''Normalize feature and converts coords to 3857. Args: feature: geojson feature to convert to mercator geometry. ''' # Ref: https://gist.github.com/dnomadb/5cbc116aacc352c7126e779c29ab7abe src_crs = CRS.from_epsg(4326) dst_crs = CRS.from_epsg(3857) geometry = feature['geometry'] if geometry['type'] == 'Polygon': xys = (zip(*part) for part in geometry['coordinates']) xys = (list(zip(*transform(src_crs, dst_crs, *xy))) for xy in xys) yield {'coordinates': list(xys), 'type': 'Polygon'} elif geometry['type'] == 'MultiPolygon': for component in geometry['coordinates']: xys = (zip(*part) for part in component) xys = (list(zip(*transform(src_crs, dst_crs, *xy))) for xy in xys) yield {'coordinates': list(xys), 'type': 'Polygon'}
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 example_reproject(): import idfpy from matplotlib import pyplot as plt from rasterio import Affine from rasterio.crs import CRS from rasterio.warp import reproject, Resampling import numpy as np with idfpy.open('bxk1-d-ck.idf') as src: a = src.read(masked=True) nr, nc = src.header['nrow'], src.header['ncol'] dx, dy = src.header['dx'], src.header['dy'] src_transform = Affine.from_gdal(*src.geotransform) # define new grid transform (same extent, 10 times resolution) dst_transform = Affine.translation(src_transform.c, src_transform.f) dst_transform *= Affine.scale(dx / 10., -dy / 10.) # define coordinate system (here RD New) src_crs = CRS.from_epsg(28992) # initialize new data array b = np.empty((10*nr, 10*nc)) # reproject using Rasterio reproject( source=a, destination=b, src_transform=src_transform, dst_transform=dst_transform, src_crs=src_crs, dst_crs=src_crs, resampling=Resampling.bilinear, ) # result as masked array b = np.ma.masked_equal(b, a.fill_value) # plot images fig, axes = plt.subplots(nrows=2, ncols=1) axes[0].imshow(a.filled(np.nan)) axes[0].set_title('bxk1 original') axes[1].imshow(b.filled(np.nan)) axes[1].set_title('bxk1 resampled') plt.show()
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 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 to_raster(self, fp=None, epsg=28992, driver='AAIGrid'): """export Idf to a geotiff""" self.check_read() if fp is None: fp = self.filepath.replace('.idf', '.geotiff') logging.warning('no filepath was given, exported to {fp}'.format(fp=fp)) # set profile profile = { 'width': self.header['ncol'], 'height': self.header['nrow'], 'transform': Affine.from_gdal(*self.geotransform), 'nodata': self.header['nodata'], 'count': 1, 'dtype': rasterio.float64, 'driver': driver, 'crs': CRS.from_epsg(epsg), } logging.info('writing to {f:}'.format(f=fp)) with rasterio.open(fp, 'w', **profile) as dst: dst.write(self.masked_data.astype(profile['dtype']), 1)
# coding=utf-8 from __future__ import absolute_import import mercantile from rasterio.crs import CRS from .. import InvalidTileRequest MIN_LAT = -85.05113 MIN_LON = -180.0 MAX_LAT = 85.05113 MAX_LON = 180 WGS84_CRS = CRS.from_epsg(4326) class Catalog(object): _bounds = [MIN_LON, MIN_LAT, MAX_LON, MAX_LAT] _center = [0, 0, 2] _headers = {} _id = None _maxzoom = 22 _metadata_url = None _minzoom = 0 _name = "Untitled" _provider = None _provider_url = None @property def bounds(self): w, s, e, n = self._bounds return (max(MIN_LON, w), max(MIN_LAT, s), min(MAX_LON,
"""timvt.custom.tms: Custom TileMatrixSet.""" import morecantile from rasterio.crs import CRS # CUSTOM TMS for EPSG:3413 EPSG3413 = morecantile.TileMatrixSet.custom( (-4194300, -4194300, 4194300, 4194300), CRS.from_epsg(3413), identifier="EPSG3413", matrix_scale=[2, 2], ) # CUSTOM TMS for EPSG:6933 # info from https://epsg.io/6933 EPSG6933 = morecantile.TileMatrixSet.custom( (-17357881.81713629, -7324184.56362408, 17357881.81713629, 7324184.56362408), CRS.from_epsg(6933), identifier="EPSG6933", matrix_scale=[1, 1], )
def test_esri_auth__to_epsg(): assert CRS.from_user_input('ESRI:54009').to_epsg() is None
def test_from_authority__to_authority(): assert CRS.from_authority("EPSG", 4326).to_authority() == ("EPSG", "4326")
def test_latlong_northingeasting_gdal3(): """Check CRS created from epsg with GDAL 3.""" assert epsg_treats_as_latlong(CRS.from_epsg(4326)) assert epsg_treats_as_northingeasting(CRS.from_epsg(2193))
class GeoRasterCropTest(TestCase): metric_affine = Affine(1, 0.0, 2653750, 0.0, -1, 4594461) metric_crs = CRS({'init': 'epsg:3857'}) geographic_affine = Affine(8.03258139076081e-06, 0.0, 23.83904185232179, 0.0, -8.03258139076081e-06, 38.10635414334363) geographic_crs = CRS({'init': 'epsg:4326'}) def metric_raster(cls): return GeoRaster2(np.random.uniform(0, 256, (3, 3911, 3708)), affine=cls.metric_affine, crs=cls.metric_crs) def geographic_raster(cls): return GeoRaster2(np.random.uniform(0, 256, (3, 4147, 4147)), affine=cls.geographic_affine, crs=cls.geographic_crs) def test_crop_and_get_tile_do_without_resizing_the_same(self): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster2 = GeoRaster2.open(rf.name) tile15 = raster2.get_tile(*tiles[15], blocksize=None) # load the image data raster2.image cropped15 = raster2.crop(shape) self.assertEqual(tile15, cropped15) @window_data def test_crop_and_get_tile_do_the_same(self): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster2 = GeoRaster2.open(rf.name) tile15 = raster2.get_tile(*tiles[15]) # load the image data raster2.image cropped15 = raster2.crop(shape, mercator_zoom_to_resolution[15]) self.assertEqual(tile15, cropped15) @window_data def test_crop_and_get_tile_do_the_same_when_image_is_populated(self): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster = GeoRaster2.open(rf.name) tile15 = raster.get_tile(*tiles[15]) raster._populate_from_rasterio_object(read_image=True) cropped_15 = raster.crop(shape, mercator_zoom_to_resolution[15]) self.assertEqual(tile15, cropped_15) @window_data def test_crop_image_from_and_get_win_do_the_same_with_resize(self): bounds = (2, 3, 4, 5) win = rasterio.windows.Window(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1]) xsize = round((bounds[2] - bounds[0]) / 2) ysize = round((bounds[3] - bounds[1]) / 2) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster.save('area.tif', tags={'AREA_OR_POINT': 'Area'}) raster.save('point.tif', tags={'AREA_OR_POINT': 'Point'}) saved_raster = GeoRaster2.open(rf.name) cropped_win = saved_raster.get_window(win, xsize=xsize, ysize=ysize) saved_raster_area = GeoRaster2.open('area.tif') cropped_win_area = saved_raster_area.get_window(win, xsize=xsize, ysize=ysize) saved_raster_point = GeoRaster2.open('point.tif') cropped_win_point = saved_raster_point.get_window(win, xsize=xsize, ysize=ysize) cropped_image = raster._crop(bounds, xsize=xsize, ysize=ysize) print('cropped_win_area pixels\n', cropped_win_area.image) print('cropped_win_point pixels\n', cropped_win_point.image) print('cropped_win pixels\n', cropped_win.image) print('cropped_image pixels\n', cropped_image.image) if (cropped_win_point == cropped_win_area): print('point == area') if (cropped_image == cropped_win_area): print('image == area') if (cropped_image == cropped_win_point): print('image == point') if (cropped_win == cropped_win_area): print('win == area') if (cropped_win == cropped_win_point): print('win == point') self.assertEqual(cropped_image, cropped_win) @framing def test_crop_and_get_tile_do_the_same_when_image_is_populated_first_high_zoom( self): coords = mercantile.xy_bounds(*tiles[17]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster = GeoRaster2.open(rf.name) raster._populate_from_rasterio_object(read_image=True) tile17 = raster.get_tile(*tiles[17]) cropped_17 = raster.crop(shape, mercator_zoom_to_resolution[17]) self.assertEqual(tile17, cropped_17) @framing def test_crop_and_get_tile_do_the_same_when_image_is_populated_first_mid_zoom( self): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster = GeoRaster2.open(rf.name) raster._populate_from_rasterio_object(read_image=True) tile15 = raster.get_tile(*tiles[15]) cropped_15 = raster.crop(shape, mercator_zoom_to_resolution[15]) self.assertEqual(tile15, cropped_15) @framing def test_crop_and_get_tile_do_the_same_when_image_is_populated_first_for_low_zoom( self): coords = mercantile.xy_bounds(*tiles[11]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster = GeoRaster2.open(rf.name) raster._populate_from_rasterio_object(read_image=True) tile11 = raster.get_tile(*tiles[11]) cropped_11 = raster.crop(shape, mercator_zoom_to_resolution[11]) self.assertEqual(tile11, cropped_11) def test_crop_image_from_and_get_win_do_the_same_full_resolution(self): bounds = (200, 130, 400, 150) win = rasterio.windows.Window(bounds[0], bounds[1], bounds[2] - bounds[0], bounds[3] - bounds[1]) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) saved_raster = GeoRaster2.open(rf.name) cropped_win = saved_raster.get_window(win) cropped_image = raster._crop(bounds) self.assertEqual(cropped_image, cropped_win) @patch.object(GeoRaster2, '_crop') def test_crop_use_crop_image_for_a_loaded_image(self, mock__crop): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() raster.crop(shape, mercator_zoom_to_resolution[15]) assert mock__crop.called_once @patch.object(GeoRaster2, 'get_window') def test_crop_use_get_window_for_a_not_loaded_image(self, mock_get_window): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() with NamedTemporaryFile(mode='w+b', suffix=".tif") as rf: raster.save(rf.name) raster = GeoRaster2.open(rf.name) raster.crop(shape, mercator_zoom_to_resolution[15]) assert mock_get_window.called_once def test_crop_returns_full_resolution_as_default(self): coords = mercantile.xy_bounds(*tiles[17]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() _, win = raster._vector_to_raster_bounds(shape) cropped = raster.crop(shape) self.assertEqual( cropped.shape, (raster.num_bands, round(win.height), round(win.width))) self.assertEqual(cropped.affine[0], raster.affine[0]) def test_memory_crop_returns_resized_resolution(self): coords = mercantile.xy_bounds(*tiles[15]) shape = GeoVector(Polygon.from_bounds(*coords), WEB_MERCATOR_CRS) raster = self.metric_raster() cropped = raster.crop(shape, mercator_zoom_to_resolution[15]) self.assertEqual(cropped.shape, (raster.num_bands, 256, 256)) self.assertAlmostEqual(cropped.affine[0], mercator_zoom_to_resolution[15], 2) def test_geographic_crop(self): raster = self.geographic_raster() rhombus_on_image = Polygon([[0, 2], [1, 1], [2, 2], [1, 3]]) # in pixels rhombus_world = raster.to_world(rhombus_on_image) cropped = raster.crop(rhombus_world) r = raster[0:2, 1:3] assert cropped == r def test_geographic_crop_with_resize(self): coords = mercantile.xy_bounds(*tiles[17]) raster = self.geographic_raster() vector = GeoVector(Polygon.from_bounds(*coords), crs=self.metric_crs).reproject(self.geographic_crs) cropped = raster.crop(vector, mercator_zoom_to_resolution[17]) x_ex_res, y_ex_res = convert_resolution_from_meters_to_deg( self.metric_affine[6], mercator_zoom_to_resolution[17]) self.assertAlmostEqual(cropped.affine[0], x_ex_res) self.assertAlmostEqual(abs(cropped.affine[4]), y_ex_res, 6)
def _tile_read( src_dst, bounds, tilesize, indexes=None, nodata=None, resampling_method="bilinear", tile_edge_padding=2, dst_crs=CRS({"init": "EPSG:3857"}), bounds_crs=None, minimum_tile_cover=None, warp_vrt_option={}, ): """ Read data and mask. Attributes ---------- src_dst : rasterio.io.DatasetReader rasterio.io.DatasetReader object bounds : list Output bounds (left, bottom, right, top) in target crs ("dst_crs"). tilesize : int Output image size indexes : list of ints or a single int, optional, (defaults: None) If `indexes` is a list, the result is a 3D array, but is a 2D array if it is a band index number. nodata: int or float, optional (defaults: None) resampling_method : str, optional (default: "bilinear") Resampling algorithm. tile_edge_padding : int, optional (default: 2) Padding to apply to each edge of the tile when retrieving data to assist in reducing resampling artefacts along edges. dst_crs: CRS or str, optional Target coordinate reference system (default "epsg:3857"). bounds_crs: CRS or str, optional Overwrite bounds coordinate reference system (default None, equal to dst_crs). minimum_tile_cover: float, optional (default: None) Minimum % overlap for which to raise an error with dataset not covering enought of the tile. warp_vrt_option: dict, optional (default: {}) These will be passed to the rasterio.warp.WarpedVRT class. Returns ------- data : numpy ndarray mask: numpy array """ if isinstance(indexes, int): indexes = [indexes] elif isinstance(indexes, tuple): indexes = list(indexes) if not bounds_crs: bounds_crs = dst_crs bounds = transform_bounds(bounds_crs, dst_crs, *bounds, densify_pts=21) vrt_params = dict(add_alpha=True, crs=dst_crs, resampling=Resampling[resampling_method]) vrt_transform, vrt_width, vrt_height = get_vrt_transform(src_dst, bounds, dst_crs=dst_crs) out_window = windows.Window(col_off=0, row_off=0, width=vrt_width, height=vrt_height) src_bounds = transform_bounds(src_dst.crs, dst_crs, *src_dst.bounds, densify_pts=21) x_overlap = max( 0, min(src_bounds[2], bounds[2]) - max(src_bounds[0], bounds[0])) y_overlap = max( 0, min(src_bounds[3], bounds[3]) - max(src_bounds[1], bounds[1])) cover_ratio = (x_overlap * y_overlap) / ((bounds[2] - bounds[0]) * (bounds[3] - bounds[1])) if minimum_tile_cover and cover_ratio < minimum_tile_cover: raise TileOutsideBounds( "Dataset covers less than {:.0f}% of tile".format(cover_ratio * 100)) if tile_edge_padding > 0 and not _requested_tile_aligned_with_internal_tile( src_dst, bounds, tilesize): vrt_transform = vrt_transform * Affine.translation( -tile_edge_padding, -tile_edge_padding) orig_vrt_height = vrt_height orig_vrt_width = vrt_width vrt_height = vrt_height + 2 * tile_edge_padding vrt_width = vrt_width + 2 * tile_edge_padding out_window = windows.Window( col_off=tile_edge_padding, row_off=tile_edge_padding, width=orig_vrt_width, height=orig_vrt_height, ) vrt_params.update( dict(transform=vrt_transform, width=vrt_width, height=vrt_height)) indexes = indexes if indexes is not None else src_dst.indexes out_shape = (len(indexes), tilesize, tilesize) nodata = nodata if nodata is not None else src_dst.nodata if nodata is not None: vrt_params.update( dict(nodata=nodata, add_alpha=False, src_nodata=nodata)) if has_alpha_band(src_dst): vrt_params.update(dict(add_alpha=False)) vrt_params.update(warp_vrt_option) with WarpedVRT(src_dst, **vrt_params) as vrt: data = vrt.read( out_shape=out_shape, indexes=indexes, window=out_window, resampling=Resampling[resampling_method], ) mask = vrt.dataset_mask(out_shape=(tilesize, tilesize), window=out_window) return data, mask
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 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, lats, 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, 'latitude': lats, '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 test_geovector_has_given_crs(): crs = CRS({'init': 'epsg:4326'}) gv = GeoVector(None, crs) assert gv.crs == crs
import numpy as np from PIL import Image from .cog import COGReader, ReaderMixin try: import morecantile from morecantile import TileMatrixSet from rasterio.crs import CRS from rasterio.transform import from_bounds from rasterio.warp import reproject, transform_bounds, transform as transform_coords from rio_tiler.mercator import zoom_for_pixelsize DEFAULT_TMS = morecantile.tms.get("WebMercatorQuad") WGS84 = CRS.from_epsg(4326) except ImportError: CRS = None DEFAULT_TMS = None TileMatrixSet = None WGS84 = None @dataclass class COGInfo: min_zoom: int max_zoom: int bounds: List[float] dtype: str color_interp: str
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 _raster_get_stats( src_dst, indexes=None, nodata=None, overview_level=None, max_size=1024, percentiles=(2, 98), dst_crs=CRS({"init": "EPSG:4326"}), histogram_bins=10, histogram_range=None, resampling_method="bilinear", warp_vrt_option={}, ): """ Retrieve dataset statistics. Attributes ---------- src_dst : rasterio.io.DatasetReader rasterio.io.DatasetReader object indexes : tuple, list, int, optional Dataset band indexes. nodata, int, optional Custom nodata value if not preset in dataset. overview_level : int, optional Overview (decimation) level to fetch. max_size: int, optional Maximum size of dataset to retrieve (will be used to calculate the overview level to fetch). percentiles : tulple, optional Percentile or sequence of percentiles to compute, which must be between 0 and 100 inclusive (default: (2, 98)). dst_crs: CRS or dict Target coordinate reference system (default: EPSG:4326). histogram_bins: int, optional Defines the number of equal-width histogram bins (default: 10). histogram_range: tuple or list, optional The lower and upper range of the bins. If not provided, range is simply the min and max of the array. resampling_method : str, optional (default: "bilinear") Resampling algorithm. warp_vrt_option: dict, optional (default: {}) These will be passed to the rasterio.warp.WarpedVRT class. Returns ------- out : dict bounds, mercator zoom range, band descriptions and band statistics: (percentiles), min, max, stdev, histogram e.g. { 'bounds': { 'value': (145.72265625, 14.853515625, 145.810546875, 14.94140625), 'crs': '+init=EPSG:4326' }, 'minzoom': 8, 'maxzoom': 12, 'band_descriptions': [(1, 'red'), (2, 'green'), (3, 'blue'), (4, 'nir')] 'statistics': { 1: { 'pc': [38, 147], 'min': 20, 'max': 180, 'std': 28.123562304138662, 'histogram': [ [1625, 219241, 28344, 15808, 12325, 10687, 8535, 7348, 4656, 1208], [20.0, 36.0, 52.0, 68.0, 84.0, 100.0, 116.0, 132.0, 148.0, 164.0, 180.0] ] } ... 3: {...} 4: {...} } } """ if isinstance(indexes, int): indexes = [indexes] elif isinstance(indexes, tuple): indexes = list(indexes) levels = src_dst.overviews(1) width = src_dst.width height = src_dst.height indexes = indexes if indexes else src_dst.indexes nodata = nodata if nodata is not None else src_dst.nodata bounds = transform_bounds(src_dst.crs, dst_crs, *src_dst.bounds, densify_pts=21) minzoom, maxzoom = get_zooms(src_dst) def _get_descr(ix): """Return band description.""" name = src_dst.descriptions[ix - 1] if not name: name = "band{}".format(ix) return name band_descriptions = [(ix, _get_descr(ix)) for ix in indexes] if len(levels): if overview_level: decim = levels[overview_level] else: # determine which zoom level to read for ii, decim in enumerate(levels): if (max(_div_round_up(width, decim), _div_round_up(height, decim)) < max_size): break else: decim = 1 warnings.warn("Dataset has no overviews, reading the full dataset", NoOverviewWarning) out_shape = ( len(indexes), _div_round_up(height, decim), _div_round_up(width, decim), ) vrt_params = dict(add_alpha=True) if has_alpha_band(src_dst): vrt_params.update(dict(add_alpha=False)) if nodata is not None: vrt_params.update( dict(nodata=nodata, add_alpha=False, src_nodata=nodata)) vrt_params.update(warp_vrt_option) with WarpedVRT(src_dst, **vrt_params) as vrt: arr = vrt.read( out_shape=out_shape, indexes=indexes, resampling=Resampling[resampling_method], masked=True, ) params = {} if histogram_bins: params.update(dict(bins=histogram_bins)) if histogram_range: params.update(dict(range=histogram_range)) stats = { indexes[b]: _stats(arr[b], percentiles=percentiles, **params) for b in range(arr.shape[0]) if vrt.colorinterp[b] != ColorInterp.alpha } return { "bounds": { "value": bounds, "crs": dst_crs.to_string() if isinstance(dst_crs, CRS) else dst_crs, }, "minzoom": minzoom, "maxzoom": maxzoom, "band_descriptions": band_descriptions, "statistics": stats, }
def reproject_geometry( geometry, src_crs=None, dst_crs=None, error_on_clip=False, validity_check=True ): """ Reproject a geometry to target CRS. Also, clips geometry if it lies outside the destination CRS boundary. Supported destination CRSes for clipping: 4326 (WGS84), 3857 (Spherical Mercator) and 3035 (ETRS89 / ETRS-LAEA). Parameters ---------- geometry : ``shapely.geometry`` src_crs : ``rasterio.crs.CRS`` or EPSG code CRS of source data dst_crs : ``rasterio.crs.CRS`` or EPSG code target CRS error_on_clip : bool raises a ``RuntimeError`` if a geometry is outside of CRS bounds (default: False) validity_check : bool checks if reprojected geometry is valid and throws ``TopologicalError`` if invalid (default: True) Returns ------- geometry : ``shapely.geometry`` """ src_crs = _validated_crs(src_crs) dst_crs = _validated_crs(dst_crs) def _repair(geom): if geom.geom_type in ["Polygon", "MultiPolygon"]: return geom.buffer(0) else: return geom def _reproject_geom(geometry, src_crs, dst_crs): if geometry.is_empty or src_crs == dst_crs: return _repair(geometry) out_geom = _repair( to_shape( transform_geom( src_crs.to_dict(), dst_crs.to_dict(), mapping(geometry) ) ) ) if validity_check and (not out_geom.is_valid or out_geom.is_empty): raise TopologicalError("invalid geometry after reprojection") return out_geom # return repaired geometry if no reprojection needed if src_crs == dst_crs: return _repair(geometry) # if geometry potentially has to be clipped, reproject to WGS84 and clip # with CRS bounds elif dst_crs.is_epsg_code and ( dst_crs.get("init") in CRS_BOUNDS) and ( # if known CRS not dst_crs.get("init") == "epsg:4326" # WGS84 does not need clipping ): wgs84_crs = CRS().from_epsg(4326) # get dst_crs boundaries crs_bbox = box(*CRS_BOUNDS[dst_crs.get("init")]) # reproject geometry to WGS84 geometry_4326 = _reproject_geom(geometry, src_crs, wgs84_crs) # raise error if geometry has to be clipped if error_on_clip and not geometry_4326.within(crs_bbox): raise RuntimeError("geometry outside target CRS bounds") # clip geometry dst_crs boundaries and return return _reproject_geom( crs_bbox.intersection(geometry_4326), wgs84_crs, dst_crs ) # return without clipping if destination CRS does not have defined bounds else: return _reproject_geom(geometry, src_crs, dst_crs)
def get_crs(dataset, variable_name): """ Return a PROJ4 projection string for a variable in a dataset. If non-standard 'proj4' attribute is found in attributes of dataset or variable, that is used instead. Otherwise, the projection parameters are extracted from attributes in the grid_mapping variable in the dataset referenced from the data variable's attributes. :param dataset: open netCDF dataset :param variable_name: name of data variable to extract projection :return: PROJ4 projection string or None """ ncatts = get_ncattrs(dataset.variables[variable_name]) dsatts = get_ncattrs(dataset) # If dataset already includes proj4 string, just use it existing_proj4 = dsatts.get(PROJ4_KEY) or ncatts.get(PROJ4_KEY) if existing_proj4: return existing_proj4 # Attempt to construct proj4 string based on CF convention parameters if 'grid_mapping' not in ncatts: logger.debug('grid_mapping attribute not found for variable {0}'.format(variable_name)) return None if ncatts['grid_mapping'] not in dataset.variables: logger.debug('grid_mapping variable {0} not found in dataset'.format(ncatts['grid_mapping'])) return None crs_variable = dataset.variables[ncatts['grid_mapping']] crs_atts = get_ncattrs(crs_variable) cf_crs_name = crs_atts.get('grid_mapping_name') if not (cf_crs_name and cf_crs_name in CF_PROJ4_PARAM_MAP): # Could not determine projection name logger.debug('No supported projection found for {0}'.format(cf_crs_name)) return None param_map = CF_PROJ4_PARAM_MAP[cf_crs_name] proj4_params = {'proj': CF_PROJ4_NAMES[cf_crs_name]} expected_params = set(CF_PROJ4_PARAM_MAP[cf_crs_name].keys()) if expected_params.difference(crs_atts): logger.debug('Missing expected parameters {0}'.format(expected_params.difference(crs_atts))) for param in expected_params.intersection(crs_atts): value = crs_atts[param] if param == 'standard_parallel' and '{' in param_map[param]: # Special case: variable number of standard parallels value = list(value) for index, val in enumerate(value, start=1): proj4_params[param_map[param].format(index)] = val else: proj4_params[param_map[param]] = value for param in set(CF_PROJ4_ELLPSOID_MAP.keys()).intersection(crs_atts): proj4_params[CF_PROJ4_ELLPSOID_MAP[param]] = crs_atts[param] try: return Proj(**CRS(proj4_params).to_dict()).srs except: # Could not create valid projection logger.debug('Could not create valid Proj4 projection from parameters') return 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 = 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)
tundra = 70 artificial = 80 bareland = 90 snow = 100 no_data = 255 SETTINGS = { 'headers': { 'headers': { 'User-Agent': "Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11" } }, 'wgs84': CRS.from_epsg(4326), 'data': cache_directories(get_data_dir()), 'canopy_densities': list(range(0, 100, 1)), # old setting in 5 increment 'cover_classes': [GL30Classes.forest.value], 'classify_years': list(range(1, 11)), 'canopy_density': 10, 'clustering': [GL30Classes.forest.value], 'reject': [ GL30Classes.zero.value, GL30Classes.forest.value, GL30Classes.no_data.value ], 'buffer':
def reproject_geometry(geometry, src_crs=None, dst_crs=None, error_on_clip=False, validity_check=True, antimeridian_cutting=False): """ Reproject a geometry to target CRS. Also, clips geometry if it lies outside the destination CRS boundary. Supported destination CRSes for clipping: 4326 (WGS84), 3857 (Spherical Mercator) and 3035 (ETRS89 / ETRS-LAEA). Parameters ---------- geometry : ``shapely.geometry`` src_crs : ``rasterio.crs.CRS`` or EPSG code CRS of source data dst_crs : ``rasterio.crs.CRS`` or EPSG code target CRS error_on_clip : bool raises a ``RuntimeError`` if a geometry is outside of CRS bounds (default: False) validity_check : bool checks if reprojected geometry is valid and throws ``TopologicalError`` if invalid (default: True) antimeridian_cutting : bool cut geometry at Antimeridian; can result in a multipart output geometry Returns ------- geometry : ``shapely.geometry`` """ src_crs = validate_crs(src_crs) dst_crs = validate_crs(dst_crs) def _reproject_geom(geometry, src_crs, dst_crs): if geometry.is_empty: return geometry else: out_geom = to_shape( transform_geom(src_crs.to_dict(), dst_crs.to_dict(), mapping(geometry), antimeridian_cutting=antimeridian_cutting)) return _repair(out_geom) if validity_check else out_geom # return repaired geometry if no reprojection needed if src_crs == dst_crs or geometry.is_empty: return _repair(geometry) # geometry needs to be clipped to its CRS bounds elif (dst_crs.is_epsg_code and # just in case for an CRS with EPSG code dst_crs.get("init") in CRS_BOUNDS and # if CRS has defined bounds dst_crs.get("init") != "epsg:4326" # and is not WGS84 (does not need clipping) ): wgs84_crs = CRS().from_epsg(4326) # get dst_crs boundaries crs_bbox = box(*CRS_BOUNDS[dst_crs.get("init")]) # reproject geometry to WGS84 geometry_4326 = _reproject_geom(geometry, src_crs, wgs84_crs) # raise error if geometry has to be clipped if error_on_clip and not geometry_4326.within(crs_bbox): raise RuntimeError("geometry outside target CRS bounds") # clip geometry dst_crs boundaries and return return _reproject_geom(crs_bbox.intersection(geometry_4326), wgs84_crs, dst_crs) # return without clipping if destination CRS does not have defined bounds else: return _reproject_geom(geometry, src_crs, dst_crs)
def _describe_file(filepath): """ Helper function to describe a geospatial data First checks if a sidecar mcf file is available, if so uses that if not, script will parse the file to retrieve some info from the file :param filepath: path to file :returns: `dict` of GeoJSON item """ content = {'bbox': None, 'geometry': None, 'properties': {}} mcf_file = '{}.yml'.format(os.path.splitext(filepath)[0]) if os.path.isfile(mcf_file): try: from pygeometa.core import read_mcf, MCFReadError from pygeometa.schemas.stac import STACItemOutputSchema md = read_mcf(mcf_file) stacjson = STACItemOutputSchema.write(STACItemOutputSchema, md) stacdata = loads(stacjson) for k, v in stacdata.items(): content[k] = v except ImportError: LOGGER.debug('pygeometa not found') except MCFReadError as err: LOGGER.warning('MCF error: {}'.format(err)) else: LOGGER.debug('No mcf found at: {}'.format(mcf_file)) if content['geometry'] is None and content['bbox'] is None: try: import rasterio from rasterio.crs import CRS from rasterio.warp import transform_bounds except ImportError as err: LOGGER.warning('rasterio not found') LOGGER.warning(err) return content try: import fiona except ImportError as err: LOGGER.warning('fiona not found') LOGGER.warning(err) return content try: # raster LOGGER.debug('Testing raster data detection') d = rasterio.open(filepath) content['bbox'] = [ d.bounds.left, d.bounds.bottom, d.bounds.right, d.bounds.top ] content['geometry'] = { 'type': 'Polygon', 'coordinates': [[[d.bounds.left, d.bounds.bottom], [d.bounds.left, d.bounds.top], [d.bounds.right, d.bounds.top], [d.bounds.right, d.bounds.bottom], [d.bounds.left, d.bounds.bottom]]] } for k, v in d.tags(1).items(): content['properties'][k] = v except rasterio.errors.RasterioIOError: LOGGER.debug('Testing vector data detection') d = fiona.open(filepath) scrs = CRS(d.crs) if scrs.to_epsg() is not None and scrs.to_epsg() != 4326: tcrs = CRS.from_epsg(4326) bnds = transform_bounds(scrs, tcrs, d.bounds[0], d.bounds[1], d.bounds[2], d.bounds[3]) content['properties']['projection'] = scrs.to_epsg() else: bnds = d.bounds if d.schema['geometry'] not in [None, 'None']: content['bbox'] = [bnds[0], bnds[1], bnds[2], bnds[3]] content['geometry'] = { 'type': 'Polygon', 'coordinates': [[[bnds[0], bnds[1]], [bnds[0], bnds[3]], [bnds[2], bnds[3]], [bnds[2], bnds[1]], [bnds[0], bnds[1]]]] } for k, v in d.schema['properties'].items(): content['properties'][k] = v if d.driver == 'ESRI Shapefile': id_ = os.path.splitext(os.path.basename(filepath))[0] content['assets'] = {} for suffix in ['shx', 'dbf', 'prj', 'shp.xml']: content['assets'][suffix] = { 'href': './{}.{}'.format(id_, suffix) } return content
def test_crs_constructor_crs_obj(): """Can create a CRS from a CRS obj""" crs = CRS(CRS(init='epsg:3857')) assert crs['init'] == 'epsg:3857' assert 'PROJCS["WGS 84 / Pseudo-Mercator"' in crs.wkt
def test_to_authority__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_authority() is None
def test_from_user_input_custom_crs_class(): """Support comparison to foreign objects that provide to_wkt()""" assert CRS.from_user_input(CustomCRS()) == CRS.from_epsg(4326)
def test_esri_auth__to_authority(): assert CRS.from_user_input('ESRI:54009').to_authority() == ('ESRI', '54009')
"""Useful constants. """ from rasterio.crs import CRS WGS84_SRID = 4326 #: WGS84 CRS. WGS84_CRS = CRS.from_epsg(WGS84_SRID) WEB_MERCATOR_SRID = 3857 #: Web Mercator CRS. WEB_MERCATOR_CRS = CRS.from_epsg(WEB_MERCATOR_SRID) # Best widely used, equal area projection according to # http://icaci.org/documents/ICC_proceedings/ICC2001/icc2001/file/f24014.doc # (found on https://en.wikipedia.org/wiki/Winkel_tripel_projection#Comparison_with_other_projections) #: Eckert IV CRS. EQUAL_AREA_CRS = CRS({'proj': 'eck4'}) DEFAULT_SRID = WGS84_SRID #: Default CRS, set to :py:data:`~telluric.constants.WGS84_CRS`. DEFAULT_CRS = WGS84_CRS def _MERCATOR_RESOLUTION_MAPPING(zoom_level): return (2 * 20037508.342789244) / (256 * pow(2, zoom_level)) MERCATOR_RESOLUTION_MAPPING = dict( (i, _MERCATOR_RESOLUTION_MAPPING(i)) for i in range(19))
def test_esri_auth__from_string(): assert CRS.from_string('ESRI:54009').to_string() == 'ESRI:54009'
"boundless": mercantile.Tile(x=540, y=497, z=10), }, "dateline": { "full": mercantile.Tile(x=510, y=169, z=10), "masked": mercantile.Tile(x=510, y=168, z=10), "boundless": mercantile.Tile(x=509, y=171, z=10), }, } # LC08_L1TP_212004_20190816_20190902_01_T1 north = { "bounds": BoundingBox(left=433192.5, bottom=8534992.5, right=707407.5, top=8809207.5), "crs": CRS.from_epsg(32633), } # LC08_L1GT_054115_20200120_20200120_01_RT south = { "bounds": BoundingBox(left=123892.5, bottom=-1521007.5, right=387607.5, top=-1258492.5), "crs": CRS.from_epsg(3031), } # LC08_L1TP_085024_20170816_20170825_01_T1 dateline = {
def test_crs_constructor_keywords(): """Can create a CRS from keyword args, ignoring unknowns""" crs = CRS(init='epsg:3857', foo='bar') assert crs['init'] == 'epsg:3857' assert 'PROJCS["WGS 84 / Pseudo-Mercator"' in crs.wkt
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, warper_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, 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: left, bottom, right, top = src.bounds out_kwargs = src.profile out_kwargs.pop("driver", None) if driver: 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 bounds = src_bounds or src.bounds try: xmin, ymin, xmax, ymax = transform_bounds( src.crs, dst_crs, *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, **warper_options) 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 l, b, r, t = src_bounds or (left, bottom, right, top) 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(round((xmax - xmin) / res[0])), 1) dst_height = max(int(round((ymax - ymin) / res[1])), 1) elif res: # Same projection, different resolution. dst_crs = src.crs dst_transform = Affine(res[0], 0, left, 0, -res[1], top) dst_width = max(int(round((right - left) / res[0])), 1) dst_height = max(int(round((top - bottom) / res[1])), 1) else: dst_crs = src.crs inv_transform = ~src.transform eps = sys.float_info.epsilon c1, r1 = inv_transform * (left + eps, top + eps) c2, r2 = inv_transform * (right + eps, top + eps) c3, r3 = inv_transform * (right + eps, bottom + eps) c4, r4 = inv_transform * (left + eps, bottom + eps) col1 = min(c1, c2, c3, c4) col2 = max(c1, c2, c3, c4) row1 = min(r1, r2, r3, r4) row2 = max(r1, r2, r3, r4) col1 = floor(col1) col2 = ceil(col2) row1 = floor(row1) row2 = ceil(row2) px = (right - left) / (col2 - col1) py = (top - bottom) / (row2 - row1) res = max(px, py) dst_width = max(int(round((right - left) / res)), 1) dst_height = max(int(round((top - bottom) / res)), 1) dst_transform = Affine.translation(left, top) * Affine.scale( res, -res) 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 < int( out_kwargs["blockxsize"]): del out_kwargs["blockxsize"] logger.warning( "Blockxsize removed from creation options to accomodate small output width" ) if "blockysize" in out_kwargs and dst_height < int( out_kwargs["blockysize"]): del out_kwargs["blockysize"] logger.warning( "Blockxsize removed from creation options to accomodate small output height" ) 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, **warper_options)
"""rio-tiler constant values.""" import multiprocessing import os from typing import Sequence, Tuple, Union import morecantile from rasterio.crs import CRS NumType = Union[float, int] BBox = Tuple[float, float, float, float] ColorTuple = Tuple[int, int, int, int] NoData = Union[float, int, str] Indexes = Union[Sequence[int], int] MAX_THREADS = int( os.environ.get("MAX_THREADS", multiprocessing.cpu_count() * 5)) WEB_MERCATOR_CRS = CRS.from_epsg(3857) WGS84_CRS = CRS.from_epsg(4326) WEB_MERCATOR_TMS = morecantile.tms.get("WebMercatorQuad")
def wms_vrt(wms_file, bounds=None, resolution=None): """Make a VRT XML document from a wms file. Parameters ---------- wms_file : str The source wms file bounds : GeoVector, optional The requested footprint of the generated VRT resolution : float, optional The requested resolution of the generated VRT Returns ------- bytes An ascii-encoded string (an ElementTree detail) """ from telluric import rasterization, constants wms_tree = ET.parse(wms_file) service = wms_tree.find(".//Service") if service is not None: service_name = service.attrib.get("name") else: raise ValueError("Service tag is required") # definition is based on https://www.gdal.org/frmt_wms.html if service_name == "VirtualEarth": left = find_and_convert_to_type(float, wms_tree, ".//DataWindow/UpperLeftX", -20037508.34) up = find_and_convert_to_type(float, wms_tree, ".//DataWindow/UpperLeftY", 20037508.34) right = find_and_convert_to_type(float, wms_tree, ".//DataWindow/LowerRightX", 20037508.34) bottom = find_and_convert_to_type(float, wms_tree, ".//DataWindow/LowerRightY", -20037508.34) upper_bound_zoom = find_and_convert_to_type(int, wms_tree, ".//DataWindow/TileLevel", 19) projection = find_and_convert_to_type(str, wms_tree, ".//Projection", "EPSG: 3857") projection = CRS(init=projection) blockx = find_and_convert_to_type(str, wms_tree, ".//BlockSizeX", 256) blocky = find_and_convert_to_type(str, wms_tree, ".//BlockSizeY", 256) else: left = find_and_convert_to_type(float, wms_tree, ".//DataWindow/UpperLeftX", -180.0) up = find_and_convert_to_type(float, wms_tree, ".//DataWindow/UpperLeftY", 90.0) right = find_and_convert_to_type(float, wms_tree, ".//DataWindow/LowerRightX", 180.0) bottom = find_and_convert_to_type(float, wms_tree, ".//DataWindow/LowerRightY", -90.0) upper_bound_zoom = find_and_convert_to_type(int, wms_tree, ".//DataWindow/TileLevel", 0) projection = find_and_convert_to_type(str, wms_tree, ".//Projection", "EPSG:4326") blockx = find_and_convert_to_type(str, wms_tree, ".//BlockSizeX", 1024) blocky = find_and_convert_to_type(str, wms_tree, ".//BlockSizeY", 1024) projection = CRS(init=projection) bands_count = find_and_convert_to_type(int, wms_tree, ".//BandsCount", 3) data_type = find_and_convert_to_type(str, wms_tree, ".//DataType", "Byte") src_bounds = (left, bottom, right, up) bounds = bounds.get_bounds(crs=projection) or src_bounds src_resolution = constants.MERCATOR_RESOLUTION_MAPPING[upper_bound_zoom] resolution = resolution or constants.MERCATOR_RESOLUTION_MAPPING[ upper_bound_zoom] dst_width, dst_height, transform = rasterization.raster_data( bounds=bounds, dest_resolution=resolution) orig_width, orig_height, orig_transform = rasterization.raster_data( bounds=src_bounds, dest_resolution=src_resolution) src_window = from_bounds(*bounds, transform=orig_transform) vrt = BaseVRT(dst_width, dst_height, projection, transform) vrt.add_metadata(domain="IMAGE_STRUCTURE", items={"INTERLEAVE": "PIXEL"}) if bands_count != 3: raise ValueError("We support currently on 3 bands WMS") for idx, band in enumerate(["RED", "GREEN", "BLUE"]): bidx = idx + 1 band_element = vrt.add_band(data_type, bidx, band) dst_window = Window(0, 0, dst_width, dst_height) vrt.add_band_simplesource(band_element, bidx, data_type, False, os.path.abspath(wms_file), orig_width, orig_height, blockx, blocky, src_window, dst_window) return vrt