def test_multi_polygon(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature( MultiPolygon([ Polygon([(0, 0), (1, 1), (0, 1)]), Polygon([(0, 0), (1, 1), (1, 0)]) ]), complete_test_data_1) feature2 = Feature( MultiPolygon([ Polygon([(1, 1), (2, 2), (1, 2)]), Polygon([(1, 1), (2, 2), (2, 1)]) ]), complete_test_data_2) featuresToDisk = [feature1, feature2] geoms = [f.geom for f in featuresToDisk] data = [f.data for f in featuresToDisk] schema = featureIO.get_feature_schema(featuresToDisk[0]) featureIO.write_shape(geoms, data, schema, filename) featuresFromDisk = featureIO.load_features(filename) self.FeaturesEqual(featuresFromDisk, featuresToDisk)
def test_no_split(self): test_data = {"this": "is", "a": "test", "testing": 123} test_line = LineString([(0, 0), (0, 1)]) features = [Feature(test_line, test_data)] distance = 2 result = geometry_line.split_features_by_distance(features, distance) self.FeaturesEqual(result, [Feature(test_line, test_data)])
def test_non_lines(self): features = [ Feature(Polygon([(0, 0), (100, 0), (100, 100), (0, 100)]), { "t1": 2 }), Feature(Polygon([(0, -100), (-100, -100), (-100, 0), (0, -5)]), { "t2": 3 }) ] radius = 10 with self.assertRaises(NotImplementedError): result = geometry_snap.snap_features(radius, features)
def test_two_features_merge(self): f1data = {'feature': 1, 'test 1': 'data 23'} f2data = {'feature': 2, 'test 2': 'data 34'} feature1 = Feature(LineString([(0, 0), (1, 1)]), f1data) feature2 = Feature(LineString([(1, 1), (2, 2)]), f2data) result = geometry_merge.merge_features([feature1, feature2]) self.FeaturesEqual( result, [Feature(LineString([(0, 0), (1, 1), (2, 2)]), f1data)])
def test_single_snap_middle_to_end(self): features = [ Feature(LineString([(100, 100), (0, 0), (0, 100)]), { "t1": 2 }), Feature(LineString([(0, -100), (0, -5)]), { "t2": 3 }) ] radius = 10 result = geometry_snap.snap_features(radius, features) self.FeaturesEqual(result, [ Feature(LineString([(100, 100), (0, 0), (0, 100)]), { "t1": 2 }), Feature(LineString([(0, -100), (0, -5), (0, 0)]), { "t2": 3 }) ])
def test_mixed_geom(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(Point(20, 20), complete_test_data_2) featuresToDisk = [feature1, feature2] with self.assertRaises(fiona.errors.GeometryTypeValidationError): featureIO.write_features(featuresToDisk, filename)
def test_snap_both_ends(self): features = [ Feature(LineString([(0, 0), (100, 100)]), { "t1": 2 }), Feature(LineString([(100, 95), (5, 0)]), { "t2": 3 }) ] radius = 10 result = geometry_snap.snap_features(radius, features) self.FeaturesEqual(result, [ Feature(LineString([(5, 0), (0, 0), (100, 100), (100, 95)]), { "t1": 2 }), Feature(LineString([(100, 95), (5, 0)]), { "t2": 3 }) ])
def test_no_snap_when_intersecting(self): features = [ Feature(LineString([(0, 0), (100, 100)]), { "t1": 2 }), Feature(LineString([(100, 105), (0, -5)]), { "t2": 3 }) ] radius = 10 result = geometry_snap.snap_features(radius, features) self.FeaturesEqual(result, [ Feature(LineString([(0, 0), (100, 100)]), { "t1": 2 }), Feature(LineString([(100, 105), (0, -5)]), { "t2": 3 }) ])
def test_point(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(Point(0, 0), complete_test_data_1) feature2 = Feature(Point(1, 1), complete_test_data_2) featuresToDisk = [feature1, feature2] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) self.FeaturesEqual(featuresFromDisk, featuresToDisk)
def test_snap_to_non_point(self): features = [ Feature(LineString([(0, 0), (100, 100)]), { "t1": 2 }), Feature(LineString([(-5, 0), (0, -5)]), { "t2": 3 }) ] radius = 10 result = geometry_snap.snap_features(radius, features) self.FeaturesEqual(result, [ Feature(LineString([(-2.5, -2.5), (0, 0), (100, 100)]), { "t1": 2 }), Feature(LineString([(-5, 0), (0, -5)]), { "t2": 3 }) ])
def test_three_non_adjacent_features_merge(self): f1data = {'feature': 1, 'test 1': 'data 23'} f2data = {'feature': 2, 'test 2': 'data 34'} f3data = {'feature': 3, 'test 3': 'data 45'} feature1 = Feature(LineString([(0, 0), (1, 1)]), f1data) feature2 = Feature(LineString([(2, 2), (3, 3)]), f2data) feature3 = Feature(LineString([(1, 1), (2, 2)]), f3data) result = geometry_merge.merge_features([feature1, feature2, feature3]) self.FeaturesEqual( result, [Feature(LineString([(0, 0), (1, 1), (2, 2), (3, 3)]), f1data)])
def test_different_schema(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(LineString([(2, 2), (3, 3)]), diff_schema_test_data) featuresToDisk = [feature1, feature2] with self.assertRaises(ValueError): featureIO.write_features(featuresToDisk, filename)
def merge_features(features): """ Merge feature geometries together where possible, forming several contiguous MultiLineStrings. Applies data of first feature to all. Note: - ordering of loops seems to be somewhat arbitary - ordering of crossing loops is unpredictable - touching loops don't seem to join avoid loops if feasible. Arguments: features {list} -- list of Features """ if (features == None or len(features) < 1): raise ValueError('List of features needs at least 1 member') merged_features = [] merged_geoms = linemerge([f.geom for f in features]) if merged_geoms.geom_type == 'MultiLineString': merged_geoms = merged_geoms.geoms else: merged_geoms = [merged_geoms] for mg in merged_geoms: merged_features.append(Feature(mg, features[0].data)) return merged_features
def test_data_mismatch(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(Point(0, 0), complete_test_data_1) feature2 = Feature(Point(1, 1), diff_schema_test_data) featuresToDisk = [feature1, feature2] geoms = [f.geom for f in featuresToDisk] data = [f.data for f in featuresToDisk] schema = featureIO.get_feature_schema(featuresToDisk[0]) with self.assertRaises(ValueError): featureIO.write_shape(geoms, data, schema, filename)
def test_one_feature(self): feature1 = Feature(LineString([(0, 0), (1, 1)]), { 'feature': 1, 'test 1': 'data 23' }) result = geometry_merge.merge_features([feature1]) self.FeaturesEqual(result, [feature1])
def test_multi_snap_with_central_point(self): features = [ Feature(LineString([(0, 5), (0, 100)]), { "t1": 2 }), Feature(LineString([(0, -5), (0, -100)]), { "t2": 3 }), Feature(LineString([(5, 0), (100, 0)]), { "t3": 4 }), Feature(LineString([(-5, 0), (-100, 0)]), { "t4": 5 }), Feature(LineString([(0, 0), (0, 0)]), { "t5": 6 }) ] radius = 10 result = geometry_snap.snap_features(radius, features) self.FeaturesEqual(result, [ Feature(LineString([(0, 0), (0, 5), (0, 100)]), { "t1": 2 }), Feature(LineString([(0, 0), (0, -5), (0, -100)]), { "t2": 3 }), Feature(LineString([(0, 0), (5, 0), (100, 0)]), { "t3": 4 }), Feature(LineString([(0, 0), (-5, 0), (-100, 0)]), { "t4": 5 }), Feature(LineString([(0, 0), (0, 0)]), { "t5": 6 }) ])
def test_point_geometry(self): feature = Feature(Point(0, 0), {"test": "data"}) self.assertEqual(featureIO.get_feature_schema(feature), { "geometry": "Point", "properties": { "test": "str:250" } })
def test_int_data(self): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": 123}) self.assertEqual(featureIO.get_feature_schema(feature), { "geometry": "LineString", "properties": { "test": "int:16" } })
def test_string_data(self): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": "data"}) self.assertEqual(featureIO.get_feature_schema(feature), { "geometry": "LineString", "properties": { "test": "str:250" } })
def test_polygon_geometry(self): feature = Feature(Polygon([(0, 0), (1, 1), (0, 1)]), {"test": "data"}) self.assertEqual(featureIO.get_feature_schema(feature), { "geometry": "Polygon", "properties": { "test": "str:250" } })
def test_mixed_geom(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(Point(20, 20), complete_test_data_2) featuresToDisk = [feature1, feature2] geoms = [f.geom for f in featuresToDisk] data = [f.data for f in featuresToDisk] schema = featureIO.get_feature_schema(featuresToDisk[0]) with self.assertRaises(fiona.errors.GeometryTypeValidationError): featureIO.write_shape(geoms, data, schema, filename)
def test_multi_polygon(self): with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature( MultiPolygon([ Polygon([(0, 0), (1, 1), (0, 1)]), Polygon([(0, 0), (1, 1), (1, 0)]) ]), complete_test_data_1) feature2 = Feature( MultiPolygon([ Polygon([(1, 1), (2, 2), (1, 2)]), Polygon([(1, 1), (2, 2), (2, 1)]) ]), complete_test_data_2) featuresToDisk = [feature1, feature2] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) self.FeaturesEqual(featuresFromDisk, featuresToDisk)
def test_other_geometry(self): feature = Feature(LinearRing([(0, 0), (1, 1), (0, 1)]), {"test": "data"}) # other types will fail later in the process self.assertEqual(featureIO.get_feature_schema(feature), { "geometry": "LinearRing", "properties": { "test": "str:250" } })
def test_other_geom(self): """Other geometry is silently lost, no exceptions stop the process""" with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LinearRing([(0, 0), (1, 1), (0, 1)]), complete_test_data_1) featuresToDisk = [feature1] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) self.assertEqual(featuresFromDisk, [])
def test_different_schema_convertable_float(self): """Note that the float is converted to an int""" with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(LineString([(2, 2), (3, 3)]), diff_schema_convertable_float_test_data) featuresToDisk = [feature1, feature2] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) modifiedFeaturesFromDisk = featureIO.load_features(filename) modifiedFeaturesFromDisk[1].data["uniqueKey"] = 3.5 self.assertNotEqual(featuresFromDisk[1].data["uniqueKey"], 3.5) self.assertEqual(featuresFromDisk[1].data["uniqueKey"], 3) self.FeaturesEqual(modifiedFeaturesFromDisk, featuresToDisk)
def test_different_schema_unconvertable_string(self): """Note that the string value is dropped and a default of 0 is used""" with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(LineString([(2, 2), (3, 3)]), diff_schema_unconvertable_string_test_data) featuresToDisk = [feature1, feature2] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) modifiedFeaturesFromDisk = featureIO.load_features(filename) modifiedFeaturesFromDisk[1].data["uniqueKey"] = "NaN" self.assertNotEqual(featuresFromDisk[1].data["uniqueKey"], "NaN") self.assertEqual(featuresFromDisk[1].data["uniqueKey"], 0) self.FeaturesEqual(modifiedFeaturesFromDisk, featuresToDisk)
def test_invalid_data_types(self): with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": None}) featureIO.get_feature_schema(feature) with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": [0, 1, 2]}) featureIO.get_feature_schema(feature) with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": (0, 1, 2)}) featureIO.get_feature_schema(feature) with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": { "more": "test", "data": 123 }}) featureIO.get_feature_schema(feature) with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": object()}) featureIO.get_feature_schema(feature) with self.assertRaises(ValueError): feature = Feature(LineString([(0, 0), (1, 1)]), {"test": Point(0, 0)}) featureIO.get_feature_schema(feature)
def split_features_by_distance(features, distance): """ Split up each geometry in a list of features based on distance Arguments: features {list} -- List of Feature objects distance {int|float} -- Approx distance in metres between splits """ split_features = [] for f in features: split_geoms = split_line_by_distance(f.geom, distance) for sg in split_geoms: split_features.append(Feature(sg, f.data)) return split_features
def test_different_schema_unconvertable_float(self): """Note that the NaN value is dropped, it seems like int_64(1<<64) is used i.e. -(2**63), the minimum 64 bit int value odd given that we map to int:16, TODO: maybe we should be mapping to int:64 instead""" with tempfile.TemporaryDirectory("-allfed-spatial-test") as tempdir: filename = os.path.join(tempdir, "testfile.file") feature1 = Feature(LineString([(0, 0), (1, 1)]), complete_test_data_1) feature2 = Feature(LineString([(2, 2), (3, 3)]), diff_schema_unconvertable_float_test_data) featuresToDisk = [feature1, feature2] featureIO.write_features(featuresToDisk, filename) featuresFromDisk = featureIO.load_features(filename) self.assertEqual(featuresFromDisk[1].data["uniqueKey"], -(2**63)) self.assertTrue(math.isnan(featuresToDisk[1].data["uniqueKey"]))
def snap_features(r, features): """ Geometrically 'snap' (connect) features together which are within radius `r` of each other Arguments: r {int|float} -- Tolerance radius in metres within which to snap lines together features {list} -- list of Features Returns: list -- list of snapped Features """ snapped_geoms = snap_linestrings(r, [f.geom for f in features]) return [Feature(snapped_geoms[i], f.data) for i, f in enumerate(features)]