diff --git a/python/core/auto_generated/tiledmesh/qgstiledmeshboundingvolume.sip.in b/python/core/auto_generated/tiledmesh/qgstiledmeshboundingvolume.sip.in index 7bd6276fd19d..42a8cd26c9d6 100644 --- a/python/core/auto_generated/tiledmesh/qgstiledmeshboundingvolume.sip.in +++ b/python/core/auto_generated/tiledmesh/qgstiledmeshboundingvolume.sip.in @@ -55,6 +55,18 @@ Returns the axis aligned bounding box of the volume. virtual QgsAbstractTiledMeshNodeBoundingVolume *clone() const = 0 /Factory/; %Docstring Returns a clone of the volume. +%End + + virtual void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) throw( QgsCsException ) = 0; +%Docstring +Transforms the volume using the specified coordinate ``transform``. +%End + + virtual QgsAbstractGeometry *as2DGeometry() const = 0 /Factory/; +%Docstring +Return a new geometry representing the 2-dimensional X/Y projection of the volume. + +Caller takes ownership of the returned geometry. %End }; @@ -83,6 +95,10 @@ Constructor for QgsTiledMeshNodeBoundingVolumeRegion, with the specified ``regio virtual QgsTiledMeshNodeBoundingVolumeRegion *clone() const ${SIP_FINAL} /Factory/; + virtual QgsAbstractGeometry *as2DGeometry() const ${SIP_FINAL} /Factory/; + + virtual void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) ${SIP_FINAL} throw( QgsCsException ); + QgsBox3d region() const; %Docstring @@ -104,7 +120,7 @@ A oriented box bounding volume for tiled mesh nodes. %End public: - QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBoundingBox &box ); + QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBox3D &box ); %Docstring Constructor for QgsTiledMeshNodeBoundingVolumeBox, with the specified oriented ``box``. %End @@ -115,8 +131,12 @@ Constructor for QgsTiledMeshNodeBoundingVolumeBox, with the specified oriented ` virtual QgsTiledMeshNodeBoundingVolumeBox *clone() const ${SIP_FINAL} /Factory/; + virtual QgsAbstractGeometry *as2DGeometry() const ${SIP_FINAL} /Factory/; + + virtual void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) ${SIP_FINAL} throw( QgsCsException ); - QgsOrientedBoundingBox box() const; + + QgsOrientedBox3D box() const; %Docstring Returns the volume's oriented box. %End @@ -147,6 +167,10 @@ Constructor for :py:class:`QgsTiledMeshNodeBoundingVolumeBox`, with the specifie virtual QgsTiledMeshNodeBoundingVolumeSphere *clone() const ${SIP_FINAL} /Factory/; + virtual QgsAbstractGeometry *as2DGeometry() const ${SIP_FINAL} /Factory/; + + virtual void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) ${SIP_FINAL} throw( QgsCsException ); + QgsSphere sphere() const; %Docstring diff --git a/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp b/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp index 59ae7c5a2025..42a25b175c0c 100644 --- a/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp +++ b/src/core/tiledmesh/qgscesiumtilesdataprovider.cpp @@ -63,14 +63,8 @@ void QgsCesiumTilesDataProviderSharedData::setTilesetContent( const QString &til { mBoundingVolume = std::make_unique< QgsTiledMeshNodeBoundingVolumeRegion >( rootRegion ); mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); - // The latitude and longitude values are given in radians! - // TODO -- is this ALWAYS the case? What if there's a region root bounding volume, but a transform object present? What if there's crs metadata specifying a different crs? mCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) ); - const double xMin = rootRegion.xMinimum() * 180 / M_PI; - const double xMax = rootRegion.xMaximum() * 180 / M_PI; - const double yMin = rootRegion.yMinimum() * 180 / M_PI; - const double yMax = rootRegion.yMaximum() * 180 / M_PI; - mExtent = QgsRectangle( xMin, yMin, xMax, yMax ); + mExtent = rootRegion.toRectangle(); } } else if ( rootBoundingVolume.contains( "box" ) ) @@ -81,11 +75,8 @@ void QgsCesiumTilesDataProviderSharedData::setTilesetContent( const QString &til mBoundingVolume = std::make_unique< QgsTiledMeshNodeBoundingVolumeBox >( bbox ); const QgsBox3d rootRegion = bbox.extent(); mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); - const double xMin = rootRegion.xMinimum(); - const double xMax = rootRegion.xMaximum(); - const double yMin = rootRegion.yMinimum(); - const double yMax = rootRegion.yMaximum(); - mExtent = QgsRectangle( xMin, yMin, xMax, yMax ); + mExtent = rootRegion.toRectangle(); + mCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ); } } else if ( rootBoundingVolume.contains( "sphere" ) ) @@ -96,11 +87,12 @@ void QgsCesiumTilesDataProviderSharedData::setTilesetContent( const QString &til mBoundingVolume = std::make_unique< QgsTiledMeshNodeBoundingVolumeSphere >( sphere ); const QgsBox3d rootRegion = sphere.boundingBox(); mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() ); - const double xMin = rootRegion.xMinimum(); - const double xMax = rootRegion.xMaximum(); - const double yMin = rootRegion.yMinimum(); - const double yMax = rootRegion.yMaximum(); - mExtent = QgsRectangle( xMin, yMin, xMax, yMax ); + + // HMMM -- how to handle 4978 here? we need to use z coordinate for its transform to work correctly. + // Maybe we could say the layer is always in 4326?? + mExtent = rootRegion.toRectangle(); + + mCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ); } } else diff --git a/src/core/tiledmesh/qgscesiumutils.cpp b/src/core/tiledmesh/qgscesiumutils.cpp index c2c5b3d44aa7..b3aa60f982d5 100644 --- a/src/core/tiledmesh/qgscesiumutils.cpp +++ b/src/core/tiledmesh/qgscesiumutils.cpp @@ -26,12 +26,16 @@ QgsBox3d QgsCesiumUtils::parseRegion( const json ®ion ) { try { - const double west = region[0].get(); - const double south = region[1].get(); - const double east = region[2].get(); - const double north = region[3].get(); + // The latitude and longitude values are given in radians! + // TODO -- is this ALWAYS the case? What if there's a region root bounding volume, but a transform object present? What if there's crs metadata specifying a different crs? + + const double west = region[0].get() * 180 / M_PI; + const double south = region[1].get() * 180 / M_PI; + const double east = region[2].get() * 180 / M_PI; + const double north = region[3].get() * 180 / M_PI; double minHeight = region[4].get(); double maxHeight = region[5].get(); + return QgsBox3d( west, south, minHeight, east, north, maxHeight ); } catch ( nlohmann::json::exception & ) diff --git a/src/core/tiledmesh/qgstiledmeshboundingvolume.cpp b/src/core/tiledmesh/qgstiledmeshboundingvolume.cpp index 404535dc3669..8c69811f592e 100644 --- a/src/core/tiledmesh/qgstiledmeshboundingvolume.cpp +++ b/src/core/tiledmesh/qgstiledmeshboundingvolume.cpp @@ -16,6 +16,8 @@ ***************************************************************************/ #include "qgstiledmeshboundingvolume.h" +#include "qgscircle.h" +#include "qgscoordinatetransform.h" QgsAbstractTiledMeshNodeBoundingVolume::~QgsAbstractTiledMeshNodeBoundingVolume() = default; @@ -44,12 +46,36 @@ QgsTiledMeshNodeBoundingVolumeRegion *QgsTiledMeshNodeBoundingVolumeRegion::clon return new QgsTiledMeshNodeBoundingVolumeRegion( *this ); } +QgsAbstractGeometry *QgsTiledMeshNodeBoundingVolumeRegion::as2DGeometry() const +{ + std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString >( + QVector< double >() << mRegion.xMinimum() + << mRegion.xMaximum() + << mRegion.xMaximum() + << mRegion.xMinimum() + << mRegion.xMinimum(), + QVector< double >() << mRegion.yMinimum() + << mRegion.yMinimum() + << mRegion.yMaximum() + << mRegion.yMaximum() + << mRegion.yMinimum() ); + std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >(); + polygon->setExteriorRing( ext.release() ); + return polygon.release(); +} + +void QgsTiledMeshNodeBoundingVolumeRegion::transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) +{ + +} + + // // QgsTiledMeshNodeBoundingVolumeBox // -QgsTiledMeshNodeBoundingVolumeBox::QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBoundingBox &box ) +QgsTiledMeshNodeBoundingVolumeBox::QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBox3D &box ) : mBox( box ) { } @@ -69,6 +95,33 @@ QgsTiledMeshNodeBoundingVolumeBox *QgsTiledMeshNodeBoundingVolumeBox::clone() co return new QgsTiledMeshNodeBoundingVolumeBox( *this ); } +QgsAbstractGeometry *QgsTiledMeshNodeBoundingVolumeBox::as2DGeometry() const +{ + std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString >(); +#if 0 + QVector< double >() << mRegion.xMinimum() + << mRegion.xMaximum() + << mRegion.xMaximum() + << mRegion.xMinimum() + << mRegion.xMinimum(), + QVector< double >() << mRegion.yMinimum() + << mRegion.yMinimum() + << mRegion.yMaximum() + << mRegion.yMaximum() + << mRegion.yMinimum() ); +#endif + std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >(); + polygon->setExteriorRing( ext.release() ); + return polygon.release(); +} + +void QgsTiledMeshNodeBoundingVolumeBox::transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) +{ + +} + + + // // QgsTiledMeshNodeBoundingVolumeSphere // @@ -93,3 +146,23 @@ QgsTiledMeshNodeBoundingVolumeSphere *QgsTiledMeshNodeBoundingVolumeSphere::clon { return new QgsTiledMeshNodeBoundingVolumeSphere( *this ); } + +QgsAbstractGeometry *QgsTiledMeshNodeBoundingVolumeSphere::as2DGeometry() const +{ + std::unique_ptr< QgsCircularString > exterior( mSphere.toCircle().toCircularString() ); + std::unique_ptr< QgsCurvePolygon > polygon = std::make_unique< QgsCurvePolygon >(); + polygon->setExteriorRing( exterior.release() ); + polygon->addZValue( mSphere.centerZ() ); + return polygon.release(); +} + +void QgsTiledMeshNodeBoundingVolumeSphere::transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) +{ + // hmmm maybe the transform argument should be used by as2DGeometry...! + // other types won't be as easy to handle as spheres... + double x = mSphere.centerX(); + double y = mSphere.centerY(); + double z = mSphere.centerZ(); + transform.transformInPlace( x, y, z, direction ); + mSphere = QgsSphere( x, y, z, mSphere.radius() ); +} diff --git a/src/core/tiledmesh/qgstiledmeshboundingvolume.h b/src/core/tiledmesh/qgstiledmeshboundingvolume.h index 1bcaca2b0ff0..638affa7c293 100644 --- a/src/core/tiledmesh/qgstiledmeshboundingvolume.h +++ b/src/core/tiledmesh/qgstiledmeshboundingvolume.h @@ -24,7 +24,7 @@ #include "qgis.h" #include "qgsbox3d.h" #include "qgssphere.h" -#include "qgscesiumutils.h" +#include "qgsorientedbox3d.h" /** * \ingroup core @@ -73,6 +73,18 @@ class CORE_EXPORT QgsAbstractTiledMeshNodeBoundingVolume */ virtual QgsAbstractTiledMeshNodeBoundingVolume *clone() const = 0 SIP_FACTORY; + /** + * Transforms the volume using the specified coordinate \a transform. + */ + virtual void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) SIP_THROW( QgsCsException ) = 0; + + /** + * Return a new geometry representing the 2-dimensional X/Y projection of the volume. + * + * Caller takes ownership of the returned geometry. + */ + virtual QgsAbstractGeometry *as2DGeometry() const = 0 SIP_FACTORY; + }; /** @@ -93,6 +105,8 @@ class CORE_EXPORT QgsTiledMeshNodeBoundingVolumeRegion : public QgsAbstractTiled Qgis::TiledMeshBoundingVolumeType type() const FINAL; QgsBox3d bounds() const FINAL; QgsTiledMeshNodeBoundingVolumeRegion *clone() const FINAL SIP_FACTORY; + QgsAbstractGeometry *as2DGeometry() const FINAL SIP_FACTORY; + void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) FINAL SIP_THROW( QgsCsException ); /** * Returns the volume's region. @@ -116,20 +130,22 @@ class CORE_EXPORT QgsTiledMeshNodeBoundingVolumeBox : public QgsAbstractTiledMes /** * Constructor for QgsTiledMeshNodeBoundingVolumeBox, with the specified oriented \a box. */ - QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBoundingBox &box ); + QgsTiledMeshNodeBoundingVolumeBox( const QgsOrientedBox3D &box ); Qgis::TiledMeshBoundingVolumeType type() const FINAL; QgsBox3d bounds() const FINAL; QgsTiledMeshNodeBoundingVolumeBox *clone() const FINAL SIP_FACTORY; + QgsAbstractGeometry *as2DGeometry() const FINAL SIP_FACTORY; + void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) FINAL SIP_THROW( QgsCsException ); /** * Returns the volume's oriented box. */ - QgsOrientedBoundingBox box() const { return mBox; } + QgsOrientedBox3D box() const { return mBox; } private: - QgsOrientedBoundingBox mBox; + QgsOrientedBox3D mBox; }; @@ -151,6 +167,8 @@ class CORE_EXPORT QgsTiledMeshNodeBoundingVolumeSphere: public QgsAbstractTiledM Qgis::TiledMeshBoundingVolumeType type() const FINAL; QgsBox3d bounds() const FINAL; QgsTiledMeshNodeBoundingVolumeSphere *clone() const FINAL SIP_FACTORY; + QgsAbstractGeometry *as2DGeometry() const FINAL SIP_FACTORY; + void transform( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) FINAL SIP_THROW( QgsCsException ); /** * Returns the volume's sphere. diff --git a/src/core/tiledmesh/qgstiledmeshlayerrenderer.cpp b/src/core/tiledmesh/qgstiledmeshlayerrenderer.cpp index 2fb95161dac3..01ed9727da46 100644 --- a/src/core/tiledmesh/qgstiledmeshlayerrenderer.cpp +++ b/src/core/tiledmesh/qgstiledmeshlayerrenderer.cpp @@ -17,10 +17,14 @@ #include "qgstiledmeshlayerrenderer.h" +#include "qgscurve.h" +#include "qgstiledmeshboundingvolume.h" #include "qgstiledmeshlayer.h" #include "qgsfeedback.h" #include "qgsmapclippingutils.h" #include "qgsrendercontext.h" +#include "qgscurvepolygon.h" +#include "qgsfillsymbol.h" QgsTiledMeshLayerRenderer::QgsTiledMeshLayerRenderer( QgsTiledMeshLayer *layer, QgsRenderContext &context ) : QgsMapLayerRenderer( layer->id(), &context ) @@ -32,6 +36,8 @@ QgsTiledMeshLayerRenderer::QgsTiledMeshLayerRenderer( QgsTiledMeshLayer *layer, return; mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer ); + if ( const QgsAbstractTiledMeshNodeBoundingVolume *layerBoundingVolume = layer->dataProvider()->boundingVolume() ) + mLayerBoundingVolume.reset( layerBoundingVolume->clone() ); mReadyToCompose = false; } @@ -62,6 +68,43 @@ bool QgsTiledMeshLayerRenderer::render() mElapsedTimer.start(); } + if ( mLayerBoundingVolume ) + { + const QgsCoordinateTransform transform = renderContext()->coordinateTransform(); + try + { + mLayerBoundingVolume->transform( transform ); + std::unique_ptr< QgsAbstractGeometry > rootBoundsGeometry( mLayerBoundingVolume->as2DGeometry() ); + QgsDebugError( rootBoundsGeometry->asWkt( ) ); + if ( QgsCurvePolygon *polygon = qgsgeometry_cast< QgsCurvePolygon * >( rootBoundsGeometry.get() ) ) + { + QPolygonF rootBoundsPoly = polygon->exteriorRing()->asQPolygonF(); + + // remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors + rootBoundsPoly.erase( std::remove_if( rootBoundsPoly.begin(), rootBoundsPoly.end(), + []( const QPointF point ) + { + return !std::isfinite( point.x() ) || !std::isfinite( point.y() ); + } ), rootBoundsPoly.end() ); + + QPointF *ptr = rootBoundsPoly.data(); + for ( int i = 0; i < rootBoundsPoly.size(); ++i, ++ptr ) + { + renderContext()->mapToPixel().transformInPlace( ptr->rx(), ptr->ry() ); + } + + QgsFillSymbol fillSymbol; + fillSymbol.startRender( *renderContext() ); + fillSymbol.renderPolygon( rootBoundsPoly, nullptr, nullptr, *renderContext() ); + fillSymbol.stopRender( *renderContext() ); + } + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Error transforming root bounding volume" ) ); + } + } + mReadyToCompose = true; return false; } diff --git a/src/core/tiledmesh/qgstiledmeshlayerrenderer.h b/src/core/tiledmesh/qgstiledmeshlayerrenderer.h index 3eeba3ef414a..de76caeba8f0 100644 --- a/src/core/tiledmesh/qgstiledmeshlayerrenderer.h +++ b/src/core/tiledmesh/qgstiledmeshlayerrenderer.h @@ -20,6 +20,7 @@ #include "qgis_core.h" #include "qgsmaplayerrenderer.h" +#include "qgscoordinatereferencesystem.h" #include #include @@ -29,6 +30,8 @@ class QgsTiledMeshLayer; class QgsFeedback; class QgsMapClippingRegion; +class QgsAbstractTiledMeshNodeBoundingVolume; + /** * \ingroup core @@ -60,6 +63,8 @@ class CORE_EXPORT QgsTiledMeshLayerRenderer: public QgsMapLayerRenderer bool mBlockRenderUpdates = false; QElapsedTimer mElapsedTimer; + std::unique_ptr< QgsAbstractTiledMeshNodeBoundingVolume > mLayerBoundingVolume; + std::unique_ptr mFeedback = nullptr; };