def mesh_and_interpolate(args): set_logger_level(args.verbose) t1 = perf_counter() logger.info("~> Reading input files") axe = get_hydraulic_axis(args.infile_axis) section_seq = CrossSectionSequence.from_file( args.infile_cross_sections, "Cross-section", field_id=args.attr_cross_sections, project_straight_line=args.project_straight_line) # if args.infile_epis is not None and args.dist_corr_epi is not None: # epis = CrossSectionSequence.from_file(args.infile_epis, "Groynes", field_id=args.attr_epis, # project_straight_line=args.project_straight_line) # else: # epis = None section_seq.compute_dist_proj_axe(axe, args.dist_max) section_seq.check_intersections() section_seq.sort_by_dist() # section_seq.export_sections_shp('profiles_projected.shp') # DEBUG if args.infile_constraint_lines is None: constraint_lines = ConstraintLine.get_lines_and_set_limits_from_sections( section_seq, args.interp_constraint_lines) else: constraint_lines = ConstraintLine.get_lines_from_file( args.infile_constraint_lines, args.interp_constraint_lines) if args.nb_pts_lat is not None and len(constraint_lines) != 2: raise TatooineException( "Argument `--nb_pts_lat` is only compatible with 2 constraint lines!" ) if args.interp_values.startswith('BI') and len(constraint_lines) != 2: raise TatooineException( "A 2D interpolation is only compatible with 2 constraint lines!" ) section_seq.find_and_add_limits(constraint_lines, args.dist_max) 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.export_segments('check_segments.shp') # DEBUG # if epis is not None: # mesh_constr.corr_bathy_on_epis(epis, args.dist_corr_epi) if args.outfile_nodes is not None: mesh_constr.export_points(args.outfile_nodes) if args.outfile_mesh is not None: mesh_constr.build_mesh() mesh_constr.export_mesh(args.outfile_mesh, lang=args.lang) t2 = perf_counter() logger.info("=> Execution time: {}s".format(t2 - t1))
def check_intersections(self): """Display warning with intersections details""" logger.info("~> Checking that cross-sections do not intersect") intersections = get_intersections([section.geom for section in self]) if intersections: logger.warn("Following limits are found:") for (i, j) in intersections: logger.warn("- between '{}' and '{}'".format(self[i], self[j]))
def export_segments(self, path): if path.endswith('.shp'): logger.info("~> Export segments in a shp file") with shapefile.Writer(path, shapeType=shapefile.POLYLINE) as w: w.field('id_seg', 'N', decimal=6) for i, (node1, node2) in enumerate(self.segments): point1 = self.points[node1] point2 = self.points[node2] w.line([[[point1['X'], point1['Y']], [point2['X'], point2['Y']]]]) w.record(id_seg=i) else: raise TatooineException( "Only the shp format is supported for segments")
def find_and_add_limits(self, constraint_lines, dist_max): """ @param constraint_lines <[ConstraintLine]>: list of constraint lines @param dist_max <float>: maximum search distance to rescue intersections for limits """ logger.info("~> Looking for limits") for i, section in enumerate(self): for constraint_line in constraint_lines: section.find_and_add_limit(constraint_line, dist_max) section.sort_limits() limits = section.limits.keys() # only to print logger.debug("> {}".format(section)) logger.debug("{} limits found with lines {}".format( len(limits), list(limits)))
def corr_bathy_on_epis(self, epis, dist_corr_epi): raise NotImplementedError #TODO logger.info("~> Correction bathymetry around 'épis'") if self.var_names() != ['Z']: raise TatooineException( "Impossible to correct elevation of 'épis' for the sediment layers" ) for epi in epis: epi_geom = epi.coord.convert_as_linestring() for i, coord in enumerate(self.points): pt_node = Point((coord['X'], coord['Y'])) if epi_geom.distance(pt_node) < dist_corr_epi: Xt_proj = epi_geom.project(pt_node) pt_proj = epi_geom.interpolate(Xt_proj) epi.coord.values['Z'][i] = pt_proj.z
def densify_cross_sections(args): set_logger_level(args.verbose) t1 = perf_counter() logger.info("~> Reading input files") axe = get_hydraulic_axis(args.infile_axis) section_seq = CrossSectionSequence.from_file( args.infile_cross_sections, "Cross-section", field_id=args.attr_cross_sections, project_straight_line=args.project_straight_line) section_seq.compute_dist_proj_axe(axe, args.dist_max) section_seq.check_intersections() section_seq.sort_by_dist() if args.infile_constraint_lines is None: constraint_lines = ConstraintLine.get_lines_and_set_limits_from_sections( section_seq, args.interp_constraint_lines) else: constraint_lines = ConstraintLine.get_lines_from_file( args.infile_constraint_lines, args.interp_constraint_lines) if args.nb_pts_lat is not None and len(constraint_lines) != 2: raise TatooineException( "Argument `--nb_pts_lat` is only compatible with 2 constraint lines!" ) if args.interp_values.startswith('BI') and len(constraint_lines) != 2: raise TatooineException( "A 2D interpolation is only compatible with 2 constraint lines!" ) section_seq.find_and_add_limits(constraint_lines, args.dist_max) #section_seq.export_sections_shp('export_cross-sections.shp') 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, True) mesh_constr.export_sections(args.outfile_sections) t2 = perf_counter() logger.info("=> Execution time: {}s".format(t2 - t1))
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 compute_dist_proj_axe(self, axe_geom, dist_max): """ @brief: Compute distance along hydraulic axis @param axe_geom <shapely.geometry.LineString>: hydraulic axis (/!\ Beware of its orientation) @param dist_max <float>: maximum search distance to rescue intersections for limits """ logger.info( "~> Compute distances along hydraulic axis to order cross-sections" ) to_keep_list = [] for section in self: section_geom = section.geom if section_geom.intersects(axe_geom): intersection = section_geom.intersection(axe_geom) if isinstance(intersection, Point): section.dist_proj_axe = axe_geom.project(intersection) else: raise TatooineException( "Intersection between '{}' and the hydraulic axis " "is not a unique point".format(section)) else: if dist_max is not None: for pos in (0, -1): dist = section_geom.distance( Point(axe_geom.coords[pos])) if dist < dist_max: section.dist_proj_axe = 0.0 if pos == 0 else axe_geom.length if section.dist_proj_axe == -1: logger.warn( "{} do not intersect the hydraulic axis (distance = {}m) and is ignored" .format(section, section.geom.distance(axe_geom))) to_keep_list.append(False) else: to_keep_list.append(True) self.section_list = [ p for p, to_keep in zip(self.section_list, to_keep_list) if to_keep ]
def export_points(self, path): if path.endswith('.xyz'): logger.info("~> Exports points in xyz") with open(path, 'wb') as fileout: z_array = self.interp_values_from_geom()[0, :] np.savetxt(fileout, np.vstack((self.points['X'], self.points['Y'], z_array)).T, delimiter=' ', fmt='%.{}f'.format(DIGITS)) elif path.endswith('.shp'): logger.info("~> Exports points in shp") z_array = self.interp_values_from_geom()[0, :] with shapefile.Writer(path, shapeType=shapefile.POINT) as w: w.field('zone', 'N', decimal=6) w.field('bed', 'N', decimal=6) w.field('Xt_upstream', 'N', decimal=6) w.field('Xt_downstream', 'N', decimal=6) w.field('xt', 'N', decimal=6) w.field('xl', 'N', decimal=6) w.field('Z', 'N', decimal=6) for row, z in zip(self.points, z_array): w.point(row['X'], row['Y']) w.record( **{ 'zone': float(row['zone']), 'bed': float(row['bed']), 'Xt_upstream': row['Xt_upstream'], 'Xt_downstream': row['Xt_downstream'], 'xt': row['xt'], 'xl': row['xl'], 'Z': z }) else: raise TatooineException( "Only shp and xyz are supported for points set")
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 export_mesh(self, path, lang='en'): """ Export generated mesh in slf, t3s or LandXML TODO: export multiple variables in t3s and LandXML """ logger.info("~> Write generated mesh") nnode, nelem = len(self.triangle['vertices']), len( self.triangle['triangles']) if path.endswith('.t3s'): with open(path, 'w', newline='') as fileout: # Write header date = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) fileout.write( """######################################################################### :FileType t3s ASCII EnSim 1.0 # Canadian Hydraulics Centre/National Research Council (c) 1998-2012 # DataType 2D T3 Scalar Mesh # :Application BlueKenue :Version 3.3.4 :WrittenBy TatooineMesher :CreationDate {} # #------------------------------------------------------------------------ # :NodeCount {} :ElementCount {} :ElementType T3 # :EndHeader """.format(date, nnode, nelem)) with open(path, mode='ab') as fileout: # Table with x, y, z coordinates np.savetxt(fileout, np.column_stack( (self.triangle['vertices'], self.interp_values_from_geom()[0, :])), delimiter=' ', fmt='%.{}f'.format(DIGITS)) # Table with elements (connectivity) np.savetxt(fileout, self.triangle['triangles'] + 1, delimiter=' ', fmt='%i') elif path.endswith('.xml'): env = Environment(loader=FileSystemLoader( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data'))) template = env.get_template("LandXML_template.xml") template_render = template.render(nodes=np.round( np.column_stack((self.triangle['vertices'], self.interp_values_from_geom()[0, :])), DIGITS), ikle=self.triangle['triangles'] + 1) # Write XML file with open(path, 'w') as fileout: fileout.write(template_render) elif path.endswith('.slf'): with Serafin.Write(path, lang, overwrite=True) as resout: output_header = Serafin.SerafinHeader( title='%s (Written by TatooineMesher)' % os.path.basename(path), lang=lang) output_header.from_triangulation( self.triangle['vertices'], self.triangle['triangles'] + 1) for var in self.var_names(): if var in basic_2D_vars_IDs: output_header.add_variable_from_ID(var) else: output_header.add_variable_str(var, var, '') resout.write_header(output_header) resout.write_entire_frame(output_header, 0.0, self.interp_values_from_geom()) else: raise NotImplementedError( "Only slf, t3s and xml formats are supported for the output mesh" )
def build_interp(self, constraint_lines, long_step, constant_long_disc): """ Build interpolation, add points and segments @param constraint_lines <[ConstraintLine]>: list of constraint lines @param long_step <float>: longitudinal space step @param constant_long_disc <bool> """ nb_pts_inter = 0 self.build_initial_profiles() # LOOP ON ZONES (between 2 consecutive cross-sections) logger.info("~> Building mesh per zone and then per bed") for i, (prev_section, next_section) in enumerate( zip(self.section_seq, self.section_seq[1:])): logger.debug("> Zone n°{} : between {} and {}".format( i, prev_section, next_section)) if constant_long_disc: nb_pts_inter = prev_section.compute_nb_pts_inter( next_section, long_step) Xp_adm_list = np.linspace(0.0, 1.0, num=nb_pts_inter + 2)[1:-1] # Looking for common limits between cross-sections common_limits_id = prev_section.common_limits( next_section.limits.keys()) logger.debug("Common limits: {}".format(list(common_limits_id))) if len(common_limits_id) < 2: raise TatooineException( "No interpolation in the interval %i, between %s and %s (%i common limits)" % (i, prev_section, next_section, len(common_limits_id))) else: first_bed = True # LOOP ON BEDS for j, (id1, id2) in enumerate( zip(common_limits_id, common_limits_id[1:])): pt_list_L1 = [] pt_list_L2 = [] logger.debug("Bed {}-{}".format(id1, id2)) # Extraction of cross-section portions (= beds) bed_1 = prev_section.extract_bed(id1, id2) bed_2 = next_section.extract_bed(id1, id2) # Curvilinear abscissa along constraint lines (Xp_profil1_L1, Xp_profil1_L2) = prev_section.get_Xt_lines(id1, id2) (Xp_profil2_L1, Xp_profil2_L2) = next_section.get_Xt_lines(id1, id2) dXp_L1 = Xp_profil2_L1 - Xp_profil1_L1 dXp_L2 = Xp_profil2_L2 - Xp_profil1_L2 if dXp_L1 < 0: raise TatooineException( "The constraint line {} is not oriented correctly". format(id1)) if dXp_L2 < 0: raise TatooineException( "The constraint line {} is not oriented correctly". format(id2)) if not constant_long_disc: nb_pts_inter = math.ceil( min(dXp_L1, dXp_L2) / long_step) - 1 Xp_adm_list = np.linspace(0.0, 1.0, num=nb_pts_inter + 2)[1:-1] L1_coord_int = constraint_lines[ id1].coord_sampling_along_line(Xp_profil1_L1, Xp_profil2_L1, Xp_adm_list) L2_coord_int = constraint_lines[ id2].coord_sampling_along_line(Xp_profil1_L2, Xp_profil2_L2, Xp_adm_list) # LOOP ON INTERMEDIATE CROSS-SECTIONS for k in range(nb_pts_inter): Xp = Xp_adm_list[k] P1 = Point(tuple(L1_coord_int[k])) P2 = Point(tuple(L2_coord_int[k])) if self.nb_pts_lat is None: nb_pts_lat = math.ceil( P1.distance(P2) / self.lat_step) + 1 else: nb_pts_lat = self.nb_pts_lat array = bed_1.interp_coord_linear( bed_2, Xp, nb_pts_lat) bed_int = Bed(array, ['Xt', 'xt']) bed_int.move_between_targets(P1, P2) coord_int = bed_int.array[[ 'X', 'Y', 'xt', 'Xt_upstream', 'Xt_downstream' ]] # Ignore `Xt` pt_list_L1.append(self.i_pt + 1) if not first_bed: # ignore first point because the constraint line was already considered coord_int = coord_int[1:] self.add_points(coord_int, i, Xp, j) pt_list_L2.append(self.i_pt) pt_list_L2 = np.array( [prev_section.get_limit_by_id(id2)['id_pt']] + pt_list_L2 + [next_section.get_limit_by_id(id2)['id_pt']]) self.add_segments_from_node_list(pt_list_L2) if first_bed: pt_list_L1 = np.array( [prev_section.get_limit_by_id(id1)['id_pt']] + pt_list_L1 + [next_section.get_limit_by_id(id1)['id_pt']]) self.add_segments_from_node_list(pt_list_L1) first_bed = False
def build_initial_profiles(self): logger.info( "~> Interpolate initial cross-sections taking into account limits") for i in range(len(self.section_seq)): curr_profile = self.section_seq[i] logger.debug(curr_profile) # Looking for upstream common limits if i == 0: common_limits_id_1 = curr_profile.limits.keys() else: common_limits_id_1 = curr_profile.common_limits( self.section_seq[i - 1].limits.keys()) # Looking for downstream common limits if i == len(self.section_seq) - 1: common_limits_id_2 = curr_profile.limits.keys() else: common_limits_id_2 = curr_profile.common_limits( self.section_seq[i + 1].limits.keys()) # Ordered union of upstream and downstream limits limits_id = curr_profile.common_limits( list(set(common_limits_id_1).union(common_limits_id_2))) first_bed = True for j, (id1, id2) in enumerate(zip(limits_id, limits_id[1:])): bed = curr_profile.extract_bed(id1, id2) coord_int = bed.interp_coord_along_bed_auto( self.lat_step, self.nb_pts_lat) if first_bed: curr_profile.get_limit_by_id(id1)['id_pt'] = self.i_pt + 1 else: coord_int = coord_int[1:] if i == 0: coord_int = rename_fields(coord_int, {'Xt': 'Xt_upstream'}) coord_int = append_fields(coord_int, 'Xt_downstream', np.zeros(len(coord_int)), usemask=False) self.add_points(coord_int, i, 0.0, j) else: coord_int = rename_fields(coord_int, {'Xt': 'Xt_downstream'}) coord_int = append_fields(coord_int, 'Xt_upstream', np.zeros(len(coord_int)), usemask=False) self.add_points(coord_int, i - 1, 1.0, j) curr_profile.get_limit_by_id(id2)['id_pt'] = self.i_pt # Add new segments new_i_pt = np.arange( curr_profile.get_limit_by_id(id1)['id_pt'], curr_profile.get_limit_by_id(id2)['id_pt'] + 1) self.add_segments_from_node_list(new_i_pt) if first_bed: first_bed = False
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))