def test_to_crs(): poly = geometry.polygon([(0, 0), (0, 5), (10, 5)], epsg4326) num_points = 3 assert poly.crs is epsg4326 assert poly.to_crs(epsg3857).crs is epsg3857 assert poly.to_crs('EPSG:3857').crs == 'EPSG:3857' assert poly.to_crs('EPSG:3857', 0.1).crs == epsg3857 assert poly.exterior.to_crs(epsg3857) == poly.to_crs(epsg3857).exterior # test that by default segmentation happens # +1 is because exterior loops back to start point assert len(poly.to_crs(epsg3857).exterior.xy[0]) > num_points + 1 # test that +inf disables segmentation # +1 is because exterior loops back to start point assert len(poly.to_crs(epsg3857, float('+inf')).exterior.xy[0]) == num_points + 1 # test the segmentation works on multi-polygons mpoly = (geometry.box(0, 0, 1, 3, 'EPSG:4326') | geometry.box(2, 4, 3, 6, 'EPSG:4326')) assert mpoly.type == 'MultiPolygon' assert mpoly.to_crs(epsg3857).type == 'MultiPolygon' poly = geometry.polygon([(0, 0), (0, 5), (10, 5)], None) assert poly.crs is None with pytest.raises(ValueError): poly.to_crs(epsg3857)
def test_wrap_dateline(): sinus_crs = geometry.CRS("""PROJCS["unnamed", GEOGCS["Unknown datum based upon the custom spheroid", DATUM["Not specified (based on custom spheroid)", SPHEROID["Custom spheroid",6371007.181,0]], PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]], PROJECTION["Sinusoidal"], PARAMETER["longitude_of_center",0], PARAMETER["false_easting",0], PARAMETER["false_northing",0], UNIT["Meter",1]]""") albers_crs = geometry.CRS('EPSG:3577') geog_crs = geometry.CRS('EPSG:4326') wrap = geometry.polygon([(12231455.716333, -5559752.598333), (12231455.716333, -4447802.078667), (13343406.236, -4447802.078667), (13343406.236, -5559752.598333), (12231455.716333, -5559752.598333)], crs=sinus_crs) wrapped = wrap.to_crs(geog_crs) assert wrapped.type == 'Polygon' wrapped = wrap.to_crs(geog_crs, wrapdateline=True) # assert wrapped.type == 'MultiPolygon' TODO: these cases are quite hard to implement. # hopefully GDAL's CutGeometryOnDateLineAndAddToMulti will be available through py API at some point wrap = geometry.polygon([(13343406.236, -5559752.598333), (13343406.236, -4447802.078667), (14455356.755667, -4447802.078667), (14455356.755667, -5559752.598333), (13343406.236, -5559752.598333)], crs=sinus_crs) wrapped = wrap.to_crs(geog_crs) assert wrapped.type == 'Polygon' wrapped = wrap.to_crs(geog_crs, wrapdateline=True) # assert wrapped.type == 'MultiPolygon' TODO: same as above wrap = geometry.polygon([(14455356.755667, -5559752.598333), (14455356.755667, -4447802.078667), (15567307.275333, -4447802.078667), (15567307.275333, -5559752.598333), (14455356.755667, -5559752.598333)], crs=sinus_crs) wrapped = wrap.to_crs(geog_crs) assert wrapped.type == 'Polygon' wrapped = wrap.to_crs(geog_crs, wrapdateline=True) # assert wrapped.type == 'MultiPolygon' TODO: same as above wrap = geometry.polygon([(3658653.1976781483, -4995675.379595791), (4025493.916030875, -3947239.249752495), (4912789.243100313, -4297237.125269571), (4465089.861944263, -5313778.16975072), (3658653.1976781483, -4995675.379595791)], crs=albers_crs) wrapped = wrap.to_crs(geog_crs) assert wrapped.type == 'Polygon' assert wrapped.intersects(geometry.line([(0, -90), (0, 90)], crs=geog_crs)) wrapped = wrap.to_crs(geog_crs, wrapdateline=True) assert wrapped.type == 'MultiPolygon' assert not wrapped.intersects( geometry.line([(0, -90), (0, 90)], crs=geog_crs))
def test_gridspec(): gs = GridSpec(crs=geometry.CRS('EPSG:4326'), tile_size=(1, 1), resolution=(-0.1, 0.1), origin=(10, 10)) poly = geometry.polygon([(10, 12.2), (10.8, 13), (13, 10.8), (12.2, 10), (10, 12.2)], crs=geometry.CRS('EPSG:4326')) cells = {index: geobox for index, geobox in list(gs.tiles_inside_geopolygon(poly))} assert set(cells.keys()) == {(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1)} assert numpy.isclose(cells[(2, 0)].coordinates['longitude'].values, numpy.linspace(12.05, 12.95, num=10)).all() assert numpy.isclose(cells[(2, 0)].coordinates['latitude'].values, numpy.linspace(10.95, 10.05, num=10)).all()
def test_gridspec(): gs = GridSpec(crs=geometry.CRS('EPSG:4326'), tile_size=(1, 1), resolution=(-0.1, 0.1), origin=(10, 10)) poly = geometry.polygon([(10, 12.2), (10.8, 13), (13, 10.8), (12.2, 10), (10, 12.2)], crs=geometry.CRS('EPSG:4326')) cells = { index: geobox for index, geobox in list(gs.tiles_from_geopolygon(poly)) } assert set(cells.keys()) == {(0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1)} assert numpy.isclose(cells[(2, 0)].coordinates['longitude'].values, numpy.linspace(12.05, 12.95, num=10)).all() assert numpy.isclose(cells[(2, 0)].coordinates['latitude'].values, numpy.linspace(10.95, 10.05, num=10)).all() # check geobox_cache cache = {} poly = gs.tile_geobox((3, 4)).extent (c1, gbox1), = list(gs.tiles_from_geopolygon(poly, geobox_cache=cache)) (c2, gbox2), = list(gs.tiles_from_geopolygon(poly, geobox_cache=cache)) assert c1 == (3, 4) and c2 == c1 assert gbox1 is gbox2
def extent(self) -> Optional[geometry.Geometry]: """ :returns: valid extent of the dataset or None """ def xytuple(obj): return obj['x'], obj['y'] # If no projection or crs, they have no extent. projection = self._gs if not projection: return None crs = self.crs if not crs: _LOG.debug("No CRS, assuming no extent (dataset %s)", self.id) return None valid_data = projection.get('valid_data') geo_ref_points = projection.get('geo_ref_points') if valid_data: return geometry.Geometry(valid_data, crs=crs) elif geo_ref_points: return geometry.polygon([ xytuple(geo_ref_points[key]) for key in ('ll', 'ul', 'ur', 'lr', 'll') ], crs=crs) return None
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=geometry.CRS('EPSG:3577')) 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)
def test_props(): crs = epsg4326 box1 = geometry.box(10, 10, 30, 30, crs=crs) assert box1 assert box1.is_valid assert not box1.is_empty assert box1.area == 400.0 assert box1.boundary.length == 80.0 assert box1.centroid == geometry.point(20, 20, crs) triangle = geometry.polygon([(10, 20), (20, 20), (20, 10), (10, 20)], crs=crs) assert triangle.envelope == geometry.BoundingBox(10, 10, 20, 20) outer = next(iter(box1)) assert outer.length == 80.0 box1copy = geometry.box(10, 10, 30, 30, crs=crs) assert box1 == box1copy assert box1.convex_hull == box1copy # NOTE: this might fail because of point order box2 = geometry.box(20, 10, 40, 30, crs=crs) assert box1 != box2 bbox = geometry.BoundingBox(1, 0, 10, 13) assert bbox.width == 9 assert bbox.height == 13 pt = geometry.point(3, 4, crs) assert pt.json['coordinates'] == (3.0, 4.0) assert 'Point' in str(pt) assert bool(pt) is True assert pt.__nonzero__() is True
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 GeoPolygon(coordinates, crs): # pylint: disable=invalid-name warnings.warn( "GeoPolygon is depricated. Use 'datacube.utils.geometry.polygon'", DeprecationWarning) if not isinstance(coordinates, Sequence): raise ValueError( "points ({}) must be a sequence of (x, y) coordinates".format( coordinates)) return geometry.polygon(coordinates + [coordinates[0]], crs=crs)
def bounding_box_to_geom(bbox, bb_crs, target_crs): poly = geometry.polygon([ (bbox.left, bbox.top), (bbox.left, bbox.bottom), (bbox.right, bbox.bottom), (bbox.right, bbox.top), (bbox.left, bbox.top), ], bb_crs) return poly.to_crs(target_crs)
def _polygon_from_boundingbox(boundingbox, crs=None): points = [ (boundingbox.left, boundingbox.top), (boundingbox.right, boundingbox.top), (boundingbox.right, boundingbox.bottom), (boundingbox.left, boundingbox.bottom), (boundingbox.left, boundingbox.top), ] return geometry.polygon(points, crs=crs)
def __init__(self, width, height, affine, crs): assert height > 0 and width > 0, "Can't create GeoBox of zero size" #: :type: int self.width = width #: :type: int self.height = height #: :rtype: affine.Affine self.affine = affine points = [(0, 0), (0, height), (width, height), (width, 0), (0, 0)] self.affine.itransform(points) #: :rtype: geometry.Geometry self.extent = geometry.polygon(points, crs=crs)
def test_wrap_dateline(): albers_crs = epsg3577 geog_crs = epsg4326 wrap = geometry.polygon([(3658653.1976781483, -4995675.379595791), (4025493.916030875, -3947239.249752495), (4912789.243100313, -4297237.125269571), (4465089.861944263, -5313778.16975072), (3658653.1976781483, -4995675.379595791)], crs=albers_crs) wrapped = wrap.to_crs(geog_crs) assert wrapped.type == 'Polygon' assert wrapped.intersects(geometry.line([(0, -90), (0, 90)], crs=geog_crs)) wrapped = wrap.to_crs(geog_crs, wrapdateline=True) assert wrapped.type == 'MultiPolygon' assert not wrapped.intersects(geometry.line([(0, -90), (0, 90)], crs=geog_crs))
def extent(self): """ :rtype: geometry.Geometry """ def xytuple(obj): return obj['x'], obj['y'] projection = self.metadata.grid_spatial if 'valid_data' in projection: return geometry.Geometry(projection['valid_data'], crs=self.crs) else: geo_ref_points = projection['geo_ref_points'] return geometry.polygon([xytuple(geo_ref_points[key]) for key in ('ll', 'ul', 'ur', 'lr', 'll')], crs=self.crs)
def test_wrap_dateline_sinusoidal(pts): sinus_crs = geometry.CRS("""PROJCS["unnamed", GEOGCS["Unknown datum based upon the custom spheroid", DATUM["Not specified (based on custom spheroid)", SPHEROID["Custom spheroid",6371007.181,0]], PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]], PROJECTION["Sinusoidal"], PARAMETER["longitude_of_center",0], PARAMETER["false_easting",0], PARAMETER["false_northing",0], UNIT["Meter",1]]""") wrap = geometry.polygon(pts, crs=sinus_crs) wrapped = wrap.to_crs(epsg4326) assert wrapped.type == 'Polygon' wrapped = wrap.to_crs(epsg4326, wrapdateline=True) assert wrapped.type == 'MultiPolygon' assert not wrapped.intersects(geometry.line([(0, -90), (0, 90)], crs=epsg4326))
def test_props(): crs = epsg4326 box1 = geometry.box(10, 10, 30, 30, crs=crs) assert box1 assert box1.is_valid assert not box1.is_empty assert box1.area == 400.0 assert box1.boundary.length == 80.0 assert box1.centroid == geometry.point(20, 20, crs) triangle = geometry.polygon([(10, 20), (20, 20), (20, 10), (10, 20)], crs=crs) assert triangle.boundingbox == geometry.BoundingBox(10, 10, 20, 20) assert triangle.envelope.contains(triangle) assert box1.length == 80.0 box1copy = geometry.box(10, 10, 30, 30, crs=crs) assert box1 == box1copy assert box1.convex_hull == box1copy # NOTE: this might fail because of point order box2 = geometry.box(20, 10, 40, 30, crs=crs) assert box1 != box2 bbox = geometry.BoundingBox(1, 0, 10, 13) assert bbox.width == 9 assert bbox.height == 13 assert bbox.points == [(1, 0), (1, 13), (10, 0), (10, 13)] assert bbox.transform(Affine.identity()) == bbox assert bbox.transform(Affine.translation(1, 2)) == geometry.BoundingBox( 2, 2, 11, 15) pt = geometry.point(3, 4, crs) assert pt.json['coordinates'] == (3.0, 4.0) assert 'Point' in str(pt) assert bool(pt) is True assert pt.__nonzero__() is True # check "CRS as string is converted to class automatically" assert isinstance(geometry.point(3, 4, 'epsg:3857').crs, geometry.CRS) # constructor with bad input should raise ValueError with pytest.raises(ValueError): geometry.Geometry(object())
def test_props(): box1 = geometry.box(10, 10, 30, 30, crs=geometry.CRS('EPSG:4326')) assert box1 assert box1.is_valid assert not box1.is_empty assert box1.area == 400.0 assert box1.boundary.length == 80.0 assert box1.centroid == geometry.point(20, 20, geometry.CRS('EPSG:4326')) triangle = geometry.polygon([(10, 20), (20, 20), (20, 10), (10, 20)], crs=geometry.CRS('EPSG:4326')) assert triangle.envelope == geometry.BoundingBox(10, 10, 20, 20) outer = next(iter(box1)) assert outer.length == 80.0 box1copy = geometry.box(10, 10, 30, 30, crs=geometry.CRS('EPSG:4326')) assert box1 == box1copy assert box1.convex_hull == box1copy # NOTE: this might fail because of point order box2 = geometry.box(20, 10, 40, 30, crs=geometry.CRS('EPSG:4326')) assert box1 != box2
def test_shapely_wrappers(): poly = geometry.polygon([(0, 0), (0, 5), (10, 5)], epsg4326) assert isinstance(poly.svg(), str) assert isinstance(poly._repr_svg_(), str) with_hole = poly.buffer(1) - poly assert len(poly.interiors) == 0 assert len(with_hole.interiors) == 1 assert isinstance(with_hole.interiors, list) assert isinstance(with_hole.interiors[0], geometry.Geometry) assert with_hole.interiors[0].crs == with_hole.crs assert poly.exterior.crs == poly.crs x, y = poly.exterior.xy assert len(x) == len(y) assert x.typecode == y.typecode assert x.typecode == 'd' assert (poly | poly) == poly assert (poly & poly) == poly assert (poly ^ poly).is_empty assert (poly - poly).is_empty
def grid2polygon(grid: Dict[str, Any], crs: SomeCRS) -> Geometry: return polygon(grid2points(grid, ring=True), crs)
def test_pickleable(): poly = geometry.polygon([(10, 20), (20, 20), (20, 10), (10, 20)], crs=geometry.CRS('EPSG:4326')) pickled = pickle.dumps(poly, pickle.HIGHEST_PROTOCOL) unpickled = pickle.loads(pickled) assert poly == unpickled
def _get_polygon(args, crs): minx, miny, maxx, maxy = _get_geobox_xy(args, crs) poly = geometry.polygon([(minx, maxy), (minx, miny), (maxx, miny), (maxx, maxy), (minx, maxy)], crs) return poly
def to_crs(self, new_crs): grid = self.layer.grids[new_crs] skip_x_xform = False skip_y_xform = False if self.crs != new_crs: if not self.subsetted.x and not self.subsetted.y: # Neither axis subsetted self.min.x = self.layer.ranges["bboxes"][new_crs]["left"] self.max.x = self.layer.ranges["bboxes"][new_crs]["right"] self.min.y = self.layer.ranges["bboxes"][new_crs]["bottom"] self.max.y = self.layer.ranges["bboxes"][new_crs]["top"] self.crs = new_crs elif not self.subsetted.x or not self.subsetted.y: # One axis subsetted if self.subsetted.x: self.min.y = self.layer.ranges["bboxes"][self.crs]["bottom"] self.max.y = self.layer.ranges["bboxes"][self.crs]["top"] skip_y_xform = True if self.subsetted.y: self.min.x = self.layer.ranges["bboxes"][self.crs]["left"] self.max.x = self.layer.ranges["bboxes"][self.crs]["right"] skip_x_xform = True else: # Both axes subsetted pass if self.crs != new_crs: is_point = False # Prepare geometry for transformation old_crs_obj = self.cfg.crs(self.crs) if self.is_slice("x") and self.is_slice("y"): geom = geometry.point(self.min.x, self.min.y, old_crs_obj) is_point = True elif self.is_slice("x") or self.is_slice("y"): geom = geometry.line( ( (self.min.x, self.min.y), (self.max.x, self.max.y) ), old_crs_obj) else: geom = geometry.polygon( ( (self.min.x, self.min.y), (self.min.x, self.max.y), (self.max.x, self.max.y), (self.max.x, self.min.y), (self.min.x, self.min.y), ), old_crs_obj ) new_crs_obj = self.cfg.crs(new_crs) grid = self.layer.grids[new_crs] if is_point: prj_pt = geom.to_crs(new_crs_obj) x, y = prj_pt.coords[0] self.min.set(x, y) self.max.set(x + grid["resolution"][0], y + grid["resolution"][1]) self.size.set(1, 1) else: proj_geom = geom.to_crs(new_crs_obj) bbox = proj_geom.boundingbox if skip_x_xform: self.min.x = self.layer.ranges["bboxes"][new_crs]["left"] self.max.x = self.layer.ranges["bboxes"][new_crs]["right"] else: self.min.x = bbox.left self.max.x = bbox.right if skip_y_xform: self.min.y = self.layer.ranges["bboxes"][new_crs]["bottom"] self.max.y = self.layer.ranges["bboxes"][new_crs]["top"] else: self.min.y = bbox.bottom self.max.y = bbox.top self.quantise_to_resolution(grid) self.crs = new_crs else: self.quantise_to_resolution(grid)
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 = 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 GeoBox(1, 1, mkA(0), epsg4326).geographic_extent.crs == epsg4326 assert GeoBox(1, 1, mkA(0), None).dimensions == ('y', 'x') 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 assert gbox.is_empty() is False assert bool(gbox) is True assert (gbox[:3, :4] & gbox[3:, 4:]).is_empty() assert (gbox[:3, :4] & gbox[30:, 40:]).is_empty() with pytest.raises(ValueError): geobox_intersection_conservative([]) with pytest.raises(ValueError): geobox_union_conservative([]) # can not combine across CRSs with pytest.raises(ValueError): bounding_box_in_pixel_domain(GeoBox(1, 1, mkA(0), epsg4326), GeoBox(2, 3, mkA(0), epsg3577))
def test_ops(): box1 = geometry.box(10, 10, 30, 30, crs=epsg4326) box2 = geometry.box(20, 10, 40, 30, crs=epsg4326) box3 = geometry.box(20, 10, 40, 30, crs=epsg4326) box4 = geometry.box(40, 10, 60, 30, crs=epsg4326) no_box = None assert box1 != box2 assert box2 == box3 assert box3 != no_box union1 = box1.union(box2) assert union1.area == 600.0 with pytest.raises(geometry.CRSMismatchError): box1.union(box2.to_crs(epsg3857)) inter1 = box1.intersection(box2) assert bool(inter1) assert inter1.area == 200.0 inter2 = box1.intersection(box4) assert not bool(inter2) assert inter2.is_empty # assert not inter2.is_valid TODO: what's going on here? diff1 = box1.difference(box2) assert diff1.area == 200.0 symdiff1 = box1.symmetric_difference(box2) assert symdiff1.area == 400.0 # test segmented line = geometry.line([(0, 0), (0, 5), (10, 5)], epsg4326) line2 = line.segmented(2) assert line.crs is line2.crs assert line.length == line2.length assert len(line.coords) < len(line2.coords) poly = geometry.polygon([(0, 0), (0, 5), (10, 5)], epsg4326) poly2 = poly.segmented(2) assert poly.crs is poly2.crs assert poly.length == poly2.length assert poly.area == poly2.area assert len(poly.geom.exterior.coords) < len(poly2.geom.exterior.coords) poly2 = poly.exterior.segmented(2) assert poly.crs is poly2.crs assert poly.length == poly2.length assert len(poly.geom.exterior.coords) < len(poly2.geom.coords) # test interpolate pt = line.interpolate(1) assert pt.crs is line.crs assert pt.coords[0] == (0, 1) assert isinstance(pt.coords, list) with pytest.raises(TypeError): pt.interpolate(3) # test array interface assert line.__array_interface__ is not None assert np.array(line).shape == (3, 2) # test simplify poly = geometry.polygon([(0, 0), (0, 5), (10, 5)], epsg4326) assert poly.simplify(100) == poly # test iteration poly_2_parts = geometry.Geometry( { "type": "MultiPolygon", "coordinates": [[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]], [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]], [[100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2]]]] }, 'EPSG:4326') pp = list(poly_2_parts) assert len(pp) == 2 assert all(p.crs == poly_2_parts.crs for p in pp) # test transform assert geometry.point( 0, 0, epsg4326).transform(lambda x, y: (x + 1, y + 2)) == geometry.point( 1, 2, epsg4326) # test sides box = geometry.box(1, 2, 11, 22, epsg4326) ll = list(geometry.sides(box)) assert all(l.crs is epsg4326 for l in ll) assert len(ll) == 4 assert ll[0] == geometry.line([(1, 2), (1, 22)], epsg4326) assert ll[1] == geometry.line([(1, 22), (11, 22)], epsg4326) assert ll[2] == geometry.line([(11, 22), (11, 2)], epsg4326) assert ll[3] == geometry.line([(11, 2), (1, 2)], epsg4326)
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 __init__(self, args): self.args = args layers = get_layers() svc_cfg = get_service_cfg() # Argument: Coverage (required) if "coverage" not in args: raise WCS1Exception("No coverage specified", WCS1Exception.MISSING_PARAMETER_VALUE, locator="COVERAGE parameter") self.product_name = args["coverage"] self.product = layers.product_index.get(self.product_name) if not self.product: raise WCS1Exception("Invalid coverage: %s" % self.product_name, WCS1Exception.COVERAGE_NOT_DEFINED, locator="COVERAGE parameter") # Argument: FORMAT (required) if "format" not in args: raise WCS1Exception("No FORMAT parameter supplied", WCS1Exception.MISSING_PARAMETER_VALUE, locator="FORMAT parameter") if args["format"] not in svc_cfg.wcs_formats: raise WCS1Exception("Unsupported format: %s" % args["format"], WCS1Exception.INVALID_PARAMETER_VALUE, locator="FORMAT parameter") self.format = svc_cfg.wcs_formats[args["format"]] # Argument: (request) CRS (required) if "crs" not in args: raise WCS1Exception("No request CRS specified", WCS1Exception.MISSING_PARAMETER_VALUE, locator="CRS parameter") self.request_crsid = args["crs"] if self.request_crsid not in svc_cfg.published_CRSs: raise WCS1Exception("%s is not a supported CRS" % self.request_crsid, WCS1Exception.INVALID_PARAMETER_VALUE, locator="CRS parameter") self.request_crs = geometry.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 svc_cfg.published_CRSs: raise WCS1Exception("%s is not a supported CRS" % self.request_crsid, WCS1Exception.INVALID_PARAMETER_VALUE, locator="RESPONSE_CRS parameter") self.response_crs = geometry.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.) if "bbox" not in args: raise WCS1Exception("No BBOX parameter supplied", WCS1Exception.MISSING_PARAMETER_VALUE, locator="BBOX or TIME parameter") try: if svc_cfg.published_CRSs[self.request_crsid]["vertical_coord_first"]: self.miny, self.minx, self.maxy, self.maxx = map(float, args['bbox'].split(',')) else: 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 "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 if self.format["multi-time"]: self.times = self.product.ranges["times"] else: 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 = [] if times == "now": pass else: for t in times: try: time = datetime.datetime.strptime(t, "%Y-%m-%d").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" ) 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" ) self.times.sort() if len(times) == 0: raise WCS1Exception( "Time value '%s' not a valid ISO-8601 date" % t, WCS1Exception.INVALID_PARAMETER_VALUE, locator="TIME parameter" ) elif len(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 let's try picking a reasonable default if "measurements" not in args: if len(self.product.bands) <= 3: self.bands = list(self.product.bands) elif "red" in self.product.bands and "green" in self.product.bands and "blue" in self.product.bands: self.bands = [ "red", "green", "blue" ] else: self.bands = list(self.product.bands[0:3]) else: bands = args["measurements"] self.bands = [] for b in bands.split(","): if b not in self.product.bands: raise WCS1Exception("Invalid measurement '%s'" % b, WCS1Exception.INVALID_PARAMETER_VALUE, locator="MEASUREMENTS parameter") self.bands.append(b) if not bands: raise WCS1Exception("No measurements supplied", WCS1Exception.INVALID_PARAMETER_VALUE, locator="MEASUREMENTS parameter") # Argument: EXCEPTIONS (optional - defaults to XML) if "exceptions" in args and args["exceptions"] != "application/vnd.ogc.se_xml": raise WCS1Exception("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("Unsupported interpolation method: " % args["interpolation"], WCS1Exception.INVALID_PARAMETER_VALUE, locator="INTERPOLATION parameter") if "width" in args: if "height" not in args: raise WCS1Exception("WIDTH parameter supplied without HEIGHT parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="WIDTH/HEIGHT parameters") 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") 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 "resy" not in args: raise WCS1Exception("RESX parameter supplied without RESY parameter", WCS1Exception.MISSING_PARAMETER_VALUE, locator="RESX/RESY parameters") 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") 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) self.affine = Affine.translation(self.minx, self.miny) * Affine.scale((self.maxx-self.minx)/self.width, (self.maxy-self.miny)/self.height) self.geobox = geometry.GeoBox(self.width, self.height, self.affine, self.request_crs)