QGIS API Documentation 3.41.0-Master (af5edcb665c)
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#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
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
52QgsMeshContours::QgsMeshContours( const QgsTriangularMesh &triangularMesh, const QgsMesh &nativeMesh, const QVector<double> &datasetValues, const QgsMeshDataBlock scalarActiveFaceFlagValues )
53 : mTriangularMesh( triangularMesh )
54 , mNativeMesh( nativeMesh )
55 , mDatasetValues( datasetValues )
56 , mScalarActiveFaceFlagValues( scalarActiveFaceFlagValues )
57{}
58
60
62 const QgsMeshDatasetIndex &index,
63 double min_value,
64 double max_value,
66 QgsFeedback *feedback
67)
68{
69 if ( !mMeshLayer )
70 return QgsGeometry();
71
72 populateCache( index, method );
73
74 return exportPolygons( min_value, max_value, feedback );
75}
76
77QgsGeometry QgsMeshContours::exportPolygons( double min_value, double max_value, QgsFeedback *feedback )
78{
79 if ( min_value > max_value )
80 {
81 const double tmp = max_value;
82 max_value = min_value;
83 min_value = tmp;
84 }
85
86 QVector<QgsGeometry> multiPolygon;
87
88 // STEP 1: Get Data
89 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
90 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
91
92 // STEP 2: For each triangle get the contour line
93 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
94 {
95 if ( feedback && feedback->isCanceled() )
96 break;
97
98 const int nativeIndex = trianglesToNativeFaces.at( i );
99 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
100 continue;
101
102 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
103 const int indices[3] = {
104 triangle.at( 0 ),
105 triangle.at( 1 ),
106 triangle.at( 2 )
107 };
108
109 const QVector<QgsMeshVertex> coords = {
110 vertices.at( indices[0] ),
111 vertices.at( indices[1] ),
112 vertices.at( indices[2] )
113 };
114
115 const double values[3] = {
116 mDatasetValues.at( indices[0] ),
117 mDatasetValues.at( indices[1] ),
118 mDatasetValues.at( indices[2] )
119 };
120
121 // any value is NaN
122 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
123 continue;
124
125 // all values on vertices are outside the range
126 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] ) ) )
127 continue;
128
129 const bool valueInRange[3] = {
130 ( min_value <= values[0] ) && ( max_value >= values[0] ),
131 ( min_value <= values[1] ) && ( max_value >= values[1] ),
132 ( min_value <= values[2] ) && ( max_value >= values[2] )
133 };
134
135 // all values are inside the range == take whole triangle
136 if ( valueInRange[0] && valueInRange[1] && valueInRange[2] )
137 {
138 QVector<QgsMeshVertex> ring = coords;
139 ring.push_back( coords[0] );
140 std::unique_ptr<QgsLineString> ext = std::make_unique<QgsLineString>( coords );
141 std::unique_ptr<QgsPolygon> poly = std::make_unique<QgsPolygon>();
142 poly->setExteriorRing( ext.release() );
143 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
144 continue;
145 }
146
147 // go through all edges
148 QVector<QgsMeshVertex> ring;
149 for ( int i = 0; i < 3; ++i )
150 {
151 const int j = ( i + 1 ) % 3;
152
153 if ( valueInRange[i] )
154 {
155 if ( valueInRange[j] )
156 {
157 // whole edge is part of resulting contour polygon edge
158 if ( !ring.contains( coords[i] ) )
159 ring.push_back( coords[i] );
160 if ( !ring.contains( coords[j] ) )
161 ring.push_back( coords[j] );
162 }
163 else
164 {
165 // i is part or the resulting edge
166 if ( !ring.contains( coords[i] ) )
167 ring.push_back( coords[i] );
168 // we need to find the other point
169 double value = max_value;
170 if ( values[i] > values[j] )
171 {
172 value = min_value;
173 }
174 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
175 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
176 if ( !ring.contains( xy ) )
177 ring.push_back( xy );
178 }
179 }
180 else
181 {
182 if ( valueInRange[j] )
183 {
184 // we need to find the other point
185 double value = max_value;
186 if ( values[i] < values[j] )
187 {
188 value = min_value;
189 }
190
191 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
192 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
193 if ( !ring.contains( xy ) )
194 ring.push_back( xy );
195
196 // j is part
197 if ( !ring.contains( coords[j] ) )
198 ring.push_back( coords[j] );
199 }
200 else
201 {
202 // last option we need to consider is that both min and max are between
203 // value i and j, and in that case we need to calculate both point
204 double value1 = max_value;
205 double value2 = max_value;
206 if ( values[i] < values[j] )
207 {
208 if ( ( min_value < values[i] ) || ( max_value > values[j] ) )
209 {
210 continue;
211 }
212 value1 = min_value;
213 }
214 else
215 {
216 if ( ( min_value < values[j] ) || ( max_value > values[i] ) )
217 {
218 continue;
219 }
220 value2 = min_value;
221 }
222
223 const double fraction1 = ( value1 - values[i] ) / ( values[j] - values[i] );
224 const QgsPoint xy1 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction1 );
225 if ( !ring.contains( xy1 ) )
226 ring.push_back( xy1 );
227
228 const double fraction2 = ( value2 - values[i] ) / ( values[j] - values[i] );
229 const QgsPoint xy2 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction2 );
230 if ( !ring.contains( xy2 ) )
231 ring.push_back( xy2 );
232 }
233 }
234 }
235
236 // add if the polygon is not degraded
237 if ( ring.size() > 2 )
238 {
239 std::unique_ptr<QgsLineString> ext = std::make_unique<QgsLineString>( ring );
240 std::unique_ptr<QgsPolygon> poly = std::make_unique<QgsPolygon>();
241 poly->setExteriorRing( ext.release() );
242 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
243 }
244 }
245
246 // STEP 3: dissolve the individual polygons from triangles if possible
247 if ( multiPolygon.isEmpty() )
248 {
249 return QgsGeometry();
250 }
251 else
252 {
253 const QgsGeometry res = QgsGeometry::unaryUnion( multiPolygon );
254 return res;
255 }
256}
257
259{
260 // Check if the layer/mesh is valid
261 if ( !mMeshLayer )
262 return QgsGeometry();
263
264 populateCache( index, method );
265
266 return exportLines( value, feedback );
267}
268
270{
271 std::unique_ptr<QgsMultiLineString> multiLineString( new QgsMultiLineString() );
272 QSet<QPair<int, int>> exactEdges;
273
274 // STEP 1: Get Data
275 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
276 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
277
278 // STEP 2: For each triangle get the contour line
279 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
280 {
281 if ( feedback && feedback->isCanceled() )
282 break;
283
284 const int nativeIndex = trianglesToNativeFaces.at( i );
285 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
286 continue;
287
288 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
289
290 const int indices[3] = {
291 triangle.at( 0 ),
292 triangle.at( 1 ),
293 triangle.at( 2 )
294 };
295
296 const QVector<QgsMeshVertex> coords = {
297 vertices.at( indices[0] ),
298 vertices.at( indices[1] ),
299 vertices.at( indices[2] )
300 };
301
302 const double values[3] = {
303 mDatasetValues.at( indices[0] ),
304 mDatasetValues.at( indices[1] ),
305 mDatasetValues.at( indices[2] )
306 };
307
308 // any value is NaN
309 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
310 continue;
311
312 // value is outside the range
313 if ( ( ( value > values[0] ) && ( value > values[1] ) && ( value > values[2] ) ) || ( ( value < values[0] ) && ( value < values[1] ) && ( value < values[2] ) ) )
314 continue;
315
316 // all values are the same
317 if ( qgsDoubleNear( values[0], values[1] ) && qgsDoubleNear( values[1], values[2] ) )
318 continue;
319
320 // go through all edges
321 QgsPoint tmp;
322
323 for ( int i = 0; i < 3; ++i )
324 {
325 const int j = ( i + 1 ) % 3;
326 // value is outside the range
327 if ( ( ( value > values[i] ) && ( value > values[j] ) ) || ( ( value < values[i] ) && ( value < values[j] ) ) )
328 continue;
329
330 // the whole edge is result and we are done
331 if ( qgsDoubleNear( values[i], values[j] ) && qgsDoubleNear( values[i], values[j] ) )
332 {
333 if ( exactEdges.contains( { indices[i], indices[j] } ) || exactEdges.contains( { indices[j], indices[i] } ) )
334 {
335 break;
336 }
337 else
338 {
339 exactEdges.insert( { indices[i], indices[j] } );
340 std::unique_ptr<QgsLineString> line( new QgsLineString( coords[i], coords[j] ) );
341 multiLineString->addGeometry( line.release() );
342 break;
343 }
344 }
345
346 // only one point matches, we are not interested in this
347 if ( qgsDoubleNear( values[i], value ) || qgsDoubleNear( values[j], value ) )
348 {
349 continue;
350 }
351
352 // ok part of the result contour line is one point on this edge
353 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
354 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
355
356 if ( std::isnan( tmp.x() ) )
357 {
358 // ok we have found start point of the contour line
359 tmp = xy;
360 }
361 else
362 {
363 // we have found the end point of the contour line, we are done
364 std::unique_ptr<QgsLineString> line( new QgsLineString( tmp, xy ) );
365 multiLineString->addGeometry( line.release() );
366 break;
367 }
368 }
369 }
370
371 // STEP 3: merge the contour segments to linestrings
372 if ( multiLineString->isEmpty() )
373 {
374 return QgsGeometry();
375 }
376 else
377 {
378 const QgsGeometry in( multiLineString.release() );
379 const QgsGeometry res = in.mergeLines();
380 return res;
381 }
382}
383
384void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
385{
386 if ( !mMeshLayer )
387 return;
388
389 if ( mCachedIndex != index )
390 {
391 const bool scalarDataOnVertices = mMeshLayer->dataProvider()->datasetGroupMetadata( index ).dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
392 const int count = scalarDataOnVertices ? mNativeMesh.vertices.count() : mNativeMesh.faces.count();
393
394 // populate scalar values
395 const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues(
396 mMeshLayer,
397 index,
398 0,
399 count
400 );
401 if ( vals.isValid() )
402 {
403 // vals could be scalar or vectors, for contour rendering we want always magnitude
404 mDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
405 }
406 else
407 {
408 mDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
409 }
410
411 // populate face active flag, always defined on faces
412 mScalarActiveFaceFlagValues = mMeshLayer->dataProvider()->areFacesActive(
413 index,
414 0,
415 mNativeMesh.faces.count()
416 );
417
418 // for data on faces, there could be request to interpolate the data to vertices
419 if ( ( !scalarDataOnVertices ) )
420 {
421 mDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData(
422 mDatasetValues,
423 &mNativeMesh,
424 &mTriangularMesh,
425 &mScalarActiveFaceFlagValues,
426 method
427 );
428 }
429 }
430}
virtual bool isValid() const =0
Returns true if this is a valid layer.
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.
QgsGeometry mergeLines() 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.
Line string geometry type, with support for z-dimension and m-values.
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.
QgsMeshDataBlock is a block of integers/doubles that can be used to retrieve: active flags (e....
bool isValid() const
Whether the block is valid.
bool active(int index) const
Returns a value for active flag by the index For scalar and vector 2d the behavior is undefined.
bool contains(const QgsMesh::ElementType &type) const
Returns whether the mesh contains at mesh elements of given type.
virtual void populateMesh(QgsMesh *mesh) const =0
Populates the mesh vertices, edges and faces.
DataType dataType() const
Returns whether dataset group data is defined on vertices or faces or volumes.
@ DataOnVertices
Data is defined on vertices.
QgsMeshDatasetIndex is index that identifies the dataset group (e.g.
virtual QgsMeshDatasetGroupMetadata datasetGroupMetadata(int groupIndex) const =0
Returns dataset group metadata.
virtual QgsMeshDataBlock areFacesActive(QgsMeshDatasetIndex index, int faceIndex, int count) const =0
Returns whether the faces are active for particular dataset.
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.
Multi line string geometry collection.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
double x
Definition qgspoint.h:52
Triangular/Derived Mesh is mesh with vertices in map coordinates.
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
bool update(QgsMesh *nativeMesh, const QgsCoordinateTransform &transform)
Constructs triangular mesh from layer's native mesh and transform to destination CRS.
const QVector< int > & trianglesToNativeFaces() const
Returns mapping between triangles and original faces.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
QVector< int > QgsMeshFace
List of vertex indexes.
Mesh - vertices, edges and faces.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces