QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
qgstriangularmesh.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstriangularmesh.cpp
3  ---------------------
4  begin : April 2018
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 
18 #include <memory>
19 #include <QList>
20 #include "qgspolygon.h"
21 #include "qgslinestring.h"
22 #include "qgstriangularmesh.h"
23 #include "qgsrendercontext.h"
24 #include "qgscoordinatetransform.h"
25 #include "qgsgeometry.h"
26 #include "qgsrectangle.h"
27 #include "qgslogger.h"
28 #include "qgsmeshspatialindex.h"
29 
30 static void ENP_centroid_step( const QPolygonF &pX, double &cx, double &cy, double &signedArea, int i, int i1 )
31 {
32  double x0 = 0.0; // Current vertex X
33  double y0 = 0.0; // Current vertex Y
34  double x1 = 0.0; // Next vertex X
35  double y1 = 0.0; // Next vertex Y
36  double a = 0.0; // Partial signed area
37 
38  x0 = pX[i].x();
39  y0 = pX[i].y();
40  x1 = pX[i1].x();
41  y1 = pX[i1].y();
42  a = x0 * y1 - x1 * y0;
43  signedArea += a;
44  cx += ( x0 + x1 ) * a;
45  cy += ( y0 + y1 ) * a;
46 }
47 
48 static void ENP_centroid( const QPolygonF &pX, double &cx, double &cy )
49 {
50  // http://stackoverflow.com/questions/2792443/finding-the-centroid-of-a-polygon/2792459#2792459
51  cx = 0;
52  cy = 0;
53 
54  if ( pX.isEmpty() )
55  return;
56 
57  double signedArea = 0.0;
58 
59  // For all vertices except last
60  int i = 0;
61  for ( ; i < pX.size() - 1; ++i )
62  {
63  ENP_centroid_step( pX, cx, cy, signedArea, i, i + 1 );
64  }
65  // Do last vertex separately to avoid performing an expensive
66  // modulus operation in each iteration.
67  ENP_centroid_step( pX, cx, cy, signedArea, i, 0 );
68 
69  signedArea *= 0.5;
70  cx /= ( 6.0 * signedArea );
71  cy /= ( 6.0 * signedArea );
72 }
73 
74 
75 void QgsTriangularMesh::triangulate( const QgsMeshFace &face, int nativeIndex )
76 {
77  int vertexCount = face.size();
78  if ( vertexCount < 3 )
79  return;
80 
81  while ( vertexCount > 3 )
82  {
83  // clip one ear from last 2 and first vertex
84  const QgsMeshFace ear = { face[vertexCount - 2], face[vertexCount - 1], face[0] };
85  if ( !( std::isnan( mTriangularMesh.vertex( ear[0] ).x() ) ||
86  std::isnan( mTriangularMesh.vertex( ear[1] ).x() ) ||
87  std::isnan( mTriangularMesh.vertex( ear[2] ).x() ) ) )
88  {
89  mTriangularMesh.faces.push_back( ear );
90  mTrianglesToNativeFaces.push_back( nativeIndex );
91  }
92  --vertexCount;
93  }
94 
95  const QgsMeshFace triangle = { face[1], face[2], face[0] };
96  if ( !( std::isnan( mTriangularMesh.vertex( triangle[0] ).x() ) ||
97  std::isnan( mTriangularMesh.vertex( triangle[1] ).x() ) ||
98  std::isnan( mTriangularMesh.vertex( triangle[2] ).x() ) ) )
99  {
100  mTriangularMesh.faces.push_back( triangle );
101  mTrianglesToNativeFaces.push_back( nativeIndex );
102  }
103 }
104 
105 
106 void QgsTriangularMesh::setTrianglesCounterClockWise( QgsMeshFace &face )
107 {
108  if ( face.size() != 3 )
109  return;
110 
111  const QgsMeshVertex &v0 = mTriangularMesh.vertex( face[0] );
112  const QgsMeshVertex &v1 = mTriangularMesh.vertex( face[1] );
113  const QgsMeshVertex &v2 = mTriangularMesh.vertex( face[2] );
114 
115  double ux = v1.x() - v0.x();
116  double uy = v1.y() - v0.y();
117  double vx = v2.x() - v0.x();
118  double vy = v2.y() - v0.y();
119 
120  double crossProduct = ux * vy - uy * vx;
121  if ( crossProduct < 0 ) //CW -->change the orientation
122  {
123  std::swap( face[1], face[2] );
124  }
125 }
126 
129 
131 {
132  Q_ASSERT( nativeMesh );
133  Q_ASSERT( context );
134 
135  // FIND OUT IF UPDATE IS NEEDED
136  if ( mTriangularMesh.vertices.size() >= nativeMesh->vertices.size() &&
137  mTriangularMesh.faces.size() >= nativeMesh->faces.size() &&
138  mCoordinateTransform.sourceCrs() == context->coordinateTransform().sourceCrs() &&
139  mCoordinateTransform.destinationCrs() == context->coordinateTransform().destinationCrs() )
140  return;
141 
142  // CLEAN-UP
143  mTriangularMesh.vertices.clear();
144  mTriangularMesh.faces.clear();
145  mTrianglesToNativeFaces.clear();
146  mNativeMeshFaceCentroids.clear();
147 
148  // TRANSFORM VERTICES
149  mCoordinateTransform = context->coordinateTransform();
150  mTriangularMesh.vertices.resize( nativeMesh->vertices.size() );
151  for ( int i = 0; i < nativeMesh->vertices.size(); ++i )
152  {
153  const QgsMeshVertex &vertex = nativeMesh->vertices.at( i );
154  if ( mCoordinateTransform.isValid() )
155  {
156  try
157  {
158  QgsPointXY mapPoint = mCoordinateTransform.transform( QgsPointXY( vertex.x(), vertex.y() ) );
159  QgsMeshVertex mapVertex( mapPoint );
160  mapVertex.addZValue( vertex.z() );
161  mapVertex.setM( vertex.m() );
162  mTriangularMesh.vertices[i] = mapVertex;
163  }
164  catch ( QgsCsException &cse )
165  {
166  Q_UNUSED( cse )
167  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
168  mTriangularMesh.vertices[i] = QgsMeshVertex();
169  }
170  }
171  else
172  {
173  mTriangularMesh.vertices[i] = vertex;
174  }
175  }
176 
177  // CREATE TRIANGULAR MESH
178  for ( int i = 0; i < nativeMesh->faces.size(); ++i )
179  {
180  const QgsMeshFace &face = nativeMesh->faces.at( i ) ;
181  triangulate( face, i );
182  }
183 
184  // CALCULATE CENTROIDS
185  mNativeMeshFaceCentroids.resize( nativeMesh->faces.size() );
186  for ( int i = 0; i < nativeMesh->faces.size(); ++i )
187  {
188  const QgsMeshFace &face = nativeMesh->faces.at( i ) ;
189  QVector<QPointF> points;
190  points.reserve( face.size() );
191  for ( int j = 0; j < face.size(); ++j )
192  {
193  int index = face.at( j );
194  const QgsMeshVertex &vertex = mTriangularMesh.vertices[index]; // we need projected vertices
195  points.push_back( vertex.toQPointF() );
196  }
197  QPolygonF poly( points );
198  double cx, cy;
199  ENP_centroid( poly, cx, cy );
200  mNativeMeshFaceCentroids[i] = QgsMeshVertex( cx, cy );
201  }
202 
203  // CALCULATE SPATIAL INDEX
204  mSpatialIndex = QgsMeshSpatialIndex( mTriangularMesh );
205 
206  // SET ALL TRIANGLE CCW
207  for ( int i = 0; i < mTriangularMesh.faceCount(); ++i )
208  {
209  setTrianglesCounterClockWise( mTriangularMesh.faces[i] );
210  }
211 }
212 
213 const QVector<QgsMeshVertex> &QgsTriangularMesh::vertices() const
214 {
215  return mTriangularMesh.vertices;
216 }
217 
218 const QVector<QgsMeshFace> &QgsTriangularMesh::triangles() const
219 {
220  return mTriangularMesh.faces;
221 }
222 
223 const QVector<QgsMeshVertex> &QgsTriangularMesh::centroids() const
224 {
225  return mNativeMeshFaceCentroids;
226 }
227 
228 const QVector<int> &QgsTriangularMesh::trianglesToNativeFaces() const
229 {
230  return mTrianglesToNativeFaces;
231 }
232 
234 {
235  const QList<int> faceIndexes = mSpatialIndex.intersects( QgsRectangle( point, point ) );
236  for ( const int faceIndex : faceIndexes )
237  {
238  const QgsMeshFace &face = mTriangularMesh.faces.at( faceIndex );
239  const QgsGeometry geom = QgsMeshUtils::toGeometry( face, mTriangularMesh.vertices );
240  if ( geom.contains( &point ) )
241  return faceIndex;
242  }
243  return -1;
244 }
245 
247 {
248  const QList<int> faceIndexes = mSpatialIndex.intersects( QgsRectangle( point, point ) );
249 
250  for ( const int faceIndex : faceIndexes )
251  {
252  const QgsMeshFace &face = mTriangularMesh.faces.at( faceIndex );
253  if ( QgsMeshUtils::isInTriangleFace( point, face, mTriangularMesh.vertices ) )
254  return faceIndex;
255  }
256  return -1;
257 }
258 
259 QList<int> QgsTriangularMesh::faceIndexesForRectangle( const QgsRectangle &rectangle ) const
260 {
261  return mSpatialIndex.intersects( rectangle );
262 }
263 
264 QVector<QVector3D> QgsTriangularMesh::vertexNormals( float vertScale ) const
265 {
266  QVector<QVector3D> normales( vertices().count(), QVector3D( 0, 0, 0 ) );
267 
268  for ( const auto &face : triangles() )
269  {
270  for ( int i = 0; i < 3; i++ )
271  {
272  int index1( face.at( i ) );
273  int index2( face.at( ( i + 1 ) % 3 ) );
274  int index3( face.at( ( i + 2 ) % 3 ) );
275 
276  const QgsMeshVertex &vert( vertices().at( index1 ) );
277  const QgsMeshVertex &otherVert1( vertices().at( index2 ) );
278  const QgsMeshVertex &otherVert2( vertices().at( index3 ) );
279 
280  QVector3D v1( float( otherVert1.x() - vert.x() ), float( otherVert1.y() - vert.y() ), vertScale * float( otherVert1.z() - vert.z() ) );
281  QVector3D v2( float( otherVert2.x() - vert.x() ), float( otherVert2.y() - vert.y() ), vertScale * float( otherVert2.z() - vert.z() ) );
282 
283  normales[index1] += QVector3D::crossProduct( v1, v2 );
284  }
285  }
286  return normales;
287 }
288 
289 std::unique_ptr< QgsPolygon > QgsMeshUtils::toPolygon( const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
290 {
291  QVector<QgsPoint> ring;
292  for ( int j = 0; j < face.size(); ++j )
293  {
294  int vertexId = face[j];
295  Q_ASSERT( vertexId < vertices.size() );
296  const QgsPoint &vertex = vertices[vertexId];
297  ring.append( vertex );
298  }
299  std::unique_ptr< QgsPolygon > polygon = qgis::make_unique< QgsPolygon >();
300  polygon->setExteriorRing( new QgsLineString( ring ) );
301  return polygon;
302 }
303 
304 QgsGeometry QgsMeshUtils::toGeometry( const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
305 {
306  return QgsGeometry( QgsMeshUtils::toPolygon( face, vertices ) );
307 }
308 
309 QList<int> QgsMeshUtils::nativeFacesFromTriangles( const QList<int> &triangleIndexes, const QVector<int> &trianglesToNativeFaces )
310 {
311  QSet<int> nativeFaces;
312  for ( const int triangleIndex : triangleIndexes )
313  {
314  const int nativeIndex = trianglesToNativeFaces[triangleIndex];
315  nativeFaces.insert( nativeIndex );
316  }
317  return nativeFaces.toList();
318 }
319 
320 static double _isLeft2D( const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p )
321 {
322  return ( p2.x() - p1.x() ) * ( p.y() - p1.y() ) - ( p.x() - p1.x() ) * ( p2.y() - p1.y() );
323 }
324 
325 static bool _isInTriangle2D( const QgsPoint &p, const QVector<QgsMeshVertex> &triangle )
326 {
327  return ( ( _isLeft2D( triangle[2], triangle[0], p ) * _isLeft2D( triangle[2], triangle[0], triangle[1] ) >= 0 )
328  && ( _isLeft2D( triangle[0], triangle[1], p ) * _isLeft2D( triangle[0], triangle[1], triangle[2] ) >= 0 )
329  && ( _isLeft2D( triangle[2], triangle[1], p ) * _isLeft2D( triangle[2], triangle[1], triangle[0] ) >= 0 ) );
330 }
331 
332 bool QgsMeshUtils::isInTriangleFace( const QgsPointXY point, const QgsMeshFace &face, const QVector<QgsMeshVertex> &vertices )
333 {
334  if ( face.count() != 3 )
335  return false;
336 
337  QVector<QgsMeshVertex> triangle( 3 );
338  for ( int i = 0; i < 3; ++i )
339  {
340  if ( face[i] > vertices.count() )
341  return false;
342  triangle[i] = vertices[face[i]];
343  }
344 
345  const QgsPoint p( point.x(), point.y() );
346 
347  return _isInTriangle2D( p, triangle );
348 }
CORE_EXPORT QgsGeometry toGeometry(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
double y
Definition: qgspoint.h:42
bool addZValue(double zValue=0) override
Adds a z-dimension to the geometry, initialized to a preset value.
Definition: qgspoint.cpp:506
QPointF toQPointF() const
Returns the point as a QPointF.
Definition: qgspoint.h:320
QgsMeshVertex vertex(int index) const
Returns a vertex at the index.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
int faceIndexForPoint(const QgsPointXY &point) const
Finds index of triangle at given point It uses spatial indexing.
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
QList< int > intersects(const QgsRectangle &rectangle) const
Returns a list of face ids with a bounding box which intersects the specified rectangle.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QString what() const
Definition: qgsexception.h:48
QVector< QgsMeshVertex > vertices
vertices
const QVector< QgsMeshFace > & triangles() const
Returns triangles.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from...
void setM(double m)
Sets the point&#39;s m-value.
Definition: qgspoint.h:308
int faceIndexForPoint_v2(const QgsPointXY &point) const
Finds index of triangle at given point It uses spatial indexing and don&#39;t use geos to be faster...
bool isInTriangleFace(const QgsPointXY point, const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Tests if point p is on the face defined with vertices.
CORE_EXPORT std::unique_ptr< QgsPolygon > toPolygon(const QgsMeshFace &face, const QVector< QgsMeshVertex > &vertices)
Returns face as polygon geometry, caller is responsible for delete.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
~QgsTriangularMesh()
Dtor.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
double x
Definition: qgspointxy.h:47
CORE_EXPORT QList< int > nativeFacesFromTriangles(const QList< int > &triangleIndexes, const QVector< int > &trianglesToNativeFaces)
Returns unique native faces indexes from list of triangle indexes.
QList< int > faceIndexesForRectangle(const QgsRectangle &rectangle) const
Finds indexes of triangles intersecting given bounding box It uses spatial indexing.
A spatial index for QgsMeshFace objects.
void update(QgsMesh *nativeMesh, QgsRenderContext *context)
Constructs triangular mesh from layer&#39;s native mesh and context.
Contains information about the context of a rendering operation.
Mesh - vertices and faces.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QVector< int > QgsMeshFace
List of vertex indexes.
QgsTriangularMesh()
Ctor.
double z
Definition: qgspoint.h:43
QVector< QgsMeshFace > faces
faces
QVector< QVector3D > vertexNormals(float vertScale) const
Calculates and returns normale vector on each vertex.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
int faceCount() const
Returns number of faces.
bool contains(const QgsPointXY *p) const
Returns true if the geometry contains the point p.
const QVector< int > & trianglesToNativeFaces() const
Returns mapping between triangles and original faces.
const QVector< QgsMeshVertex > & vertices() const
Returns vertices in map coordinate system.
const QVector< QgsMeshVertex > & centroids() const
Returns centroids of the native faces in map CRS.
double m
Definition: qgspoint.h:44
QgsPoint QgsMeshVertex
xyz coords of vertex
double x
Definition: qgspoint.h:41