def test_point_snapping_break_ties(self): """DelineateIt: distance ties are broken using flow accumulation.""" from natcap.invest.delineateit import delineateit srs = osr.SpatialReference() srs.ImportFromEPSG(32731) # WGS84/UTM zone 31s wkt = srs.ExportToWkt() # need stream layer, points stream_matrix = numpy.array( [[0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]], dtype=numpy.int8) stream_raster_path = os.path.join(self.workspace_dir, 'streams.tif') flow_accum_array = numpy.array( [[1, 5, 1, 1, 1, 1], [1, 5, 1, 1, 1, 1], [1, 5, 1, 1, 1, 1], [1, 5, 1, 1, 1, 1], [1, 5, 9, 9, 9, 9], [1, 4, 1, 1, 1, 1], [1, 4, 1, 1, 1, 1]], dtype=numpy.int8) flow_accum_path = os.path.join(self.workspace_dir, 'flow_accum.tif') pygeoprocessing.numpy_array_to_raster(stream_matrix, 255, (2, -2), (2, -2), wkt, stream_raster_path) pygeoprocessing.numpy_array_to_raster(flow_accum_array, -1, (2, -2), (2, -2), wkt, flow_accum_path) source_points_path = os.path.join(self.workspace_dir, 'source_features.geojson') source_features = [Point(9, -7)] # equidistant from two streams pygeoprocessing.shapely_geometry_to_vector( source_features, source_points_path, wkt, 'GeoJSON', ogr_geom_type=ogr.wkbUnknown) snapped_points_path = os.path.join(self.workspace_dir, 'snapped_points.gpkg') snap_distance = 10 # large enough to get multiple streams per point. delineateit.snap_points_to_nearest_stream(source_points_path, stream_raster_path, flow_accum_path, snap_distance, snapped_points_path) snapped_points_vector = gdal.OpenEx(snapped_points_path, gdal.OF_VECTOR) snapped_points_layer = snapped_points_vector.GetLayer() # should snap to stream point [4, 3] in the array above # if not considering flow accumulation, it would snap to the # nearest stream point found first in the array, at [2, 1] points = [ shapely.wkb.loads(bytes(feature.GetGeometryRef().ExportToWkb())) for feature in snapped_points_layer ] self.assertEqual(len(points), 1) self.assertEqual((points[0].x, points[0].y), (9, -11))
def test_point_snapping_multipoint(self): """DelineateIt: test multi-point snapping.""" from natcap.invest.delineateit import delineateit srs = osr.SpatialReference() srs.ImportFromEPSG(32731) # WGS84/UTM zone 31s wkt = srs.ExportToWkt() # need stream layer, points stream_matrix = numpy.array( [[0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]], dtype=numpy.int8) stream_raster_path = os.path.join(self.workspace_dir, 'streams.tif') # byte datatype pygeoprocessing.numpy_array_to_raster(stream_matrix, 255, (2, -2), (2, -2), wkt, stream_raster_path) source_points_path = os.path.join(self.workspace_dir, 'source_features.gpkg') gpkg_driver = gdal.GetDriverByName('GPKG') points_vector = gpkg_driver.Create(source_points_path, 0, 0, 0, gdal.GDT_Unknown) layer_name = os.path.splitext(os.path.basename(source_points_path))[0] points_layer = points_vector.CreateLayer(layer_name, points_vector.GetSpatialRef(), ogr.wkbUnknown) # Create a bunch of points for the various OGR multipoint types and # make sure that they are all snapped to exactly the same place. points_layer.StartTransaction() for multipoint_type in (ogr.wkbMultiPoint, ogr.wkbMultiPointM, ogr.wkbMultiPointZM, ogr.wkbMultiPoint25D): new_feature = ogr.Feature(points_layer.GetLayerDefn()) new_geom = ogr.Geometry(multipoint_type) component_point = ogr.Geometry(ogr.wkbPoint) component_point.AddPoint(3, -5) new_geom.AddGeometry(component_point) new_feature.SetGeometry(new_geom) points_layer.CreateFeature(new_feature) # Verify point snapping will run if we give it empty multipoints. for point_type in (ogr.wkbPoint, ogr.wkbMultiPoint): new_feature = ogr.Feature(points_layer.GetLayerDefn()) new_geom = ogr.Geometry(point_type) new_feature.SetGeometry(new_geom) points_layer.CreateFeature(new_feature) points_layer.CommitTransaction() snapped_points_path = os.path.join(self.workspace_dir, 'snapped_points.gpkg') snap_distance = 10 # large enough to get multiple streams per point. delineateit.snap_points_to_nearest_stream(source_points_path, (stream_raster_path, 1), snap_distance, snapped_points_path) try: snapped_points_vector = gdal.OpenEx(snapped_points_path, gdal.OF_VECTOR) snapped_points_layer = snapped_points_vector.GetLayer() # All 4 multipoints should have been snapped to the same place and # should all be Point geometries. self.assertEqual(4, snapped_points_layer.GetFeatureCount()) expected_feature = shapely.geometry.Point(5, -5) for feature in snapped_points_layer: shapely_feature = shapely.wkb.loads( bytes(feature.GetGeometryRef().ExportToWkb())) self.assertTrue(shapely_feature.equals(expected_feature)) finally: snapped_points_layer = None snapped_points_vector = None
def test_point_snapping(self): """DelineateIt: test point snapping.""" from natcap.invest.delineateit import delineateit srs = osr.SpatialReference() srs.ImportFromEPSG(32731) # WGS84/UTM zone 31s wkt = srs.ExportToWkt() # need stream layer, points stream_matrix = numpy.array( [[0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]], dtype=numpy.int8) stream_raster_path = os.path.join(self.workspace_dir, 'streams.tif') # byte datatype pygeoprocessing.numpy_array_to_raster(stream_matrix, 255, (2, -2), (2, -2), wkt, stream_raster_path) source_points_path = os.path.join(self.workspace_dir, 'source_features.geojson') source_features = [ Point(-1, -1), # off the edge of the stream raster. Point(3, -5), Point(7, -9), Point(13, -5), MultiPoint([(13, -5)]), box(-2, -2, -1, -1), # Off the edge ] fields = {'foo': ogr.OFTInteger, 'bar': ogr.OFTString} attributes = [ { 'foo': 0, 'bar': '0.1' }, { 'foo': 1, 'bar': '1.1' }, { 'foo': 2, 'bar': '2.1' }, { 'foo': 3, 'bar': '3.1' }, { 'foo': 3, 'bar': '3.1' }, # intentional duplicate fields { 'foo': 4, 'bar': '4.1' } ] pygeoprocessing.shapely_geometry_to_vector( source_features, source_points_path, wkt, 'GeoJSON', fields=fields, attribute_list=attributes, ogr_geom_type=ogr.wkbUnknown) snapped_points_path = os.path.join(self.workspace_dir, 'snapped_points.gpkg') snap_distance = -1 with self.assertRaises(ValueError) as cm: delineateit.snap_points_to_nearest_stream(source_points_path, (stream_raster_path, 1), snap_distance, snapped_points_path) self.assertTrue('must be >= 0' in str(cm.exception)) snap_distance = 10 # large enough to get multiple streams per point. delineateit.snap_points_to_nearest_stream(source_points_path, (stream_raster_path, 1), snap_distance, snapped_points_path) snapped_points_vector = gdal.OpenEx(snapped_points_path, gdal.OF_VECTOR) snapped_points_layer = snapped_points_vector.GetLayer() # snapped layer will include 4 valid points and 1 polygon. self.assertEqual(5, snapped_points_layer.GetFeatureCount()) expected_geometries_and_fields = [ (Point(5, -5), { 'foo': 1, 'bar': '1.1' }), (Point(5, -9), { 'foo': 2, 'bar': '2.1' }), (Point(13, -11), { 'foo': 3, 'bar': '3.1' }), (Point(13, -11), { 'foo': 3, 'bar': '3.1' }), # Multipoint now point (box(-2, -2, -1, -1), { 'foo': 4, 'bar': '4.1' }), # unchanged ] for feature, (expected_geom, expected_fields) in zip(snapped_points_layer, expected_geometries_and_fields): shapely_feature = shapely.wkb.loads( bytes(feature.GetGeometryRef().ExportToWkb())) self.assertTrue(shapely_feature.equals(expected_geom)) self.assertEqual(expected_fields, feature.items())