class CFASTimporter(): def __init__(self): # {{{ self.json = Json() self.conf = self.json.read("{}/conf.json".format( os.environ['AAMKS_PROJECT'])) if self.conf['fire_model'] == 'FDS': return self.s = Sqlite("{}/aamks.sqlite".format(os.environ['AAMKS_PROJECT'])) self.raw_geometry = self.json.read("{}/cad.json".format( os.environ['AAMKS_PROJECT'])) self.geomsMap = self.json.read("{}/inc.json".format( os.environ['AAMKS_PATH']))['aamksGeomsMap'] self.doors_width = 32 self.walls_width = 4 self._geometry2sqlite() self._enhancements() self._init_dd_geoms() self._towers_slices() self._floors_meta() self._world_meta() self._aamks_geom_into_polygons() self._make_id2compa_name() self._find_intersections_within_floor() self._get_faces() self._hvents_per_room() self._find_intersections_between_floors() self._vvents_per_room() self._add_names_to_vents_from_to() self._recalculate_vents_from_to() self._calculate_sills() self._terminal_doors() self._auto_detectors_and_sprinklers() self._assert_faces_ok() self._assert_room_has_door() self._debug() # }}} def _floors_meta(self): # {{{ ''' Floor dimensions are needed here and there. for z_absolute high towers are not taken under account, we want the natural floor's zdim for z_relative high towers are not taken under account, we want the natural floor's zdim ''' self.floors_meta = OrderedDict() self._world3d = dict() self._world3d['minx'] = 9999999 self._world3d['maxx'] = -9999999 self._world3d['miny'] = 9999999 self._world3d['maxy'] = -9999999 prev_maxz = 0 for floor in self.floors: ty = 0 tx = 0 minx = self.s.query( "SELECT min(x0) AS minx FROM aamks_geom WHERE floor=?", (floor, ))[0]['minx'] maxx = self.s.query( "SELECT max(x1) AS maxx FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxx'] miny = self.s.query( "SELECT min(y0) AS miny FROM aamks_geom WHERE floor=?", (floor, ))[0]['miny'] maxy = self.s.query( "SELECT max(y1) AS maxy FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxy'] minz_abs = self.s.query( "SELECT min(z0) AS minz FROM aamks_geom WHERE type_sec NOT IN('STAI','HALL') AND floor=?", (floor, ))[0]['minz'] maxz_abs = self.s.query( "SELECT max(z1) AS maxz FROM aamks_geom WHERE type_sec NOT IN('STAI','HALL') AND floor=?", (floor, ))[0]['maxz'] zdim = maxz_abs - prev_maxz prev_maxz = maxz_abs xdim = maxx - minx ydim = maxy - miny center = (minx + int(xdim / 2), miny + int(ydim / 2), minz_abs) self.floors_meta[floor] = OrderedDict([('name', floor), ('xdim', xdim), ('ydim', ydim), ('center', center), ('minx', minx), ('miny', miny), ('maxx', maxx), ('maxy', maxy), ('minz_abs', minz_abs), ('maxz_abs', maxz_abs), ('zdim', zdim), ('ty', ty), ('tx', tx)]) self._world3d['minx'] = min(self._world3d['minx'], minx) self._world3d['maxx'] = max(self._world3d['maxx'], maxx) self._world3d['miny'] = min(self._world3d['miny'], miny) self._world3d['maxy'] = max(self._world3d['maxy'], maxy) self.s.query("CREATE TABLE floors_meta(json)") self.s.query('INSERT INTO floors_meta VALUES (?)', (json.dumps(self.floors_meta), )) # }}} def _world_meta(self): # {{{ self.s.query("CREATE TABLE world_meta(json)") self.world_meta = {} self.world_meta['world3d'] = self._world3d self.world_meta['walls_width'] = self.walls_width self.world_meta['doors_width'] = self.doors_width if len(self.floors_meta) > 1: self.world_meta['multifloor_building'] = 1 else: self.world_meta['multifloor_building'] = 0 self.s.query('INSERT INTO world_meta(json) VALUES (?)', (json.dumps(self.world_meta), )) # }}} def _geometry2sqlite(self): # {{{ ''' Parse geometry and place geoms in sqlite. The lowest floor is always 0. The self.raw_geometry example for floor("0"): "0": [ "ROOM": [ [ [ 3.0 , 4.8 , 0.0 ] , [ 4.8 , 6.5 , 3.0 ] ] , [ [ 3.0 , 6.5 , 0.0 ] , [ 6.8 , 7.4 , 3.0 ] ] ] ] Some columns in db are left empty for now. Sqlite's aamks_geom table must use two unique ids a) 'name' for visualisation and b) 'global_type_id' for cfast enumeration. ''' data = [] for floor, gg in self.raw_geometry.items(): for k, arr in gg.items(): if k in ('UNDERLAY'): continue for v in arr: rr = { 'x0': int(v[0][0]), 'y0': int(v[0][1]), 'x1': int(v[1][0]), 'y1': int(v[1][1]), 'z0': int(v[0][2]), 'z1': int(v[1][2]) } points = ((rr['x0'], rr['y0']), (rr['x1'], rr['y0']), (rr['x1'], rr['y1']), (rr['x0'], rr['y1']), (rr['x0'], rr['y0'])) width = rr['x1'] - rr['x0'] depth = rr['y1'] - rr['y0'] height = rr['z1'] - rr['z0'] attrs = self._prepare_attrs(v[2]) record = self._prepare_geom_record(k, rr, points, width, depth, height, floor, attrs) if record != False: data.append(record) self.s.query( "CREATE TABLE aamks_geom(name,floor,global_type_id,hvent_room_seq,vvent_room_seq,type_pri,type_sec,type_tri,x0,y0,z0,width,depth,height,cfast_width,sill,face,face_offset,vent_from,vent_to,material_ceiling,material_floor,material_wall,heat_detectors,smoke_detectors,sprinklers,is_vertical,vent_from_name,vent_to_name, how_much_open, room_area, x1, y1, z1, center_x, center_y, center_z, fire_model_ignore, mvent_throughput, exit_type, room_enter, terminal_door, points)" ) self.s.executemany( 'INSERT INTO aamks_geom VALUES ({})'.format(','.join( '?' * len(data[0]))), data) #dd(self.s.dump()) #}}} def _prepare_attrs(self, attrs): # {{{ aa = {"mvent_throughput": None, "exit_type": None, "room_enter": None} for k, v in attrs.items(): aa[k] = v return aa # }}} def _prepare_geom_record(self, k, rect, points, width, depth, height, floor, attrs): # {{{ ''' Format a record for sqlite. Hvents get fixed width self.doors_width cm ''' # OBST if k in ('OBST', ): type_pri = 'OBST' type_tri = '' # EVACUEE elif k in ('EVACUEE', ): type_pri = 'EVACUEE' type_tri = '' # FIRE elif k in ('FIRE', ): type_pri = 'FIRE' type_tri = '' # MVENT elif k in ('MVENT', ): type_pri = 'MVENT' type_tri = '' # VVENT elif k in ('VVENT', ): type_pri = 'VVENT' height = 10 type_tri = '' # COMPA elif k in ('ROOM', 'COR', 'HALL', 'STAI'): type_pri = 'COMPA' type_tri = '' # HVENT elif k in ('DOOR', 'DCLOSER', 'DELECTR', 'HOLE', 'WIN'): width = max(width, self.doors_width) depth = max(depth, self.doors_width) type_pri = 'HVENT' if k in ('DOOR', 'DCLOSER', 'DELECTR', 'HOLE'): type_tri = 'DOOR' elif k in ('WIN'): type_tri = 'WIN' global_type_id = attrs['idx'] name = '{}{}'.format(self.geomsMap[k], global_type_id) #self.s.query("CREATE TABLE aamks_geom(name , floor , global_type_id , hvent_room_seq , vvent_room_seq , type_pri , type_sec , type_tri , x0 , y0 , z0 , width , depth , height , cfast_width , sill , face , face_offset , vent_from , vent_to , material_ceiling , material_floor , material_wall , heat_detectors , smoke_detectors , sprinklers , is_vertical , vent_from_name , vent_to_name , how_much_open , room_area , x1 , y1 , z1 , center_x , center_y , center_z , fire_model_ignore , mvent_throughput , exit_type , room_enter , terminal_door , points)") return (name, floor, global_type_id, None, None, type_pri, k, type_tri, rect['x0'], rect['y0'], rect['z0'], width, depth, height, None, None, None, None, None, None, self.conf['material_ceiling']['type'], self.conf['material_floor']['type'], self.conf['material_wall']['type'], 0, 0, 0, None, None, None, None, None, None, None, None, None, None, None, 0, attrs['mvent_throughput'], attrs['exit_type'], attrs['room_enter'], None, json.dumps(points)) # }}} def _enhancements(self): # {{{ ''' Is HVENT vertical or horizontal? Apart from what is width and height for geometry, HVENTS have their cfast_width always along the wall Doors and Holes will be later interescted with parallel walls (obstacles). We inspect is_vertical and make the doors just enough smaller to avoid perpendicular intersections. Since obstacles are generated to right/top direction, we need to address the overlapping coming from left/bottom. So we make doors shorter at x0 and y0, but not at x1 and y1. At the end our door=90cm are now 86cm. ''' self.outside_compa = self.s.query( "SELECT count(*) FROM aamks_geom WHERE type_pri='COMPA'" )[0]['count(*)'] + 1 self.floors = [ z['floor'] for z in self.s.query( "SELECT DISTINCT floor FROM aamks_geom ORDER BY floor") ] self.all_doors = [ z['name'] for z in self.s.query( "SELECT name FROM aamks_geom WHERE type_tri='DOOR' ORDER BY name" ) ] self.s.query( "UPDATE aamks_geom SET is_vertical=0, cfast_width=width WHERE type_pri='HVENT' AND width > depth" ) self.s.query( "UPDATE aamks_geom SET is_vertical=1, cfast_width=depth WHERE type_pri='HVENT' AND width < depth" ) self.s.query( "UPDATE aamks_geom SET room_area=width*depth WHERE type_pri='COMPA'" ) self.s.query( "UPDATE aamks_geom SET x1=x0+width, y1=y0+depth, z1=z0+height, center_x=x0+width/2, center_y=y0+depth/2, center_z=z0+height/2" ) self.s.query( "UPDATE aamks_geom SET y0=y0+?, depth=depth-? WHERE type_tri='DOOR' AND is_vertical=1", (self.walls_width, self.walls_width)) self.s.query( "UPDATE aamks_geom SET x0=x0+?, width=width-? WHERE type_tri='DOOR' AND is_vertical=0", (self.walls_width, self.walls_width)) # }}} def _init_dd_geoms(self): # {{{ ''' dd_geoms are some optional extra rectangles, points, lines and circles that are written to on top of our geoms. Useful for developing and debugging features. Must come early, because visualization depends on it. Procedure: z=self.json.read('{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) z["0"]['rectangles'].append( { "xy": (0 , 0) , "width": 200 , "depth": 200 , "strokeColor": "#fff" , "strokeWidth": 2 , "fillColor": "#f80" , "opacity": 0.7 } ) z["0"]['circles'].append({ "xy": (i['center_x'], i['center_y']),"radius": 200, "fillColor": "#fff" , "opacity": 0.3 } ) self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) ''' z = dict() for floor in self.floors: z[floor] = dict() z[floor]['rectangles'] = [] z[floor]['lines'] = [] z[floor]['circles'] = [] z[floor]['texts'] = [] z[floor]['rectangles'] = [] #for i in self.s.query("SELECT * FROM aamks_geom WHERE type_tri='DOOR' AND floor=?", (floor,)): # z[floor]['circles'].append({ "xy": (i['center_x'] , i['center_y']) , "radius": 90 , "fillColor": "#fff" , "opacity": 0.05 } ) # Example usage anywhere inside aamks: # z=self.json.read('{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # z["0"]['rectangles'].append( { "xy": (1000 , 1000) , "width": 200 , "depth": 300 , "strokeColor": "#fff" , "strokeWidth": 2 , "fillColor": "#f80" , "opacity": 0.7 } ) # z["0"]['rectangles'].append( { "xy": (0 , 0) , "width": 200 , "depth": 200 , "strokeColor": "#fff" , "strokeWidth": 2 , "fillColor": "#f80" , "opacity": 0.7 } ) # z["0"]['lines'].append( { "xy": (2000 , 200) , "x1": 3400 , "y1": 500 , "strokeColor": "#fff" , "strokeWidth": 2 , "opacity": 0.7 } ) # z["0"]['texts'].append( { "xy": (1000 , 1000) , "content": "(1000x1000)" , "fontSize": 400 , "fillColor":"#06f" , "opacity":0.7 } ) # self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # Vis(None, 'image', 'dd_geoms example') self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # }}} def _make_id2compa_name(self): # {{{ ''' Create a map of ids to names for COMPAS, e.g. _id2compa_name[4]='ROOM_1_4' This map is stored to sqlite because path.py needs it. Still true after mesh travelling? ''' self._id2compa_name = OrderedDict() for v in self.s.query( "select name,global_type_id from aamks_geom where type_pri='COMPA' ORDER BY global_type_id" ): self._id2compa_name[v['global_type_id']] = v['name'] self._id2compa_name[self.outside_compa] = 'outside' # }}} def _add_names_to_vents_from_to(self): # {{{ ''' Vents from/to use indices, but names will be simpler for AAMKS and for debugging. Hvents/Vvents lower_id/higher_id are already taken care of in _find_intersections_between_floors() ''' query = [] for v in self.s.query( "select vent_from,vent_to,name from aamks_geom where type_pri IN('HVENT', 'VVENT')" ): query.append((self._id2compa_name[v['vent_from']], self._id2compa_name[v['vent_to']], v['name'])) self.s.executemany( 'UPDATE aamks_geom SET vent_from_name=?, vent_to_name=? WHERE name=?', query) # }}} def _recalculate_vents_from_to(self): # {{{ ''' CFAST requires that towers slices are mapped back to the original cuboids ''' update = [] for hi, lo in self.towers_parents.items(): z = self.s.query( "SELECT name,vent_from FROM aamks_geom WHERE type_pri='HVENT' AND vent_from=? OR vent_to=? ORDER BY name", (hi, hi)) for i in z: update.append( (min(lo, i['vent_from']), max(lo, i['vent_from']), i['name'])) self.s.executemany( "UPDATE aamks_geom SET vent_from=?, vent_to=? WHERE name=?", update) # }}} def _calculate_sills(self): # {{{ ''' Sill is the distance relative to the floor. Say there's a HVENT H between rooms A and B. Say A and B may have variate z0 (floor elevations). We find 'hvent_from' and then we use it's z0. This z0 is the needed 'relative 0' for the calcuation. ''' update = [] for v in self.s.query( "SELECT global_type_id, z0, vent_from FROM aamks_geom WHERE type_pri='HVENT' ORDER BY name" ): floor_baseline = self.s.query( "SELECT z0 FROM aamks_geom WHERE global_type_id=? AND type_pri='COMPA'", (v['vent_from'], ))[0]['z0'] update.append((v['z0'] - floor_baseline, v['global_type_id'])) self.s.executemany( "UPDATE aamks_geom SET sill=? WHERE type_pri='HVENT' AND global_type_id=?", update) # }}} def _terminal_doors(self): # {{{ ''' Doors that lead to outside or lead to staircases are terminal ''' terminal_rooms = self.s.query( "SELECT global_type_id FROM aamks_geom WHERE type_sec='STAI'") update = [] for i in terminal_rooms: z = self.s.query( "SELECT name,exit_type FROM aamks_geom WHERE type_pri='HVENT' AND (vent_from=? OR vent_to=? OR vent_to_name='outside')", (i['global_type_id'], i['global_type_id'])) for ii in z: update.append((ii['exit_type'], ii['name'])) self.s.executemany( "UPDATE aamks_geom SET terminal_door=? WHERE name=?", update) #dd(self.s.query("SELECT name,terminal_door,vent_from_name,vent_to_name from aamks_geom order by name")) # }}} def _auto_detectors_and_sprinklers(self): # {{{ if len(''.join([str(i) for i in self.conf['heat_detectors'].values()])) > 0: self.s.query( "UPDATE aamks_geom set heat_detectors = 1 WHERE type_pri='COMPA'" ) if len(''.join([str(i) for i in self.conf['smoke_detectors'].values()])) > 0: self.s.query( "UPDATE aamks_geom set smoke_detectors = 1 WHERE type_pri='COMPA'" ) if len(''.join([str(i) for i in self.conf['sprinklers'].values()])) > 0: self.s.query( "UPDATE aamks_geom set sprinklers = 1 WHERE type_pri='COMPA'") # }}} # INTERSECTIONS def _aamks_geom_into_polygons(self): # {{{ ''' aamks_geom into shapely polygons for intersections ''' self.aamks_polies = OrderedDict() self.aamks_polies['COMPA'] = OrderedDict() self.aamks_polies['HVENT'] = OrderedDict() self.aamks_polies['VVENT'] = OrderedDict() self.aamks_polies['MVENT'] = OrderedDict() for floor in self.floors: for elem in self.aamks_polies.keys(): self.aamks_polies[elem][floor] = OrderedDict() for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_pri NOT IN('OBST', 'EVACUEE', 'FIRE') AND floor=?", (floor, )): self.aamks_polies[v['type_pri']][floor][ v['global_type_id']] = box(v['x0'], v['y0'], v['x0'] + v['width'], v['y0'] + v['depth']) # }}} def _get_faces(self): # {{{ ''' Cfast faces and offsets calculation. HVENTS have self.doors_width, so we only consider intersection.length > self.doors_width. Faces are properties of the doors. They are calculated in respect to the room of lower id. The orientation of faces in each room: 3 +-------+ 4 | | 2 +-------+ 1 ''' for floor in self.floors: for i in self.s.query( "SELECT vent_from as compa_id, global_type_id as vent_id FROM aamks_geom WHERE type_pri='HVENT' AND floor=?", (floor, )): hvent_poly = self.aamks_polies['HVENT'][floor][i['vent_id']] compa_poly = self.aamks_polies['COMPA'][floor][i['compa_id']] compa = [(round(x), round(y)) for x, y in compa_poly.exterior.coords] lines = OrderedDict() lines[2] = LineString([compa[0], compa[1]]) lines[3] = LineString([compa[1], compa[2]]) lines[4] = LineString([compa[2], compa[3]]) lines[1] = LineString([compa[3], compa[0]]) for key, line in lines.items(): if hvent_poly.intersection(line).length > self.doors_width: pt = list(zip(*line.xy))[0] face = key offset = hvent_poly.distance(Point(pt)) self.s.query( "UPDATE aamks_geom SET face=?, face_offset=? WHERE global_type_id=? AND type_pri='HVENT'", (face, offset, i['vent_id'])) # }}} def _hvents_per_room(self): # {{{ ''' If there are more than one vent in a room Cfast needs them enumerated. ''' i = 0 j = 0 update = [] for v in self.s.query( "SELECT name,vent_from,vent_to FROM aamks_geom WHERE type_pri='HVENT' ORDER BY vent_from,vent_to" ): if v['vent_from'] != i: i = v['vent_from'] j = 0 j += 1 update.append((j, v['name'])) self.s.executemany( 'UPDATE aamks_geom SET hvent_room_seq=? WHERE name=?', (update)) # }}} def _vvents_per_room(self): # {{{ ''' If there are more than one vent in a room Cfast needs them enumerated. ''' i = 0 j = 0 update = [] for v in self.s.query( "SELECT name,vent_from,vent_to FROM aamks_geom WHERE type_pri='VVENT' ORDER BY vent_from,vent_to" ): if v['vent_from'] != i: i = v['vent_from'] j = 0 j += 1 update.append((j, v['name'])) self.s.executemany( 'UPDATE aamks_geom SET vvent_room_seq=? WHERE name=?', (update)) # }}} def _find_intersections_within_floor(self): # {{{ ''' Find intersections (hvents vs compas). This is how we know which doors belong to which compas. We expect that HVENT intersects either: a) room_from, room_to (two rectangles) b) room_from, outside (one rectangle) If the door originates at the very beginning of the room, then it also has a tiny intersection with some third rectangle which we filter out with: intersection.length > 100 (intersection perimeter, 100 is arbitrary) self.aamks_polies is a dict of floors: COMPA: OrderedDict([(1, OrderedDict([(42, <shapely.geometry.polygon.Polygon object at 0x2b2206282e48>), (43, <shapely.geometry.polygon.Polygon object at 0x2b2206282eb8>), (44, <shapely.geometry.polygon.Polygon object at 0x2b2206282f28>)))]) ... HVENT: OrderedDict([(1, OrderedDict([(21, <shapely.geometry.polygon.Polygon object at 0x2b2206282fd0>), (22, <shapely.geometry.polygon.Polygon object at 0x2b2206293048>)))]) ... VVENT: OrderedDict([(1, OrderedDict([(1, <shapely.geometry.polygon.Polygon object at 0x2b2206298550>)]))]) We aim at having vc_intersections (Vent_x_Compa intersections) dict of vents. Each vent collects two compas: 1: [48, 29] 2: [49, 29] 3: [11 ] -> [11, 99(Outside)] v=sorted(v) asserts that we go from lower_vent_id to higher_vent_id Also, we need to make sure room A and room B do intersect if there is door from A to B. ''' update = [] for floor, vents_dict in self.aamks_polies['HVENT'].items(): all_hvents = [ z['global_type_id'] for z in self.s.query( "SELECT global_type_id FROM aamks_geom WHERE type_pri='HVENT' AND floor=? ORDER BY name", floor) ] vc_intersections = {key: [] for key in all_hvents} for vent_id, vent_poly in vents_dict.items(): for compa_id, compa_poly in self.aamks_polies['COMPA'][ floor].items(): if vent_poly.intersection(compa_poly).length > 100: vc_intersections[vent_id].append(compa_id) for vent_id, v in vc_intersections.items(): v = sorted(v) if len(v) == 2 and self.aamks_polies['COMPA'][floor][ v[0]].intersects( self.aamks_polies['COMPA'][floor][v[1]]) == False: self.make_vis("Space between compas", vent_id) if len(v) == 1: v.append(self.outside_compa) if len(v) > 2: self.make_vis( 'Door intersects no rooms or more than 2 rooms.', vent_id) update.append((v[0], v[1], vent_id)) self.s.executemany( "UPDATE aamks_geom SET vent_from=?, vent_to=? where global_type_id=? and type_pri='HVENT'", update) # }}} def _find_intersections_between_floors(self): # {{{ ''' Similar to _find_intersections_within_floor() This time we are looking for a vvent (at floor n) which intersects a compa at it's floor (floor n) and a compa above (floor n+1) We will iterate over two_floors, which can contain: a) the set of compas from (floor n) and (floor n+1) b) the set of compas from (floor n) only if it is the top most floor -- outside_compa will come into play As opposed to _find_intersections_between_floors() vents go from higher to lower: "UPDATE aamks_geom SET vent_to=?, vent_from=?" intersection.length > 100 (intersection perimeter, 100 is arbitrary) ''' update = [] for floor, vents_dict in self.aamks_polies['VVENT'].items(): all_vvents = [ z['global_type_id'] for z in self.s.query( "SELECT global_type_id FROM aamks_geom WHERE type_pri='VVENT' AND floor=? ORDER BY name", floor) ] vc_intersections = {key: [] for key in all_vvents} for vent_id, vent_poly in vents_dict.items(): try: two_floors = self.aamks_polies['COMPA'][ floor] + self.aamks_polies['COMPA'][floor + 1] except: two_floors = self.aamks_polies['COMPA'][floor] for compa_id, compa_poly in two_floors.items(): if vent_poly.intersection(compa_poly).length > 100: vc_intersections[vent_id].append(compa_id) for vent_id, v in vc_intersections.items(): v = sorted(v) if len(v) == 1: v.append(self.outside_compa) if len(v) > 2: self.make_vis( 'Vent intersects no rooms or more than 2 rooms.', vent_id) update.append((v[0], v[1], vent_id)) self.s.executemany( "UPDATE aamks_geom SET vent_to=?, vent_from=? where global_type_id=? and type_pri='VVENT'", update) # }}} def _towers_slices(self): # {{{ ''' This is for evacuation only and cannot interfere with fire models (fire_model_ignore=1). Most STAI(RCASES) or HALL(S) are drawn on floor 0, but they are tall and need to cross other floors (towers). Say we have floor bottoms at 0, 3, 6, 9, 12 metres and STAI's top is at 9 metres - the STAI belongs to floors 0, 1, 2. We INSERT (x,y) STAI slices on proper floors. The slices are enumerated from 100000. ''' towers = {} for w in self.s.query( "SELECT name,z0 as tower_z0,height+z0 as tower_z1,floor,height,type_sec FROM aamks_geom WHERE type_sec in ('STAI','HALL')" ): floor_max_z = self.s.query( "SELECT max(z1) FROM aamks_geom WHERE type_sec NOT IN('STAI','HALL') AND floor=?", (w['floor'], ))[0]['max(z1)'] if w['tower_z1'] >= floor_max_z + 200: towers[w['name']] = [] current_floor = w['floor'] for floor in self.floors: for v in self.s.query( "SELECT min(z0) FROM aamks_geom WHERE type_pri='COMPA' AND floor=?", (floor, )): if v['min(z0)'] < w['tower_z1'] and v['min(z0)'] >= w[ 'tower_z0']: towers[w['name']].append(floor) towers[w['name']].remove(current_floor) self.towers_parents = {} high_global_type_id = 1000001 for orig_name, floors in towers.items(): orig_record = self.s.query( "SELECT global_type_id,type_pri,type_sec,type_tri,x0,y0,width,depth,x1,y1,room_area,room_enter,points,1 as fire_model_ignore, terminal_door FROM aamks_geom WHERE name=?", (orig_name, ))[0] parent_id = orig_record['global_type_id'] kk = list(orig_record.keys()) kk.append('floor') kk.append('name') for flo in floors: self.towers_parents[high_global_type_id] = parent_id orig_record['global_type_id'] = high_global_type_id vv = list(orig_record.values()) vv.append(flo) vv.append("{}.{}".format(orig_name, flo)) self.s.query( "INSERT INTO aamks_geom ({}) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" .format(",".join(kk)), tuple(vv)) high_global_type_id += 1 # }}} # ASSERTIONS def _assert_faces_ok(self): # {{{ ''' Are all hvents' faces fine? ''' for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_tri='DOOR' ORDER BY vent_from,vent_to" ): assert v['face_offset'] is not None, self.make_vis( 'Problem with cfast face calculation.', v['global_type_id']) # }}} def _assert_room_has_door(self): # {{{ # TODO: MUST ENABLE! return ''' Each room must have at least one type_tri DOOR. ''' doors_intersect_room_ids = [] for i in self.s.query( "SELECT vent_from,vent_to FROM aamks_geom WHERE type_tri='DOOR'" ): doors_intersect_room_ids.append(i['vent_from']) doors_intersect_room_ids.append(i['vent_to']) all_interected_room = set(doors_intersect_room_ids) for i in self.s.query( "SELECT name,floor,global_type_id FROM aamks_geom WHERE type_pri='COMPA'" ): if i['global_type_id'] not in all_interected_room: self.make_vis('Room without door (see Animator)', i['global_type_id'], 'COMPA') # }}} def make_vis(self, title, faulty_id='', type_pri='HVENT'): # {{{ ''' This method is for visualizing both errors and just how things look. If faulty_id comes non-empty then we are signaling an error. ''' if faulty_id != '': r = self.s.query( "SELECT name,floor FROM aamks_geom WHERE type_pri=? AND global_type_id=?", (type_pri, faulty_id))[0] fatal = "Fatal: {}: {}".format(r['name'], title) Vis({ 'highlight_geom': r['name'], 'anim': None, 'title': "<div id=python_msg>{}</div>".format(fatal), 'srv': 1 }) print(fatal) sys.exit() else: Vis({ 'highlight_geom': None, 'anim': None, 'title': title, 'srv': 1 }) # }}} def _debug(self): # {{{ #dd(os.environ['AAMKS_PROJECT']) #self.s.dumpall() #self.s.dump_geoms() #dd(self.s.query("select * from aamks_geom")) #dd(self.s.query("select * from world2d")) #exit() #self.s.dump() pass
class CfastMcarlo(): def __init__(self): # {{{ ''' Generate montecarlo cfast.in. Log what was drawn to psql. ''' self.json = Json() self.hrrpua = 0 self.alpha = 0 self.fire_origin = None self.conf = self.json.read("{}/conf.json".format( os.environ['AAMKS_PROJECT'])) if self.conf['fire_model'] == 'FDS': return self.s = Sqlite("{}/aamks.sqlite".format(os.environ['AAMKS_PROJECT'])) self.p = Psql() self._psql_collector = OrderedDict() self.s.query( "CREATE TABLE fire_origin(name,is_room,x,y,z,floor,sim_id)") si = SimIterations(self.conf['project_id'], self.conf['scenario_id'], self.conf['number_of_simulations']) for self._sim_id in range(*si.get()): seed(self._sim_id) self._new_psql_log() self._make_cfast() self._write() # }}} # DISTRIBUTIONS / DRAWS def _draw_outdoor_temp(self): # {{{ outdoor_temp = round( normal(self.conf['outdoor_temperature']['mean'], self.conf['outdoor_temperature']['sd']), 2) self._psql_log_variable('outdoort', outdoor_temp) return outdoor_temp # }}} def _save_fire_origin(self, fire_origin): # {{{ fire_origin.append(self._sim_id) self.s.query('INSERT INTO fire_origin VALUES (?,?,?,?,?,?,?)', fire_origin) self._psql_log_variable('fireorigname', fire_origin[0]) self._psql_log_variable('fireorig', fire_origin[1]) # }}} def _draw_fire_origin(self): # {{{ is_origin_in_room = binomial(1, self.conf['fire_starts_in_a_room']) self.all_corridors_and_halls = [ z['name'] for z in self.s.query( "SELECT name FROM aamks_geom WHERE type_pri='COMPA' AND fire_model_ignore!=1 AND type_sec in('COR','HALL') ORDER BY global_type_id" ) ] self.all_rooms = [ z['name'] for z in self.s.query( "SELECT name FROM aamks_geom WHERE type_sec='ROOM' ORDER BY global_type_id" ) ] fire_origin = [] if is_origin_in_room == 1 or len(self.all_corridors_and_halls) == 0: fire_origin.append(str(choice(self.all_rooms))) fire_origin.append('room') else: fire_origin.append(str(choice(self.all_corridors_and_halls))) fire_origin.append('non_room') compa = self.s.query("SELECT * FROM aamks_geom WHERE name=?", (fire_origin[0], ))[0] x = int(compa['x0'] + compa['width'] / 2.0) y = int(compa['y0'] + compa['depth'] / 2.0) z = int(compa['height'] * (1 - math.log10(uniform(1, 10)))) fire_origin += [x, y, z] fire_origin += [compa['floor']] self._save_fire_origin(fire_origin) self.fire_origin = fire_origin collect = ('FIRE', compa['global_type_id'], round(compa['width'] / (2.0 * 100), 2), round(compa['depth'] / (2.0 * 100), 2), z, 1, 'TIME', '0', '0', '0', '0', 'medium') return (','.join(str(i) for i in collect)) # }}} def _fire_origin(self): # {{{ ''' Either deterministic fire from Apainter, or probabilistic (_draw_fire_origin()). ''' if len(self.s.query( "SELECT * FROM aamks_geom WHERE type_pri='FIRE'")) > 0: z = self.s.query("SELECT * FROM aamks_geom WHERE type_pri='FIRE'") i = z[0] x = i['center_x'] y = i['center_y'] z = i['z1'] - i['z0'] room = self.s.query( "SELECT floor,name,type_sec,global_type_id FROM aamks_geom WHERE floor=? AND type_pri='COMPA' AND fire_model_ignore!=1 AND x0<=? AND y0<=? AND x1>=? AND y1>=?", (i['floor'], i['x0'], i['y0'], i['x1'], i['y1'])) if room[0]['type_sec'] in ('COR', 'HALL'): fire_origin = [ room[0]['name'], 'non_room', x, y, z, room[0]['floor'] ] else: fire_origin = [ room[0]['name'], 'room', x, y, z, room[0]['floor'] ] self._save_fire_origin(fire_origin) collect = ('FIRE', room[0]['global_type_id'], x * 100, y * 100, z * 100, 1, 'TIME', '0', '0', '0', '0', 'medium') cfast_fire = (','.join(str(i) for i in collect)) else: cfast_fire = self._draw_fire_origin() return cfast_fire # }}} def _draw_fire_properties(self, intervals): # {{{ i = OrderedDict() i['coyield'] = round(uniform(0.01, 0.043), 3) i['sootyield'] = round(uniform(0.11, 0.17), 3) i['trace'] = 0 i['q_star'] = round(uniform(0.5, 2), 3) i['heigh'] = 0 i['radfrac'] = round(gamma(124.48, 0.00217), 3) i['heatcom'] = round(uniform(16400000, 27000000), 1) for k, v in i.items(): self._psql_log_variable(k, v) result = OrderedDict() result['sootyield'] = 'SOOT,{}'.format(','.join([str(i['sootyield'])] * intervals)) result['coyield'] = 'CO,{}'.format(','.join([str(i['coyield'])] * intervals)) result['trace'] = 'TRACE,{}'.format(','.join([str(i['trace'])] * intervals)) result['q_star'] = i['q_star'] result['heigh'] = 'HEIGH,{}'.format(','.join([str(i['heigh'])] * intervals)) result['radfrac'] = str(i['radfrac']) result['heatcom'] = str(i['heatcom']) return result # }}} def _draw_fire_development(self): # {{{ ''' Generate fire. Alpha t square on the left, then constant in the middle, then fading on the right. At the end read hrrs at given times. ''' hrrpua_d = self.conf['hrrpua'] hrr_alpha = self.conf['hrr_alpha'] ''' Fire area is draw from pareto distrubution regarding the BS PD-7974-7. There is lack of vent condition - underventilated fires ''' p = pareto(b=0.668, scale=0.775) fire_area = p.rvs(size=1)[0] fire_origin = self.fire_origin orig_area = self.s.query( "SELECT (width * depth)/10000 as area FROM aamks_geom WHERE name='{}'" .format(fire_origin[0]))[0]['area'] if fire_area > orig_area: fire_area = orig_area self.hrrpua = int( triangular(hrrpua_d['min'], hrrpua_d['mode'], hrrpua_d['max'])) hrr_peak = int(self.hrrpua * 1000 * fire_area) self.alpha = int( triangular(hrr_alpha['min'], hrr_alpha['mode'], hrr_alpha['max']) * 1000) self._psql_log_variable('hrrpeak', hrr_peak / 1000) self._psql_log_variable('alpha', self.alpha / 1000.0) # left t_up_to_hrr_peak = int((hrr_peak / self.alpha)**0.5) interval = int(round(t_up_to_hrr_peak / 10)) times0 = list(range(0, t_up_to_hrr_peak, interval)) + [t_up_to_hrr_peak] hrrs0 = [int((self.alpha * t**2)) for t in times0] # middle t_up_to_starts_dropping = 15 * 60 times1 = [t_up_to_starts_dropping] hrrs1 = [hrr_peak] # right t_up_to_drops_to_zero = t_up_to_starts_dropping + t_up_to_hrr_peak interval = int( round((t_up_to_drops_to_zero - t_up_to_starts_dropping) / 10)) times2 = list( range(t_up_to_starts_dropping, t_up_to_drops_to_zero, interval)) + [t_up_to_drops_to_zero] hrrs2 = [ int((self.alpha * (t - t_up_to_drops_to_zero)**2)) for t in times2 ] times = list(times0 + times1 + times2) hrrs = list(hrrs0 + hrrs1 + hrrs2) return times, hrrs # }}} def _draw_window_opening(self, outdoor_temp): # {{{ ''' Windows are open / close based on outside temperatures but even still, there's a distribution of users willing to open/close the windows. Windows can be full-open (1), quarter-open (0.25) or closed (0). ''' draw_value = uniform(0, 1) for i in self.conf['windows']: if outdoor_temp > i['min'] and outdoor_temp <= i['max']: if draw_value < i['full']: how_much_open = 1 elif draw_value < i['full'] + i['quarter']: how_much_open = 0.25 else: how_much_open = 0 self._psql_log_variable('w', how_much_open) return how_much_open # }}} def _draw_door_and_hole_opening(self, Type): # {{{ ''' Door may be open or closed, but Hole is always open and we don't need to log that. ''' vents = self.conf['vents_open'] if Type == 'HOLE': how_much_open = 1 else: how_much_open = binomial(1, vents[Type]) self._psql_log_variable(Type.lower(), how_much_open) return how_much_open # }}} def _draw_heat_detectors_triggers(self): # {{{ mean = self.conf['heat_detectors']['temp_mean'] sd = self.conf['heat_detectors']['temp_sd'] zero_or_one = binomial(1, self.conf['heat_detectors']['not_broken']) chosen = round(normal(mean, sd), 2) * zero_or_one self._psql_log_variable('heat_detectors', chosen) return str(chosen) # }}} def _draw_smoke_detectors_triggers(self): # {{{ mean = self.conf['smoke_detectors']['temp_mean'] sd = self.conf['smoke_detectors']['temp_sd'] zero_or_one = binomial(1, self.conf['smoke_detectors']['not_broken']) chosen = round(normal(mean, sd), 2) * zero_or_one self._psql_log_variable('smoke_detectors', chosen) return str(chosen) # }}} def _draw_sprinklers_triggers(self): # {{{ mean = self.conf['sprinklers']['temp_mean'] sd = self.conf['sprinklers']['temp_sd'] zero_or_one = binomial(1, self.conf['sprinklers']['not_broken']) chosen = round(normal(mean, sd), 2) * zero_or_one self._psql_log_variable('sprinklers', chosen) return str(chosen) # }}} def _psql_log_variable(self, attr, val): #{{{ self._psql_collector[self._sim_id][attr].append(val) # }}} # CFAST SECTIONS def _make_cfast(self): # {{{ ''' Compose cfast.in sections ''' outdoor_temp = self._draw_outdoor_temp() txt = ( self._section_preamble(outdoor_temp), self._section_matl(), self._section_compa(), self._section_halls_onez(), self._section_windows(outdoor_temp), self._section_doors_and_holes(), self._section_vvent(), self._section_mvent(), self._section_fire(), self._section_heat_detectors(), self._section_smoke_detectors(), self._section_sprinklers(), ) with open( "{}/workers/{}/cfast.in".format(os.environ['AAMKS_PROJECT'], self._sim_id), "w") as output: output.write("\n".join(filter(None, txt))) # }}} def _section_preamble(self, outdoor_temp): # {{{ ''' We use 600 as time, since Cfast will be killed by aamks. ''' txt = ( 'VERSN,7,{}_{}'.format('SIM', self.conf['project_id']), 'TIMES,{},-120,10,10'.format(self.conf['simulation_time']), 'EAMB,{},101300,0'.format(273 + outdoor_temp), 'TAMB,293.15,101300,0,50', 'DTCHECK,1.E-9,100', '', ) return "\n".join(txt) # }}} def _section_matl(self): # {{{ txt = ( '!! MATL,name,param1,param2,param3,param4,param5,param6', 'MATL,concrete,1.7,840,2500,0.4,0.9,concrete', 'MATL,gypsum,0.3,1000,1000,0.02,0.85,gipsum', 'MATL,glass,0.8,840,2500,0.013,0.9,glass', 'MATL,block,0.3,840,800,0.03,0.85,floor', 'MATL,brick,0.3,840,800,0.03,0.85,brick', '', ) return "\n".join(txt) # }}} def _section_compa(self): # {{{ txt = [ '!! COMPA,name,width,depth,height,x,y,z,matl_ceiling,matl_floor,matl_wall' ] for v in self.s.query( "SELECT * from aamks_geom WHERE type_pri='COMPA' AND fire_model_ignore!=1 ORDER BY global_type_id" ): collect = [] collect.append('COMPA') # COMPA collect.append(v['name']) # NAME collect.append(round(v['width'] / 100.0, 2)) # WIDTH collect.append(round(v['depth'] / 100.0, 2)) # DEPTH collect.append(round(v['height'] / 100.0, 2)) # INTERNAL_HEIGHT collect.append(round(v['x0'] / 100.0, 2)) # ABSOLUTE_X_POSITION collect.append(round(v['y0'] / 100.0, 2)) # ABSOLUTE_Y_POSITION collect.append(round(v['z0'] / 100.0, 2)) # ABSOLUTE_Z_POSITION collect.append(v['material_ceiling']) # CEILING_MATERIAL_NAME collect.append(v['material_floor']) # FLOOR_MATERIAL_NAME collect.append(v['material_wall']) # WALL_MATERIAL_NAME txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_halls_onez(self): # {{{ txt = ['!! ONEZ,id'] for v in self.s.query( "SELECT * from aamks_geom WHERE type_sec in ('STAI', 'COR') AND fire_model_ignore!=1 order by type_sec" ): collect = [] if v['type_sec'] == 'COR': collect.append('HALL') else: collect.append('ONEZ') collect.append(v['global_type_id']) txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_windows(self, outdoor_temp): # {{{ ''' Randomize how windows are opened/closed. ''' txt = ['!! WINDOWS, from,to,id,width,soffit,sill,offset,face,open'] windows_setup = [] for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_tri='WIN' ORDER BY vent_from,vent_to" ): collect = [] collect.append('HVENT') # HVENT collect.append(v['vent_from']) # COMPARTMENT1 collect.append(v['vent_to']) # COMPARTMENT2 collect.append(v['hvent_room_seq']) # HVENT_NUMBER collect.append(round(v['cfast_width'] / 100.0, 2)) # WIDTH collect.append(round( (v['sill'] + v['height']) / 100.0, 2 )) # SOFFIT (height of the top of the hvent relative to the floor) collect.append(round(v['sill'] / 100.0, 2)) # SILL collect.append(round(v['face_offset'] / 100.0, 2)) # COMPARTMENT1_OFFSET collect.append(v['face']) # FACE how_much_open = self._draw_window_opening(outdoor_temp) windows_setup.append((how_much_open, v['name'])) collect.append(how_much_open) txt.append(','.join(str(i) for i in collect)) self.s.executemany( 'UPDATE aamks_geom SET how_much_open=? WHERE name=?', windows_setup) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_doors_and_holes(self): # {{{ ''' Randomize how doors are opened/close. ''' txt = ['!! DOORS, from,to,id,width,soffit,sill,offset,face,open'] hvents_setup = [] for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_tri='DOOR' ORDER BY vent_from,vent_to" ): collect = [] collect.append('HVENT') # HVENT collect.append(v['vent_from']) # COMPARTMENT1 collect.append(v['vent_to']) # COMPARTMENT2 collect.append(v['hvent_room_seq']) # VENT_NUMBER collect.append(round(v['cfast_width'] / 100.0, 2)) # WIDTH collect.append(round( (v['sill'] + v['height']) / 100.0, 2 )) # SOFFIT (height of the top of the hvent relative to the floor) collect.append(round(v['sill'] / 100.0, 2)) # SILL collect.append(round(v['face_offset'] / 100.0, 2)) # COMPARTMENT1_OFFSET collect.append(v['face']) # FACE how_much_open = self._draw_door_and_hole_opening( v['type_sec']) # HOLE_CLOSE hvents_setup.append((how_much_open, v['name'])) collect.append(how_much_open) txt.append(','.join(str(i) for i in collect)) self.s.executemany( 'UPDATE aamks_geom SET how_much_open=? WHERE name=?', hvents_setup) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_vvent(self): # {{{ # VVENT AREA, SHAPE, INITIAL_FRACTION txt = [ '!! VVENT,top,bottom,id,area,shape,rel_type,criterion,target,i_time, i_frac, f_time, f_frac, offset_x, offset_y' ] #for v in self.s.query("SELECT distinct v.room_area, v.type_sec, v.vent_from, v.vent_to, v.vvent_room_seq, v.width, v.depth, (v.x0 - c.x0) + 0.5*v.width as x0, (v.y0 - c.y0) + 0.5*v.depth as y0 FROM aamks_geom v JOIN aamks_geom c on v.vent_to_name = c.name WHERE v.type_pri='VVENTS' AND c.type_pri = 'COMPA' ORDER BY v.vent_from,v.vent_to"): for v in self.s.query( "SELECT distinct v.room_area, v.type_sec, v.vent_from, v.vent_to, v.vvent_room_seq, v.width, v.depth, (v.x0 - c.x0) + 0.5*v.width as x0, (v.y0 - c.y0) + 0.5*v.depth as y0 FROM aamks_geom v JOIN aamks_geom c on v.vent_to_name = c.name WHERE v.type_sec='VVENT' ORDER BY v.vent_from,v.vent_to" ): collect = [] collect.append('VVENT') # VVENT AREA, SHAPE, INITIAL_FRACTION collect.append(v['vent_from']) # COMPARTMENT1 collect.append(v['vent_to']) # COMPARTMENT2 collect.append(v['vvent_room_seq']) # VENT_NUMBER collect.append( round((v['width'] * v['depth']) / 1e4, 2) ) # AREA OF THE ROOM, feb.2018: previously: round((v['width']*v['depth'])/1e4, 2) collect.append(2) # Type of dumper 1 - round, 2 - squere collect.append('TIME') # Type of realease collect.append('') # empty for time release collect.append('') # empty for time release collect.append(60) # start work on time collect.append(0) # intial state before triggering collect.append(120) # end work on time #collect.append(1) # end state with probability of working collect.append(self._draw_door_and_hole_opening( v['type_sec'])) # end state with probability of working collect.append(round(v['x0'] / 100, 2)) # Offset_x collect.append(round(v['y0'] / 100, 2)) # Offset_y txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_mvent(self): # {{{ # VVENT AREA, SHAPE, INITIAL_FRACTION txt = [ '!!VVENT,first_comp,second_comp,id,orientation1,height_in,area_in,orientation2,height_out,area_out,flowm3/s,press_l,press_u,release,nix,nix,initial_time,initial_fraction,final_time,final_fraction' ] collect = [] #collect.append('MVENT,28,35,1,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,1,1') #collect.append('MVENT,28,35,2,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,2.5,1') #collect.append('MVENT,35,28,3,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,10,1') #collect.append('MVENT,35,28,4,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,11,1') #collect.append('MVENT,30,35,1,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,1,1') #collect.append('MVENT,31,35,2,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,1,1') #collect.append('MVENT,35,30,3,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,1,4') #collect.append('MVENT,35,31,4,V,2.3,0.48,H,3,0.48,1.7,200,300,TIME,,,60,0,70,1,2,1') txt.append('\n'.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_heat_detectors(self): # {{{ txt = [ '!! DETECTORS,type,compa,temp,width,depth,height,rti,supress,density' ] for v in self.s.query( "SELECT * from aamks_geom WHERE type_pri='COMPA' AND fire_model_ignore!=1 AND heat_detectors=1" ): temp = self._draw_heat_detectors_triggers( ) # ACTIVATION_TEMPERATURE, if temp == '0.0': collect = [] else: collect = [] collect.append('DETECT') # DETECT, collect.append('HEAT') # TYPE: HEAT,SMOKE,SPRINKLER collect.append(v['global_type_id']) # COMPARTMENT, collect.append(temp) # ACTIVATION_TEMPERATURE, collect.append(round(v['width'] / (2.0 * 100), 2)) # WIDTH collect.append(round(v['depth'] / (2.0 * 100), 2)) # DEPTH collect.append(round(v['height'] / 100.0, 2)) # HEIGHT collect.append(80) # RTI, collect.append(0) # SUPPRESSION, collect.append(7E-05) # SPRAY_DENSITY txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_smoke_detectors(self): # {{{ txt = [ '!! DETECTORS,type,compa,temp,width,depth,height,rti,supress,density' ] for v in self.s.query( "SELECT * from aamks_geom WHERE type_pri='COMPA' AND fire_model_ignore!=1 AND smoke_detectors=1" ): temp = self._draw_smoke_detectors_triggers( ) # ACTIVATION_TEMPERATURE, if temp == '0.0': collect = [] else: collect = [] collect.append('DETECT') # DETECT, collect.append('SMOKE') # TYPE: HEAT,SMOKE,SPRINKLER collect.append(v['global_type_id']) # COMPARTMENT, collect.append(temp) # ACTIVATION_TEMPERATURE, collect.append(round(v['width'] / (2.0 * 100), 2)) # WIDTH collect.append(round(v['depth'] / (2.0 * 100), 2)) # DEPTH collect.append(round(v['height'] / 100.0, 2)) # HEIGHT collect.append(80) # RTI, collect.append(0) # SUPPRESSION, collect.append(7E-05) # SPRAY_DENSITY txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_sprinklers(self): # {{{ txt = [ '!! SPRINKLERS,type,compa,temp,width,depth,height,rti,supress,density' ] for v in self.s.query( "SELECT * from aamks_geom WHERE type_pri='COMPA' AND fire_model_ignore!=1 AND sprinklers=1" ): temp = self._draw_sprinklers_triggers() # ACTIVATION_TEMPERATURE, if temp == '0.0': collect = [] else: collect = [] collect.append('DETECT') # DETECT, collect.append('SPRINKLER') # TYPE: HEAT,SMOKE,SPRINKLER collect.append(v['global_type_id']) # COMPARTMENT, collect.append(temp) # ACTIVATION_TEMPERATURE, collect.append(round(v['width'] / (2.0 * 100), 2)) # WIDTH collect.append(round(v['depth'] / (2.0 * 100), 2)) # DEPTH collect.append(round(v['height'] / 100.0, 2)) # HEIGHT collect.append(50) # RTI, collect.append(1) # SUPPRESSION, collect.append(7E-05) # SPRAY_DENSITY txt.append(','.join(str(i) for i in collect)) return "\n".join(txt) + "\n" if len(txt) > 1 else "" # }}} def _section_fire(self): # {{{ fire_origin = self._fire_origin() times, hrrs = self._draw_fire_development() fire_properties = self._draw_fire_properties(len(times)) self._fire_obstacle() area = nround(npa(hrrs) / (self.hrrpua * 1000) + 0.1, decimals=1) fire_origin = self._fire_origin() txt = ( '!! FIRE,compa,x,y,z,fire_number,ignition_type,ignition_criterion,ignition_target,?,?,name', fire_origin, '', '!! CHEMI,?,?,?,?', 'CHEMI,1,1.8,0.3,0.05,0,0.283,{}'.format( fire_properties['heatcom']), '', 'TIME,' + ','.join(str(i) for i in times), 'HRR,' + ','.join(str(round(i, 3)) for i in hrrs), fire_properties['sootyield'], fire_properties['coyield'], fire_properties['trace'], 'AREA,' + ','.join(str(i) for i in area), fire_properties['heigh'], ) return "\n".join(txt) + "\n" # }}} def _new_psql_log(self): #{{{ ''' Init the collector for storing montecarlo cfast setup. Variables will be registered later one at a time. ''' self._psql_collector[self._sim_id] = OrderedDict([ ('fireorig', []), ('fireorigname', []), ('heat_detectors', []), ('smoke_detectors', []), ('hrrpeak', []), ('sootyield', []), ('coyield', []), ('alpha', []), ('trace', []), ('area', []), ('q_star', []), ('heigh', []), ('w', []), ('outdoort', []), ('dcloser', []), ('door', []), ('sprinklers', []), ('heatcom', []), ('delectr', []), ('vvent', []), ('radfrac', []), ]) #}}} def _write(self): #{{{ ''' Write cfast variables to postgres. Both column names and the values for psql come from trusted source, so there should be no security issues with just joining dict data (non-parametrized query). ''' pairs = [] for k, v in self._psql_collector[self._sim_id].items(): pairs.append("{}='{}'".format(k, ','.join(str(x) for x in v))) data = ', '.join(pairs) self.p.query( "UPDATE simulations SET {} WHERE project=%s AND scenario_id=%s AND iteration=%s" .format(data), (self.conf['project_id'], self.conf['scenario_id'], self._sim_id))
class Geom(): def __init__(self): # {{{ self.json = Json() self.s = Sqlite("{}/aamks.sqlite".format(os.environ['AAMKS_PROJECT'])) self.raw_geometry = self.json.read("{}/cad.json".format( os.environ['AAMKS_PROJECT'])) self.conf = self.json.read("{}/conf.json".format( os.environ['AAMKS_PROJECT'])) self._doors_width = 32 self._wall_width = 4 self._make_elem_counter() self._geometry2sqlite() self._enhancements() self._init_dd_geoms() self._make_fake_wells() self._floors_details() self._aamks_geom_into_polygons() self._make_id2compa_name() self._find_intersections_within_floor() self._get_faces() self._hvents_per_room() self._find_intersections_between_floors() self._vvents_per_room() self._add_names_to_vents_from_to() self._calculate_sills() self._auto_detectors_and_sprinklers() self._create_obstacles() self.make_vis('Create obstacles') self._navmesh() self._assert_faces_ok() self._assert_room_has_door() #self.s.dumpall() # }}} def _floors_details(self): # {{{ ''' Floor dimensions are needed here and there, therefore we store it in sqlite. Canvas size is 1600 x 800 in css.css. Calculate how to scale the whole floor to fit the canvas. Minima don't have to be at (0,0) in autocad, therefore we also need to translate the drawing for the canvas. ''' values = OrderedDict() for floor in self.floors: minx = self.s.query( "SELECT min(x0) AS minx FROM aamks_geom WHERE floor=?", (floor, ))[0]['minx'] miny = self.s.query( "SELECT min(y0) AS miny FROM aamks_geom WHERE floor=?", (floor, ))[0]['miny'] maxx = self.s.query( "SELECT max(x1) AS maxx FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxx'] maxy = self.s.query( "SELECT max(y1) AS maxy FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxy'] z0 = self.s.query("SELECT z0 FROM aamks_geom WHERE floor=?", (floor, ))[0]['z0'] width = maxx - minx height = maxy - miny center = (minx + int(width / 2), miny + int(height / 2), z0) animation_scale = round(min(1600 / width, 800 / height) * 0.95, 2) # 0.95 is canvas padding animation_translate = [ int(maxx - 0.5 * width), int(maxy - 0.5 * height) ] values[floor] = OrderedDict([('width', width), ('height', height), ('z', z0), ('center', center), ('minx', minx), ('miny', miny), ('maxx', maxx), ('maxy', maxy), ('animation_scale', animation_scale), ('animation_translate', animation_translate)]) self.s.query("CREATE TABLE floors(json)") self.s.query('INSERT INTO floors VALUES (?)', (json.dumps(values), )) # }}} def _geometry2sqlite(self): # {{{ ''' Parse geometry and place geoms in sqlite. The lowest floor is always 0. The self.raw_geometry example for floor("0"): "0": [ "ROOM": [ [ [ 3.0 , 4.8 , 0.0 ] , [ 4.8 , 6.5 , 3.0 ] ] , [ [ 3.0 , 6.5 , 0.0 ] , [ 6.8 , 7.4 , 3.0 ] ] ] ] Each geom entity will be classified as DOOR, WINDOW, ROOM etc, and will get a unique name via elem_counter. Some columns in db are left empty for now. Sqlite's aamks_geom table must use two unique ids a) 'name' for visualisation and b) 'global_type_id' for cfast enumeration. ''' data = [] for floor, gg in self.raw_geometry.items(): for k, arr in gg.items(): for v in arr: p0 = [int(i) for i in v[0]] p1 = [int(i) for i in v[1]] width = p1[0] - p0[0] depth = p1[1] - p0[1] height = p1[2] - p0[2] record = self._prepare_geom_record(k, [p0, p1], width, depth, height, floor) if record != False: data.append(record) self.s.query( "CREATE TABLE aamks_geom(name,floor,global_type_id,hvent_room_seq,vvent_room_seq,type_pri,type_sec,type_tri,x0,y0,z0,width,depth,height,cfast_width,sill,face,face_offset,vent_from,vent_to,material_ceiling,material_floor,material_wall,heat_detectors,smoke_detectors,sprinklers,is_vertical,vent_from_name,vent_to_name, how_much_open, room_area, x1, y1, z1, center_x, center_y, center_z, fire_model_ignore)" ) self.s.executemany( 'INSERT INTO aamks_geom VALUES ({})'.format(','.join( '?' * len(data[0]))), data) #dd(self.s.dump()) #}}} def _prepare_geom_record(self, k, v, width, depth, height, floor): # {{{ ''' Format a record for sqlite. Hvents get fixed width self._doors_width cm ''' # OBST if k in ('OBST', ): type_pri = 'OBST' type_tri = '' # EVACUEE elif k in ('EVACUEE', ): type_pri = 'EE' type_tri = '' # MVENT elif k in ('MVENT', ): type_pri = 'MVENT' type_tri = '' # VVENT elif k in ('VVENT', ): type_pri = 'VVENT' height = 10 type_tri = '' # COMPA elif k in ('ROOM', 'COR', 'HALL', 'STAI'): type_pri = 'COMPA' type_tri = '' # HVENT elif k in ('D', 'C', 'E', 'HOLE', 'W'): width = max(width, self._doors_width) depth = max(depth, self._doors_width) type_pri = 'HVENT' if k in ('D', 'C', 'E', 'HOLE'): type_tri = 'DOOR' elif k in ('W'): type_tri = 'WIN' self._elem_counter[type_pri] += 1 global_type_id = self._elem_counter[type_pri] name = '{}_{}'.format(k[0], global_type_id) #data.append('name' , 'floor' , 'global_type_id' , 'hvent_room_seq' , 'vvent_room_seq' , 'type_pri' , 'type_sec' , 'type_tri' , 'x0' , 'y0' , 'z0' , 'width' , 'depth' , 'height' , 'cfast_width' , 'sill' , 'face' , 'face_offset' , 'vent_from' , 'vent_to' , material_ceiling , material_floor , material_wall , 'heat_detectors' , 'smoke_detectors' , 'sprinklers' , 'is_vertical' , 'vent_from_name' , 'vent_to_name' , 'how_much_open' , 'room_area' , 'x1' , 'y1' , 'z1' , 'center_x' , 'center_y' , 'center_z' , 'fire_model_ignore') return (name, floor, global_type_id, None, None, type_pri, k, type_tri, v[0][0], v[0][1], v[0][2], width, depth, height, None, None, None, None, None, None, self.conf['material_ceiling']['type'], self.conf['material_floor']['type'], self.conf['material_wall']['type'], 0, 0, 0, None, None, None, None, None, None, None, None, None, None, None, 0) # }}} def _enhancements(self): # {{{ ''' Is HVENT vertical or horizontal? Apart from what is width and height for geometry, HVENTS have their cfast_width always along the wall Doors and Holes will be later interescted with parallel walls (obstacles). We inspect is_vertical and make the doors just enough smaller to avoid perpendicular intersections. Since obstacles are generated to right/top direction, we need to address the overlapping coming from left/bottom. So we make doors shorter at x0 and y0, but not at x1 and y1. At the end our door=90cm are now 86cm. ''' self.outside_compa = self.s.query( "SELECT count(*) FROM aamks_geom WHERE type_pri='COMPA'" )[0]['count(*)'] + 1 self.floors = [ z['floor'] for z in self.s.query( "SELECT DISTINCT floor FROM aamks_geom ORDER BY floor") ] self.all_doors = [ z['name'] for z in self.s.query( "SELECT name FROM aamks_geom WHERE type_tri='DOOR' ORDER BY name" ) ] self.s.query( "UPDATE aamks_geom SET is_vertical=0, cfast_width=width WHERE type_pri='HVENT' AND width > depth" ) self.s.query( "UPDATE aamks_geom SET is_vertical=1, cfast_width=depth WHERE type_pri='HVENT' AND width < depth" ) self.s.query( "UPDATE aamks_geom SET room_area=round(width*depth/10000,2) WHERE type_pri='COMPA'" ) self.s.query( "UPDATE aamks_geom SET x1=x0+width, y1=y0+depth, z1=z0+height, center_x=x0+width/2, center_y=y0+depth/2, center_z=z0+height/2" ) self.s.query( "UPDATE aamks_geom SET y0=y0+?, depth=depth-? WHERE type_tri='DOOR' AND is_vertical=1", (self._wall_width, self._wall_width)) self.s.query( "UPDATE aamks_geom SET x0=x0+?, width=width-? WHERE type_tri='DOOR' AND is_vertical=0", (self._wall_width, self._wall_width)) # }}} def _init_dd_geoms(self): # {{{ ''' dd_geoms are some optional extra rectangles, points, lines and circles that are written to on top of our geoms. Useful for developing and debugging features. Must come early, because visualization depends on it. Procedure: * z=self.json.read('{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) * z["0"]['circles'].append({ "xy": (i['center_x'], i['center_y']),"radius": 200, "fillColor": "#fff" , "opacity": 0.3 } ) * z["0"]['circles'].append({ "xy": (i['center_x'], i['center_y']),"radius": 200, "fillColor": "#fff" , "opacity": 0.3 } ) * self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) ''' z = dict() for floor in self.floors: z[floor] = dict() z[floor]['rectangles'] = [] z[floor]['lines'] = [] z[floor]['circles'] = [] z[floor]['texts'] = [] z[floor]['rectangles'] = [] for i in self.s.query( "SELECT * FROM aamks_geom WHERE type_tri='DOOR' AND floor=?", (floor, )): z[floor]['circles'].append({ "xy": (i['center_x'], i['center_y']), "radius": 90, "fillColor": "#fff", "opacity": 0.05 }) # Example usage anywhere inside aamks: # z=self.json.read('{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # z["0"]['rectangles'].append( { "xy": (1000 , 1000) , "width": 200 , "depth": 300 , "strokeColor": "#fff" , "strokeWidth": 2 , "fillColor": "#f80" , "opacity": 0.7 } ) # z["0"]['rectangles'].append( { "xy": (0 , 0) , "width": 200 , "depth": 200 , "strokeColor": "#fff" , "strokeWidth": 2 , "fillColor": "#f80" , "opacity": 0.7 } ) # z["0"]['lines'].append( { "xy": (2000 , 200) , "x1": 3400 , "y1": 500 , "strokeColor": "#fff" , "strokeWidth": 2 , "opacity": 0.7 } ) # z["0"]['texts'].append( { "xy": (1000 , 1000) , "content": "(1000x1000)" , "fontSize": 400 , "fillColor":"#06f" , "opacity":0.7 } ) # self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # Vis(None, 'image', 'dd_geoms example') self.json.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # }}} def _make_fake_wells(self): # {{{ ''' TODO: are we using this? This is for evacuation only and cannot interfere with fire models (fire_model_ignore=1). Most STAI(RCASES) or HALL(S) are drawn on floor 0, but they are tall and need to cross other floors. We call them WELLs. Say we have floor bottoms at 0, 3, 6, 9, 12 metres and WELL's top is at 9 metres - the WELL belongs to floors 0, 1, 2. We INSERT fake (x,y) WELL slices on proper floors in order to calculate vent_from / vent_to properly. ''' return add_wells = {} for w in self.s.query( "SELECT floor,global_type_id,height FROM aamks_geom WHERE type_sec in ('STAI','HALL')" ): add_wells[(w['floor'], w['global_type_id'])] = [] current_floor = w['floor'] for floor in self.floors: for v in self.s.query( "SELECT min(z0) FROM aamks_geom WHERE type_pri='COMPA' AND floor=?", (floor, )): if v['min(z0)'] < w['height']: add_wells[(w['floor'], w['global_type_id'])].append(floor) add_wells[(w['floor'], w['global_type_id'])].remove(current_floor) for w, floors in add_wells.items(): row = self.s.query( "SELECT * FROM aamks_geom WHERE type_pri='COMPA' AND global_type_id=?", (w[1], ))[0] for floor in floors: row['fire_model_ignore'] = 1 row['floor'] = floor self.s.query( 'INSERT INTO aamks_geom VALUES ({})'.format(','.join( '?' * len(row.keys()))), list(row.values())) # }}} def _make_id2compa_name(self): # {{{ ''' Create a map of ids to names for COMPAS, e.g. _id2compa_name[4]='ROOM_1_4' This map is stored to sqlite because path.py needs it. Still true after mesh travelling? ''' self._id2compa_name = OrderedDict() for v in self.s.query( "select name,global_type_id from aamks_geom where type_pri='COMPA' ORDER BY global_type_id" ): self._id2compa_name[v['global_type_id']] = v['name'] self._id2compa_name[self.outside_compa] = 'outside' # }}} def _add_names_to_vents_from_to(self): # {{{ ''' Vents from/to use indices, but names will be simpler for AAMKS and for debugging. Hvents/Vvents lower_id/higher_id are already taken care of in _find_intersections_between_floors() ''' query = [] for v in self.s.query( "select vent_from,vent_to,name from aamks_geom where type_pri IN('HVENT', 'VVENT')" ): query.append((self._id2compa_name[v['vent_from']], self._id2compa_name[v['vent_to']], v['name'])) self.s.executemany( 'UPDATE aamks_geom SET vent_from_name=?, vent_to_name=? WHERE name=?', query) # }}} def _calculate_sills(self): # {{{ ''' Sill is the distance relative to the floor. Say there's a HVENT H between rooms A and B. Say A and B have variate z0 (floor elevations). How do we calculate the height of the sill? The CFAST.in solution is simple: We find 'hvent_from' for H and then we use it's z0. This z0 is the needed 'relative 0' for the calcuation. ''' update = [] for v in self.s.query( "SELECT global_type_id, z0, vent_from FROM aamks_geom WHERE type_pri='HVENT' ORDER BY name" ): floor_baseline = self.s.query( "SELECT z0 FROM aamks_geom WHERE global_type_id=? AND type_pri='COMPA'", (v['vent_from'], ))[0]['z0'] update.append((v['z0'] - floor_baseline, v['global_type_id'])) self.s.executemany( "UPDATE aamks_geom SET sill=? WHERE type_pri='HVENT' AND global_type_id=?", update) # }}} def _auto_detectors_and_sprinklers(self): # {{{ if len(''.join([str(i) for i in self.conf['heat_detectors'].values()])) > 0: self.s.query( "UPDATE aamks_geom set heat_detectors = 1 WHERE type_pri='COMPA'" ) if len(''.join([str(i) for i in self.conf['smoke_detectors'].values()])) > 0: self.s.query( "UPDATE aamks_geom set smoke_detectors = 1 WHERE type_pri='COMPA'" ) if len(''.join([str(i) for i in self.conf['sprinklers'].values()])) > 0: self.s.query( "UPDATE aamks_geom set sprinklers = 1 WHERE type_pri='COMPA'") # }}} def _make_elem_counter(self): # {{{ ''' Geom primary types are enumerated globally for the building in sqlite. Each of the types has separate numbering starting from 1. ROOM_2_8 refers to the eight compartment in the building which happens to exist on floor 2. ''' self._elem_counter = {} for i in ('COMPA', 'HVENT', 'VVENT', 'OBST', 'EE', 'MVENT'): self._elem_counter[i] = 0 # }}} # INTERSECTIONS def _aamks_geom_into_polygons(self): # {{{ ''' aamks_geom into shapely polygons for intersections ''' self.aamks_polies = OrderedDict() self.aamks_polies['COMPA'] = OrderedDict() self.aamks_polies['HVENT'] = OrderedDict() self.aamks_polies['VVENT'] = OrderedDict() self.aamks_polies['MVENT'] = OrderedDict() for floor in self.floors: for elem in self.aamks_polies.keys(): self.aamks_polies[elem][floor] = OrderedDict() for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_pri NOT IN('OBST', 'EE') AND floor=?", (floor, )): self.aamks_polies[v['type_pri']][floor][ v['global_type_id']] = box(v['x0'], v['y0'], v['x0'] + v['width'], v['y0'] + v['depth']) # }}} def _get_faces(self): # {{{ ''' Cfast faces and offsets calculation. HVENTS have self._doors_width, so we only consider intersection.length > self._doors_width Faces are properties of doors. They are calculated in respect to the room of lower id. The orientation of faces in each room: 3 +-------+ 4 | | 2 +-------+ 1 ''' for floor in self.floors: for i in self.s.query( "SELECT vent_from as compa_id, global_type_id as vent_id FROM aamks_geom WHERE type_pri='HVENT' AND floor=?", (floor, )): hvent_poly = self.aamks_polies['HVENT'][floor][i['vent_id']] compa_poly = self.aamks_polies['COMPA'][floor][i['compa_id']] compa = [(round(x), round(y)) for x, y in compa_poly.exterior.coords] lines = OrderedDict() lines[2] = LineString([compa[0], compa[1]]) lines[3] = LineString([compa[1], compa[2]]) lines[4] = LineString([compa[2], compa[3]]) lines[1] = LineString([compa[3], compa[0]]) for key, line in lines.items(): if hvent_poly.intersection( line).length > self._doors_width: pt = list(zip(*line.xy))[0] face = key offset = hvent_poly.distance(Point(pt)) self.s.query( "UPDATE aamks_geom SET face=?, face_offset=? WHERE global_type_id=? AND type_pri='HVENT'", (face, offset, i['vent_id'])) # }}} def _hvents_per_room(self): # {{{ ''' If there are more than one vent in a room Cfast needs them enumerated. ''' i = 0 j = 0 update = [] for v in self.s.query( "SELECT name,vent_from,vent_to FROM aamks_geom WHERE type_pri='HVENT' ORDER BY vent_from,vent_to" ): if v['vent_from'] != i: i = v['vent_from'] j = 0 j += 1 update.append((j, v['name'])) self.s.executemany( 'UPDATE aamks_geom SET hvent_room_seq=? WHERE name=?', (update)) # }}} def _vvents_per_room(self): # {{{ ''' If there are more than one vent in a room Cfast needs them enumerated. ''' i = 0 j = 0 update = [] for v in self.s.query( "SELECT name,vent_from,vent_to FROM aamks_geom WHERE type_pri='VVENT' ORDER BY vent_from,vent_to" ): if v['vent_from'] != i: i = v['vent_from'] j = 0 j += 1 update.append((j, v['name'])) self.s.executemany( 'UPDATE aamks_geom SET vvent_room_seq=? WHERE name=?', (update)) # }}} def _find_intersections_within_floor(self): # {{{ ''' Find intersections (hvents vs compas). This is how we know which doors belong to which compas. We expect that HVENT intersects either: a) room_from, room_to (two rectangles) b) room_from, outside (one rectangle) If the door originates at the very beginning of the room, then it also has a tiny intersection with some third rectangle which we filter out with: intersection.length > 100 (intersection perimeter, 100 is arbitrary) self.aamks_polies is a dict of floors: COMPA: OrderedDict([(1, OrderedDict([(42, <shapely.geometry.polygon.Polygon object at 0x2b2206282e48>), (43, <shapely.geometry.polygon.Polygon object at 0x2b2206282eb8>), (44, <shapely.geometry.polygon.Polygon object at 0x2b2206282f28>)))]) ... HVENT: OrderedDict([(1, OrderedDict([(21, <shapely.geometry.polygon.Polygon object at 0x2b2206282fd0>), (22, <shapely.geometry.polygon.Polygon object at 0x2b2206293048>)))]) ... VVENT: OrderedDict([(1, OrderedDict([(1, <shapely.geometry.polygon.Polygon object at 0x2b2206298550>)]))]) We aim at having vc_intersections (Vent_x_Compa intersections) dict of vents. Each vent collects two compas: 1: [48, 29] 2: [49, 29] 3: [11 ] -> [11, 99(Outside)] v=sorted(v) asserts that we go from lower_vent_id to higher_vent_id Also, we need to make sure room A and room B do intersect if there is door from A to B. ''' update = [] for floor, vents_dict in self.aamks_polies['HVENT'].items(): all_hvents = [ z['global_type_id'] for z in self.s.query( "SELECT global_type_id FROM aamks_geom WHERE type_pri='HVENT' AND floor=? ORDER BY name", floor) ] vc_intersections = {key: [] for key in all_hvents} for vent_id, vent_poly in vents_dict.items(): for compa_id, compa_poly in self.aamks_polies['COMPA'][ floor].items(): if vent_poly.intersection(compa_poly).length > 100: vc_intersections[vent_id].append(compa_id) for vent_id, v in vc_intersections.items(): v = sorted(v) if len(v) == 2 and self.aamks_polies['COMPA'][floor][ v[0]].intersects( self.aamks_polies['COMPA'][floor][v[1]]) == False: self.make_vis("Space between compas".format(floor), vent_id) if len(v) == 1: v.append(self.outside_compa) if len(v) > 2: self.make_vis( 'Door intersects no rooms or more than 2 rooms.', vent_id) update.append((v[0], v[1], vent_id)) self.s.executemany( "UPDATE aamks_geom SET vent_from=?, vent_to=? where global_type_id=? and type_pri='HVENT'", update) # }}} def _find_intersections_between_floors(self): # {{{ ''' Similar to _find_intersections_within_floor() This time we are looking for a vvent (at floor n) which intersects a compa at it's floor (floor n) and a compa above (floor n+1) We will iterate over two_floors, which can contain: a) the set of compas from (floor n) and (floor n+1) b) the set of compas from (floor n) only if it is the top most floor -- outside_compa will come into play As opposed to _find_intersections_between_floors() vents go from higher to lower: "UPDATE aamks_geom SET vent_to=?, vent_from=?" intersection.length > 100 (intersection perimeter, 100 is arbitrary) ''' update = [] for floor, vents_dict in self.aamks_polies['VVENT'].items(): all_vvents = [ z['global_type_id'] for z in self.s.query( "SELECT global_type_id FROM aamks_geom WHERE type_pri='VVENT' AND floor=? ORDER BY name", floor) ] vc_intersections = {key: [] for key in all_vvents} for vent_id, vent_poly in vents_dict.items(): try: two_floors = self.aamks_polies['COMPA'][ floor] + self.aamks_polies['COMPA'][floor + 1] except: two_floors = self.aamks_polies['COMPA'][floor] for compa_id, compa_poly in two_floors.items(): if vent_poly.intersection(compa_poly).length > 100: vc_intersections[vent_id].append(compa_id) for vent_id, v in vc_intersections.items(): v = sorted(v) if len(v) == 1: v.append(self.outside_compa) if len(v) > 2: self.make_vis( 'Vent intersects no rooms or more than 2 rooms.', vent_id) update.append((v[0], v[1], vent_id)) self.s.executemany( "UPDATE aamks_geom SET vent_to=?, vent_from=? where global_type_id=? and type_pri='VVENT'", update) # }}} # OBSTACLES def _create_obstacles(self): # {{{ ''' Geometry may contain obstacles to model machines, FDS walls, bookcases, etc. Obstacles are not visible in CFAST, since they don't belong to aamks_geom table. ''' data = OrderedDict() data['points'] = OrderedDict() data['named'] = OrderedDict() floors_meta = json.loads( self.s.query("SELECT json FROM floors")[0]['json']) for floor, gg in self.raw_geometry.items(): data['points'][floor] = [] data['named'][floor] = [] boxen = [] # TODO: once cad.json uses 'OBST': [], there's no need to try. try: for v in gg['OBST']: boxen.append( box(int(v[0][0]), int(v[0][1]), int(v[1][0]), int(v[1][1]))) except: pass boxen += self._rooms_into_boxen(floor) data['named'][floor] = self._boxen_into_rectangles(boxen) for i in boxen: data['points'][floor].append([(int(x), int(y), floors_meta[floor]['z']) for x, y in i.exterior.coords]) self.s.query("CREATE TABLE obstacles(json)") self.s.query("INSERT INTO obstacles VALUES (?)", (json.dumps(data), )) #}}} def _rooms_into_boxen(self, floor): # {{{ ''' For a roomX we create a roomX_ghost, we move it by self._wall_width, which must match the width of hvents. Then we create walls via logical operations. Finally doors cut the openings in walls. ''' walls = [] for i in self.s.query( "SELECT * FROM aamks_geom WHERE floor=? AND type_pri='COMPA' ORDER BY name", (floor, )): walls.append((i['x0'] + self._wall_width, i['y0'], i['x0'] + i['width'], i['y0'] + self._wall_width)) walls.append((i['x0'] + i['width'], i['y0'], i['x0'] + i['width'] + self._wall_width, i['y0'] + i['depth'] + self._wall_width)) walls.append((i['x0'] + self._wall_width, i['y0'] + i['depth'], i['x0'] + i['width'], i['y0'] + i['depth'] + self._wall_width)) walls.append((i['x0'], i['y0'], i['x0'] + self._wall_width, i['y0'] + i['depth'] + self._wall_width)) walls_polygons = ([ box(ii[0], ii[1], ii[2], ii[3]) for ii in set(walls) ]) doors_polygons = [] for i in self.s.query( "SELECT * FROM aamks_geom WHERE floor=? AND type_tri='DOOR' ORDER BY name", (floor, )): doors_polygons.append( box(i['x0'], i['y0'], i['x0'] + i['width'], i['y0'] + i['depth'])) boxen = [] for wall in walls_polygons: for door in doors_polygons: wall = wall.difference(door) if isinstance(wall, MultiPolygon): for i in polygonize(wall): boxen.append(i) elif isinstance(wall, Polygon): boxen.append(wall) return boxen # }}} def _boxen_into_rectangles(self, boxen): # {{{ ''' Transform shapely boxen into rectangles for paperjs visualization: [(x0,y0,width,height)] ''' rectangles = [] for i in boxen: m = i.bounds coords = OrderedDict() coords["x0"] = int(m[0]) coords["y0"] = int(m[1]) coords["width"] = int(m[2] - m[0]) coords["depth"] = int(m[3] - m[1]) rectangles.append(coords) return rectangles # }}} # NAVMESH def _navmesh(self): # {{{ ''' 1. Create obj file from aamks geometries. 2. Build navmesh with golang, obj is input 3. Query navmesh with python ''' self.nav = OrderedDict() z = self.s.query("SELECT json FROM obstacles") for floor, faces in json.loads(z[0]['json'])['points'].items(): self._obj_num = 0 obj = '' for face in faces: obj += self._obj_elem(face, 99) for face in self._obj_platform(floor): obj += self._obj_elem(face, 0) with open("{}/{}.obj".format(os.environ['AAMKS_PROJECT'], floor), "w") as f: f.write(obj) self.nav[floor] = Navmesh() self.nav[floor].build(obj, os.environ['AAMKS_PROJECT'], floor) self._navmesh_test(floor) Vis({ 'highlight_geom': None, 'anim': None, 'title': 'Navmesh test', 'srv': 1 }) # }}} def _obj_platform(self, floor): # {{{ z = self.s.query( "SELECT x0,y0,x1,y1 FROM aamks_geom WHERE type_pri='COMPA' AND floor=?", (floor, )) platforms = [] for i in z: platforms.append([(i['x1'], i['y1']), (i['x1'], i['y0']), (i['x0'], i['y0']), (i['x0'], i['y1'])]) return platforms # }}} def _obj_elem(self, face, z): # {{{ elem = '' elem += "o Face{}\n".format(self._obj_num) for verts in face[:4]: elem += "v {}\n".format(" ".join( [str(i / 100) for i in [verts[0], z, verts[1]]])) elem += "f {}\n\n".format(" ".join( [str(4 * self._obj_num + i) + "//1" for i in [1, 2, 3, 4]])) self._obj_num += 1 return elem # }}} def _navmesh_test(self, floor): # {{{ colors = ["#fff", "#f80", "#f00", "#8f0", "#08f", "#f0f"] navmesh_paths = [] for x in range(6): src_dest = [] for i in self.s.query( "SELECT * FROM aamks_geom WHERE type_pri='COMPA' AND floor=? ORDER BY RANDOM() LIMIT 2", (floor, )): src_dest.append([ round(uniform(i['x0'], i['x1'])), round(uniform(i['y0'], i['y1'])) ]) z = self.json.read('{}/dd_geoms.json'.format( os.environ['AAMKS_PROJECT'])) z[floor]['circles'].append({ "xy": (src_dest[0]), "radius": 20, "fillColor": colors[x], "opacity": 1 }) z[floor]['circles'].append({ "xy": (src_dest[1]), "radius": 20, "fillColor": colors[x], "opacity": 1 }) self.json.write( z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) navmesh_paths.append(self.nav[floor].query(src_dest, floor)) self._navmesh_vis(floor, navmesh_paths, colors) # }}} def _navmesh_vis(self, floor, navmesh_paths, colors): # {{{ j = Json() z = j.read('{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) for cc, path in enumerate(navmesh_paths): for i, p in enumerate(path): try: z[floor]['lines'].append({ "xy": (path[i][0], path[i][1]), "x1": path[i + 1][0], "y1": path[i + 1][1], "strokeColor": colors[cc], "strokeWidth": 2, "opacity": 0.7 }) except: pass j.write(z, '{}/dd_geoms.json'.format(os.environ['AAMKS_PROJECT'])) # }}} # ASSERTIONS def _assert_faces_ok(self): # {{{ ''' Are all hvents' faces fine? ''' for v in self.s.query( "SELECT * FROM aamks_geom WHERE type_tri='DOOR' ORDER BY vent_from,vent_to" ): assert v['face_offset'] is not None, self.make_vis( 'Problem with cfast face calculation.', v['global_type_id']) # }}} def _assert_room_has_door(self): # {{{ ''' Each room must have at least one type_tri DOOR. ''' doors_intersect_room_ids = [] for i in self.s.query( "SELECT vent_from,vent_to FROM aamks_geom WHERE type_tri='DOOR'" ): doors_intersect_room_ids.append(i['vent_from']) doors_intersect_room_ids.append(i['vent_to']) all_interected_room = set(doors_intersect_room_ids) for i in self.s.query( "SELECT name,floor,global_type_id FROM aamks_geom WHERE type_pri='COMPA'" ): if i['global_type_id'] not in all_interected_room: self.make_vis('Room without door (see Animator)', i['global_type_id'], 'COMPA') # }}} def make_vis(self, title, faulty_id='', type_pri='HVENT'): # {{{ ''' This method is for visualizing both errors and just how things look. If faulty_id comes non-empty then we are signaling an error. ''' if faulty_id != '': r = self.s.query( "SELECT name,floor FROM aamks_geom WHERE type_pri=? AND global_type_id=?", (type_pri, faulty_id))[0] fatal = "Fatal: {}: {}".format(r['name'], title) Vis({ 'highlight_geom': r['name'], 'anim': None, 'title': "<div id=python_msg>{}</div>".format(fatal), 'srv': 1 }) print(fatal) sys.exit() else: Vis({ 'highlight_geom': None, 'anim': None, 'title': title, 'srv': 1 })
class FDSimporter(): def __init__(self): # {{{ self.json = Json() self.conf = self.json.read("{}/conf.json".format( os.environ['AAMKS_PROJECT'])) if self.conf['fire_model'] == 'CFAST': return self.s = Sqlite("{}/aamks.sqlite".format(os.environ['AAMKS_PROJECT'])) self.cadfds = self.json.read("{}/cadfds.json".format( os.environ['AAMKS_PROJECT'])) self.doors_width = 32 self.walls_width = 4 self._geometry2sqlite() self.geomsMap = self.json.read("{}/inc.json".format( os.environ['AAMKS_PATH']))['aamksGeomsMap'] self._floors_meta() self._world_meta() self._fire_origin() self._debug() # }}} def _floors_meta(self): # {{{ ''' Floor dimensions are needed here and there. for z_absolute high towers are not taken under account, we want the natural floor's zdim for z_relative high towers are not taken under account, we want the natural floor's zdim ''' self.floors = [ z['floor'] for z in self.s.query( "SELECT DISTINCT floor FROM aamks_geom ORDER BY floor") ] self.floors_meta = OrderedDict() self._world3d = dict() self._world3d['minx'] = 9999999 self._world3d['maxx'] = -9999999 self._world3d['miny'] = 9999999 self._world3d['maxy'] = -9999999 prev_maxz = 0 for floor in self.floors: ty = 0 tx = 0 minx = self.s.query( "SELECT min(x0) AS minx FROM aamks_geom WHERE floor=?", (floor, ))[0]['minx'] maxx = self.s.query( "SELECT max(x1) AS maxx FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxx'] miny = self.s.query( "SELECT min(y0) AS miny FROM aamks_geom WHERE floor=?", (floor, ))[0]['miny'] maxy = self.s.query( "SELECT max(y1) AS maxy FROM aamks_geom WHERE floor=?", (floor, ))[0]['maxy'] minz_abs = self.s.query( "SELECT min(z0) AS minz FROM aamks_geom WHERE type_sec NOT IN('STAI','HALL') AND floor=?", (floor, ))[0]['minz'] maxz_abs = self.s.query( "SELECT max(z1) AS maxz FROM aamks_geom WHERE type_sec NOT IN('STAI','HALL') AND floor=?", (floor, ))[0]['maxz'] zdim = maxz_abs - prev_maxz prev_maxz = maxz_abs xdim = maxx - minx ydim = maxy - miny center = (minx + int(xdim / 2), miny + int(ydim / 2), minz_abs) self.floors_meta[floor] = OrderedDict([('name', floor), ('xdim', xdim), ('ydim', ydim), ('center', center), ('minx', minx), ('miny', miny), ('maxx', maxx), ('maxy', maxy), ('minz_abs', minz_abs), ('maxz_abs', maxz_abs), ('zdim', zdim), ('ty', ty), ('tx', tx)]) self._world3d['minx'] = min(self._world3d['minx'], minx) self._world3d['maxx'] = max(self._world3d['maxx'], maxx) self._world3d['miny'] = min(self._world3d['miny'], miny) self._world3d['maxy'] = max(self._world3d['maxy'], maxy) self.s.query("CREATE TABLE floors_meta(json)") self.s.query('INSERT INTO floors_meta VALUES (?)', (json.dumps(self.floors_meta), )) # }}} def _world_meta(self): # {{{ self.s.query("CREATE TABLE world_meta(json)") self.world_meta = {} self.world_meta['world3d'] = self._world3d self.world_meta['walls_width'] = self.walls_width self.world_meta['doors_width'] = self.doors_width if len(self.floors_meta) > 1: self.world_meta['multifloor_building'] = 1 else: self.world_meta['multifloor_building'] = 0 self.s.query('INSERT INTO world_meta(json) VALUES (?)', (json.dumps(self.world_meta), )) # }}} def _geometry2sqlite(self): # {{{ data = [] for floor, gg in self.cadfds.items(): for k, arr in gg.items(): if k in ('META'): continue for v in arr: zz = list(zip(*v['points'])) bbox = [ int(min(zz[0])), int(min(zz[1])), int(max(zz[0])), int(max(zz[1])) ] attrs = self._prepare_attrs(v) record = self._prepare_geom_record(k, v, bbox, floor, attrs, gg['META']) if record != False: data.append(record) self.s.query( "CREATE TABLE aamks_geom(name,floor,type_pri,type_sec,type_tri,x0,y0,z0,x1,y1,z1,global_type_id,exit_type, room_enter, terminal_door, points)" ) self.s.executemany( 'INSERT INTO aamks_geom VALUES ({})'.format(','.join( '?' * len(data[0]))), data) #}}} def _prepare_attrs(self, v): # {{{ aa = {"exit_type": None, "room_enter": None} if 'attribs' in v: for kk, vv in v['attribs'].items(): aa[kk] = vv return aa # }}} def _prepare_geom_record(self, k, v, bbox, floor, attrs, meta): # {{{ ''' Format a record for sqlite. Hvents get fixed width self.doors_width cm ''' # OBST if k in ('OBST', ): if len(v['points']) > 5: print( "You need to split obstacles having more than 5 vertices") exit() type_pri = 'OBST' type_tri = '' # EVACUEE elif k in ('EVACUEE', ): type_pri = 'EVACUEE' type_tri = '' # FIRE elif k in ('FIRE', ): type_pri = 'FIRE' type_tri = '' # MVENT elif k in ('MVENT', ): type_pri = 'MVENT' type_tri = '' # VVENT elif k in ('VVENT', ): type_pri = 'VVENT' height = 10 type_tri = '' # COMPA elif k in ('ROOM', 'COR', 'HALL', 'STAI'): type_pri = 'COMPA' type_tri = '' # HVENT elif k in ('DOOR', 'DCLOSER', 'DELECTR', 'HOLE', 'WIN'): type_pri = 'HVENT' if k in ('DOOR', 'DCLOSER', 'DELECTR', 'HOLE'): type_tri = 'DOOR' elif k in ('WIN'): type_tri = 'WIN' if 'name' not in v: v['name'] = '' #self.s.query("CREATE TABLE aamks_geom(name , floor , type_pri , type_sec , type_tri , x0 , y0 , z0 , x1 , y1 , z1 , global_type_id , exit_type , room_enter , terminal_door , points)") return (v['name'], floor, type_pri, k, type_tri, bbox[0], bbox[1], meta['z0'], bbox[2], bbox[3], meta['z1'], None, attrs['exit_type'], attrs['room_enter'], attrs['exit_type'], json.dumps(v['points'])) # }}} def _fire_origin(self): # {{{ si = SimIterations(self.conf['project_id'], self.conf['scenario_id'], self.conf['number_of_simulations']) self.s.query( "CREATE TABLE fire_origin(name,is_room,x,y,z,floor,sim_id)") for sim_id in range(*si.get()): seed(sim_id) r = self.s.query( "SELECT * FROM aamks_geom WHERE type_pri='FIRE'")[0] fire_origin = [ 'fire', 1, r['x0'], r['y0'], r['z0'], r['floor'], sim_id ] self.s.query( 'INSERT INTO fire_origin VALUES (? , ? , ? , ? , ? , ? , ?)', fire_origin) # }}} def _debug(self): # {{{ #dd(os.environ['AAMKS_PROJECT']) #self.s.dumpall() #self.s.dump_geoms() #dd(self.s.query("select * from aamks_geom")) #dd(self.s.query("select * from world2d")) #exit() #self.s.dump() pass
class SmokeQuery: def __init__(self, floor): ''' * On class init we read cell2compa map and and query_vertices from sqlite. * We are getting read_cfast_record(T) calls in say 10s intervals: We only store this single T'th record in a dict. * We are getting lots of get_conditions((x,y),param) calls after read_cfast_record(T) returns the needed CFAST record. Sqlite sends: a) cell2compa: (1000, 600): R_1 b) query_vertices: (1000, 600): [1000, 1100, 1200], [ 10, 10, 12 ] After CFAST produces the conditions at time T, feed _compa_conditions. For any evacuee's (x,y) it will be easy to find the square he is in. If we have rectangles in our square we use some optimizations to find the correct rectangle. Finally fetch the conditions via cell-compa map. ''' self.json = Json() try: self.s = Sqlite("aamks.sqlite", 1) except: print("mimooh CFAST fallback, not for production!") self.s = Sqlite( "{}/aamks.sqlite".format(os.environ['AAMKS_PROJECT']), 1) self.config = self.json.read('{}/evac/config.json'.format( os.environ['AAMKS_PATH'])) self._sqlite_query_vertices(floor) self._sqlite_cell2compa(floor) self._init_compa_conditions() #print("smoke_query, enable me") self._cfast_headers() # TODO needs to enable! def _sqlite_query_vertices(self, floor): # {{{ ''' Python has this nice dict[(1,2)], but json cannot handle it. We have passed it as dict['1x2'] and now need to bring back from str to tuple. ''' son = json.loads( self.s.query("SELECT * FROM query_vertices")[0]['json']) d = son[floor] self._square_side = d['square_side'] self._query_vertices = OrderedDict() for k, v in d['query_vertices'].items(): z = tuple(int(n) for n in k.split("x")) self._query_vertices[z] = v # }}} def _sqlite_cell2compa(self, floor): # {{{ son = json.loads(self.s.query("SELECT * FROM cell2compa")[0]['json']) d = son[floor] self._cell2compa = OrderedDict() for k, v in d.items(): z = tuple(int(n) for n in k.split("x")) self._cell2compa[z] = v # }}} def _results(self, query, cell): # {{{ ''' Outside is for debugging - should never happen in aamks. ''' try: compa = self._cell2compa[cell] except Exception as e: compa = "outside" return self.compa_conditions[compa], compa # }}} def _init_compa_conditions(self): # {{{ ''' Prepare dict structure for cfast csv values. Csv contain some params that are relevant for aamks and some that are not. self._compa_conditions['R_1']: OrderedDict([('TIME', None), ('CEILT' , None) , ...) self._compa_conditions['R_2']: OrderedDict([('TIME', None), ('CEILT' , None) , ...) self._compa_conditions['R_3']: OrderedDict([('TIME', None), ('CEILT' , None) , ...) self._compa_conditions['outside']=OrderedDict() is more for debugging. ''' self.relevant_params = ('CEILT', 'DJET', 'FLHGT', 'FLOORT', 'HGT', 'HRR', 'HRRL', 'HRRU', 'IGN', 'LLCO', 'LLCO2', 'LLH2O', 'LLHCL', 'LLHCN', 'LLN2', 'LLO2', 'LLOD', 'LLT', 'LLTS', 'LLTUHC', 'LWALLT', 'PLUM', 'PRS', 'PYROL', 'TRACE', 'ULCO', 'ULCO2', 'ULH2O', 'ULHCL', 'ULHCN', 'ULN2', 'ULO2', 'ULOD', 'ULT', 'ULTS', 'ULTUHC', 'UWALLT', 'VOL') self.all_compas = [ i['name'] for i in self.s.query( "SELECT name FROM aamks_geom where type_pri = 'COMPA'") ] self.compa_conditions = OrderedDict() for compa in self.all_compas: self.compa_conditions[compa] = OrderedDict([ (x, None) for x in ['TIME'] + list(self.relevant_params) ]) self.compa_conditions['outside'] = OrderedDict([('TIME', None)]) # }}} def _cfast_headers(self): # {{{ ''' CFAST must have produced the first lines of csv by now, which is the header. Get 3 first rows from n,s,w files and make headers: params and geoms. Happens only once. ''' self._headers = OrderedDict() for letter in ['n', 's', 'w']: f = 'cfast_{}.csv'.format(letter) with open(f, 'r') as csvfile: reader = csv.reader(csvfile, delimiter=',') headers = [] for x in range(3): headers.append( [field.replace(' ', '') for field in next(reader)]) headers[x] headers[0] = [re.sub("_.*", "", xx) for xx in headers[0]] self._headers[letter] = OrderedDict() self._headers[letter]['params'] = headers[0] self._headers[letter]['geoms'] = headers[2] # }}} def cfast_has_time(self, time): # {{{ ''' CFAST dumps 4 header records and then data records. Data records are indexed by time with delta=10s. AAMKS has this delta hardcoded: CFAST dumps data in 10s intervals. ''' needed_record_id = int(time / 10) + 1 with open('cfast_n.csv') as f: num_data_records = sum(1 for _ in f) - 4 if num_data_records > needed_record_id: return 1 else: return 0 # }}} def read_cfast_record(self, time): # {{{ ''' We had parsed headers separately. Now we only parse numbers from n,s,w files. Application needs to call us prior to massive queries for conditions at (x,y). ''' for letter in ['n', 's', 'w']: f = 'cfast_{}.csv'.format(letter) with open(f, 'r') as csvfile: reader = csv.reader(csvfile, delimiter=',') for x in range(4): next(reader) for row in reader: if int(float(row[0])) == time: needed_record = [float(j) for j in row] needed_record[0] = int(float(row[0])) break for compa in self.all_compas: self.compa_conditions[compa]['TIME'] = needed_record[0] self.compa_conditions['outside']['TIME'] = needed_record[0] for m in range(len(needed_record)): if self._headers[letter]['params'][ m] in self.relevant_params and self._headers[letter][ 'geoms'][m] in self.all_compas: self.compa_conditions[self._headers[letter]['geoms'][m]][ self._headers[letter]['params'][m]] = needed_record[m] return 1 # }}} def get_conditions(self, q, floor): # {{{ ''' First we find to which square our q belongs. If this square has 0 rectangles then we return conditions from the square. If the square has rectangles we need to loop through those rectangles. Finally we read the smoke conditions from the cell. ''' floors = json.loads( self.s.query("SELECT * FROM floors_meta")[0]['json']) self.floor_dim = floors[str(floor)] x = self.floor_dim['minx'] + self._square_side * int( (q[0] - self.floor_dim['minx']) / self._square_side) y = self.floor_dim['miny'] + self._square_side * int( (q[1] - self.floor_dim['miny']) / self._square_side) if len(self._query_vertices[x, y]['x']) == 1: return self._results(q, (x, y)) else: for i in range( bisect.bisect(self._query_vertices[(x, y)]['x'], q[0]), 0, -1): if self._query_vertices[(x, y)]['y'][i - 1] < q[1]: rx = self._query_vertices[(x, y)]['x'][i - 1] ry = self._query_vertices[(x, y)]['y'][i - 1] return self._results(q, (rx, ry)) return self._results(q, (x, y)) # outside! # }}} def get_visibility(self, position, time, floor): # {{{ query = self.get_conditions(position, floor) conditions = query[0] room = query[1] #print('FLOOR: {}, ROOM: {}, HGT: {}, TIME: {}'.format(floor, room, conditions['HGT'], time)) if conditions == 'outside': print('outside') hgt = conditions['HGT'] if hgt == None: return 0, room if hgt > self.config['LAYER_HEIGHT']: return conditions['LLOD'], room else: return conditions['ULOD'], room # }}} def get_fed(self, position, time, floor): # {{{ conditions = self.get_conditions(position, floor)[0] hgt = conditions['HGT'] if hgt == None: return 0. if hgt > self.config['LAYER_HEIGHT']: layer = 'L' else: layer = 'U' fed_co = 2.764e-5 * ((conditions[layer + 'LCO'] * 1000000)** 1.036) * (self.config['TIME_STEP'] / 60) fed_hcn = (exp((conditions[layer + 'LHCN'] * 10000) / 43) / 220 - 0.0045) * (self.config['TIME_STEP'] / 60) fed_hcl = ( (conditions['LLHCL'] * 1000000) / 1900) * self.config['TIME_STEP'] fed_o2 = (self.config['TIME_STEP'] / 60) / (60 * exp(8.13 - 0.54 * (20.9 - conditions[layer + 'LO2']))) hv_co2 = exp(0.1903 * conditions[layer + 'LCO2'] + 2.0004) / 7.1 fed_total = (fed_co + fed_hcn + fed_hcl) * hv_co2 + fed_o2 return fed_total # }}} def get_final_vars(self): # {{{ ''' The app should call us after CFAST produced all output. These are the summaries of various min values. ''' self._collect_final_vars() #dd(self.sf.query('SELECT * from finals order by param,value')) finals = OrderedDict() # min(time) for HGT_COR < 1.8 hgt = self.sf.query( "SELECT MIN(time) FROM finals WHERE compa_type='c' AND param='HGT' AND value < 1.8" )[0]['MIN(time)'] if hgt == None: hgt = 9999 ulod = self.sf.query( "SELECT MIN(time) FROM finals WHERE compa_type='c' AND param='ULOD' AND value > 0" )[0]['MIN(time)'] if ulod == None: ulod = 9999 ult = self.sf.query( "SELECT MIN(time) FROM finals WHERE compa_type='c' AND param='ULT' AND value > 60" )[0]['MIN(time)'] if ult == None: ult = 9999 finals['dcbe'] = max(hgt, ulod, ult) # min(HGT_COR) finals['min_hgt_cor'] = self.sf.query( "SELECT MIN(value) FROM finals WHERE compa_type='c' AND param='HGT'" )[0]['MIN(value)'] # min(HGT_COMPA) finals['min_hgt_compa'] = self.sf.query( "SELECT MIN(value) FROM finals WHERE param='HGT'")[0]['MIN(value)'] # min(ULT_COMPA) finals['max_temp_compa'] = self.sf.query( "SELECT MAX(value) FROM finals WHERE param='ULT'")[0]['MAX(value)'] c_const = 5 # min(ULOD_COR) ul_od_cor = self.sf.query( "SELECT MAX(value) FROM finals WHERE compa_type='c' AND param='ULOD'" )[0]['MAX(value)'] if ul_od_cor == 0: finals['min_vis_cor'] = 30 else: finals['min_vis_cor'] = c_const / (ul_od_cor * 2.303) # min(ULOD_COMPA) ul_od_compa = self.sf.query( "SELECT MAX(value) FROM finals WHERE param='ULOD'" )[0]['MAX(value)'] if ul_od_compa == 0: finals['min_vis_compa'] = 30 else: finals['min_vis_compa'] = c_const / (ul_od_compa * 2.303) return finals # }}} def _collect_final_vars(self): # {{{ ''' Create finals.sqlite for this very sim_id. Convert CFAST csvs into sqlite. ''' finals = [] for letter in ['n', 's']: f = 'cfast_{}.csv'.format(letter) with open(f, 'r') as csvfile: reader = csv.reader(csvfile, delimiter=',') for x in range(4): next(reader) for row in reader: record = [float(j) for j in row] record[0] = int(float(row[0])) for i, param in enumerate(self._headers[letter]['params']): if param != 'Time': if self._headers[letter]['geoms'][i] != 'Outside': if self._headers[letter]['geoms'][ i] != 'medium': compa = self._headers[letter]['geoms'][i][ 0] finals.append( (record[0], param, record[i], self._headers[letter]['geoms'][i], compa)) try: os.remove("finals.sqlite") except: pass self.sf = Sqlite("finals.sqlite") self.sf.query( "CREATE TABLE finals('time','param','value','compa','compa_type')") self.sf.executemany( 'INSERT INTO finals VALUES ({})'.format(','.join( '?' * len(finals[0]))), finals)