QGIS API Documentation 4.1.0-Master (5bf3c20f3c9)
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{
64 if ( !mMeshLayer )
65 return QgsGeometry();
66
67 populateCache( index, method );
68
69 return exportPolygons( min_value, max_value, feedback );
70}
71
72QgsGeometry QgsMeshContours::exportPolygons( double min_value, double max_value, QgsFeedback *feedback )
73{
74 if ( min_value > max_value )
75 {
76 const double tmp = max_value;
77 max_value = min_value;
78 min_value = tmp;
79 }
80
81 QVector<QgsGeometry> multiPolygon;
82
83 // STEP 1: Get Data
84 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
85 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
86
87 // STEP 2: For each triangle get the contour line
88 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
89 {
90 if ( feedback && feedback->isCanceled() )
91 break;
92
93 const int nativeIndex = trianglesToNativeFaces.at( i );
94 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
95 continue;
96
97 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
98 const int indices[3] = { triangle.at( 0 ), triangle.at( 1 ), triangle.at( 2 ) };
99
100 const QVector<QgsMeshVertex> coords = { vertices.at( indices[0] ), vertices.at( indices[1] ), vertices.at( indices[2] ) };
101
102 const double values[3] = { mDatasetValues.at( indices[0] ), mDatasetValues.at( indices[1] ), mDatasetValues.at( indices[2] ) };
103
104 // any value is NaN
105 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
106 continue;
107
108 // all values on vertices are outside the range
109 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] ) ) )
110 continue;
111
112 const bool valueInRange[3]
113 = { ( min_value <= values[0] ) && ( max_value >= values[0] ), ( min_value <= values[1] ) && ( max_value >= values[1] ), ( min_value <= values[2] ) && ( max_value >= values[2] ) };
114
115 // all values are inside the range == take whole triangle
116 if ( valueInRange[0] && valueInRange[1] && valueInRange[2] )
117 {
118 QVector<QgsMeshVertex> ring = coords;
119 ring.push_back( coords[0] );
120 auto ext = std::make_unique<QgsLineString>( coords );
121 auto poly = std::make_unique<QgsPolygon>();
122 poly->setExteriorRing( ext.release() );
123 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
124 continue;
125 }
126
127 // go through all edges
128 QVector<QgsMeshVertex> ring;
129 for ( int i = 0; i < 3; ++i )
130 {
131 const int j = ( i + 1 ) % 3;
132
133 if ( valueInRange[i] )
134 {
135 if ( valueInRange[j] )
136 {
137 // whole edge is part of resulting contour polygon edge
138 if ( !ring.contains( coords[i] ) )
139 ring.push_back( coords[i] );
140 if ( !ring.contains( coords[j] ) )
141 ring.push_back( coords[j] );
142 }
143 else
144 {
145 // i is part or the resulting edge
146 if ( !ring.contains( coords[i] ) )
147 ring.push_back( coords[i] );
148 // we need to find the other point
149 double value = max_value;
150 if ( values[i] > values[j] )
151 {
152 value = min_value;
153 }
154 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
155 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
156 if ( !ring.contains( xy ) )
157 ring.push_back( xy );
158 }
159 }
160 else
161 {
162 if ( valueInRange[j] )
163 {
164 // we need to find the other point
165 double value = max_value;
166 if ( values[i] < values[j] )
167 {
168 value = min_value;
169 }
170
171 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
172 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
173 if ( !ring.contains( xy ) )
174 ring.push_back( xy );
175
176 // j is part
177 if ( !ring.contains( coords[j] ) )
178 ring.push_back( coords[j] );
179 }
180 else
181 {
182 // last option we need to consider is that both min and max are between
183 // value i and j, and in that case we need to calculate both point
184 double value1 = max_value;
185 double value2 = max_value;
186 if ( values[i] < values[j] )
187 {
188 if ( ( min_value < values[i] ) || ( max_value > values[j] ) )
189 {
190 continue;
191 }
192 value1 = min_value;
193 }
194 else
195 {
196 if ( ( min_value < values[j] ) || ( max_value > values[i] ) )
197 {
198 continue;
199 }
200 value2 = min_value;
201 }
202
203 const double fraction1 = ( value1 - values[i] ) / ( values[j] - values[i] );
204 const QgsPoint xy1 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction1 );
205 if ( !ring.contains( xy1 ) )
206 ring.push_back( xy1 );
207
208 const double fraction2 = ( value2 - values[i] ) / ( values[j] - values[i] );
209 const QgsPoint xy2 = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction2 );
210 if ( !ring.contains( xy2 ) )
211 ring.push_back( xy2 );
212 }
213 }
214 }
215
216 // add if the polygon is not degraded
217 if ( ring.size() > 2 )
218 {
219 auto ext = std::make_unique<QgsLineString>( ring );
220 auto poly = std::make_unique<QgsPolygon>();
221 poly->setExteriorRing( ext.release() );
222 multiPolygon.push_back( QgsGeometry( std::move( poly ) ) );
223 }
224 }
225
226 // STEP 3: dissolve the individual polygons from triangles if possible
227 if ( multiPolygon.isEmpty() )
228 {
229 return QgsGeometry();
230 }
231 else
232 {
233 const QgsGeometry res = QgsGeometry::unaryUnion( multiPolygon );
234 return res;
235 }
236}
237
239{
240 // Check if the layer/mesh is valid
241 if ( !mMeshLayer )
242 return QgsGeometry();
243
244 populateCache( index, method );
245
246 return exportLines( value, feedback );
247}
248
250{
251 auto multiLineString = std::make_unique<QgsMultiLineString>();
252 QSet<QPair<int, int>> exactEdges;
253
254 // STEP 1: Get Data
255 const QVector<QgsMeshVertex> vertices = mTriangularMesh.vertices();
256 const QVector<int> &trianglesToNativeFaces = mTriangularMesh.trianglesToNativeFaces();
257
258 // STEP 2: For each triangle get the contour line
259 for ( int i = 0; i < mTriangularMesh.triangles().size(); ++i )
260 {
261 if ( feedback && feedback->isCanceled() )
262 break;
263
264 const int nativeIndex = trianglesToNativeFaces.at( i );
265 if ( !mScalarActiveFaceFlagValues.active( nativeIndex ) )
266 continue;
267
268 const QgsMeshFace &triangle = mTriangularMesh.triangles().at( i );
269
270 const int indices[3] = { triangle.at( 0 ), triangle.at( 1 ), triangle.at( 2 ) };
271
272 const QVector<QgsMeshVertex> coords = { vertices.at( indices[0] ), vertices.at( indices[1] ), vertices.at( indices[2] ) };
273
274 const double values[3] = { mDatasetValues.at( indices[0] ), mDatasetValues.at( indices[1] ), mDatasetValues.at( indices[2] ) };
275
276 // any value is NaN
277 if ( std::isnan( values[0] ) || std::isnan( values[1] ) || std::isnan( values[2] ) )
278 continue;
279
280 // value is outside the range
281 if ( ( ( value > values[0] ) && ( value > values[1] ) && ( value > values[2] ) ) || ( ( value < values[0] ) && ( value < values[1] ) && ( value < values[2] ) ) )
282 continue;
283
284 // all values are the same
285 if ( qgsDoubleNear( values[0], values[1] ) && qgsDoubleNear( values[1], values[2] ) )
286 continue;
287
288 // go through all edges
289 QgsPoint tmp;
290
291 for ( int i = 0; i < 3; ++i )
292 {
293 const int j = ( i + 1 ) % 3;
294 // value is outside the range
295 if ( ( ( value > values[i] ) && ( value > values[j] ) ) || ( ( value < values[i] ) && ( value < values[j] ) ) )
296 continue;
297
298 // the whole edge is result and we are done
299 if ( qgsDoubleNear( values[i], values[j] ) && qgsDoubleNear( values[i], values[j] ) )
300 {
301 if ( exactEdges.contains( { indices[i], indices[j] } ) || exactEdges.contains( { indices[j], indices[i] } ) )
302 {
303 break;
304 }
305 else
306 {
307 exactEdges.insert( { indices[i], indices[j] } );
308 auto line = std::make_unique<QgsLineString>( coords[i], coords[j] );
309 multiLineString->addGeometry( line.release() );
310 break;
311 }
312 }
313
314 // only one point matches, we are not interested in this
315 if ( qgsDoubleNear( values[i], value ) || qgsDoubleNear( values[j], value ) )
316 {
317 continue;
318 }
319
320 // ok part of the result contour line is one point on this edge
321 const double fraction = ( value - values[i] ) / ( values[j] - values[i] );
322 const QgsPoint xy = QgsGeometryUtils::interpolatePointOnLine( coords[i], coords[j], fraction );
323
324 if ( std::isnan( tmp.x() ) )
325 {
326 // ok we have found start point of the contour line
327 tmp = xy;
328 }
329 else
330 {
331 // we have found the end point of the contour line, we are done
332 auto line = std::make_unique<QgsLineString>( tmp, xy );
333 multiLineString->addGeometry( line.release() );
334 break;
335 }
336 }
337 }
338
339 // STEP 3: merge the contour segments to linestrings
340 if ( multiLineString->isEmpty() )
341 {
342 return QgsGeometry();
343 }
344 else
345 {
346 const QgsGeometry in( multiLineString.release() );
347 const QgsGeometry res = in.mergeLines();
348 return res;
349 }
350}
351
352void QgsMeshContours::populateCache( const QgsMeshDatasetIndex &index, QgsMeshRendererScalarSettings::DataResamplingMethod method )
353{
354 if ( !mMeshLayer )
355 return;
356
357 if ( mCachedIndex != index )
358 {
359 const bool scalarDataOnVertices = mMeshLayer->dataProvider()->datasetGroupMetadata( index ).dataType() == QgsMeshDatasetGroupMetadata::DataOnVertices;
360 const int count = scalarDataOnVertices ? mNativeMesh.vertices.count() : mNativeMesh.faces.count();
361
362 // populate scalar values
363 const QgsMeshDataBlock vals = QgsMeshLayerUtils::datasetValues( mMeshLayer, index, 0, count );
364 if ( vals.isValid() )
365 {
366 // vals could be scalar or vectors, for contour rendering we want always magnitude
367 mDatasetValues = QgsMeshLayerUtils::calculateMagnitudes( vals );
368 }
369 else
370 {
371 mDatasetValues = QVector<double>( count, std::numeric_limits<double>::quiet_NaN() );
372 }
373
374 // populate face active flag, always defined on faces
375 mScalarActiveFaceFlagValues = mMeshLayer->dataProvider()->areFacesActive( index, 0, mNativeMesh.faces.count() );
376
377 // for data on faces, there could be request to interpolate the data to vertices
378 if ( ( !scalarDataOnVertices ) )
379 {
380 mDatasetValues = QgsMeshLayerUtils::interpolateFromFacesData( mDatasetValues, &mNativeMesh, &mTriangularMesh, &mScalarActiveFaceFlagValues, method );
381 }
382 }
383}
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:56
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:53
double x
Definition qgspoint.h:56
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:6975
QVector< int > QgsMeshFace
List of vertex indexes.
Mesh - vertices, edges and faces.
QVector< QgsMeshVertex > vertices
QVector< QgsMeshFace > faces