1 /***************************************************************************
2                           qgsmeshcontours.cpp
3                           -------------------
4     begin                : September 25th, 2019
5     copyright            : (C) 2018 by Peter Petrik
6     email                : zilolv at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 #include "qgsmeshcontours.h"
18 #include "qgspoint.h"
19 #include "qgsmultilinestring.h"
20 #include "qgslinestring.h"
21 #include "qgspolygon.h"
22 #include "qgsmapsettings.h"
23 #include "qgsmeshlayer.h"
24 #include "qgstriangularmesh.h"
25 #include "qgsgeometryutils.h"
26 #include "qgsfeature.h"
27 #include "qgsgeometry.h"
28 #include "qgsmeshlayerutils.h"
29 #include "qgsmeshdataprovider.h"
30 #include "qgsfeedback.h"
31 #include "qgsproject.h"
32 
33 #include <limits>
34 
35 #include <QSet>
36 #include <QPair>
37 
QgsMeshContours(QgsMeshLayer * layer)38 QgsMeshContours::QgsMeshContours( QgsMeshLayer *layer )
39   : mMeshLayer( layer )
40 {
41   if ( !mMeshLayer ||  !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() )
42     return;
43 
44   // Support for meshes with edges is not implemented
45   if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) )
46     return;
47 
48   mMeshLayer->dataProvider()->populateMesh( &mNativeMesh );
49   mTriangularMesh.update( &mNativeMesh );
50 }
51 
QgsMeshContours(const QgsTriangularMesh & triangularMesh,const QgsMesh & nativeMesh,const QVector<double> & datasetValues,const QgsMeshDataBlock scalarActiveFaceFlagValues)52 QgsMeshContours::QgsMeshContours( const QgsTriangularMesh &triangularMesh,
53                                   const QgsMesh &nativeMesh,
54                                   const QVector<double> &datasetValues,
55                                   const QgsMeshDataBlock scalarActiveFaceFlagValues ):
56   mTriangularMesh( triangularMesh )
57   , mNativeMesh( nativeMesh )
58   , mDatasetValues( datasetValues )
59   , mScalarActiveFaceFlagValues( scalarActiveFaceFlagValues )
60 {}
61 
62 QgsMeshContours::~QgsMeshContours() = default;
63 
exportPolygons(const QgsMeshDatasetIndex & index,double min_value,double max_value,QgsMeshRendererScalarSettings::DataResamplingMethod method,QgsFeedback * feedback)64 QgsGeometry QgsMeshContours::exportPolygons(
65   const QgsMeshDatasetIndex &index,
66   double min_value,
67   double max_value,
68   QgsMeshRendererScalarSettings::DataResamplingMethod method,
69   QgsFeedback *feedback
70 )
71 {
72   if ( !mMeshLayer )
73     return QgsGeometry();
74 
75   populateCache( index, method );
76 
77   return exportPolygons( min_value, max_value, feedback );
78 }
79 
exportPolygons(double min_value,double max_value,QgsFeedback * feedback)80 QgsGeometry QgsMeshContours::exportPolygons( double min_value, double max_value, QgsFeedback *feedback )
81 {
82   if ( min_value > max_value )
83   {
84     const double tmp = max_value;
85     max_value = min_value;
86     min_value = tmp;
87   }
88 
89   QVector<QgsGeometry> multiPolygon;
90 
91   // STEP 1: Get Data
92   const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
93   const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
94 
95   // STEP 2: For each triangle get the contour line
96   for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
97   {
98     if ( feedback && feedback->isCanceled() )
99       break;
100 
101     const int nativeIndex = trianglesToNativeFaces.at( i );
102     if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
103       continue;
104 
105     const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
106     const int indices[3] =
107     {
108       triangle.at( 0 ),
109       triangle.at( 1 ),
110       triangle.at( 2 )
111     };
112 
113     const QVector<QgsMeshVertex> coords =
114     {
115       vertices.at( indices[0] ),
116       vertices.at( indices[1] ),
117       vertices.at( indices[2] )
118     };
119 
120     const double values[3] =
121     {
122       mDatasetValues.at( indices[0] ),
123       mDatasetValues.at( indices[1] ),
124       mDatasetValues.at( indices[2] )
125     };
126 
127     // any value is NaN
128     if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
129       continue;
130 
131     // all values on vertices are outside the range
132     if ( ( ( min_value > values[0] ) && ( min_value > values[1] ) && ( min_value > values[2] ) )  ||
133          ( ( max_value < values[0] ) && ( max_value < values[1] ) && ( max_value < values[2] ) ) )
134       continue;
135 
136     const bool valueInRange[3] =
137     {
138       ( min_value <= values[0] ) &&( max_value >= values[0] ),
139       ( min_value <= values[1] ) &&( max_value >= values[1] ),
140       ( min_value <= values[2] ) &&( max_value >= values[2] )
141     };
142 
143     // all values are inside the range == take whole triangle
144     if ( valueInRange[0] && valueInRange[1] && valueInRange[2] )
145     {
146       QVector<QgsMeshVertex> ring = coords;
147       ring.push_back( coords[0] );
148       std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString> ( coords );
149       std::unique_ptr< QgsPolygon > poly = std::make_unique< QgsPolygon >();
150       poly->setExteriorRing( ext.release() );
151       multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
152       continue;
153     }
154 
155     // go through all edges
156     QVector<QgsMeshVertex> ring;
157     for ( int i = 0; i < 3; ++i )
158     {
159       const int j = ( i + 1 ) % 3;
160 
161       if ( valueInRange[i] )
162       {
163         if ( valueInRange[j] )
164         {
165           // whole edge is part of resulting contour polygon edge
166           if ( !ring.contains( coords[i] ) )
167             ring.push_back( coords[i] );
168           if ( !ring.contains( coords[j] ) )
169             ring.push_back( coords[j] );
170         }
171         else
172         {
173           // i is part or the resulting edge
174           if ( !ring.contains( coords[i] ) )
175             ring.push_back( coords[i] );
176           // we need to find the other point
177           double value = max_value;
178           if ( values[i] > values[j] )
179           {
180             value = min_value;
181           }
182           const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
183           const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
184           if ( !ring.contains( xy ) )
185             ring.push_back( xy );
186         }
187       }
188       else
189       {
190         if ( valueInRange[j] )
191         {
192           // we need to find the other point
193           double value = max_value;
194           if ( values[i] < values[j] )
195           {
196             value = min_value;
197           }
198 
199           const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
200           const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
201           if ( !ring.contains( xy ) )
202             ring.push_back( xy );
203 
204           // j is part
205           if ( !ring.contains( coords[j] ) )
206             ring.push_back( coords[j] );
207 
208         }
209         else
210         {
211           // last option we need to consider is that both min and max are between
212           // value i and j, and in that case we need to calculate both point
213           double value1 = max_value;
214           double value2 = max_value;
215           if ( values[i] < values[j] )
216           {
217             if ( ( min_value < values[i] ) || ( max_value > values[j] ) )
218             {
219               continue;
220             }
221             value1 = min_value;
222           }
223           else
224           {
225             if ( ( min_value < values[j] ) || ( max_value > values[i] ) )
226             {
227               continue;
228             }
229             value2 = min_value;
230           }
231 
232           const double fraction1 = ( value1 - values[i] ) / ( values[j] - values[i] );
233           const QgsPoint xy1 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction1 );
234           if ( !ring.contains( xy1 ) )
235             ring.push_back( xy1 );
236 
237           const double fraction2 = ( value2 - values[i] ) / ( values[j] - values[i] );
238           const QgsPoint xy2 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction2 );
239           if ( !ring.contains( xy2 ) )
240             ring.push_back( xy2 );
241         }
242       }
243     }
244 
245     // add if the polygon is not degraded
246     if ( ring.size() > 2 )
247     {
248       std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString> ( ring );
249       std::unique_ptr< QgsPolygon > poly = std::make_unique< QgsPolygon >();
250       poly->setExteriorRing( ext.release() );
251       multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
252     }
253   }
254 
255   // STEP 3: dissolve the individual polygons from triangles if possible
256   if ( multiPolygon.isEmpty() )
257   {
258     return QgsGeometry();
259   }
260   else
261   {
262     const QgsGeometry res = QgsGeometry::unaryUnion( multiPolygon );
263     return res;
264   }
265 }
266 
exportLines(const QgsMeshDatasetIndex & index,double value,QgsMeshRendererScalarSettings::DataResamplingMethod method,QgsFeedback * feedback)267 QgsGeometry QgsMeshContours::exportLines( const QgsMeshDatasetIndex &index,
268     double value,
269     QgsMeshRendererScalarSettings::DataResamplingMethod method,
270     QgsFeedback *feedback )
271 {
272   // Check if the layer/mesh is valid
273   if ( !mMeshLayer )
274     return QgsGeometry();
275 
276   populateCache( index, method );
277 
278   return exportLines( value, feedback );
279 }
280 
exportLines(double value,QgsFeedback * feedback)281 QgsGeometry QgsMeshContours::exportLines( double value, QgsFeedback *feedback )
282 {
283   std::unique_ptr<QgsMultiLineString> multiLineString( new QgsMultiLineString() );
284   QSet<QPair<int, int>> exactEdges;
285 
286   // STEP 1: Get Data
287   const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
288   const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
289 
290   // STEP 2: For each triangle get the contour line
291   for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
292   {
293     if ( feedback && feedback->isCanceled() )
294       break;
295 
296     const int nativeIndex = trianglesToNativeFaces.at( i );
297     if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
298       continue;
299 
300     const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
301 
302     const int indices[3] =
303     {
304       triangle.at( 0 ),
305       triangle.at( 1 ),
306       triangle.at( 2 )
307     };
308 
309     const QVector<QgsMeshVertex> coords =
310     {
311       vertices.at( indices[0] ),
312       vertices.at( indices[1] ),
313       vertices.at( indices[2] )
314     };
315 
316     const double values[3] =
317     {
318       mDatasetValues.at( indices[0] ),
319       mDatasetValues.at( indices[1] ),
320       mDatasetValues.at( indices[2] )
321     };
322 
323     // any value is NaN
324     if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
325       continue;
326 
327     // value is outside the range
328     if ( ( ( value > values[0] ) && ( value > values[1] ) && ( value > values[2] ) )  ||
329          ( ( value < values[0] ) && ( value < values[1] ) && ( value < values[2] ) ) )
330       continue;
331 
332     // all values are the same
333     if ( qgsDoubleNear( values[0], values[1] ) && qgsDoubleNear( values[1], values[2] ) )
334       continue;
335 
336     // go through all edges
337     QgsPoint tmp;
338 
339     for ( int i = 0; i < 3; ++i )
340     {
341       const int j = ( i + 1 ) % 3;
342       // value is outside the range
343       if ( ( ( value > values[i] ) && ( value > values[j] ) ) ||
344            ( ( value < values[i] ) && ( value < values[j] ) ) )
345         continue;
346 
347       // the whole edge is result and we are done
348       if ( qgsDoubleNear( values[i], values[j] ) && qgsDoubleNear( values[i], values[j] ) )
349       {
350         if ( exactEdges.contains( { indices[i], indices[j] } ) || exactEdges.contains( { indices[j], indices[i] } ) )
351         {
352           break;
353         }
354         else
355         {
356           exactEdges.insert( { indices[i], indices[j] } );
357           std::unique_ptr<QgsLineString> line( new QgsLineString( coords[i], coords[j] ) );
358           multiLineString->addGeometry( line.release() );
359           break;
360         }
361       }
362 
363       // only one point matches, we are not interested in this
364       if ( qgsDoubleNear( values[i], value ) || qgsDoubleNear( values[j], value ) )
365       {
366         continue;
367       }
368 
369       // ok part of the result contour line is one point on this edge
370       const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
371       const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
372 
373       if ( std::isnan( tmp.x() ) )
374       {
375         // ok we have found start point of the contour line
376         tmp = xy;
377       }
378       else
379       {
380         // we have found the end point of the contour line, we are done
381         std::unique_ptr<QgsLineString> line( new QgsLineString( tmp, xy ) );
382         multiLineString->addGeometry( line.release() );
383         break;
384       }
385     }
386   }
387 
388   // STEP 3: merge the contour segments to linestrings
389   if ( multiLineString->isEmpty() )
390   {
391     return QgsGeometry();
392   }
393   else
394   {
395     const QgsGeometry in( multiLineString.release() );
396     const QgsGeometry res = in.mergeLines();
397     return res;
398   }
399 }
400 
populateCache(const QgsMeshDatasetIndex & index,QgsMeshRendererScalarSettings::DataResamplingMethod method)401 void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
402 {
403   if ( !mMeshLayer )
404     return;
405 
406   if ( mCachedIndex != index )
407   {
408     const bool scalarDataOnVertices = mMeshLayer->dataProvider()->datasetGroupMetadata( index ).dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
409     const int count =  scalarDataOnVertices ? mNativeMesh.vertices.count() : mNativeMesh.faces.count();
410 
411     // populate scalar values
412     const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues(
413                                     mMeshLayer,
414                                     index,
415                                     0,
416                                     count );
417     if ( vals.isValid() )
418     {
419       // vals could be scalar or vectors, for contour rendering we want always magnitude
420       mDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
421     }
422     else
423     {
424       mDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
425     }
426 
427     // populate face active flag, always defined on faces
428     mScalarActiveFaceFlagValues = mMeshLayer->dataProvider()->areFacesActive(
429                                     index,
430                                     0,
431                                     mNativeMesh.faces.count() );
432 
433     // for data on faces, there could be request to interpolate the data to vertices
434     if ( ( !scalarDataOnVertices ) )
435     {
436       mDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
437                          mDatasetValues,
438                          &mNativeMesh,
439                          &mTriangularMesh,
440                          &mScalarActiveFaceFlagValues,
441                          method
442                        );
443     }
444   }
445 }
446