diff --git a/CHANGELOG.md b/CHANGELOG.md index af5d1f7..e22424a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Improved in GeoJSON to JSON: - Makes Phases into references & populates phases array (Nodes, Spans) - Makes Organisations into references & populates organisations array (Nodes, Spans) + - Add meta output, with output field coverage + - Fix bug that meant get_json() could not be called twice ### Changed diff --git a/libcoveofds/cli.py b/libcoveofds/cli.py index dc21849..fd6fcb7 100644 --- a/libcoveofds/cli.py +++ b/libcoveofds/cli.py @@ -50,6 +50,11 @@ def main(): geojson_to_json_parser.add_argument( "outputfilename", help="Output filename to write JSON data to" ) + geojson_to_json_parser.add_argument( + "--outputmetafilename", + help="Output filename to write meta JSON data to", + required=False, + ) args = parser.parse_args() @@ -119,6 +124,10 @@ def main(): with open(args.outputfilename, "w") as fp: json.dump(converter.get_json(), fp, indent=4) + if args.outputmetafilename: + with open(args.outputmetafilename, "w") as fp: + json.dump(converter.get_meta_json(), fp, indent=4) + if __name__ == "__main__": main() diff --git a/libcoveofds/geojson.py b/libcoveofds/geojson.py index e6307bf..38f33cc 100644 --- a/libcoveofds/geojson.py +++ b/libcoveofds/geojson.py @@ -1,5 +1,7 @@ import copy +from libcove2.common import fields_present_generator + class JSONToGeoJSONConverter: def __init__(self): @@ -268,6 +270,8 @@ def _process_organisation(self, network_id: str, organisation: dict) -> str: def get_json(self) -> dict: out: dict = {"networks": []} for network in self._networks.values(): + # We are going to change network, so we need to take a copy + network = copy.deepcopy(network) # Arrays have minItems: 1 set - so if no content, remove the empty array for key in ["nodes", "spans", "phases", "organisations", "contracts"]: if not network[key]: @@ -278,3 +282,14 @@ def get_json(self) -> dict: network[key] = list(network[key].values()) out["networks"].append(network) return out + + def get_meta_json(self) -> dict: + out: dict = {"output_field_coverage": {}} + # field coverage + for key, value in fields_present_generator(self.get_json()): + if key not in out["output_field_coverage"]: + out["output_field_coverage"][key] = {"count": 1} + else: + out["output_field_coverage"][key]["count"] += 1 + # return + return out diff --git a/tests/fixtures/geojson_to_json/basic_1.meta.expected.json b/tests/fixtures/geojson_to_json/basic_1.meta.expected.json new file mode 100644 index 0000000..c39a592 --- /dev/null +++ b/tests/fixtures/geojson_to_json/basic_1.meta.expected.json @@ -0,0 +1,58 @@ +{ + "output_field_coverage": { + "/networks": { + "count": 1 + }, + "/networks/id": { + "count": 1 + }, + "/networks/name": { + "count": 1 + }, + "/networks/nodes": { + "count": 1 + }, + "/networks/nodes/id": { + "count": 2 + }, + "/networks/nodes/name": { + "count": 2 + }, + "/networks/nodes/location": { + "count": 2 + }, + "/networks/nodes/location/type": { + "count": 2 + }, + "/networks/nodes/location/coordinates": { + "count": 2 + }, + "/networks/nodes/status": { + "count": 1 + }, + "/networks/spans": { + "count": 1 + }, + "/networks/spans/id": { + "count": 1 + }, + "/networks/spans/name": { + "count": 1 + }, + "/networks/spans/start": { + "count": 1 + }, + "/networks/spans/end": { + "count": 1 + }, + "/networks/spans/route": { + "count": 1 + }, + "/networks/spans/route/type": { + "count": 1 + }, + "/networks/spans/route/coordinates": { + "count": 1 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/geojson_to_json/no_geometry_1.meta.expected.json b/tests/fixtures/geojson_to_json/no_geometry_1.meta.expected.json new file mode 100644 index 0000000..d45c664 --- /dev/null +++ b/tests/fixtures/geojson_to_json/no_geometry_1.meta.expected.json @@ -0,0 +1,40 @@ +{ + "output_field_coverage": { + "/networks": { + "count": 1 + }, + "/networks/id": { + "count": 1 + }, + "/networks/name": { + "count": 1 + }, + "/networks/nodes": { + "count": 1 + }, + "/networks/nodes/id": { + "count": 2 + }, + "/networks/nodes/name": { + "count": 2 + }, + "/networks/nodes/status": { + "count": 1 + }, + "/networks/spans": { + "count": 1 + }, + "/networks/spans/id": { + "count": 1 + }, + "/networks/spans/name": { + "count": 1 + }, + "/networks/spans/start": { + "count": 1 + }, + "/networks/spans/end": { + "count": 1 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/geojson_to_json/organisations_1.meta.expected.json b/tests/fixtures/geojson_to_json/organisations_1.meta.expected.json new file mode 100644 index 0000000..2b060f3 --- /dev/null +++ b/tests/fixtures/geojson_to_json/organisations_1.meta.expected.json @@ -0,0 +1,91 @@ +{ + "output_field_coverage": { + "/networks": { + "count": 1 + }, + "/networks/id": { + "count": 1 + }, + "/networks/name": { + "count": 1 + }, + "/networks/nodes": { + "count": 1 + }, + "/networks/nodes/id": { + "count": 2 + }, + "/networks/nodes/name": { + "count": 2 + }, + "/networks/nodes/physicalInfrastructureProvider": { + "count": 2 + }, + "/networks/nodes/physicalInfrastructureProvider/id": { + "count": 2 + }, + "/networks/nodes/networkProvider": { + "count": 2 + }, + "/networks/nodes/networkProvider/id": { + "count": 2 + }, + "/networks/nodes/location": { + "count": 2 + }, + "/networks/nodes/location/type": { + "count": 2 + }, + "/networks/nodes/location/coordinates": { + "count": 2 + }, + "/networks/nodes/status": { + "count": 1 + }, + "/networks/spans": { + "count": 1 + }, + "/networks/spans/id": { + "count": 1 + }, + "/networks/spans/name": { + "count": 1 + }, + "/networks/spans/start": { + "count": 1 + }, + "/networks/spans/end": { + "count": 1 + }, + "/networks/spans/physicalInfrastructureProvider": { + "count": 1 + }, + "/networks/spans/physicalInfrastructureProvider/id": { + "count": 1 + }, + "/networks/spans/networkProvider": { + "count": 1 + }, + "/networks/spans/networkProvider/id": { + "count": 1 + }, + "/networks/spans/route": { + "count": 1 + }, + "/networks/spans/route/type": { + "count": 1 + }, + "/networks/spans/route/coordinates": { + "count": 1 + }, + "/networks/organisations": { + "count": 1 + }, + "/networks/organisations/id": { + "count": 2 + }, + "/networks/organisations/name": { + "count": 2 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/geojson_to_json/phases_1.meta.expected.json b/tests/fixtures/geojson_to_json/phases_1.meta.expected.json new file mode 100644 index 0000000..4520499 --- /dev/null +++ b/tests/fixtures/geojson_to_json/phases_1.meta.expected.json @@ -0,0 +1,82 @@ +{ + "output_field_coverage": { + "/networks": { + "count": 1 + }, + "/networks/id": { + "count": 1 + }, + "/networks/name": { + "count": 1 + }, + "/networks/nodes": { + "count": 1 + }, + "/networks/nodes/id": { + "count": 2 + }, + "/networks/nodes/name": { + "count": 2 + }, + "/networks/nodes/phase": { + "count": 2 + }, + "/networks/nodes/phase/id": { + "count": 2 + }, + "/networks/nodes/location": { + "count": 2 + }, + "/networks/nodes/location/type": { + "count": 2 + }, + "/networks/nodes/location/coordinates": { + "count": 2 + }, + "/networks/nodes/status": { + "count": 1 + }, + "/networks/spans": { + "count": 1 + }, + "/networks/spans/id": { + "count": 1 + }, + "/networks/spans/name": { + "count": 1 + }, + "/networks/spans/start": { + "count": 1 + }, + "/networks/spans/end": { + "count": 1 + }, + "/networks/spans/phase": { + "count": 1 + }, + "/networks/spans/phase/id": { + "count": 1 + }, + "/networks/spans/route": { + "count": 1 + }, + "/networks/spans/route/type": { + "count": 1 + }, + "/networks/spans/route/coordinates": { + "count": 1 + }, + "/networks/phases": { + "count": 1 + }, + "/networks/phases/id": { + "count": 1 + }, + "/networks/phases/name": { + "count": 1 + }, + "/networks/phases/description": { + "count": 1 + } + } +} \ No newline at end of file diff --git a/tests/test_geojson_to_json.py b/tests/test_geojson_to_json.py index 359c89a..8e89650 100644 --- a/tests/test_geojson_to_json.py +++ b/tests/test_geojson_to_json.py @@ -39,6 +39,12 @@ def test_geojson_to_json(filename): "geojson_to_json", filename + ".expected.json", ) + meta_expected_filename = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "fixtures", + "geojson_to_json", + filename + ".meta.expected.json", + ) with open(nodes_filename) as fp: nodes_data = json.load(fp) @@ -51,3 +57,7 @@ def test_geojson_to_json(filename): with open(expected_filename) as fp: expected_data = json.load(fp) assert expected_data == converter.get_json() + + with open(meta_expected_filename) as fp: + meta_expected_data = json.load(fp) + assert meta_expected_data == converter.get_meta_json()