Skip to content

Commit

Permalink
Merge pull request #47 from chriswmackey/main
Browse files Browse the repository at this point in the history
feat(ghe): Set GHEDesigner to use GeoJSON polygons
  • Loading branch information
mitchute authored Nov 14, 2024
2 parents 198d7a5 + 96e7db2 commit ddad013
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
{
"buildings": [
{
"geojson_id": "8",
"load_model": "time_series",
"load_model_parameters": {
"time_series": {
"filepath": "C:\\Users\\tcharan\\Desktop\\NRELWork\\Gitrepos\\geojson-modelica-translator\\tests\\management\\data\\sdk_project_scraps\\run\\baseline_scenario\\2\\016_export_modelica_loads\\modelica.mos",
"delta_temp_air_cooling": 10,
"delta_temp_air_heating": 18,
"has_liquid_cooling": true,
"has_liquid_heating": true,
"has_electric_cooling": false,
"has_electric_heating": false,
"max_electrical_load": 823879000,
"temp_chw_return": 12,
"temp_chw_supply": 7,
"temp_hw_return": 35,
"temp_hw_supply": 40,
"temp_setpoint_cooling": 24,
"temp_setpoint_heating": 20
}
},
"ets_model": "Fifth Gen Heat Pump",
"fifth_gen_ets_parameters": {
"supply_water_temperature_building": 15,
"chilled_water_supply_temp": 5,
"hot_water_supply_temp": 50,
"cop_heat_pump_heating": 2.5,
"cop_heat_pump_cooling": 3.5,
"ets_pump_flow_rate": 0.0005,
"ets_pump_head": 10000,
"fan_design_flow_rate": 0.25,
"fan_design_head": 150
},
"photovoltaic_panels": [],
"diesel_generators": [],
"battery_banks": []
},
{
"geojson_id": "9",
"load_model": "time_series",
"load_model_parameters": {
"time_series": {
"filepath": "C:\\Users\\tcharan\\Desktop\\NRELWork\\Gitrepos\\geojson-modelica-translator\\tests\\management\\data\\sdk_project_scraps\\run\\baseline_scenario\\5\\016_export_modelica_loads\\modelica.mos",
"delta_temp_air_cooling": 10,
"delta_temp_air_heating": 18,
"has_liquid_cooling": true,
"has_liquid_heating": true,
"has_electric_cooling": false,
"has_electric_heating": false,
"max_electrical_load": 45830800,
"temp_chw_return": 12,
"temp_chw_supply": 7,
"temp_hw_return": 35,
"temp_hw_supply": 40,
"temp_setpoint_cooling": 24,
"temp_setpoint_heating": 20
}
},
"ets_model": "Fifth Gen Heat Pump",
"fifth_gen_ets_parameters": {
"supply_water_temperature_building": 15,
"chilled_water_supply_temp": 5,
"hot_water_supply_temp": 50,
"cop_heat_pump_heating": 2.5,
"cop_heat_pump_cooling": 3.5,
"ets_pump_flow_rate": 0.0005,
"ets_pump_head": 10000,
"fan_design_flow_rate": 0.25,
"fan_design_head": 150
}
}
],
"district_system": {
"fifth_generation": {
"soil": {
"conductivity": 2.0,
"rho_cp": 2343493,
"undisturbed_temp": 18.3
},
"horizontal_piping_parameters": {
"hydraulic_diameter": 0.089,
"insulation_thickness": 0.2,
"insulation_conductivity": 2.3,
"diameter_ratio": 11,
"roughness": 1e-06,
"rho_cp": 2139000,
"number_of_segments": 1,
"buried_depth": 1.5
},
"central_pump_parameters": {
"pump_design_head": 60000,
"pump_flow_rate": 0.01
},
"ghe_parameters": {
"version": "0.2.5",
"ghe_dir": "tests\\management\\data\\sdk_project_scraps\\run\\baseline_scenario\\ghe_dir",
"fluid": {
"fluid_name": "Water",
"concentration_percent": 0.0,
"temperature": 20
},
"grout": {
"conductivity": 1.0,
"rho_cp": 3901000
},
"pipe": {
"inner_diameter": 0.0216,
"outer_diameter": 0.0266,
"shank_spacing": 0.0323,
"roughness": 1e-06,
"conductivity": 0.4,
"rho_cp": 1542000,
"arrangement": "singleutube"
},
"simulation": {
"num_months": 240
},
"geometric_constraints": {
"b_min": 3.0,
"b_max": 10.0,
"max_height": 135.0,
"min_height": 60.0,
"method": "polygon"
},
"design": {
"method": "AREAPROPORTIONAL",
"flow_rate": 0.2,
"flow_type": "borehole",
"max_eft": 35.0,
"min_eft": 5.0
},
"ghe_specific_params": [
{
"ghe_id": "8c369df2-18e9-439a-8c25-875851c5aaf0",
"borehole": {
"buried_depth": 2.0,
"diameter": 0.15,
"length_of_boreholes": 1.0,
"number_of_boreholes": 5
}
}
]
}
}
},
"weather": "USA_NY_Buffalo-Greater.Buffalo.Intl.AP.725280_TMY3.mos"
}
37 changes: 30 additions & 7 deletions thermalnetwork/ground_heat_exchanger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from thermalnetwork.base_component import BaseComponent
from thermalnetwork.enums import ComponentType
from thermalnetwork.projection import polygon_area

logging.basicConfig(level=logging.DEBUG, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()])
logger = logging.getLogger(__name__)
Expand All @@ -23,7 +24,20 @@ def __init__(self, data: dict) -> None:
# self.json_data = json.load(f)
# compute Area
self.id = data["id"]
self.area = self.json_data["geometric_constraints"]["length"] * self.json_data["geometric_constraints"]["width"]
if "polygons" in self.json_data["geometric_constraints"]:
bound_area = polygon_area(self.json_data["geometric_constraints"]["polygons"][0])
if bound_area < 0: # clockwise polygon; reverse for GHE Designer
self.json_data["geometric_constraints"]["polygons"][0].reverse()
self.area = abs(bound_area)
for i, hole_poly in enumerate(self.json_data["geometric_constraints"]["polygons"][1:]):
hole_area = polygon_area(hole_poly)
if hole_area < 0: # clockwise polygon; reverse for GHE Designer
self.json_data["geometric_constraints"]["polygons"][i + 1].reverse()
self.area -= abs(hole_area)
else:
length = self.json_data["geometric_constraints"]["length"]
width = self.json_data["geometric_constraints"]["width"]
self.area = length * width

def ghe_size(self, total_space_loads, output_path: Path) -> float:
logger.info(f"GHE_SIZE with total_space_loads: {total_space_loads}")
Expand Down Expand Up @@ -59,12 +73,21 @@ def ghe_size(self, total_space_loads, output_path: Path) -> float:
max_boreholes=2500,
)
ghe.set_ground_loads_from_hourly_list(self.json_data["loads"]["ground_loads"])
ghe.set_geometry_constraints_rectangle(
length=self.json_data["geometric_constraints"]["length"],
width=self.json_data["geometric_constraints"]["width"],
b_min=self.json_data["geometric_constraints"]["b_min"],
b_max=self.json_data["geometric_constraints"]["b_max"],
)
if "polygons" in self.json_data["geometric_constraints"]:
ghe.set_geometry_constraints_bi_rectangle_constrained(
property_boundary=self.json_data["geometric_constraints"]["polygons"][0],
no_go_boundaries=self.json_data["geometric_constraints"]["polygons"][1:],
b_min=self.json_data["geometric_constraints"]["b_min"],
b_max_x=self.json_data["geometric_constraints"]["b_max"],
b_max_y=self.json_data["geometric_constraints"]["b_max"],
)
else:
ghe.set_geometry_constraints_rectangle(
length=self.json_data["geometric_constraints"]["length"],
width=self.json_data["geometric_constraints"]["width"],
b_min=self.json_data["geometric_constraints"]["b_min"],
b_max=self.json_data["geometric_constraints"]["b_max"],
)
ghe.set_design(
flow_rate=self.json_data["design"]["flow_rate"], flow_type_str=self.json_data["design"]["flow_type"]
)
Expand Down
30 changes: 24 additions & 6 deletions thermalnetwork/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from thermalnetwork.enums import ComponentType, DesignType
from thermalnetwork.ground_heat_exchanger import GHE
from thermalnetwork.pump import Pump
from thermalnetwork.projection import lower_left_point, upper_right_point, \
meters_to_long_lat_factors, lon_lat_to_polygon

logging.basicConfig(level=logging.DEBUG, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()])
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -115,6 +117,7 @@ def get_connected_features(self):
for k, v in feature["properties"].items()
if k not in ["type", "name", "id", "district_system_type"]
},
"geometry": feature["geometry"],
"is_ghe_start_loop": feature_id == startloop_feature_id,
}
)
Expand Down Expand Up @@ -208,15 +211,30 @@ def convert_features(self, json_data):
}
elif feature["type"] == "District System" and feature["district_system_type"] == "Ground Heat Exchanger":
feature_type = "GROUNDHEATEXCHANGER"
geometric_constraints = self.ghe_parameters["geometric_constraints"]
# get ghe parameters for 'ghe_specific_params' key of system_parameters.json
matching_ghe = self.find_matching_ghe_id(feature["id"])
# matching_ghe.pop('ground_loads', None)
logger.debug(f"matching_ghe: {matching_ghe}\n")
length = matching_ghe["ghe_geometric_params"]["length_of_ghe"]
width = matching_ghe["ghe_geometric_params"]["width_of_ghe"]
geometric_constraints = self.ghe_parameters["geometric_constraints"]
geometric_constraints["length"] = length
geometric_constraints["width"] = width
if "ghe_geometric_params" not in matching_ghe:
# convert detailed geometry coordinates to meters
lat_long_polys = feature["geometry"]["coordinates"]
origin_lon_lat = lower_left_point(lat_long_polys[0])
convert_facs = meters_to_long_lat_factors(origin_lon_lat)
ghe_polygons = []
for poly in lat_long_polys:
coords = lon_lat_to_polygon(poly, origin_lon_lat, convert_facs)
ghe_polygons.append(coords)
# set geometric constraints to be dictated by the polygons
geometric_constraints["polygons"] = ghe_polygons
min_pt = lower_left_point(ghe_polygons[0])
max_pt = upper_right_point(ghe_polygons[0])
geometric_constraints["length"] = max_pt[0] - min_pt[0]
geometric_constraints["width"] = max_pt[1] - min_pt[1]
else: # use the old ghe_geometric_params
length = matching_ghe["ghe_geometric_params"]["length_of_ghe"]
width = matching_ghe["ghe_geometric_params"]["width_of_ghe"]
geometric_constraints["length"] = length
geometric_constraints["width"] = width
properties = {
"fluid": self.ghe_parameters["fluid"],
"grout": self.ghe_parameters["grout"],
Expand Down
20 changes: 18 additions & 2 deletions thermalnetwork/projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ def lon_lat_to_polygon(polygon_lon_lat_coords, origin_lon_lat=None,
# Unpack or auto-calculate the conversion factors
if not conversion_factors:
meters_to_lon, meters_to_lat = meters_to_long_lat_factors(origin_lon_lat)
lon_to_meters, lat_to_meters = 1.0 / meters_to_lon, 1.0 / meters_to_lat
else:
lon_to_meters, lat_to_meters = conversion_factors
meters_to_lon, meters_to_lat = conversion_factors
lon_to_meters, lat_to_meters = 1.0 / meters_to_lon, 1.0 / meters_to_lat

# Get the (X, Y) values for the polygon in meters
return [((pt[0] - origin_lon_lat[0]) / lon_to_meters,
Expand Down Expand Up @@ -123,6 +123,22 @@ def lower_left_point(polygon):
return min_pt


def upper_right_point(polygon):
"""
Get (X, Y) values for the upper right corner of the bounding rectangle for a polygon.
:param polygon: An array of (X, Y) values in any units system.
:return: X and Y coordinates for the upper right point around the polygon.
"""
max_pt = [polygon[0][0], polygon[0][1]]
for point in polygon[1:]:
if point[0] > max_pt[0]:
max_pt[0] = point[0]
if point[1] > max_pt[1]:
max_pt[1] = point[1]
return max_pt


def polygon_area(polygon):
"""
Get the area of polygon.
Expand Down
4 changes: 4 additions & 0 deletions thermalnetwork/tests/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def setUp(self) -> None:
self.scenario_directory_path_1_ghe / "ghe_dir" / "sys_params.json"
).resolve()

self.system_parameter_path_1_ghe_geometry = (
self.scenario_directory_path_1_ghe / "ghe_dir" / "sys_params_detailed_geo.json"
).resolve()

self.geojson_file_path_2_ghe = (
self.demos_path / "sdk_output_skeleton_2_ghe_sequential" / "network.geojson"
).resolve()
Expand Down
41 changes: 38 additions & 3 deletions thermalnetwork/tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,6 @@ def test_network_ghe_proportional(self):

# -- Check
expected_depth_of_boreholes = pytest.approx(133, 2)
# FIXME: 135 is the max borehole length for a GHE (as set in the sys-params file).
# This implies the borefield size is too small.
# Borefield dimensions are set in the geojson file and transferred to the sys-params file by the GMT.
for ghe_id in output_path.iterdir():
if ghe_id.is_dir():
sim_summary = json.loads((ghe_id / "SimulationSummary.json").read_text())
Expand All @@ -166,6 +163,44 @@ def test_network_ghe_proportional(self):
# Restore the trailing newline
sys_param_file.write("\n")

def test_network_one_ghe_detailed(self):
# -- Set up
output_path = self.test_outputs_path / "one_ghe_detailed_geo"
output_path.mkdir(parents=True, exist_ok=True)

# -- Run
run_sizer_from_cli_worker(
self.system_parameter_path_1_ghe_geometry,
self.scenario_directory_path_1_ghe,
self.geojson_file_path_1_ghe,
output_path,
)

# -- Check
expected_depth_of_boreholes = pytest.approx(133, 2)
# FIXME: 135 is the max borehole length for a GHE (as set in the sys-params file).
# This implies the borefield size is too small.
# Borefield dimensions are set in the geojson file and transferred to the sys-params file by the GMT.
for ghe_id in output_path.iterdir():
if ghe_id.is_dir():
sim_summary = json.loads((ghe_id / "SimulationSummary.json").read_text())

assert sim_summary["ghe_system"]["active_borehole_length"]["value"] == expected_depth_of_boreholes

# -- Clean up
# Restore the original borehole length and number of boreholes.
sys_param_ghe = json.loads(self.system_parameter_path_1_ghe_geometry.read_text())
ghe_specific_params = sys_param_ghe["district_system"]["fifth_generation"]["ghe_parameters"][
"ghe_specific_params"
]
for ghe in ghe_specific_params:
ghe["borehole"]["length_of_boreholes"] = self.original_borehole_length
ghe["borehole"]["number_of_boreholes"] = self.original_num_boreholes
with open(self.system_parameter_path_1_ghe_geometry, "w") as sys_param_file:
json.dump(sys_param_ghe, sys_param_file, indent=2)
# Restore the trailing newline
sys_param_file.write("\n")


# def test_network_two_ghe_no_load(self):
# geojson_file_path = self.demos_path / 'sdk_output_skeleton' / 'example_project_combine_GHE_2.json'
Expand Down

0 comments on commit ddad013

Please sign in to comment.