RINGMesh  Version 5.0.0
A programming library for geological model meshes
io_svg.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2012-2017, Association Scientifique pour la Geologie et ses
3  * Applications (ASGA). All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of ASGA nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
18  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ASGA BE LIABLE FOR ANY DIRECT,
20  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  * http://www.ring-team.org
28  *
29  * RING Project
30  * Ecole Nationale Superieure de Geologie - GeoRessources
31  * 2 Rue du Doyen Marcel Roubault - TSA 70605
32  * 54518 VANDOEUVRE-LES-NANCY
33  * FRANCE
34  */
35 
41 namespace {
42 
43  class GeoModelBuilderSVG final : public GeoModelBuilderFile< 2 > {
44  public:
45  GeoModelBuilderSVG( GeoModel2D& geomodel, const std::string& filename )
46  : GeoModelBuilderFile< 2 >( geomodel, filename ), height_( 0. )
47  {
48  }
49  virtual ~GeoModelBuilderSVG() = default;
50 
51  void load_file()
52  {
53  tinyxml2::XMLDocument svg_file;
54  if( svg_file.LoadFile( filename_.c_str() ) != tinyxml2::XML_SUCCESS ) {
55  throw RINGMeshException( "I/O", "Error while loading svg file." );
56  }
57  tinyxml2::XMLElement* svg_node = svg_file.FirstChildElement( "svg" );
58  if( svg_node == nullptr ) {
59  throw RINGMeshException( "I/O",
60  "Error while getting root of svg file." );
61  }
62  height_ = svg_node->DoubleAttribute( "height" );
63  create_lines( svg_node );
64  create_corners();
65  }
66 
67  private:
68  void create_lines( tinyxml2::XMLElement* svg_node )
69  {
70  tinyxml2::XMLElement* group = svg_node->FirstChildElement( "g" );
71  while( group != nullptr ) {
72  vec2 group_translation = get_transform( group );
73  tinyxml2::XMLElement* path = group->FirstChildElement( "path" );
74  while( path != nullptr ) {
75  vec2 translation = group_translation + get_transform( path );
76  std::string data = path->Attribute( "d" );
77  std::vector< vec2 > vertices = get_path_vertices( data,
78  translation );
79  index_t line_id = topology.create_mesh_entity(
80  Line2D::type_name_static() ).index();
81  geometry.set_line( line_id, vertices );
82  path = path->NextSiblingElement( "path" );
83  }
84  group = group->NextSiblingElement( "g" );
85  }
86  }
87 
88  vec2 get_transform( tinyxml2::XMLElement* group )
89  {
90  const char* attribute = group->Attribute( "transform" );
91  if( attribute == nullptr ) {
92  return vec2();
93  }
94  std::string action, parameters;
95  GEO::String::split_string( attribute, '(', action, parameters );
96  if( action != "translate" ) {
97  throw RINGMeshException( "I/O", "Forbidden transformation ", action,
98  " found in the svg file. " );
99  }
100  parameters.pop_back();
101  std::vector< std::string > coordinates;
102  GEO::String::split_string( parameters, ',', coordinates );
103  vec2 translation;
104  translation.x = GEO::String::to_double( coordinates.front() );
105  translation.y = GEO::String::to_double( coordinates.back() );
106  return translation;
107  }
108 
109  void create_corners()
110  {
111  std::vector< vec2 > point_extremities;
112  point_extremities.reserve( geomodel_.nb_lines() * 2 );
113  for( const auto& line : line_range < 2 > ( geomodel_ ) ) {
114  point_extremities.push_back( line.vertex( 0 ) );
115  point_extremities.push_back( line.vertex( line.nb_vertices() - 1 ) );
116  }
117 
118  NNSearch2D nn_search( point_extremities );
119  std::vector< index_t > index_map;
120  std::vector< vec2 > unique_points;
121  std::tie( std::ignore, index_map, unique_points ) =
122  nn_search.get_colocated_index_mapping_and_unique_points(
123  geomodel_.epsilon() );
124 
125  topology.create_mesh_entities( Corner2D::type_name_static(),
126  static_cast< index_t > ( unique_points.size() ) );
127  for( index_t c : range( geomodel_.nb_corners() ) ) {
128  geometry.set_corner( c, unique_points[c] );
129  }
130  index_t index = 0;
131  for( const auto& line : line_range < 2 > ( geomodel_ ) ) {
132  gmme_id line_id = line.gmme();
133  index_t point0 = index_map[index++ ];
134  gmme_id corner0( Corner2D::type_name_static(), point0 );
135  index_t point1 = index_map[index++ ];
136  gmme_id corner1( Corner2D::type_name_static(), point1 );
137  topology.add_mesh_entity_boundary_relation( line_id, corner0 );
138  topology.add_mesh_entity_boundary_relation( line_id, corner1 );
139 
140  // Update line vertex extremities with corner coordinates
141  geometry.set_mesh_entity_vertex( line_id, 0, unique_points[point0],
142  false );
143  geometry.set_mesh_entity_vertex( line_id, line.nb_vertices() - 1,
144  unique_points[point1], false );
145  }
146  }
147 
148  std::vector< vec2 > get_path_vertices(
149  std::string& data,
150  const vec2& translation ) const
151  {
152  std::vector< std::string > tokens = get_tokens( data );
153  std::vector< vec2 > vertices;
154  vertices.reserve( tokens.size() );
155  bool is_absolute = true;
156  for( index_t i = 0; i < tokens.size(); i++ ) {
157  std::string& token = tokens[i];
158  if( std::isalpha( token.front() ) ) {
159  ringmesh_assert( token.size() == 1 );
160  is_absolute = is_command_absolute( token );
161  } else {
162  vec2 vertex;
163  GEO::String::from_string( token, vertex.x );
164  GEO::String::from_string( tokens[++i], vertex.y );
165  if( is_absolute || vertices.empty() ) {
166  vertex += translation;
167  vertex.y = height_ - vertex.y;
168  vertices.push_back( vertex );
169  } else {
170  vertex.y = -vertex.y;
171  vertices.push_back( vertices.back() + vertex );
172  }
173  }
174  }
175  return vertices;
176  }
177 
178  void throw_if_forbidden_command( const std::string& data ) const
179  {
180  std::string forbidden_letters( "HhVvCcSs" );
181  for( char letter : forbidden_letters ) {
182  if( data.find( letter ) != std::string::npos ) {
183  throw RINGMeshException( "I/O", "Forbidden command ", letter,
184  " found in the svg file. ",
185  "Flatten your paths before importing in RINGMesh." );
186  }
187  }
188  }
189 
190  std::vector< std::string > get_tokens( std::string& data ) const
191  {
192  throw_if_forbidden_command( data );
193  std::replace( data.begin(), data.end(), ',', ' ' );
194  std::string letters( "MmLl" );
195  for( char letter : letters ) {
196  std::string string_letter = GEO::String::to_string( letter );
197  replace_all_string_occurens( data, string_letter,
198  " " + string_letter + " " );
199  }
200  std::vector< std::string > tokens;
201  GEO::String::split_string( data, ' ', tokens );
202  return tokens;
203  }
204 
205  void replace_all_string_occurens(
206  std::string& context,
207  const std::string& to_replace,
208  const std::string& replacement ) const
209  {
210  std::size_t look_here = 0;
211  std::size_t found_here;
212  while( ( found_here = context.find( to_replace, look_here ) )
213  != std::string::npos ) {
214  context.replace( found_here, to_replace.size(), replacement );
215  look_here = found_here + replacement.size();
216  }
217  }
218 
219  bool is_command_absolute( const std::string& letter ) const
220  {
221  return letter == "M" || letter == "L";
222  }
223 
224  private:
225  double height_;
226  };
227 
228  class SVGIOHandler final: public GeoModelIOHandler< 2 > {
229  public:
230  virtual ~SVGIOHandler() = default;
231 
232  void load( const std::string& filename, GeoModel2D& geomodel ) final
233  {
234  GeoModelBuilderSVG builder( geomodel, filename );
235  builder.build_geomodel();
236  }
237  void save( const GeoModel2D& geomodel, const std::string& filename ) final
238  {
239  ringmesh_unused( filename );
240  ringmesh_unused( geomodel );
241  throw RINGMeshException( "I/O",
242  "Saving a GeoModel in svg not implemented yet" );
243  }
244  };
245 
246 }
void ringmesh_unused(const T &)
Definition: common.h:105
vecn< 2 > vec2
Definition: types.h:78
#define ringmesh_assert(x)