def build_mesh(self, in_floworiented_crs=False, opts='p'): """ @brief: Build mesh under constraints @param in_floworiented_crs <bool>: boolean to which coordinate system is usec @param opts <str>: options for the triangulation. `p` - Triangulates a Planar Straight Line Graph. `q` - Quality mesh generation with no angles smaller than 20 degrees. An alternate minimum angle may be specified after the `q`. `a` - Imposes a maximum triangle area constraint. A fixed area constraint (that applies to every triangle) may be specified after the `a`, or varying areas may be read from the input dictionary. """ logger.info("~> Building mesh") if in_floworiented_crs: tri = self.export_floworiented_triangulation_dict() self.triangle = triangle.triangulate(tri, opts=opts) self.triangle['vertices'] = self.export_triangulation_dict()[ 'vertices'] # overwrite by cartesian coordinates else: tri = self.export_triangulation_dict() self.triangle = triangle.triangulate(tri, opts=opts) if opts == 'p': # Check that vertices correspond to points if len(self.points) != len(self.triangle['vertices']): if len(self.points) < len(self.triangle['vertices']): logger.error("New nodes are:") ori_points = np.column_stack( (self.points['X'], self.points['Y'])) ori_combined = ori_points[:, 0] * ori_points[:, 1] / ( ori_points[:, 0] + ori_points[:, 1]) new_points = self.triangle['vertices'] new_combined = new_points[:, 0] * new_points[:, 1] / ( new_points[:, 0] + new_points[:, 1]) diff = np.setxor1d(ori_combined, new_combined) logger.error(new_points[np.isin(new_combined, diff)]) raise TatooineException( "Mesh is corrupted... %i vs %i nodes." % (len(self.points), len(self.triangle['vertices']))) if 'triangles' not in self.triangle: raise TatooineException( "Mesh was not generated, no triangle found!") logger.info(self.summary())
def mesh_crue10_run(args): set_logger_level(args.verbose) t1 = perf_counter() # Read the model and its submodels from xml/shp files etude = Etude(args.infile_etu) modele = etude.get_modele(args.model_name) modele.read_all() logger.info(modele) for sous_modele in modele.liste_sous_modeles: sous_modele.remove_sectioninterpolee() sous_modele.normalize_geometry() logger.info(sous_modele.summary()) # sous_modele.write_shp_limites_lits_numerotes('limites_lits.shp') # DEBUG logger.info(modele) global_mesh_constr = MeshConstructor() # Handle branches in minor bed for i, branche in enumerate(modele.get_liste_branches()): # Ignore branch if branch_patterns is set and do not match with current branch name if args.branch_patterns is not None: ignore = True for pattern in args.branch_patterns: if pattern in branche.id: ignore = False break else: ignore = False if branche.type not in args.branch_types_filter or not branche.is_active: ignore = True if not ignore: logger.info("===== TRAITEMENT DE LA BRANCHE %s =====" % branche.id) axe = branche.geom try: section_seq = CrossSectionSequence() for crue_section in branche.liste_sections_dans_branche: if isinstance(crue_section, SectionProfil): coords = list(crue_section.get_coord(add_z=True)) section = CrossSection(crue_section.id, [(coord[0], coord[1]) for coord in coords], 'Section') # Determine some variables (constant over the simulation) from the geometry z = np.array([coord[2] for coord in coords]) is_bed_active = crue_section.get_is_bed_active_array() mean_strickler = crue_section.get_friction_coeff_array( ) section.coord.values = np.core.records.fromarrays( np.column_stack( (z, is_bed_active, mean_strickler)).T, names=VARIABLES_FROM_GEOMETRY) section_seq.add_section(section) section_seq.compute_dist_proj_axe(axe, args.dist_max) if len(section_seq) >= 2: section_seq.check_intersections() # section_seq.sort_by_dist() is useless because profiles are already sorted constraint_lines = ConstraintLine.get_lines_and_set_limits_from_sections( section_seq, args.interp_constraint_lines) mesh_constr = MeshConstructor( section_seq=section_seq, lat_step=args.lat_step, nb_pts_lat=args.nb_pts_lat, interp_values=args.interp_values) mesh_constr.build_interp(constraint_lines, args.long_step, args.constant_long_disc) mesh_constr.build_mesh(in_floworiented_crs=True) global_mesh_constr.append_mesh_constr(mesh_constr) else: logger.warning("Branche ignorée par manque de sections") except TatooineException as e: logger.error( "/!\\ Branche ignorée à cause d'une erreur bloquante :") logger.error(e.message) logger.info("\n") # Handle casiers in floodplain nb_casiers = len(modele.get_liste_casiers()) if args.infile_dem and nb_casiers > 0: logger.info("===== TRAITEMENT DES CASIERS =====") if not os.path.exists(args.infile_dem): raise TatooineException("File not found: %s" % args.infile_dem) from gdal import Open raster = Open(args.infile_dem) dem_interp = interp_raster(raster) floodplain_step = args.floodplain_step if not None else args.long_step max_elem_area = floodplain_step * floodplain_step / 2.0 simplify_dist = floodplain_step / 2.0 for i, casier in enumerate(modele.get_liste_casiers()): if casier.is_active: if casier.geom is None: raise TatooineException( "Geometry of %s could not be found" % casier) line = casier.geom.simplify(simplify_dist) if not line.is_closed: raise RuntimeError coords = resample_2d_line( line.coords, floodplain_step)[1:] # Ignore last duplicated node hard_nodes_xy = np.array(coords, dtype=np.float) hard_nodes_idx = np.arange(0, len(hard_nodes_xy), dtype=np.int) hard_segments = np.column_stack( (hard_nodes_idx, np.roll(hard_nodes_idx, 1))) tri = { 'vertices': np.array( np.column_stack( (hard_nodes_xy[:, 0], hard_nodes_xy[:, 1]))), 'segments': hard_segments, } triangulation = triangle.triangulate(tri, opts='qpa%f' % max_elem_area) nodes_xy = np.array(triangulation['vertices'], dtype=np.float) bottom = dem_interp(nodes_xy) points = unstructured_to_structured(np.column_stack( (nodes_xy, bottom)), names=['X', 'Y', 'Z']) global_mesh_constr.add_floodplain_mesh(triangulation, points) if len(global_mesh_constr.points) == 0: raise ExceptionCrue10( "Aucun point à traiter, adaptez l'option `--branch_patterns` et/ou `--branch_types_filter`" ) logger.info(global_mesh_constr.summary() ) # General information about the merged mesh if args.infile_rcal: # Read rcal result file results = RunResults(args.infile_rcal) logger.info(results.summary()) # Check result consistency missing_sections = modele.get_missing_active_sections( results.emh['Section']) if missing_sections: raise ExceptionCrue10("Sections manquantes :\n%s" % missing_sections) # Subset results to get requested variables at active sections varnames_1d = results.variables['Section'] logger.info("Variables 1D disponibles aux sections: %s" % varnames_1d) try: pos_z = varnames_1d.index('Z') except ValueError: raise TatooineException( "La variable Z doit être présente dans les résultats aux sections" ) if global_mesh_constr.has_floodplain: try: pos_z_fp = results.variables['Casier'].index('Z') except ValueError: raise TatooineException( "La variable Z doit être présente dans les résultats aux casiers" ) else: pos_z_fp = None pos_variables = [ results.variables['Section'].index(var) for var in varnames_1d ] pos_sections_list = [ results.emh['Section'].index(profil.id) for profil in global_mesh_constr.section_seq ] if global_mesh_constr.has_floodplain: pos_casiers_list = [ results.emh['Casier'].index(casier.id) for casier in modele.get_liste_casiers() if casier.is_active ] else: pos_casiers_list = [] additional_variables_id = ['H'] if 'Vact' in varnames_1d: additional_variables_id.append('M') values_geom = global_mesh_constr.interp_values_from_geom() z_bottom = values_geom[0, :] with Serafin.Write(args.outfile_mesh, args.lang, overwrite=True) as resout: title = '%s (written by TatooineMesher)' % os.path.basename( args.outfile_mesh) output_header = Serafin.SerafinHeader(title=title, lang=args.lang) output_header.from_triangulation( global_mesh_constr.triangle['vertices'], global_mesh_constr.triangle['triangles'] + 1) for var_name in VARIABLES_FROM_GEOMETRY: if var_name in ['B', 'W']: output_header.add_variable_from_ID(var_name) else: output_header.add_variable_str(var_name, var_name, '') for var_id in additional_variables_id: output_header.add_variable_from_ID(var_id) for var_name in varnames_1d: output_header.add_variable_str(var_name, var_name, '') resout.write_header(output_header) if args.calc_unsteady is None: for i, calc_name in enumerate(results.calc_steady_dict.keys()): logger.info("~> Calcul permanent %s" % calc_name) # Read a single *steady* calculation res_steady = results.get_res_steady(calc_name) variables_at_profiles = res_steady['Section'][ pos_sections_list, :][:, pos_variables] if global_mesh_constr.has_floodplain: z_at_casiers = res_steady['Casier'][pos_casiers_list, pos_z_fp] else: z_at_casiers = None # Interpolate between sections and set in casiers values_res = global_mesh_constr.interp_values_from_res( variables_at_profiles, z_at_casiers, pos_z) # Compute water depth: H = Z - Zf and clip below 0m (avoid negative values) depth = np.clip(values_res[pos_z, :] - z_bottom, a_min=0.0, a_max=None) # Merge and write values if 'Vact' in varnames_1d: # Compute velocity magnitude from Vact and apply mask "is active bed" velocity = values_res[ varnames_1d.index('Vact'), :] * values_geom[1, :] values = np.vstack( (values_geom, depth, velocity, values_res)) else: values = np.vstack((values_geom, depth, values_res)) resout.write_entire_frame(output_header, 3600.0 * i, values) else: calc_unsteady = results.get_calc_unsteady(args.calc_unsteady) logger.info("Calcul transitoire %s" % args.calc_unsteady) res_unsteady = results.get_res_unsteady(args.calc_unsteady) for i, (time, _) in enumerate(calc_unsteady.frame_list): logger.info("~> %fs" % time) res_at_sections = res_unsteady['Section'][i, :, :] variables_at_profiles = res_at_sections[ pos_sections_list, :][:, pos_variables] if global_mesh_constr.has_floodplain: z_at_casiers = res_unsteady['Casier'][i, pos_casiers_list, pos_z_fp] else: z_at_casiers = None # Interpolate between sections values_res = global_mesh_constr.interp_values_from_res( variables_at_profiles, z_at_casiers, pos_z) # Compute water depth: H = Z - Zf and clip below 0m (avoid negative values) depth = np.clip(values_res[pos_z, :] - z_bottom, a_min=0.0, a_max=None) # Merge and write values if 'Vact' in varnames_1d: # Compute velocity magnitude from Vact and apply mask "is active bed" velocity = values_res[ varnames_1d.index('Vact'), :] * values_geom[1, :] values = np.vstack( (values_geom, depth, velocity, values_res)) else: values = np.vstack((values_geom, depth, values_res)) resout.write_entire_frame(output_header, time, values) else: # Write a single frame with only variables from geometry global_mesh_constr.export_mesh(args.outfile_mesh, lang=args.lang) t2 = perf_counter() logger.info("=> Execution time: {}s".format(t2 - t1))
def mesh_mascaret_run(args): set_logger_level(args.verbose) t1 = perf_counter() masc_geo = MascaretGeoFile(args.infile_geo) logger.info("Read %s " % masc_geo) # masc_geo.export_shp_lines(args.infile_geo.replace('.georef', '.shp')) if not masc_geo.has_ref: raise TatooineException( "The file `%s` does not contain any georeferenced data" % masc_geo.file_name) global_mesh_constr = MeshConstructor() for reach_id, reach in masc_geo.reaches.items(): logger.info(reach) section_seq = CrossSectionSequence() dist_proj_axe = 0.0 prev_x, prev_y = 0.0, 0.0 for section_idx, masc_section in enumerate(reach): section = CrossSection( masc_section.id, [(x, y) for x, y in zip(masc_section.x, masc_section.y)], "Cross-section") section.coord.values = np.core.records.fromarrays( np.column_stack((masc_section.z, )).T, names=VARIABLES_FROM_GEOMETRY) x, y = masc_section.axis if section_idx != 0: dist_proj_axe += sqrt((x - prev_x)**2 + (y - prev_y)**2) section.dist_proj_axe = dist_proj_axe prev_x, prev_y = x, y section_seq.add_section(section) if len(section_seq) >= 2: section_seq.check_intersections() # section_seq.sort_by_dist() is useless because cross-sections are already sorted constraint_lines = ConstraintLine.get_lines_and_set_limits_from_sections( section_seq, args.interp_constraint_lines) mesh_constr = MeshConstructor(section_seq=section_seq, lat_step=args.lat_step, nb_pts_lat=args.nb_pts_lat, interp_values=args.interp_values) mesh_constr.build_interp(constraint_lines, args.long_step, args.constant_long_disc) mesh_constr.build_mesh(in_floworiented_crs=True) global_mesh_constr.append_mesh_constr(mesh_constr) else: logger.error( "/!\\ Reach %s ignored because it does not contain at least 2 sections" % reach_id) if len(global_mesh_constr.points) == 0: raise ExceptionCrue10("No node in the generated mesh!") logger.info(global_mesh_constr.summary() ) # General information about the merged mesh if args.infile_res: masc_res = MascaretFile(args.infile_res) masc_res.get_reaches() nb_section_in_geom = masc_geo.nsections if masc_res.nsections != nb_section_in_geom: raise TatooineException( "The number of sections is different between geometry (%i) and results file (%i)" % (nb_section_in_geom, masc_res.nsections)) varnames_1d = masc_res.varnames_dict['abbr'] logger.info("Variables 1D available at sections: %s" % varnames_1d) try: pos_z = varnames_1d.index('Z') except ValueError: raise TatooineException( "The variable Z must be present in the results file") additional_variables_id = ['H'] values_geom = global_mesh_constr.interp_values_from_geom() z_bottom = values_geom[0, :] with Serafin.Write(args.outfile_mesh, args.lang, overwrite=True) as resout: title = '%s (written by TatooineMesher)' % os.path.basename( args.outfile_mesh) output_header = Serafin.SerafinHeader(title=title, lang=args.lang) output_header.from_triangulation( global_mesh_constr.triangle['vertices'], global_mesh_constr.triangle['triangles'] + 1) for var_name in VARIABLES_FROM_GEOMETRY: if var_name == 'B': output_header.add_variable_from_ID(var_name) else: output_header.add_variable_str(var_name, var_name, '') for var_id in additional_variables_id: output_header.add_variable_from_ID(var_id) for var_name in varnames_1d: output_header.add_variable_str(var_name, var_name, '') resout.write_header(output_header) for idx_time, time in enumerate(masc_res.times): variables_at_sections = masc_res.get_values(idx_time)[reach.id] # Interpolate between sections and set in casiers values_res = global_mesh_constr.interp_values_from_res( variables_at_sections, None, pos_z) # Compute water depth: H = Z - Zf and clip below 0m (avoid negative values) depth = np.clip(values_res[pos_z, :] - z_bottom, a_min=0.0, a_max=None) values = np.vstack((values_geom, depth, values_res)) resout.write_entire_frame(output_header, time, values) else: # Write a single frame with only variables from geometry global_mesh_constr.export_mesh(args.outfile_mesh, lang=args.lang) t2 = perf_counter() logger.info("=> Execution time: {}s".format(t2 - t1))