def test_geobox_simple(): from affine import Affine t = geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), epsg4326) expect_lon = np.asarray([ 151.000125, 151.000375, 151.000625, 151.000875, 151.001125, 151.001375, 151.001625, 151.001875, 151.002125, 151.002375 ]) expect_lat = np.asarray([ -29.000125, -29.000375, -29.000625, -29.000875, -29.001125, -29.001375, -29.001625, -29.001875, -29.002125, -29.002375 ]) expect_resolution = np.asarray([-0.00025, 0.00025]) assert t.coordinates['latitude'].values.shape == (4000, ) assert t.coordinates['longitude'].values.shape == (4000, ) np.testing.assert_almost_equal(t.resolution, expect_resolution) np.testing.assert_almost_equal(t.coords['latitude'].values[:10], expect_lat) np.testing.assert_almost_equal(t.coords['longitude'].values[:10], expect_lon) assert (t == "some random thing") is False # ensure GeoBox accepts string CRS assert isinstance( geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), 'epsg:4326').crs, CRS)
def test_geobox_simple(): t = geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), epsg4326) expect_lon = np.asarray([151.000125, 151.000375, 151.000625, 151.000875, 151.001125, 151.001375, 151.001625, 151.001875, 151.002125, 151.002375]) expect_lat = np.asarray([-29.000125, -29.000375, -29.000625, -29.000875, -29.001125, -29.001375, -29.001625, -29.001875, -29.002125, -29.002375]) expect_resolution = np.asarray([-0.00025, 0.00025]) assert t.coordinates['latitude'].values.shape == (4000,) assert t.coordinates['longitude'].values.shape == (4000,) np.testing.assert_almost_equal(t.resolution, expect_resolution) np.testing.assert_almost_equal(t.coords['latitude'].values[:10], expect_lat) np.testing.assert_almost_equal(t.coords['longitude'].values[:10], expect_lon) assert (t == "some random thing") is False # ensure GeoBox accepts string CRS assert isinstance(geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), 'epsg:4326').crs, CRS) # Check GeoBox class is hashable t_copy = GeoBox(t.width, t.height, t.transform, t.crs) t_other = GeoBox(t.width+1, t.height, t.transform, t.crs) assert t_copy is not t assert t == t_copy assert len(set([t, t, t_copy])) == 1 assert len(set([t, t_copy, t_other])) == 2
def test_geobox(): points_list = [ [(148.2697, -35.20111), (149.31254, -35.20111), (149.31254, -36.331431), (148.2697, -36.331431)], [(148.2697, 35.20111), (149.31254, 35.20111), (149.31254, 36.331431), (148.2697, 36.331431)], [(-148.2697, 35.20111), (-149.31254, 35.20111), (-149.31254, 36.331431), (-148.2697, 36.331431)], [(-148.2697, -35.20111), (-149.31254, -35.20111), (-149.31254, -36.331431), (-148.2697, -36.331431), (148.2697, -35.20111)], ] for points in points_list: polygon = geometry.polygon(points, crs=epsg3577) resolution = (-25, 25) geobox = geometry.GeoBox.from_geopolygon(polygon, resolution) assert abs(resolution[0]) > abs(geobox.extent.boundingbox.left - polygon.boundingbox.left) assert abs(resolution[0]) > abs(geobox.extent.boundingbox.right - polygon.boundingbox.right) assert abs(resolution[1]) > abs(geobox.extent.boundingbox.top - polygon.boundingbox.top) assert abs(resolution[1]) > abs(geobox.extent.boundingbox.bottom - polygon.boundingbox.bottom) A = mkA(0, scale=(10, -10), translation=(-48800, -2983006)) w, h = 512, 256 gbox = geometry.GeoBox(w, h, A, epsg3577) assert gbox.shape == (h, w) assert gbox.transform == A assert gbox.extent.crs == gbox.crs assert gbox.geographic_extent.crs == epsg4326 assert gbox.extent.boundingbox.height == h*10.0 assert gbox.extent.boundingbox.width == w*10.0 assert isinstance(str(gbox), str) assert 'EPSG:3577' in repr(gbox) assert geometry.GeoBox(1, 1, mkA(0), epsg4326).geographic_extent.crs == epsg4326 g2 = gbox[:-10, :-20] assert g2.shape == (gbox.height - 10, gbox.width - 20) # step of 1 is ok g2 = gbox[::1, ::1] assert g2.shape == gbox.shape assert gbox[0].shape == (1, gbox.width) assert gbox[:3].shape == (3, gbox.width) with pytest.raises(NotImplementedError): gbox[::2, :] # too many slices with pytest.raises(ValueError): gbox[:1, :1, :] assert gbox.buffered(10, 0).shape == (gbox.height + 2*1, gbox.width) assert gbox.buffered(30, 20).shape == (gbox.height + 2*3, gbox.width + 2*2) assert (gbox | gbox) == gbox assert (gbox & gbox) == gbox
def simple_geobox(): from datacube_ows.wms_utils import _get_geobox from affine import Affine from datacube.utils import geometry aff = Affine.translation(145.0, -35.0) * Affine.scale(1.0 / 256, 2.0 / 256) return geometry.GeoBox(256, 256, aff, 'EPSG:4326')
def _xarray_geobox(obj): transform, sdims = _xarray_affine_impl(obj) if sdims is None: return None crs = None try: crs = _get_crs_from_coord(obj) except ValueError: pass if crs is None: crs = _get_crs_from_attrs(obj, sdims) if crs is None: return None try: crs = _norm_crs(crs) except (ValueError, geometry.CRSError): warnings.warn(f"Encountered malformed CRS: {crs}") return None h, w = (obj.coords[dim].size for dim in sdims) return geometry.GeoBox(w, h, transform, crs)
def test_write_dataset_to_netcdf(tmpnetcdf_filename): affine = Affine.scale(0.1, 0.1) * Affine.translation(20, 30) geobox = geometry.GeoBox(100, 100, affine, geometry.CRS(GEO_PROJ)) dataset = xarray.Dataset(attrs={'extent': geobox.extent, 'crs': geobox.crs}) for name, coord in geobox.coordinates.items(): dataset[name] = (name, coord.values, {'units': coord.units, 'crs': geobox.crs}) dataset['B10'] = (geobox.dimensions, np.arange(10000, dtype='int16').reshape(geobox.shape), {'nodata': 0, 'units': '1', 'crs': geobox.crs}) write_dataset_to_netcdf(dataset, tmpnetcdf_filename, global_attributes={'foo': 'bar'}, variable_params={'B10': {'attrs': {'abc': 'xyz'}}}) with netCDF4.Dataset(tmpnetcdf_filename) as nco: nco.set_auto_mask(False) assert 'B10' in nco.variables var = nco.variables['B10'] assert (var[:] == dataset['B10'].values).all() assert 'foo' in nco.ncattrs() assert nco.getncattr('foo') == 'bar' assert 'abc' in var.ncattrs() assert var.getncattr('abc') == 'xyz'
def test_geobox_simple(): from affine import Affine t = geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), geometry.CRS('EPSG:4326')) expect_lon = np.asarray([ 151.000125, 151.000375, 151.000625, 151.000875, 151.001125, 151.001375, 151.001625, 151.001875, 151.002125, 151.002375 ]) expect_lat = np.asarray([ -29.000125, -29.000375, -29.000625, -29.000875, -29.001125, -29.001375, -29.001625, -29.001875, -29.002125, -29.002375 ]) expect_resolution = np.asarray([-0.00025, 0.00025]) assert t.coordinates['latitude'].values.shape == (4000, ) assert t.coordinates['longitude'].values.shape == (4000, ) np.testing.assert_almost_equal(t.resolution, expect_resolution) np.testing.assert_almost_equal(t.coords['latitude'].values[:10], expect_lat) np.testing.assert_almost_equal(t.coords['longitude'].values[:10], expect_lon)
def odc_style_xr_dataset(): """An xarray.Dataset with ODC style coordinates and CRS, and no time dimension. Contains an EPSG:4326, single variable 'B10' of 100x100 int16 pixels.""" affine = Affine.scale(0.1, 0.1) * Affine.translation(20, 30) geobox = geometry.GeoBox(100, 100, affine, geometry.CRS(GEO_PROJ)) dataset = xarray.Dataset(attrs={ 'extent': geobox.extent, 'crs': geobox.crs }) for name, coord in geobox.coordinates.items(): dataset[name] = (name, coord.values, { 'units': coord.units, 'crs': geobox.crs }) dataset['B10'] = (geobox.dimensions, np.arange(10000, dtype='int16').reshape(geobox.shape), { 'nodata': 0, 'units': '1', 'crs': geobox.crs }) # To include a time dimension: # dataset['B10'] = (('time', 'latitude', 'longitude'), np.arange(10000, dtype='int16').reshape((1, 100, 100)), # {'nodata': 0, 'units': '1', 'crs': geobox.crs}) return dataset
def check_data_with_api(index, time_slices): """Chek retrieved data for specific values. We scale down by 100 and check for predefined values in the corners. """ from datacube import Datacube dc = Datacube(index=index) # Make the retrieved data 100 less granular shape_x = int(GEOTIFF['shape']['x'] / 100.0) shape_y = int(GEOTIFF['shape']['y'] / 100.0) pixel_x = int(GEOTIFF['pixel_size']['x'] * 100) pixel_y = int(GEOTIFF['pixel_size']['y'] * 100) input_type_name = 'ls5_nbar_albers' input_type = dc.index.products.get_by_name(input_type_name) geobox = geometry.GeoBox( shape_x + 1, shape_y + 1, Affine(pixel_x, 0.0, GEOTIFF['ul']['x'], 0.0, pixel_y, GEOTIFF['ul']['y']), geometry.CRS(GEOTIFF['crs'])) observations = dc.find_datasets(product='ls5_nbar_albers', geopolygon=geobox.extent) group_by = query_group_by('time') sources = dc.group_datasets(observations, group_by) data = dc.load_data(sources, geobox, input_type.measurements.values()) assert hashlib.md5( data.green.data).hexdigest() == '7f5ace486e88d33edf3512e8de6b6996' assert hashlib.md5( data.blue.data).hexdigest() == 'b58204f1e10dd678b292df188c242c7e' for time_slice in range(time_slices): assert data.blue.values[time_slice][-1, -1] == -999
def load_with_dc(dc, product_def, product_id, measurement, time=None, datasets=None, dask_chunks=None): """Load data with dc, with settable params. If `datasets` is specified, the no `product` is used in the load command. `dask_chunks` get passed as-is. """ params = SimpleNamespace( measurements=[measurement], like=geometry.GeoBox( *product_id.size, product_id.affine, GEDI_PRODUCT.crs, ), dask_chunks=dask_chunks, ) if time: params.time = time if product_def.wavelengths: params.z = product_def.wavelengths if datasets: params.datasets = datasets else: params.product = product_def.name.format(measurement=measurement) _LOG.info(f"DC Loading {params}") data = dc.load(**params.__dict__) _LOG.info(f"DC Loaded\n{data}\n{'-'*80}") return data
def _xarray_geobox(obj): crs = _norm_crs(_get_crs(obj)) if crs is None: return None dims = crs.dimensions return geometry.GeoBox(obj[dims[1]].size, obj[dims[0]].size, obj.affine, crs)
def _get_geobox(args, crs): width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = map(float, args['bbox'].split(',')) # miny-maxy for negative scale factor and maxy in the translation, includes inversion of Y axis. affine = Affine.translation(minx, maxy) * Affine.scale((maxx - minx) / width, (miny - maxy) / height) return geometry.GeoBox(width, height, affine, crs)
def odc_style_xr_dataset(): """An xarray.Dataset with ODC style coordinates and CRS, and no time dimension. Contains an EPSG:4326, single variable 'B10' of 100x100 int16 pixels.""" affine = Affine.scale(0.1, 0.1) * Affine.translation(20, 30) geobox = geometry.GeoBox(100, 100, affine, geometry.CRS(GEO_PROJ)) return Datacube.create_storage({}, geobox, [Measurement(name='B10', dtype='int16', nodata=0, units='1')])
def create_geobox( crs, minx, miny, maxx, maxy, width, height, ): affine = Affine.translation(minx, maxy) * Affine.scale((maxx - minx) / width, (miny - maxy) / height) return geometry.GeoBox(width, height, affine, crs)
def _get_geobox(args): width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = map(float, args['bbox'].split(',')) crs = geometry.CRS(args['srs']) affine = Affine.translation(minx, miny) * Affine.scale( (maxx - minx) / width, (maxy - miny) / height) return geometry.GeoBox(width, height, affine, crs)
def tile_geobox(self, tile_index: Tuple[int, int]) -> geometry.GeoBox: """ Tile geobox. :param (int,int) tile_index: """ res_y, res_x = self.resolution y, x = self.tile_coords(tile_index) h, w = self.tile_resolution geobox = geometry.GeoBox(crs=self.crs, affine=Affine(res_x, 0.0, x, 0.0, res_y, y), width=w, height=h) return geobox
def _get_geobox(args, crs): width = int(args['width']) height = int(args['height']) if service_cfg["published_CRSs"][crs.crs_str].get("vertical_coord_first"): miny, minx, maxy, maxx = map(float, args['bbox'].split(',')) else: minx, miny, maxx, maxy = map(float, args['bbox'].split(',')) # miny-maxy for negative scale factor and maxy in the translation, includes inversion of Y axis. affine = Affine.translation(minx, maxy) * Affine.scale( (maxx - minx) / width, (miny - maxy) / height) return geometry.GeoBox(width, height, affine, crs)
def check_open_with_api(driver_manager, time_slices): from datacube import Datacube dc = Datacube(driver_manager=driver_manager) input_type_name = 'ls5_nbar_albers' input_type = dc.index.products.get_by_name(input_type_name) geobox = geometry.GeoBox(200, 200, Affine(25, 0.0, 638000, 0.0, -25, 6276000), geometry.CRS('EPSG:28355')) observations = dc.find_datasets(product='ls5_nbar_albers', geopolygon=geobox.extent) group_by = query_group_by('time') sources = dc.group_datasets(observations, group_by) data = dc.load_data(sources, geobox, input_type.measurements.values(), driver_manager=driver_manager) assert data.blue.shape == (time_slices, 200, 200)
def check_open_with_api(index): from datacube import Datacube dc = Datacube(index=index) input_type_name = 'ls5_nbar_albers' input_type = dc.index.products.get_by_name(input_type_name) geobox = geometry.GeoBox(200, 200, Affine(25, 0.0, 1500000, 0.0, -25, -3900000), geometry.CRS('EPSG:3577')) observations = dc.find_datasets(product='ls5_nbar_albers', geopolygon=geobox.extent) group_by = query_group_by('time') sources = dc.group_datasets(observations, group_by) data = dc.load_data(sources, geobox, input_type.measurements.values()) assert data.blue.shape == (1, 200, 200)
def test_pix_transform(): pt = tuple([ int(x / 10) * 10 for x in geometry.point(145, -35, epsg4326).to_crs(epsg3577).coords[0] ]) A = mkA(scale=(20, -20), translation=pt) src = geometry.GeoBox(1024, 512, A, epsg3577) dst = geometry.GeoBox.from_geopolygon(src.geographic_extent, (0.0001, -0.0001)) tr = native_pix_transform(src, dst) pts_src = [(0, 0), (10, 20), (300, 200)] pts_dst = tr(pts_src) pts_src_ = tr.back(pts_dst) np.testing.assert_almost_equal(pts_src, pts_src_) assert tr.linear is None # check identity transform tr = native_pix_transform(src, src) pts_src = [(0, 0), (10, 20), (300, 200)] pts_dst = tr(pts_src) pts_src_ = tr.back(pts_dst) np.testing.assert_almost_equal(pts_src, pts_src_) np.testing.assert_almost_equal(pts_src, pts_dst) assert tr.linear is not None assert tr.back.linear is not None assert tr.back.back is tr # check scale only change tr = native_pix_transform(src, scaled_down_geobox(src, 2)) pts_dst = tr(pts_src) pts_src_ = tr.back(pts_dst) assert tr.linear is not None assert tr.back.linear is not None assert tr.back.back is tr np.testing.assert_almost_equal(pts_dst, [(x / 2, y / 2) for (x, y) in pts_src]) np.testing.assert_almost_equal(pts_src, pts_src_)
def check_open_with_api(index, time_slices): with rasterio.Env(): from datacube import Datacube dc = Datacube(index=index) input_type_name = 'ls5_nbar_albers' input_type = dc.index.products.get_by_name(input_type_name) geobox = geometry.GeoBox(200, 200, Affine(25, 0.0, 638000, 0.0, -25, 6276000), geometry.CRS('EPSG:28355')) observations = dc.find_datasets(product='ls5_nbar_albers', geopolygon=geobox.extent) group_by = query_group_by('time') sources = dc.group_datasets(observations, group_by) data = dc.load_data(sources, geobox, input_type.measurements.values()) assert data.blue.shape == (time_slices, 200, 200) chunk_profile = {'time': 1, 'x': 100, 'y': 100} lazy_data = dc.load_data(sources, geobox, input_type.measurements.values(), dask_chunks=chunk_profile) assert lazy_data.blue.shape == (time_slices, 200, 200) assert (lazy_data.blue.load() == data.blue).all()
def _get_geobox(args, src_crs, dst_crs=None): width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = _get_geobox_xy(args, src_crs) if minx == maxx or miny == maxy: raise WMSException("Bounding box must enclose a non-zero area") if dst_crs is not None: minx, miny, maxx, maxy = _bounding_pts( minx, miny, maxx, maxy, width, height, src_crs, dst_crs=dst_crs ) out_crs = src_crs if dst_crs is None else dst_crs affine = Affine.translation(minx, maxy) * Affine.scale((maxx - minx) / width, (miny - maxy) / height) return geometry.GeoBox(width, height, affine, out_crs)
def test_geobox_simple(): from affine import Affine t = geometry.GeoBox(4000, 4000, Affine(0.00025, 0.0, 151.0, 0.0, -0.00025, -29.0), geometry.CRS('EPSG:4326')) expect_lon = np.asarray([151.000125, 151.000375, 151.000625, 151.000875, 151.001125, 151.001375, 151.001625, 151.001875, 151.002125, 151.002375]) expect_lat = np.asarray([-29.000125, -29.000375, -29.000625, -29.000875, -29.001125, -29.001375, -29.001625, -29.001875, -29.002125, -29.002375]) expect_resolution = np.asarray([-0.00025, 0.00025]) assert (np.abs(np.r_[t.resolution] - expect_resolution) < 1e-6).all() assert t.coordinates['latitude'].values.shape == (4000,) assert t.coordinates['longitude'].values.shape == (4000,) assert (np.abs(t.coords['latitude'].values[:10] - expect_lat) < 1e-6).all() assert (np.abs(t.coords['longitude'].values[:10] - expect_lon) < 1e-6).all()
def _get_geobox(args, src_crs, dst_crs=None): width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = _get_geobox_xy(args, src_crs) if dst_crs is not None: minx, miny, maxx, maxy = _bounding_pts(minx, miny, maxx, maxy, width, height, src_crs, dst_crs=dst_crs) out_crs = src_crs if dst_crs is None else dst_crs affine = Affine.translation(minx, maxy) * Affine.scale( (maxx - minx) / width, (miny - maxy) / height) return geometry.GeoBox(width, height, affine, out_crs)
def check_data_with_api(index, time_slices): """Chek retrieved data for specific values. We scale down by 100 and check for predefined values in the corners. """ from datacube import Datacube dc = Datacube(index=index) # TODO: this test needs to change, it tests that results are exactly the # same as some time before, but with the current zoom out factor it's # hard to verify that results are as expected even with human # judgement. What it should test is that reading native from the # ingested product gives exactly the same results as reading into the # same GeoBox from the original product. Separate to that there # should be a read test that confirms that what you read from native # product while changing projection is of expected value # Make the retrieved data lower res ss = 100 shape_x = int(GEOTIFF['shape']['x'] / ss) shape_y = int(GEOTIFF['shape']['y'] / ss) pixel_x = int(GEOTIFF['pixel_size']['x'] * ss) pixel_y = int(GEOTIFF['pixel_size']['y'] * ss) input_type_name = 'ls5_nbar_albers' input_type = dc.index.products.get_by_name(input_type_name) geobox = geometry.GeoBox( shape_x + 2, shape_y + 2, Affine(pixel_x, 0.0, GEOTIFF['ul']['x'], 0.0, pixel_y, GEOTIFF['ul']['y']), geometry.CRS(GEOTIFF['crs'])) observations = dc.find_datasets(product='ls5_nbar_albers', geopolygon=geobox.extent) group_by = query_group_by('time') sources = dc.group_datasets(observations, group_by) data = dc.load_data(sources, geobox, input_type.measurements.values()) assert hashlib.md5( data.green.data).hexdigest() == '0f64647bad54db4389fb065b2128025e' assert hashlib.md5( data.blue.data).hexdigest() == '41a7b50dfe5c4c1a1befbc378225beeb' for time_slice in range(time_slices): assert data.blue.values[time_slice][-1, -1] == -999
def geobox_from_rio(xds): """This function retrieves the geobox using rioxarray extension. Parameters ---------- xds: :obj:`xarray.DataArray` or :obj:`xarray.Dataset` The xarray dataset to get the geobox from. Returns ------- :obj:`datacube.utils.geometry.GeoBox` """ width, height = xds.rio.shape try: transform = xds.rio.transform() except AttributeError: transform = xds[xds.rio.vars[0]].rio.transform() return geometry.GeoBox( width=width, height=height, affine=transform, crs=geometry.CRS(crs_to_wkt(xds.rio.crs)), )
def _xarray_geobox(obj): crs = None try: crs = _get_crs_from_coord(obj) except ValueError: pass if crs is None: try: crs = _get_crs_from_attrs(obj) except ValueError: pass if crs is None: return None try: crs = _norm_crs(crs) except ValueError: return None dims = crs.dimensions return geometry.GeoBox(obj[dims[1]].size, obj[dims[0]].size, obj.affine, crs)
def __init__(self, args): self.args = args cfg = get_config() # Argument: Coverage (required) if "coverage" not in args: raise WCS1Exception("No coverage specified", WCS1Exception.MISSING_PARAMETER_VALUE, locator="COVERAGE parameter", valid_keys=list(cfg.product_index)) self.product_name = args["coverage"] self.product = cfg.product_index.get(self.product_name) if not self.product or not self.product.wcs: raise WCS1Exception("Invalid coverage: %s" % self.product_name, WCS1Exception.COVERAGE_NOT_DEFINED, locator="COVERAGE parameter", valid_keys=list(cfg.product_index)) # Argument: FORMAT (required) if "format" not in args: raise WCS1Exception("No FORMAT parameter supplied", WCS1Exception.MISSING_PARAMETER_VALUE, locator="FORMAT parameter", valid_keys=cfg.wcs_formats_by_name) if args["format"] not in cfg.wcs_formats_by_name: raise WCS1Exception("Unsupported format: %s" % args["format"], WCS1Exception.INVALID_PARAMETER_VALUE, locator="FORMAT parameter", valid_keys=cfg.wcs_formats_by_name) self.format = cfg.wcs_formats_by_name[args["format"]] # Argument: (request) CRS (required) if "crs" not in args: raise WCS1Exception("No request CRS specified", WCS1Exception.MISSING_PARAMETER_VALUE, locator="CRS parameter", valid_keys=list(cfg.published_CRSs)) self.request_crsid = args["crs"] if self.request_crsid not in cfg.published_CRSs: raise WCS1Exception("%s is not a supported CRS" % self.request_crsid, WCS1Exception.INVALID_PARAMETER_VALUE, locator="CRS parameter", valid_keys=list(cfg.published_CRSs)) self.request_crs = cfg.crs(self.request_crsid) # Argument: response_crs (optional) if "response_crs" in args: self.response_crsid = args["response_crs"] if self.response_crsid not in cfg.published_CRSs: raise WCS1Exception("%s is not a supported CRS" % self.response_crsid, WCS1Exception.INVALID_PARAMETER_VALUE, locator="RESPONSE_CRS parameter", valid_keys=list(cfg.published_CRSs)) self.response_crs = geometry.CRS(self.response_crsid) self.response_crs = cfg.crs(self.response_crsid) else: self.response_crsid = self.request_crsid self.response_crs = self.request_crs # Arguments: One of BBOX or TIME is required #if "bbox" not in args and "time" not in args: # raise WCS1Exception("At least one of BBOX or TIME parameters must be supplied", # WCS1Exception.MISSING_PARAMETER_VALUE, # locator="BBOX or TIME parameter" # ) # Argument: BBOX (technically not required if TIME supplied, but # it's not clear to me what that would mean.) # For WCS 1.0.0 all bboxes will be specified as minx, miny, maxx, maxy if "bbox" not in args: raise WCS1Exception("No BBOX parameter supplied", WCS1Exception.MISSING_PARAMETER_VALUE, locator="BBOX or TIME parameter") try: self.minx, self.miny, self.maxx, self.maxy = map( float, args['bbox'].split(',')) except: raise WCS1Exception("Invalid BBOX parameter", WCS1Exception.INVALID_PARAMETER_VALUE, locator="BBOX parameter") # Argument: TIME # if self.product.wcs_sole_time: # self.times = [parse(self.product.wcs_sole_time).date()] if "time" not in args: # CEOS treats no supplied time argument as all time. # I'm really not sure what the right thing to do is, but QGIS wants us to do SOMETHING - treat it as "now" self.times = [self.product.ranges["times"][-1]] else: # TODO: the min/max/res format option? # It's a bit underspeced. I'm not sure what the "res" would look like. times = args["time"].split(",") self.times = [] for t in times: if t == "now": continue try: time = parse(t).date() if time not in self.product.ranges["time_set"]: raise WCS1Exception( "Time value '%s' not a valid date for coverage %s" % (t, self.product_name), WCS1Exception.INVALID_PARAMETER_VALUE, locator="TIME parameter", valid_keys=[ d.strftime('%Y-%m-%d') for d in self.product.ranges["time_set"] ]) self.times.append(time) except ValueError: raise WCS1Exception( "Time value '%s' not a valid ISO-8601 date" % t, WCS1Exception.INVALID_PARAMETER_VALUE, locator="TIME parameter", valid_keys=[ d.strftime('%Y-%m-%d') for d in self.product.ranges["time_set"] ]) self.times.sort() if len(self.times) == 0: raise WCS1Exception( "No valid ISO-8601 dates", WCS1Exception.INVALID_PARAMETER_VALUE, locator="TIME parameter", valid_keys=[ d.strftime('%Y-%m-%d') for d in self.product.ranges["time_set"] ]) elif len(self.times) > 1 and not self.format["multi-time"]: raise WCS1Exception( "Cannot select more than one time slice with the %s format" % self.format["name"], WCS1Exception.INVALID_PARAMETER_VALUE, locator="TIME and FORMAT parameters") # Range constraint parameter: MEASUREMENTS # No default is set in the DescribeCoverage, so it is required # But QGIS wants us to work without one, so take default from config if "measurements" in args: bands = args["measurements"] self.bands = [] for b in bands.split(","): if not b: continue try: self.bands.append(self.product.band_idx.band(b)) except ConfigException: raise WCS1Exception( f"Invalid measurement: {b}", WCS1Exception.INVALID_PARAMETER_VALUE, locator="MEASUREMENTS parameter", valid_keys=self.product.band_idx.band_labels()) if not bands: raise WCS1Exception( "No measurements supplied", WCS1Exception.INVALID_PARAMETER_VALUE, locator="MEASUREMENTS parameter", valid_keys=self.product.band_idx.band_labels()) elif "styles" in args and args["styles"]: # Use style bands. # Non-standard protocol extension. # # As we have correlated WCS and WMS service implementations, # we can accept a style from WMS, and return the bands used for it. styles = args["styles"].split(",") if len(styles) != 1: raise WCS1Exception("Multiple style parameters not supported") style = self.product.style_index.get(styles[0]) if style: self.bands = style.needed_bands else: self.bands = self.product.wcs_default_bands else: self.bands = self.product.wcs_default_bands # Argument: EXCEPTIONS (optional - defaults to XML) if "exceptions" in args and args[ "exceptions"] != "application/vnd.ogc.se_xml": raise WCS1Exception( f"Unsupported exception format: {args['exceptions']}", WCS1Exception.INVALID_PARAMETER_VALUE, locator="EXCEPTIONS parameter") # Argument: INTERPOLATION (optional only nearest-neighbour currently supported.) # If 'none' is supported in future, validation of width/height/res will need to change. if "interpolation" in args and args[ "interpolation"] != "nearest neighbor": raise WCS1Exception( f'Unsupported interpolation method: {args["interpolation"]}', WCS1Exception.INVALID_PARAMETER_VALUE, locator="INTERPOLATION parameter") if "width" in args: if "resx" in args or "resy" in args: raise WCS1Exception( "Specify WIDTH/HEIGHT parameters OR RESX/RESY parameters - not both", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY/WIDTH/HEIGHT parameters") if "height" not in args: raise WCS1Exception( "WIDTH parameter supplied without HEIGHT parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="WIDTH/HEIGHT parameters") try: self.height = int(args["height"]) if self.height < 1: raise ValueError() except ValueError: raise WCS1Exception( "HEIGHT parameter must be a positive integer", WCS1Exception.INVALID_PARAMETER_VALUE, locator="HEIGHT parameter") try: self.width = int(args["width"]) if self.width < 1: raise ValueError() except ValueError: raise WCS1Exception( "WIDTH parameter must be a positive integer", WCS1Exception.INVALID_PARAMETER_VALUE, locator="WIDTH parameter") self.resx = (self.maxx - self.minx) / self.width self.resy = (self.maxy - self.miny) / self.height elif "resx" in args: if "height" in args: raise WCS1Exception( "Specify WIDTH/HEIGHT parameters OR RESX/RESY parameters - not both", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY/WIDTH/HEIGHT parameters") if "resy" not in args: raise WCS1Exception( "RESX parameter supplied without RESY parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY parameters") try: self.resx = float(args["resx"]) if self.resx <= 0.0: raise ValueError(0) except ValueError: raise WCS1Exception("RESX parameter must be a positive number", WCS1Exception.INVALID_PARAMETER_VALUE, locator="RESX parameter") try: self.resy = float(args["resy"]) if self.resy <= 0.0: raise ValueError(0) except ValueError: raise WCS1Exception("RESY parameter must be a positive number", WCS1Exception.INVALID_PARAMETER_VALUE, locator="RESY parameter") self.width = (self.maxx - self.minx) / self.resx self.height = (self.maxy - self.miny) / self.resy self.width = int(self.width + 0.5) self.height = int(self.height + 0.5) elif "height" in args: raise WCS1Exception( "HEIGHT parameter supplied without WIDTH parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="WIDTH/HEIGHT parameters") elif "resy" in args: raise WCS1Exception( "RESY parameter supplied without RESX parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY parameters") else: raise WCS1Exception( "You must specify either the WIDTH/HEIGHT parameters or RESX/RESY", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY/WIDTH/HEIGHT parameters") self.extent = geometry.polygon([(self.minx, self.miny), (self.minx, self.maxy), (self.maxx, self.maxy), (self.maxx, self.miny), (self.minx, self.miny)], self.request_crs) xscale = (self.maxx - self.minx) / self.width yscale = (self.miny - self.maxy) / self.height trans_aff = Affine.translation(self.minx, self.maxy) scale_aff = Affine.scale(xscale, yscale) self.affine = trans_aff * scale_aff self.geobox = geometry.GeoBox(self.width, self.height, self.affine, self.request_crs)
def _xarray_geobox(obj): dims = obj.crs.dimensions return geometry.GeoBox(obj[dims[1]].size, obj[dims[0]].size, obj.affine, obj.crs)
def sample_geometry(): gb = geometry.GeoBox(40, 40, Affine(2500, 0.0, 1200000.0, 0.0, -2500, -4300000.0), geometry.CRS('EPSG:3577')) json = gb.extent.json return json