mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-09 00:04:30 -05:00
Implement profile generation for point vector layers
This commit is contained in:
parent
bdc1d28bfc
commit
87c3c911c0
@ -9,6 +9,45 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class QgsAbstractProfileResults
|
||||||
|
{
|
||||||
|
%Docstring(signature="appended")
|
||||||
|
Abstract base class for storage of elevation profiles.
|
||||||
|
|
||||||
|
.. versionadded:: 3.26
|
||||||
|
%End
|
||||||
|
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include "qgsabstractprofilegenerator.h"
|
||||||
|
%End
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~QgsAbstractProfileResults();
|
||||||
|
|
||||||
|
virtual QString type() const = 0;
|
||||||
|
%Docstring
|
||||||
|
Returns the unique string identifier for the results type.
|
||||||
|
%End
|
||||||
|
|
||||||
|
virtual QHash< double, double > distanceToHeightMap() const = 0;
|
||||||
|
%Docstring
|
||||||
|
Returns the map of distance (chainage) to height.
|
||||||
|
%End
|
||||||
|
|
||||||
|
virtual QgsPointSequence sampledPoints() const = 0;
|
||||||
|
%Docstring
|
||||||
|
Returns a list of sampled points, with their calculated elevation
|
||||||
|
as the point z value.
|
||||||
|
%End
|
||||||
|
|
||||||
|
virtual QVector< QgsGeometry > asGeometries() const = 0;
|
||||||
|
%Docstring
|
||||||
|
Returns a list of geometries representing the calculated elevation results.
|
||||||
|
%End
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
class QgsAbstractProfileGenerator
|
class QgsAbstractProfileGenerator
|
||||||
{
|
{
|
||||||
%Docstring(signature="appended")
|
%Docstring(signature="appended")
|
||||||
@ -31,7 +70,7 @@ The scenario will be:
|
|||||||
additional calls to the source.
|
additional calls to the source.
|
||||||
# profile job (still in GUI thread) stores the generator for later use.
|
# profile job (still in GUI thread) stores the generator for later use.
|
||||||
# profile job (in worker thread) calls :py:func:`QgsAbstractProfileGenerator.generateProfile()`
|
# profile job (in worker thread) calls :py:func:`QgsAbstractProfileGenerator.generateProfile()`
|
||||||
# profile job (again in GUI thread) will check :py:func:`~errors` and report them
|
# profile job (again in GUI thread) will check :py:func:`~QgsAbstractProfileResults.errors` and report them
|
||||||
|
|
||||||
.. versionadded:: 3.26
|
.. versionadded:: 3.26
|
||||||
%End
|
%End
|
||||||
@ -51,24 +90,13 @@ Returns ``True`` if the profile was generated successfully (i.e. the generation
|
|||||||
was not canceled early).
|
was not canceled early).
|
||||||
%End
|
%End
|
||||||
|
|
||||||
struct Result
|
virtual QgsAbstractProfileResults *takeResults() = 0 /TransferBack/;
|
||||||
{
|
|
||||||
double distance;
|
|
||||||
double height;
|
|
||||||
};
|
|
||||||
|
|
||||||
QList< QgsAbstractProfileGenerator::Result > results() const;
|
|
||||||
%Docstring
|
%Docstring
|
||||||
Temporary method to return results.
|
Takes results from the generator.
|
||||||
%End
|
|
||||||
|
|
||||||
QList< QgsPoint > rawPoints() const;
|
Ownership is transferred to the caller.
|
||||||
%Docstring
|
|
||||||
Temporary method to return results.
|
|
||||||
%End
|
%End
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
|
|||||||
@ -78,6 +78,14 @@ Returns the unique type ID string for the provider.
|
|||||||
Creates a clone of the provider and returns the new object.
|
Creates a clone of the provider and returns the new object.
|
||||||
|
|
||||||
Ownership is transferred to the caller.
|
Ownership is transferred to the caller.
|
||||||
|
%End
|
||||||
|
|
||||||
|
virtual void prepare() = 0 /Factory/;
|
||||||
|
%Docstring
|
||||||
|
Called on the main thread prior to accessing the provider from a background thread.
|
||||||
|
|
||||||
|
Subclasses must implement suitable logic in order to prepare for thread-safe calculation of
|
||||||
|
terrain heights on background threads.
|
||||||
%End
|
%End
|
||||||
|
|
||||||
virtual QgsCoordinateReferenceSystem crs() const = 0;
|
virtual QgsCoordinateReferenceSystem crs() const = 0;
|
||||||
@ -183,6 +191,8 @@ Constructor for QgsFlatTerrainProvider.
|
|||||||
|
|
||||||
virtual QgsFlatTerrainProvider *clone() const /Factory/;
|
virtual QgsFlatTerrainProvider *clone() const /Factory/;
|
||||||
|
|
||||||
|
virtual void prepare();
|
||||||
|
|
||||||
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -221,6 +231,8 @@ Constructor for QgsRasterDemTerrainProvider.
|
|||||||
|
|
||||||
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
||||||
|
|
||||||
|
virtual void prepare();
|
||||||
|
|
||||||
|
|
||||||
void setLayer( QgsRasterLayer *layer );
|
void setLayer( QgsRasterLayer *layer );
|
||||||
%Docstring
|
%Docstring
|
||||||
@ -274,6 +286,8 @@ Constructor for QgsMeshTerrainProvider.
|
|||||||
|
|
||||||
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
virtual bool equals( const QgsAbstractTerrainProvider *other ) const;
|
||||||
|
|
||||||
|
virtual void prepare();
|
||||||
|
|
||||||
|
|
||||||
void setLayer( QgsMeshLayer *layer );
|
void setLayer( QgsMeshLayer *layer );
|
||||||
%Docstring
|
%Docstring
|
||||||
|
|||||||
@ -18,7 +18,7 @@ typedef QList<int> QgsAttributeList;
|
|||||||
typedef QSet<int> QgsAttributeIds;
|
typedef QSet<int> QgsAttributeIds;
|
||||||
|
|
||||||
|
|
||||||
class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator, QgsExpressionContextScopeGenerator, QgsFeatureSink, QgsFeatureSource
|
class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator, QgsExpressionContextScopeGenerator, QgsFeatureSink, QgsFeatureSource, QgsAbstractProfileSource
|
||||||
{
|
{
|
||||||
%Docstring(signature="appended")
|
%Docstring(signature="appended")
|
||||||
Represents a vector layer which manages a vector based data sets.
|
Represents a vector layer which manages a vector based data sets.
|
||||||
@ -513,6 +513,8 @@ Uses :py:class:`QgsExpression`
|
|||||||
|
|
||||||
virtual QgsMapLayerElevationProperties *elevationProperties();
|
virtual QgsMapLayerElevationProperties *elevationProperties();
|
||||||
|
|
||||||
|
virtual QgsAbstractProfileGenerator *createProfileGenerator( const QgsProfileRequest &request ) /Factory/;
|
||||||
|
|
||||||
|
|
||||||
void setProviderEncoding( const QString &encoding );
|
void setProviderEncoding( const QString &encoding );
|
||||||
%Docstring
|
%Docstring
|
||||||
|
|||||||
@ -960,6 +960,88 @@ template<double, TYPE>
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
%MappedType QHash<double, double>
|
||||||
|
{
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include <QHash>
|
||||||
|
%End
|
||||||
|
|
||||||
|
%ConvertFromTypeCode
|
||||||
|
// Create the dictionary.
|
||||||
|
PyObject *d = PyDict_New();
|
||||||
|
|
||||||
|
if (!d)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
// Set the dictionary elements.
|
||||||
|
QHash<double, double>::iterator i;
|
||||||
|
|
||||||
|
for (i = sipCpp->begin(); i != sipCpp->end(); ++i)
|
||||||
|
{
|
||||||
|
PyObject *t1obj = PyFloat_FromDouble(i.key());
|
||||||
|
PyObject *t2obj = PyFloat_FromDouble(i.value());
|
||||||
|
|
||||||
|
if (t1obj == NULL || t2obj == NULL || PyDict_SetItem(d, t1obj, t2obj) < 0)
|
||||||
|
{
|
||||||
|
Py_DECREF(d);
|
||||||
|
|
||||||
|
if (t1obj)
|
||||||
|
Py_DECREF(t1obj);
|
||||||
|
|
||||||
|
if (t2obj)
|
||||||
|
Py_DECREF(t2obj);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(t1obj);
|
||||||
|
Py_DECREF(t2obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return d;
|
||||||
|
%End
|
||||||
|
|
||||||
|
%ConvertToTypeCode
|
||||||
|
PyObject *t1obj, *t2obj;
|
||||||
|
Py_ssize_t i = 0;
|
||||||
|
|
||||||
|
// Check the type if that is all that is required.
|
||||||
|
if (sipIsErr == NULL)
|
||||||
|
{
|
||||||
|
if (!PyDict_Check(sipPy))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
|
||||||
|
{
|
||||||
|
if (!PyFloat_Check(t1obj))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!PyFloat_Check(t2obj))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<double, double> *qm = new QHash<double, double>;
|
||||||
|
|
||||||
|
while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
|
||||||
|
{
|
||||||
|
int state;
|
||||||
|
|
||||||
|
double k = PyFloat_AsDouble(t1obj);
|
||||||
|
double v = PyFloat_AsDouble(t2obj);
|
||||||
|
|
||||||
|
qm->insert(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
*sipCppPtr = qm;
|
||||||
|
|
||||||
|
return sipGetState(sipTransferObj);
|
||||||
|
%End
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
template<TYPE2>
|
template<TYPE2>
|
||||||
%MappedType QMap<QString, QList<TYPE2> >
|
%MappedType QMap<QString, QList<TYPE2> >
|
||||||
{
|
{
|
||||||
|
|||||||
@ -837,6 +837,7 @@ set(QGIS_CORE_SRCS
|
|||||||
vector/qgsvectorlayerexporter.cpp
|
vector/qgsvectorlayerexporter.cpp
|
||||||
vector/qgsvectorlayerjoinbuffer.cpp
|
vector/qgsvectorlayerjoinbuffer.cpp
|
||||||
vector/qgsvectorlayerjoininfo.cpp
|
vector/qgsvectorlayerjoininfo.cpp
|
||||||
|
vector/qgsvectorlayerprofilegenerator.cpp
|
||||||
vector/qgsvectorlayerrenderer.cpp
|
vector/qgsvectorlayerrenderer.cpp
|
||||||
vector/qgsvectorlayertemporalproperties.cpp
|
vector/qgsvectorlayertemporalproperties.cpp
|
||||||
vector/qgsvectorlayertools.cpp
|
vector/qgsvectorlayertools.cpp
|
||||||
@ -1793,6 +1794,7 @@ set(QGIS_CORE_HDRS
|
|||||||
vector/qgsvectorlayerfeatureiterator.h
|
vector/qgsvectorlayerfeatureiterator.h
|
||||||
vector/qgsvectorlayerjoinbuffer.h
|
vector/qgsvectorlayerjoinbuffer.h
|
||||||
vector/qgsvectorlayerjoininfo.h
|
vector/qgsvectorlayerjoininfo.h
|
||||||
|
vector/qgsvectorlayerprofilegenerator.h
|
||||||
vector/qgsvectorlayerrenderer.h
|
vector/qgsvectorlayerrenderer.h
|
||||||
vector/qgsvectorlayertemporalproperties.h
|
vector/qgsvectorlayertemporalproperties.h
|
||||||
vector/qgsvectorlayertools.h
|
vector/qgsvectorlayertools.h
|
||||||
|
|||||||
@ -17,3 +17,5 @@
|
|||||||
#include "qgsabstractprofilegenerator.h"
|
#include "qgsabstractprofilegenerator.h"
|
||||||
|
|
||||||
QgsAbstractProfileGenerator::~QgsAbstractProfileGenerator() = default;
|
QgsAbstractProfileGenerator::~QgsAbstractProfileGenerator() = default;
|
||||||
|
|
||||||
|
QgsAbstractProfileResults::~QgsAbstractProfileResults() = default;
|
||||||
|
|||||||
@ -23,6 +23,43 @@
|
|||||||
|
|
||||||
#include "qgspoint.h"
|
#include "qgspoint.h"
|
||||||
|
|
||||||
|
class QgsGeometry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Abstract base class for storage of elevation profiles.
|
||||||
|
*
|
||||||
|
* \ingroup core
|
||||||
|
* \since QGIS 3.26
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsAbstractProfileResults
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~QgsAbstractProfileResults();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the unique string identifier for the results type.
|
||||||
|
*/
|
||||||
|
virtual QString type() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map of distance (chainage) to height.
|
||||||
|
*/
|
||||||
|
virtual QHash< double, double > distanceToHeightMap() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of sampled points, with their calculated elevation
|
||||||
|
* as the point z value.
|
||||||
|
*/
|
||||||
|
virtual QgsPointSequence sampledPoints() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of geometries representing the calculated elevation results.
|
||||||
|
*/
|
||||||
|
virtual QVector< QgsGeometry > asGeometries() const = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Abstract base class for objects which generate elevation profiles.
|
* \brief Abstract base class for objects which generate elevation profiles.
|
||||||
*
|
*
|
||||||
@ -63,26 +100,12 @@ class CORE_EXPORT QgsAbstractProfileGenerator
|
|||||||
*/
|
*/
|
||||||
virtual bool generateProfile() = 0;
|
virtual bool generateProfile() = 0;
|
||||||
|
|
||||||
// Temporary class only!
|
|
||||||
struct CORE_EXPORT Result
|
|
||||||
{
|
|
||||||
double distance;
|
|
||||||
double height;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary method to return results.
|
* Takes results from the generator.
|
||||||
|
*
|
||||||
|
* Ownership is transferred to the caller.
|
||||||
*/
|
*/
|
||||||
QList< QgsAbstractProfileGenerator::Result > results() const { return mResults; }
|
virtual QgsAbstractProfileResults *takeResults() = 0 SIP_TRANSFERBACK;
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary method to return results.
|
|
||||||
*/
|
|
||||||
QList< QgsPoint > rawPoints() const { return mRawPoints; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
QList< QgsPoint > mRawPoints;
|
|
||||||
QList< Result > mResults;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
#define QGSPROFILEREQUEST_H
|
#define QGSPROFILEREQUEST_H
|
||||||
|
|
||||||
#include "qgis_core.h"
|
#include "qgis_core.h"
|
||||||
#include "qgis_sip.h"
|
|
||||||
#include "qgscoordinatereferencesystem.h"
|
#include "qgscoordinatereferencesystem.h"
|
||||||
#include "qgscoordinatetransformcontext.h"
|
#include "qgscoordinatetransformcontext.h"
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
* *
|
* *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
#include "qgsterrainprovider.h"
|
#include "qgsterrainprovider.h"
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
QgsAbstractTerrainProvider::~QgsAbstractTerrainProvider() = default;
|
QgsAbstractTerrainProvider::~QgsAbstractTerrainProvider() = default;
|
||||||
|
|
||||||
@ -83,6 +84,12 @@ QgsFlatTerrainProvider *QgsFlatTerrainProvider::clone() const
|
|||||||
return new QgsFlatTerrainProvider( *this );
|
return new QgsFlatTerrainProvider( *this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsFlatTerrainProvider::prepare()
|
||||||
|
{
|
||||||
|
Q_ASSERT_X( QThread::currentThread() == QCoreApplication::instance()->thread(), "QgsFlatTerrainProvider::prepare", "prepare() must be called from the main thread" );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsFlatTerrainProvider::equals( const QgsAbstractTerrainProvider *other ) const
|
bool QgsFlatTerrainProvider::equals( const QgsAbstractTerrainProvider *other ) const
|
||||||
{
|
{
|
||||||
if ( other->type() != type() )
|
if ( other->type() != type() )
|
||||||
@ -144,21 +151,27 @@ QDomElement QgsRasterDemTerrainProvider::writeXml( QDomDocument &document, const
|
|||||||
|
|
||||||
QgsCoordinateReferenceSystem QgsRasterDemTerrainProvider::crs() const
|
QgsCoordinateReferenceSystem QgsRasterDemTerrainProvider::crs() const
|
||||||
{
|
{
|
||||||
return mRasterLayer ? mRasterLayer->crs() : QgsCoordinateReferenceSystem();
|
return mRasterProvider ? mRasterProvider->crs()
|
||||||
|
: ( mRasterLayer ? mRasterLayer->crs() : QgsCoordinateReferenceSystem() );
|
||||||
}
|
}
|
||||||
|
|
||||||
double QgsRasterDemTerrainProvider::heightAt( double x, double y ) const
|
double QgsRasterDemTerrainProvider::heightAt( double x, double y ) const
|
||||||
{
|
{
|
||||||
// TODO -- may want to use a more efficient approach here, i.e. requesting whole
|
// TODO -- may want to use a more efficient approach here, i.e. requesting whole
|
||||||
// blocks upfront instead of multiple sample calls
|
// blocks upfront instead of multiple sample calls
|
||||||
if ( mRasterLayer && mRasterLayer->isValid() )
|
bool ok = false;
|
||||||
|
double res = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
if ( mRasterProvider )
|
||||||
{
|
{
|
||||||
bool ok = false;
|
res = mRasterProvider->sample( QgsPointXY( x, y ), 1, &ok );
|
||||||
const double res = mRasterLayer->dataProvider()->sample( QgsPointXY( x, y ), 1, &ok );
|
|
||||||
|
|
||||||
if ( ok )
|
|
||||||
return res * mScale + mOffset;
|
|
||||||
}
|
}
|
||||||
|
else if ( QThread::currentThread() == QCoreApplication::instance()->thread() && mRasterLayer && mRasterLayer->isValid() )
|
||||||
|
{
|
||||||
|
res = mRasterLayer->dataProvider()->sample( QgsPointXY( x, y ), 1, &ok );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ok )
|
||||||
|
return res * mScale + mOffset;
|
||||||
|
|
||||||
return std::numeric_limits<double>::quiet_NaN();
|
return std::numeric_limits<double>::quiet_NaN();
|
||||||
}
|
}
|
||||||
@ -182,6 +195,14 @@ bool QgsRasterDemTerrainProvider::equals( const QgsAbstractTerrainProvider *othe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsRasterDemTerrainProvider::prepare()
|
||||||
|
{
|
||||||
|
Q_ASSERT_X( QThread::currentThread() == QCoreApplication::instance()->thread(), "QgsRasterDemTerrainProvider::prepare", "prepare() must be called from the main thread" );
|
||||||
|
|
||||||
|
if ( mRasterLayer && mRasterLayer->isValid() )
|
||||||
|
mRasterProvider.reset( mRasterLayer->dataProvider()->clone() );
|
||||||
|
}
|
||||||
|
|
||||||
void QgsRasterDemTerrainProvider::setLayer( QgsRasterLayer *layer )
|
void QgsRasterDemTerrainProvider::setLayer( QgsRasterLayer *layer )
|
||||||
{
|
{
|
||||||
mRasterLayer.setLayer( layer );
|
mRasterLayer.setLayer( layer );
|
||||||
@ -192,6 +213,13 @@ QgsRasterLayer *QgsRasterDemTerrainProvider::layer() const
|
|||||||
return mRasterLayer.get();
|
return mRasterLayer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QgsRasterDemTerrainProvider::QgsRasterDemTerrainProvider( const QgsRasterDemTerrainProvider &other )
|
||||||
|
: QgsAbstractTerrainProvider( other )
|
||||||
|
, mRasterLayer( other.mRasterLayer )
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// QgsMeshTerrainProvider
|
// QgsMeshTerrainProvider
|
||||||
@ -273,6 +301,12 @@ bool QgsMeshTerrainProvider::equals( const QgsAbstractTerrainProvider *other ) c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsMeshTerrainProvider::prepare()
|
||||||
|
{
|
||||||
|
Q_ASSERT_X( QThread::currentThread() == QCoreApplication::instance()->thread(), "QgsMeshTerrainProvider::prepare", "prepare() must be called from the main thread" );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void QgsMeshTerrainProvider::setLayer( QgsMeshLayer *layer )
|
void QgsMeshTerrainProvider::setLayer( QgsMeshLayer *layer )
|
||||||
{
|
{
|
||||||
mMeshLayer.setLayer( layer );
|
mMeshLayer.setLayer( layer );
|
||||||
|
|||||||
@ -103,6 +103,14 @@ class CORE_EXPORT QgsAbstractTerrainProvider
|
|||||||
*/
|
*/
|
||||||
virtual QgsAbstractTerrainProvider *clone() const = 0 SIP_FACTORY;
|
virtual QgsAbstractTerrainProvider *clone() const = 0 SIP_FACTORY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on the main thread prior to accessing the provider from a background thread.
|
||||||
|
*
|
||||||
|
* Subclasses must implement suitable logic in order to prepare for thread-safe calculation of
|
||||||
|
* terrain heights on background threads.
|
||||||
|
*/
|
||||||
|
virtual void prepare() = 0 SIP_FACTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the native coordinate reference system of the terrain provider.
|
* Returns the native coordinate reference system of the terrain provider.
|
||||||
*/
|
*/
|
||||||
@ -208,6 +216,7 @@ class CORE_EXPORT QgsFlatTerrainProvider : public QgsAbstractTerrainProvider
|
|||||||
QgsCoordinateReferenceSystem crs() const override;
|
QgsCoordinateReferenceSystem crs() const override;
|
||||||
double heightAt( double x, double y ) const override;
|
double heightAt( double x, double y ) const override;
|
||||||
QgsFlatTerrainProvider *clone() const override SIP_FACTORY;
|
QgsFlatTerrainProvider *clone() const override SIP_FACTORY;
|
||||||
|
void prepare() override;
|
||||||
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -234,6 +243,7 @@ class CORE_EXPORT QgsRasterDemTerrainProvider : public QgsAbstractTerrainProvide
|
|||||||
double heightAt( double x, double y ) const override;
|
double heightAt( double x, double y ) const override;
|
||||||
QgsRasterDemTerrainProvider *clone() const override SIP_FACTORY;
|
QgsRasterDemTerrainProvider *clone() const override SIP_FACTORY;
|
||||||
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
||||||
|
void prepare() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the raster \a layer with elevation model to be used as the terrain source.
|
* Sets the raster \a layer with elevation model to be used as the terrain source.
|
||||||
@ -251,8 +261,10 @@ class CORE_EXPORT QgsRasterDemTerrainProvider : public QgsAbstractTerrainProvide
|
|||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QgsRasterDemTerrainProvider( const QgsRasterDemTerrainProvider &other );
|
||||||
|
|
||||||
_LayerRef<QgsRasterLayer> mRasterLayer;
|
_LayerRef<QgsRasterLayer> mRasterLayer;
|
||||||
|
std::unique_ptr< QgsRasterDataProvider > mRasterProvider;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -280,6 +292,7 @@ class CORE_EXPORT QgsMeshTerrainProvider : public QgsAbstractTerrainProvider
|
|||||||
double heightAt( double x, double y ) const override;
|
double heightAt( double x, double y ) const override;
|
||||||
QgsMeshTerrainProvider *clone() const override SIP_FACTORY;
|
QgsMeshTerrainProvider *clone() const override SIP_FACTORY;
|
||||||
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
bool equals( const QgsAbstractTerrainProvider *other ) const override;
|
||||||
|
void prepare() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the mesh \a layer to be used as the terrain source.
|
* Sets the mesh \a layer to be used as the terrain source.
|
||||||
|
|||||||
@ -23,6 +23,45 @@
|
|||||||
#include "qgsgeometryengine.h"
|
#include "qgsgeometryengine.h"
|
||||||
#include "qgsgeos.h"
|
#include "qgsgeos.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsRasterLayerProfileResults
|
||||||
|
//
|
||||||
|
|
||||||
|
QString QgsRasterLayerProfileResults::type() const
|
||||||
|
{
|
||||||
|
return QStringLiteral( "raster" );
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<double, double> QgsRasterLayerProfileResults::distanceToHeightMap() const
|
||||||
|
{
|
||||||
|
QHash<double, double> res;
|
||||||
|
for ( const Result &r : results )
|
||||||
|
{
|
||||||
|
res.insert( r.distance, r.height );
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsPointSequence QgsRasterLayerProfileResults::sampledPoints() const
|
||||||
|
{
|
||||||
|
return rawPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QgsGeometry> QgsRasterLayerProfileResults::asGeometries() const
|
||||||
|
{
|
||||||
|
QVector<QgsGeometry> res;
|
||||||
|
res.reserve( rawPoints.size() );
|
||||||
|
for ( const QgsPoint &point : rawPoints )
|
||||||
|
res.append( QgsGeometry( point.clone() ) );
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsRasterLayerProfileGenerator
|
||||||
|
//
|
||||||
|
|
||||||
QgsRasterLayerProfileGenerator::QgsRasterLayerProfileGenerator( QgsRasterLayer *layer, const QgsProfileRequest &request )
|
QgsRasterLayerProfileGenerator::QgsRasterLayerProfileGenerator( QgsRasterLayer *layer, const QgsProfileRequest &request )
|
||||||
: mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
|
: mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
|
||||||
, mSourceCrs( layer->crs() )
|
, mSourceCrs( layer->crs() )
|
||||||
@ -60,6 +99,8 @@ bool QgsRasterLayerProfileGenerator::generateProfile()
|
|||||||
if ( !profileCurveBoundingBox.intersects( mRasterProvider->extent() ) )
|
if ( !profileCurveBoundingBox.intersects( mRasterProvider->extent() ) )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
mResults = std::make_unique< QgsRasterLayerProfileResults >();
|
||||||
|
|
||||||
std::unique_ptr< QgsGeometryEngine > curveEngine( QgsGeometry::createGeometryEngine( transformedCurve.get() ) );
|
std::unique_ptr< QgsGeometryEngine > curveEngine( QgsGeometry::createGeometryEngine( transformedCurve.get() ) );
|
||||||
curveEngine->prepareGeometry();
|
curveEngine->prepareGeometry();
|
||||||
|
|
||||||
@ -136,7 +177,7 @@ bool QgsRasterLayerProfileGenerator::generateProfile()
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
mRawPoints.append( pixel );
|
mResults->rawPoints.append( pixel );
|
||||||
}
|
}
|
||||||
currentY -= mRasterUnitsPerPixelY;
|
currentY -= mRasterUnitsPerPixelY;
|
||||||
}
|
}
|
||||||
@ -145,17 +186,22 @@ bool QgsRasterLayerProfileGenerator::generateProfile()
|
|||||||
// convert x/y values back to distance/height values
|
// convert x/y values back to distance/height values
|
||||||
QgsGeos originalCurveGeos( mProfileCurve.get() );
|
QgsGeos originalCurveGeos( mProfileCurve.get() );
|
||||||
originalCurveGeos.prepareGeometry();
|
originalCurveGeos.prepareGeometry();
|
||||||
mResults.reserve( mRawPoints.size() );
|
mResults->results.reserve( mResults->rawPoints.size() );
|
||||||
QString lastError;
|
QString lastError;
|
||||||
for ( const QgsPoint &pixel : std::as_const( mRawPoints ) )
|
for ( const QgsPoint &pixel : std::as_const( mResults->rawPoints ) )
|
||||||
{
|
{
|
||||||
const double distance = originalCurveGeos.lineLocatePoint( pixel, &lastError );
|
const double distance = originalCurveGeos.lineLocatePoint( pixel, &lastError );
|
||||||
|
|
||||||
Result res;
|
QgsRasterLayerProfileResults::Result res;
|
||||||
res.distance = distance;
|
res.distance = distance;
|
||||||
res.height = pixel.z();
|
res.height = pixel.z();
|
||||||
mResults.push_back( res );
|
mResults->results.push_back( res );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QgsAbstractProfileResults *QgsRasterLayerProfileGenerator::takeResults()
|
||||||
|
{
|
||||||
|
return mResults.release();
|
||||||
|
}
|
||||||
|
|||||||
@ -33,6 +33,33 @@ class QgsRasterDataProvider;
|
|||||||
|
|
||||||
#define SIP_NO_FILE
|
#define SIP_NO_FILE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Implementation of QgsAbstractProfileResults for raster layers.
|
||||||
|
*
|
||||||
|
* \note Not available in Python bindings
|
||||||
|
* \ingroup core
|
||||||
|
* \since QGIS 3.26
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsRasterLayerProfileResults : public QgsAbstractProfileResults
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Result
|
||||||
|
{
|
||||||
|
double distance;
|
||||||
|
double height;
|
||||||
|
};
|
||||||
|
|
||||||
|
QgsPointSequence rawPoints;
|
||||||
|
QList< Result > results;
|
||||||
|
|
||||||
|
QString type() const override;
|
||||||
|
QHash< double, double > distanceToHeightMap() const override;
|
||||||
|
QgsPointSequence sampledPoints() const override;
|
||||||
|
QVector< QgsGeometry > asGeometries() const override;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Implementation of QgsAbstractProfileGenerator for raster layers.
|
* \brief Implementation of QgsAbstractProfileGenerator for raster layers.
|
||||||
*
|
*
|
||||||
@ -53,6 +80,7 @@ class CORE_EXPORT QgsRasterLayerProfileGenerator : public QgsAbstractProfileGene
|
|||||||
~QgsRasterLayerProfileGenerator() override;
|
~QgsRasterLayerProfileGenerator() override;
|
||||||
|
|
||||||
bool generateProfile() override;
|
bool generateProfile() override;
|
||||||
|
QgsAbstractProfileResults *takeResults() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@ -67,6 +95,8 @@ class CORE_EXPORT QgsRasterLayerProfileGenerator : public QgsAbstractProfileGene
|
|||||||
|
|
||||||
std::unique_ptr< QgsRasterDataProvider > mRasterProvider;
|
std::unique_ptr< QgsRasterDataProvider > mRasterProvider;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsRasterLayerProfileResults > mResults;
|
||||||
|
|
||||||
int mBand = 1;
|
int mBand = 1;
|
||||||
double mRasterUnitsPerPixelX = 1;
|
double mRasterUnitsPerPixelX = 1;
|
||||||
double mRasterUnitsPerPixelY = 1;
|
double mRasterUnitsPerPixelY = 1;
|
||||||
|
|||||||
@ -89,6 +89,7 @@
|
|||||||
#include "qgsruntimeprofiler.h"
|
#include "qgsruntimeprofiler.h"
|
||||||
#include "qgsfeaturerenderergenerator.h"
|
#include "qgsfeaturerenderergenerator.h"
|
||||||
#include "qgsvectorlayerutils.h"
|
#include "qgsvectorlayerutils.h"
|
||||||
|
#include "qgsvectorlayerprofilegenerator.h"
|
||||||
|
|
||||||
#include "diagram/qgsdiagram.h"
|
#include "diagram/qgsdiagram.h"
|
||||||
|
|
||||||
@ -683,6 +684,11 @@ QgsMapLayerElevationProperties *QgsVectorLayer::elevationProperties()
|
|||||||
return mElevationProperties;
|
return mElevationProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QgsAbstractProfileGenerator *QgsVectorLayer::createProfileGenerator( const QgsProfileRequest &request )
|
||||||
|
{
|
||||||
|
return new QgsVectorLayerProfileGenerator( this, request );
|
||||||
|
}
|
||||||
|
|
||||||
void QgsVectorLayer::setProviderEncoding( const QString &encoding )
|
void QgsVectorLayer::setProviderEncoding( const QString &encoding )
|
||||||
{
|
{
|
||||||
if ( isValid() && mDataProvider && mDataProvider->encoding() != encoding )
|
if ( isValid() && mDataProvider && mDataProvider->encoding() != encoding )
|
||||||
|
|||||||
@ -42,6 +42,7 @@
|
|||||||
#include "qgsexpressioncontextgenerator.h"
|
#include "qgsexpressioncontextgenerator.h"
|
||||||
#include "qgsexpressioncontextscopegenerator.h"
|
#include "qgsexpressioncontextscopegenerator.h"
|
||||||
#include "qgsexpressioncontext.h"
|
#include "qgsexpressioncontext.h"
|
||||||
|
#include "qgsabstractprofilesource.h"
|
||||||
|
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QImage;
|
class QImage;
|
||||||
@ -389,7 +390,7 @@ typedef QSet<int> QgsAttributeIds;
|
|||||||
*
|
*
|
||||||
* \see QgsVectorLayerUtils()
|
* \see QgsVectorLayerUtils()
|
||||||
*/
|
*/
|
||||||
class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionContextGenerator, public QgsExpressionContextScopeGenerator, public QgsFeatureSink, public QgsFeatureSource
|
class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionContextGenerator, public QgsExpressionContextScopeGenerator, public QgsFeatureSink, public QgsFeatureSource, public QgsAbstractProfileSource
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -627,6 +628,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
|||||||
const QgsVectorDataProvider *dataProvider() const FINAL SIP_SKIP;
|
const QgsVectorDataProvider *dataProvider() const FINAL SIP_SKIP;
|
||||||
QgsMapLayerTemporalProperties *temporalProperties() override;
|
QgsMapLayerTemporalProperties *temporalProperties() override;
|
||||||
QgsMapLayerElevationProperties *elevationProperties() override;
|
QgsMapLayerElevationProperties *elevationProperties() override;
|
||||||
|
QgsAbstractProfileGenerator *createProfileGenerator( const QgsProfileRequest &request ) override SIP_FACTORY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the text \a encoding of the data provider.
|
* Sets the text \a encoding of the data provider.
|
||||||
|
|||||||
240
src/core/vector/qgsvectorlayerprofilegenerator.cpp
Normal file
240
src/core/vector/qgsvectorlayerprofilegenerator.cpp
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsvectorlayerprofilegenerator.cpp
|
||||||
|
---------------
|
||||||
|
begin : March 2022
|
||||||
|
copyright : (C) 2022 by Nyall Dawson
|
||||||
|
email : nyall dot dawson at gmail dot com
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
#include "qgsvectorlayerprofilegenerator.h"
|
||||||
|
#include "qgsprofilerequest.h"
|
||||||
|
#include "qgscurve.h"
|
||||||
|
#include "qgsvectorlayer.h"
|
||||||
|
#include "qgsvectorlayerelevationproperties.h"
|
||||||
|
#include "qgscoordinatetransform.h"
|
||||||
|
#include "qgsgeos.h"
|
||||||
|
#include "qgsvectorlayerfeatureiterator.h"
|
||||||
|
#include "qgsterrainprovider.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsVectorLayerProfileResults
|
||||||
|
//
|
||||||
|
|
||||||
|
QString QgsVectorLayerProfileResults::type() const
|
||||||
|
{
|
||||||
|
return QStringLiteral( "vector" );
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<double, double> QgsVectorLayerProfileResults::distanceToHeightMap() const
|
||||||
|
{
|
||||||
|
QHash<double, double> res;
|
||||||
|
for ( const Result &r : results )
|
||||||
|
{
|
||||||
|
res.insert( r.distance, r.height );
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsPointSequence QgsVectorLayerProfileResults::sampledPoints() const
|
||||||
|
{
|
||||||
|
return rawPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QgsGeometry> QgsVectorLayerProfileResults::asGeometries() const
|
||||||
|
{
|
||||||
|
return geometries;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// QgsVectorLayerProfileGenerator
|
||||||
|
//
|
||||||
|
|
||||||
|
QgsVectorLayerProfileGenerator::QgsVectorLayerProfileGenerator( QgsVectorLayer *layer, const QgsProfileRequest &request )
|
||||||
|
: mProfileCurve( request.profileCurve() ? request.profileCurve()->clone() : nullptr )
|
||||||
|
, mTerrainProvider( request.terrainProvider() ? request.terrainProvider()->clone() : nullptr )
|
||||||
|
, mTolerance( request.tolerance() )
|
||||||
|
, mSourceCrs( layer->crs() )
|
||||||
|
, mTargetCrs( request.crs() )
|
||||||
|
, mTransformContext( request.transformContext() )
|
||||||
|
, mSource( std::make_unique< QgsVectorLayerFeatureSource >( layer ) )
|
||||||
|
, mOffset( layer->elevationProperties()->zOffset() )
|
||||||
|
, mScale( layer->elevationProperties()->zScale() )
|
||||||
|
, mClamping( qgis::down_cast< QgsVectorLayerElevationProperties* >( layer->elevationProperties() )->clamping() )
|
||||||
|
, mBinding( qgis::down_cast< QgsVectorLayerElevationProperties* >( layer->elevationProperties() )->binding() )
|
||||||
|
, mExtrusionEnabled( qgis::down_cast< QgsVectorLayerElevationProperties* >( layer->elevationProperties() )->extrusionEnabled() )
|
||||||
|
, mExtrusionHeight( qgis::down_cast< QgsVectorLayerElevationProperties* >( layer->elevationProperties() )->extrusionHeight() )
|
||||||
|
, mWkbType( layer->wkbType() )
|
||||||
|
{
|
||||||
|
if ( mTerrainProvider )
|
||||||
|
mTerrainProvider->prepare(); // must be done on main thread
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsVectorLayerProfileGenerator::~QgsVectorLayerProfileGenerator() = default;
|
||||||
|
|
||||||
|
bool QgsVectorLayerProfileGenerator::generateProfile()
|
||||||
|
{
|
||||||
|
if ( !mProfileCurve )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// we need to transform the profile curve to the vector's CRS
|
||||||
|
mTransformedCurve.reset( mProfileCurve->clone() );
|
||||||
|
mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext );
|
||||||
|
if ( mTerrainProvider )
|
||||||
|
mTargetToTerrainProviderTransform = QgsCoordinateTransform( mTargetCrs, mTerrainProvider->crs(), mTransformContext );
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mTransformedCurve->transform( mLayerToTargetTransform, Qgis::TransformDirection::Reverse );
|
||||||
|
}
|
||||||
|
catch ( QgsCsException & )
|
||||||
|
{
|
||||||
|
QgsDebugMsg( QStringLiteral( "Error transforming profile line to vector CRS" ) );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mResults = std::make_unique< QgsVectorLayerProfileResults >();
|
||||||
|
|
||||||
|
switch ( QgsWkbTypes::geometryType( mWkbType ) )
|
||||||
|
{
|
||||||
|
case QgsWkbTypes::PointGeometry:
|
||||||
|
if ( !generateProfileForPoints() )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QgsRectangle profileCurveBoundingBox = mTransformedCurve->boundingBox();
|
||||||
|
//if ( !profileCurveBoundingBox.intersects( mRasterProvider->extent() ) )
|
||||||
|
// return false;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsGeometryEngine > curveEngine( QgsGeometry::createGeometryEngine( mTransformedCurve.get() ) );
|
||||||
|
curveEngine->prepareGeometry();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// convert x/y values back to distance/height values
|
||||||
|
QgsGeos originalCurveGeos( mProfileCurve.get() );
|
||||||
|
originalCurveGeos.prepareGeometry();
|
||||||
|
mResults->results.reserve( mResults->rawPoints.size() );
|
||||||
|
QString lastError;
|
||||||
|
for ( const QgsPoint &pixel : std::as_const( mResults->rawPoints ) )
|
||||||
|
{
|
||||||
|
const double distance = originalCurveGeos.lineLocatePoint( pixel, &lastError );
|
||||||
|
|
||||||
|
QgsVectorLayerProfileResults::Result res;
|
||||||
|
res.distance = distance;
|
||||||
|
res.height = pixel.z();
|
||||||
|
mResults->results.push_back( res );
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsAbstractProfileResults *QgsVectorLayerProfileGenerator::takeResults()
|
||||||
|
{
|
||||||
|
return mResults.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsVectorLayerProfileGenerator::generateProfileForPoints()
|
||||||
|
{
|
||||||
|
// get features from layer
|
||||||
|
QgsFeatureRequest request;
|
||||||
|
request.setDestinationCrs( mTargetCrs, mTransformContext );
|
||||||
|
request.setDistanceWithin( QgsGeometry( mProfileCurve->clone() ), mTolerance );
|
||||||
|
|
||||||
|
auto processPoint = [this]( const QgsPoint * point )
|
||||||
|
{
|
||||||
|
const double height = featureZToHeight( point->x(), point->y(), point->z() );
|
||||||
|
mResults->rawPoints.append( QgsPoint( point->x(), point->y(), height ) );
|
||||||
|
|
||||||
|
if ( mExtrusionEnabled )
|
||||||
|
{
|
||||||
|
mResults->geometries.append( QgsGeometry( new QgsLineString( QgsPoint( point->x(), point->y(), height ),
|
||||||
|
QgsPoint( point->x(), point->y(), height + mExtrusionHeight ) ) ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mResults->geometries.append( QgsGeometry( new QgsPoint( point->x(), point->y(), height ) ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QgsFeature feature;
|
||||||
|
QgsFeatureIterator it = mSource->getFeatures( request );
|
||||||
|
while ( it.nextFeature( feature ) )
|
||||||
|
{
|
||||||
|
const QgsGeometry g = feature.geometry();
|
||||||
|
if ( g.isMultipart() )
|
||||||
|
{
|
||||||
|
for ( auto it = g.const_parts_begin(); it != g.const_parts_end(); ++it )
|
||||||
|
{
|
||||||
|
processPoint( qgsgeometry_cast< const QgsPoint * >( *it ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processPoint( qgsgeometry_cast< const QgsPoint * >( g.constGet() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double QgsVectorLayerProfileGenerator::featureZToHeight( double x, double y, double z )
|
||||||
|
{
|
||||||
|
switch ( mClamping )
|
||||||
|
{
|
||||||
|
case Qgis::AltitudeClamping::Absolute:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Qgis::AltitudeClamping::Relative:
|
||||||
|
case Qgis::AltitudeClamping::Terrain:
|
||||||
|
{
|
||||||
|
if ( !mTerrainProvider )
|
||||||
|
break;
|
||||||
|
|
||||||
|
// transform feature point to terrain provider crs
|
||||||
|
try
|
||||||
|
{
|
||||||
|
double dummyZ = 0;
|
||||||
|
mTargetToTerrainProviderTransform.transformInPlace( x, y, dummyZ );
|
||||||
|
}
|
||||||
|
catch ( QgsCsException & )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double terrainZ = mTerrainProvider->heightAt( x, y );
|
||||||
|
if ( !std::isnan( terrainZ ) )
|
||||||
|
{
|
||||||
|
switch ( mClamping )
|
||||||
|
{
|
||||||
|
case Qgis::AltitudeClamping::Relative:
|
||||||
|
if ( std::isnan( z ) )
|
||||||
|
z = terrainZ;
|
||||||
|
else
|
||||||
|
z += terrainZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Qgis::AltitudeClamping::Terrain:
|
||||||
|
z = terrainZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Qgis::AltitudeClamping::Absolute:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return z * mScale + mOffset;
|
||||||
|
}
|
||||||
|
|
||||||
125
src/core/vector/qgsvectorlayerprofilegenerator.h
Normal file
125
src/core/vector/qgsvectorlayerprofilegenerator.h
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgsvectorlayerprofilegenerator.h
|
||||||
|
---------------
|
||||||
|
begin : March 2022
|
||||||
|
copyright : (C) 2022 by Nyall Dawson
|
||||||
|
email : nyall dot dawson at gmail dot com
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
#ifndef QGSVECTORLAYERPROFILEGENERATOR_H
|
||||||
|
#define QGSVECTORLAYERPROFILEGENERATOR_H
|
||||||
|
|
||||||
|
#include "qgis_core.h"
|
||||||
|
#include "qgis_sip.h"
|
||||||
|
#include "qgsabstractprofilegenerator.h"
|
||||||
|
#include "qgscoordinatereferencesystem.h"
|
||||||
|
#include "qgscoordinatetransformcontext.h"
|
||||||
|
#include "qgscoordinatetransform.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class QgsProfileRequest;
|
||||||
|
class QgsCurve;
|
||||||
|
class QgsVectorLayer;
|
||||||
|
class QgsVectorLayerFeatureSource;
|
||||||
|
class QgsAbstractTerrainProvider;
|
||||||
|
|
||||||
|
#define SIP_NO_FILE
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Implementation of QgsAbstractProfileResults for vector layers.
|
||||||
|
*
|
||||||
|
* \note Not available in Python bindings
|
||||||
|
* \ingroup core
|
||||||
|
* \since QGIS 3.26
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsVectorLayerProfileResults : public QgsAbstractProfileResults
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Temporary class only!
|
||||||
|
struct Result
|
||||||
|
{
|
||||||
|
double distance;
|
||||||
|
double height;
|
||||||
|
};
|
||||||
|
|
||||||
|
QgsPointSequence rawPoints;
|
||||||
|
QList< Result > results;
|
||||||
|
QVector< QgsGeometry > geometries;
|
||||||
|
|
||||||
|
QString type() const override;
|
||||||
|
QHash< double, double > distanceToHeightMap() const override;
|
||||||
|
QgsPointSequence sampledPoints() const override;
|
||||||
|
QVector< QgsGeometry > asGeometries() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Implementation of QgsAbstractProfileGenerator for vector layers.
|
||||||
|
*
|
||||||
|
* \note Not available in Python bindings
|
||||||
|
* \ingroup core
|
||||||
|
* \since QGIS 3.26
|
||||||
|
*/
|
||||||
|
class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for QgsVectorLayerProfileGenerator.
|
||||||
|
*/
|
||||||
|
QgsVectorLayerProfileGenerator( QgsVectorLayer *layer, const QgsProfileRequest &request );
|
||||||
|
|
||||||
|
~QgsVectorLayerProfileGenerator() override;
|
||||||
|
|
||||||
|
bool generateProfile() override;
|
||||||
|
QgsAbstractProfileResults *takeResults() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
bool generateProfileForPoints();
|
||||||
|
|
||||||
|
double featureZToHeight( double x, double y, double z );
|
||||||
|
|
||||||
|
std::unique_ptr< QgsCurve > mProfileCurve;
|
||||||
|
std::unique_ptr< QgsAbstractTerrainProvider > mTerrainProvider;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsCurve > mTransformedCurve;
|
||||||
|
double mTolerance = 0;
|
||||||
|
|
||||||
|
|
||||||
|
QgsCoordinateReferenceSystem mSourceCrs;
|
||||||
|
QgsCoordinateReferenceSystem mTargetCrs;
|
||||||
|
QgsCoordinateTransformContext mTransformContext;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsVectorLayerFeatureSource > mSource;
|
||||||
|
|
||||||
|
double mOffset = 0;
|
||||||
|
double mScale = 1;
|
||||||
|
Qgis::AltitudeClamping mClamping = Qgis::AltitudeClamping::Terrain;
|
||||||
|
Qgis::AltitudeBinding mBinding = Qgis::AltitudeBinding::Centroid;
|
||||||
|
bool mExtrusionEnabled = false;
|
||||||
|
double mExtrusionHeight = 0;
|
||||||
|
|
||||||
|
QgsWkbTypes::Type mWkbType = QgsWkbTypes::Unknown;
|
||||||
|
QgsCoordinateTransform mLayerToTargetTransform;
|
||||||
|
QgsCoordinateTransform mTargetToTerrainProviderTransform;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsVectorLayerProfileResults > mResults;
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QGSVECTORLAYERPROFILEGENERATOR_H
|
||||||
@ -362,6 +362,7 @@ ADD_PYTHON_TEST(PyQgsVectorLayerCache test_qgsvectorlayercache.py)
|
|||||||
ADD_PYTHON_TEST(PyQgsVectorLayerEditBuffer test_qgsvectorlayereditbuffer.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerEditBuffer test_qgsvectorlayereditbuffer.py)
|
||||||
ADD_PYTHON_TEST(PyQgsVectorLayerEditBufferGroup test_qgsvectorlayereditbuffergroup.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerEditBufferGroup test_qgsvectorlayereditbuffergroup.py)
|
||||||
ADD_PYTHON_TEST(PyQgsVectorLayerNamedStyle test_qgsvectorlayer_namedstyle.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerNamedStyle test_qgsvectorlayer_namedstyle.py)
|
||||||
|
ADD_PYTHON_TEST(PyQgsVectorLayerProfileGenerator test_qgsvectorlayerprofilegenerator.py)
|
||||||
ADD_PYTHON_TEST(PyQgsVectorLayerRenderer test_qgsvectorlayerrenderer.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerRenderer test_qgsvectorlayerrenderer.py)
|
||||||
ADD_PYTHON_TEST(PyQgsVectorLayerSelectedFeatureSource test_qgsvectorlayerselectedfeaturesource.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerSelectedFeatureSource test_qgsvectorlayerselectedfeaturesource.py)
|
||||||
ADD_PYTHON_TEST(PyQgsVectorLayerShapefile test_qgsvectorlayershapefile.py)
|
ADD_PYTHON_TEST(PyQgsVectorLayerShapefile test_qgsvectorlayershapefile.py)
|
||||||
|
|||||||
@ -60,7 +60,8 @@ class TestQgsRasterLayerProfileGenerator(unittest.TestCase):
|
|||||||
generator = rl.createProfileGenerator(req)
|
generator = rl.createProfileGenerator(req)
|
||||||
self.assertTrue(generator.generateProfile())
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
results = {r.distance: r.height for r in generator.results()}
|
r = generator.takeResults()
|
||||||
|
results = r.distanceToHeightMap()
|
||||||
first_point = min(results.keys())
|
first_point = min(results.keys())
|
||||||
last_point = max(results.keys())
|
last_point = max(results.keys())
|
||||||
self.assertEqual(results[first_point], 154)
|
self.assertEqual(results[first_point], 154)
|
||||||
|
|||||||
231
tests/src/python/test_qgsvectorlayerprofilegenerator.py
Normal file
231
tests/src/python/test_qgsvectorlayerprofilegenerator.py
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""QGIS Unit tests for QgsVectorLayer profile generation
|
||||||
|
|
||||||
|
.. note:: This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
"""
|
||||||
|
__author__ = 'Nyall Dawson'
|
||||||
|
__date__ = '18/03/2022'
|
||||||
|
__copyright__ = 'Copyright 2022, The QGIS Project'
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import qgis # NOQA
|
||||||
|
from qgis.core import (
|
||||||
|
QgsRasterLayer,
|
||||||
|
QgsLineString,
|
||||||
|
QgsProfileRequest,
|
||||||
|
QgsCoordinateReferenceSystem,
|
||||||
|
QgsVectorLayer,
|
||||||
|
Qgis,
|
||||||
|
QgsRasterDemTerrainProvider,
|
||||||
|
QgsFeature,
|
||||||
|
QgsGeometry
|
||||||
|
)
|
||||||
|
from qgis.testing import start_app, unittest
|
||||||
|
|
||||||
|
from utilities import unitTestDataPath
|
||||||
|
|
||||||
|
start_app()
|
||||||
|
|
||||||
|
|
||||||
|
class TestQgsVectorLayerProfileGenerator(unittest.TestCase):
|
||||||
|
|
||||||
|
def testPointGenerationAbsolute(self):
|
||||||
|
vl = QgsVectorLayer(os.path.join(unitTestDataPath(), '3d', 'points_with_z.shp'), 'trees')
|
||||||
|
self.assertTrue(vl.isValid())
|
||||||
|
|
||||||
|
vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute)
|
||||||
|
vl.elevationProperties().setZScale(2.5)
|
||||||
|
vl.elevationProperties().setZOffset(10)
|
||||||
|
|
||||||
|
curve = QgsLineString()
|
||||||
|
curve.fromWkt(
|
||||||
|
'LineString (-347692.88994020794052631 6632796.97473032586276531, -346715.98066368483705446 6632458.84484416991472244, -346546.9152928851544857 6632444.29659010842442513, -346522.40419847227167338 6632307.79493184108287096)')
|
||||||
|
req = QgsProfileRequest(curve)
|
||||||
|
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertIsNotNone(generator)
|
||||||
|
# the request did not have the crs of the linestring set, so the whole linestring falls outside the vector extent
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# self.assertFalse(generator.generateProfile())
|
||||||
|
|
||||||
|
# set correct crs for linestring and re-try
|
||||||
|
req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
|
# no tolerance => no hits
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertFalse(results.distanceToHeightMap())
|
||||||
|
|
||||||
|
req.setTolerance(20)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
results = generator.takeResults()
|
||||||
|
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{167.60402625236236: 274.0, 22.897466329720825: 274.0, 1159.1281061593656: 232.75000000000003,
|
||||||
|
1171.6873910420782: 235.5, 1197.5553515228498: 241.0, 1245.704759018164: 227.25,
|
||||||
|
1291.0771889584548: 221.75})
|
||||||
|
|
||||||
|
# lower tolerance
|
||||||
|
req.setTolerance(4.5)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{167.60402625236236: 274.0, 1171.6873910420782: 235.5, 1197.5553515228498: 241.0})
|
||||||
|
|
||||||
|
def testPointGenerationTerrain(self):
|
||||||
|
"""
|
||||||
|
Points layer with terrain clamping
|
||||||
|
"""
|
||||||
|
vl = QgsVectorLayer(os.path.join(unitTestDataPath(), '3d', 'points_with_z.shp'), 'trees')
|
||||||
|
self.assertTrue(vl.isValid())
|
||||||
|
|
||||||
|
vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Terrain)
|
||||||
|
vl.elevationProperties().setZScale(2.5)
|
||||||
|
vl.elevationProperties().setZOffset(10)
|
||||||
|
|
||||||
|
rl = QgsRasterLayer(os.path.join(unitTestDataPath(), '3d', 'dtm.tif'), 'DTM')
|
||||||
|
self.assertTrue(rl.isValid())
|
||||||
|
|
||||||
|
curve = QgsLineString()
|
||||||
|
curve.fromWkt(
|
||||||
|
'LineString (-347692.88994020794052631 6632796.97473032586276531, -346715.98066368483705446 6632458.84484416991472244, -346546.9152928851544857 6632444.29659010842442513, -346522.40419847227167338 6632307.79493184108287096)')
|
||||||
|
req = QgsProfileRequest(curve)
|
||||||
|
terrain_provider = QgsRasterDemTerrainProvider()
|
||||||
|
terrain_provider.setLayer(rl)
|
||||||
|
terrain_provider.setScale(0.3)
|
||||||
|
terrain_provider.setOffset(-5)
|
||||||
|
|
||||||
|
req.setTerrainProvider(terrain_provider)
|
||||||
|
req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
|
# no tolerance => no hits
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertFalse(results.distanceToHeightMap())
|
||||||
|
|
||||||
|
req.setTolerance(4.5)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
results = generator.takeResults()
|
||||||
|
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{167.60402625236236: 69.5, 1171.6873910420782: 58.99999999999999, 1197.5553515228498: 60.5})
|
||||||
|
|
||||||
|
def testPointGenerationRelative(self):
|
||||||
|
"""
|
||||||
|
Points layer with relative clamping
|
||||||
|
"""
|
||||||
|
vl = QgsVectorLayer(os.path.join(unitTestDataPath(), '3d', 'points_with_z.shp'), 'trees')
|
||||||
|
self.assertTrue(vl.isValid())
|
||||||
|
|
||||||
|
vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Relative)
|
||||||
|
vl.elevationProperties().setZScale(2.5)
|
||||||
|
vl.elevationProperties().setZOffset(10)
|
||||||
|
|
||||||
|
rl = QgsRasterLayer(os.path.join(unitTestDataPath(), '3d', 'dtm.tif'), 'DTM')
|
||||||
|
self.assertTrue(rl.isValid())
|
||||||
|
|
||||||
|
curve = QgsLineString()
|
||||||
|
curve.fromWkt(
|
||||||
|
'LineString (-347692.88994020794052631 6632796.97473032586276531, -346715.98066368483705446 6632458.84484416991472244, -346546.9152928851544857 6632444.29659010842442513, -346522.40419847227167338 6632307.79493184108287096)')
|
||||||
|
req = QgsProfileRequest(curve)
|
||||||
|
terrain_provider = QgsRasterDemTerrainProvider()
|
||||||
|
terrain_provider.setLayer(rl)
|
||||||
|
terrain_provider.setScale(0.3)
|
||||||
|
terrain_provider.setOffset(-5)
|
||||||
|
|
||||||
|
req.setTerrainProvider(terrain_provider)
|
||||||
|
req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
|
# no tolerance => no hits
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertFalse(results.distanceToHeightMap())
|
||||||
|
|
||||||
|
req.setTolerance(4.5)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{167.60402625236236: 333.5, 1171.6873910420782: 284.5, 1197.5553515228498: 291.5})
|
||||||
|
|
||||||
|
def testPointGenerationRelativeExtrusion(self):
|
||||||
|
"""
|
||||||
|
Points layer with relative clamping and extrusion
|
||||||
|
"""
|
||||||
|
vl = QgsVectorLayer(os.path.join(unitTestDataPath(), '3d', 'points_with_z.shp'), 'trees')
|
||||||
|
self.assertTrue(vl.isValid())
|
||||||
|
|
||||||
|
vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Relative)
|
||||||
|
vl.elevationProperties().setZScale(2.5)
|
||||||
|
vl.elevationProperties().setZOffset(10)
|
||||||
|
vl.elevationProperties().setExtrusionEnabled(True)
|
||||||
|
vl.elevationProperties().setExtrusionHeight(7)
|
||||||
|
|
||||||
|
rl = QgsRasterLayer(os.path.join(unitTestDataPath(), '3d', 'dtm.tif'), 'DTM')
|
||||||
|
self.assertTrue(rl.isValid())
|
||||||
|
|
||||||
|
curve = QgsLineString()
|
||||||
|
curve.fromWkt(
|
||||||
|
'LineString (-347692.88994020794052631 6632796.97473032586276531, -346715.98066368483705446 6632458.84484416991472244, -346546.9152928851544857 6632444.29659010842442513, -346522.40419847227167338 6632307.79493184108287096)')
|
||||||
|
req = QgsProfileRequest(curve)
|
||||||
|
terrain_provider = QgsRasterDemTerrainProvider()
|
||||||
|
terrain_provider.setLayer(rl)
|
||||||
|
terrain_provider.setScale(0.3)
|
||||||
|
terrain_provider.setOffset(-5)
|
||||||
|
|
||||||
|
req.setTerrainProvider(terrain_provider)
|
||||||
|
req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
|
req.setTolerance(4.5)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{167.60402625236236: 333.5, 1171.6873910420782: 284.5, 1197.5553515228498: 291.5})
|
||||||
|
|
||||||
|
self.assertCountEqual([g.asWkt(1) for g in results.asGeometries()],
|
||||||
|
['LineStringZ (-347535.1 6632740.6 333.5, -347535.1 6632740.6 340.5)',
|
||||||
|
'LineStringZ (-346578.2 6632450.9 284.5, -346578.2 6632450.9 291.5)',
|
||||||
|
'LineStringZ (-346552.5 6632448.8 291.5, -346552.5 6632448.8 298.5)'])
|
||||||
|
|
||||||
|
def testPointGenerationMultiPoint(self):
|
||||||
|
vl = QgsVectorLayer('MultipointZ?crs=EPSG:27700', 'trees', 'memory')
|
||||||
|
self.assertTrue(vl.isValid())
|
||||||
|
|
||||||
|
f = QgsFeature()
|
||||||
|
f.setGeometry(QgsGeometry.fromWkt('MultiPointZ(322069 129893 89.1, 322077 129889 90.2, 322093 129888 92.4)'))
|
||||||
|
self.assertTrue(vl.dataProvider().addFeature(f))
|
||||||
|
|
||||||
|
vl.elevationProperties().setClamping(Qgis.AltitudeClamping.Absolute)
|
||||||
|
vl.elevationProperties().setZScale(2.5)
|
||||||
|
vl.elevationProperties().setZOffset(10)
|
||||||
|
|
||||||
|
curve = QgsLineString()
|
||||||
|
curve.fromWkt(
|
||||||
|
'LineString (-347692.88994020794052631 6632796.97473032586276531, -346715.98066368483705446 6632458.84484416991472244, -346546.9152928851544857 6632444.29659010842442513, -346522.40419847227167338 6632307.79493184108287096)')
|
||||||
|
req = QgsProfileRequest(curve)
|
||||||
|
req.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
|
||||||
|
req.setTolerance(20)
|
||||||
|
generator = vl.createProfileGenerator(req)
|
||||||
|
self.assertTrue(generator.generateProfile())
|
||||||
|
|
||||||
|
results = generator.takeResults()
|
||||||
|
self.assertEqual(results.distanceToHeightMap(),
|
||||||
|
{1158.036781085277: 232.75, 1171.3215208424097: 235.5, 1196.7677444714948: 241.0})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
Loading…
x
Reference in New Issue
Block a user