openstudio-standards 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/data/standards/test_performance_expected_dd_results.csv +90 -90
  3. data/lib/openstudio-standards/btap/activity.rb +858 -61
  4. data/lib/openstudio-standards/btap/attributes.rb +297 -103
  5. data/lib/openstudio-standards/btap/bridging.rb +1335 -593
  6. data/lib/openstudio-standards/btap/btap.rb +60 -181
  7. data/lib/openstudio-standards/btap/carbon/btap_carbon.rb +199 -142
  8. data/lib/openstudio-standards/btap/common/constructions.json +1275 -0
  9. data/lib/openstudio-standards/btap/common/costs.csv +1969 -0
  10. data/lib/openstudio-standards/btap/common/costs_local_factors.csv +2315 -0
  11. data/lib/openstudio-standards/btap/common/materials_glazing.csv +61 -0
  12. data/lib/openstudio-standards/btap/{common_resources → common}/materials_hvac.csv +1685 -1685
  13. data/lib/openstudio-standards/btap/{common_resources → common}/materials_lighting.csv +266 -266
  14. data/lib/openstudio-standards/btap/common/materials_opaque.csv +214 -0
  15. data/lib/openstudio-standards/btap/common/necb_building_types.csv +35 -0
  16. data/lib/openstudio-standards/btap/common/necb_space_types.csv +110 -0
  17. data/lib/openstudio-standards/btap/common/thermal_bridging.csv +73 -0
  18. data/lib/openstudio-standards/btap/costing/btap_costing.rb +346 -429
  19. data/lib/openstudio-standards/btap/costing/database.rb +104 -0
  20. data/lib/openstudio-standards/btap/costing/daylighting_sensor_control_costing.rb +356 -344
  21. data/lib/openstudio-standards/btap/costing/dcv_costing.rb +282 -296
  22. data/lib/openstudio-standards/btap/costing/envelope_costing.rb +179 -192
  23. data/lib/openstudio-standards/btap/costing/heating_cooling_costing.rb +2477 -2316
  24. data/lib/openstudio-standards/btap/costing/led_lighting_costing.rb +127 -142
  25. data/lib/openstudio-standards/btap/costing/lighting_costing.rb +216 -198
  26. data/lib/openstudio-standards/btap/costing/nv_costing.rb +522 -523
  27. data/lib/openstudio-standards/btap/costing/pv_ground_costing.rb +649 -649
  28. data/lib/openstudio-standards/btap/costing/shw_costing.rb +626 -625
  29. data/lib/openstudio-standards/btap/costing/thermal_bridging_costing.rb +72 -0
  30. data/lib/openstudio-standards/btap/costing/ventilation_costing.rb +2363 -2361
  31. data/lib/openstudio-standards/btap/deprecated.rb +1033 -0
  32. data/lib/openstudio-standards/btap/envelope.rb +313 -572
  33. data/lib/openstudio-standards/btap/fileio.rb +152 -1070
  34. data/lib/openstudio-standards/btap/geometry.rb +486 -766
  35. data/lib/openstudio-standards/btap/linear_regression.rb +130 -0
  36. data/lib/openstudio-standards/btap/paths.rb +46 -0
  37. data/lib/openstudio-standards/btap/qaqc/analysis.rb +163 -0
  38. data/lib/openstudio-standards/btap/qaqc/data.rb +2293 -0
  39. data/lib/openstudio-standards/btap/qaqc/datapoint.rb +641 -0
  40. data/lib/openstudio-standards/btap/schedules.rb +11 -611
  41. data/lib/openstudio-standards/btap/structure.rb +574 -189
  42. data/lib/openstudio-standards/constructions/materials/information.rb +1 -1
  43. data/lib/openstudio-standards/create_typical/create_typical.rb +1 -1
  44. data/lib/openstudio-standards/exterior_lighting/create.rb +135 -85
  45. data/lib/openstudio-standards/exterior_lighting/data/typical_exterior_lighting.json +20 -0
  46. data/lib/openstudio-standards/exterior_lighting/information.rb +23 -2
  47. data/lib/openstudio-standards/geometry/create.rb +7 -3
  48. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.Model.rb +3 -2
  49. data/lib/openstudio-standards/prototypes/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.Model.rb +3 -2
  50. data/lib/openstudio-standards/prototypes/common/data/thermostat_schedule_lookup.json +84 -84
  51. data/lib/openstudio-standards/prototypes/common/objects/Prototype.hvac_systems.rb +4 -4
  52. data/lib/openstudio-standards/refrigeration/create_walkin.rb +45 -5
  53. data/lib/openstudio-standards/refrigeration/data/refrigerated_walkins.csv +75 -75
  54. data/lib/openstudio-standards/schedules/parametric.rb +6 -0
  55. data/lib/openstudio-standards/service_water_heating/create_typical.rb +10 -3
  56. data/lib/openstudio-standards/service_water_heating/create_water_heating_loop.rb +30 -14
  57. data/lib/openstudio-standards/service_water_heating/information.rb +4 -1
  58. data/lib/openstudio-standards/sql_file/unmet_hours.rb +2 -1
  59. data/lib/openstudio-standards/standards/Standards.CoilCoolingDXSingleSpeed.rb +3 -1
  60. data/lib/openstudio-standards/standards/Standards.CoilHeatingDXSingleSpeed.rb +3 -1
  61. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.boilers.json +12 -0
  62. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2004/data/ashrae_90_1_2004.construction_properties.json +18432 -3072
  63. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.boilers.json +26 -0
  64. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2007/data/ashrae_90_1_2007.construction_properties.json +8532 -1422
  65. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.boilers.json +26 -0
  66. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2010/data/ashrae_90_1_2010.construction_properties.json +8532 -1422
  67. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.boilers.json +16 -0
  68. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.computer_room_acs.json +30 -30
  69. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2013/data/ashrae_90_1_2013.construction_properties.json +7560 -1260
  70. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/ashrae_90_1_2016.PumpVariableSpeed.rb +1 -2
  71. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.boilers.json +16 -0
  72. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2016/data/ashrae_90_1_2016.construction_properties.json +7560 -1260
  73. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.AirLoopHVAC.rb +3 -3
  74. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/ashrae_90_1_2019.PumpVariableSpeed.rb +1 -2
  75. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.boilers.json +22 -0
  76. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.construction_properties.json +7236 -1206
  77. data/lib/openstudio-standards/standards/ashrae_90_1/ashrae_90_1_2019/data/ashrae_90_1_2019.furnaces.json +223 -19
  78. data/lib/openstudio-standards/standards/ashrae_90_1/data/ashrae_90_1.schedules.json +37 -1
  79. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.boilers.json +3 -0
  80. data/lib/openstudio-standards/standards/ashrae_90_1_prm/ashrae_90_1_prm_2019/data/ashrae_90_1_prm_2019.construction_properties.json +6900 -1150
  81. data/lib/openstudio-standards/standards/cbes/data/cbes.schedules.json +1 -1
  82. data/lib/openstudio-standards/standards/deer/data/deer.schedules.json +1 -1
  83. data/lib/openstudio-standards/standards/necb/BTAP1980TO2010/data/surface_thermal_transmittance.json +76 -40
  84. data/lib/openstudio-standards/standards/necb/BTAPPRE1980/data/surface_thermal_transmittance.json +76 -40
  85. data/lib/openstudio-standards/standards/necb/ECMS/hvac_systems.rb +6 -4
  86. data/lib/openstudio-standards/standards/necb/NECB2011/NECB2011_QAQC.json +2988 -0
  87. data/lib/openstudio-standards/standards/necb/NECB2011/autozone.rb +25 -3
  88. data/lib/openstudio-standards/standards/necb/NECB2011/building_envelope.rb +672 -534
  89. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Hospital.osm +1791 -1792
  90. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPMultiTower.osm +7 -1
  91. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LEEPTownHouse.osm +7 -1
  92. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeHotel.osm +5 -6
  93. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/LargeOffice.osm +8 -9
  94. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernEducation.osm +10 -4
  95. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/NorthernHealthCare.osm +14 -15
  96. data/lib/openstudio-standards/standards/necb/NECB2011/data/geometry/Outpatient.osm +7 -1
  97. data/lib/openstudio-standards/standards/necb/NECB2011/data/surface_thermal_transmittance.json +76 -40
  98. data/lib/openstudio-standards/standards/necb/NECB2011/hvac_systems.rb +14 -6
  99. data/lib/openstudio-standards/standards/necb/NECB2011/necb_2011.rb +542 -130
  100. data/lib/openstudio-standards/standards/necb/NECB2011/qaqc/necb_qaqc.rb +30 -63
  101. data/lib/openstudio-standards/standards/necb/NECB2011/service_water_heating.rb +7 -2
  102. data/lib/openstudio-standards/standards/necb/NECB2015/data/surface_thermal_transmittance.json +90 -0
  103. data/lib/openstudio-standards/standards/necb/NECB2015/necb_2015.rb +8 -6
  104. data/lib/openstudio-standards/standards/necb/NECB2017/data/surface_thermal_transmittance.json +76 -40
  105. data/lib/openstudio-standards/standards/necb/NECB2017/necb_2017.rb +2 -1
  106. data/lib/openstudio-standards/standards/necb/NECB2020/building_envelope.rb +0 -117
  107. data/lib/openstudio-standards/standards/necb/NECB2020/data/surface_thermal_transmittance.json +76 -45
  108. data/lib/openstudio-standards/standards/necb/NECB2020/necb_2020.rb +218 -0
  109. data/lib/openstudio-standards/standards/necb/common/construction_defaults.osm +12 -7
  110. data/lib/openstudio-standards/standards/standard.rb +1 -1
  111. data/lib/openstudio-standards/version.rb +1 -1
  112. data/lib/openstudio-standards.rb +9 -4
  113. metadata +59 -66
  114. data/data/inventory/thermal_bridging.csv +0 -90
  115. data/data/standards/OpenStudio_Standards-ashrae_90_1-ALL-comstock(space_types).xlsx +0 -0
  116. data/data/standards/openstudio_standards_duplicates_log.csv +0 -7962
  117. data/lib/openstudio-standards/btap/NECB_building_types.csv +0 -35
  118. data/lib/openstudio-standards/btap/NECB_space_types.csv +0 -109
  119. data/lib/openstudio-standards/btap/WeatherData1.json +0 -2110
  120. data/lib/openstudio-standards/btap/WeatherData1.xlsx +0 -0
  121. data/lib/openstudio-standards/btap/btap_result.rb +0 -1144
  122. data/lib/openstudio-standards/btap/btap_test_helper.rb +0 -86
  123. data/lib/openstudio-standards/btap/common_paths.rb +0 -81
  124. data/lib/openstudio-standards/btap/common_resources/ConstructionProperties.csv +0 -52
  125. data/lib/openstudio-standards/btap/common_resources/Constructions.csv +0 -37
  126. data/lib/openstudio-standards/btap/common_resources/construction_sets.csv +0 -1270
  127. data/lib/openstudio-standards/btap/common_resources/constructions_glazing.csv +0 -121
  128. data/lib/openstudio-standards/btap/common_resources/constructions_opaque.csv +0 -2256
  129. data/lib/openstudio-standards/btap/common_resources/costs.csv +0 -1897
  130. data/lib/openstudio-standards/btap/common_resources/costs_local_factors.csv +0 -2315
  131. data/lib/openstudio-standards/btap/common_resources/materials_glazing.csv +0 -35
  132. data/lib/openstudio-standards/btap/common_resources/materials_opaque.csv +0 -164
  133. data/lib/openstudio-standards/btap/costing/btap_measure_helper.rb +0 -359
  134. data/lib/openstudio-standards/btap/costing/btap_workflow.rb +0 -117
  135. data/lib/openstudio-standards/btap/costing/copy_test_results_files_to_expected_results.rb +0 -11
  136. data/lib/openstudio-standards/btap/costing/cost_building_from_file.rb +0 -136
  137. data/lib/openstudio-standards/btap/costing/costing_database_wrapper.rb +0 -173
  138. data/lib/openstudio-standards/btap/costing/dummy.epw +0 -8768
  139. data/lib/openstudio-standards/btap/costing/dummy.osm +0 -5320
  140. data/lib/openstudio-standards/btap/costing/neb_end_use_prices.csv +0 -42
  141. data/lib/openstudio-standards/btap/costing/necb_2011_spacetype_info.csv +0 -225
  142. data/lib/openstudio-standards/btap/costing/necb_reference_runs.csv +0 -28705
  143. data/lib/openstudio-standards/btap/costing/parallel_tests.rb +0 -92
  144. data/lib/openstudio-standards/btap/costing/test_list.txt +0 -15
  145. data/lib/openstudio-standards/btap/costing/test_run_all_test_locally.rb +0 -27
  146. data/lib/openstudio-standards/btap/costing/test_run_costing_tests.rb +0 -80
  147. data/lib/openstudio-standards/btap/necb_2011_spacetype_info.csv +0 -225
  148. data/lib/openstudio-standards/btap/utilities/Add_template_field_to_json.rb +0 -22
  149. data/lib/openstudio-standards/btap/utilities/convert_surfaces_to_adiabatic_necb_8426.rb +0 -204
  150. data/lib/openstudio-standards/btap/utilities/generate_space_types.rb +0 -8
  151. data/lib/openstudio-standards/btap/utilities/necb_to_epw_map.rb +0 -50
  152. data/lib/openstudio-standards/btap/utilities/os_sim_extract.rb +0 -48
  153. data/lib/openstudio-standards/btap/utilities/rename_surfaces.rb +0 -24
  154. data/lib/openstudio-standards/btap/utilities/round_surf_coords.rb +0 -32
  155. data/lib/openstudio-standards/btap/utilities/sched_create.rb +0 -145
  156. data/lib/openstudio-standards/btap/utilities/set_mult_to_adiabatic.rb +0 -53
  157. data/lib/openstudio-standards/btap/utilities/space_type.rb +0 -78
  158. data/lib/openstudio-standards/btap/utilities/weatherData1_xlsx_to_json.rb +0 -56
  159. data/lib/openstudio-standards/btap/vintagizer.rb +0 -161
  160. data/lib/openstudio-standards/btap/visualization.rb +0 -72
  161. data/lib/openstudio-standards/standards/necb/NECB2011/data/necb_2011_spacetype_info.csv +0 -225
  162. data/lib/openstudio-standards/standards/necb/common/btap_analysis.rb +0 -79
  163. data/lib/openstudio-standards/standards/necb/common/btap_data.rb +0 -2656
  164. data/lib/openstudio-standards/standards/necb/common/btap_datapoint.rb +0 -653
  165. data/lib/openstudio-standards/standards/necb/common/necb_reference_runs.csv +0 -28705
  166. data/lib/openstudio-standards/standards/necb/common/utility_pricing_2025-02-20.csv +0 -92
  167. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_frame.csv +0 -0
  168. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_glazing.csv +0 -0
  169. /data/lib/openstudio-standards/btap/{common_resources → common}/carbon_opaque.csv +0 -0
  170. /data/lib/openstudio-standards/btap/{common_resources → common}/hvac_vent_ahu.csv +0 -0
  171. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting.csv +0 -0
  172. /data/lib/openstudio-standards/btap/{common_resources → common}/lighting_sets.csv +0 -0
  173. /data/lib/openstudio-standards/btap/{common_resources → common}/locations.csv +0 -0
  174. /data/lib/openstudio-standards/btap/{costing → common}/mech_sizing.json +0 -0
@@ -17,255 +17,54 @@
17
17
  # * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
  # **********************************************************************/
19
19
 
20
-
21
-
22
- require 'fileutils'
23
- require 'csv'
24
20
  require 'securerandom'
25
21
  require 'digest'
26
22
 
27
-
28
23
  module BTAP
29
24
  module FileIO
30
25
 
31
- #Test Constructions Module
32
- if __FILE__ == $0
33
- require 'test/unit'
34
- class FileIOTests < Test::Unit::TestCase
35
-
36
- def setup
37
- end
38
-
39
- def test_load_save_DOE_file()
40
- BTAP::FileIO::load_e_quest(BTAP::OS_RUBY_PATH + '\Resources\eQuest_3.64\4StoreyBuilding.inp')
41
- end
42
-
43
-
44
- end
45
- end
46
- # Get the name of the model.
47
- # @author Phylroy A. Lopez
48
- # @return [String] the name of the model.
49
- def self.get_name(model)
50
- unless model.building.get.name.empty?
51
- return model.building.get.name.get.to_s
52
- else
53
- return ""
54
- end
55
- end
56
-
57
- # @author Phylroy A. Lopez
58
- # Get the name of the model.
59
- # @author Phylroy A. Lopez
60
- # @return [String] the name of the model.
61
- def self.set_name(model,name)
62
- unless model.building.empty?
63
- model.building.get.setName(name)
64
- end
65
- end
66
-
67
- # @author Phylroy A. Lopez
68
- # Get the name of the model.
69
- # @author Phylroy A. Lopez
70
- # @return [String] the name of the model.
71
- def self.set_sql_file(model,sql_path)
72
- model.setSqlFile(OpenStudio::Path.new( sql_path) )
73
- end
74
- #@author Phylroy A. Lopez
75
- # Get the filepath of all files with extention
76
- # @param folder [String} the path to the folder to be scanned.
77
- # @param ext [String] the file extension name, ex ".epw"
78
- def self.get_find_files_from_folder_by_extension(folder, ext)
79
- Dir.glob("#{folder}/**/*#{ext}")
80
- end
81
-
82
- def self.delete_files_in_folder_by_extention(folder,ext)
83
- BTAP::FileIO::get_find_files_from_folder_by_extension(folder, ext).each do |file|
84
- FileUtils.rm(file)
85
- #puts "#{file} deleted."
86
- end
87
- end
88
-
89
- def self.find_file_in_folder_by_filename(folder,filename)
90
- Dir.glob("#{folder}/**/*#{filename}")
91
- end
92
-
93
- def self.fix_url_to_path(url_string)
94
- if url_string =~/\/([a-zA-Z]:.*)/
95
- return $1
96
- else
97
- return url_string
98
- end
99
- end
100
-
101
-
102
- # This method loads an OpenStudio file into the model.
103
- # @author Phylroy A. Lopez
104
- # @param filepath [String] path to the OSM file.
105
- # @param name [String] optional model name to be set to model.
106
- # @return [OpenStudio::Model::Model] an OpenStudio model object.
107
- def self.load_idf(filepath, name = "")
108
- #load file
109
- unless File.exist?(filepath)
110
- raise 'File does not exist: ' + filepath.to_s
111
- end
112
- #puts "loading file #{filepath}..."
113
- model_path = OpenStudio::Path.new(filepath.to_s)
114
- #Upgrade version if required.
115
- version_translator = OpenStudio::OSVersion::VersionTranslator.new
116
- model = OpenStudio::EnergyPlus::loadAndTranslateIdf(model_path)
117
- version_translator.errors.each {|error| puts "Error: #{error.logMessage}\n\n"}
118
- version_translator.warnings.each {|warning| puts "Warning: #{warning.logMessage}\n\n"}
119
- #If model did not load correctly.
120
- if model.empty?
121
- raise 'something went wrong'
122
- end
123
- model = model.get
124
- if name != ""
125
- self.set_name(model,name)
126
- end
127
- #puts "File #{filepath} loaded."
128
- return model
129
- end
130
-
131
- def self.replace_model(model,new_model,runner = nil)
132
- # pull original weather file object over
133
- weather_file = new_model.getOptionalWeatherFile
134
- if not weather_file.empty?
135
- weather_file.get.remove
136
- BTAP::runner_register("Info", "Removed alternate model's weather file object.",runner)
137
- end
138
- original_weather_file = model.getOptionalWeatherFile
139
- if not original_weather_file.empty?
140
- original_weather_file.get.clone(new_model)
141
- end
142
-
143
- # pull original design days over
144
- new_model.getDesignDays.sort.each { |designDay|
145
- designDay.remove
146
- }
147
- model.getDesignDays.sort.each { |designDay|
148
- designDay.clone(new_model)
149
- }
150
-
151
- # swap underlying data in model with underlying data in new_model
152
- # remove existing objects from model
153
- handles = OpenStudio::UUIDVector.new
154
- model.objects.each do |obj|
155
- handles << obj.handle
156
- end
157
- model.removeObjects(handles)
158
- # add new file to empty model
159
- model.addObjects( new_model.toIdfFile.objects )
160
- BTAP::runner_register("Info", "Model name is now #{model.building.get.name}.", runner)
161
- end
162
-
163
-
164
-
165
-
166
-
167
26
  # This method loads an OpenStudio file into the model.
168
27
  # @author Phylroy A. Lopez
169
28
  # @param filepath [String] path to the OSM file.
170
29
  # @param name [String] optional model name to be set to model.
171
30
  # @return [OpenStudio::Model::Model] an OpenStudio model object.
172
31
  def self.load_osm(filepath, name = "")
173
-
174
- #load file
175
32
  unless File.exist?(filepath)
176
33
  raise 'File does not exist: ' + filepath.to_s
177
34
  end
178
- #puts "loading file #{filepath}..."
179
35
  model_path = OpenStudio::Path.new(filepath.to_s)
180
- #Upgrade version if required.
36
+
37
+ # Upgrade version if required.
181
38
  version_translator = OpenStudio::OSVersion::VersionTranslator.new
182
39
  model = version_translator.loadModel(model_path)
183
- version_translator.errors.each {|error| puts "Error: #{error.logMessage}\n\n"}
184
- version_translator.warnings.each {|warning| puts "Warning: #{warning.logMessage}\n\n"}
185
- #If model did not load correctly.
40
+ version_translator.errors.each { |error| puts "Error: #{error.logMessage}\n" }
41
+ version_translator.warnings.each { |warning| puts "Warning: #{warning.logMessage}\n" }
186
42
  if model.empty?
187
- raise "could not load #{filepath}"
43
+ raise "Could not load #{filepath}"
188
44
  end
189
45
  model = model.get
190
46
  if name != "" and not name.nil?
191
47
  self.set_name(model,name)
192
48
  end
193
- #puts "File #{filepath} loaded."
194
49
 
195
50
  return model
196
51
  end
197
52
 
198
- # This method loads an *Quest file into the model.
199
- # @author Phylroy A. Lopez
200
- # @param filepath [String] path to the OSM file.
201
- # @return [OpenStudio::Model::Model] an OpenStudio model object.
202
- def self.load_e_quest(filepath)
203
- #load file
204
- unless File.exist?(filepath)
205
- raise 'File does not exist: ' + filepath.to_s
53
+ #load a model into OS & version translates, exiting and erroring if a problem is found
54
+ def self.safe_load_model(model_path_string)
55
+ model_path = OpenStudio::Path.new(model_path_string)
56
+ if OpenStudio::exists(model_path)
57
+ versionTranslator = OpenStudio::OSVersion::VersionTranslator.new
58
+ model = versionTranslator.loadModel(model_path)
59
+ if model.empty?
60
+ raise "Version translation failed for #{model_path_string}"
61
+ else
62
+ model = model.get
63
+ end
64
+ else
65
+ raise "#{model_path_string} couldn't be found"
206
66
  end
207
- #puts "loading equest file #{filepath}. This will only convert geometry."
208
- #Create an instancse of a DOE model
209
- doe_model = BTAP::EQuest::DOEBuilding.new()
210
- #Load the inp data into the DOE model.
211
- doe_model.load_inp(filepath)
212
-
213
- #Convert the model to a OSM format.
214
- model = doe_model.create_openstudio_model_new()
215
- return model
216
- end
217
-
218
- #This method will inject OSM objects from a OSM file/library into the current
219
- # model.
220
- # @author Phylroy A. Lopez
221
- # @param filepath [String] path to the OSM library file.
222
- # @return [OpenStudio::Model::Model] an OpenStudio model object (self reference).
223
- def self.inject_osm_file(model, filepath)
224
- osm_data = BTAP::FileIO::load_osm(filepath)
225
- model.addObjects(osm_data.objects);
226
67
  return model
227
- end
228
-
229
- # This method will return a deep copy of the model.
230
- # Simply because I don't trust the clone method yet.
231
- # @author Phylroy A. Lopez
232
- # @return [OpenStudio::Model::Model] a copy of the OpenStudio model object.
233
- def self.deep_copy(model,bool = true)
234
- return model.clone(bool).to_Model
235
-
236
- # pull original weather file object over
237
- weather_file = new_model.getOptionalWeatherFile
238
- if not weather_file.empty?
239
- weather_file.get.remove
240
- BTAP::runner_register("Info", "Removed alternate model's weather file object.",runner)
241
- end
242
- original_weather_file = model.getOptionalWeatherFile
243
- if not original_weather_file.empty?
244
- original_weather_file.get.clone(new_model)
245
- end
246
-
247
- # pull original design days over
248
- new_model.getDesignDays.sort.each { |designDay|
249
- designDay.remove
250
- }
251
- model.getDesignDays.sort.each { |designDay|
252
- designDay.clone(new_model)
253
- }
254
-
255
- # swap underlying data in model with underlying data in new_model
256
- # remove existing objects from model
257
- handles = OpenStudio::UUIDVector.new
258
- model.objects.each do |obj|
259
- handles << obj.handle
260
- end
261
- model.removeObjects(handles)
262
- # add new file to empty model
263
- model.addObjects( new_model.toIdfFile.objects )
264
- BTAP::runner_register("Info", "Model name is now #{model.building.get.name}.", runner)
265
-
266
-
267
-
268
-
269
68
  end
270
69
 
271
70
  # This method will save the model to an osm file.
@@ -304,404 +103,50 @@ module BTAP
304
103
  }
305
104
  end
306
105
 
106
+ # This method will return a deep copy of the model.
107
+ # Simply because I don't trust the clone method yet.
108
+ # @author Phylroy A. Lopez
109
+ # @return [OpenStudio::Model::Model] a copy of the OpenStudio model object.
110
+ def self.deep_copy(model,bool = true)
111
+ return model.clone(bool).to_Model
307
112
 
308
-
309
-
310
-
311
- def self.get_timestep_data(osm_file,sql_file,variable_name_array, env_period = nil, hourly_time_step = nil )
312
- column_data = get_timeseries_arrays(sql, env_period, hourly_time_step, "Boiler Fan Coil Part Load Ratio")
313
- end
314
-
315
-
316
- def self.convert_all_eso_to_csv(in_folder,out_folder)
317
- list_of_csv_files = Array.new
318
- FileUtils.mkdir_p(out_folder)
319
- osmfiles = BTAP::FileIO::get_find_files_from_folder_by_extension(in_folder,".eso")
320
-
321
- osmfiles.each do |eso_file_path|
322
-
323
- #Run ESO Vars command must be run in folder.
324
- root_folder = Dir.getwd()
325
- #puts File.dirname(eso_file_path)
326
- Dir.chdir(File.dirname(eso_file_path))
327
- if File.exist?("eplustbl.htm")
328
- File.open("dummy.rvi", 'w') {|f| f.write("") }
329
-
330
-
331
- system("#{BTAP::SimManager::ProcessManager::find_read_vars_eso()} dummy.rvi unlimited")
332
- #get name of run from html file.
333
- runname = ""
334
- f = File.open("eplustbl.htm")
335
- f.each_line do |line|
336
- if line =~ /<p>Building: <b>(.*)<\/b><\/p>/
337
- #puts "Found name: #{$1}"
338
- runname = $1
339
- break
340
- end
341
- end
342
- f.close
343
- #copy files over with distinct names
344
- #puts "copy hourly results to #{out_folder}/#{runname}_eplusout.csv"
345
- FileUtils.cp("eplusout.csv","#{out_folder}/#{runname}_eplusout.csv")
346
- #puts "copy html results to #{out_folder}/#{runname}_eplustbl.htm"
347
- FileUtils.cp("eplustbl.htm","#{out_folder}/#{runname}_eplustbl.htm")
348
- #puts "copy sql results to #{out_folder}/#{runname}_eplusout.sql"
349
- FileUtils.cp("eplusout.sql","#{out_folder}/#{runname}_eplusout.sql")
350
-
351
-
352
- list_of_csv_files << "#{out_folder}/#{runname}_eplusout.csv"
353
- end
354
- Dir.chdir(root_folder)
355
- end
356
- return list_of_csv_files
357
- end
358
-
359
-
360
- # This method will read a CSV file and return rows as hashes based on the selection given.
361
- # @author Phylroy Lopez
362
- # @param file The path to the csv file.
363
- # @param searchHash
364
- # @return matches A Array of rows that match the searchHash. The row is a Hash itself.
365
- def self.csv_look_up_rows(file, searchHash)
366
- options = {
367
- :headers => true,
368
- :converters => :numeric }
369
- table = CSV.read( file, options )
370
- # we'll save the matches here
371
- matches = nil
372
- # save a copy of the headers
373
- matches = table.find_all do |row|
374
- row
375
- match = true
376
- searchHash.keys.each do |key|
377
- match = match && ( row[key] == searchHash[key] )
378
- end
379
- match
380
- end
381
- return matches
382
- end
383
-
384
- def self.csv_look_up_unique_row(file, searchHash)
385
- #Load Vintage database information.
386
- matches = BTAP::FileIO::csv_look_up_rows(file, searchHash)
387
- raise( "Error: CSV lookup found more than one row that met criteria #{searchHash} in #{@file} ") if matches.size() > 1
388
- raise( "Error: CSV lookup found no rows that met criteria #{searchHash} in #{@file}") if matches.size() < 1
389
- return matches[0]
390
- end
391
-
392
-
393
- # This method will read a CSV file and return the unique values in a given column header.
394
- # @author Phylroy Lopez
395
- # @param file The path to the csv file.
396
- # @param colHeader The header name in teh csv file.
397
- # @return matches A Array of rows that match the searchHash. The row is a Hash itself.
398
- def self.csv_look_up_unique_col_data(file, colHeader)
399
- column_data = Array.new
400
- CSV.foreach( file, :headers => true ) do |row|
401
- column_data << row[colHeader] # For each row, give me the cell that is under the colHeader column
402
- end
403
- return column_data.sort!.uniq
404
- end
405
-
406
- def self.sum_row_headers(row,headers)
407
- total = 0.0
408
- headers.each { |header| total = total + row[header] }
409
- return total
410
- end
411
-
412
- def self.terminus_hourly_output(csv_file)
413
- #puts "Starting Terminus output processing."
414
- #puts "reading #{csv_file} being processed"
415
- #reads csv file into memory.
416
- original = CSV.read(csv_file,
417
- {
418
- :headers => true, #This flag tell the parser that there are headers.
419
- :converters => :numeric #This tell it to convert string data into numeric when possible.
420
- }
421
- )
422
- #puts "done reading #{csv_file} being processed"
423
- # We are going to collect the header names that fit a pattern. But first we need to
424
- # create array containers to save the header name. In ruby we can use the string header names
425
- # as the array index.
426
-
427
- #Create arrays to store the header names for each type.
428
- waterheater_gas_rate_headers = Array.new()
429
- waterheater_electric_rate_headers = Array.new()
430
- waterheater_heating_rate_headers = Array.new()
431
- cooling_coil_electric_power_headers = Array.new()
432
- cooling_coil_total_cooling_rate_headers = Array.new()
433
- heating_coil_air_heating_rate_headers = Array.new()
434
- heating_coil_gas_rate_headers = Array.new()
435
- plant_supply_heating_demand_rate_headers = Array.new()
436
- facility_total_electrical_demand_headers = Array.new()
437
- boiler_gas_rate_headers = Array.new()
438
- time_index = Array.new()
439
- boiler_gas_rate_headers = Array.new()
440
- heating_coil_electric_power_headers = Array.new()
441
-
442
-
443
- #remove rows 2-169 (or 1-168 in computer array terms)
444
- original = self.remove_rows_from_csv_table(0,72,original)
445
-
446
-
447
- #Scan the CSV file to file all the headers that match the pattern. This will go through all the headers and find
448
- # any header that matches our regular expression if a match is made, the header name is stuffed into the string array.
449
- original.headers.each do |header|
450
- stripped_header = header.strip
451
- waterheater_electric_rate_headers << header if stripped_header =~/^.*:Water Heater Electric Power \[W\]\(Hourly\)$/
452
- waterheater_gas_rate_headers << header if stripped_header =~/^.*:Water Heater Gas Rate \[W\]\(Hourly\)$/
453
- waterheater_heating_rate_headers << header if stripped_header =~/^.*:Water Heater Heating Rate \[W\]\(Hourly\)$/
454
- cooling_coil_electric_power_headers << header if stripped_header =~/^.*:Cooling Coil Electric Power \[W\]\(Hourly\)$/
455
- cooling_coil_total_cooling_rate_headers << header if stripped_header =~/^.*:Cooling Coil Total Cooling Rate \[W\]\(Hourly\)$/
456
- heating_coil_air_heating_rate_headers << header if stripped_header =~/^.*:Heating Coil Air Heating Rate \[W\]\(Hourly\)$/
457
- heating_coil_gas_rate_headers << header if stripped_header =~/^.*:Heating Coil Gas Rate \[W\]\(Hourly\)$/
458
- heating_coil_electric_power_headers << header if stripped_header =~/^.*:Heating Coil Electric Power \[W\]\(Hourly\)$/
459
- plant_supply_heating_demand_rate_headers << header if stripped_header =~/^(?!SWH PLANT LOOP).*:Plant Supply Side Heating Demand Rate \[W\]\(Hourly\)$/
460
- facility_total_electrical_demand_headers << header if stripped_header =~/^.*:Facility Total Electric Demand Power \[W\]\(Hourly\)$/
461
- boiler_gas_rate_headers << header if stripped_header =~/^.*:Boiler Gas Rate \[W\]\(Hourly\)/
462
-
463
- end
464
- #Debug printout stuff. Make sure the output it captures the headers you want otherwise modify the regex above
465
- #puts waterheater_gas_rate_headers
466
- #puts waterheater_electric_rate_headers
467
- #puts waterheater_heating_rate_headers
468
-
469
- #puts cooling_coil_electric_power_headers
470
- #puts cooling_coil_total_cooling_rate_headers
471
-
472
- #puts heating_coil_air_heating_rate_headers
473
- #puts heating_coil_gas_rate_headers
474
-
475
- #puts plant_supply_heating_demand_rate_headers
476
- #puts facility_total_electrical_demand_headers
477
- #puts boiler_gas_rate_headers
478
- #puts heating_coil_electric_power_headers
479
-
480
-
481
- #open up a new file to save the file to..Note: This will fail it the file is open in EXCEL.
482
- CSV.open("#{csv_file}.terminus_hourly.csv", 'w') do |csv|
483
- #Create header row for new terminus hourly file.
484
- csv << [
485
- "Date/Time",
486
- "water_heater_gas_rate_total",
487
- "water_heater_electric_rate_total",
488
- "water_heater_heating_rate_total",
489
- "cooling_coil_electric_power_total",
490
- "cooling_coil_total_cooling_rate_total",
491
- "heating_coil_air_heating_rate_total",
492
- "heating_coil_gas_rate_total",
493
- "heating_coil_electric_power_total",
494
- "plant_supply_heating_demand_rate_total",
495
- "facility_total_electrical_demand_total",
496
- "boiler_gas_rate_total"
497
- ]
498
- original.each do |row|
499
-
500
- # We are now writing data to the new csv file. This is where we can manipulate the data, row by row.
501
- # sum the headers collected above and store in specific *_total variables.
502
- # This is done via a small function self.sum_row_headers. There may only be a single
503
- # header collected.. That is fine. It is better to be flexible than hardcode anything.
504
- water_heater_gas_rate_total = self.sum_row_headers(row,waterheater_gas_rate_headers)
505
- water_heater_electric_rate_total = self.sum_row_headers(row,waterheater_electric_rate_headers)
506
- water_heater_heating_rate_total = self.sum_row_headers(row,waterheater_heating_rate_headers)
507
- cooling_coil_electric_power_total = self.sum_row_headers(row, cooling_coil_electric_power_headers)
508
- cooling_coil_total_cooling_rate_total = self.sum_row_headers(row, cooling_coil_total_cooling_rate_headers)
509
- heating_coil_air_heating_rate_total = self.sum_row_headers(row, heating_coil_air_heating_rate_headers)
510
- heating_coil_gas_rate_total = self.sum_row_headers(row, heating_coil_gas_rate_headers)
511
- heating_coil_electric_power_total = self.sum_row_headers(row, heating_coil_electric_power_headers)
512
- plant_supply_heating_demand_rate_total = self.sum_row_headers(row, plant_supply_heating_demand_rate_headers)
513
- facility_total_electrical_demand_total = self.sum_row_headers(row, facility_total_electrical_demand_headers)
514
- boiler_gas_rate_headers_total = self.sum_row_headers(row, boiler_gas_rate_headers)
515
-
516
-
517
-
518
- #Write the data out. Should match header row as above.
519
- csv << [
520
- row["Date/Time"], #Time index is hardcoded because every file will have a "Date/Time" column header.
521
- water_heater_gas_rate_total,
522
- water_heater_electric_rate_total,
523
- water_heater_heating_rate_total,
524
- cooling_coil_electric_power_total,
525
- cooling_coil_total_cooling_rate_total,
526
- heating_coil_air_heating_rate_total,
527
- heating_coil_gas_rate_total,
528
- heating_coil_electric_power_total,
529
- plant_supply_heating_demand_rate_total,
530
- facility_total_electrical_demand_total,
531
- boiler_gas_rate_headers_total
532
- ]
533
- end
113
+ # pull original weather file object over
114
+ weather_file = new_model.getOptionalWeatherFile
115
+ if not weather_file.empty?
116
+ weather_file.get.remove
117
+ BTAP::runner_register("Info", "Removed alternate model's weather file object.",runner)
534
118
  end
535
- #puts "Ending Terminus output processing."
536
- end
537
-
538
- def self.remove_rows_from_csv_table(start_index,stop_index,table)
539
- total_rows_to_remove = stop_index - start_index
540
- (0..total_rows_to_remove-1).each do |counter|
541
- table.delete(start_index)
119
+ original_weather_file = model.getOptionalWeatherFile
120
+ if not original_weather_file.empty?
121
+ original_weather_file.get.clone(new_model)
542
122
  end
543
- return table
544
- end
545
123
 
124
+ # pull original design days over
125
+ new_model.getDesignDays.sort.each { |designDay|
126
+ designDay.remove
127
+ }
128
+ model.getDesignDays.sort.each { |designDay|
129
+ designDay.clone(new_model)
130
+ }
546
131
 
547
- #load a model into OS & version translates, exiting and erroring if a problem is found
548
- def self.safe_load_model(model_path_string)
549
- model_path = OpenStudio::Path.new(model_path_string)
550
- if OpenStudio::exists(model_path)
551
- versionTranslator = OpenStudio::OSVersion::VersionTranslator.new
552
- model = versionTranslator.loadModel(model_path)
553
- if model.empty?
554
- raise "Version translation failed for #{model_path_string}"
555
- else
556
- model = model.get
557
- end
558
- else
559
- raise "#{model_path_string} couldn't be found"
132
+ # swap underlying data in model with underlying data in new_model
133
+ # remove existing objects from model
134
+ handles = OpenStudio::UUIDVector.new
135
+ model.objects.each do |obj|
136
+ handles << obj.handle
560
137
  end
561
- return model
138
+ model.removeObjects(handles)
139
+ # add new file to empty model
140
+ model.addObjects( new_model.toIdfFile.objects )
141
+ BTAP::runner_register("Info", "Model name is now #{model.building.get.name}.", runner)
562
142
  end
563
143
 
564
- #function to wrap debug == true puts
565
144
  def debug_puts(puts_text)
566
145
  if Debug_Mode == true
567
146
  puts "#{puts_text}"
568
147
  end
569
148
  end
570
149
 
571
- def get_timeseries_arrays(openstudio_sql_file, timestep, variable_name_array, regex_name_filter = /.*/, env_period = nil)
572
- returnArray = Array.new()
573
- variable_name_array.each do |variable_name|
574
- possible_key_values = openstudio_sql_file.availableKeyValues(env_period,timestep,variable_name)
575
- possible_variable_names = openstudio_sql_file.availableVariableNames(env_period,timestep).include?(variable_name)
576
- if not possible_variable_names.nil? and possible_variable_names.include?(variable_name) and not possible_key_values.nil?
577
- possible_key_values.get.sort.each do |key_value|
578
- unless regex_name_filter.match(key_value).nil?
579
- returnArray << get_timeseries_array(openstudio_sql_file, timestep, variable_name, key_value)
580
- end
581
- end
582
- end
583
- return returnArray
584
- end
585
- end
586
-
587
-
588
-
589
-
590
- #gets a time series data vector from the sql file and puts the values into a standard array of numbers
591
- def get_timeseries_array(openstudio_sql_file, timestep, variable_name, key_value)
592
- zone_time_step = "Zone Timestep"
593
- hourly_time_step = "Hourly"
594
- hvac_time_step = "HVAC System Timestep"
595
- timestep = hourly_time_step
596
- env_period = openstudio_sql_file.availableEnvPeriods[0]
597
- #puts openstudio_sql_file.class
598
- #puts env_period.class
599
- #puts timestep.class
600
- #puts variable_name.class
601
- #puts key_value.class
602
- key_value = key_value.upcase #upper cases the key_value b/c it is always uppercased in the sql file.
603
- #timestep = timestep.capitalize #capitalize the timestep b/c it is always capitalized in the sql file
604
- #timestep = timestep.split(" ").each{|word| word.capitalize!}.join(" ")
605
- #returns an array of all keyValues matching the variable name, envPeriod, and reportingFrequency
606
- #we'll use this to check if the query will work before we send it.
607
- puts "*#{env_period}*#{timestep}*#{variable_name}"
608
- time_series_array = []
609
- puts env_period.class
610
- if env_period.nil?
611
-
612
- time_series_array = [nil]
613
- return time_series_array
614
- end
615
- possible_env_periods = openstudio_sql_file.availableEnvPeriods()
616
- if possible_env_periods.nil?
617
- time_series_array = [nil]
618
- return time_series_array
619
- end
620
- possible_timesteps = openstudio_sql_file.availableReportingFrequencies(env_period)
621
- if possible_timesteps.nil?
622
- time_series_array = [nil]
623
- return time_series_array
624
- end
625
- possible_variable_names = openstudio_sql_file.availableVariableNames(env_period,timestep)
626
- if possible_variable_names.nil?
627
- time_series_array = [nil]
628
- return time_series_array
629
- end
630
- possible_key_values = openstudio_sql_file.availableKeyValues(env_period,timestep,variable_name)
631
- if possible_key_values.nil?
632
- time_series_array = [nil]
633
- return time_series_array
634
- end
635
-
636
- if possible_key_values.include? key_value and
637
- possible_variable_names.include? variable_name and
638
- possible_env_periods.include? env_period and
639
- possible_timesteps.include? timestep
640
- #the query is valid
641
- time_series = openstudio_sql_file.timeSeries(env_period, timestep, variable_name, key_value)
642
- if time_series #checks to see if time_series exists
643
- time_series = time_series.get.values
644
- debug_puts " #{key_value} time series length = #{time_series.size}"
645
- for i in 0..(time_series.size - 1)
646
- #puts "#{i.to_s} -- #{time_series[i]}"
647
- time_series_array << time_series[i]
648
- end
649
- end
650
- else
651
- #do this if the query is not valid. The comments might help troubleshoot.
652
- time_series_array = [nil]
653
- debug_puts "***The pieces below do NOT make a valid query***"
654
- debug_puts " *#{key_value}* - this key value might not exist for the variable you are looking for"
655
- debug_puts " *#{timestep}* - this value should be Hourly, Monthly, Zone Timestep, HVAC System Timestep, etc"
656
- debug_puts " *#{variable_name}* - every word should be capitalized EG: Refrigeration System Total Compressor Electric Energy "
657
- debug_puts " *#{env_period}* - you can get an array of all the valid env periods by using the sql_file.availableEnvPeriods() method "
658
- debug_puts " Possible key values: #{possible_key_values}"
659
- debug_puts " Possible Variable Names: #{possible_variable_names}"
660
- debug_puts " Possible run periods: #{possible_env_periods}"
661
- debug_puts " Possible timesteps: #{possible_timesteps}"
662
- end
663
- return time_series_array
664
- end
665
-
666
- #gets the average of the numbers in an array
667
- def non_zero_array_average(arr)
668
- debug_puts "average of the entire array = #{arr.inject{ |sum, el| sum + el }.to_f / arr.size}"
669
- arr.delete(0)
670
- debug_puts "average of the non-zero numbers in the array = #{arr.inject{ |sum, el| sum + el }.to_f / arr.size}"
671
- return arr.inject{ |sum, el| sum + el }.to_f / arr.size
672
- end
673
-
674
- #method for converting from IP to SI if you know the strings of the input and the output
675
- def ip_to_si(number, ip_unit_string, si_unit_string)
676
- ip_unit = OpenStudio::createUnit(ip_unit_string, "IP".to_UnitSystem).get
677
- si_unit = OpenStudio::createUnit(si_unit_string, "SI".to_UnitSystem).get
678
- #puts "#{ip_unit} --> #{si_unit}"
679
- ip_quantity = OpenStudio::Quantity.new(number, ip_unit)
680
- si_quantity = OpenStudio::convert(ip_quantity, si_unit).get
681
- #puts "#{ip_quantity} = #{si_quantity}"
682
- return si_quantity.value
683
- end
684
-
685
- def self.compile_qaqc_results(output_folder)
686
- full_json = []
687
- Dir.foreach("#{output_folder}") do |folder|
688
- next if folder == '.' or folder == '..'
689
- Dir.glob("#{output_folder}/#{folder}/qaqc.json") { |item|
690
- puts "Reading #{output_folder}/#{folder}/qaqc.json"
691
- json = JSON.parse(File.read(item))
692
- json['eplusout_err']['warnings'] = json['eplusout_err']['warnings'].size
693
- json['eplusout_err']['severe'] = json['eplusout_err']['warnings'].size
694
- json['eplusout_err']['fatal'] = json['eplusout_err']['warnings'].size
695
- json['run_uuid'] = SecureRandom.uuid
696
- bldg = json['building']['name'].split('-')
697
- json['building_type'] = bldg[1]
698
- json['template'] = bldg[0]
699
- full_json << json
700
- }
701
- end
702
- File.open("#{output_folder}/../RESULTS-#{Time.now.strftime("%m-%d-%Y")}.json", 'w') {|f| f.write(JSON.pretty_generate(full_json)) }
703
- end
704
-
705
150
  def self.compare_osm_files(model_true, model_compare)
706
151
  only_model_true = [] # objects only found in the true model
707
152
  only_model_compare = [] # objects only found in the compare model
@@ -837,438 +282,90 @@ module BTAP
837
282
  diffs << "For #{true_object.iddObject.name} called '#{true_object.name}' field '#{field_name}': before model = #{true_value}, after model = #{compare_value}"
838
283
 
839
284
  end
840
-
841
285
  end
842
286
 
843
287
  return diffs
844
288
  end
845
289
 
846
- # This method converts an idf object to a hash
847
- #
848
- # @author Padmassun Rajakareyar
849
- # @param obj [OpenStudio::ModelObject]
850
- # @return new_obj_hash [Hash] idf object converted to hash.
851
- # @example
852
- # Converts the following IDF (openstudio::ModelObject) to
853
- # ==================================================================
854
- # OS:Material,
855
- # {8adb3faa-8e6a-48e3-bd73-ba6a02154b02}, !- Handle
856
- # 1/2IN Gypsum, !- Name
857
- # Smooth, !- Roughness
858
- # 0.0127, !- Thickness {m}
859
- # 0.16, !- Conductivity {W/m-K}
860
- # 784.9, !- Density {kg/m3}
861
- # 830.000000000001, !- Specific Heat {J/kg-K}
862
- # 0.9, !- Thermal Absorptance
863
- # 0.4, !- Solar Absorptance
864
- # 0.4; !- Visible Absorptance
865
- # ===================================================================
866
- #
867
- # ===================================================================
868
- # {
869
- # "Handle": "{8adb3faa-8e6a-48e3-bd73-ba6a02154b02}",
870
- # "Name": "1/2IN Gypsum",
871
- # "Roughness": "Smooth",
872
- # "Thickness {m}": "0.0127",
873
- # "Conductivity {W/m-K}": "0.16",
874
- # "Density {kg/m3}": "784.9",
875
- # "Specific Heat {J/kg-K}": "830.000000000001",
876
- # "Thermal Absorptance": "0.9",
877
- # "Solar Absorptance": "0.4",
878
- # "Visible Absorptance": "0.4"
879
- # },
880
- # ===================================================================
881
- def self.idf_to_h(obj)
882
- # split idf object by line
883
- obj_string = obj.to_s.split("\n")
884
- new_obj_hash = {}
885
-
886
- # itterate through each line and split the value and field
887
- # and assign it to the hash
888
- obj_string.each_with_index {|line,i|
889
- next if i == 0
890
- line.gsub!(/(\,|\;)/, '') # remove commas and semi-colons
891
- line.strip! # remove whitespace at the end and the beginning of the string
892
- v,k = line.split(/\s*\!\-\s+/) # split the line into at the string '!-' including the spaces before and after
893
- new_obj_hash[k] = v
894
- }
895
- new_obj_hash
896
- end
897
-
898
- # This method uses idf_to_h(obj) method, but deletes the fields named 'Handle' and 'Name'
899
- #
900
- # @author Padmassun Rajakareyar
901
- # @param obj [OpenStudio::ModelObject]
902
- # @return new_obj_hash [Hash] idf object converted to hash.
903
- # @example
904
- # Converts the following IDF (openstudio::ModelObject) to
905
- # ==================================================================
906
- # OS:Material,
907
- # {8adb3faa-8e6a-48e3-bd73-ba6a02154b02}, !- Handle
908
- # 1/2IN Gypsum, !- Name
909
- # Smooth, !- Roughness
910
- # 0.0127, !- Thickness {m}
911
- # 0.16, !- Conductivity {W/m-K}
912
- # 784.9, !- Density {kg/m3}
913
- # 830.000000000001, !- Specific Heat {J/kg-K}
914
- # 0.9, !- Thermal Absorptance
915
- # 0.4, !- Solar Absorptance
916
- # 0.4; !- Visible Absorptance
917
- # ===================================================================
918
- #
919
- # ===================================================================
920
- # {
921
- # "Roughness": "Smooth",
922
- # "Thickness {m}": "0.0127",
923
- # "Conductivity {W/m-K}": "0.16",
924
- # "Density {kg/m3}": "784.9",
925
- # "Specific Heat {J/kg-K}": "830.000000000001",
926
- # "Thermal Absorptance": "0.9",
927
- # "Solar Absorptance": "0.4",
928
- # "Visible Absorptance": "0.4"
929
- # },
930
- # ===================================================================
931
- def self.idf_to_h_clean(obj)
932
- # converts the idf object to hash
933
- idf_hash = idf_to_h(obj)
934
-
935
- # remove the field named `Handle` and `Name` from the idf_hash
936
- idf_hash.delete("Handle") if idf_hash.key?("Handle")
937
- idf_hash.delete("Handle".to_sym) if idf_hash.key?("Handle".to_sym)
938
-
939
- idf_hash.delete("Name") if idf_hash.key?("Name")
940
- idf_hash.delete("Name".to_sym) if idf_hash.key?("Name".to_sym)
941
-
942
- # Loop through idf_hash and delete any field that matched the UUID regex
943
- # idf_hash.each {|k,v|
944
- # idf_hash.delete(k) if /^\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\}$/.match(v)
945
- # }
946
- # idf_hash
947
- end
948
-
949
- # This method will group similar objects under a key that has all the
950
- # values of an IDF object which excludes Handles and Names.
951
- # NOTE: The objexts grouped should have the fields in the same order.
952
- # If not, then it would not be consired as a duplicate.
953
- #
954
- # @author Padmassun Rajakareyar
955
- # @param obj_array [Array] An array of idf_hash. Each idf_hash was created by idf_to_h_clean or idf_to_h method
956
- # @return grouped_objs [Hash] grouped idf_hash based on values within the obj_array.
957
- # @example Output:
958
- # "[\"Smooth\", \"0.216479986995276\", \"0.9\", \"0.7\", \"0.8\"]": [
959
- # {
960
- # "Handle": "{a7c45cf6-166d-48a6-9750-efb5c3386d91}",
961
- # "Name": "Typical Carpet Pad",
962
- # "Roughness": "Smooth",
963
- # "Thermal Resistance {m2-K/W}": "0.216479986995276",
964
- # "Thermal Absorptance": "0.9",
965
- # "Solar Absorptance": "0.7",
966
- # "Visible Absorptance": "0.8"
967
- # },
968
- # {
969
- # "Handle": "{9873fb84-bfaf-459d-8b70-2c3f5722bda9}",
970
- # "Name": "Typical Carpet Pad 1",
971
- # "Roughness": "Smooth",
972
- # "Thermal Resistance {m2-K/W}": "0.216479986995276",
973
- # "Thermal Absorptance": "0.9",
974
- # "Solar Absorptance": "0.7",
975
- # "Visible Absorptance": "0.8"
976
- # },
977
- # {
978
- # "Handle": "{63a12315-1de4-453a-b154-e6e6e9871be2}",
979
- # "Name": "Typical Carpet Pad 4",
980
- # "Roughness": "Smooth",
981
- # "Thermal Resistance {m2-K/W}": "0.216479986995276",
982
- # "Thermal Absorptance": "0.9",
983
- # "Solar Absorptance": "0.7",
984
- # "Visible Absorptance": "0.8"
985
- # }
986
- # ],
987
- def self.group_similar_objects(obj_array)
988
- # group [objects] by values except Handles and Name
989
- grouped_objs = obj_array.group_by{ |idf_hash|
990
- out = []
991
- # skip Handle and Name keys
992
- #
993
- # ideally the `keys` of the `idf_hash` should be sorted,
994
- # but I'll leave it alone for now
995
- idf_hash.each {|key, val|
996
- next if key == "Handle"
997
- next if key == "Name"
998
- out << val # push all the values into an array. This becomes the key of the hash that contains the duplicate objects
999
- }
1000
- out
1001
- }
1002
-
1003
- # Sort the grouped [objects] by Name.
1004
- # This is doen such that the first object will always have the smaller name
1005
- grouped_objs.each {|key, dup_array|
1006
- dup_array.sort_by{|idf_hash|
1007
- # puts idf_hash
1008
- idf_hash['Name'] # Sort by Name
1009
- }
1010
- }
1011
- return grouped_objs
1012
- end
1013
-
1014
- # Replace the UUID of the duplicate material, unless it contains ", !- Handle"
1015
- # This is done so after the model has been written to the disk, It can be read and
1016
- # the duplicate materials can be removed safely.
1017
- #
1018
- # @author Padmassun Rajakareyar
1019
- # @param model [OpenStudio::Model]
1020
- # @param grouped_objs [Hash] Output provided by group_similar_objects()
1021
- # @return model_string [String] An OpenStudio::Model object converted to a string
1022
- def self.replace_duplicate_obj_handles(model, grouped_objs)
1023
- model_string = model.to_s # convert the OS:Model into a String
1024
- grouped_objs.each {|key, dup_array|
1025
- dup_array.each_with_index {|idf_hash, index |
1026
- next if index == 0 # skipping index 0, because it has the shortest name and considered as the original
1027
-
1028
- # givn that the idf_hash['Handle'] => '{8c88931b-e19d-479b-ac71-138d18c97cc9}'
1029
- # The following regex matches "{8c88931b-e19d-479b-ac71-138d18c97cc9}" in the following line
1030
- # {8c88931b-e19d-479b-ac71-138d18c97cc9}, !- Layer 1
1031
- #
1032
- # but matches nothing if the line has the keyword '!- Handle' in it e.g.
1033
- # {8c88931b-e19d-479b-ac71-138d18c97cc9}, !- Handle
1034
- replace_regex = idf_hash['Handle'].to_s.gsub('{', '\{'). # escape brackets
1035
- gsub('}', '\}'). # escape brackets
1036
- gsub('-', '\-') + # escape dashes
1037
- '(?!.*?\!\-(\s)*Handle)' # making sure the matched handle is not part of the line that contains the substring '!- Handle'
1038
- replace_regex = Regexp.new(replace_regex)
1039
- # p replace_regex
1040
-
1041
- # replace duplicate handles with the handle found at index 0
1042
- model_string.gsub!(replace_regex, dup_array[0]['Handle'])# {|match| puts match; dup_array[0]['Handle']}
1043
- }
1044
- }
1045
- return model_string
1046
- end
1047
-
1048
- # This method gets the model string, writes it in a temporary location, reads it, and
1049
- # converts it to an OpenStudio Model using a VersionTranslater
1050
- #
1051
- # @author Padmassun Rajakareyar
1052
- # @param model_string [String] An OpenStudio::Model object converted to a string
1053
- # @return model [OpenStudio::Model]
1054
- def self.get_OS_Model_from_string(model_string)
1055
- require 'securerandom'
1056
- require 'fileutils'
1057
- # make temerorary directory called `temp` to store the osm file
1058
- FileUtils.mkdir_p(File.join('.', 'temp'))
1059
- # Using SecureRandom to generate the UUID to keep the osm file unique
1060
- temp_filename =File.join('.', 'temp',SecureRandom.uuid.to_s + '.osm')
1061
- # write the model string as a file
1062
- File.open(temp_filename, 'w') { |file| file.write(model_string) }
1063
- # use a VersionTranslator to read the osm model
1064
- translator = OpenStudio::OSVersion::VersionTranslator.new
1065
- path = OpenStudio::Path.new(temp_filename)
1066
- # read the model
1067
- model = translator.loadModel(path)
1068
- model = model.get
1069
- # remove the temporary model
1070
- FileUtils.rm(temp_filename)
1071
- return model
1072
- end
1073
-
1074
- # Eleminates duplicate ModelObjects of the given ModelObject type,
1075
- # based on the values of each fields within the ModelObject
1076
- #
1077
- # @author Padmassun Rajakareyar
1078
- # @param model [OpenStudio::Model]
1079
- # @param model_obj_type [String] ModelObject type e.g. "OS:Material"
1080
- # @return new_model [OpenStudio::Model] Returns new model (OS:Model) if the changes were made.
1081
- # or else returns the old model if no changes were made.
1082
- def self.eleminate_duplicate_objs(model, model_obj_type) # = "OS:Material")
1083
- model_objs_json = {}
1084
-
1085
- # convert each of the ModelObjectas a hash for easy parsing
1086
- model.getModelObjects.sort.each {|obj|
1087
- # hsh = idf_to_h_clean(obj) # stores the idf converted to hash, without UUIDs and Name field
1088
-
1089
- # create a hash containing all the model objects sorted by the name of the model object.
1090
- # e.g the `OS:Construction` ModelObject type will e placed within the `OS:Construction` key, and
1091
- # each ModelObject has been converted to an idf_hash and pushed into the array with the appropriate key.
1092
- (model_objs_json[obj.iddObject.name.to_s] ||= []) << idf_to_h(obj) # unless hsh.empty?
1093
- }
1094
-
1095
- # isolate a single ModelObject type specified by the model_obj_type variable
1096
- mat_array = model_objs_json[model_obj_type]
1097
- if mat_array.nil? # return the old model if model_obj_type is not found
1098
- puts "Skipping because ModelObject of type [#{model_obj_type}] was not found"
1099
- return model
290
+ def self.set_all_epw_paths(osm_data)
291
+ osm_data.each do |hash|
292
+ hash.each do |key, value|
293
+ #Check if value is in a epw file path format like this "USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw"
294
+ if value.match?(/.*\.epw/)
295
+ # Get name of file without path from value
296
+ hash[key] = File.basename(value)
297
+ end
298
+ end
1100
299
  end
1101
- # group duplicates
1102
- grouped_objs = group_similar_objects(mat_array)
1103
- # replace handles of duplicate objects
1104
- model_string = replace_duplicate_obj_handles(model, grouped_objs)
1105
- # write the model string to a file and read it as a model
1106
- new_model = get_OS_Model_from_string(model_string)
300
+ end
1107
301
 
1108
- # Now loop through each of the grouped objects. skip the first one, and get ModelObjects (from new osm file)
1109
- # by name and safely remove all the duplicates
1110
- grouped_objs.each {|key, dup_array|
1111
- dup_array.each_with_index {|object, index |
1112
- next if index == 0
1113
- unless object.key?('Name') # if the idf_hash does not have a key called Name, Skip it.
1114
- puts "Skipping ModelObject of type [#{model_obj_type}] With data [#{object.inspect}] does not have a field called 'Name'"
1115
- next
1116
- end
1117
- name = object['Name']
1118
- # puts "object: [#{object}]"
1119
- # puts "object['Name']: [#{object['Name']}]"
1120
- # get the object to delete by name
1121
- obj_to_delete = new_model.getModelObjectByName(name)
1122
- if obj_to_delete.empty? # check if the object to be deleted is initialized (or present within the new model)
1123
- puts "ModelObject of type [#{model_obj_type}] with name [#{object['Name']}] does not exist in new model"
1124
- else
1125
- puts "ModelObject of type [#{model_obj_type}] with name [#{object['Name']}] was deleted"
1126
- obj_to_delete = obj_to_delete.get # get the modelObject if it is initialized
1127
- obj_to_delete.remove # remove object form the model
302
+ def self.set_all_data_time(osm_data)
303
+ osm_data.each do |hash|
304
+ hash.each do |key, value|
305
+ #Check if value is in a date-time format like this "2024-10-17 01:19:35 UTC"
306
+ if value.match?(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/)
307
+ hash[key] = "2024-01-01 01:00:00 UTC"
1128
308
  end
1129
- }
1130
- }
1131
- # File.open('./models/after.osm', 'w') { |file| file.write(new_model.to_s) }
1132
-
1133
-
1134
- # File.open("./models/grp_#{model_obj_type}.json", 'w') { |file| file.write(JSON.pretty_generate(grouped_objs)) }
1135
-
1136
- return new_model
1137
- # File.open('./models/after.osm', 'w') { |file| file.write(model_string) }
1138
- # puts("\n\n\n" + "=="*10 + "\n\n")
1139
- # puts JSON.pretty_generate(grouped_mats)
309
+ end
310
+ end
1140
311
  end
1141
312
 
1142
- # Eleminates duplicate Materials and Construction Objects
1143
- # The list of ModelObjects that are removed are:
1144
- # "OS:Material",
1145
- # "OS:Material:NoMass"
1146
- # "OS:WindowMaterial:SimpleGlazingSystem"
1147
- # "OS:WindowMaterial:Glazing"
1148
- # "OS:WindowMaterial:Gas"
1149
- # "OS:StandardsInformation:Material"
1150
- # "OS:Construction"
1151
- # "OS:DefaultSurfaceConstructions"
1152
- # "OS:DefaultSubSurfaceConstructions"
1153
- # "OS:DefaultConstructionSet"
1154
- # "OS:StandardsInformation:Construction"
1155
- #
1156
- # @author Padmassun Rajakareyar
1157
- # @param model [OpenStudio::Model]
1158
- # @return new_model [OpenStudio::Model] Returns new model after removing the
1159
- # duplicate ModelObjects atated abioe.
1160
- def self.remove_duplicate_materials_and_constructions(model)
1161
- old_number_of_objects = model.getModelObjects.length
1162
- new_model = model
1163
- # eleminate dplicate Material objects
1164
- obj_types = [
1165
- "OS:Material",
1166
- "OS:Material:NoMass",
1167
- "OS:WindowMaterial:SimpleGlazingSystem",
1168
- "OS:WindowMaterial:Glazing",
1169
- "OS:WindowMaterial:Gas",
1170
- "OS:StandardsInformation:Material"
1171
- ]
1172
-
1173
- obj_types.each {|model_obj_type|
1174
- new_model = eleminate_duplicate_objs(new_model, model_obj_type)
1175
- }
1176
-
1177
- # eleminate dplicate Construction objects
1178
- obj_types = [
1179
- "OS:Construction",
1180
- "OS:DefaultSurfaceConstructions",
1181
- "OS:DefaultSubSurfaceConstructions",
1182
- "OS:DefaultConstructionSet",
1183
- "OS:StandardsInformation:Construction",
1184
- ]
1185
- obj_types.each {|model_obj_type|
1186
- new_model = eleminate_duplicate_objs(new_model, model_obj_type)
1187
- }
1188
-
1189
- new_number_of_objects = new_model.getModelObjects.length
1190
-
1191
- puts "Number of objects removed: #{old_number_of_objects - new_number_of_objects}"
1192
- return new_model
313
+ def self.save_osm_file(osm_data, output_path)
314
+ File.open(output_path, 'w') do |file|
315
+ osm_data.each do |hash|
316
+ file.puts "#{hash["type"]},"
317
+ hash.each do |key, value|
318
+ next if key == "type"
319
+ is_last_key = (hash.keys - [:type]).last == key
320
+ if key == :id
321
+ file.puts " #{value},"
322
+ else
323
+ file.puts " #{value}#{is_last_key ? ';' : ','}".ljust(42) + " !- #{key}"
324
+ end
325
+ end
326
+ file.puts
327
+ end
328
+ end
1193
329
  end
1194
330
 
331
+ def self.rename_handles(osm_data)
1195
332
 
1196
- def self.clean_osm_file( file_path: ,
1197
- output_path: )
333
+ hash = Digest::SHA1.hexdigest("fixed_seed")
334
+ handle_map = {}
335
+ new_handle_index = 0
336
+ object_type_index = 0
337
+ last_object_type = nil
1198
338
 
1199
- def self.read_osm_file(file_path)
1200
- data = []
1201
- current_hash = {}
1202
-
1203
- File.foreach(file_path) do |line|
1204
- line.strip!
1205
- next if line.empty?
1206
-
1207
- if line.start_with?('OS:')
1208
- current_hash = { type: line.split(/[;,]/).first }
1209
- data << current_hash
1210
- elsif line.include?('!-')
1211
- key, value = line.split('!-').map(&:strip)
1212
- current_hash[value] = key.chomp(',;').strip
339
+ osm_data.each do |hash|
340
+ if hash['Handle']
341
+ #puts hash
342
+ #puts "#{hash['type']} == #{last_object_type}"
343
+ if hash['type'] != last_object_type
344
+ last_object_type = hash['type']
345
+ object_type_index += 1
346
+ new_handle_index = 1
1213
347
  else
1214
- current_hash[:id] ||= line.chomp(',;').strip
348
+ new_handle_index += 1
1215
349
  end
350
+
351
+ # create a formatted string that is 12 charecters created with new_handle_index, but padded with 0s on the left.
352
+ formatted_index = new_handle_index.to_s.rjust(12, '0')
353
+ formatted_index_object_type = object_type_index.to_s.rjust(4, '0')
354
+ new_handle = "{00000000-0000-0000-#{formatted_index_object_type}-#{formatted_index}}"
355
+ handle_map[hash['Handle']] = new_handle
356
+ hash['Handle'] = new_handle
1216
357
  end
1217
-
1218
- data
1219
358
  end
1220
-
1221
- def self.remove_special_characters(hash)
1222
- hash.keys.each do |key|
1223
- new_key = key.to_s.sub(/[;,]$/, '')
1224
- hash[new_key] = hash.delete(key)
1225
- end
1226
-
359
+
360
+ osm_data.each do |hash|
1227
361
  hash.each do |key, value|
1228
- next unless value.is_a?(String)
1229
-
1230
- hash[key] = value.gsub(/[;,]$/, '')
362
+ if handle_map.key?(value)
363
+ hash[key] = handle_map[value]
364
+ end
1231
365
  end
1232
366
  end
1233
-
1234
- def self.rename_handles(osm_data)
1235
-
1236
- hash = Digest::SHA1.hexdigest("fixed_seed")
1237
- handle_map = {}
1238
- new_handle_index = 0
1239
- object_type_index = 0
1240
- last_object_type = nil
1241
-
1242
- osm_data.each do |hash|
1243
- if hash['Handle']
1244
- #puts hash
1245
- #puts "#{hash['type']} == #{last_object_type}"
1246
- if hash['type'] != last_object_type
1247
- last_object_type = hash['type']
1248
- object_type_index += 1
1249
- new_handle_index = 1
1250
- else
1251
- new_handle_index += 1
1252
- end
1253
367
 
1254
- # create a formatted string that is 12 charecters created with new_handle_index, but padded with 0s on the left.
1255
- formatted_index = new_handle_index.to_s.rjust(12, '0')
1256
- formatted_index_object_type = object_type_index.to_s.rjust(4, '0')
1257
- new_handle = "{00000000-0000-0000-#{formatted_index_object_type}-#{formatted_index}}"
1258
- handle_map[hash['Handle']] = new_handle
1259
- hash['Handle'] = new_handle
1260
- end
1261
- end
1262
-
1263
- osm_data.each do |hash|
1264
- hash.each do |key, value|
1265
- if handle_map.key?(value)
1266
- hash[key] = handle_map[value]
1267
- end
1268
- end
1269
- end
1270
-
1271
- # Go through all hashes in osm_data and if the "Name" field value fits this pattern {9386c18d-e70a-447e-8b69-9a0a39fd8f06} replace it with an incremented value.
368
+ # Go through all hashes in osm_data and if the "Name" field value fits this pattern {9386c18d-e70a-447e-8b69-9a0a39fd8f06} replace it with an incremented value.
1272
369
  name_map = {}
1273
370
  new_name_index = 0
1274
371
  osm_data.each do |hash|
@@ -1288,58 +385,40 @@ module BTAP
1288
385
  end
1289
386
  end
1290
387
  end
388
+ end
1291
389
 
1292
-
1293
- end
1294
-
1295
-
1296
-
1297
-
1298
- def self.save_osm_file(osm_data, output_path)
1299
- File.open(output_path, 'w') do |file|
1300
- osm_data.each do |hash|
1301
- file.puts "#{hash["type"]},"
1302
- hash.each do |key, value|
1303
- next if key == "type"
1304
- is_last_key = (hash.keys - [:type]).last == key
1305
- if key == :id
1306
- file.puts " #{value},"
1307
- else
1308
- file.puts " #{value}#{is_last_key ? ';' : ','}".ljust(42) + " !- #{key}"
1309
- end
1310
- end
1311
- file.puts
1312
- end
1313
- end
390
+ def self.remove_special_characters(hash)
391
+ hash.keys.each do |key|
392
+ new_key = key.to_s.sub(/[;,]$/, '')
393
+ hash[new_key] = hash.delete(key)
1314
394
  end
1315
395
 
1316
- def self.set_all_data_time(osm_data)
1317
- osm_data.each do |hash|
1318
- hash.each do |key, value|
1319
- #Check if value is in a date-time format like this "2024-10-17 01:19:35 UTC"
1320
- if value.match?(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/)
1321
- hash[key] = "2024-01-01 01:00:00 UTC"
1322
- end
1323
- end
1324
- end
396
+ hash.each do |key, value|
397
+ next unless value.is_a?(String)
398
+ hash[key] = value.gsub(/[;,]$/, '')
1325
399
  end
400
+ end
1326
401
 
1327
- def self.set_all_epw_paths(osm_data)
1328
- osm_data.each do |hash|
1329
- hash.each do |key, value|
1330
- #Check if value is in a epw file path format like this "USA_CA_San.Francisco.Intl.AP.724940_TMY3.epw"
1331
- if value.match?(/.*\.epw/)
1332
- # Get name of file without path from value
1333
- hash[key] = File.basename(value)
1334
- end
1335
- end
402
+ def self.clean_osm_file(file_path:, output_path:)
403
+ osm_data = []
404
+ current_hash = {}
405
+ File.foreach(file_path) do |line|
406
+ line.strip!
407
+ next if line.empty?
408
+ if line.start_with?('OS:')
409
+ current_hash = { type: line.split(/[;,]/).first }
410
+ osm_data << current_hash
411
+ elsif line.include?('!-')
412
+ key, value = line.split('!-').map(&:strip)
413
+ current_hash[value] = key.chomp(',;').strip
414
+ elsif line.include?('! ') # for lines that have '! ' but not '!-'
415
+ key, value = line.split('! ').map(&:strip)
416
+ current_hash[value] = key.chomp(',;').strip
417
+ else
418
+ current_hash[:id] ||= line.chomp(',;').strip
1336
419
  end
1337
420
  end
1338
-
1339
-
1340
- osm_data = read_osm_file(file_path)
1341
421
  osm_data.sort_by! { |hash| [hash[:type], hash['Name'] || ''] }
1342
-
1343
422
  handles = osm_data.map { |hash| hash['Handle'] }.compact
1344
423
  duplicates = handles.select { |e| handles.count(e) > 1 }
1345
424
  raise "Duplicate handles found: #{duplicates.uniq}" if duplicates.any?
@@ -1348,19 +427,22 @@ module BTAP
1348
427
  set_all_data_time(osm_data)
1349
428
  set_all_epw_paths(osm_data)
1350
429
  rename_handles(osm_data)
1351
- save_osm_file(osm_data, output_path)
1352
- end
1353
430
 
1354
- def self.get_relative_path(abs_path_to_file, working_folder)
1355
- path = Pathname.new(abs_path_to_file).relative_path_from(Pathname.new(working_folder)).to_s
1356
- file = File.basename(abs_path_to_file)
1357
- rel_path= File.join(path)
1358
- # Check if file exists
1359
- if File.file?(File.join(working_folder, rel_path))
1360
- return rel_path
1361
- else
1362
- raise "File does not exist at #{File.join(working_folder, rel_path)}"
431
+ File.open(output_path, 'w') do |file|
432
+ osm_data.each do |hash|
433
+ file.puts "#{hash["type"]},"
434
+ hash.each do |key, value|
435
+ next if key == "type"
436
+ is_last_key = (hash.keys - [:type]).last == key
437
+ if key == :id
438
+ file.puts " #{value},"
439
+ else
440
+ file.puts " #{value}#{is_last_key ? ';' : ','}".ljust(42) + " !- #{key}"
441
+ end
442
+ end
443
+ file.puts
444
+ end
1363
445
  end
1364
446
  end
1365
- end #FileIO
1366
- end #BTAP
447
+ end
448
+ end