def test_rotation_angle(): assert Affine.identity().rotation_angle == 0.0 assert Affine.scale(2).rotation_angle == 0.0 assert Affine.scale(2, 1).rotation_angle == 0.0 assert Affine.translation(32, -47).rotation_angle == pytest.approx(0.0) assert Affine.rotation(30).rotation_angle == pytest.approx(30) assert Affine.rotation(-150).rotation_angle == pytest.approx(-150)
def test_is_orthonormal(self): assert Affine.identity().is_orthonormal assert Affine.translation(4, -1).is_orthonormal assert Affine.rotation(90).is_orthonormal assert Affine.rotation(-26).is_orthonormal assert not Affine.scale(2.5, 6.1).is_orthonormal assert not Affine.scale(.5, 2).is_orthonormal assert not Affine.shear(4, -1).is_orthonormal
def test_scale_constructor(self): scale = Affine.scale(5) assert isinstance(scale, Affine) assert_equal(tuple(scale), (5,0,0, 0,5,0, 0,0,1)) scale = Affine.scale(-1, 2) assert_equal(tuple(scale), (-1,0,0, 0,2,0, 0,0,1)) assert_equal(tuple(Affine.scale(1)), tuple(Affine.identity()))
def test_determinant(self): assert_equal(Affine.identity().determinant, 1) assert_equal(Affine.scale(2).determinant, 4) assert_equal(Affine.scale(0).determinant, 0) assert_equal(Affine.scale(5, 1).determinant, 5) assert_equal(Affine.scale(-1, 1).determinant, -1) assert_equal(Affine.scale(-1, 0).determinant, 0) assert_almost_equal(Affine.rotation(77).determinant, 1) assert_almost_equal(Affine.translation(32, -47).determinant, 1)
def test_determinant(self): assert Affine.identity().determinant == 1 assert Affine.scale(2).determinant == 4 assert Affine.scale(0).determinant == 0 assert Affine.scale(5, 1).determinant == 5 assert Affine.scale(-1, 1).determinant == -1 assert Affine.scale(-1, 0).determinant == 0 assert Affine.rotation(77).determinant == pytest.approx(1) assert Affine.translation(32, -47).determinant == pytest.approx(1)
def test_eccentricity(): assert Affine.identity().eccentricity == 0.0 assert Affine.scale(2).eccentricity == 0.0 #assert_equal(Affine.scale(0).eccentricity, ?) assert Affine.scale(2, 1).eccentricity == pytest.approx(math.sqrt(3) / 2) assert Affine.scale(2, 3).eccentricity == pytest.approx(math.sqrt(5) / 3) assert Affine.scale(1, 0).eccentricity == 1.0 assert Affine.rotation(77).eccentricity == pytest.approx(0.0) assert Affine.translation(32, -47).eccentricity == pytest.approx(0.0) assert Affine.scale(-1, 1).eccentricity == pytest.approx(0.0)
def test_eccentricity_complex(): assert \ (Affine.scale(2, 3) * Affine.rotation(77)).eccentricity == \ pytest.approx(math.sqrt(5) / 3) assert \ (Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \ pytest.approx(math.sqrt(5) / 3) assert \ (Affine.translation(32, -47) * Affine.rotation(77) * Affine.scale(2, 3)).eccentricity == \ pytest.approx(math.sqrt(5) / 3)
def test_scale_constructor(self): scale = Affine.scale(5) assert isinstance(scale, Affine) assert \ tuple(scale) == \ (5, 0, 0, 0, 5, 0, 0, 0, 1) scale = Affine.scale(-1, 2) assert \ tuple(scale) == \ (-1, 0, 0, 0, 2, 0, 0, 0, 1) assert tuple(Affine.scale(1)) == tuple(Affine.identity())
def zoom_factor(args, crs): # Determine the geographic "zoom factor" for the request. # (Larger zoom factor means deeper zoom. Smaller zoom factor means larger area.) # Extract request bbox and crs width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = _get_geobox_xy(args, crs) # Project to a geographic coordinate system # This is why we can't just use the regular geobox. The scale needs to be # "standardised" in some sense, not dependent on the CRS of the request. geo_crs = geometry.CRS("EPSG:4326") minx, miny, maxx, maxy = _bounding_pts(minx, miny, maxx, maxy, width, height, crs, dst_crs=geo_crs) # Create geobox affine transformation (N.B. Don't need an actual Geobox) affine = Affine.translation(minx, miny) * Affine.scale( (maxx - minx) / width, (maxy - miny) / height) # Zoom factor is the reciprocal of the square root of the transform determinant # (The determinant is x scale factor multiplied by the y scale factor) return 1.0 / math.sqrt(affine.determinant)
def transform(self, recalc=False): """ Parameters ---------- recalc: bool, optional If True, it will re-calculate the transform instead of using the cached transform. Returns ------- :obj:`affine.Afffine`: The affine of the :obj:`xarray.Dataset` | :obj:`xarray.DataArray` """ transform = self._cached_transform() if transform and not transform.is_rectilinear: if recalc: warnings.warn("Non-rectilinear transform found. Unable to recalculate.") return transform try: src_left, _, _, src_top = self.bounds(recalc=recalc) src_resolution_x, src_resolution_y = self.resolution(recalc=recalc) except (DimensionMissingCoordinateError, DimensionError): return Affine.identity() return Affine.translation(src_left, src_top) * Affine.scale( src_resolution_x, src_resolution_y )
def test_geometry_window_rotated_boundless(): """Get the right boundless window for a rotated dataset""" sqrt2 = math.sqrt(2.0) dataset = mock.MagicMock() dataset.transform = (Affine.rotation(-45.0) * Affine.translation(-sqrt2, sqrt2) * Affine.scale(sqrt2 / 2.0, -sqrt2 / 2.0)) dataset.height = 4.0 dataset.width = 4.0 geometry = { "type": "Polygon", "coordinates": [[ (-2.0, -2.0), (-2.0, 2.0), (2.0, 2.0), (2.0, -2.0), (-2.0, -2.0), ]], } win = geometry_window(dataset, [geometry, geometry], boundless=True) assert win.col_off == pytest.approx(-2.0) assert win.row_off == pytest.approx(-2.0) assert win.width == pytest.approx(2.0 * dataset.width) assert win.height == pytest.approx(2.0 * dataset.height)
def georef_nc_to_tif(env, target, source): nc = Dataset(str(source[0])) var = nc.variables[nc.subject] data = var[:].squeeze().data.astype(np.int32) nodata = var.missing_value lat_bnds = nc.variables['latitude_bnds'][:] lon_bnds = nc.variables['longitude_bnds'][:] yoff, xoff = data.shape sx = np.diff(lon_bnds).mean() sy = np.diff(lat_bnds).mean() affine = Affine.translation(lon_bnds.min(), lat_bnds.max()) * Affine.scale( sx, -sy) with rasterio.open(str(target[0]), 'w', driver='GTiff', width=xoff, height=yoff, crs={'init': 'epsg:4326'}, transform=affine, count=1, nodata=nodata, dtype=str(data.dtype)) as dst: dst.write(np.flipud(data), 1) nc.close() return 0
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 test_issue_2138(): """WindowError is raised if bounds and transform are inconsistent""" w, s, e, n = 1.0, 45.7, 1.2, 45.9 a = 0.001 transform = Affine.translation(w, n) * Affine.scale(a, -a) with pytest.raises(WindowError): from_bounds(w, n, e, s, transform)
def black_and_white_raster(band_names=[], height=10, width=10, dtype=np.uint16, crs=WEB_MERCATOR_CRS, affine=None): if affine is None: eps = 1e-100 affine = Affine.translation(10, 12) * Affine.scale(1, -1) bands_num = len(band_names) shape = [bands_num, height, width] array = np.zeros(shape, dtype=dtype) mask = np.full(shape, False, dtype=np.bool) val = 0 for i in range(height): for j in range(width): for z in range(bands_num): array[z, i, j] = val val = 1 - val image = np.ma.array(data=array, mask=mask) raster = GeoRaster2(image=image, affine=affine, crs=crs, band_names=band_names) return raster
def from_geopolygon(cls, geopolygon, resolution, crs=None, align=None): """ :type geopolygon: geometry.Geometry :param resolution: (y_resolution, x_resolution) :param geometry.CRS crs: CRS to use, if different from the geopolygon :param (float,float) align: Align geobox such that point 'align' lies on the pixel boundary. :rtype: GeoBox """ align = align or (0.0, 0.0) assert 0.0 <= align[1] <= abs( resolution[1]), "X align must be in [0, abs(x_resolution)] range" assert 0.0 <= align[0] <= abs( resolution[0]), "Y align must be in [0, abs(y_resolution)] range" if crs is None: crs = geopolygon.crs else: geopolygon = geopolygon.to_crs(crs) bounding_box = geopolygon.boundingbox offx, width = _align_pix(bounding_box.left, bounding_box.right, resolution[1], align[1]) offy, height = _align_pix(bounding_box.bottom, bounding_box.top, resolution[0], align[0]) affine = (Affine.translation(offx, offy) * Affine.scale(resolution[1], resolution[0])) return GeoBox(crs=crs, affine=affine, width=width, height=height)
def render_tile_from_sources( tile, sources, transformation=None, format=None, scale=1, expand=True ): """Render a tile into Web Mercator. Arguments: tile {mercantile.Tile} -- Tile to render. sources {list} -- Sources to render from. Keyword Arguments: transformation {Transformation} -- Transformation to apply. (default: {None}) format {function} -- Output format. (default: {None}) scale {int} -- Output scale factor. (default: {1}) expand {bool} -- Whether to expand single-band, paletted sources to RGBA. (default: {True}) Returns: (dict, bytes) -- Tuple of HTTP headers (dict) and bytes. """ bounds = Bounds(mercantile.xy_bounds(tile), WEB_MERCATOR_CRS) shape = tuple(map(int, Affine.scale(scale) * TILE_SHAPE)) return render( bounds, shape, WEB_MERCATOR_CRS, sources=sources, format=format, transformation=transformation, expand=expand, )
def from_geopolygon(cls, geopolygon, resolution, crs=None, align=None): """ :type geopolygon: GeoPolygon :param resolution: (x_resolution, y_resolution) :param CRS crs: CRS to use, if different from the geopolygon :param (float,float) align: Alight geobox such that point 'align' lies on the pixel boundary. :rtype: GeoBox """ # TODO: currently only flipped Y-axis data is supported assert resolution[1] > 0 assert resolution[0] < 0 align = align or (0.0, 0.0) assert 0.0 <= align[1] <= abs(resolution[1]) assert 0.0 <= align[0] <= abs(resolution[0]) if crs is None: crs = geopolygon.crs else: geopolygon = geopolygon.to_crs(crs) def align_pix(val, res, off): return math.floor((val-off)/res) * res + off bounding_box = geopolygon.boundingbox left = align_pix(bounding_box.left, resolution[1], align[1]) top = align_pix(bounding_box.top, resolution[0], align[0]) affine = (Affine.translation(left, top) * Affine.scale(resolution[1], resolution[0])) return GeoBox(crs=crs, affine=affine, width=int(math.ceil((bounding_box.right-left)/resolution[1])), height=int(math.ceil((bounding_box.bottom-top)/resolution[0])))
def test_move(fps1px): fps = fps1px with buzz.Env(warnings=False, allow_complex_footprint=True): assert fpeq( fps.B, fps.A.move(fps.B.tl), fps.B.move(fps.B.tl), fps.C.move(fps.B.tl), fps.A.move(fps.B.tl, fps.B.tr), fps.B.move(fps.B.tl, fps.B.tr), fps.C.move(fps.B.tl, fps.B.tr), fps.A.move(fps.B.tl, fps.B.tr, fps.B.br), fps.B.move(fps.B.tl, fps.B.tr, fps.B.br), fps.C.move(fps.B.tl, fps.B.tr, fps.B.br), ) aff = (Affine.translation(*fps.A.bl) * Affine.rotation(45) * Affine.scale(2**0.5, 2**0.5 * -2)) assert fpeq( buzz.Footprint(gt=aff.to_gdal(), rsize=(1, 1)), fps.A.move(fps.A.bl, fps.A.tr, fps.I.tr), fps.B.move(fps.A.bl, fps.A.tr, fps.I.tr), fps.C.move(fps.A.bl, fps.A.tr, fps.I.tr), ) with pytest.raises(ValueError, match='angle'): fps.C.move(fps.A.bl, fps.A.tr, fps.I.c)
def sources_for_tile(tile, catalog, scale=1, min_zoom=None, max_zoom=None): """Render a tile's source footprints.""" bounds = Bounds(mercantile.bounds(tile), WGS84_CRS) shape = tuple(map(int, Affine.scale(scale) * TILE_SHAPE)) resolution = get_resolution_in_meters(bounds, shape) for idx, source in enumerate( catalog.get_sources(bounds, resolution, min_zoom=min_zoom, max_zoom=max_zoom)): yield { "url": source.url, "name": source.name, "resolution": source.resolution, "band": source.band, "band_info": source.band_info, "meta": source.meta, "recipes": source.recipes, "priority": source.priority, "coverage": source.coverage, "acquired_at": source.acquired_at, "filename": source.filename, "min_zoom": source.min_zoom, "max_zoom": source.max_zoom, }
def zoom_factor(args, crs): # Determine the geographic "zoom factor" for the request. # (Larger zoom factor means deeper zoom. Smaller zoom factor means larger area.) # Extract request bbox and crs width = int(args['width']) height = int(args['height']) minx, miny, maxx, maxy = map(float, args['bbox'].split(',')) p1 = geometry.point(minx, maxy, crs) p2 = geometry.point(minx, miny, crs) p3 = geometry.point(maxx, maxy, crs) p4 = geometry.point(maxx, miny, crs) # Project to a geographic coordinate system # This is why we can't just use the regular geobox. The scale needs to be # "standardised" in some sense, not dependent on the CRS of the request. geo_crs = geometry.CRS("EPSG:4326") gp1 = p1.to_crs(geo_crs) gp2 = p2.to_crs(geo_crs) gp3 = p3.to_crs(geo_crs) gp4 = p4.to_crs(geo_crs) minx = min(gp1.points[0][0], gp2.points[0][0], gp3.points[0][0], gp4.points[0][0]) maxx = max(gp1.points[0][0], gp2.points[0][0], gp3.points[0][0], gp4.points[0][0]) miny = min(gp1.points[0][1], gp2.points[0][1], gp3.points[0][1], gp4.points[0][1]) maxy = max(gp1.points[0][1], gp2.points[0][1], gp3.points[0][1], gp4.points[0][1]) # Create geobox affine transformation (N.B. Don't need an actual Geobox) affine = Affine.translation(minx, miny) * Affine.scale((maxx - minx) / width, (maxy - miny) / height) # Zoom factor is the reciprocal of the square root of the transform determinant # (The determinant is x scale factor multiplied by the y scale factor) return 1.0 / math.sqrt(affine.determinant)
def test_merge_does_not_uncover_masked_pixels(): # See https://github.com/satellogic/telluric/issues/65 affine = Affine.translation(0, 2) * Affine.scale(1, -1) rs_a = GeoRaster2( image=np.ma.masked_array( [[[100, 89], [100, 89]], [[110, 99], [110, 99]]], [[[False, True], [False, True]], [[False, True], [False, True]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['red', 'green'], ) rs_b = GeoRaster2( image=np.array([[[0, 210], [0, 210]]], dtype=np.uint8), affine=affine, crs=WGS84_CRS, band_names=['green'], ) expected_image = np.ma.masked_array( [[[100, 89], [100, 89]], [[110, 99], [110, 99]]], [[[False, True], [False, True]], [[False, True], [False, True]]], dtype=np.uint8) result = merge_all([rs_a, rs_b], rs_a.footprint()).limit_to_bands(['red', 'green']) assert_array_equal(np.ma.filled(result.image, 0), np.ma.filled(expected_image, 0)) assert_array_equal(result.image.mask, expected_image.mask)
def test_georaster_save_emits_warning_if_uneven_mask(recwarn): affine = Affine.translation(0, 2) * Affine.scale(1, -1) raster = GeoRaster2( image=np.array([ [ [100, 200], [100, 200] ], [ [110, 0], [110, 0] ] ], dtype=np.uint8), affine=affine, crs=WGS84_CRS, nodata=0 ) orig_mask = raster.image.mask assert not (orig_mask == orig_mask[0]).all() with NamedTemporaryFile(suffix=".tif") as fp: raster.save(fp.name) w = recwarn.pop(GeoRaster2Warning) assert ( "Saving different masks per band is not supported, the union of the masked values will be performed." in str(w.message) )
def transform(self): """Returns the affine transform.""" return ( from_bounds(*self.bounds, self.width, self.height) if self.bounds else Affine.scale(self.width, -self.height) )
def mk_sample_xr_dataset(crs="EPSG:3578", shape=(33, 74), resolution=None, xy=(0, 0), time='2020-02-13T11:12:13.1234567Z', name='band', dtype='int16', nodata=-999, units='1'): """ Note that resolution is in Y,X order to match that of GeoBox. shape (height, width) resolution (y: float, x: float) - in YX, to match GeoBox/shape notation xy (x: float, y: float) -- location of the top-left corner of the top-left pixel in CRS units """ if isinstance(crs, str): crs = CRS(crs) if resolution is None: resolution = (-10, 10) if crs is None or crs.projected else (-0.01, 0.01) t_coords = {} if time is not None: t_coords['time'] = mk_time_coord([time]) transform = Affine.translation(*xy) * Affine.scale(*resolution[::-1]) h, w = shape geobox = GeoBox(w, h, transform, crs) return Datacube.create_storage( t_coords, geobox, [Measurement(name=name, dtype=dtype, nodata=nodata, units=units)])
def affine_from_axis(xx, yy, fallback_resolution=None): """ Compute Affine transform from pixel to real space given X,Y coordinates. :param xx: X axis coordinates :param yy: Y axis coordinates :param fallback_resolution: None|float|(resx:float, resy:float) resolution to assume for single element axis. (0, 0) in pixel space is defined as top left corner of the top left pixel \ `` 0 1 +---+---+ 0 | | | +---+---+ 1 | | | +---+---+ Only uses first two coordinate values, assumes that data is regularly sampled. raises ValueError when any axis is empty raises ValueError when any axis has single value and fallback resolution was not supplied. """ if fallback_resolution is not None: if isinstance(fallback_resolution, (float, int)): frx, fry = fallback_resolution, fallback_resolution else: frx, fry = fallback_resolution else: frx, fry = None, None xres, xoff = data_resolution_and_offset(xx, frx) yres, yoff = data_resolution_and_offset(yy, fry) return Affine.translation(xoff, yoff) * Affine.scale(xres, yres)
def masked_raster(cls): data = np.array([ [0, 1, 1, 1], [0, 2, 0, 2], [0, 3, 3, 3], ], dtype=np.uint8) mask = np.array([ [True, False, False, False], [True, False, False, False], [True, False, False, False], ], dtype=np.bool) image = np.ma.array(np.repeat(data[np.newaxis, :, :], 3, 0), mask=np.repeat(mask[np.newaxis, :, :], 3, 0)) # Don't use exactly -1.0 for the affine for rasterio < 1.0a13, see # https://github.com/mapbox/rasterio/issues/1272 affine = Affine.scale(1, -1.0001) * Affine.translation(0, -3) crs = WGS84_CRS return GeoRaster2( image, affine=affine, crs=crs, )
def test_is_rectilinear(self): assert Affine.identity().is_rectilinear assert Affine.scale(2.5, 6.1).is_rectilinear assert Affine.translation(4, -1).is_rectilinear assert Affine.rotation(90).is_rectilinear assert not Affine.shear(4, -1).is_rectilinear assert not Affine.rotation(-26).is_rectilinear
def test_full_stack_dataset_xysel(self, d, translate, scale, shear, rotate, samp): dataset = create3d_dataset(dims=d) xlines_, ilines_ = np.meshgrid(dataset.xline, dataset.iline) ix_pairs = np.dstack([ilines_, xlines_]) tr = Affine.translation(*translate) sc = Affine.scale(scale) sh = Affine.shear(*shear) rt = Affine.rotation(rotate) trsfm = lambda x: tr * sc * sh * rt * x ix_pairs = np.apply_along_axis(trsfm, 1, ix_pairs.reshape( -1, 2)).reshape(ix_pairs.shape) dataset['cdp_x'] = (DimensionKeyField.cdp_3d, ix_pairs[:, :, 0]) dataset['cdp_y'] = (DimensionKeyField.cdp_3d, ix_pairs[:, :, 1]) dataset['data'] = (DimensionKeyField.threed_twt, np.random.rand(*d)) test_points = np.dstack([ np.random.random(samp) * (d[0] - 0.1) + 0.1, np.random.random(samp) * (d[1] - 0.1) + 0.1 ])[0] # make sure at least one point is in the box test_points[0, :] = d[0] / 2, d[1] / 2 xys = np.apply_along_axis(trsfm, 1, test_points) res = dataset.seis.xysel(xys[:, 0], xys[:, 1], method='linear') assert isinstance(res, xr.Dataset) res = dataset.seis.xysel(xys[:, 0], xys[:, 1], method='nearest') assert isinstance(res, xr.Dataset)
def test_is_conformal(self): assert Affine.identity().is_conformal assert Affine.scale(2.5, 6.1).is_conformal assert Affine.translation(4, -1).is_conformal assert Affine.rotation(90).is_conformal assert Affine.rotation(-26).is_conformal assert not Affine.shear(4, -1).is_conformal
def test_rasters_covering_different_overlapping_areas_on_y(): affine_a = Affine.translation(1, 2) * Affine.scale(1, -1) raster_a = make_test_raster(1, [1], height=20, width=20, affine=affine_a) affine_b = Affine.translation(1, -9) * Affine.scale(1, -1) raster_b = make_test_raster(2, [1], height=20, width=20, affine=affine_b) roi = GeoVector.from_bounds(xmin=1, ymin=-29, xmax=21, ymax=2, crs=WEB_MERCATOR_CRS) rasters = [raster_a, raster_b] merged = merge_all(rasters, roi) assert (merged.affine.almost_equals(affine_a)) assert (not merged.image.mask.all()) assert ((merged.image.data[0, 0:20, 0:20] == 1).all()) assert ((merged.image.data[0, 21:30, 0:20] == 2).all())
def transform_from_latlon(lat, lon): """ input 1D array of lat / lon and output an Affine transformation """ lat = np.asarray(lat) lon = np.asarray(lon) trans = Affine.translation(lon[0], lat[0]) scale = Affine.scale(lon[1] - lon[0], lat[1] - lat[0]) return trans * scale
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 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 from_origin(west, north, xsize, ysize): """Return an Affine transformation given upper left and pixel sizes. Return an Affine transformation for a georeferenced raster given the coordinates of its upper left corner `west`, `north` and pixel sizes `xsize`, `ysize`. """ return Affine.translation(west, north) * Affine.scale(xsize, -ysize)
def transform_from_latlon( lat, lon ): ''' simple way to make an affine transform from lats and lons coords ''' from affine import Affine lat = np.asarray( lat ) lon = np.asarray(lon) trans = Affine.translation(lon[0], lat[0]) scale = Affine.scale(lon[1] - lon[0], lat[1] - lat[0]) return trans * scale
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 affine_from_bounds(a, b): # Force arguments to float a = [float(i) for i in a] b = [float(i) for i in b] return ~(Affine.translation(a[0], a[1]) * Affine.scale( (a[2] - a[0]) / (b[2] - b[0]), (a[3] - a[1]) / (b[3] - b[1])) * Affine.translation(-b[0], -b[1]))
def transform_from_latlon( lat, lon ): ''' simple way to make an affine transform from lats and lons coords ''' from affine import Affine lat = np.asarray( lat ) lon = np.asarray( lon ) trans = Affine.translation(lon[0], lat[0]) scale = Affine.scale(lon[1] - lon[0], lat[1] - lat[0]) return trans * scale
def from_bounds(west, south, east, north, width, height): """Return an Affine transformation given bounds, width and height. Return an Affine transformation for a georeferenced raster given its bounds `west`, `south`, `east`, `north` and its `width` and `height` in number of pixels. """ return Affine.translation(west, north) * Affine.scale( (east - west) / width, (south - north) / height)
def affine(self, pixelbuffer=0): """ Return an Affine object of tile. - pixelbuffer: tile buffer in pixels """ left = self.bounds(pixelbuffer=pixelbuffer)[0] top = self.bounds(pixelbuffer=pixelbuffer)[3] return Affine.translation(left, top) * Affine.scale( self.pixel_x_size, -self.pixel_y_size)
def test_itransform(self): pts = [(4, 1), (-1, 0), (3, 2)] r = Affine.scale(-2).itransform(pts) assert r is None, r assert pts == [(-8, -2), (2, 0), (-6, -4)] A = Affine.rotation(33) pts = [(4, 1), (-1, 0), (3, 2)] pts_expect = [A*pt for pt in pts] r = A.itransform(pts) assert r is None assert pts == pts_expect
def test_is_degenerate(self): from affine import EPSILON assert not Affine.identity().is_degenerate assert not Affine.translation(2, -1).is_degenerate assert not Affine.shear(0, -22.5).is_degenerate assert not Affine.rotation(88.7).is_degenerate assert not Affine.scale(0.5).is_degenerate assert Affine.scale(0).is_degenerate assert Affine.scale(-10, 0).is_degenerate assert Affine.scale(0, 300).is_degenerate assert Affine.scale(0).is_degenerate assert Affine.scale(0).is_degenerate assert Affine.scale(EPSILON).is_degenerate
def from_geopolygon(cls, geopolygon, resolution, crs=None, align=True): """ :type geopolygon: datacube.model.GeoPolygon :param resolution: (x_resolution, y_resolution) :param crs: CRS to use, if different from the geopolygon :param align: Should the geobox be aligned to pixels of the given resolution. This assumes an origin of (0,0). :type align: boolean :rtype: GeoBox """ # TODO: currently only flipped Y-axis data is supported assert resolution[1] > 0 assert resolution[0] < 0 if crs is None: crs = geopolygon.crs else: geopolygon = geopolygon.to_crs(crs) bounding_box = geopolygon.boundingbox # print(bounding_box) # print("~~~") left, top = float(bounding_box.left), float(bounding_box.top) if align: left = math.floor(left / resolution[1]) * resolution[1] top = math.floor(top / resolution[0]) * resolution[0] # print(left) #print(top) #print(Affine.translation(left, top)) #print(Affine.scale(resolution[1], resolution[0])) affine = (Affine.translation(left, top) * Affine.scale(resolution[1], resolution[0])) # print(affine) right, bottom = float(bounding_box.right), float(bounding_box.bottom) width, height = ~affine * (right, bottom) # print(right) # print(width) #width = 198 #height = 288 #print(height) if align: width = math.ceil(width) height = math.ceil(height) return GeoBox(crs=crs, affine=affine, width=int(width), height=int(height))
def transform_from_latlon( lat, lon ): ''' simple way to make an affine transform from lats and lons coords ''' from affine import Affine lat = np.asarray( lat ) lon = np.asarray( lon ) if (np.max( lat ) - 90) < np.abs( np.mean( np.diff( lat ) ) ): lat_max = 90.0 else: lat_max = np.max( lat ) # set the lonmax to the corner. --> this can get you into trouble with non-global data # but I am unsure how to make it more dynamic at the moment. [ML] lon_arr = np.array([-180.0, 0.0 ]) idx = (np.abs(lon_arr - np.min( lon ) ) ).argmin() lon_max = lon_arr[ idx ] trans = Affine.translation(lon_max, lat_max) scale = Affine.scale(lon[1] - lon[0], lat[1] - lat[0]) return trans * scale
def test_write_dataset_to_netcdf(tmpnetcdf_filename): affine = Affine.scale(0.1, 0.1)*Affine.translation(20, 30) geobox = GeoBox(100, 100, affine, CRS(GEO_PROJ)) dataset = xarray.Dataset(attrs={'extent': geobox.extent, 'crs': geobox.crs}) for name, coord in geobox.coordinates.items(): dataset[name] = (name, coord.labels, {'units': coord.units}) dataset['B10'] = (geobox.dimensions, numpy.arange(10000).reshape(geobox.shape), {'nodata': 0, 'units': '1'}) write_dataset_to_netcdf(dataset, {'foo': 'bar'}, {'B10': {'attrs': {'abc': 'xyz'}}}, Path(tmpnetcdf_filename)) 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 align_image(affine, infile, outfile=None): """ Create a GDAL VRT referencing the original dataset with an aligned georeference. If no `outfile` argument is specified, the dataset will be created in the same place with `.aligned.vrt` appended as an extension. """ trans = get_transform(infile) px_size = N.array([trans.a,trans.e]) s = Affine.scale(*px_size) px_trans = ~s*affine if outfile is None: outfile = splitext(infile)[0] + ".aligned.vrt" run("gdal_translate", "-of VRT", infile, outfile) if affine != Affine.identity(): outtrans = trans*px_trans*s set_transform(outfile, outtrans) return outfile
def test_average_slope(): _lines = [ geometry.LineString(coordinates=[(0, 5), (10, 5)]), geometry.LineString(coordinates=[(5, 0), (5, 10)]), geometry.LineString(coordinates=[(0, 0), (10, 10)]), geometry.LineString(coordinates=[(0, 0), (0, 10), (10, 10)]), geometry.LineString(coordinates=[(0, 0), (5, 5), (5, 0), (0, 0)]), ] lines = geopandas.GeoDataFrame(geometry=_lines) expected = pandas.Series([ 1.000000, 0.000000, 0.707107, 0.500000, 0.000000, ]) * 100 hill = numpy.mgrid[:11, :11][1] trans = Affine.translation(0, 10) * Affine.rotation(0) * Affine.scale(1, -1) result = algo.average_slope(lines, hill, trans) pdtest.assert_series_equal(result, expected)
def test_window_bounds_north_up(): transform = Affine.translation(0.0, 10.0) * Affine.scale(1.0, -1.0) * Affine.identity() assert_window_almost_equals( from_bounds(0, 0, 10, 10, transform, 10, 10), Window(0, 0, 10, 10))
def main(args): """Parse arguments and run graticule creation.""" parser = argparse.ArgumentParser() parser.add_argument("output", type=str, help="output vector data") parser.add_argument("stepsize", type=int, help="degree step size") parser.add_argument( "--size_x", type=int, help="model raster x size", default=8192) parser.add_argument( "--size_y", type=int, help="model raster y size", default=4096) parser.add_argument( "--driver", type=str, help="output driver", default="ESRI Shapefile") parsed = parser.parse_args(args) output_file = parsed.output stepsize = parsed.stepsize size_x = parsed.size_x size_y = parsed.size_y fieldname = "dd" elevation = 0 bounds = (-180., -90., 180., 90.) left, bottom, right, top = bounds pixel_x_size = (right - left) / float(size_x) pixel_y_size = (top - bottom) / float(size_y) affine = Affine.translation(left, top) * Affine.scale( pixel_x_size, -pixel_y_size) if os.path.isfile(output_file): os.remove(output_file) lons, lats = np.meshgrid( np.linspace(left, right, size_x, endpoint=True), np.linspace(top, bottom, size_y, endpoint=True) ) # Geodetic coordinates with elevation above the WGS84 ellipsoid. coord_gdt = np.empty((size_y, size_x, 3)) coord_gdt[:, :, 0] = lats coord_gdt[:, :, 1] = lons coord_gdt[:, :, 2] = elevation decimal_year = np.empty((size_y, size_x)) decimal_year.fill(2016.1) coord_gct = convert(coord_gdt, GEODETIC_ABOVE_WGS84, GEOCENTRIC_SPHERICAL) qd_lat, qd_lon = eval_qdlatlon( coord_gct[..., 0].ravel(), coord_gct[..., 1].ravel(), coord_gct[..., 2].ravel(), decimal_year.ravel()) # Latitudes lat = np.reshape(qd_lat, (size_y, size_x)) lat_lines = extract_contours(lat, bounds, stepsize, fieldname, 0) # Longitudes lon = np.reshape(qd_lon, (size_y, size_x)) lon_lines = extract_contours( np.absolute(lon), bounds, stepsize, fieldname, 0) meridians = extract_contours(lon, bounds, stepsize, fieldname, 0) # for row in range(len(lon)): # diff = lon[row][:-1]-lon[row][1:] # step_idxes = np.argwhere(diff > 180.) # if step_idxes: # lon[row][np.argwhere(diff > 180.)[0]+1:] += 360. # lon_lines = extract_contours(lon, bounds, stepsize, fieldname) out_schema = dict( geometry="LineString", properties=dict( degrees="int", direction="str", display="str", dd="float", scalerank="int")) with fiona.open( output_file, "w", schema=out_schema, crs={'init': 'epsg:4326'}, driver=parsed.driver ) as dst: for feature in lat_lines: latitude = feature["properties"]["dd"] lat_int = int(round(latitude)) direction = "N" if lat_int > 0 else "S" display_lat = lat_int if lat_int > 0 else -lat_int display = "%s %s" % (display_lat, direction) if lat_int == 0: direction = None display = "0" feature["properties"].update( degrees=lat_int, direction=direction, display=display, scalerank=None) dst.write(feature) for feature in _extract_longitudes(lon_lines, lon, affine): dst.write(feature) longitude = feature["properties"]["dd"] lon_int = int(round(longitude)) direction = "W" if lon_int > 0 else "E" display_lon = lon_int if lon_int > 0 else -lon_int display = "%s %s" % (display_lon, direction) if lon_int == 0: direction = None display = "0" feature["properties"].update( degrees=lon_int, direction=direction, display=display, scalerank=None) # if longitude > 180.: # feature["properties"].update(dd=longitude-360.) # dst.write(feature) for feature in meridians: longitude = feature["properties"]["dd"] if longitude in [0, 180]: feature["properties"].update( degrees=0, direction=None, display="0", scalerank=None) dst.write(feature)
def test_cant_invert_degenerate(self): t = Affine.scale(0) with pytest.raises(affine.TransformNotInvertibleError): ~t
def test_write_plus_model_jpeg(): with MemoryFile() as memfile: with memfile.open(driver='JPEG', dtype='uint8', count=3, height=32, width=32, crs='epsg:3226', transform=Affine.identity() * Affine.scale(0.5, -0.5)) as dst: dst.write(numpy.full((32, 32), 255, dtype='uint8'), 1) dst.write(numpy.full((32, 32), 204, dtype='uint8'), 2) dst.write(numpy.full((32, 32), 153, dtype='uint8'), 3) data = dst.read() assert (data[0] == 255).all() assert (data[1] == 204).all() assert (data[2] == 153).all()
def base_affine(rotation): return Affine.translation(5, 15) * Affine.rotation(rotation) * Affine.scale(3, -3)
def test_rotation_improper(): with pytest.raises(affine.UndefinedRotationError): Affine.scale(-1, 1).rotation_angle
def test_mul_transform(self): t = Affine.rotation(5) * Affine.rotation(29) assert isinstance(t, Affine) seq_almost_equal(t, Affine.rotation(34)) t = Affine.scale(3, 5) * Affine.scale(2) seq_almost_equal(t, Affine.scale(6, 10))
def test_cant_invert_degenerate(self): from affine import TransformNotInvertibleError t = Affine.scale(0) self.assertRaises(TransformNotInvertibleError, lambda: ~t)
def test_itransform(self): pts = [(4, 1), (-1, 0), (3, 2)] r = Affine.scale(-2).itransform(pts) assert r is None, r assert_equal(pts, [(-8, -2), (2, 0), (-6, -4)])