QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
qgsmeshcontours.cpp
Go to the documentation of this file.
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
19#include <limits>
20
21#include "qgsfeature.h"
22#include "qgsfeedback.h"
23#include "qgsgeometry.h"
24#include "qgsgeometryutils.h"
25#include "qgslinestring.h"
26#include "qgsmapsettings.h"
27#include "qgsmeshdataprovider.h"
28#include "qgsmeshlayer.h"
29#include "qgsmeshlayerutils.h"
30#include "qgsmultilinestring.h"
31#include "qgspoint.h"
32#include "qgspolygon.h"
33#include "qgsproject.h"
34#include "qgstriangularmesh.h"
35
36#include <QPair>
37#include <QSet>
38
40 : mMeshLayer( layer )
41{
42 if ( !mMeshLayer || !mMeshLayer->dataProvider() || !mMeshLayer->dataProvider()->isValid() )
43 return;
44
45 // Support for meshes with edges is not implemented
46 if ( mMeshLayer->dataProvider()->contains( QgsMesh::ElementType::Edge ) )
47 return;
48
49 mMeshLayer->dataProvider()->populateMesh( &mNativeMesh );
50 mTriangularMesh.update( &mNativeMesh );
51}
52
53QgsMeshContours::QgsMeshContours( const QgsTriangularMesh &triangularMesh, const QgsMesh &nativeMesh, const QVector<double> &datasetValues, const QgsMeshDataBlock scalarActiveFaceFlagValues )
54 : mTriangularMesh( triangularMesh )
55 , mNativeMesh( nativeMesh )
56 , mDatasetValues( datasetValues )
57 , mScalarActiveFaceFlagValues( scalarActiveFaceFlagValues )
58{}
59
61
63 const QgsMeshDatasetIndex &index,
64 double min_value,
65 double max_value,
67 QgsFeedback *feedback
68)
69{
70 if ( !mMeshLayer )
71 return QgsGeometry();
72
73 populateCache( index, method );
74
75 return exportPolygons( min_value, max_value, feedback );
76}
77
78QgsGeometry QgsMeshContours::exportPolygons( double min_value, double max_value, QgsFeedback *feedback )
79{
80 if ( min_value > max_value )
81 {
82 const double tmp = max_value;
83 max_value = min_value;
84 min_value = tmp;
85 }
86
87 QVector<QgsGeometry> multiPolygon;
88
89 // STEP 1: Get Data
90 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
91 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
92
93 // STEP 2: For each triangle get the contour line
94 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
95 {
96 if ( feedback && feedback->isCanceled() )
97 break;
98
99 const int nativeIndex = trianglesToNativeFaces.at( i );
100 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
101 continue;
102
103 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
104 const int indices[3] = {
105 triangle.at( 0 ),
106 triangle.at( 1 ),
107 triangle.at( 2 )
108 };
109
110 const QVector<QgsMeshVertex> coords = {
111 vertices.at( indices[0] ),
112 vertices.at( indices[1] ),
113 vertices.at( indices[2] )
114 };
115
116 const double values[3] = {
117 mDatasetValues.at( indices[0] ),
118 mDatasetValues.at( indices[1] ),
119 mDatasetValues.at( indices[2] )
120 };
121
122 // any value is NaN
123 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
124 continue;
125
126 // all values on vertices are outside the range
127 if ( ( ( min_value > values[0] ) && ( min_value > values[1] ) && ( min_value > values[2] ) ) || ( ( max_value < values[0] ) && ( max_value < values[1] ) && ( max_value < values[2] ) ) )
128 continue;
129
130 const bool valueInRange[3] = {
131 ( min_value <= values[0] ) && ( max_value >= values[0] ),
132 ( min_value <= values[1] ) && ( max_value >= values[1] ),
133 ( min_value <= values[2] ) && ( max_value >= values[2] )
134 };
135
136 // all values are inside the range == take whole triangle
137 if ( valueInRange[0] && valueInRange[1] && valueInRange[2] )
138 {
139 QVector<QgsMeshVertex> ring = coords;
140 ring.push_back( coords[0] );
141 auto ext = std::make_unique<QgsLineString>( coords );
142 auto poly = std::make_unique<QgsPolygon>();
143 poly->setExteriorRing( ext.release() );
144 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
145 continue;
146 }
147
148 // go through all edges
149 QVector<QgsMeshVertex> ring;
150 for ( int i = 0; i < 3; ++i )
151 {
152 const int j = ( i + 1 ) % 3;
153
154 if ( valueInRange[i] )
155 {
156 if ( valueInRange[j] )
157 {
158 // whole edge is part of resulting contour polygon edge
159 if ( !ring.contains( coords[i] ) )
160 ring.push_back( coords[i] );
161 if ( !ring.contains( coords[j] ) )
162 ring.push_back( coords[j] );
163 }
164 else
165 {
166 // i is part or the resulting edge
167 if ( !ring.contains( coords[i] ) )
168 ring.push_back( coords[i] );
169 // we need to find the other point
170 double value = max_value;
171 if ( values[i] > values[j] )
172 {
173 value = min_value;
174 }
175 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
176 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
177 if ( !ring.contains( xy ) )
178 ring.push_back( xy );
179 }
180 }
181 else
182 {
183 if ( valueInRange[j] )
184 {
185 // we need to find the other point
186 double value = max_value;
187 if ( values[i] < values[j] )
188 {
189 value = min_value;
190 }
191
192 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
193 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
194 if ( !ring.contains( xy ) )
195 ring.push_back( xy );
196
197 // j is part
198 if ( !ring.contains( coords[j] ) )
199 ring.push_back( coords[j] );
200 }
201 else
202 {
203 // last option we need to consider is that both min and max are between
204 // value i and j, and in that case we need to calculate both point
205 double value1 = max_value;
206 double value2 = max_value;
207 if ( values[i] < values[j] )
208 {
209 if ( ( min_value < values[i] ) || ( max_value > values[j] ) )
210 {
211 continue;
212 }
213 value1 = min_value;
214 }
215 else
216 {
217 if ( ( min_value < values[j] ) || ( max_value > values[i] ) )
218 {
219 continue;
220 }
221 value2 = min_value;
222 }
223
224 const double fraction1 = ( value1 - values[i] ) / ( values[j] - values[i] );
225 const QgsPoint xy1 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction1 );
226 if ( !ring.contains( xy1 ) )
227 ring.push_back( xy1 );
228
229 const double fraction2 = ( value2 - values[i] ) / ( values[j] - values[i] );
230 const QgsPoint xy2 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction2 );
231 if ( !ring.contains( xy2 ) )
232 ring.push_back( xy2 );
233 }
234 }
235 }
236
237 // add if the polygon is not degraded
238 if ( ring.size() > 2 )
239 {
240 auto ext = std::make_unique<QgsLineString>( ring );
241 auto poly = std::make_unique<QgsPolygon>();
242 poly->setExteriorRing( ext.release() );
243 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
244 }
245 }
246
247 // STEP 3: dissolve the individual polygons from triangles if possible
248 if ( multiPolygon.isEmpty() )
249 {
250 return QgsGeometry();
251 }
252 else
253 {
254 const QgsGeometry res = QgsGeometry::unaryUnion( multiPolygon );
255 return res;
256 }
257}
258
260{
261 // Check if the layer/mesh is valid
262 if ( !mMeshLayer )
263 return QgsGeometry();
264
265 populateCache( index, method );
266
267 return exportLines( value, feedback );
268}
269
271{
272 auto multiLineString = std::make_unique<QgsMultiLineString>();
273 QSet<QPair<int, int>> exactEdges;
274
275 // STEP 1: Get Data
276 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
277 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
278
279 // STEP 2: For each triangle get the contour line
280 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
281 {
282 if ( feedback && feedback->isCanceled() )
283 break;
284
285 const int nativeIndex = trianglesToNativeFaces.at( i );
286 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
287 continue;
288
289 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
290
291 const int indices[3] = {
292 triangle.at( 0 ),
293 triangle.at( 1 ),
294 triangle.at( 2 )
295 };
296
297 const QVector<QgsMeshVertex> coords = {
298 vertices.at( indices[0] ),
299 vertices.at( indices[1] ),
300 vertices.at( indices[2] )
301 };
302
303 const double values[3] = {
304 mDatasetValues.at( indices[0] ),
305 mDatasetValues.at( indices[1] ),
306 mDatasetValues.at( indices[2] )
307 };
308
309 // any value is NaN
310 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
311 continue;
312
313 // value is outside the range
314 if ( ( ( value > values[0] ) && ( value > values[1] ) && ( value > values[2] ) ) || ( ( value < values[0] ) && ( value < values[1] ) && ( value < values[2] ) ) )
315 continue;
316
317 // all values are the same
318 if ( qgsDoubleNear( values[0], values[1] ) && qgsDoubleNear( values[1], values[2] ) )
319 continue;
320
321 // go through all edges
322 QgsPoint tmp;
323
324 for ( int i = 0; i < 3; ++i )
325 {
326 const int j = ( i + 1 ) % 3;
327 // value is outside the range
328 if ( ( ( value > values[i] ) && ( value > values[j] ) ) || ( ( value < values[i] ) && ( value < values[j] ) ) )
329 continue;
330
331 // the whole edge is result and we are done
332 if ( qgsDoubleNear( values[i], values[j] ) && qgsDoubleNear( values[i], values[j] ) )
333 {
334 if ( exactEdges.contains( { indices[i], indices[j] } ) || exactEdges.contains( { indices[j], indices[i] } ) )
335 {
336 break;
337 }
338 else
339 {
340 exactEdges.insert( { indices[i], indices[j] } );
341 auto line = std::make_unique<QgsLineString>( coords[i], coords[j] );
342 multiLineString->addGeometry( line.release() );
343 break;
344 }
345 }
346
347 // only one point matches, we are not interested in this
348 if ( qgsDoubleNear( values[i], value ) || qgsDoubleNear( values[j], value ) )
349 {
350 continue;
351 }
352
353 // ok part of the result contour line is one point on this edge
354 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
355 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
356
357 if ( std::isnan( tmp.x() ) )
358 {
359 // ok we have found start point of the contour line
360 tmp = xy;
361 }
362 else
363 {
364 // we have found the end point of the contour line, we are done
365 auto line = std::make_unique<QgsLineString>( tmp, xy );
366 multiLineString->addGeometry( line.release() );
367 break;
368 }
369 }
370 }
371
372 // STEP 3: merge the contour segments to linestrings
373 if ( multiLineString->isEmpty() )
374 {
375 return QgsGeometry();
376 }
377 else
378 {
379 const QgsGeometry in( multiLineString.release() );
380 const QgsGeometry res = in.mergeLines();
381 return res;
382 }
383}
384
385void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
386{
387 if ( !mMeshLayer )
388 return;
389
390 if ( mCachedIndex != index )
391 {
392 const bool scalarDataOnVertices = mMeshLayer->dataProvider()->datasetGroupMetadata( index ).dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
393 const int count = scalarDataOnVertices ? mNativeMesh.vertices.count() : mNativeMesh.faces.count();
394
395 // populate scalar values
396 const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues(
397 mMeshLayer,
398 index,
399 0,
400 count
401 );
402 if ( vals.isValid() )
403 {
404 // vals could be scalar or vectors, for contour rendering we want always magnitude
405 mDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
406 }
407 else
408 {
409 mDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
410 }
411
412 // populate face active flag, always defined on faces
413 mScalarActiveFaceFlagValues = mMeshLayer->dataProvider()->areFacesActive(
414 index,
415 0,
416 mNativeMesh.faces.count()
417 );
418
419 // for data on faces, there could be request to interpolate the data to vertices
420 if ( ( !scalarDataOnVertices ) )
421 {
422 mDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
423 mDatasetValues,
424 &mNativeMesh,
425 &mTriangularMesh,
426 &mScalarActiveFaceFlagValues,
427 method
428 );
429 }
430 }
431}
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:53
static QgsPointXY interpolatePointOnLine(double x1, double y1, double x2, double y2, double fraction)
Interpolates the position of a point a fraction of the way along the line from (x1,...
A geometry is the spatial representation of a feature.
QgsVertexIterator vertices() const
Returns a read-only, Java-style iterator for traversal of vertices of all the geometry,...
QgsGeometry mergeLines(const QgsGeometryParameters &parameters=QgsGeometryParameters()) const
Merges any connected lines in a LineString/MultiLineString geometry and converts them to single line ...
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
QgsGeometry exportPolygons(const QgsMeshDatasetIndex &index, double min_value, double max_value, QgsMeshRendererScalarSettings::DataResamplingMethod method, QgsFeedback *feedback=nullptr)
Exports multi polygons representing the areas with values in range for particular dataset.
QgsGeometry exportLines(const QgsMeshDatasetIndex &index, double value, QgsMeshRendererScalarSettings::DataResamplingMethod method, QgsFeedback *feedback=nullptr)
Exports multi line string containing the contour line for particular dataset and value.
QgsMeshContours(QgsMeshLayer *layer)
Constructs the mesh contours exporter.
A block of integers/doubles from a mesh dataset.
bool isValid() const
Whether the block is valid.
DataType dataType() const
Returns whether dataset group data is defined on vertices or faces or volumes.
@ DataOnVertices
Data is defined on vertices.
An index that identifies the dataset group (e.g.
virtual QgsMeshDatasetGroupMetadata datasetGroupMetadata(int groupIndex) const =0
Returns dataset group metadata.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
QgsMeshDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
DataResamplingMethod
Resampling of value from dataset.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
A triangular/derived mesh with vertices in map coordinates.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
QVector< int > QgsMeshFace
List of vertex indexes.
Mesh - vertices, edges and faces.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces