From 810a514e8654618f06e9fad546ece7e258cb30e8 Mon Sep 17 00:00:00 2001 From: Hugo Ledoux Date: Tue, 2 Jun 2020 15:38:33 +0200 Subject: [PATCH 01/26] Add skeleton for updating the metadata All code should be in that function in cityjson.py --- cjio/cityjson.py | 3 +++ cjio/cjio.py | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index d43a145..21ecc4d 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -1566,3 +1566,6 @@ def translate(self, values, minimum_xyz): self.set_epsg(None) self.update_bbox() return bbox + + def update_metadata(self): + self.update_bbox() \ No newline at end of file diff --git a/cjio/cjio.py b/cjio/cjio.py index 3b97489..76458a9 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -704,6 +704,19 @@ def processor(cm): return processor +@cli.command('update_metadata') +def update_metadata_cmd(): + """ + Update the metadata for properties/values that can be + computed. Updates the dataset. + """ + def processor(cm): + utils.print_cmd_status('Update the metadata') + cm.update_metadata() + return cm + return processor + + # Needed for the executable created by PyInstaller if getattr(sys, 'frozen', False): cli(sys.argv[1:]) From 23471c9c2dda19dcee20709f16d208e31494739c Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 26 Jun 2020 16:42:59 +0200 Subject: [PATCH 02/26] Add initial metadata functionality --- .gitignore | 3 + cjio/cityjson.py | 18 +++++- cjio/cjio.py | 6 +- cjio/metadata.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 cjio/metadata.py diff --git a/.gitignore b/.gitignore index 60bcfe5..b6444e3 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,6 @@ ENV/ # KDE .directory + +# VS Code +.vscode \ No newline at end of file diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 21ecc4d..099bb23 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -13,6 +13,7 @@ from io import StringIO from sys import platform from click import progressbar +from datetime import datetime MODULE_NUMPY_AVAILABLE = True MODULE_PYPROJ_AVAILABLE = True @@ -39,6 +40,7 @@ from cjio import validation, subset, geom_help, convert, models from cjio.errors import InvalidOperation from cjio.utils import print_cmd_warning +from cjio.metadata import generate_metadata CITYJSON_VERSIONS_SUPPORTED = ['0.6', '0.8', '0.9', '1.0'] @@ -205,10 +207,12 @@ def __init__(self, file=None, j=None, ignore_duplicate_keys=False): if file is not None: self.read(file, ignore_duplicate_keys) self.path = os.path.abspath(file.name) + self.reference_date = datetime.fromtimestamp(os.path.getmtime(file.name)).strftime('%Y-%m-%d') self.cityobjects = {} elif j is not None: self.j = j self.cityobjects = {} + self.reference_date = datetime.now() else: #-- create an empty one self.j = {} self.j["type"] = "CityJSON" @@ -216,6 +220,7 @@ def __init__(self, file=None, j=None, ignore_duplicate_keys=False): self.j["CityObjects"] = {} self.j["vertices"] = [] self.cityobjects = {} + self.reference_date = datetime.now() def __repr__(self): @@ -1566,6 +1571,15 @@ def translate(self, values, minimum_xyz): self.set_epsg(None) self.update_bbox() return bbox + + def get_metadata(self, overwrite=False): + return generate_metadata(self.j, self.path, self.reference_date, overwrite) - def update_metadata(self): - self.update_bbox() \ No newline at end of file + def update_metadata(self, overwrite=False): + self.update_bbox() + + metadata, errors = self.get_metadata(overwrite) + + self.j["metadata"] = metadata + + return (True, errors) \ No newline at end of file diff --git a/cjio/cjio.py b/cjio/cjio.py index 76458a9..bd153f2 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -712,7 +712,11 @@ def update_metadata_cmd(): """ def processor(cm): utils.print_cmd_status('Update the metadata') - cm.update_metadata() + re, errors = cm.update_metadata(False) + + for e in errors: + utils.print_cmd_warning(e) + return cm return processor diff --git a/cjio/metadata.py b/cjio/metadata.py new file mode 100644 index 0000000..4e74d80 --- /dev/null +++ b/cjio/metadata.py @@ -0,0 +1,159 @@ +"""Module containing metadata related functions""" + +import uuid +import json +import os +import time +import re +import collections, functools, operator +import sys +from datetime import date, datetime + +def generate_metadata(citymodel: dict, filename: str, reference_date: 'datetime', overwrite_values: bool = False): + """Returns a tuple containing a dictionary of the metadata and a list of errors. + + Keyword arguments: + citymodel -- Dictionary containing the city model + filename -- String with the name of the original file + overwrite_values -- Boolean that forces to overwrite existing values if True (default: False) + """ + + def citymodelIdentifier_func(): + return str(uuid.uuid4()) + + def datasetReferenceDate_func(): + return reference_date + + def distributionFormatVersion_func(): + return citymodel["version"] + + def fileIdentifier_func(): + return os.path.basename(filename) + + def geographicalExtent_func(): + x,y,z = zip(*citymodel["vertices"]) + ge = [min(x),min(y),min(z),max(x),max(y),max(z)] + if "transform" in citymodel: + s = citymodel["transform"]["scale"] + t = citymodel["transform"]["translate"] + ge = [a*b+c for a,b,c in zip(ge,(s+s),(t+t))] + return ge + + def metadataDateStamp_func(): + return str(date.today()) + + def is_present_in_appearance(k): + if "appearance" in citymodel: + if k in citymodel["appearance"]: + if len(citymodel["appearance"][k]) > 0: + if any(len(d) > 0 for d in citymodel["appearance"][k]): + return "present" + else: + return "absent" + + def textures_func(): + return is_present_in_appearance("textures") + + def materials_func(): + return is_present_in_appearance("materials") + + def cityfeatureMetadata_func(): + children = ("Part", "Installation", "ConstructionElement") + parent = lambda x: x[0:[match.start() for match in re.finditer ("[A-Z]", x)][1]] + + def LoD_func(): + presentLoDs = CityObjects_md[cm_type]["presentLoDs"] + for g in CityObjects[c_o]["geometry"]: + if "template" in g.keys(): + LoD = str(citymodel["geometry-templates"]["templates"][g["template"]]["lod"]) + else: + LoD = str(g["lod"]) + if LoD in presentLoDs: + presentLoDs[LoD] += 1 + else: + presentLoDs[LoD] = 1 + + CityObjects = citymodel["CityObjects"] + CityObjects_md = {} + + c_o_p = list(set([v["type"] for k,v in CityObjects.items()])) + c_o_c = [c_o_p.pop(c_o_p.index(x)) for x in c_o_p[:] if any(child in x for child in children)] + + for c in c_o_p: + CityObjects_md[c] = { + "uniqueFeatureCount":0, + "aggregateFeatureCount":0, + "presentLoDs":{} + } + if c == "TINRelief": + CityObjects_md[c]["triangleCount"] = 0 + elif c == "CityObjectGroup": + del CityObjects_md[c]["aggregateFeatureCount"] + + for c in c_o_c: CityObjects_md[parent(c)][c+"s"] = 0 + + for c_o in CityObjects: + cm_type = CityObjects[c_o]["type"] + if cm_type == "CityObjectGroup": + CityObjects_md[cm_type]["uniqueFeatureCount"] += 1 + LoD_func() + elif any(child in cm_type for child in children): + CityObjects_md[parent(cm_type)][cm_type+"s"] += 1 + else: + CityObjects_md[cm_type]["uniqueFeatureCount"] += 1 + CityObjects_md[cm_type]["aggregateFeatureCount"] += len(CityObjects[c_o]["geometry"]) + LoD_func() + if cm_type == "TINRelief": + CityObjects_md[cm_type]["triangleCount"] += sum([len(b) for g in CityObjects[c_o]["geometry"] for b in g["boundaries"]]) + return CityObjects_md + + def thematicModels_func(): + return [*metadata["cityfeatureMetadata"]] + + def presentLoDs_func(): + return dict(functools.reduce(operator.add, + map(collections.Counter, + [v["presentLoDs"] for k,v in metadata["cityfeatureMetadata"].items() if k != "CityObjectGroup"]))) + + md_dictionary = { + "datasetReferenceDate": datasetReferenceDate_func, + "datasetCharacterSet": "UTF-8", + "datasetTopicCategory": "geoscientificInformation", + "distributionFormatVersion": distributionFormatVersion_func, + "spatialRepresentationType": "vector", + "fileIdentifier": fileIdentifier_func, + "geographicalExtent": geographicalExtent_func, + "metadataStandard": "ISO 19115 - Geographic Information - Metadata", + "metadataStandardVersion": "ISO 19115:2014(E)", + "metadataCharacterSet": "UTF-8", + "metadataDateStamp": metadataDateStamp_func, + "textures": textures_func, + "materials": materials_func, + "cityfeatureMetadata": cityfeatureMetadata_func + } + + md_dependent_dictionary = { + "presentLoDs":presentLoDs_func, + "thematicModels":thematicModels_func + } + + error = lambda x: bad_list.append(x + " = " + str(sys.exc_info()[1]) + "\n") + def populate_metadata_dict(d): + def compute_item(key, value): + try: + metadata[key] = value if isinstance(v, str) else value() + except: + error(key) + pass + for k, v in d.items(): + if overwrite_values or k not in metadata: + compute_item(k, v) + + metadata = citymodel["metadata"] + if "citymodelIdentifier" not in metadata: + metadata["citymodelIdentifier"] = citymodelIdentifier_func() + bad_list = [] + populate_metadata_dict(md_dictionary) + populate_metadata_dict(md_dependent_dictionary) + + return metadata, bad_list \ No newline at end of file From 9cfac396a8c9ea0c817eaae457c501ec0f774920 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Thu, 9 Jul 2020 14:16:31 +0200 Subject: [PATCH 03/26] Add --overwrite option in update_metadata command --- cjio/cjio.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cjio/cjio.py b/cjio/cjio.py index bd153f2..d68331f 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -705,14 +705,15 @@ def processor(cm): @cli.command('update_metadata') -def update_metadata_cmd(): +@click.option('--overwrite', is_flag=True, help='Overwrite existing values.') +def update_metadata_cmd(overwrite): """ Update the metadata for properties/values that can be computed. Updates the dataset. """ def processor(cm): utils.print_cmd_status('Update the metadata') - re, errors = cm.update_metadata(False) + re, errors = cm.update_metadata(overwrite) for e in errors: utils.print_cmd_warning(e) From 4bd6e755fdeb239b32aad282bd8ba73f3024a33a Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Thu, 9 Jul 2020 14:16:58 +0200 Subject: [PATCH 04/26] Fix datasetReferenceDate formatting in metadata computation --- cjio/metadata.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cjio/metadata.py b/cjio/metadata.py index 4e74d80..dac03d0 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -8,6 +8,7 @@ import collections, functools, operator import sys from datetime import date, datetime +import platform def generate_metadata(citymodel: dict, filename: str, reference_date: 'datetime', overwrite_values: bool = False): """Returns a tuple containing a dictionary of the metadata and a list of errors. @@ -22,7 +23,21 @@ def citymodelIdentifier_func(): return str(uuid.uuid4()) def datasetReferenceDate_func(): - return reference_date + """ + Try to get the date that a file was created, falling back to when it was + last modified if that isn't possible. + See http://stackoverflow.com/a/39501288/1709587 for explanation. + """ + if platform.system() == 'Windows': + return str(date.fromtimestamp(os.path.getctime(filename))) + else: + stat = os.stat(filename) + try: + return str(date.fromtimestamp(stat.st_birthtime)) + except AttributeError: + # We're probably on Linux. No easy way to get creation dates here, + # so we'll settle for when its content was last modified. + return str(date.fromtimestamp(stat.st_mtime)) def distributionFormatVersion_func(): return citymodel["version"] From 001507b6899d9474965635795a9dfe6e4a79d23a Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Thu, 9 Jul 2020 14:26:57 +0200 Subject: [PATCH 05/26] Treat datasetReferenceDate like citymodelIdentifier in update_metadata If datasetReferenceDate is found inside the metadata, then we are not overwritting it anymore. --- cjio/metadata.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cjio/metadata.py b/cjio/metadata.py index dac03d0..1f70538 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -131,7 +131,6 @@ def presentLoDs_func(): [v["presentLoDs"] for k,v in metadata["cityfeatureMetadata"].items() if k != "CityObjectGroup"]))) md_dictionary = { - "datasetReferenceDate": datasetReferenceDate_func, "datasetCharacterSet": "UTF-8", "datasetTopicCategory": "geoscientificInformation", "distributionFormatVersion": distributionFormatVersion_func, @@ -167,8 +166,11 @@ def compute_item(key, value): metadata = citymodel["metadata"] if "citymodelIdentifier" not in metadata: metadata["citymodelIdentifier"] = citymodelIdentifier_func() + if "datasetReferenceDate" not in metadata: + metadata["datasetReferenceDate"] = datasetReferenceDate_func() + bad_list = [] populate_metadata_dict(md_dictionary) populate_metadata_dict(md_dependent_dictionary) - return metadata, bad_list \ No newline at end of file + return metadata, bad_list From df66ed5f38551bdb5599ffefd69122e7bd91b572 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Thu, 9 Jul 2020 14:37:55 +0200 Subject: [PATCH 06/26] Fix issue with discovery of textures/materials in update_metadata --- cjio/metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cjio/metadata.py b/cjio/metadata.py index 1f70538..1164f19 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -63,8 +63,8 @@ def is_present_in_appearance(k): if len(citymodel["appearance"][k]) > 0: if any(len(d) > 0 for d in citymodel["appearance"][k]): return "present" - else: - return "absent" + + return "absent" def textures_func(): return is_present_in_appearance("textures") From 822fd4034190a671d47ff774ec2a13488af74b6a Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 11:47:28 +0200 Subject: [PATCH 07/26] Remove the update_bbox command This is not useful anymore, since it's calculated by the update_metadata function. --- cjio/cjio.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cjio/cjio.py b/cjio/cjio.py index d68331f..34a2e37 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -313,20 +313,6 @@ def processor(cm): return cm return processor - -@cli.command('update_bbox') -def update_bbox_cmd(): - """ - Update the bbox of a CityJSON file. - If there is none then it is added. - """ - def processor(cm): - utils.print_cmd_status("Updating bbox") - cm.update_bbox() - return cm - return processor - - @cli.command('validate') @click.option('--hide_errors', is_flag=True, help='Do not print all the errors.') @click.option('--skip_schema', is_flag=True, help='Skip the schema validation (since it can be painfully slow).') From a1a548297abbc807d4532eb2d6ed8c4f7427c14b Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 12:43:12 +0200 Subject: [PATCH 08/26] Improve calculate_bbox function --- cjio/cityjson.py | 20 +++++++------------- tests/test_cityjson.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 099bb23..1db9f90 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -601,19 +601,12 @@ def get_bbox(self): def calculate_bbox(self): - bbox = [9e9, 9e9, 9e9, -9e9, -9e9, -9e9] - for v in self.j["vertices"]: - for i in range(3): - if v[i] < bbox[i]: - bbox[i] = v[i] - for i in range(3): - if v[i] > bbox[i+3]: - bbox[i+3] = v[i] + x, y, z = zip(*self.j["vertices"]) + bbox = [min(x), min(y), min(z), max(x), max(y), max(z)] if "transform" in self.j: - for i in range(3): - bbox[i] = (bbox[i] * self.j["transform"]["scale"][i]) + self.j["transform"]["translate"][i] - for i in range(3): - bbox[i+3] = (bbox[i+3] * self.j["transform"]["scale"][i]) + self.j["transform"]["translate"][i] + s = self.j["transform"]["scale"] + t = self.j["transform"]["translate"] + bbox = [a * b + c for a, b, c in zip(bbox, (s + s), (t + t))] return bbox @@ -1582,4 +1575,5 @@ def update_metadata(self, overwrite=False): self.j["metadata"] = metadata - return (True, errors) \ No newline at end of file + return (True, errors) + \ No newline at end of file diff --git a/tests/test_cityjson.py b/tests/test_cityjson.py index 77e00c6..279d2c6 100644 --- a/tests/test_cityjson.py +++ b/tests/test_cityjson.py @@ -2,7 +2,7 @@ """ import pytest -from cjio import cityjson,models +from cjio import cityjson, models @pytest.fixture(scope='module') def cm_zur_subset(zurich_subset): @@ -67,4 +67,34 @@ def test_get_children(self): """# TODO BD: Get all childeren of a CityObject""" def test_get_parents(self): - """# TODO BD: Get all parents of a CityObject""" \ No newline at end of file + """# TODO BD: Get all parents of a CityObject""" + + def test_calculate_bbox(self): + """Test the calculate_bbox function""" + + data = {"vertices": [ + [0, 0, 0], + [1, 1, 1] + ]} + + cm = cityjson.CityJSON(j=data) + bbox = cm.calculate_bbox() + + assert bbox == [0, 0, 0, 1, 1, 1] + + def test_calculate_bbox_with_transform(self): + """Test the calculate_bbox function""" + + data = {"vertices": [ + [0, 0, 0], + [1, 1, 1] + ], + "transform": { + "scale": [0.001, 0.001, 0.001], + "translate": [100, 100, 100] + }} + + cm = cityjson.CityJSON(j=data) + bbox = cm.calculate_bbox() + + assert bbox == [100, 100, 100, 100.001, 100.001, 100.001] From c57667a7f7b94babcd23741dae2b09862ef71848 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 12:48:03 +0200 Subject: [PATCH 09/26] Fix small things in metadata --- cjio/metadata.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cjio/metadata.py b/cjio/metadata.py index 1164f19..2093f3e 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -38,6 +38,8 @@ def datasetReferenceDate_func(): # We're probably on Linux. No easy way to get creation dates here, # so we'll settle for when its content was last modified. return str(date.fromtimestamp(stat.st_mtime)) + + return reference_dates def distributionFormatVersion_func(): return citymodel["version"] @@ -46,12 +48,12 @@ def fileIdentifier_func(): return os.path.basename(filename) def geographicalExtent_func(): - x,y,z = zip(*citymodel["vertices"]) - ge = [min(x),min(y),min(z),max(x),max(y),max(z)] + x, y, z = zip(*citymodel["vertices"]) + ge = [min(x), min(y), min(z), max(x), max(y), max(z)] if "transform" in citymodel: s = citymodel["transform"]["scale"] t = citymodel["transform"]["translate"] - ge = [a*b+c for a,b,c in zip(ge,(s+s),(t+t))] + ge = [a * b + c for a, b, c in zip(ge, (s + s), (t + t))] return ge def metadataDateStamp_func(): From a3d25b32ac1030b1d34011b29c1bd7c7121917f1 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 12:52:11 +0200 Subject: [PATCH 10/26] Add docstring in calculate_bbox --- cjio/cityjson.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 1db9f90..a03df22 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -601,6 +601,9 @@ def get_bbox(self): def calculate_bbox(self): + """ + Calculate the bbox of the CityJSON. + """ x, y, z = zip(*self.j["vertices"]) bbox = [min(x), min(y), min(z), max(x), max(y), max(z)] if "transform" in self.j: From c05299d80536eaf31653bfffb5510376d2714fb5 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 14:18:03 +0200 Subject: [PATCH 11/26] Rename metadata functions and update docstrings --- cjio/cityjson.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index a03df22..3d29d4f 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -1568,13 +1568,19 @@ def translate(self, values, minimum_xyz): self.update_bbox() return bbox - def get_metadata(self, overwrite=False): + def compute_metadata(self, overwrite=False): + """ + Returns the metadata of this CityJSON file + """ return generate_metadata(self.j, self.path, self.reference_date, overwrite) def update_metadata(self, overwrite=False): + """ + Computes and updates the "metadata" property of this CityJSON file + """ self.update_bbox() - metadata, errors = self.get_metadata(overwrite) + metadata, errors = self.compute_metadata(overwrite) self.j["metadata"] = metadata From d1c23754f02c6ebb8e146602845e3e2de0559ff5 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 14:34:00 +0200 Subject: [PATCH 12/26] Add get_metadata command --- cjio/cityjson.py | 16 ++++++++++++++++ cjio/cjio.py | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 3d29d4f..99f6113 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -1568,6 +1568,22 @@ def translate(self, values, minimum_xyz): self.update_bbox() return bbox + def has_metadata(self): + """ + Returns whether metadata exist in this CityJSON file or not + """ + return "metadata" in self.j + + def get_metadata(self): + """ + Returns the "metadata" property of this CityJSON file + + Raises a KeyError exception if metadata is missing + """ + if not "metadata" in self.j: + raise KeyError("Metadata is missing") + return self.j["metadata"] + def compute_metadata(self, overwrite=False): """ Returns the metadata of this CityJSON file diff --git a/cjio/cjio.py b/cjio/cjio.py index 34a2e37..77951f1 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -708,6 +708,25 @@ def processor(cm): return processor +@cli.command('get_metadata') +def get_metadata_cmd(): + """ + Shows the metadata of this dataset. + + The difference between 'info' and this command is that this + command lists the "pure" metadata as stored in the file. + The 'info' command should be used when an overview of the + file is needed. + """ + def processor(cm): + if cm.has_metadata(): + click.echo("=============== Metadata ===============") + click.echo_via_pager(json.dumps(cm.get_metadata(), indent=2)) + else: + utils.print_cmd_warning("You are missing metadata! Quickly! Run 'update_metadata' before it's too late!") + return processor + + # Needed for the executable created by PyInstaller if getattr(sys, 'frozen', False): cli(sys.argv[1:]) From 23f3e90d7dce420e900bc72f23c6d3138eee4ddb Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 15:17:39 +0200 Subject: [PATCH 13/26] Add function to add lineage items in CityJSON --- cjio/cityjson.py | 35 +++++++++++++++++++++++++++++++++++ tests/test_cityjson.py | 19 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 99f6113..f3f264d 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -1601,4 +1601,39 @@ def update_metadata(self, overwrite=False): self.j["metadata"] = metadata return (True, errors) + + def add_lineage_item(self, description: str, features: list = None, source: list = None, processor: dict = None): + """ + Adds a lineage item in metadata. + + description --- A string with the description of the process + features (optional) --- A list of object ids that are affected by it + source (optional) --- A list of sources. Every source is a dict with + the respective info (description, sourceReferenceSystem etc.) + processor (optional) --- A dict with contact information for the + person that conducted the processing + """ + + new_item = { + "processStep": { + "description": description + } + } + + if isinstance(features, list): + new_item["featureIDs"] = features + + if isinstance(source, list): + new_item["source"] = source + + if isinstance(processor, dict): + new_item["processStep"]["processor"] = processor + + if not self.has_metadata(): + self.j["metadata"] = {} + + if not "lineage" in self.j["metadata"]: + self.j["metadata"]["lineage"] = [] + + self.j["metadata"]["lineage"].append(new_item) \ No newline at end of file diff --git a/tests/test_cityjson.py b/tests/test_cityjson.py index 279d2c6..dd6860a 100644 --- a/tests/test_cityjson.py +++ b/tests/test_cityjson.py @@ -98,3 +98,22 @@ def test_calculate_bbox_with_transform(self): bbox = cm.calculate_bbox() assert bbox == [100, 100, 100, 100.001, 100.001, 100.001] + + def test_add_lineage_item(self): + """Test the add_lineage_item function""" + + test_desc = "We did something" + + cm = cityjson.CityJSON() + + cm.add_lineage_item(test_desc) + + assert cm.j["metadata"]["lineage"][0]["processStep"]["description"] == test_desc + + cm.add_lineage_item("Something else", features=["id1", "id2"], source=[{"description": "BAG"}], processor={"contactName": "3D geoinfo"}) + + item = cm.j["metadata"]["lineage"][1] + assert item["processStep"]["description"] == "Something else" + assert len(item["featureIDs"]) == 2 + assert len(item["source"]) == 1 + assert item["processStep"]["processor"]["contactName"] == "3D geoinfo" From 979802657c42d63bbdcd949a9b6f506736d21146 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 15:40:25 +0200 Subject: [PATCH 14/26] Add lineage creation in subset by bbox --- cjio/cityjson.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index f3f264d..bf7c2be 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -716,6 +716,21 @@ def recusionvisit(a, vs): return None + def get_identifier(self): + """ + Returns the identifier of this file. + + If there is one in metadata, it will be returned. Otherwise, the filename will. + """ + if "metadata" in self.j: + if "fileIdentifier" in self.j["metadata"]: + return self.j["metadata"]["fileIdentifier"] + + if self.path: + return os.path.basename(self.path) + + return "unknown" + def get_subset_bbox(self, bbox, exclude=False): # print ('get_subset_bbox') #-- new sliced CityJSON object @@ -757,9 +772,10 @@ def get_subset_bbox(self, bbox, exclude=False): cm2.j["appearance"] = {} subset.process_appearance(self.j, cm2.j) #-- metadata - if ("metadata" in self.j): - cm2.j["metadata"] = self.j["metadata"] - cm2.update_bbox() + cm2.update_metadata() + fids = [fid for fid in self.j["CityObjects"]] + cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) + return cm2 From af15139946eda6d76488560c91f731493886cdf4 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Fri, 24 Jul 2020 15:56:36 +0200 Subject: [PATCH 15/26] Add lineage item for every subset command --- cjio/cityjson.py | 33 +++++++++++++++++++++++---------- cjio/metadata.py | 10 ---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index bf7c2be..e495d67 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -772,9 +772,12 @@ def get_subset_bbox(self, bbox, exclude=False): cm2.j["appearance"] = {} subset.process_appearance(self.j, cm2.j) #-- metadata - cm2.update_metadata() - fids = [fid for fid in self.j["CityObjects"]] - cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) + try: + cm2.update_metadata() + fids = [fid for fid in cm2.j["CityObjects"]] + cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) + except: + print("Issue with metadata creation") return cm2 @@ -822,7 +825,12 @@ def get_subset_random(self, number=1, exclude=False): sallkeys = set(self.j["CityObjects"].keys()) re = sallkeys ^ re re = list(re) - return self.get_subset_ids(re) + cm = self.get_subset_ids(re) + try: + cm.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Random subset of {}".format(self.get_identifier()) + except: + print("Problem with metadata") + return cm def get_subset_ids(self, lsIDs, exclude=False): @@ -850,9 +858,12 @@ def get_subset_ids(self, lsIDs, exclude=False): cm2.j["appearance"] = {} subset.process_appearance(self.j, cm2.j) #-- metadata - if ("metadata" in self.j): - cm2.j["metadata"] = self.j["metadata"] - cm2.update_bbox() + try: + cm2.update_metadata() + fids = [fid for fid in cm2.j["CityObjects"]] + cm2.add_lineage_item("Subset of {} based on user specified IDs".format(self.get_identifier()), features=fids) + except: + print("Issue with metadata creation") return cm2 @@ -892,9 +903,11 @@ def get_subset_cotype(self, cotype, exclude=False): cm2.j["appearance"] = {} subset.process_appearance(self.j, cm2.j) #-- metadata - if ("metadata" in self.j): - cm2.j["metadata"] = self.j["metadata"] - cm2.update_bbox() + try: + cm2.update_metadata() + cm2.add_lineage_item("Subset of {} by object type {}".format(self.get_identifier(), cotype)) + except: + print("Issue with metadata creation") return cm2 diff --git a/cjio/metadata.py b/cjio/metadata.py index 2093f3e..1fe813a 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -47,15 +47,6 @@ def distributionFormatVersion_func(): def fileIdentifier_func(): return os.path.basename(filename) - def geographicalExtent_func(): - x, y, z = zip(*citymodel["vertices"]) - ge = [min(x), min(y), min(z), max(x), max(y), max(z)] - if "transform" in citymodel: - s = citymodel["transform"]["scale"] - t = citymodel["transform"]["translate"] - ge = [a * b + c for a, b, c in zip(ge, (s + s), (t + t))] - return ge - def metadataDateStamp_func(): return str(date.today()) @@ -138,7 +129,6 @@ def presentLoDs_func(): "distributionFormatVersion": distributionFormatVersion_func, "spatialRepresentationType": "vector", "fileIdentifier": fileIdentifier_func, - "geographicalExtent": geographicalExtent_func, "metadataStandard": "ISO 19115 - Geographic Information - Metadata", "metadataStandardVersion": "ISO 19115:2014(E)", "metadataCharacterSet": "UTF-8", From 169c471ea4d22f8c6e35407439b6ee920e25378a Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 11:50:47 +0200 Subject: [PATCH 16/26] Fix issue with integer coords in remove_duplicate_vertices --- cjio/cityjson.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index dfd98d8..18c09f2 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -1133,6 +1133,8 @@ def update_face(a, oldnewids): def remove_duplicate_vertices(self, precision=3): + if "transform" in self.j: + precision = 0 def update_geom_indices(a, newids): for i, each in enumerate(a): if isinstance(each, list): From 79faa17896c4cd57a8a454d7b99e6affb633edaf Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 12:02:20 +0200 Subject: [PATCH 17/26] Fix issue with calculate_bbox calculation It was broken when the vertices list was empty --- cjio/cityjson.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 7cec27d..3efd3d2 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -612,6 +612,8 @@ def calculate_bbox(self): """ Calculate the bbox of the CityJSON. """ + if len(self.j["vertices"]) == 0: + return [0, 0, 0, 0, 0, 0] x, y, z = zip(*self.j["vertices"]) bbox = [min(x), min(y), min(z), max(x), max(y), max(z)] if "transform" in self.j: From d0366c7d96c17de39f44d067aa0dab981e36a03e Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 12:39:23 +0200 Subject: [PATCH 18/26] Add lineage in extract_lod command Also make the identifier in lineage description more descriptive. --- cjio/cityjson.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 3efd3d2..fde6610 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -733,14 +733,24 @@ def get_identifier(self): If there is one in metadata, it will be returned. Otherwise, the filename will. """ if "metadata" in self.j: - if "fileIdentifier" in self.j["metadata"]: - return self.j["metadata"]["fileIdentifier"] + if "citymodelIdentifier" in self.j["metadata"]: + cm_id = self.j["metadata"]["citymodelIdentifier"] + if cm_id: + template = "{cm_id} ({file_id})" + else: + template = "{file_id}" + + if "metadata" in self.j: + if "fileIdentifier" in self.j["metadata"]: + return template.format(cm_id=cm_id, file_id=self.j["metadata"]["fileIdentifier"]) + if self.path: return os.path.basename(self.path) return "unknown" + def get_subset_bbox(self, bbox, exclude=False): # print ('get_subset_bbox') #-- new sliced CityJSON object @@ -1167,6 +1177,7 @@ def update_face(a, oldnewids): def remove_duplicate_vertices(self, precision=3): if "transform" in self.j: precision = 0 + def update_geom_indices(a, newids): for i, each in enumerate(a): if isinstance(each, list): @@ -1587,7 +1598,14 @@ def extract_lod(self, thelod): for each in re: self.j['CityObjects'][co]['geometry'].remove(each) self.remove_duplicate_vertices() - self.remove_orphan_vertices() + self.remove_orphan_vertices() + #-- metadata + try: + self.update_metadata(overwrite=True) + fids = [fid for fid in self.j["CityObjects"]] + self.add_lineage_item("Extract LoD{} from {}".format(thelod, self.get_identifier()), features=fids) + except: + print("Issue with metadata creation") def translate(self, values, minimum_xyz): From 0216335ded3c85f2f682b77956077675fdda1355 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 12:46:22 +0200 Subject: [PATCH 19/26] Add date to lineage item creation --- cjio/cityjson.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index fde6610..24d67de 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -13,7 +13,7 @@ from io import StringIO from sys import platform from click import progressbar -from datetime import datetime +from datetime import datetime, date MODULE_NUMPY_AVAILABLE = True MODULE_PYPROJ_AVAILABLE = True @@ -1677,7 +1677,8 @@ def add_lineage_item(self, description: str, features: list = None, source: list new_item = { "processStep": { - "description": description + "description": description, + "stepDateTime": str(date.today()) } } From 46c2cbf88caa8591537ad2ec3c5264ec1a0ee734 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 12:53:19 +0200 Subject: [PATCH 20/26] Update fileIdentifier in metadata when saving a CityJSON file --- cjio/cjio.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cjio/cjio.py b/cjio/cjio.py index 4dfd601..8d8518d 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -289,6 +289,12 @@ def saver(cm): f=input_filename, ext='json')) else: os.makedirs(os.path.dirname(output['path']), exist_ok=True) + + try: + if "metadata" in cm.j: + cm.j["metadata"]["fileIdentifier"] = os.path.basename(output['path']) + except: + pass utils.print_cmd_status("Saving CityJSON to a file %s" % output['path']) try: From 22d0854ddd0abcb270721cd9292293e8d2753f29 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 12:54:42 +0200 Subject: [PATCH 21/26] Force metadata overwrite in all commands with lineage --- cjio/cityjson.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 24d67de..e913cc8 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -793,7 +793,7 @@ def get_subset_bbox(self, bbox, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata() + cm2.update_metadata(overwrite=True) fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) except: @@ -879,7 +879,7 @@ def get_subset_ids(self, lsIDs, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata() + cm2.update_metadata(overwrite=True) fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} based on user specified IDs".format(self.get_identifier()), features=fids) except: @@ -924,7 +924,7 @@ def get_subset_cotype(self, cotype, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata() + cm2.update_metadata(overwrite=True) cm2.add_lineage_item("Subset of {} by object type {}".format(self.get_identifier(), cotype)) except: print("Issue with metadata creation") From 6bd12864a9d84627ea839be9e5cfe80db3e5bdd2 Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 13:07:20 +0200 Subject: [PATCH 22/26] Add metadata and lineage creation in partition command --- cjio/cjio.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cjio/cjio.py b/cjio/cjio.py index 8d8518d..738be63 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -435,6 +435,11 @@ def processor(cm): input_filename = os.path.splitext(os.path.basename(cm.path))[0] for idx, colist in partitions.items(): s = cm.get_subset_ids(colist) + try: + s.update_metadata() + s.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Partition {}/{} of {}".format(idx, len(partitions), s.get_identifier()) + except: + pass filename = '{}_{}.json'.format(input_filename, idx) s.path = filename cms.append(s) From 51b020bf40278d3f84de4eddf29f01fa5a6790ba Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 13:33:48 +0200 Subject: [PATCH 23/26] Add lineage and metadata creation in merge command --- cjio/cityjson.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index e913cc8..0d1c493 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -751,6 +751,19 @@ def get_identifier(self): return "unknown" + def get_title(self): + """ + Returns the description of this file from metadata. + + If there is none, the identifier will be returned, instead. + """ + + if "metadata" in self.j: + if "datasetTitle" in self.j["metadata"]: + return self.j["metadata"]["datasetTitle"] + + return self.get_identifier() + def get_subset_bbox(self, bbox, exclude=False): # print ('get_subset_bbox') #-- new sliced CityJSON object @@ -1288,6 +1301,12 @@ def update_texture_indices(a, toffset, voffset): a[i] = each + voffset #-- decompress current CM self.decompress() + + #-- metadata + try: + self.update_metadata(overwrite=True) + except: + print("Issue with metadata creation") for cm in lsCMs: #-- decompress cm.decompress() @@ -1385,6 +1404,16 @@ def update_texture_indices(a, toffset, voffset): if 'texture' in g: for m in g['texture']: update_texture_indices(g['texture'][m]['values'], toffset, voffset) + #-- metadata + try: + fids = [fid for fid in cm.j["CityObjects"]] + src = { + "description": cm.get_title(), + "sourceReferenceSystem": "urn:ogc:def:crs:EPSG::{}".format(cm.get_epsg()) if cm.get_epsg() else None + } + self.add_lineage_item("Merge {} into {}".format(cm.get_identifier(), self.get_identifier()), features=fids, source=[src]) + except: + pass # self.remove_duplicate_vertices() # self.remove_orphan_vertices() return True From 19dffec0d6476e5a39e73c092b94cfa9a04909df Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 13:35:59 +0200 Subject: [PATCH 24/26] Cleanup code regarding lineage/metadata creation in commands --- cjio/cityjson.py | 12 ++++++------ cjio/cjio.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index 0d1c493..fbfc64a 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -810,7 +810,7 @@ def get_subset_bbox(self, bbox, exclude=False): fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) except: - print("Issue with metadata creation") + pass return cm2 @@ -862,7 +862,7 @@ def get_subset_random(self, number=1, exclude=False): try: cm.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Random subset of {}".format(self.get_identifier()) except: - print("Problem with metadata") + pass return cm @@ -896,7 +896,7 @@ def get_subset_ids(self, lsIDs, exclude=False): fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} based on user specified IDs".format(self.get_identifier()), features=fids) except: - print("Issue with metadata creation") + pass return cm2 @@ -940,7 +940,7 @@ def get_subset_cotype(self, cotype, exclude=False): cm2.update_metadata(overwrite=True) cm2.add_lineage_item("Subset of {} by object type {}".format(self.get_identifier(), cotype)) except: - print("Issue with metadata creation") + pass return cm2 @@ -1306,7 +1306,7 @@ def update_texture_indices(a, toffset, voffset): try: self.update_metadata(overwrite=True) except: - print("Issue with metadata creation") + pass for cm in lsCMs: #-- decompress cm.decompress() @@ -1634,7 +1634,7 @@ def extract_lod(self, thelod): fids = [fid for fid in self.j["CityObjects"]] self.add_lineage_item("Extract LoD{} from {}".format(thelod, self.get_identifier()), features=fids) except: - print("Issue with metadata creation") + pass def translate(self, values, minimum_xyz): diff --git a/cjio/cjio.py b/cjio/cjio.py index 738be63..05a0e7e 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -437,7 +437,7 @@ def processor(cm): s = cm.get_subset_ids(colist) try: s.update_metadata() - s.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Partition {}/{} of {}".format(idx, len(partitions), s.get_identifier()) + s.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Partition {}/{} of {}".format(idx + 1, len(partitions), s.get_identifier()) except: pass filename = '{}_{}.json'.format(input_filename, idx) From 93d08c77849b4215511b5f6aaa6fbf4017a1272a Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 14:02:49 +0200 Subject: [PATCH 25/26] Make subset by ids to inherit metadata (fixes #55) --- cjio/cityjson.py | 15 ++++++++------- cjio/cjio.py | 1 - cjio/metadata.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index fbfc64a..f1d9039 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -806,7 +806,7 @@ def get_subset_bbox(self, bbox, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata(overwrite=True) + cm2.update_metadata(overwrite=True, new_uuid=True) fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) except: @@ -892,7 +892,8 @@ def get_subset_ids(self, lsIDs, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata(overwrite=True) + cm2.j["metadata"] = copy.deepcopy(self.j["metadata"]) + cm2.update_metadata(overwrite=True, new_uuid=True) fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} based on user specified IDs".format(self.get_identifier()), features=fids) except: @@ -937,7 +938,7 @@ def get_subset_cotype(self, cotype, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: - cm2.update_metadata(overwrite=True) + cm2.update_metadata(overwrite=True, new_uuid=True) cm2.add_lineage_item("Subset of {} by object type {}".format(self.get_identifier(), cotype)) except: pass @@ -1674,19 +1675,19 @@ def get_metadata(self): raise KeyError("Metadata is missing") return self.j["metadata"] - def compute_metadata(self, overwrite=False): + def compute_metadata(self, overwrite=False, new_uuid=False): """ Returns the metadata of this CityJSON file """ - return generate_metadata(self.j, self.path, self.reference_date, overwrite) + return generate_metadata(self.j, self.path, self.reference_date, overwrite, new_uuid) - def update_metadata(self, overwrite=False): + def update_metadata(self, overwrite=False, new_uuid=False): """ Computes and updates the "metadata" property of this CityJSON file """ self.update_bbox() - metadata, errors = self.compute_metadata(overwrite) + metadata, errors = self.compute_metadata(overwrite, new_uuid) self.j["metadata"] = metadata diff --git a/cjio/cjio.py b/cjio/cjio.py index 05a0e7e..e0959f8 100755 --- a/cjio/cjio.py +++ b/cjio/cjio.py @@ -436,7 +436,6 @@ def processor(cm): for idx, colist in partitions.items(): s = cm.get_subset_ids(colist) try: - s.update_metadata() s.j["metadata"]["lineage"][-1]["processStep"]["description"] = "Partition {}/{} of {}".format(idx + 1, len(partitions), s.get_identifier()) except: pass diff --git a/cjio/metadata.py b/cjio/metadata.py index 1fe813a..9611029 100644 --- a/cjio/metadata.py +++ b/cjio/metadata.py @@ -10,7 +10,7 @@ from datetime import date, datetime import platform -def generate_metadata(citymodel: dict, filename: str, reference_date: 'datetime', overwrite_values: bool = False): +def generate_metadata(citymodel: dict, filename: str, reference_date: 'datetime', overwrite_values: bool = False, recompute_uuid: bool = False): """Returns a tuple containing a dictionary of the metadata and a list of errors. Keyword arguments: @@ -156,7 +156,7 @@ def compute_item(key, value): compute_item(k, v) metadata = citymodel["metadata"] - if "citymodelIdentifier" not in metadata: + if ("citymodelIdentifier" not in metadata) or recompute_uuid: metadata["citymodelIdentifier"] = citymodelIdentifier_func() if "datasetReferenceDate" not in metadata: metadata["datasetReferenceDate"] = datasetReferenceDate_func() From e984e5a1351c24ba17add37b863bfd25915b753e Mon Sep 17 00:00:00 2001 From: Anna Labetski Date: Mon, 27 Jul 2020 14:56:19 +0200 Subject: [PATCH 26/26] Make all subset commands recompute the citymodelIdentifier --- cjio/cityjson.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cjio/cityjson.py b/cjio/cityjson.py index f1d9039..0bc5abc 100644 --- a/cjio/cityjson.py +++ b/cjio/cityjson.py @@ -806,6 +806,7 @@ def get_subset_bbox(self, bbox, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: + cm2.j["metadata"] = copy.deepcopy(self.j["metadata"]) cm2.update_metadata(overwrite=True, new_uuid=True) fids = [fid for fid in cm2.j["CityObjects"]] cm2.add_lineage_item("Subset of {} by bounding box {}".format(self.get_identifier(), bbox), features=fids) @@ -938,6 +939,7 @@ def get_subset_cotype(self, cotype, exclude=False): subset.process_appearance(self.j, cm2.j) #-- metadata try: + cm2.j["metadata"] = copy.deepcopy(self.j["metadata"]) cm2.update_metadata(overwrite=True, new_uuid=True) cm2.add_lineage_item("Subset of {} by object type {}".format(self.get_identifier(), cotype)) except: