From 0ea64f70ad9c2148ff03b4634d4195cdd8fc6840 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 14 Jan 2025 13:39:38 -0700 Subject: [PATCH 01/11] put uo-des geojson methods into main gmt geojson class --- .../geojson/urbanopt_geojson.py | 159 +++++++++++++++--- 1 file changed, 136 insertions(+), 23 deletions(-) diff --git a/geojson_modelica_translator/geojson/urbanopt_geojson.py b/geojson_modelica_translator/geojson/urbanopt_geojson.py index b7812e19b..4a0ca64b0 100644 --- a/geojson_modelica_translator/geojson/urbanopt_geojson.py +++ b/geojson_modelica_translator/geojson/urbanopt_geojson.py @@ -1,6 +1,7 @@ # :copyright (c) URBANopt, Alliance for Sustainable Energy, LLC, and other contributors. # See also https://github.com/urbanopt/geojson-modelica-translator/blob/develop/LICENSE.md +import json import logging from pathlib import Path @@ -8,33 +9,11 @@ from jsonpath_ng.ext import parse from geojson_modelica_translator.geojson.schemas import Schemas +from geojson_modelica_translator.geojson.urbanopt_load import GeoJsonValidationError, UrbanOptLoad _log = logging.getLogger(__name__) -class GeoJsonValidationError(Exception): - pass - - -# TODO: Inherit from GeoJSON Feature class, move to its own file -class UrbanOptLoad: - """An UrbanOptLoad is a container for holding Building-related data in a dictionary. This object - does not do much work on the GeoJSON definition of the data at the moment, rather it creates - an isolation layer between the GeoJSON data and the GMT. - """ - - def __init__(self, feature): - self.feature = feature - self.id = feature.get("properties", {}).get("id", None) - - # do some validation - if self.id is None: - raise GeoJsonValidationError("GeoJSON feature requires an ID property but value was null") - - def __str__(self): - return f"ID: {self.id}" - - class UrbanOptGeoJson: """Root class for parsing an URBANopt GeoJSON file. This class simply reads and parses URBANopt GeoJSON files. @@ -122,3 +101,137 @@ def get_feature(self, jsonpath): # otherwise return the list of values return results + + def get_building_paths(self, scenario_name: str) -> list[Path]: + """Return a list of Path objects for the building GeoJSON files""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building": + building_path = self._filename.parent / "run" / scenario_name / feature["properties"]["id"] + result.append(building_path) + # result.append(Path(feature["properties"]["file"])) + + # verify that the paths exist + for path in result: + if not path.exists(): + raise FileNotFoundError(f"File not found: {path}") + + return result + + def get_building_ids(self) -> list: + """Return a list of building names""" + result = [] + for feature in self.data["features"]: + if "type" in feature["properties"] and feature["properties"]["type"] == "Building": + result.append(feature["properties"]["id"]) + elif "name" in feature["properties"] and feature["properties"]["name"] == "Site Origin": + pass + else: + # need to implement a reasonable logger. + pass + # print(f"Feature does not have a type Building: {feature}") + # print("Did you forget to call the `update_geojson_from_seed_data` method?") + + return result + + def get_building_names(self) -> list: + """Return a list of building names. Typically this field is only used for visual display name only.""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building": + result.append(feature["properties"]["name"]) + + return result + + def get_buildings(self, ids: list[str] | None = None) -> list: + """Return a list of all the properties of type Building""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and (ids is None or feature["properties"]["id"] in ids): + # TODO: eventually add a list of building ids to keep, for now it + # will be all buildings. + result.append(feature) + + return result + + def get_building_properties_by_id(self, building_id: str) -> dict: + """Get the list of building ids in the GeoJSON file. The Building id is what + is used in URBANopt as the identifier. It is common that this is used to name + the building, more than the GeoJSON's building name field. + + Args: + building_id (str): building id, this is the property.id values in the geojson's feature + + Returns: + dict: building properties + """ + result = {} + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: + result = feature["properties"] + + return result + + def get_meters_for_building(self, building_id: str) -> list: + """Return a list of meters for the building_id""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: + for meter in feature["properties"].get("meters", []): + result.append(meter["type"]) + + return result + + def get_meter_readings_for_building(self, building_id: str, meter_type: str) -> list: + """Return a list of meter readings for the building_id""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: + for meter in feature["properties"].get("meters", []): + if meter["type"] == meter_type: + result = meter["readings"] + + return result + + def get_monthly_readings(self, building_id: str, meter_type: str) -> list: + """Return a list of monthly electricity consumption for the building_id""" + result = [] + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: + result = feature["properties"]["monthly_electricity"] + + return result + + def set_property_on_building_id( + self, building_id: str, property_name: str, property_value: str, overwrite=True + ) -> None: + """Set a property on a building_id""" + for feature in self.data["features"]: + if ( + feature["properties"]["type"] == "Building" + and feature["properties"]["id"] == building_id + and (overwrite or property_name not in feature["properties"]) + ): + feature["properties"][property_name] = property_value + + def get_property_on_building_id(self, building_id: str, property_name: str) -> str: + """Get a property on a building_id""" + for feature in self.data["features"]: + if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: + return feature["properties"].get(property_name, None) + + def get_site_lat_lon(self) -> tuple: + """Return the site's latitude and longitude""" + for feature in self.data["features"]: + if feature["properties"]["name"] == "Site Origin": + # reverse the order of the coordinates + return feature["geometry"]["coordinates"][::-1] + + def save(self) -> None: + """Save the GeoJSON file""" + self.save_as(self._filename) + + def save_as(self, filename: Path) -> None: + """Save the GeoJSON file""" + with open(filename, "w") as f: + json.dump(self.data, f, indent=2) From 3577b8b1d71f1857e263dc4573c90237199ad689 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 14 Jan 2025 13:40:01 -0700 Subject: [PATCH 02/11] break out urbanopt_load class to its own file --- .../geojson/urbanopt_load.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 geojson_modelica_translator/geojson/urbanopt_load.py diff --git a/geojson_modelica_translator/geojson/urbanopt_load.py b/geojson_modelica_translator/geojson/urbanopt_load.py new file mode 100644 index 000000000..ff394547d --- /dev/null +++ b/geojson_modelica_translator/geojson/urbanopt_load.py @@ -0,0 +1,20 @@ +class GeoJsonValidationError(Exception): + pass + + +class UrbanOptLoad: + """An UrbanOptLoad is a container for holding Building-related data in a dictionary. This object + does not do much work on the GeoJSON definition of the data at the moment, rather it creates + an isolation layer between the GeoJSON data and the GMT. + """ + + def __init__(self, feature): + self.feature = feature + self.id = feature.get("properties", {}).get("id", None) + + # do some validation + if self.id is None: + raise GeoJsonValidationError("GeoJSON feature requires an ID property but value was null") + + def __str__(self): + return f"ID: {self.id}" From 9c53799b60909c5db42c967edbed8c967b06bfe3 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 14 Jan 2025 14:05:10 -0700 Subject: [PATCH 03/11] add types and returns for mypy --- geojson_modelica_translator/geojson/urbanopt_geojson.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/geojson_modelica_translator/geojson/urbanopt_geojson.py b/geojson_modelica_translator/geojson/urbanopt_geojson.py index 4a0ca64b0..4e2b6c135 100644 --- a/geojson_modelica_translator/geojson/urbanopt_geojson.py +++ b/geojson_modelica_translator/geojson/urbanopt_geojson.py @@ -26,6 +26,8 @@ def __init__(self, filename, building_ids=None, skip_validation=False): :param building_ids: list[str | int] | None, optional, list of GeoJSON building IDs to parse from the file. If None or an empty list, parse all buildings. """ + + self._filename = Path(filename).resolve() if not Path(filename).exists(): raise GeoJsonValidationError(f"URBANopt GeoJSON file does not exist: {filename}") @@ -102,6 +104,7 @@ def get_feature(self, jsonpath): # otherwise return the list of values return results + # TODO: test the following methods def get_building_paths(self, scenario_name: str) -> list[Path]: """Return a list of Path objects for the building GeoJSON files""" result = [] @@ -214,18 +217,20 @@ def set_property_on_building_id( ): feature["properties"][property_name] = property_value - def get_property_on_building_id(self, building_id: str, property_name: str) -> str: + def get_property_on_building_id(self, building_id: str, property_name: str) -> str | None: """Get a property on a building_id""" for feature in self.data["features"]: if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: return feature["properties"].get(property_name, None) + return None - def get_site_lat_lon(self) -> tuple: + def get_site_lat_lon(self) -> tuple | None: """Return the site's latitude and longitude""" for feature in self.data["features"]: if feature["properties"]["name"] == "Site Origin": # reverse the order of the coordinates return feature["geometry"]["coordinates"][::-1] + return None def save(self) -> None: """Save the GeoJSON file""" From 6bd7ca6bb0a79742c40bec89bc5f8111d9f9121b Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 14 Jan 2025 14:08:15 -0700 Subject: [PATCH 04/11] remove what mypy said was unnecessary type hinting --- geojson_modelica_translator/model_connectors/model_base.py | 2 +- tests/model_connectors/test_district_multi_ghe.py | 2 +- tests/model_connectors/test_district_single_ghe.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/geojson_modelica_translator/model_connectors/model_base.py b/geojson_modelica_translator/model_connectors/model_base.py index d085b130e..810904f75 100644 --- a/geojson_modelica_translator/model_connectors/model_base.py +++ b/geojson_modelica_translator/model_connectors/model_base.py @@ -62,7 +62,7 @@ def __init__(self, system_parameters, template_dir): } # Get access to loop order output from ThermalNetwork package. if "fifth_generation" in district_params and "ghe_parameters" in district_params["fifth_generation"]: - self.loop_order: list = load_loop_order(self.system_parameters.filename) + self.loop_order = load_loop_order(self.system_parameters.filename) def ft2_to_m2(self, area_in_ft2: float) -> float: """Converts square feet to square meters diff --git a/tests/model_connectors/test_district_multi_ghe.py b/tests/model_connectors/test_district_multi_ghe.py index cdf21302b..44e4df35f 100644 --- a/tests/model_connectors/test_district_multi_ghe.py +++ b/tests/model_connectors/test_district_multi_ghe.py @@ -43,7 +43,7 @@ def setUp(self): sys_params = SystemParameters(sys_param_filename) # read the loop order and create building groups - loop_order: list = load_loop_order(sys_param_filename) + loop_order = load_loop_order(sys_param_filename) # create ambient water stub ambient_water_stub = NetworkDistributionPump(sys_params) diff --git a/tests/model_connectors/test_district_single_ghe.py b/tests/model_connectors/test_district_single_ghe.py index 21999e1b3..fffe4695b 100644 --- a/tests/model_connectors/test_district_single_ghe.py +++ b/tests/model_connectors/test_district_single_ghe.py @@ -36,7 +36,7 @@ def setUp(self): sys_params = SystemParameters(sys_param_filename) # read the loop order and create building groups - loop_order: list = load_loop_order(sys_param_filename) + loop_order = load_loop_order(sys_param_filename) # create ambient water loop stub ambient_water_stub = NetworkDistributionPump(sys_params) From 1a87d987df96b4336d4828eb73ab0b5e6511134a Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 11:08:00 -0700 Subject: [PATCH 05/11] add error catching in geojson class --- geojson_modelica_translator/geojson/urbanopt_geojson.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/geojson_modelica_translator/geojson/urbanopt_geojson.py b/geojson_modelica_translator/geojson/urbanopt_geojson.py index 4e2b6c135..1f8381007 100644 --- a/geojson_modelica_translator/geojson/urbanopt_geojson.py +++ b/geojson_modelica_translator/geojson/urbanopt_geojson.py @@ -43,7 +43,7 @@ def __init__(self, filename, building_ids=None, skip_validation=False): if feature["properties"]["type"] == "Building": building = UrbanOptLoad(feature) if not building_ids or building.id in building_ids: - # Ignore validation failures for features with 'detailed_model_filename' in the properties + # Do not attempt validation for features with 'detailed_model_filename' in the properties # Buildings defined by an osm don't have all keys in geojson, therefore will always fail validation if "detailed_model_filename" not in feature["properties"]: errors = self.schemas.validate("building", building.feature.properties) @@ -78,6 +78,8 @@ def get_feature_by_id(self, feature_id=None): for feature in self.data.features: if feature["properties"]["id"] == str(feature_id): return feature + if feature_id not in self.data.features: + raise KeyError(f"No matches found for id {feature_id}") def get_feature(self, jsonpath): """Return the parameter(s) from a jsonpath. @@ -99,7 +101,7 @@ def get_feature(self, jsonpath): # If only one value, then return that value and not a list of values results = results[0] elif len(results) == 0: - return print(f"No matches found for jsonpath {jsonpath}") + raise KeyError(f"No matches found for jsonpath {jsonpath}") # otherwise return the list of values return results From 48d807e9e47068da304763d3e746f2d6acaad1a0 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 11:08:32 -0700 Subject: [PATCH 06/11] WIP tests for methods moved into geojson class from uo-des repo --- tests/geojson/test_geojson.py | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/geojson/test_geojson.py b/tests/geojson/test_geojson.py index 221edb55c..4b265e601 100644 --- a/tests/geojson/test_geojson.py +++ b/tests/geojson/test_geojson.py @@ -36,3 +36,68 @@ def test_validate(self): filename = self.data_dir / "geojson_1_invalid.json" with pytest.raises(GeoJsonValidationError, match="is not valid under any of the given schemas"): UrbanOptGeoJson(filename) + + def test_get_all_features(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + feature_properties = json.get_feature("$.features.[*].properties") + assert len(feature_properties) == 4 + # Check that the first feature has the expected properties + assert feature_properties[0]["floor_height"] == 9 + + def test_get_feature(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + feature = json.get_feature("$.features[1]") + assert feature["properties"]["floor_height"] == 3 + + def test_get_feature_invalid(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(KeyError, match="No matches found"): + json.get_feature("$.features[4]") + + def test_get_feature_by_id(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + feature = json.get_feature_by_id("5a7229e737f4de77124f946d") + assert feature["properties"]["footprint_area"] == 8612 + + def test_get_feature_by_id_invalid(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(KeyError, match="No matches found"): + json.get_feature_by_id("non-existent-id") + + def test_get_feature_by_id_missing(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(SystemExit): + json.get_feature_by_id() + + def test_get_building_paths(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + building_paths = json.get_building_paths(scenario_name="baseline_test") + assert len(building_paths) == 3 + # Check that the building paths end with the dir of the building_id + assert building_paths[0].stem == "5a6b99ec37f4de7f94020090" + assert building_paths[1].stem == "5a72287837f4de77124f946a" + assert building_paths[2].stem == "5a7229e737f4de77124f946d" + # Check that the correct error is raised if the path doesn't exist + with pytest.raises(FileNotFoundError, match="File not found"): + json.get_building_paths(scenario_name="baseline") + + def test_get_building_ids(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + building_names = json.get_building_names() + assert len(building_names) == 3 + assert building_names[0] == "Medium Office" + + def test_get_buildings(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + buildings = json.get_buildings(ids=None) + assert len(buildings) == 3 + assert buildings[3]["properties"]["floor_area"] == 34448 From cf92d5b2b891a4f6ea8d89f8aa118ffb98fe890a Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 11:57:27 -0700 Subject: [PATCH 07/11] add logging, errors, and a docstring comment to geojson class --- .../geojson/urbanopt_geojson.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/geojson_modelica_translator/geojson/urbanopt_geojson.py b/geojson_modelica_translator/geojson/urbanopt_geojson.py index 1f8381007..42baae331 100644 --- a/geojson_modelica_translator/geojson/urbanopt_geojson.py +++ b/geojson_modelica_translator/geojson/urbanopt_geojson.py @@ -185,6 +185,8 @@ def get_meters_for_building(self, building_id: str) -> list: for meter in feature["properties"].get("meters", []): result.append(meter["type"]) + if not result: + raise KeyError(f"No meters found for building {building_id}") return result def get_meter_readings_for_building(self, building_id: str, meter_type: str) -> list: @@ -196,21 +198,31 @@ def get_meter_readings_for_building(self, building_id: str, meter_type: str) -> if meter["type"] == meter_type: result = meter["readings"] + if not result: + raise KeyError(f"No meter readings found for building {building_id}") return result - def get_monthly_readings(self, building_id: str, meter_type: str) -> list: + def get_monthly_readings(self, building_id: str, meter_type: str = "Electricity") -> list: """Return a list of monthly electricity consumption for the building_id""" result = [] for feature in self.data["features"]: - if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: - result = feature["properties"]["monthly_electricity"] + if ( + feature["properties"]["type"] == "Building" + and feature["properties"]["id"] == building_id + and meter_type == "Electricity" + ): + result = feature["properties"].get("monthly_electricity") + if not result: + raise KeyError(f"No monthly readings found for building {building_id}") return result def set_property_on_building_id( self, building_id: str, property_name: str, property_value: str, overwrite=True ) -> None: - """Set a property on a building_id""" + """Set a property on a building_id. + + Note this method does not change the GeoJSON file, it only changes the in-memory data.""" for feature in self.data["features"]: if ( feature["properties"]["type"] == "Building" @@ -219,7 +231,7 @@ def set_property_on_building_id( ): feature["properties"][property_name] = property_value - def get_property_on_building_id(self, building_id: str, property_name: str) -> str | None: + def get_property_by_building_id(self, building_id: str, property_name: str) -> str | None: """Get a property on a building_id""" for feature in self.data["features"]: if feature["properties"]["type"] == "Building" and feature["properties"]["id"] == building_id: @@ -232,6 +244,7 @@ def get_site_lat_lon(self) -> tuple | None: if feature["properties"]["name"] == "Site Origin": # reverse the order of the coordinates return feature["geometry"]["coordinates"][::-1] + _log.warning("Site Origin not found in GeoJSON file") return None def save(self) -> None: From 14819f8f7295beaf5e5a2a169ba36b4050a4d04d Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 11:58:11 -0700 Subject: [PATCH 08/11] finish adding tests for geojson class --- tests/geojson/test_geojson.py | 47 ++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/geojson/test_geojson.py b/tests/geojson/test_geojson.py index 4b265e601..07a9a5083 100644 --- a/tests/geojson/test_geojson.py +++ b/tests/geojson/test_geojson.py @@ -100,4 +100,49 @@ def test_get_buildings(self): json = UrbanOptGeoJson(filename) buildings = json.get_buildings(ids=None) assert len(buildings) == 3 - assert buildings[3]["properties"]["floor_area"] == 34448 + assert buildings[2]["properties"]["floor_area"] == 34448 + + def test_get_building_properties_by_id(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + building_properties = json.get_building_properties_by_id("5a72287837f4de77124f946a") + assert building_properties["floor_area"] == 24567 + + def test_get_meters_for_building(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(KeyError, match="No meters found"): + json.get_meters_for_building("5a72287837f4de77124f946a") + + def test_get_meter_readings_for_building(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(KeyError, match="No meter readings found"): + json.get_meter_readings_for_building(building_id="5a72287837f4de77124f946a", meter_type="Electricity") + + def test_get_monthly_readings(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + with pytest.raises(KeyError, match="No monthly readings found"): + json.get_monthly_readings(building_id="5a72287837f4de77124f946a") + + def test_set_property_on_building_id(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + building_id = "5a72287837f4de77124f946a" + property_name = "floor_area" + property_value = 12345 + json.set_property_on_building_id(building_id, property_name, property_value) + assert json.get_building_properties_by_id(building_id)[property_name] == property_value + + def test_get_property_by_building_id(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + building_id = "5a72287837f4de77124f946a" + property_name = "building_type" + assert json.get_property_by_building_id(building_id, property_name) == "Retail other than mall" + + def test_get_site_lat_lon(self): + filename = self.data_dir / "geojson_1.json" + json = UrbanOptGeoJson(filename) + assert json.get_site_lat_lon() is None From 5454481d576febd1be21ae09345f3305701ca26b Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 13:00:22 -0700 Subject: [PATCH 09/11] add empty files to force testfolder creation on windows --- .../run/baseline_test/5a6b99ec37f4de7f94020090/empty_file.txt | 0 .../run/baseline_test/5a72287837f4de77124f946a/empty_file.txt | 0 .../run/baseline_test/5a7229e737f4de77124f946d/empty_file.txt | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/geojson/data/run/baseline_test/5a6b99ec37f4de7f94020090/empty_file.txt create mode 100644 tests/geojson/data/run/baseline_test/5a72287837f4de77124f946a/empty_file.txt create mode 100644 tests/geojson/data/run/baseline_test/5a7229e737f4de77124f946d/empty_file.txt diff --git a/tests/geojson/data/run/baseline_test/5a6b99ec37f4de7f94020090/empty_file.txt b/tests/geojson/data/run/baseline_test/5a6b99ec37f4de7f94020090/empty_file.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/geojson/data/run/baseline_test/5a72287837f4de77124f946a/empty_file.txt b/tests/geojson/data/run/baseline_test/5a72287837f4de77124f946a/empty_file.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/geojson/data/run/baseline_test/5a7229e737f4de77124f946d/empty_file.txt b/tests/geojson/data/run/baseline_test/5a7229e737f4de77124f946d/empty_file.txt new file mode 100644 index 000000000..e69de29bb From d9775561d13bdedff81383eccbc33a26e2e9f8d8 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 13:13:13 -0700 Subject: [PATCH 10/11] test getting site lat/lon from geojson --- tests/geojson/test_geojson.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/geojson/test_geojson.py b/tests/geojson/test_geojson.py index 07a9a5083..95b0965fa 100644 --- a/tests/geojson/test_geojson.py +++ b/tests/geojson/test_geojson.py @@ -142,7 +142,18 @@ def test_get_property_by_building_id(self): property_name = "building_type" assert json.get_property_by_building_id(building_id, property_name) == "Retail other than mall" - def test_get_site_lat_lon(self): + def test_get_site_lat_lon_none(self): filename = self.data_dir / "geojson_1.json" json = UrbanOptGeoJson(filename) assert json.get_site_lat_lon() is None + + def test_get_site_lat_lon(self): + filename = ( + self.data_dir.parent.parent + / "model_connectors" + / "data" + / "sdk_output_skeleton_13_buildings" + / "exportGeo.json" + ) + json = UrbanOptGeoJson(filename) + assert json.get_site_lat_lon() == [42.816772, -78.849485] From 17de6f946ee5d54d3ddefab8fb8db195ef4134f9 Mon Sep 17 00:00:00 2001 From: Nathan Moore Date: Tue, 21 Jan 2025 13:13:50 -0700 Subject: [PATCH 11/11] improve docstring for geojson lat-lon method --- geojson_modelica_translator/geojson/urbanopt_geojson.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/geojson_modelica_translator/geojson/urbanopt_geojson.py b/geojson_modelica_translator/geojson/urbanopt_geojson.py index 42baae331..81f3d9fc9 100644 --- a/geojson_modelica_translator/geojson/urbanopt_geojson.py +++ b/geojson_modelica_translator/geojson/urbanopt_geojson.py @@ -238,8 +238,11 @@ def get_property_by_building_id(self, building_id: str, property_name: str) -> s return feature["properties"].get(property_name, None) return None - def get_site_lat_lon(self) -> tuple | None: - """Return the site's latitude and longitude""" + def get_site_lat_lon(self) -> list | None: + """Return the site's latitude and longitude + + Rounds to 6 decimal places, if the geojson file has more than 6 decimal places. + Returns None if the site origin is not found.""" for feature in self.data["features"]: if feature["properties"]["name"] == "Site Origin": # reverse the order of the coordinates