diff --git a/label_maker/main.py b/label_maker/main.py index 45854b3..b3769e5 100644 --- a/label_maker/main.py +++ b/label_maker/main.py @@ -4,10 +4,13 @@ import argparse import logging import json +import numpy as np from os import makedirs, path as op from cerberus import Validator from shapely.geometry import MultiPolygon, Polygon +from shapely.geometry import shape +from shapely.ops import unary_union from label_maker.version import __version__ from label_maker.download import download_mbtiles @@ -20,9 +23,9 @@ logger = logging.getLogger(__name__) def get_bounds(feature_collection): - """Get a bounding box for a FeatureCollection of Polygon Features""" - features = [f for f in feature_collection['features'] if f['geometry']['type'] in ['Polygon']] - return MultiPolygon(list(map(lambda x: Polygon(x['geometry']['coordinates'][0]), features))).bounds + """Get a bounding box for a FeatureCollection""" + shape_lst = [shape(f['geometry']) for f in feature_collection['features']] + return unary_union(shape_lst).bounds def parse_args(args): diff --git a/test/fixtures/integration/classification_linestring.geojson b/test/fixtures/integration/classification_linestring.geojson new file mode 100644 index 0000000..128fd35 --- /dev/null +++ b/test/fixtures/integration/classification_linestring.geojson @@ -0,0 +1 @@ +{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82424926757812, 19.055627823152516], [72.82424926757812, 19.05692585554252], [72.82562255859375, 19.05692585554252], [72.82562255859375, 19.055627823152516], [72.82424926757812, 19.055627823152516]]]}, "properties": {"label": [0, 1, 0, 0, 0, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82424926757812, 19.05692585554252], [72.82424926757812, 19.05822387777432], [72.82562255859375, 19.05822387777432], [72.82562255859375, 19.05692585554252], [72.82424926757812, 19.05692585554252]]]}, "properties": {"label": [0, 1, 0, 0, 0, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82424926757812, 19.053031727900255], [72.82424926757812, 19.0543297806049], [72.82562255859375, 19.0543297806049], [72.82562255859375, 19.053031727900255], [72.82424926757812, 19.053031727900255]]]}, "properties": {"label": [0, 1, 0, 0, 0, 1, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82699584960938, 19.055627823152516], [72.82699584960938, 19.05692585554252], [72.828369140625, 19.05692585554252], [72.828369140625, 19.055627823152516], [72.82699584960938, 19.055627823152516]]]}, "properties": {"label": [0, 0, 0, 0, 0, 1, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82699584960938, 19.0543297806049], [72.82699584960938, 19.055627823152516], [72.828369140625, 19.055627823152516], [72.828369140625, 19.0543297806049], [72.82699584960938, 19.0543297806049]]]}, "properties": {"label": [0, 0, 0, 0, 1, 1, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82562255859375, 19.055627823152516], [72.82562255859375, 19.05692585554252], [72.82699584960938, 19.05692585554252], [72.82699584960938, 19.055627823152516], [72.82562255859375, 19.055627823152516]]]}, "properties": {"label": [0, 1, 0, 0, 0, 1, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82562255859375, 19.05692585554252], [72.82562255859375, 19.05822387777432], [72.82699584960938, 19.05822387777432], [72.82699584960938, 19.05692585554252], [72.82562255859375, 19.05692585554252]]]}, "properties": {"label": [0, 1, 0, 0, 0, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82424926757812, 19.0543297806049], [72.82424926757812, 19.055627823152516], [72.82562255859375, 19.055627823152516], [72.82562255859375, 19.0543297806049], [72.82424926757812, 19.0543297806049]]]}, "properties": {"label": [0, 1, 0, 0, 1, 1, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82562255859375, 19.053031727900255], [72.82562255859375, 19.0543297806049], [72.82699584960938, 19.0543297806049], [72.82699584960938, 19.053031727900255], [72.82562255859375, 19.053031727900255]]]}, "properties": {"label": [0, 1, 0, 0, 0, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82699584960938, 19.053031727900255], [72.82699584960938, 19.0543297806049], [72.828369140625, 19.0543297806049], [72.828369140625, 19.053031727900255], [72.82699584960938, 19.053031727900255]]]}, "properties": {"label": [0, 1, 0, 0, 0, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82562255859375, 19.0543297806049], [72.82562255859375, 19.055627823152516], [72.82699584960938, 19.055627823152516], [72.82699584960938, 19.0543297806049], [72.82562255859375, 19.0543297806049]]]}, "properties": {"label": [0, 1, 0, 0, 1, 0, 0]}}, {"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[72.82699584960938, 19.05692585554252], [72.82699584960938, 19.05822387777432], [72.828369140625, 19.05822387777432], [72.828369140625, 19.05692585554252], [72.82699584960938, 19.05692585554252]]]}, "properties": {"label": [1, 0, 0, 0, 0, 0, 0]}}]} \ No newline at end of file diff --git a/test/fixtures/integration/config-linestring.geojson.json b/test/fixtures/integration/config-linestring.geojson.json new file mode 100644 index 0000000..17d8a36 --- /dev/null +++ b/test/fixtures/integration/config-linestring.geojson.json @@ -0,0 +1,17 @@ +{ + "zoom": 18, + "classes": [ + {"name": "tertiary", "filter": ["in","highway","tertiary"]}, + {"name": "motorway", "filter": ["in", "highway", "motorway"]}, + {"name": "primary", "filter": ["in", "highway", "primary"]}, + {"name": "secondary", "filter": ["in", "highway", "secondary"]}, + {"name": "residential", "filter": ["in", "highway", "residential"]}, + {"name": "unclassified", "filter": ["in", "highway", "unclassified"]} + ], + "geojson": "/Users/marthamorrissey/repos/label-maker/test/fixtures/integration/labels-linestring.geojson", + "imagery": "https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.jpg?access_token=ACCESS_TOKEN", + "background_ratio": 1, + "split_vals": [1.0, 0.0, 0.0], + "split_names": ["train", "validation", "test"], + "ml_type": "classification" +} diff --git a/test/fixtures/integration/labels-linestring.geojson b/test/fixtures/integration/labels-linestring.geojson new file mode 100644 index 0000000..78e3c48 --- /dev/null +++ b/test/fixtures/integration/labels-linestring.geojson @@ -0,0 +1,33 @@ +{ +"type": "FeatureCollection", +"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, +"features": [ +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825157300000058, 19.057085400000062 ], [ 72.825155500000051, 19.057146400000022 ], [ 72.824615333099686, 19.0571280429862 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825155500000051, 19.057146400000022 ], [ 72.825155900000084, 19.05720830000007 ], [ 72.825155558349365, 19.057218598633863 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825155500000051, 19.057146400000022 ], [ 72.825903600000061, 19.057181300000025 ], [ 72.826663500000052, 19.05721740000007 ], [ 72.826686300000063, 19.057030900000029 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825903600000061, 19.057181300000025 ], [ 72.825902481175845, 19.057218598633863 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826663500000052, 19.05721740000007 ], [ 72.826663397204598, 19.057218598633863 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826663500000052, 19.05721740000007 ], [ 72.826674942130694, 19.057218598633863 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary" }, "geometry": { "type": "LineString", "coordinates": [ [ 72.826733800000056, 19.054766900000061 ], [ 72.826745600000038, 19.054660200000058 ], [ 72.82674940000004, 19.054554400000029 ], [ 72.826731400000028, 19.054217900000026 ] ] } }, +{ "type": "Feature", "properties": { "highway": "residential"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825243667732295, 19.053565022042594 ], [ 72.825305, 19.053576100000043 ], [ 72.825366600000052, 19.053594 ], [ 72.825409200000081, 19.053714800000023 ], [ 72.825456600000052, 19.053853600000025 ], [ 72.825537100000076, 19.053936400000055 ], [ 72.825548900000058, 19.054079600000023 ], [ 72.825534700000048, 19.054283300000066 ], [ 72.825454200000081, 19.054372800000067 ], [ 72.825395, 19.054500300000029 ], [ 72.825367, 19.054628700000023 ] ] } }, +{ "type": "Feature", "properties": { "highway": "residential"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.827679800000055, 19.054791600000044 ], [ 72.827760200000057, 19.056113800000048 ] ] } }, +{ "type": "Feature", "properties": { "highway": "residential"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826005800000075, 19.056489800000065 ], [ 72.82601290000008, 19.056308500000057 ], [ 72.826287600000057, 19.05629730000004 ], [ 72.826313600000049, 19.056243600000073 ], [ 72.82670440000004, 19.056270700000027 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826287600000057, 19.055807200000061 ], [ 72.826320700000053, 19.055514100000039 ], [ 72.826446, 19.055473600000028 ] ] } }, +{ "type": "Feature", "properties": { "highway": "secondary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.824692900000059, 19.054467 ], [ 72.82472510000008, 19.054551700000047 ], [ 72.824763, 19.054584 ], [ 72.824998200000039, 19.054655700000069 ], [ 72.825244200000043, 19.054697500000032 ], [ 72.82534540000006, 19.05470820000005 ], [ 72.825437, 19.054717900000071 ], [ 72.825928500000032, 19.054769200000067 ], [ 72.826292400000057, 19.054795 ], [ 72.826475200000061, 19.054811900000061 ], [ 72.82653270000003, 19.054750400000046 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826733800000056, 19.054766900000061 ], [ 72.826711200000034, 19.055120700000032 ], [ 72.82670440000004, 19.055479900000023 ], [ 72.826712, 19.055629100000033 ], [ 72.826708800000063, 19.056027700000072 ], [ 72.82670440000004, 19.056270700000027 ], [ 72.826699900000051, 19.056516500000043 ], [ 72.826689700000031, 19.056831700000032 ], [ 72.826686300000063, 19.057030900000029 ] ] } }, +{ "type": "Feature", "properties": { "highway": "secondary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.824615333099686, 19.054372428398164 ], [ 72.824692900000059, 19.054467 ] ] } }, +{ "type": "Feature", "properties": { "highway": "secondary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.824692900000059, 19.054467 ], [ 72.824789700000053, 19.054501100000039 ], [ 72.824923100000035, 19.054546100000039 ], [ 72.825027800000043, 19.054569600000036 ], [ 72.825220200000047, 19.054614 ], [ 72.825367, 19.054628700000023 ], [ 72.825453500000037, 19.054637300000024 ], [ 72.825730400000054, 19.054669600000068 ], [ 72.82600750000006, 19.054689300000064 ], [ 72.82612110000008, 19.054697400000066 ], [ 72.826449800000034, 19.054722100000049 ], [ 72.82653270000003, 19.054750400000046 ] ] } }, +{ "type": "Feature", "properties": { "highway": "secondary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.82653270000003, 19.054750400000046 ], [ 72.826733800000056, 19.054766900000061 ], [ 72.827414600000054, 19.054782500000044 ], [ 72.827679800000055, 19.054791600000044 ], [ 72.827989200000047, 19.05480220000004 ], [ 72.828268909690948, 19.054808057794592 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826475200000061, 19.054811900000061 ], [ 72.826446, 19.055473600000028 ], [ 72.82670440000004, 19.055479900000023 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.826689700000031, 19.056831700000032 ], [ 72.825182900000073, 19.056758900000034 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.825157300000058, 19.057085400000062 ], [ 72.825182900000073, 19.056758900000034 ], [ 72.825142, 19.056470600000068 ], [ 72.825085100000081, 19.056345100000044 ], [ 72.82503550000007, 19.056230200000073 ], [ 72.824983100000054, 19.056112600000063 ], [ 72.824961400000063, 19.056017200000042 ], [ 72.824954800000057, 19.055897100000038 ], [ 72.82496830000008, 19.055794800000058 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.82534540000006, 19.05470820000005 ], [ 72.825347200000067, 19.054909 ], [ 72.825346100000047, 19.055033700000024 ], [ 72.825228800000048, 19.055137100000024 ], [ 72.825171300000079, 19.055186100000071 ], [ 72.825157800000056, 19.055243700000062 ], [ 72.825143100000048, 19.055436600000064 ], [ 72.825121700000068, 19.055511200000069 ], [ 72.825026300000047, 19.055670700000064 ], [ 72.82496830000008, 19.055794800000058 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.824615333099686, 19.055298401040453 ], [ 72.82463690000003, 19.05527660000007 ], [ 72.824745200000052, 19.055210600000066 ], [ 72.825171300000079, 19.055186100000071 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.828246535504462, 19.053565022042594 ], [ 72.828246900000067, 19.053566600000067 ], [ 72.828264, 19.053632 ], [ 72.828268909690948, 19.053650283600202 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.828268909690948, 19.05412767431369 ], [ 72.827803900000049, 19.054134500000032 ], [ 72.827427300000068, 19.054137900000057 ], [ 72.827039200000058, 19.054152100000067 ], [ 72.826987400000064, 19.054184100000043 ], [ 72.826731400000028, 19.054217900000026 ], [ 72.826599400000077, 19.054235300000073 ], [ 72.826479900000038, 19.054297100000042 ], [ 72.826362600000039, 19.054365300000029 ], [ 72.826076200000045, 19.054582800000048 ], [ 72.82600750000006, 19.054689300000064 ] ] } }, +{ "type": "Feature", "properties": {"highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.82727562635931, 19.053565022042594 ], [ 72.827273500000047, 19.053646900000047 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.827273500000047, 19.053646900000047 ], [ 72.827261600000043, 19.053758300000027 ], [ 72.827178700000047, 19.053830600000026 ], [ 72.827119700000083, 19.053800500000023 ], [ 72.826878900000054, 19.053768800000057 ], [ 72.826859700000057, 19.054002500000024 ], [ 72.826851800000043, 19.054065800000046 ], [ 72.827024, 19.054079400000035 ], [ 72.827039200000058, 19.054152100000067 ] ] } }, +{ "type": "Feature", "properties": {"highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.828246900000067, 19.053566600000067 ], [ 72.827698, 19.053585200000043 ] ] } }, +{ "type": "Feature", "properties": { "highway": "tertiary"}, "geometry": { "type": "LineString", "coordinates": [ [ 72.824615333099686, 19.053756329319864 ], [ 72.82503580000008, 19.053739300000075 ], [ 72.825051179925325, 19.053565022042594 ] ] } } +] +} diff --git a/test/integration/test_classification_labels_linestring_geojson.py b/test/integration/test_classification_labels_linestring_geojson.py new file mode 100644 index 0000000..359150d --- /dev/null +++ b/test/integration/test_classification_labels_linestring_geojson.py @@ -0,0 +1,70 @@ +"""Test that the following CLI command returns the expected outputs +label-maker labels --dest integration-cl --config test/fixtures/integration/config.geojson.json""" +import unittest +import json +from os import makedirs +from shutil import copyfile, rmtree +import subprocess + +import numpy as np + +class TestClassificationLabelGeoJSON(unittest.TestCase): + """Tests for classification label creation""" + @classmethod + def setUpClass(cls): + makedirs('integration-cl') + copyfile('test/fixtures/integration/labels-linestring.geojson', 'integration-cl/labels.geojson') + + @classmethod + def tearDownClass(cls): + rmtree('integration-cl') + + def test_cli(self): + """Verify stdout, geojson, and labels.npz produced by CLI""" + # our command line output should look like this + expected_output = """Determining labels for each tile +--- +tertiary: 9 tiles +motorway: 0 tiles +primary: 0 tiles +secondary: 3 tiles +residential: 5 tiles +unclassified: 0 tiles +Total tiles: 12 +Writing out labels to integration-cl/labels.npz +""" + + cmd = 'label-maker labels --dest integration-cl --config test/fixtures/integration/config-linestring.geojson.json' + cmd = cmd.split(' ') + with subprocess.Popen(cmd, universal_newlines=True, stdout=subprocess.PIPE) as p: + self.assertEqual(expected_output, p.stdout.read()) + + # our labels should look like this + + expected_labels = { + '184101-116932-18': np.array([0, 1, 0, 0, 0, 0, 0]), + '184101-116931-18': np.array([0, 1, 0, 0, 0, 0, 0]), + '184101-116934-18': np.array([0, 1, 0, 0, 0, 1, 0]), + '184103-116932-18': np.array([0, 0, 0, 0, 0, 1, 0]), + '184103-116933-18': np.array([0, 0, 0, 0, 1, 1, 0]), + '184102-116932-18': np.array([0, 1, 0, 0, 0, 1, 0]), + '184102-116931-18': np.array([0, 1, 0, 0, 0, 0, 0]), + '184101-116933-18': np.array([0, 1, 0, 0, 1, 1, 0]), + '184102-116934-18': np.array([0, 1, 0, 0, 0, 0, 0]), + '184103-116934-18': np.array([0, 1, 0, 0, 0, 0, 0]), + '184102-116933-18': np.array([0, 1, 0, 0, 1, 0, 0]), + '184103-116931-18': np.array([1, 0, 0, 0, 0, 0, 0]) + } + + labels = np.load('integration-cl/labels.npz') + self.assertEqual(len(labels.files), len(expected_labels.keys())) # First check number of tiles + for tile in labels.files: + self.assertTrue(np.array_equal(expected_labels[tile], labels[tile])) # Now, content + + # our GeoJSON looks like the fixture + with open('test/fixtures/integration/classification_linestring.geojson') as fixture: + with open('integration-cl/classification.geojson') as geojson_file: + expected_geojson = json.load(fixture) + geojson = json.load(geojson_file) + + self.assertCountEqual(expected_geojson, geojson)