mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-05 00:04:40 -05:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
c504439ebf
@ -21,8 +21,6 @@
|
||||
__author__ = 'Matthias Kuhn'
|
||||
__date__ = 'March 2017'
|
||||
__copyright__ = '(C) 2017, Matthias Kuhn'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
# This script parses output from ctest and injects
|
||||
#
|
||||
|
||||
2
.github/workflows/mingw-w64-msys2.yml
vendored
2
.github/workflows/mingw-w64-msys2.yml
vendored
@ -83,7 +83,7 @@ jobs:
|
||||
-DWITH_3D=ON \
|
||||
-DWITH_PDAL=OFF \
|
||||
-DWITH_CUSTOM_WIDGETS=ON \
|
||||
-DWITH_QWTPOLAR=ON \
|
||||
-DWITH_QWTPOLAR=OFF \
|
||||
-DWITH_BINDINGS=OFF \
|
||||
-DWITH_GRASS=OFF \
|
||||
-DWITH_DRACO=OFF \
|
||||
|
||||
@ -118,7 +118,7 @@ def register_function(
|
||||
:param args: DEPRECATED since QGIS 3.32. Use ``params_as_list`` if you want to pass parameters as a list.
|
||||
:param group: the expression group in which the function should be added
|
||||
:param usesgeometry: Defines if this expression requires the geometry. By default False.
|
||||
:param referenced_columns: An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to None for slightly faster evaluation.
|
||||
:param referenced_columns: An array of names of fields on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Specifying a subset of fields or an empty list will result in a faster execution.
|
||||
:param handlesnull: Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default.
|
||||
:param params_as_list: If True, the function will receive the expression parameters as a list. If False, the function will receive the parameters as individual arguments. False by default.
|
||||
|
||||
@ -185,7 +185,7 @@ def qgsfunction(args="auto", group="custom", **kwargs):
|
||||
* *usesgeometry* (``bool``) --
|
||||
Defines if this expression requires the geometry. By default False.
|
||||
* *referenced_columns* (``list``) --
|
||||
An array of field names on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Can be set to None for slightly faster evaluation.
|
||||
An array of names of fields on which this expression works. By default ``[QgsFeatureRequest.ALL_ATTRIBUTES]``. Specifying a subset of fields or an empty list will result in a faster execution.
|
||||
* *handlesnull* (``bool``) --
|
||||
Defines if this expression has custom handling for NULL values. If False, the result will always be NULL as soon as any parameter is NULL. False by default.
|
||||
* *params_as_list* (``bool``) \since QGIS 3.32 --
|
||||
|
||||
@ -2542,7 +2542,10 @@ QgsCurve.Clockwise.__doc__ = "Clockwise direction"
|
||||
QgsCurve.CounterClockwise = Qgis.AngularDirection.CounterClockwise
|
||||
QgsCurve.CounterClockwise.is_monkey_patched = True
|
||||
QgsCurve.CounterClockwise.__doc__ = "Counter-clockwise direction"
|
||||
Qgis.AngularDirection.__doc__ = "Angular directions.\n\n.. versionadded:: 3.24\n\n" + '* ``Clockwise``: ' + Qgis.AngularDirection.Clockwise.__doc__ + '\n' + '* ``CounterClockwise``: ' + Qgis.AngularDirection.CounterClockwise.__doc__
|
||||
QgsCurve.NoOrientation = Qgis.AngularDirection.NoOrientation
|
||||
QgsCurve.NoOrientation.is_monkey_patched = True
|
||||
QgsCurve.NoOrientation.__doc__ = "Unknown orientation or sentinel value"
|
||||
Qgis.AngularDirection.__doc__ = "Angular directions.\n\n.. versionadded:: 3.24\n\n" + '* ``Clockwise``: ' + Qgis.AngularDirection.Clockwise.__doc__ + '\n' + '* ``CounterClockwise``: ' + Qgis.AngularDirection.CounterClockwise.__doc__ + '\n' + '* ``NoOrientation``: ' + Qgis.AngularDirection.NoOrientation.__doc__
|
||||
# --
|
||||
Qgis.AngularDirection.baseClass = Qgis
|
||||
# monkey patching scoped based enum
|
||||
|
||||
@ -2549,6 +2549,69 @@ They require builds based on GEOS 3.10 or later.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
Qgis::AngularDirection polygonOrientation() const;
|
||||
%Docstring
|
||||
Returns the orientation of the polygon.
|
||||
|
||||
.. warning::
|
||||
|
||||
Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
you have to perform in deep verification.
|
||||
|
||||
.. warning::
|
||||
|
||||
returns :py:class:`Qgis`.AngularDirection.NoOrientation if the geometry is not a polygon type or empty
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
bool isPolygonCounterClockwise() const;
|
||||
%Docstring
|
||||
Returns True if the Polygon is counter-clockwise.
|
||||
|
||||
.. warning::
|
||||
|
||||
Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
you have to perform in deep verification.
|
||||
|
||||
.. warning::
|
||||
|
||||
returns false if the geometry is not a polygon type or empty
|
||||
|
||||
|
||||
.. seealso:: :py:func:`isPolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonCounterClockwise`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
bool isPolygonClockwise() const;
|
||||
%Docstring
|
||||
Returns True if the Polygon is clockwise.
|
||||
|
||||
.. warning::
|
||||
|
||||
Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
you have to perform in deep verification.
|
||||
|
||||
.. warning::
|
||||
|
||||
returns true if the geometry is not a polygon type or empty
|
||||
|
||||
|
||||
.. seealso:: :py:func:`isPolygonCounterClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonCounterClockwise`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
|
||||
QgsGeometry forceRHR() const;
|
||||
%Docstring
|
||||
Forces geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon
|
||||
@ -2560,6 +2623,10 @@ and the interior rings in a counter-clockwise direction.
|
||||
Due to the conflicting definitions of the right-hand-rule in general use, it is recommended
|
||||
to use the explicit :py:func:`~QgsGeometry.forcePolygonClockwise` or :py:func:`~QgsGeometry.forcePolygonCounterClockwise` methods instead.
|
||||
|
||||
.. seealso:: :py:func:`isPolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`isPolygonCounterClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonCounterClockwise`
|
||||
@ -2573,6 +2640,10 @@ Forces geometries to respect the exterior ring is clockwise, interior rings are
|
||||
|
||||
This convention is used primarily by ESRI software.
|
||||
|
||||
.. seealso:: :py:func:`isPolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`isPolygonCounterClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonCounterClockwise`
|
||||
|
||||
.. versionadded:: 3.24
|
||||
@ -2584,6 +2655,10 @@ Forces geometries to respect the exterior ring is counter-clockwise, interior ri
|
||||
|
||||
This convention matches the OGC Simple Features specification.
|
||||
|
||||
.. seealso:: :py:func:`isPolygonClockwise`
|
||||
|
||||
.. seealso:: :py:func:`isPolygonCounterClockwise`
|
||||
|
||||
.. seealso:: :py:func:`forcePolygonClockwise`
|
||||
|
||||
.. versionadded:: 3.24
|
||||
|
||||
@ -424,8 +424,8 @@ angular deviation (in radians) allowed when testing for regular point spacing.
|
||||
|
||||
static int segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 ) /HoldGIL/;
|
||||
%Docstring
|
||||
For line defined by points pt1 and pt3, find out on which side of the line is point pt3.
|
||||
Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line.
|
||||
For line defined by points pt1 and pt3, find out on which side of the line is point pt2.
|
||||
Returns -1 if pt2 on the left side, 1 if pt2 is on the right side or 0 if pt2 lies on the line.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
@ -157,6 +157,7 @@ Returns the feedback object used to cancel rendering
|
||||
%End
|
||||
|
||||
|
||||
|
||||
private:
|
||||
QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh );
|
||||
};
|
||||
@ -436,6 +437,110 @@ Sets the ``unit`` for the maximum screen error allowed when rendering the point
|
||||
.. seealso:: :py:func:`setMaximumScreenError`
|
||||
|
||||
.. seealso:: :py:func:`maximumScreenErrorUnit`
|
||||
%End
|
||||
|
||||
bool renderAsTriangles() const;
|
||||
%Docstring
|
||||
Returns whether points are triangulated to render solid surface
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void setRenderAsTriangles( bool asTriangles );
|
||||
%Docstring
|
||||
Sets whether points are triangulated to render solid surface
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
bool horizontalTriangleFilter() const;
|
||||
%Docstring
|
||||
Returns whether large triangles will get rendered. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles`
|
||||
is enabled. When the triangle filtering is enabled, triangles where at least one side is
|
||||
horizontally longer than the threshold in :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterThreshold` do not get rendered.
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterThreshold`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterUnit`
|
||||
|
||||
.. seealso:: :py:func:`setHorizontalTriangleFilter`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void setHorizontalTriangleFilter( bool enabled );
|
||||
%Docstring
|
||||
Sets whether large triangles will get rendered. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles`
|
||||
is enabled. When the triangle filtering is enabled, triangles where at least one side is
|
||||
horizontally longer than the threshold in :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterThreshold` do not get rendered.
|
||||
|
||||
.. seealso:: :py:func:`setHorizontalTriangleFilterThreshold`
|
||||
|
||||
.. seealso:: :py:func:`setHorizontalTriangleFilterUnit`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilter`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
double horizontalTriangleFilterThreshold() const;
|
||||
%Docstring
|
||||
Returns threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and
|
||||
:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer
|
||||
than the threshold, such triangle will not get rendered. Units of the threshold value are
|
||||
given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`.
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilter`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterUnit`
|
||||
|
||||
.. seealso:: :py:func:`setHorizontalTriangleFilterThreshold`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void setHorizontalTriangleFilterThreshold( double threshold );
|
||||
%Docstring
|
||||
Sets threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and
|
||||
:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled. If any edge of a triangle is horizontally longer
|
||||
than the threshold, such triangle will not get rendered. Units of the threshold value are
|
||||
given by :py:func:`~QgsPointCloudRenderer.horizontalTriangleFilterUnits`.
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilter`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterUnit`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterThreshold`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
Qgis::RenderUnit horizontalTriangleFilterUnit() const;
|
||||
%Docstring
|
||||
Returns units of the threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and
|
||||
:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled.
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilter`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterThreshold`
|
||||
|
||||
.. seealso:: :py:func:`setHorizontalTriangleFilterUnit`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void setHorizontalTriangleFilterUnit( Qgis::RenderUnit unit );
|
||||
%Docstring
|
||||
Sets units of the threshold for filtering of triangles. This only applies when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` and
|
||||
:py:func:`~QgsPointCloudRenderer.horizontalTriangleFilter` are both enabled.
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilter`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterThreshold`
|
||||
|
||||
.. seealso:: :py:func:`horizontalTriangleFilterUnit`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
virtual QList<QgsLayerTreeModelLegendNode *> createLegendNodes( QgsLayerTreeLayer *nodeLayer ) /Factory/;
|
||||
@ -466,6 +571,13 @@ Draws a point using a ``color`` at the specified ``x`` and ``y`` (in map coordin
|
||||
%End
|
||||
|
||||
|
||||
void addPointToTriangulation( double x, double y, double z, const QColor &color, QgsPointCloudRenderContext &context );
|
||||
%Docstring
|
||||
Adds a point to the list of points to be triangulated (only used when :py:func:`~QgsPointCloudRenderer.renderAsTriangles` is enabled)
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void copyCommonProperties( QgsPointCloudRenderer *destination ) const;
|
||||
%Docstring
|
||||
Copies common point cloud properties (such as point size and screen error) to the ``destination`` renderer.
|
||||
|
||||
@ -197,6 +197,14 @@ Returns the extent of the layer
|
||||
:return: :py:class:`QgsRectangle` containing the extent of the layer
|
||||
%End
|
||||
|
||||
virtual QgsBox3D extent3D() const;
|
||||
%Docstring
|
||||
Returns the 3D extent of the layer
|
||||
|
||||
:return: :py:class:`QgsBox3D` containing the 3D extent of the layer
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
virtual bool isValid() const = 0;
|
||||
%Docstring
|
||||
|
||||
@ -1508,6 +1508,7 @@ The development version
|
||||
{
|
||||
Clockwise,
|
||||
CounterClockwise,
|
||||
NoOrientation,
|
||||
};
|
||||
|
||||
enum class RendererUsage
|
||||
|
||||
@ -36,9 +36,35 @@ Base class for handling elevation related properties for a data provider.
|
||||
QgsDataProviderElevationProperties();
|
||||
%Docstring
|
||||
Constructor for QgsDataProviderElevationProperties.
|
||||
%End
|
||||
|
||||
virtual bool containsElevationData() const;
|
||||
%Docstring
|
||||
Returns ``True`` if the data provider definitely contains elevation related data.
|
||||
|
||||
.. note::
|
||||
|
||||
Even if this method returns ``False``, the data may still relate to elevation values. ``True`` will only
|
||||
be returned in situations where elevation data is definitively present.
|
||||
|
||||
.. seealso:: :py:func:`setContainsElevationData`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
virtual void setContainsElevationData( bool contains );
|
||||
%Docstring
|
||||
Sets whether the data provider definitely contains elevation related data.
|
||||
|
||||
.. seealso:: :py:func:`containsElevationData`
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
virtual ~QgsDataProviderElevationProperties();
|
||||
|
||||
protected:
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
||||
@ -99,6 +99,7 @@ with the unit of the encoded elevation in this elevation map.
|
||||
Returns raw elevation image with elevations encoded as color values
|
||||
%End
|
||||
|
||||
|
||||
QPainter *painter() const;
|
||||
%Docstring
|
||||
Returns painter to the underlying QImage with elevations
|
||||
|
||||
@ -127,6 +127,15 @@ all features in the source.
|
||||
Returns the extent of all geometries from the source.
|
||||
The base class implementation uses a non-optimised approach of looping through
|
||||
all features in the source.
|
||||
%End
|
||||
|
||||
virtual QgsBox3D sourceExtent3D() const;
|
||||
%Docstring
|
||||
Returns the 3D extent of all geometries from the source.
|
||||
The base class implementation uses a non-optimised approach of looping through
|
||||
all features in the source.
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
virtual QgsFeatureIds allFeatureIds() const;
|
||||
|
||||
@ -520,6 +520,13 @@ Returns new instance of :py:class:`QgsMapLayerRenderer` that will be used for re
|
||||
virtual QgsRectangle extent() const;
|
||||
%Docstring
|
||||
Returns the extent of the layer.
|
||||
%End
|
||||
|
||||
virtual QgsBox3D extent3D() const;
|
||||
%Docstring
|
||||
Returns the 3D extent of the layer.
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
QgsRectangle wgs84Extent( bool forceRecalculate = false ) const;
|
||||
@ -2117,6 +2124,13 @@ Copies attributes like name, short name, ... into another layer.
|
||||
virtual void setExtent( const QgsRectangle &rect );
|
||||
%Docstring
|
||||
Sets the extent
|
||||
%End
|
||||
|
||||
virtual void setExtent3D( const QgsBox3D &box );
|
||||
%Docstring
|
||||
Sets the extent
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
void setValid( bool valid );
|
||||
|
||||
@ -37,6 +37,17 @@ Decodes a distance unit from a DOM element.
|
||||
|
||||
static QgsRectangle readRectangle( const QDomElement &element );
|
||||
|
||||
static QgsBox3D readBox3D( const QDomElement &element );
|
||||
%Docstring
|
||||
Decodes a DOM element to a 3D box.
|
||||
|
||||
:param element: DOM document
|
||||
|
||||
:return: decoded 3D box
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
|
||||
static QDomElement writeMapUnits( Qgis::DistanceUnit units, QDomDocument &doc );
|
||||
%Docstring
|
||||
@ -48,6 +59,19 @@ Encodes a distance unit to a DOM element.
|
||||
:return: element containing encoded units
|
||||
|
||||
.. seealso:: :py:func:`readMapUnits`
|
||||
%End
|
||||
|
||||
static QDomElement writeBox3D( const QgsBox3D &box, QDomDocument &doc, const QString &elementName = QStringLiteral( "extent3D" ) );
|
||||
%Docstring
|
||||
Encodes a 3D box to a DOM element.
|
||||
|
||||
:param box: 3D box to encode
|
||||
:param doc: DOM document
|
||||
:param elementName: name of the DOM element
|
||||
|
||||
:return: element containing encoded 3D box
|
||||
|
||||
.. versionadded:: 3.36
|
||||
%End
|
||||
|
||||
static QDomElement writeRectangle( const QgsRectangle &rect, QDomDocument &doc, const QString &elementName = QStringLiteral( "extent" ) );
|
||||
|
||||
@ -26,25 +26,6 @@ Handles elevation related properties for a raster data provider.
|
||||
QgsRasterDataProviderElevationProperties();
|
||||
%Docstring
|
||||
Constructor for QgsRasterDataProviderElevationProperties.
|
||||
%End
|
||||
|
||||
bool containsElevationData() const;
|
||||
%Docstring
|
||||
Returns ``True`` if the raster data provider definitely contains elevation related data.
|
||||
|
||||
.. note::
|
||||
|
||||
Even if this method returns ``False``, the raster data may still relate to elevation values. ``True`` will only
|
||||
be returned in situations where elevation data is definitively present.
|
||||
|
||||
.. seealso:: :py:func:`setContainsElevationData`
|
||||
%End
|
||||
|
||||
void setContainsElevationData( bool contains );
|
||||
%Docstring
|
||||
Sets whether the raster data provider definitely contains elevation related data.
|
||||
|
||||
.. seealso:: :py:func:`containsElevationData`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
@ -175,6 +175,8 @@ Returns the fields associated with this data provider.
|
||||
|
||||
virtual QgsRectangle sourceExtent() const;
|
||||
|
||||
virtual QgsBox3D sourceExtent3D() const;
|
||||
|
||||
virtual QString sourceName() const;
|
||||
|
||||
virtual QString dataComment() const;
|
||||
@ -654,6 +656,9 @@ from the ``source`` provider.
|
||||
virtual QgsVectorDataProviderTemporalCapabilities *temporalCapabilities();
|
||||
|
||||
|
||||
virtual QgsDataProviderElevationProperties *elevationProperties();
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
void raiseError( const QString &msg ) const;
|
||||
|
||||
@ -1745,6 +1745,11 @@ Returns new instance of :py:class:`QgsMapLayerRenderer` that will be used for re
|
||||
virtual QgsRectangle sourceExtent() const ${SIP_FINAL};
|
||||
|
||||
|
||||
virtual QgsBox3D extent3D() const ${SIP_FINAL};
|
||||
|
||||
virtual QgsBox3D sourceExtent3D() const ${SIP_FINAL};
|
||||
|
||||
|
||||
virtual QgsFields fields() const ${SIP_FINAL};
|
||||
|
||||
%Docstring
|
||||
@ -2993,6 +2998,11 @@ Emitted when the feature count for symbols on this layer has been recalculated.
|
||||
protected:
|
||||
virtual void setExtent( const QgsRectangle &rect ) ${SIP_FINAL};
|
||||
|
||||
%Docstring
|
||||
Sets the extent
|
||||
%End
|
||||
virtual void setExtent3D( const QgsBox3D &rect ) ${SIP_FINAL};
|
||||
|
||||
%Docstring
|
||||
Sets the extent
|
||||
%End
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
class QgsQmlWidgetWrapper : QgsWidgetWrapper
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
|
||||
@ -20,7 +20,6 @@ __date__ = 'May 2017'
|
||||
__copyright__ = '(C) 2017, Sandro Santilli'
|
||||
|
||||
import os
|
||||
import qgis
|
||||
import unittest
|
||||
from qgis.testing import start_app, QgisTestCase
|
||||
from qgis.core import QgsDataSourceUri
|
||||
|
||||
@ -21,7 +21,6 @@ __copyright__ = '(C) 2017, Sandro Santilli'
|
||||
|
||||
import os
|
||||
import re
|
||||
import qgis
|
||||
import unittest
|
||||
from qgis.testing import start_app, QgisTestCase
|
||||
from qgis.core import QgsDataSourceUri
|
||||
|
||||
@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn'
|
||||
__date__ = 'January 2016'
|
||||
__copyright__ = '(C) 2016, Matthias Kuhn'
|
||||
|
||||
import qgis # NOQA switch sip api
|
||||
|
||||
import os
|
||||
import yaml
|
||||
|
||||
@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn'
|
||||
__date__ = 'January 2016'
|
||||
__copyright__ = '(C) 2016, Matthias Kuhn'
|
||||
|
||||
import qgis # NOQA switch sip api
|
||||
|
||||
import os
|
||||
import yaml
|
||||
|
||||
@ -89,10 +89,10 @@ class buildvrt(GdalAlgorithm):
|
||||
return True, ''
|
||||
|
||||
self.RESAMPLING_OPTIONS = ((self.tr('Nearest Neighbour'), 'nearest'),
|
||||
(self.tr('Bilinear'), 'bilinear'),
|
||||
(self.tr('Cubic Convolution'), 'cubic'),
|
||||
(self.tr('B-Spline Convolution'), 'cubicspline'),
|
||||
(self.tr('Lanczos Windowed Sinc'), 'lanczos'),
|
||||
(self.tr('Bilinear (2x2 Kernel)'), 'bilinear'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),
|
||||
(self.tr('Average'), 'average'),
|
||||
(self.tr('Mode'), 'mode'))
|
||||
|
||||
|
||||
@ -61,11 +61,11 @@ class gdal2tiles(GdalAlgorithm):
|
||||
(self.tr('Raster'), 'raster'))
|
||||
|
||||
self.methods = ((self.tr('Average'), 'average'),
|
||||
(self.tr('Nearest neighbour'), 'near'),
|
||||
(self.tr('Bilinear'), 'bilinear'),
|
||||
(self.tr('Cubic'), 'cubic'),
|
||||
(self.tr('Cubic spline'), 'cubicspline'),
|
||||
(self.tr('Lanczos windowed sinc'), 'lanczos'),
|
||||
(self.tr('Nearest Neighbour'), 'near'),
|
||||
(self.tr('Bilinear (2x2 Kernel)'), 'bilinear'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),
|
||||
(self.tr('Antialias'), 'antialias'))
|
||||
|
||||
self.viewers = ((self.tr('All'), 'all'),
|
||||
|
||||
@ -52,9 +52,9 @@ class gdaladdo(GdalAlgorithm):
|
||||
self.methods = ((self.tr('Nearest Neighbour (default)'), 'nearest'),
|
||||
(self.tr('Average'), 'average'),
|
||||
(self.tr('Gaussian'), 'gauss'),
|
||||
(self.tr('Cubic Convolution'), 'cubic'),
|
||||
(self.tr('B-Spline Convolution'), 'cubicspline'),
|
||||
(self.tr('Lanczos Windowed Sinc'), 'lanczos'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),
|
||||
(self.tr('Average MP'), 'average_mp'),
|
||||
(self.tr('Average in Mag/Phase Space'), 'average_magphase'),
|
||||
(self.tr('Mode'), 'mode'))
|
||||
|
||||
@ -51,10 +51,10 @@ class pansharp(GdalAlgorithm):
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.methods = ((self.tr('Nearest Neighbour'), 'nearest'),
|
||||
(self.tr('Bilinear'), 'bilinear'),
|
||||
(self.tr('Cubic'), 'cubic'),
|
||||
(self.tr('Cubic Spline'), 'cubicspline'),
|
||||
(self.tr('Lanczos Windowed Sinc'), 'lanczos'),
|
||||
(self.tr('Bilinear (2x2 Kernel)'), 'bilinear'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),
|
||||
(self.tr('Average'), 'average'))
|
||||
|
||||
self.addParameter(QgsProcessingParameterRasterLayer(self.SPECTRAL,
|
||||
|
||||
@ -61,10 +61,10 @@ class retile(GdalAlgorithm):
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.methods = ((self.tr('Nearest Neighbour'), 'near'),
|
||||
(self.tr('Bilinear'), 'bilinear'),
|
||||
(self.tr('Cubic'), 'cubic'),
|
||||
(self.tr('Cubic Spline'), 'cubicspline'),
|
||||
(self.tr('Lanczos Windowed Sinc'), 'lanczos'),)
|
||||
(self.tr('Bilinear (2x2 Kernel)'), 'bilinear'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),)
|
||||
|
||||
self.addParameter(QgsProcessingParameterMultipleLayers(self.INPUT,
|
||||
self.tr('Input files'),
|
||||
|
||||
@ -61,17 +61,17 @@ class warp(GdalAlgorithm):
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.methods = ((self.tr('Nearest Neighbour'), 'near'),
|
||||
(self.tr('Bilinear'), 'bilinear'),
|
||||
(self.tr('Cubic'), 'cubic'),
|
||||
(self.tr('Cubic Spline'), 'cubicspline'),
|
||||
(self.tr('Lanczos Windowed Sinc'), 'lanczos'),
|
||||
(self.tr('Bilinear (2x2 Kernel)'), 'bilinear'),
|
||||
(self.tr('Cubic (4x4 Kernel)'), 'cubic'),
|
||||
(self.tr('Cubic B-Spline (4x4 Kernel)'), 'cubicspline'),
|
||||
(self.tr('Lanczos (6x6 Kernel)'), 'lanczos'),
|
||||
(self.tr('Average'), 'average'),
|
||||
(self.tr('Mode'), 'mode'),
|
||||
(self.tr('Maximum'), 'max'),
|
||||
(self.tr('Minimum'), 'min'),
|
||||
(self.tr('Median'), 'med'),
|
||||
(self.tr('First Quartile'), 'q1'),
|
||||
(self.tr('Third Quartile'), 'q3'))
|
||||
(self.tr('First Quartile (Q1)'), 'q1'),
|
||||
(self.tr('Third Quartile (Q3)'), 'q3'))
|
||||
|
||||
self.TYPES = [self.tr('Use Input Layer Data Type'), 'Byte', 'Int16', 'UInt16', 'UInt32', 'Int32', 'Float32', 'Float64', 'CInt16', 'CInt32', 'CFloat32', 'CFloat64', 'Int8']
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ __author__ = 'Matthias Kuhn'
|
||||
__date__ = 'January 2016'
|
||||
__copyright__ = '(C) 2016, Matthias Kuhn'
|
||||
|
||||
import qgis # NOQA switch sip api
|
||||
|
||||
import os
|
||||
import yaml
|
||||
|
||||
@ -42,11 +42,20 @@ from qgis.PyQt.QtWidgets import (
|
||||
)
|
||||
from qgis.PyQt.QtNetwork import QNetworkRequest
|
||||
|
||||
import qgis
|
||||
from qgis.core import Qgis, QgsApplication, QgsMessageLog, QgsNetworkAccessManager, QgsSettings, QgsSettingsTree, QgsNetworkRequestParameters
|
||||
from qgis.gui import QgsMessageBar, QgsPasswordLineEdit, QgsHelp
|
||||
from qgis.utils import (iface, startPlugin, unloadPlugin, loadPlugin, OverrideCursor,
|
||||
reloadPlugin, updateAvailablePlugins, plugins_metadata_parser, isPluginLoaded)
|
||||
from qgis.utils import (
|
||||
iface,
|
||||
startPlugin,
|
||||
unloadPlugin,
|
||||
loadPlugin,
|
||||
OverrideCursor,
|
||||
reloadPlugin,
|
||||
updateAvailablePlugins,
|
||||
plugins_metadata_parser,
|
||||
isPluginLoaded,
|
||||
HOME_PLUGIN_PATH
|
||||
)
|
||||
from .installer_data import (repositories, plugins, officialRepo,
|
||||
reposGroup, removeDir)
|
||||
from .qgsplugininstallerinstallingdialog import QgsPluginInstallerInstallingDialog
|
||||
@ -323,7 +332,7 @@ class QgsPluginInstaller(QObject):
|
||||
dlg = QgsPluginInstallerInstallingDialog(iface.mainWindow(), plugin, stable=stable)
|
||||
dlg.exec_()
|
||||
|
||||
plugin_path = qgis.utils.home_plugin_path + "/" + key
|
||||
plugin_path = HOME_PLUGIN_PATH + "/" + key
|
||||
if dlg.result():
|
||||
error = True
|
||||
infoString = (self.tr("Plugin installation failed"), dlg.result())
|
||||
@ -387,7 +396,7 @@ class QgsPluginInstaller(QObject):
|
||||
dlg.exec_()
|
||||
if dlg.result():
|
||||
# revert installation
|
||||
pluginDir = qgis.utils.home_plugin_path + "/" + plugin["id"]
|
||||
pluginDir = HOME_PLUGIN_PATH + "/" + plugin["id"]
|
||||
result = removeDir(pluginDir)
|
||||
if QDir(pluginDir).exists():
|
||||
error = True
|
||||
@ -436,7 +445,7 @@ class QgsPluginInstaller(QObject):
|
||||
unloadPlugin(key)
|
||||
except:
|
||||
pass
|
||||
pluginDir = qgis.utils.home_plugin_path + "/" + plugin["id"]
|
||||
pluginDir = HOME_PLUGIN_PATH + "/" + plugin["id"]
|
||||
result = removeDir(pluginDir)
|
||||
if result:
|
||||
QApplication.restoreOverrideCursor()
|
||||
@ -612,7 +621,7 @@ class QgsPluginInstaller(QObject):
|
||||
QgsHelp.openHelp("plugins/plugins.html#the-install-from-zip-tab")
|
||||
return
|
||||
|
||||
pluginsDirectory = qgis.utils.home_plugin_path
|
||||
pluginsDirectory = HOME_PLUGIN_PATH
|
||||
if not QDir(pluginsDirectory).exists():
|
||||
QDir().mkpath(pluginsDirectory)
|
||||
|
||||
|
||||
@ -42,7 +42,11 @@ import configparser
|
||||
import qgis.utils
|
||||
from qgis.core import QgsNetworkAccessManager, QgsApplication
|
||||
from qgis.gui import QgsGui
|
||||
from qgis.utils import iface, plugin_paths
|
||||
from qgis.utils import (
|
||||
iface,
|
||||
plugin_paths,
|
||||
HOME_PLUGIN_PATH
|
||||
)
|
||||
from .version_compare import pyQgisVersion, compareVersions, normalizeVersion, isCompatible
|
||||
|
||||
|
||||
@ -133,7 +137,7 @@ def removeDir(path):
|
||||
if QFile(path).exists():
|
||||
result = QCoreApplication.translate("QgsPluginInstaller", "Failed to remove the directory:") + "\n" + path + "\n" + QCoreApplication.translate("QgsPluginInstaller", "Check permissions or remove it manually")
|
||||
# restore plugin directory if removed by QDir().rmpath()
|
||||
pluginDir = qgis.utils.home_plugin_path
|
||||
pluginDir = HOME_PLUGIN_PATH
|
||||
if not QDir(pluginDir).exists():
|
||||
QDir().mkpath(pluginDir)
|
||||
return result
|
||||
|
||||
@ -29,8 +29,8 @@ from qgis.PyQt.QtCore import QDir, QUrl, QFile, QCoreApplication
|
||||
from qgis.PyQt.QtWidgets import QDialog
|
||||
from qgis.PyQt.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
import qgis
|
||||
from qgis.core import QgsNetworkAccessManager, QgsApplication, QgsNetworkRequestParameters
|
||||
from qgis.utils import HOME_PLUGIN_PATH
|
||||
|
||||
from .ui_qgsplugininstallerinstallingbase import Ui_QgsPluginInstallerInstallingDialogBase
|
||||
from .installer_data import removeDir, repositories
|
||||
@ -142,7 +142,7 @@ class QgsPluginInstallerInstallingDialog(QDialog, Ui_QgsPluginInstallerInstallin
|
||||
self.file.close()
|
||||
self.stateChanged(0)
|
||||
reply.deleteLater()
|
||||
pluginDir = qgis.utils.home_plugin_path
|
||||
pluginDir = HOME_PLUGIN_PATH
|
||||
tmpPath = self.file.fileName()
|
||||
# make sure that the parent directory exists
|
||||
if not QDir(pluginDir).exists():
|
||||
|
||||
@ -73,11 +73,14 @@ class QgisTestCase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.report = ''
|
||||
cls.markdown_report = ''
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls.report:
|
||||
cls.write_local_html_report(cls.report)
|
||||
if cls.markdown_report:
|
||||
cls.write_local_markdown_report(cls.markdown_report)
|
||||
|
||||
@classmethod
|
||||
def control_path_prefix(cls) -> Optional[str]:
|
||||
@ -127,6 +130,24 @@ class QgisTestCase(unittest.TestCase):
|
||||
if not QgisTestCase.is_ci_run():
|
||||
QDesktopServices.openUrl(QUrl.fromLocalFile(report_file))
|
||||
|
||||
@classmethod
|
||||
def write_local_markdown_report(cls, report: str):
|
||||
report_dir = QgsRenderChecker.testReportDir()
|
||||
if not report_dir.exists():
|
||||
QDir().mkpath(report_dir.path())
|
||||
|
||||
report_file = report_dir.filePath('summary.md')
|
||||
|
||||
# only append to existing reports if running under CI
|
||||
if cls.is_ci_run() or \
|
||||
os.environ.get("QGIS_APPEND_TO_TEST_REPORT") == 'true':
|
||||
file_mode = 'ta'
|
||||
else:
|
||||
file_mode = 'wt'
|
||||
|
||||
with open(report_file, file_mode, encoding='utf-8') as f:
|
||||
f.write(report)
|
||||
|
||||
@classmethod
|
||||
def image_check(cls,
|
||||
name: str,
|
||||
@ -156,6 +177,11 @@ class QgisTestCase(unittest.TestCase):
|
||||
cls.report += f"<h2>Render {name}</h2>\n"
|
||||
cls.report += checker.report()
|
||||
|
||||
markdown = checker.markdownReport()
|
||||
if markdown:
|
||||
cls.markdown_report += "## {}\n\n".format(name)
|
||||
cls.markdown_report += markdown
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
@ -178,6 +204,11 @@ class QgisTestCase(unittest.TestCase):
|
||||
cls.report += f"<h2>Render {name}</h2>\n"
|
||||
cls.report += checker.report()
|
||||
|
||||
markdown = checker.markdownReport()
|
||||
if markdown:
|
||||
cls.markdown_report += "## {}\n\n".format(name)
|
||||
cls.markdown_report += markdown
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
@ -193,6 +224,12 @@ class QgisTestCase(unittest.TestCase):
|
||||
if not result:
|
||||
cls.report += f"<h2>Render {name}</h2>\n"
|
||||
cls.report += checker.report()
|
||||
|
||||
markdown = checker.markdownReport()
|
||||
if markdown:
|
||||
cls.markdown_report += "## {}\n\n".format(name)
|
||||
cls.markdown_report += markdown
|
||||
|
||||
return result
|
||||
|
||||
def assertLayersEqual(self, layer_expected, layer_result, **kwargs):
|
||||
|
||||
@ -222,6 +222,9 @@ def initInterface(pointer):
|
||||
#######################
|
||||
# PLUGINS
|
||||
|
||||
# The current path for home directory Python plugins.
|
||||
HOME_PLUGIN_PATH: Optional[str] = None
|
||||
|
||||
# list of plugin paths. it gets filled in by the QGIS python library
|
||||
plugin_paths = []
|
||||
|
||||
|
||||
@ -17,5 +17,5 @@
|
||||
"expression": "hour(age('2012-05-12','2012-05-02'))",
|
||||
"returns": "240"
|
||||
}],
|
||||
"tags": ["difference", "needs", "datetimes", "order", "extract", "information", "following", "interval", "dates", "functions", "yearmonthweekdayhourminutesecond"]
|
||||
"tags": ["difference", "needs", "datetimes", "order", "extract", "information", "following", "interval", "dates", "functions", "year", "month", "week", "day", "hour", "minute", "second"]
|
||||
}
|
||||
|
||||
@ -20,5 +20,5 @@
|
||||
"expression": "layer_property('landsat','crs')",
|
||||
"returns": "'EPSG:4326'"
|
||||
}],
|
||||
"tags": ["property", "matching", "metadata"]
|
||||
"tags": ["property", "matching", "metadata", "layer name", "layer id", "title", "abstract", "keywords", "data url", "attribution", "attribution url", "layer source", "minimum scale", "min scale", "maximum scale", "max scale", "is editable", "crs", "crs definition", "crs description", "layer extent", "distance units", "layer type", "storage type", "geometry type", "feature count", "file path"]
|
||||
}
|
||||
|
||||
@ -544,8 +544,6 @@ Qgs3DExportObject *Qgs3DSceneExporter::processGeometryRenderer( Qt3DRender::QGeo
|
||||
if ( tessGeom )
|
||||
{
|
||||
QVector<QgsFeatureId> featureIds = tessGeom->featureIds();
|
||||
// sort feature by their id in order to always export them in the same way:
|
||||
std::sort( featureIds.begin(), featureIds.end() );
|
||||
|
||||
QVector<uint> triangleIndex = tessGeom->triangleIndexStartingIndices();
|
||||
for ( int idx = 0; idx < featureIds.size(); idx++ )
|
||||
|
||||
@ -31,9 +31,7 @@ QgsCameraController::QgsCameraController( Qgs3DMapScene *scene )
|
||||
: Qt3DCore::QEntity( scene )
|
||||
, mScene( scene )
|
||||
, mCamera( scene->engine()->camera() )
|
||||
, mCameraBeforeRotation( new Qt3DRender::QCamera )
|
||||
, mCameraBeforeDrag( new Qt3DRender::QCamera )
|
||||
, mCameraBeforeZoom( new Qt3DRender::QCamera )
|
||||
, mCameraBefore( new Qt3DRender::QCamera )
|
||||
, mMouseHandler( new Qt3DInput::QMouseHandler )
|
||||
, mKeyboardHandler( new Qt3DInput::QKeyboardHandler )
|
||||
{
|
||||
@ -182,11 +180,7 @@ void QgsCameraController::setCameraPose( const QgsCameraPose &camPose )
|
||||
return;
|
||||
|
||||
mCameraPose = camPose;
|
||||
|
||||
if ( mCamera )
|
||||
mCameraPose.updateCamera( mCamera );
|
||||
|
||||
emit cameraChanged();
|
||||
updateCameraFromPose();
|
||||
}
|
||||
|
||||
QDomElement QgsCameraController::writeXml( QDomDocument &doc ) const
|
||||
@ -260,6 +254,7 @@ void QgsCameraController::updateCameraFromPose()
|
||||
{
|
||||
if ( mCamera )
|
||||
mCameraPose.updateCamera( mCamera );
|
||||
|
||||
emit cameraChanged();
|
||||
}
|
||||
|
||||
@ -283,7 +278,7 @@ void QgsCameraController::onPositionChanged( Qt3DInput::QMouseEvent *mouse )
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *cameraBefore, double &depth, QVector3D &worldPosition )
|
||||
bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QCamera *mCameraBefore, double &depth, QVector3D &worldPosition )
|
||||
{
|
||||
depth = sampleDepthBuffer( mDepthBufferImage, position.x(), position.y() );
|
||||
if ( !std::isfinite( depth ) )
|
||||
@ -293,7 +288,7 @@ bool QgsCameraController::screenPointToWorldPos( QPoint position, Qt3DRender::QC
|
||||
}
|
||||
else
|
||||
{
|
||||
worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), cameraBefore );
|
||||
worldPosition = Qgs3DUtils::screenPointToWorldPos( position, depth, mScene->engine()->size(), mCameraBefore );
|
||||
if ( !std::isfinite( worldPosition.x() ) || !std::isfinite( worldPosition.y() ) || !std::isfinite( worldPosition.z() ) )
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "screenPointToWorldPos: position is NaN or Inf. This should not happen." ), 2 );
|
||||
@ -327,10 +322,11 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
if ( ( hasLeftButton && hasShift && !hasCtrl ) || ( hasMiddleButton && !hasShift && !hasCtrl ) )
|
||||
{
|
||||
// rotate/tilt using mouse (camera moves as it rotates around the clicked point)
|
||||
setMouseParameters( MouseOperation::RotationCenter, mMousePos );
|
||||
|
||||
double scale = std::max( mScene->engine()->size().width(), mScene->engine()->size().height() );
|
||||
float pitchDiff = 180 * ( mouse->y() - mMiddleButtonClickPos.y() ) / scale;
|
||||
float yawDiff = -180 * ( mouse->x() - mMiddleButtonClickPos.x() ) / scale;
|
||||
float scale = static_cast<float>( std::max( mScene->engine()->size().width(), mScene->engine()->size().height() ) );
|
||||
float pitchDiff = 180.0f * static_cast<float>( mouse->y() - mClickPoint.y() ) / scale;
|
||||
float yawDiff = -180.0f * static_cast<float>( mouse->x() - mClickPoint.x() ) / scale;
|
||||
|
||||
if ( !mDepthBufferIsReady )
|
||||
return;
|
||||
@ -339,10 +335,10 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
{
|
||||
double depth;
|
||||
QVector3D worldPosition;
|
||||
if ( screenPointToWorldPos( mMiddleButtonClickPos, mCameraBeforeRotation.get(), depth, worldPosition ) )
|
||||
if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
|
||||
{
|
||||
mRotationCenter = worldPosition;
|
||||
mRotationDistanceFromCenter = ( mRotationCenter - mCameraBeforeRotation->position() ).length();
|
||||
mRotationDistanceFromCenter = ( mRotationCenter - mCameraBefore->position() ).length();
|
||||
emit cameraRotationCenterChanged( mRotationCenter );
|
||||
mRotationCenterCalculated = true;
|
||||
}
|
||||
@ -365,7 +361,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
|
||||
// Second transformation : Shift camera position back
|
||||
{
|
||||
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mMiddleButtonClickPos.x(), mMiddleButtonClickPos.y() ), mScene->engine()->size(), mCamera );
|
||||
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( mClickPoint, mScene->engine()->size(), mCamera );
|
||||
|
||||
QVector3D clickedPositionWorld = ray.origin() + mRotationDistanceFromCenter * ray.direction();
|
||||
|
||||
@ -381,6 +377,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
}
|
||||
else if ( hasLeftButton && hasCtrl && !hasShift )
|
||||
{
|
||||
setMouseParameters( MouseOperation::RotationCamera );
|
||||
// rotate/tilt using mouse (camera stays at one position as it rotates)
|
||||
const float diffPitch = 0.2f * dy;
|
||||
const float diffYaw = - 0.2f * dx;
|
||||
@ -389,6 +386,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
else if ( hasLeftButton && !hasShift && !hasCtrl )
|
||||
{
|
||||
// translation works as if one grabbed a point on the 3D viewer and dragged it
|
||||
setMouseParameters( MouseOperation::Translation, mMousePos );
|
||||
|
||||
if ( !mDepthBufferIsReady )
|
||||
return;
|
||||
@ -397,7 +395,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
{
|
||||
double depth;
|
||||
QVector3D worldPosition;
|
||||
if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBeforeDrag.get(), depth, worldPosition ) )
|
||||
if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
|
||||
{
|
||||
mDragDepth = depth;
|
||||
mDragPoint = worldPosition;
|
||||
@ -406,11 +404,11 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
|
||||
}
|
||||
|
||||
QVector3D cameraBeforeDragPos = mCameraBeforeDrag->position();
|
||||
QVector3D cameraBeforeDragPos = mCameraBefore->position();
|
||||
|
||||
QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBeforeDrag.get() );
|
||||
QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBeforeDrag->position() ).normalized();
|
||||
QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBeforeDrag->position() ).normalized();
|
||||
QVector3D moveToPosition = Qgs3DUtils::screenPointToWorldPos( mMousePos, mDragDepth, mScene->engine()->size(), mCameraBefore.get() );
|
||||
QVector3D cameraBeforeToMoveToPos = ( moveToPosition - mCameraBefore->position() ).normalized();
|
||||
QVector3D cameraBeforeToDragPointPos = ( mDragPoint - mCameraBefore->position() ).normalized();
|
||||
|
||||
// Make sure the rays are not horizontal (add small y shift if it is)
|
||||
if ( cameraBeforeToMoveToPos.y() == 0 )
|
||||
@ -433,7 +431,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
|
||||
QVector3D shiftVector = to - from;
|
||||
|
||||
mCameraPose.setCenterPoint( mCameraBeforeDrag->viewCenter() + shiftVector );
|
||||
mCameraPose.setCenterPoint( mCameraBefore->viewCenter() + shiftVector );
|
||||
updateCameraFromPose();
|
||||
}
|
||||
else if ( hasLeftButton && hasShift && hasCtrl )
|
||||
@ -447,6 +445,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
}
|
||||
else if ( hasRightButton && !hasShift && !hasCtrl )
|
||||
{
|
||||
setMouseParameters( MouseOperation::Zoom, mMousePos );
|
||||
if ( !mDepthBufferIsReady )
|
||||
return;
|
||||
|
||||
@ -454,14 +453,14 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
{
|
||||
double depth;
|
||||
QVector3D worldPosition;
|
||||
if ( screenPointToWorldPos( mDragButtonClickPos, mCameraBeforeDrag.get(), depth, worldPosition ) )
|
||||
if ( screenPointToWorldPos( mClickPoint, mCameraBefore.get(), depth, worldPosition ) )
|
||||
{
|
||||
mDragPoint = worldPosition;
|
||||
mDragPointCalculated = true;
|
||||
}
|
||||
}
|
||||
|
||||
float dist = ( mCameraBeforeDrag->position() - mDragPoint ).length();
|
||||
float dist = ( mCameraBefore->position() - mDragPoint ).length();
|
||||
|
||||
int yOffset = 0;
|
||||
int screenHeight = mScene->engine()->size().height();
|
||||
@ -473,16 +472,16 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
}
|
||||
|
||||
// Applies smoothing
|
||||
if ( mMousePos.y() > mDragButtonClickPos.y() ) // zoom in
|
||||
if ( mMousePos.y() > mClickPoint.y() ) // zoom in
|
||||
{
|
||||
double f = ( double )( mMousePos.y() - mDragButtonClickPos.y() ) / ( double )( screenHeight - mDragButtonClickPos.y() - yOffset );
|
||||
double f = ( double )( mMousePos.y() - mClickPoint.y() ) / ( double )( screenHeight - mClickPoint.y() - yOffset );
|
||||
f = std::max( 0.0, std::min( 1.0, f ) );
|
||||
f = 1 - ( std::expm1( -2 * f ) ) / ( std::expm1( -2 ) );
|
||||
dist = dist * f;
|
||||
}
|
||||
else // zoom out
|
||||
{
|
||||
double f = 1 - ( double )( mMousePos.y() + yOffset ) / ( double )( mDragButtonClickPos.y() + yOffset );
|
||||
double f = 1 - ( double )( mMousePos.y() + yOffset ) / ( double )( mClickPoint.y() + yOffset );
|
||||
f = std::max( 0.0, std::min( 1.0, f ) );
|
||||
f = ( std::expm1( 2 * f ) ) / ( std::expm1( 2 ) );
|
||||
dist = dist + 2 * dist * f;
|
||||
@ -501,7 +500,7 @@ void QgsCameraController::onPositionChangedTerrainNavigation( Qt3DInput::QMouseE
|
||||
|
||||
// Second transformation : Shift camera position back
|
||||
{
|
||||
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( QPoint( mDragButtonClickPos.x(), mDragButtonClickPos.y() ), mScene->engine()->size(), mCamera );
|
||||
QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( mClickPoint, mScene->engine()->size(), mCamera );
|
||||
QVector3D clickedPositionWorld = ray.origin() + dist * ray.direction();
|
||||
|
||||
QVector3D shiftVector = clickedPositionWorld - mCamera->viewCenter();
|
||||
@ -536,7 +535,7 @@ void QgsCameraController::handleTerrainNavigationWheelZoom()
|
||||
{
|
||||
double depth;
|
||||
QVector3D worldPosition;
|
||||
if ( screenPointToWorldPos( mMousePos, mCameraBeforeZoom.get(), depth, worldPosition ) )
|
||||
if ( screenPointToWorldPos( mMousePos, mCameraBefore.get(), depth, worldPosition ) )
|
||||
{
|
||||
mZoomPoint = worldPosition;
|
||||
mZoomPointCalculated = true;
|
||||
@ -545,7 +544,7 @@ void QgsCameraController::handleTerrainNavigationWheelZoom()
|
||||
|
||||
float f = mCumulatedWheelY / ( 120.0 * 24.0 );
|
||||
|
||||
double dist = ( mZoomPoint - mCameraBeforeZoom->position() ).length();
|
||||
double dist = ( mZoomPoint - mCameraBefore->position() ).length();
|
||||
dist -= dist * f;
|
||||
|
||||
// First transformation : Shift camera position and view center and rotate the camera
|
||||
@ -573,8 +572,8 @@ void QgsCameraController::handleTerrainNavigationWheelZoom()
|
||||
mCameraPose.setCenterPoint( newViewCenterWorld );
|
||||
updateCameraFromPose();
|
||||
}
|
||||
mIsInZoomInState = false;
|
||||
mCumulatedWheelY = 0;
|
||||
setMouseParameters( MouseOperation::None );
|
||||
}
|
||||
|
||||
void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
|
||||
@ -597,24 +596,14 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
|
||||
// see: https://doc.qt.io/qt-5/qwheelevent.html#angleDelta
|
||||
mCumulatedWheelY += scaling * wheel->angleDelta().y();
|
||||
|
||||
if ( !mIsInZoomInState )
|
||||
if ( mCurrentOperation != MouseOperation::ZoomWheel )
|
||||
{
|
||||
mCameraPose.updateCamera( mCameraBeforeZoom.get() );
|
||||
|
||||
mCameraBeforeZoom->setProjectionMatrix( mCamera->projectionMatrix() );
|
||||
mCameraBeforeZoom->setNearPlane( mCamera->nearPlane() );
|
||||
mCameraBeforeZoom->setFarPlane( mCamera->farPlane() );
|
||||
mCameraBeforeZoom->setAspectRatio( mCamera->aspectRatio() );
|
||||
mCameraBeforeZoom->setFieldOfView( mCamera->fieldOfView() );
|
||||
|
||||
mZoomPointCalculated = false;
|
||||
mIsInZoomInState = true;
|
||||
mDepthBufferIsReady = false;
|
||||
emit requestDepthBufferCapture();
|
||||
setMouseParameters( MouseOperation::ZoomWheel );
|
||||
}
|
||||
else
|
||||
{
|
||||
handleTerrainNavigationWheelZoom();
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -623,64 +612,42 @@ void QgsCameraController::onWheel( Qt3DInput::QWheelEvent *wheel )
|
||||
void QgsCameraController::onMousePressed( Qt3DInput::QMouseEvent *mouse )
|
||||
{
|
||||
mKeyboardHandler->setFocus( true );
|
||||
if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
|
||||
|
||||
if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton ||
|
||||
( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ||
|
||||
( ( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
|
||||
{
|
||||
mMousePos = QPoint( mouse->x(), mouse->y() );
|
||||
mDragButtonClickPos = QPoint( mouse->x(), mouse->y() );
|
||||
mPressedButton = mouse->button();
|
||||
mMousePressed = true;
|
||||
|
||||
if ( mCaptureFpsMouseMovements )
|
||||
mIgnoreNextMouseMove = true;
|
||||
|
||||
mCameraPose.updateCamera( mCameraBeforeDrag.get() );
|
||||
|
||||
mCameraBeforeDrag->setProjectionMatrix( mCamera->projectionMatrix() );
|
||||
mCameraBeforeDrag->setNearPlane( mCamera->nearPlane() );
|
||||
mCameraBeforeDrag->setFarPlane( mCamera->farPlane() );
|
||||
mCameraBeforeDrag->setAspectRatio( mCamera->aspectRatio() );
|
||||
mCameraBeforeDrag->setFieldOfView( mCamera->fieldOfView() );
|
||||
|
||||
mDepthBufferIsReady = false;
|
||||
mDragPointCalculated = false;
|
||||
|
||||
emit requestDepthBufferCapture();
|
||||
const MouseOperation operation
|
||||
{
|
||||
( mouse->modifiers() & Qt::ControlModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ?
|
||||
MouseOperation::RotationCamera :
|
||||
MouseOperation::RotationCenter
|
||||
};
|
||||
setMouseParameters( operation, mMousePos );
|
||||
}
|
||||
|
||||
if ( mouse->button() == Qt3DInput::QMouseEvent::MiddleButton || ( ( mouse->modifiers() & Qt::ShiftModifier ) != 0 && mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) )
|
||||
else if ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton || mouse->button() == Qt3DInput::QMouseEvent::RightButton )
|
||||
{
|
||||
mMousePos = QPoint( mouse->x(), mouse->y() );
|
||||
mMiddleButtonClickPos = QPoint( mouse->x(), mouse->y() );
|
||||
mPressedButton = mouse->button();
|
||||
mMousePressed = true;
|
||||
|
||||
if ( mCaptureFpsMouseMovements )
|
||||
mIgnoreNextMouseMove = true;
|
||||
mDepthBufferIsReady = false;
|
||||
mRotationCenterCalculated = false;
|
||||
|
||||
mRotationPitch = mCameraPose.pitchAngle();
|
||||
mRotationYaw = mCameraPose.headingAngle();
|
||||
|
||||
mCameraPose.updateCamera( mCameraBeforeRotation.get() );
|
||||
|
||||
mCameraBeforeRotation->setProjectionMatrix( mCamera->projectionMatrix() );
|
||||
mCameraBeforeRotation->setNearPlane( mCamera->nearPlane() );
|
||||
mCameraBeforeRotation->setFarPlane( mCamera->farPlane() );
|
||||
mCameraBeforeRotation->setAspectRatio( mCamera->aspectRatio() );
|
||||
mCameraBeforeRotation->setFieldOfView( mCamera->fieldOfView() );
|
||||
|
||||
emit requestDepthBufferCapture();
|
||||
const MouseOperation operation = ( mouse->button() == Qt3DInput::QMouseEvent::LeftButton ) ? MouseOperation::Translation : MouseOperation::Zoom;
|
||||
setMouseParameters( operation, mMousePos );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsCameraController::onMouseReleased( Qt3DInput::QMouseEvent *mouse )
|
||||
{
|
||||
Q_UNUSED( mouse )
|
||||
mPressedButton = Qt3DInput::QMouseEvent::NoButton;
|
||||
mMousePressed = false;
|
||||
|
||||
mDragPointCalculated = false;
|
||||
mRotationCenterCalculated = false;
|
||||
setMouseParameters( MouseOperation::None );
|
||||
}
|
||||
|
||||
void QgsCameraController::onKeyPressed( Qt3DInput::QKeyEvent *event )
|
||||
@ -1086,6 +1053,56 @@ void QgsCameraController::depthBufferCaptured( const QImage &depthImage )
|
||||
mDepthBufferImage = depthImage;
|
||||
mDepthBufferIsReady = true;
|
||||
|
||||
if ( mIsInZoomInState )
|
||||
if ( mCurrentOperation == MouseOperation::ZoomWheel )
|
||||
{
|
||||
handleTerrainNavigationWheelZoom();
|
||||
}
|
||||
}
|
||||
|
||||
bool QgsCameraController::isATranslationRotationSequence( MouseOperation newOperation ) const
|
||||
{
|
||||
return std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), newOperation ) != std::end( mTranslateOrRotate ) &&
|
||||
std::find( mTranslateOrRotate.begin(), mTranslateOrRotate.end(), mCurrentOperation ) != std::end( mTranslateOrRotate );
|
||||
}
|
||||
|
||||
void QgsCameraController::setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint )
|
||||
{
|
||||
if ( newOperation == mCurrentOperation )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( newOperation == MouseOperation::None )
|
||||
{
|
||||
mClickPoint = QPoint();
|
||||
}
|
||||
// click point and rotation angles are updated if:
|
||||
// - it has never been computed
|
||||
// - the current and new operations are both rotation and translation
|
||||
// Indeed, if the sequence such as rotation -> zoom -> rotation updating mClickPoint on
|
||||
// the click point does not need to be updated because the relative mouse position is kept
|
||||
// during a zoom operation
|
||||
else if ( mClickPoint.isNull() || isATranslationRotationSequence( newOperation ) )
|
||||
{
|
||||
mClickPoint = clickPoint;
|
||||
mRotationPitch = mCameraPose.pitchAngle();
|
||||
mRotationYaw = mCameraPose.headingAngle();
|
||||
}
|
||||
mCurrentOperation = newOperation;
|
||||
mDepthBufferIsReady = false;
|
||||
mRotationCenterCalculated = false;
|
||||
mDragPointCalculated = false;
|
||||
mZoomPointCalculated = false;
|
||||
|
||||
if ( mCurrentOperation != MouseOperation::None && mCurrentOperation != MouseOperation::RotationCamera )
|
||||
{
|
||||
emit requestDepthBufferCapture();
|
||||
|
||||
mCameraBefore->setProjectionMatrix( mCamera->projectionMatrix() );
|
||||
mCameraBefore->setNearPlane( mCamera->nearPlane() );
|
||||
mCameraBefore->setFarPlane( mCamera->farPlane() );
|
||||
mCameraBefore->setAspectRatio( mCamera->aspectRatio() );
|
||||
mCameraBefore->setFieldOfView( mCamera->fieldOfView() );
|
||||
mCameraPose.updateCamera( mCameraBefore.get() );
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,6 +225,32 @@ class _3D_EXPORT QgsCameraController : public QObject
|
||||
//! Returns a pointer to the scene's engine's window or nullptr if engine is QgsOffscreen3DEngine
|
||||
QWindow *window() const;
|
||||
|
||||
//! List of possible operations with the mouse in TerrainBased navigation
|
||||
enum class MouseOperation
|
||||
{
|
||||
None = 0, // no operation
|
||||
Translation, // left button pressed, no modifier
|
||||
RotationCamera, // left button pressed + ctrl modifier
|
||||
RotationCenter, // left button pressed + shift modifier
|
||||
Zoom, // right button pressed
|
||||
ZoomWheel // mouse wheel scroll
|
||||
};
|
||||
|
||||
// This list gathers all the rotation and translation operations.
|
||||
// It is used to update the appropriate parameters when successive
|
||||
// translation and rotation happen.
|
||||
const QList<MouseOperation> mTranslateOrRotate =
|
||||
{
|
||||
MouseOperation::Translation,
|
||||
MouseOperation::RotationCamera,
|
||||
MouseOperation::RotationCenter
|
||||
};
|
||||
|
||||
// check that current sequence (current operation and new operation) is a rotation or translation
|
||||
bool isATranslationRotationSequence( MouseOperation newOperation ) const;
|
||||
|
||||
void setMouseParameters( const MouseOperation &newOperation, const QPoint &clickPoint = QPoint() );
|
||||
|
||||
signals:
|
||||
//! Emitted when camera has been updated
|
||||
void cameraChanged();
|
||||
@ -294,28 +320,25 @@ class _3D_EXPORT QgsCameraController : public QObject
|
||||
|
||||
//! Last mouse position recorded
|
||||
QPoint mMousePos;
|
||||
bool mMousePressed = false;
|
||||
Qt3DInput::QMouseEvent::Buttons mPressedButton = Qt3DInput::QMouseEvent::Buttons::NoButton;
|
||||
|
||||
//! click point for a rotation or a translation
|
||||
QPoint mClickPoint;
|
||||
|
||||
bool mDepthBufferIsReady = false;
|
||||
QImage mDepthBufferImage;
|
||||
|
||||
QPoint mMiddleButtonClickPos;
|
||||
std::unique_ptr< Qt3DRender::QCamera > mCameraBefore;
|
||||
|
||||
bool mRotationCenterCalculated = false;
|
||||
QVector3D mRotationCenter;
|
||||
double mRotationDistanceFromCenter;
|
||||
double mRotationPitch = 0;
|
||||
double mRotationYaw = 0;
|
||||
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeRotation;
|
||||
|
||||
QPoint mDragButtonClickPos;
|
||||
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeDrag;
|
||||
bool mDragPointCalculated = false;
|
||||
QVector3D mDragPoint;
|
||||
double mDragDepth;
|
||||
|
||||
bool mIsInZoomInState = false;
|
||||
std::unique_ptr< Qt3DRender::QCamera > mCameraBeforeZoom;
|
||||
bool mZoomPointCalculated = false;
|
||||
QVector3D mZoomPoint;
|
||||
|
||||
@ -332,6 +355,8 @@ class _3D_EXPORT QgsCameraController : public QObject
|
||||
|
||||
double mCumulatedWheelY = 0;
|
||||
|
||||
MouseOperation mCurrentOperation = MouseOperation::None;
|
||||
|
||||
friend QgsCameraController4Test;
|
||||
};
|
||||
|
||||
|
||||
@ -47,23 +47,26 @@ static bool hasLargeBounds( const QgsTiledSceneTile &t )
|
||||
|
||||
///
|
||||
|
||||
QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneChunkLoaderFactory &factory, const QgsTiledSceneTile &t, double zValueScale, double zValueOffset )
|
||||
QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneIndex &index, const QgsTiledSceneChunkLoaderFactory &factory, double zValueScale, double zValueOffset )
|
||||
: QgsChunkLoader( node )
|
||||
, mFactory( factory )
|
||||
, mTile( t )
|
||||
, mIndex( index )
|
||||
{
|
||||
mFutureWatcher = new QFutureWatcher<void>( this );
|
||||
connect( mFutureWatcher, &QFutureWatcher<void>::finished, this, &QgsChunkQueueJob::finished );
|
||||
|
||||
const QFuture<void> future = QtConcurrent::run( [this, zValueScale, zValueOffset]
|
||||
const QgsChunkNodeId tileId = node->tileId();
|
||||
const QFuture<void> future = QtConcurrent::run( [this, tileId, zValueScale, zValueOffset]
|
||||
{
|
||||
const QgsTiledSceneTile tile = mIndex.getTile( tileId.uniqueId );
|
||||
|
||||
// we do not load tiles that are too big - at least for the time being
|
||||
// the problem is that their 3D bounding boxes with ECEF coordinates are huge
|
||||
// and we are unable to turn them into planar bounding boxes
|
||||
if ( hasLargeBounds( mTile ) )
|
||||
if ( hasLargeBounds( tile ) )
|
||||
return;
|
||||
|
||||
QString uri = mTile.resources().value( QStringLiteral( "content" ) ).toString();
|
||||
QString uri = tile.resources().value( QStringLiteral( "content" ) ).toString();
|
||||
if ( uri.isEmpty() )
|
||||
{
|
||||
// nothing to show for this tile
|
||||
@ -71,7 +74,7 @@ QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const Qg
|
||||
return;
|
||||
}
|
||||
|
||||
uri = mTile.baseUrl().resolved( uri ).toString();
|
||||
uri = tile.baseUrl().resolved( uri ).toString();
|
||||
QByteArray content = mFactory.mIndex.retrieveContent( uri );
|
||||
if ( content.isEmpty() )
|
||||
{
|
||||
@ -88,13 +91,13 @@ QgsTiledSceneChunkLoader::QgsTiledSceneChunkLoader( QgsChunkNode *node, const Qg
|
||||
}
|
||||
|
||||
QgsGltf3DUtils::EntityTransform entityTransform;
|
||||
entityTransform.tileTransform = ( mTile.transform() ? *mTile.transform() : QgsMatrix4x4() );
|
||||
entityTransform.tileTransform = ( tile.transform() ? *tile.transform() : QgsMatrix4x4() );
|
||||
entityTransform.tileTransform.translate( tileContent.rtcCenter );
|
||||
entityTransform.sceneOriginTargetCrs = mFactory.mMap.origin();
|
||||
entityTransform.ecefToTargetCrs = &mFactory.mBoundsTransform;
|
||||
entityTransform.zValueScale = zValueScale;
|
||||
entityTransform.zValueOffset = zValueOffset;
|
||||
entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( mTile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() );
|
||||
entityTransform.gltfUpAxis = static_cast< Qgis::Axis >( tile.metadata().value( QStringLiteral( "gltfUpAxis" ), static_cast< int >( Qgis::Axis::Y ) ).toInt() );
|
||||
|
||||
QStringList errors;
|
||||
mEntity = QgsGltf3DUtils::gltfToEntity( tileContent.gltf, entityTransform, uri, &errors );
|
||||
@ -122,7 +125,6 @@ QgsTiledSceneChunkLoader::~QgsTiledSceneChunkLoader()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Qt3DCore::QEntity *QgsTiledSceneChunkLoader::createEntity( Qt3DCore::QEntity *parent )
|
||||
{
|
||||
if ( !mEntity )
|
||||
@ -145,9 +147,7 @@ QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory( const Qgs3DMap
|
||||
|
||||
QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
|
||||
{
|
||||
const QgsTiledSceneTile t = mIndex.getTile( node->tileId().uniqueId );
|
||||
|
||||
return new QgsTiledSceneChunkLoader( node, *this, t, mZValueScale, mZValueOffset );
|
||||
return new QgsTiledSceneChunkLoader( node, mIndex, *this, mZValueScale, mZValueOffset );
|
||||
}
|
||||
|
||||
// converts box from map coordinates to world coords (also flips [X,Y] to [X,-Z])
|
||||
|
||||
@ -54,7 +54,7 @@ class QgsTiledSceneChunkLoader : public QgsChunkLoader
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneChunkLoaderFactory &factory, const QgsTiledSceneTile &t, double zValueScale, double zValueOffset );
|
||||
QgsTiledSceneChunkLoader( QgsChunkNode *node, const QgsTiledSceneIndex &index, const QgsTiledSceneChunkLoaderFactory &factory, double zValueScale, double zValueOffset );
|
||||
|
||||
~QgsTiledSceneChunkLoader();
|
||||
|
||||
@ -62,7 +62,7 @@ class QgsTiledSceneChunkLoader : public QgsChunkLoader
|
||||
|
||||
private:
|
||||
const QgsTiledSceneChunkLoaderFactory &mFactory;
|
||||
QgsTiledSceneTile mTile;
|
||||
QgsTiledSceneIndex mIndex;
|
||||
QFutureWatcher<void> *mFutureWatcher = nullptr;
|
||||
Qt3DCore::QEntity *mEntity = nullptr;
|
||||
};
|
||||
|
||||
@ -68,18 +68,18 @@ void QgsAlignSingleRasterAlgorithm::initAlgorithm( const QVariantMap & )
|
||||
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
||||
|
||||
QStringList resamplingMethods;
|
||||
resamplingMethods << QObject::tr( "Nearest neighbour" )
|
||||
<< QObject::tr( "Bilinear" )
|
||||
<< QObject::tr( "Cubic" )
|
||||
<< QObject::tr( "Cubic spline" )
|
||||
<< QObject::tr( "Lanczos" )
|
||||
resamplingMethods << QObject::tr( "Nearest Neighbour" )
|
||||
<< QObject::tr( "Bilinear (2x2 Kernel)" )
|
||||
<< QObject::tr( "Cubic (4x4 Kernel)" )
|
||||
<< QObject::tr( "Cubic B-Spline (4x4 Kernel)" )
|
||||
<< QObject::tr( "Lanczos (6x6 Kernel)" )
|
||||
<< QObject::tr( "Average" )
|
||||
<< QObject::tr( "Mode" )
|
||||
<< QObject::tr( "Maximum" )
|
||||
<< QObject::tr( "Minimum" )
|
||||
<< QObject::tr( "Median" )
|
||||
<< QObject::tr( "First quartile" )
|
||||
<< QObject::tr( "Third quartile" );
|
||||
<< QObject::tr( "First Quartile (Q1)" )
|
||||
<< QObject::tr( "Third Quartile (Q3)" );
|
||||
addParameter( new QgsProcessingParameterEnum( QStringLiteral( "RESAMPLING_METHOD" ), QObject::tr( "Resampling method" ), resamplingMethods, false, 0, false ) );
|
||||
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "RESCALE" ), QObject::tr( "Rescale values according to the cell size" ), false ) );
|
||||
addParameter( new QgsProcessingParameterRasterLayer( QStringLiteral( "REFERENCE_LAYER" ), QObject::tr( "Reference layer" ) ) );
|
||||
|
||||
@ -60,6 +60,12 @@ Qgs3DOptionsWidget::Qgs3DOptionsWidget( QWidget *parent )
|
||||
mGpuMemoryLimit->setValue( settings.value( QStringLiteral( "map3d/gpuMemoryLimit" ), 500.0, QgsSettings::App ).toDouble() );
|
||||
}
|
||||
|
||||
QString Qgs3DOptionsWidget::helpKey() const
|
||||
{
|
||||
// typo IS correct here!
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#d-options" );
|
||||
}
|
||||
|
||||
void Qgs3DOptionsWidget::apply()
|
||||
{
|
||||
QgsSettings settings;
|
||||
|
||||
@ -36,7 +36,7 @@ class Qgs3DOptionsWidget : public QgsOptionsPageWidget, private Ui::Qgs3DOptions
|
||||
* Constructor for Qgs3DOptionsWidget with the specified \a parent widget.
|
||||
*/
|
||||
Qgs3DOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
};
|
||||
|
||||
@ -125,8 +125,8 @@ void QgsDecorationCopyright::render( const QgsMapSettings &mapSettings, QgsRende
|
||||
const double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, Qgis::TextLayoutMode::Point );
|
||||
|
||||
QPaintDevice *device = context.painter()->device();
|
||||
const int deviceHeight = device->height() / device->devicePixelRatioF();
|
||||
const int deviceWidth = device->width() / device->devicePixelRatioF();
|
||||
const float deviceHeight = static_cast<float>( device->height() ) / context.devicePixelRatio();
|
||||
const float deviceWidth = static_cast<float>( device->width() ) / context.devicePixelRatio();
|
||||
|
||||
float xOffset( 0 ), yOffset( 0 );
|
||||
|
||||
|
||||
@ -201,8 +201,8 @@ void QgsDecorationImage::render( const QgsMapSettings &mapSettings, QgsRenderCon
|
||||
|
||||
// need width/height of paint device
|
||||
QPaintDevice *device = context.painter()->device();
|
||||
const int deviceHeight = device->height() / device->devicePixelRatioF();
|
||||
const int deviceWidth = device->width() / device->devicePixelRatioF();
|
||||
const float deviceHeight = static_cast<float>( device->height() ) / context.devicePixelRatio();
|
||||
const float deviceWidth = static_cast<float>( device->width() ) / context.devicePixelRatio();
|
||||
|
||||
// Set margin according to selected units
|
||||
int xOffset = 0;
|
||||
|
||||
@ -171,8 +171,8 @@ void QgsDecorationNorthArrow::render( const QgsMapSettings &mapSettings, QgsRend
|
||||
) - centerYDouble );
|
||||
// need width/height of paint device
|
||||
QPaintDevice *device = context.painter()->device();
|
||||
const int deviceHeight = device->height() / device->devicePixelRatioF();
|
||||
const int deviceWidth = device->width() / device->devicePixelRatioF();
|
||||
const float deviceHeight = static_cast<float>( device->height() ) / context.devicePixelRatio();
|
||||
const float deviceWidth = static_cast<float>( device->width() ) / context.devicePixelRatio();
|
||||
|
||||
// Set margin according to selected units
|
||||
int xOffset = 0;
|
||||
|
||||
@ -47,10 +47,11 @@ void QgsDecorationOverlay::paintEvent( QPaintEvent * )
|
||||
|
||||
QgsRenderContext context = QgsRenderContext::fromMapSettings( QgisApp::instance()->mapCanvas()->mapSettings() );
|
||||
context.setPainter( &p );
|
||||
context.setDevicePixelRatio( 1 );
|
||||
|
||||
for ( QgsMapDecoration *item : decorations )
|
||||
{
|
||||
// Do not render decorations with fixed map positionn they are rendered directly on the map canvas
|
||||
// Do not render decorations with fixed map position they are rendered directly on the map canvas
|
||||
if ( item->hasFixedMapPosition() )
|
||||
continue;
|
||||
item->render( QgisApp::instance()->mapCanvas()->mapSettings(), context );
|
||||
|
||||
@ -240,8 +240,8 @@ void QgsDecorationScaleBar::render( const QgsMapSettings &mapSettings, QgsRender
|
||||
|
||||
//Get canvas dimensions
|
||||
QPaintDevice *device = context.painter()->device();
|
||||
const int deviceHeight = device->height() / device->devicePixelRatioF();
|
||||
const int deviceWidth = device->width() / device->devicePixelRatioF();
|
||||
const float deviceHeight = static_cast<float>( device->height() ) / context.devicePixelRatio();
|
||||
const float deviceWidth = static_cast<float>( device->width() ) / context.devicePixelRatio();
|
||||
const Qgis::DistanceUnit preferredUnits = QgsProject::instance()->distanceUnits();
|
||||
Qgis::DistanceUnit scaleBarUnits = mapSettings.mapUnits();
|
||||
|
||||
|
||||
@ -114,8 +114,8 @@ void QgsDecorationTitle::render( const QgsMapSettings &mapSettings, QgsRenderCon
|
||||
const double textHeight = QgsTextRenderer::textHeight( context, mTextFormat, displayStringList, Qgis::TextLayoutMode::Point );
|
||||
|
||||
QPaintDevice *device = context.painter()->device();
|
||||
const int deviceHeight = device->height() / device->devicePixelRatioF();
|
||||
const int deviceWidth = device->width() / device->devicePixelRatioF();
|
||||
const float deviceHeight = static_cast<float>( device->height() ) / context.devicePixelRatio();
|
||||
const float deviceWidth = static_cast<float>( device->width() ) / context.devicePixelRatio();
|
||||
|
||||
float xOffset( 0 ), yOffset( 0 );
|
||||
|
||||
|
||||
@ -117,10 +117,10 @@ QgsTransformSettingsDialog::QgsTransformSettingsDialog( Qgis::LayerType type, co
|
||||
cmbCompressionComboBox->addItem( tr( "DEFLATE" ), QStringLiteral( "DEFLATE" ) );
|
||||
|
||||
cmbResampling->addItem( tr( "Nearest Neighbour" ), static_cast< int >( QgsImageWarper::ResamplingMethod::NearestNeighbour ) );
|
||||
cmbResampling->addItem( tr( "Linear" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Bilinear ) );
|
||||
cmbResampling->addItem( tr( "Cubic" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Cubic ) );
|
||||
cmbResampling->addItem( tr( "Cubic Spline" ), static_cast< int >( QgsImageWarper::ResamplingMethod::CubicSpline ) );
|
||||
cmbResampling->addItem( tr( "Lanczos" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Lanczos ) );
|
||||
cmbResampling->addItem( tr( "Bilinear (2x2 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Bilinear ) );
|
||||
cmbResampling->addItem( tr( "Cubic (4x4 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Cubic ) );
|
||||
cmbResampling->addItem( tr( "Cubic B-Spline (4x4 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::CubicSpline ) );
|
||||
cmbResampling->addItem( tr( "Lanczos (6x6 Kernel)" ), static_cast< int >( QgsImageWarper::ResamplingMethod::Lanczos ) );
|
||||
|
||||
connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsTransformSettingsDialog::showHelp );
|
||||
}
|
||||
|
||||
@ -4127,7 +4127,6 @@ void QgsLayoutDesignerDialog::createAtlasWidget()
|
||||
atlasWidget->setMessageBar( mMessageBar );
|
||||
mAtlasDock->setWidget( atlasWidget );
|
||||
|
||||
mAtlasToolbar->show();
|
||||
mPanelsMenu->addAction( mAtlasDock->toggleViewAction() );
|
||||
|
||||
connect( atlas, &QgsLayoutAtlas::messagePushed, mStatusBar, [ = ]( const QString & message )
|
||||
|
||||
@ -61,9 +61,14 @@ QgsAdvancedSettingsWidget::~QgsAdvancedSettingsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
QString QgsAdvancedSettingsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#optionsadvanced" );
|
||||
}
|
||||
|
||||
void QgsAdvancedSettingsWidget::apply()
|
||||
{
|
||||
// the old settings editor applies changes immediately
|
||||
// the old settings editor applies changes immediately
|
||||
// new settings tree is performing changes on apply
|
||||
if ( mTreeWidget )
|
||||
mTreeWidget->applyChanges();
|
||||
|
||||
@ -46,6 +46,7 @@ class QgsAdvancedSettingsWidget : public QgsOptionsPageWidget, private Ui::QgsAd
|
||||
*/
|
||||
QgsAdvancedSettingsWidget( QWidget *parent );
|
||||
~QgsAdvancedSettingsWidget() override;
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
private:
|
||||
|
||||
@ -358,7 +358,7 @@ QgsCodeEditorOptionsWidget::~QgsCodeEditorOptionsWidget()
|
||||
|
||||
QString QgsCodeEditorOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#code-editor-settings" );
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#code-editor-options" );
|
||||
}
|
||||
|
||||
void QgsCodeEditorOptionsWidget::apply()
|
||||
|
||||
@ -420,7 +420,7 @@ QString QgsCustomProjectionOptionsWidget::multiLineWktToSingleLine( const QStrin
|
||||
|
||||
QString QgsCustomProjectionOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "working_with_projections/working_with_projections.html" );
|
||||
return QStringLiteral( "working_with_projections/working_with_projections" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -38,6 +38,11 @@ QgsElevationOptionsWidget::QgsElevationOptionsWidget( QWidget *parent )
|
||||
mButtonBackgroundColor->setToNull();
|
||||
}
|
||||
|
||||
QString QgsElevationOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#elevation-options" );
|
||||
}
|
||||
|
||||
void QgsElevationOptionsWidget::apply()
|
||||
{
|
||||
QgsElevationProfileWidget::settingBackgroundColor->setValue(
|
||||
|
||||
@ -35,7 +35,7 @@ class QgsElevationOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsEl
|
||||
* Constructor for QgsElevationOptionsWidget with the specified \a parent widget.
|
||||
*/
|
||||
QgsElevationOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
};
|
||||
|
||||
@ -104,6 +104,11 @@ QgsFontOptionsWidget::QgsFontOptionsWidget( QWidget *parent )
|
||||
|
||||
}
|
||||
|
||||
QString QgsFontOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#fonts-options" );
|
||||
}
|
||||
|
||||
void QgsFontOptionsWidget::apply()
|
||||
{
|
||||
QMap< QString, QString > replacements;
|
||||
|
||||
@ -36,7 +36,7 @@ class QgsFontOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsFontOpt
|
||||
* Constructor for QgsFontOptionsWidget with the specified \a parent widget.
|
||||
*/
|
||||
QgsFontOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
};
|
||||
|
||||
@ -89,6 +89,11 @@ QgsGpsDeviceOptionsWidget::QgsGpsDeviceOptionsWidget( QWidget *parent )
|
||||
mGpsBabelFileWidget->setFilePath( QgsSettingsRegistryCore::settingsGpsBabelPath->value() );
|
||||
}
|
||||
|
||||
QString QgsGpsDeviceOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#defining-new-device" );
|
||||
}
|
||||
|
||||
void QgsGpsDeviceOptionsWidget::apply()
|
||||
{
|
||||
QgsBabelFormatRegistry::sTreeBabelDevices->deleteAllItems();
|
||||
|
||||
@ -38,7 +38,7 @@ class QgsGpsDeviceOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsGp
|
||||
* Constructor for QgsGpsDeviceOptionsWidget with the specified \a parent widget.
|
||||
*/
|
||||
QgsGpsDeviceOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
private slots:
|
||||
|
||||
@ -313,6 +313,11 @@ QgsGpsOptionsWidget::QgsGpsOptionsWidget( QWidget *parent )
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
QString QgsGpsOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#gps-options" );
|
||||
}
|
||||
|
||||
void QgsGpsOptionsWidget::apply()
|
||||
{
|
||||
if ( QgsSymbol *markerSymbol = mGpsMarkerSymbolButton->symbol() )
|
||||
|
||||
@ -36,7 +36,7 @@ class APP_EXPORT QgsGpsOptionsWidget : public QgsOptionsPageWidget, private Ui::
|
||||
* Constructor for QgsGpsOptionsWidget with the specified \a parent widget.
|
||||
*/
|
||||
QgsGpsOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
private slots:
|
||||
|
||||
@ -2807,13 +2807,65 @@ void QgsOptions::showHelp()
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html" );
|
||||
|
||||
if ( activeTab == mOptionsPageAuth )
|
||||
if ( activeTab == mOptionsPageGeneral )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#general-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageSystem )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#env-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageTransformations )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#transformations-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageDataSources )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#datasources-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageGDAL )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#gdal-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageMapCanvas )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#canvas-legend-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageMapTools )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#maptools-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageDigitizing )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#digitizing-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageColors )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#colors-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageComposer )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#layout-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageNetwork )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#network-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsLocatorSettings )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#locator-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageAcceleration )
|
||||
{
|
||||
link = QStringLiteral( "introduction/qgis_configuration.html#acceleration-options" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageAuth )
|
||||
{
|
||||
link = QStringLiteral( "auth_system/index.html" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageVariables )
|
||||
{
|
||||
link = QStringLiteral( "introduction/general_tools.html#variables" );
|
||||
link = QStringLiteral( "introduction/general_tools.html#general-tools-variables" );
|
||||
}
|
||||
else if ( activeTab == mOptionsPageCRS )
|
||||
{
|
||||
|
||||
@ -39,12 +39,12 @@ QgsRasterRenderingOptionsWidget::QgsRasterRenderingOptionsWidget( QWidget *paren
|
||||
spnBlue->setClearValue( 3 );
|
||||
|
||||
mZoomedInResamplingComboBox->insertItem( 0, tr( "Nearest Neighbour" ), QStringLiteral( "nearest neighbour" ) );
|
||||
mZoomedInResamplingComboBox->insertItem( 1, tr( "Bilinear" ), QStringLiteral( "bilinear" ) );
|
||||
mZoomedInResamplingComboBox->insertItem( 2, tr( "Cubic" ), QStringLiteral( "cubic" ) );
|
||||
mZoomedInResamplingComboBox->insertItem( 1, tr( "Bilinear (2x2 Kernel)" ), QStringLiteral( "bilinear" ) );
|
||||
mZoomedInResamplingComboBox->insertItem( 2, tr( "Cubic (4x4 Kernel)" ), QStringLiteral( "cubic" ) );
|
||||
|
||||
mZoomedOutResamplingComboBox->insertItem( 0, tr( "Nearest Neighbour" ), QStringLiteral( "nearest neighbour" ) );
|
||||
mZoomedOutResamplingComboBox->insertItem( 1, tr( "Bilinear" ), QStringLiteral( "bilinear" ) );
|
||||
mZoomedOutResamplingComboBox->insertItem( 2, tr( "Cubic" ), QStringLiteral( "cubic" ) );
|
||||
mZoomedOutResamplingComboBox->insertItem( 1, tr( "Bilinear (2x2 Kernel)" ), QStringLiteral( "bilinear" ) );
|
||||
mZoomedOutResamplingComboBox->insertItem( 2, tr( "Cubic (4x4 Kernel)" ), QStringLiteral( "cubic" ) );
|
||||
|
||||
QString zoomedInResampling = settings.value( QStringLiteral( "/Raster/defaultZoomedInResampling" ), QStringLiteral( "nearest neighbour" ) ).toString();
|
||||
mZoomedInResamplingComboBox->setCurrentIndex( mZoomedInResamplingComboBox->findData( zoomedInResampling ) );
|
||||
@ -78,6 +78,11 @@ QgsRasterRenderingOptionsWidget::QgsRasterRenderingOptionsWidget( QWidget *paren
|
||||
spnThreeBandStdDev->setClearValue( QgsRasterMinMaxOrigin::DEFAULT_STDDEV_FACTOR );
|
||||
}
|
||||
|
||||
QString QgsRasterRenderingOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#raster-rendering-options" );
|
||||
}
|
||||
|
||||
void QgsRasterRenderingOptionsWidget::apply()
|
||||
{
|
||||
QgsSettings settings;
|
||||
|
||||
@ -25,7 +25,7 @@ class QgsRasterRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui:
|
||||
public:
|
||||
|
||||
QgsRasterRenderingOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
private:
|
||||
bool mBlockStoringChanges = false;
|
||||
|
||||
@ -55,6 +55,11 @@ QgsRenderingOptionsWidget::QgsRenderingOptionsWidget( QWidget *parent )
|
||||
chkAntiAliasing->setChecked( settings.value( QStringLiteral( "/qgis/enable_anti_aliasing" ), true ).toBool() );
|
||||
}
|
||||
|
||||
QString QgsRenderingOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#rendering-options" );
|
||||
}
|
||||
|
||||
void QgsRenderingOptionsWidget::apply()
|
||||
{
|
||||
QgsSettings settings;
|
||||
|
||||
@ -28,7 +28,7 @@ class QgsRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui::QgsRe
|
||||
public:
|
||||
|
||||
QgsRenderingOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
private:
|
||||
bool mBlockStoringChanges = false;
|
||||
|
||||
@ -80,6 +80,11 @@ QgsUserProfileOptionsWidget::QgsUserProfileOptionsWidget( QWidget *parent )
|
||||
mActiveProfileIconLabel->setText( tr( "Active Profile (%1) icon", "Active profile icon" ).arg( manager->userProfile()->name() ) );
|
||||
}
|
||||
|
||||
QString QgsUserProfileOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#user-profiles" );
|
||||
}
|
||||
|
||||
void QgsUserProfileOptionsWidget::apply()
|
||||
{
|
||||
QgsUserProfileManager *manager = QgisApp::instance()->userProfileManager();
|
||||
|
||||
@ -34,7 +34,7 @@ class APP_EXPORT QgsUserProfileOptionsWidget : public QgsOptionsPageWidget, priv
|
||||
|
||||
//! Constructor for QgsUserProfileOptionsWidget with the specified \a parent widget.
|
||||
QgsUserProfileOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
|
||||
private slots:
|
||||
|
||||
@ -68,6 +68,11 @@ QgsVectorRenderingOptionsWidget::QgsVectorRenderingOptionsWidget( QWidget *paren
|
||||
mSimplifyAlgorithmComboBox->setCurrentIndex( mSimplifyAlgorithmComboBox->findData( QgsVectorLayer::settingsSimplifyAlgorithm->value() ) );
|
||||
}
|
||||
|
||||
QString QgsVectorRenderingOptionsWidget::helpKey() const
|
||||
{
|
||||
return QStringLiteral( "introduction/qgis_configuration.html#vector-rendering-options" );
|
||||
}
|
||||
|
||||
void QgsVectorRenderingOptionsWidget::apply()
|
||||
{
|
||||
QgsSettings settings;
|
||||
|
||||
@ -25,7 +25,7 @@ class QgsVectorRenderingOptionsWidget : public QgsOptionsPageWidget, private Ui:
|
||||
public:
|
||||
|
||||
QgsVectorRenderingOptionsWidget( QWidget *parent );
|
||||
|
||||
QString helpKey() const override;
|
||||
void apply() override;
|
||||
private:
|
||||
bool mBlockStoringChanges = false;
|
||||
|
||||
@ -568,7 +568,7 @@ bool QgsCustomizationDialog::catchOn()
|
||||
|
||||
void QgsCustomizationDialog::showHelp()
|
||||
{
|
||||
QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#customization" ) );
|
||||
QgsHelp::openHelp( QStringLiteral( "introduction/qgis_configuration.html#sec-customization" ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -2305,6 +2305,7 @@ target_include_directories(qgis_core PUBLIC
|
||||
vector
|
||||
vectortile
|
||||
${CMAKE_SOURCE_DIR}/external
|
||||
${CMAKE_SOURCE_DIR}/external/delaunator-cpp
|
||||
${CMAKE_SOURCE_DIR}/external/nlohmann
|
||||
${CMAKE_SOURCE_DIR}/external/kdbush/include
|
||||
${CMAKE_SOURCE_DIR}/external/nmea
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
#include <QDomElement>
|
||||
#include <QDomDocument>
|
||||
#include <QRegularExpression>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
#include <QRandomGenerator>
|
||||
@ -50,7 +51,6 @@
|
||||
#include "keychain.h"
|
||||
|
||||
// QGIS includes
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsauthcertutils.h"
|
||||
#include "qgsauthcrypto.h"
|
||||
#include "qgsauthmethod.h"
|
||||
@ -75,7 +75,7 @@ const QString QgsAuthManager::AUTH_MAN_TAG = QObject::tr( "Authentication Manage
|
||||
const QString QgsAuthManager::AUTH_CFG_REGEX = QStringLiteral( "authcfg=([a-z]|[A-Z]|[0-9]){7}" );
|
||||
|
||||
|
||||
const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME( "QGIS-Master-Password" );
|
||||
const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_KEY_NAME_BASE( "QGIS-Master-Password" );
|
||||
const QLatin1String QgsAuthManager::AUTH_PASSWORD_HELPER_FOLDER_NAME( "QGIS" );
|
||||
|
||||
|
||||
@ -131,7 +131,7 @@ QSqlDatabase QgsAuthManager::authDatabaseConnection() const
|
||||
authdb = QSqlDatabase::addDatabase( QStringLiteral( "QSQLITE" ), connectionName );
|
||||
authdb.setDatabaseName( authenticationDatabasePath() );
|
||||
// for background threads, remove database when current thread finishes
|
||||
if ( QThread::currentThread() != qApp->thread() )
|
||||
if ( QThread::currentThread() != QCoreApplication::instance()->thread() )
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "Scheduled auth db remove on thread close" ), 2 );
|
||||
|
||||
@ -3207,7 +3207,7 @@ bool QgsAuthManager::passwordHelperDelete()
|
||||
QgsSettings settings;
|
||||
job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
|
||||
job.setAutoDelete( false );
|
||||
job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
|
||||
job.setKey( authPasswordHelperKeyName() );
|
||||
QEventLoop loop;
|
||||
connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
|
||||
job.start();
|
||||
@ -3239,7 +3239,7 @@ QString QgsAuthManager::passwordHelperRead()
|
||||
QgsSettings settings;
|
||||
job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
|
||||
job.setAutoDelete( false );
|
||||
job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
|
||||
job.setKey( authPasswordHelperKeyName() );
|
||||
QEventLoop loop;
|
||||
connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
|
||||
job.start();
|
||||
@ -3281,7 +3281,7 @@ bool QgsAuthManager::passwordHelperWrite( const QString &password )
|
||||
QgsSettings settings;
|
||||
job.setInsecureFallback( settings.value( QStringLiteral( "password_helper_insecure_fallback" ), false, QgsSettings::Section::Auth ).toBool() );
|
||||
job.setAutoDelete( false );
|
||||
job.setKey( AUTH_PASSWORD_HELPER_KEY_NAME );
|
||||
job.setKey( authPasswordHelperKeyName() );
|
||||
job.setTextData( password );
|
||||
QEventLoop loop;
|
||||
connect( &job, &QKeychain::Job::finished, &loop, &QEventLoop::quit );
|
||||
@ -3956,3 +3956,12 @@ void QgsAuthManager::insertCaCertInCache( QgsAuthCertUtils::CaCertSource source,
|
||||
QPair<QgsAuthCertUtils::CaCertSource, QSslCertificate>( source, cert ) );
|
||||
}
|
||||
}
|
||||
|
||||
QString QgsAuthManager::authPasswordHelperKeyName() const
|
||||
{
|
||||
const QFileInfo info( mAuthDbPath );
|
||||
const QString dbProfilePath = info.dir().dirName();
|
||||
|
||||
// if not running from the default profile, ensure that a different key is used
|
||||
return AUTH_PASSWORD_HELPER_KEY_NAME_BASE + ( dbProfilePath.compare( QLatin1String( "default" ), Qt::CaseInsensitive ) == 0 ? QString() : dbProfilePath );
|
||||
}
|
||||
|
||||
@ -874,6 +874,8 @@ class CORE_EXPORT QgsAuthManager : public QObject
|
||||
|
||||
const QString authDbTrustTable() const { return AUTH_TRUST_TABLE; }
|
||||
|
||||
QString authPasswordHelperKeyName() const;
|
||||
|
||||
static QgsAuthManager *sInstance;
|
||||
static const QString AUTH_CONFIG_TABLE;
|
||||
static const QString AUTH_PASS_TABLE;
|
||||
@ -939,7 +941,7 @@ class CORE_EXPORT QgsAuthManager : public QObject
|
||||
bool mPasswordHelperFailedInit = false;
|
||||
|
||||
//! Master password name in the wallets
|
||||
static const QLatin1String AUTH_PASSWORD_HELPER_KEY_NAME;
|
||||
static const QLatin1String AUTH_PASSWORD_HELPER_KEY_NAME_BASE;
|
||||
|
||||
//! password helper folder in the wallets
|
||||
static const QLatin1String AUTH_PASSWORD_HELPER_FOLDER_NAME;
|
||||
|
||||
@ -502,8 +502,13 @@ QString QgsExpression::replaceExpressionText( const QString &action, const QgsEx
|
||||
continue;
|
||||
}
|
||||
|
||||
QgsDebugMsgLevel( "Expression result is: " + result.toString(), 3 );
|
||||
expr_action += action.mid( start, pos - start ) + result.toString();
|
||||
QString resultString;
|
||||
if ( !QgsVariantUtils::isNull( result ) )
|
||||
resultString = result.toString();
|
||||
|
||||
QgsDebugMsgLevel( "Expression result is: " + resultString, 3 );
|
||||
|
||||
expr_action += action.mid( start, pos - start ) + resultString;
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
|
||||
|
||||
@ -51,8 +51,11 @@ QString QgsCheckBoxFieldFormatter::representValue( QgsVectorLayer *layer, int fi
|
||||
const QVariant::Type fieldType = layer->fields().at( fieldIndex ).type();
|
||||
if ( fieldType == QVariant::Bool )
|
||||
{
|
||||
boolValue = value.toBool();
|
||||
textValue = boolValue ? QObject::tr( "true" ) : QObject::tr( "false" );
|
||||
if ( ! isNull )
|
||||
{
|
||||
boolValue = value.toBool();
|
||||
textValue = boolValue ? QObject::tr( "true" ) : QObject::tr( "false" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -3174,6 +3174,34 @@ QgsGeometry QgsGeometry::forceRHR() const
|
||||
return forcePolygonClockwise();
|
||||
}
|
||||
|
||||
Qgis::AngularDirection QgsGeometry::polygonOrientation() const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
{
|
||||
return Qgis::AngularDirection::NoOrientation;
|
||||
}
|
||||
|
||||
if ( isMultipart() )
|
||||
{
|
||||
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( d->geometry.get() );
|
||||
const QgsAbstractGeometry *g = collection->geometryN( 0 );
|
||||
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) )
|
||||
{
|
||||
return cp->exteriorRing() ? cp->exteriorRing()->orientation() : Qgis::AngularDirection::NoOrientation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( d->geometry.get() ) )
|
||||
{
|
||||
return cp->exteriorRing() ? cp->exteriorRing()->orientation() : Qgis::AngularDirection::NoOrientation;
|
||||
}
|
||||
}
|
||||
|
||||
return Qgis::AngularDirection::NoOrientation;
|
||||
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::forcePolygonClockwise() const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
|
||||
@ -2646,6 +2646,48 @@ class CORE_EXPORT QgsGeometry
|
||||
*/
|
||||
QgsGeometry makeValid( Qgis::MakeValidMethod method = Qgis::MakeValidMethod::Linework, bool keepCollapsed = false ) const SIP_THROW( QgsNotSupportedException );
|
||||
|
||||
/**
|
||||
* Returns the orientation of the polygon.
|
||||
* \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
* you have to perform in deep verification.
|
||||
*
|
||||
* \warning returns Qgis::AngularDirection::NoOrientation if the geometry is not a polygon type or empty
|
||||
*
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
Qgis::AngularDirection polygonOrientation() const;
|
||||
|
||||
/**
|
||||
* Returns True if the Polygon is counter-clockwise.
|
||||
*
|
||||
* \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
* you have to perform in deep verification.
|
||||
*
|
||||
* \warning returns false if the geometry is not a polygon type or empty
|
||||
*
|
||||
* \see isPolygonClockwise()
|
||||
* \see forcePolygonClockwise()
|
||||
* \see forcePolygonCounterClockwise()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
bool isPolygonCounterClockwise() const { return polygonOrientation() == Qgis::AngularDirection::CounterClockwise; }
|
||||
|
||||
/**
|
||||
* Returns True if the Polygon is clockwise.
|
||||
*
|
||||
* \warning Only the first exterior ring is taken to perform this operation. In case of degenerate orders,
|
||||
* you have to perform in deep verification.
|
||||
*
|
||||
* \warning returns true if the geometry is not a polygon type or empty
|
||||
*
|
||||
* \see isPolygonCounterClockwise()
|
||||
* \see forcePolygonClockwise()
|
||||
* \see forcePolygonCounterClockwise()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
bool isPolygonClockwise() const { return polygonOrientation() == Qgis::AngularDirection::Clockwise; }
|
||||
|
||||
|
||||
/**
|
||||
* Forces geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon
|
||||
* is to the right of the boundary. In particular, the exterior ring is oriented in a clockwise direction
|
||||
@ -2654,6 +2696,8 @@ class CORE_EXPORT QgsGeometry
|
||||
* \warning Due to the conflicting definitions of the right-hand-rule in general use, it is recommended
|
||||
* to use the explicit forcePolygonClockwise() or forcePolygonCounterClockwise() methods instead.
|
||||
*
|
||||
* \see isPolygonClockwise()
|
||||
* \see isPolygonCounterClockwise()
|
||||
* \see forcePolygonClockwise()
|
||||
* \see forcePolygonCounterClockwise()
|
||||
* \since QGIS 3.6
|
||||
@ -2665,6 +2709,8 @@ class CORE_EXPORT QgsGeometry
|
||||
*
|
||||
* This convention is used primarily by ESRI software.
|
||||
*
|
||||
* \see isPolygonClockwise()
|
||||
* \see isPolygonCounterClockwise()
|
||||
* \see forcePolygonCounterClockwise()
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
@ -2675,6 +2721,8 @@ class CORE_EXPORT QgsGeometry
|
||||
*
|
||||
* This convention matches the OGC Simple Features specification.
|
||||
*
|
||||
* \see isPolygonClockwise()
|
||||
* \see isPolygonCounterClockwise()
|
||||
* \see forcePolygonClockwise()
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
|
||||
@ -433,8 +433,8 @@ class CORE_EXPORT QgsGeometryUtils
|
||||
double pointSpacingAngleTolerance ) SIP_HOLDGIL;
|
||||
|
||||
/**
|
||||
* For line defined by points pt1 and pt3, find out on which side of the line is point pt3.
|
||||
* Returns -1 if pt3 on the left side, 1 if pt3 is on the right side or 0 if pt3 lies on the line.
|
||||
* For line defined by points pt1 and pt3, find out on which side of the line is point pt2.
|
||||
* Returns -1 if pt2 on the left side, 1 if pt2 is on the right side or 0 if pt2 lies on the line.
|
||||
* \since 3.0
|
||||
*/
|
||||
static int segmentSide( const QgsPoint &pt1, const QgsPoint &pt3, const QgsPoint &pt2 ) SIP_HOLDGIL;
|
||||
|
||||
@ -2049,7 +2049,7 @@ Qgis::CoverageValidityResult QgsGeos::validateCoverage( double gapWidth, std::un
|
||||
( void )gapWidth;
|
||||
( void )invalidEdges;
|
||||
( void )errorMsg;
|
||||
throw QgsNotSupportedException( QObject::tr( "Validiting coverages requires a QGIS build based on GEOS 3.12 or later" ) );
|
||||
throw QgsNotSupportedException( QObject::tr( "Validating coverages requires a QGIS build based on GEOS 3.12 or later" ) );
|
||||
#else
|
||||
if ( !mGeos )
|
||||
{
|
||||
|
||||
@ -287,6 +287,13 @@ static bool E3T_physicalToBarycentric( const QgsPointXY &pA, const QgsPointXY &p
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QgsMeshLayerUtils::calculateBarycentricCoordinates(
|
||||
const QgsPointXY &pA, const QgsPointXY &pB, const QgsPointXY &pC, const QgsPointXY &pP,
|
||||
double &lam1, double &lam2, double &lam3 )
|
||||
{
|
||||
return E3T_physicalToBarycentric( pA, pB, pC, pP, lam1, lam2, lam3 );
|
||||
}
|
||||
|
||||
double QgsMeshLayerUtils::interpolateFromVerticesData( const QgsPointXY &p1, const QgsPointXY &p2, const QgsPointXY &p3,
|
||||
double val1, double val2, double val3, const QgsPointXY &pt )
|
||||
{
|
||||
|
||||
@ -141,6 +141,24 @@ class CORE_EXPORT QgsMeshLayerUtils
|
||||
double devicePixelRatio = 1.0
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculates barycentric coordinates of point \a pP in a triangle given by
|
||||
* its vertices \a pA, \a pB and \a pC. The results are written to \a lam1,
|
||||
* \a lam2, \a lam3. The function returns true or false, depending on whether
|
||||
* the point \a pP is inside the triangle.
|
||||
*
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
static bool calculateBarycentricCoordinates(
|
||||
const QgsPointXY &pA,
|
||||
const QgsPointXY &pB,
|
||||
const QgsPointXY &pC,
|
||||
const QgsPointXY &pP,
|
||||
double &lam1,
|
||||
double &lam2,
|
||||
double &lam3
|
||||
);
|
||||
|
||||
/**
|
||||
* Interpolates value based on known values on the vertices of a edge
|
||||
* \returns value on the point pt a or NaN
|
||||
|
||||
@ -49,7 +49,13 @@ QgsPointCloudRenderer *QgsPointCloudAttributeByRampRenderer::clone() const
|
||||
|
||||
void QgsPointCloudAttributeByRampRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context )
|
||||
{
|
||||
const QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
// we need to include also points slightly outside of the visible extent,
|
||||
// otherwise the triangulation may be missing triangles near the edges and corners
|
||||
visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 );
|
||||
}
|
||||
|
||||
const char *ptr = block->data();
|
||||
int count = block->pointCount();
|
||||
@ -122,9 +128,17 @@ void QgsPointCloudAttributeByRampRenderer::renderBlock( const QgsPointCloudBlock
|
||||
attributeValue = ( context.offset().z() + context.scale().z() * attributeValue ) * context.zValueScale() + context.zValueFixedOffset();
|
||||
|
||||
mColorRampShader.shade( attributeValue, &red, &green, &blue, &alpha );
|
||||
drawPoint( x, y, QColor( red, green, blue, alpha ), context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
addPointToTriangulation( x, y, z, QColor( red, green, blue, alpha ), context );
|
||||
}
|
||||
else
|
||||
{
|
||||
drawPoint( x, y, QColor( red, green, blue, alpha ), context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
}
|
||||
|
||||
rendered++;
|
||||
}
|
||||
|
||||
@ -67,7 +67,13 @@ QgsPointCloudRenderer *QgsPointCloudClassifiedRenderer::clone() const
|
||||
|
||||
void QgsPointCloudClassifiedRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context )
|
||||
{
|
||||
const QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
// we need to include also points slightly outside of the visible extent,
|
||||
// otherwise the triangulation may be missing triangles near the edges and corners
|
||||
visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 );
|
||||
}
|
||||
|
||||
const char *ptr = block->data();
|
||||
int count = block->pointCount();
|
||||
@ -136,9 +142,16 @@ void QgsPointCloudClassifiedRenderer::renderBlock( const QgsPointCloudBlock *blo
|
||||
}
|
||||
}
|
||||
|
||||
drawPoint( x, y, color, context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
addPointToTriangulation( x, y, z, color, context );
|
||||
}
|
||||
else
|
||||
{
|
||||
drawPoint( x, y, color, context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
}
|
||||
rendered++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "qgspointcloudindex.h"
|
||||
#include "qgscolorramp.h"
|
||||
#include "qgselevationmap.h"
|
||||
#include "qgsmeshlayerutils.h"
|
||||
#include "qgspointcloudrequest.h"
|
||||
#include "qgspointcloudattribute.h"
|
||||
#include "qgspointcloudrenderer.h"
|
||||
@ -36,6 +37,9 @@
|
||||
#include "qgsruntimeprofiler.h"
|
||||
#include "qgsapplication.h"
|
||||
|
||||
#include <delaunator.hpp>
|
||||
|
||||
|
||||
QgsPointCloudLayerRenderer::QgsPointCloudLayerRenderer( QgsPointCloudLayer *layer, QgsRenderContext &context )
|
||||
: QgsMapLayerRenderer( layer->id(), &context )
|
||||
, mLayer( layer )
|
||||
@ -279,7 +283,15 @@ bool QgsPointCloudLayerRenderer::renderIndex( QgsPointCloudIndex *pc )
|
||||
int nodesDrawn = 0;
|
||||
bool canceled = false;
|
||||
|
||||
switch ( mRenderer->drawOrder2d() )
|
||||
Qgis::PointCloudDrawOrder drawOrder = mRenderer->drawOrder2d();
|
||||
if ( mRenderer->renderAsTriangles() )
|
||||
{
|
||||
// Ordered rendering is ignored when drawing as surface, because all points are used for triangulation.
|
||||
// We would need to have a way to detect if a point is occluded by some other points, which may be costly.
|
||||
drawOrder = Qgis::PointCloudDrawOrder::Default;
|
||||
}
|
||||
|
||||
switch ( drawOrder )
|
||||
{
|
||||
case Qgis::PointCloudDrawOrder::BottomToTop:
|
||||
case Qgis::PointCloudDrawOrder::TopToBottom:
|
||||
@ -355,6 +367,12 @@ int QgsPointCloudLayerRenderer::renderNodesSync( const QVector<IndexedPointCloud
|
||||
mReadyToCompose = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( mRenderer->renderAsTriangles() )
|
||||
{
|
||||
renderTriangulatedSurface( context );
|
||||
}
|
||||
|
||||
return nodesDrawn;
|
||||
}
|
||||
|
||||
@ -440,6 +458,11 @@ int QgsPointCloudLayerRenderer::renderNodesAsync( const QVector<IndexedPointClou
|
||||
blockRequest->deleteLater();
|
||||
}
|
||||
|
||||
if ( mRenderer->renderAsTriangles() )
|
||||
{
|
||||
renderTriangulatedSurface( context );
|
||||
}
|
||||
|
||||
return nodesDrawn;
|
||||
}
|
||||
|
||||
@ -569,6 +592,129 @@ int QgsPointCloudLayerRenderer::renderNodesSorted( const QVector<IndexedPointClo
|
||||
return blockCount;
|
||||
}
|
||||
|
||||
inline bool isEdgeTooLong( const QPointF &p1, const QPointF &p2, float length )
|
||||
{
|
||||
QPointF p = p1 - p2;
|
||||
return p.x() * p.x() + p.y() * p.y() > length;
|
||||
}
|
||||
|
||||
static void renderTriangle( QImage &img, QPointF *pts, QRgb c0, QRgb c1, QRgb c2, float horizontalFilter, float *elev, QgsElevationMap *elevationMap )
|
||||
{
|
||||
if ( horizontalFilter > 0 )
|
||||
{
|
||||
float filterThreshold2 = horizontalFilter * horizontalFilter;
|
||||
if ( isEdgeTooLong( pts[0], pts[1], filterThreshold2 ) ||
|
||||
isEdgeTooLong( pts[1], pts[2], filterThreshold2 ) ||
|
||||
isEdgeTooLong( pts[2], pts[0], filterThreshold2 ) )
|
||||
return;
|
||||
}
|
||||
|
||||
QgsRectangle screenBBox = QgsMeshLayerUtils::triangleBoundingBox( pts[0], pts[1], pts[2] );
|
||||
|
||||
QSize outputSize = img.size();
|
||||
|
||||
int topLim = std::max( int( screenBBox.yMinimum() ), 0 );
|
||||
int bottomLim = std::min( int( screenBBox.yMaximum() ), outputSize.height() - 1 );
|
||||
int leftLim = std::max( int( screenBBox.xMinimum() ), 0 );
|
||||
int rightLim = std::min( int( screenBBox.xMaximum() ), outputSize.width() - 1 );
|
||||
|
||||
int red0 = qRed( c0 ), green0 = qGreen( c0 ), blue0 = qBlue( c0 );
|
||||
int red1 = qRed( c1 ), green1 = qGreen( c1 ), blue1 = qBlue( c1 );
|
||||
int red2 = qRed( c2 ), green2 = qGreen( c2 ), blue2 = qBlue( c2 );
|
||||
|
||||
QRgb *elevData = elevationMap ? elevationMap->rawElevationImageData() : nullptr;
|
||||
|
||||
for ( int j = topLim; j <= bottomLim; j++ )
|
||||
{
|
||||
QRgb *scanLine = ( QRgb * ) img.scanLine( j );
|
||||
QRgb *elevScanLine = elevData ? elevData + static_cast<size_t>( outputSize.width() * j ) : nullptr;
|
||||
for ( int k = leftLim; k <= rightLim; k++ )
|
||||
{
|
||||
QPointF pt( k, j );
|
||||
double lam1, lam2, lam3;
|
||||
if ( !QgsMeshLayerUtils::calculateBarycentricCoordinates( pts[0], pts[1], pts[2], pt, lam3, lam2, lam1 ) )
|
||||
continue;
|
||||
|
||||
// interpolate color
|
||||
int r = static_cast<int>( red0 * lam1 + red1 * lam2 + red2 * lam3 );
|
||||
int g = static_cast<int>( green0 * lam1 + green1 * lam2 + green2 * lam3 );
|
||||
int b = static_cast<int>( blue0 * lam1 + blue1 * lam2 + blue2 * lam3 );
|
||||
scanLine[k] = qRgb( r, g, b );
|
||||
|
||||
// interpolate elevation - in case we are doing global map shading
|
||||
if ( elevScanLine )
|
||||
{
|
||||
float z = static_cast<float>( elev[0] * lam1 + elev[1] * lam2 + elev[2] * lam3 );
|
||||
elevScanLine[k] = QgsElevationMap::encodeElevation( z );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsPointCloudLayerRenderer::renderTriangulatedSurface( QgsPointCloudRenderContext &context )
|
||||
{
|
||||
const QgsPointCloudRenderContext::TriangulationData &triangulation = context.triangulationData();
|
||||
const std::vector<double> &points = triangulation.points;
|
||||
|
||||
// Delaunator would crash if it gets less than three points
|
||||
if ( points.size() < 3 )
|
||||
{
|
||||
QgsDebugMsgLevel( QStringLiteral( "Need at least 3 points to triangulate" ), 4 );
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<delaunator::Delaunator> delaunator;
|
||||
try
|
||||
{
|
||||
delaunator.reset( new delaunator::Delaunator( points ) );
|
||||
}
|
||||
catch ( std::exception &e )
|
||||
{
|
||||
// something went wrong, better to retrieve initial state
|
||||
QgsDebugMsgLevel( QStringLiteral( "Error with triangulation" ), 4 );
|
||||
return;
|
||||
}
|
||||
|
||||
float horizontalFilter = 0;
|
||||
if ( mRenderer->horizontalTriangleFilter() )
|
||||
{
|
||||
horizontalFilter = static_cast<float>( renderContext()->convertToPainterUnits(
|
||||
mRenderer->horizontalTriangleFilterThreshold(), mRenderer->horizontalTriangleFilterUnit() ) );
|
||||
}
|
||||
|
||||
QImage img( context.renderContext().deviceOutputSize(), QImage::Format_ARGB32_Premultiplied );
|
||||
img.setDevicePixelRatio( context.renderContext().devicePixelRatio() );
|
||||
img.fill( 0 );
|
||||
|
||||
const std::vector<size_t> &triangleIndexes = delaunator->triangles;
|
||||
QPainter *painter = context.renderContext().painter();
|
||||
QgsElevationMap *elevationMap = context.renderContext().elevationMap();
|
||||
QPointF triangle[3];
|
||||
float elev[3];
|
||||
for ( size_t i = 0; i < triangleIndexes.size(); i += 3 )
|
||||
{
|
||||
size_t v0 = triangleIndexes[i], v1 = triangleIndexes[i + 1], v2 = triangleIndexes[i + 2];
|
||||
triangle[0].rx() = points[v0 * 2];
|
||||
triangle[0].ry() = points[v0 * 2 + 1];
|
||||
triangle[1].rx() = points[v1 * 2];
|
||||
triangle[1].ry() = points[v1 * 2 + 1];
|
||||
triangle[2].rx() = points[v2 * 2];
|
||||
triangle[2].ry() = points[v2 * 2 + 1];
|
||||
|
||||
if ( elevationMap )
|
||||
{
|
||||
elev[0] = triangulation.elevations[v0];
|
||||
elev[1] = triangulation.elevations[v1];
|
||||
elev[2] = triangulation.elevations[v2];
|
||||
}
|
||||
|
||||
QRgb c0 = triangulation.colors[v0], c1 = triangulation.colors[v1], c2 = triangulation.colors[v2];
|
||||
renderTriangle( img, triangle, c0, c1, c2, horizontalFilter, elev, elevationMap );
|
||||
}
|
||||
|
||||
painter->drawImage( 0, 0, img );
|
||||
}
|
||||
|
||||
bool QgsPointCloudLayerRenderer::forceRasterRender() const
|
||||
{
|
||||
// unless we are using the extent only renderer, point cloud layers should always be rasterized -- we don't want to export points as vectors
|
||||
|
||||
@ -75,6 +75,7 @@ class CORE_EXPORT QgsPointCloudLayerRenderer: public QgsMapLayerRenderer
|
||||
int renderNodesSync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled );
|
||||
int renderNodesAsync( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled );
|
||||
int renderNodesSorted( const QVector<IndexedPointCloudNode> &nodes, QgsPointCloudIndex *pc, QgsPointCloudRenderContext &context, QgsPointCloudRequest &request, bool &canceled, Qgis::PointCloudDrawOrder order );
|
||||
void renderTriangulatedSurface( QgsPointCloudRenderContext &context );
|
||||
bool renderIndex( QgsPointCloudIndex *pc );
|
||||
|
||||
QgsPointCloudLayer *mLayer = nullptr;
|
||||
|
||||
@ -195,6 +195,11 @@ void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destina
|
||||
destination->setMaximumScreenErrorUnit( mMaximumScreenErrorUnit );
|
||||
destination->setPointSymbol( mPointSymbol );
|
||||
destination->setDrawOrder2d( mDrawOrder2d );
|
||||
|
||||
destination->setRenderAsTriangles( mRenderAsTriangles );
|
||||
destination->setHorizontalTriangleFilter( mHorizontalTriangleFilter );
|
||||
destination->setHorizontalTriangleFilterThreshold( mHorizontalTriangleFilterThreshold );
|
||||
destination->setHorizontalTriangleFilterUnit( mHorizontalTriangleFilterUnit );
|
||||
}
|
||||
|
||||
void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & )
|
||||
@ -207,6 +212,11 @@ void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element,
|
||||
mMaximumScreenErrorUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "maximumScreenErrorUnit" ), QStringLiteral( "MM" ) ) );
|
||||
mPointSymbol = static_cast< Qgis::PointCloudSymbol >( element.attribute( QStringLiteral( "pointSymbol" ), QStringLiteral( "0" ) ).toInt() );
|
||||
mDrawOrder2d = static_cast< Qgis::PointCloudDrawOrder >( element.attribute( QStringLiteral( "drawOrder2d" ), QStringLiteral( "0" ) ).toInt() );
|
||||
|
||||
mRenderAsTriangles = element.attribute( QStringLiteral( "renderAsTriangles" ), QStringLiteral( "0" ) ).toInt();
|
||||
mHorizontalTriangleFilter = element.attribute( QStringLiteral( "horizontalTriangleFilter" ), QStringLiteral( "0" ) ).toInt();
|
||||
mHorizontalTriangleFilterThreshold = element.attribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), QStringLiteral( "5" ) ).toDouble();
|
||||
mHorizontalTriangleFilterUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QStringLiteral( "MM" ) ) );
|
||||
}
|
||||
|
||||
void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const
|
||||
@ -219,6 +229,11 @@ void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const Qg
|
||||
element.setAttribute( QStringLiteral( "maximumScreenErrorUnit" ), QgsUnitTypes::encodeUnit( mMaximumScreenErrorUnit ) );
|
||||
element.setAttribute( QStringLiteral( "pointSymbol" ), QString::number( static_cast< int >( mPointSymbol ) ) );
|
||||
element.setAttribute( QStringLiteral( "drawOrder2d" ), QString::number( static_cast< int >( mDrawOrder2d ) ) );
|
||||
|
||||
element.setAttribute( QStringLiteral( "renderAsTriangles" ), QString::number( static_cast< int >( mRenderAsTriangles ) ) );
|
||||
element.setAttribute( QStringLiteral( "horizontalTriangleFilter" ), QString::number( static_cast< int >( mHorizontalTriangleFilter ) ) );
|
||||
element.setAttribute( QStringLiteral( "horizontalTriangleFilterThreshold" ), qgsDoubleToString( mHorizontalTriangleFilterThreshold ) );
|
||||
element.setAttribute( QStringLiteral( "horizontalTriangleFilterUnit" ), QgsUnitTypes::encodeUnit( mHorizontalTriangleFilterUnit ) );
|
||||
}
|
||||
|
||||
Qgis::PointCloudSymbol QgsPointCloudRenderer::pointSymbol() const
|
||||
|
||||
@ -225,6 +225,28 @@ class CORE_EXPORT QgsPointCloudRenderContext
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef SIP_RUN // this is only meant for low-level rendering in C++ code
|
||||
|
||||
/**
|
||||
* Helper data structure used when rendering points as triangulated surface.
|
||||
* We populate the structure as we traverse the nodes and then run Delaunay
|
||||
* triangulation at the end + draw the triangles.
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
struct TriangulationData
|
||||
{
|
||||
std::vector<double> points; //!< X,Y for each point - kept in this structure so that we can use it without further conversions in Delaunator-cpp
|
||||
std::vector<QRgb> colors; //!< RGB color for each point
|
||||
std::vector<float> elevations; //!< Z value for each point (only used when global map shading is enabled)
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns reference to the triangulation data structure (only used when rendering as triangles is enabled)
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
TriangulationData &triangulationData() { return mTriangulationData; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
#ifdef SIP_RUN
|
||||
QgsPointCloudRenderContext( const QgsPointCloudRenderContext &rh );
|
||||
@ -243,6 +265,8 @@ class CORE_EXPORT QgsPointCloudRenderContext
|
||||
double mZValueFixedOffset = 0;
|
||||
|
||||
QgsFeedback *mFeedback = nullptr;
|
||||
|
||||
TriangulationData mTriangulationData;
|
||||
};
|
||||
|
||||
#ifndef SIP_RUN
|
||||
@ -561,6 +585,92 @@ class CORE_EXPORT QgsPointCloudRenderer
|
||||
*/
|
||||
void setMaximumScreenErrorUnit( Qgis::RenderUnit unit );
|
||||
|
||||
/**
|
||||
* Returns whether points are triangulated to render solid surface
|
||||
*
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
bool renderAsTriangles() const { return mRenderAsTriangles; }
|
||||
|
||||
/**
|
||||
* Sets whether points are triangulated to render solid surface
|
||||
*
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
void setRenderAsTriangles( bool asTriangles ) { mRenderAsTriangles = asTriangles; }
|
||||
|
||||
/**
|
||||
* Returns whether large triangles will get rendered. This only applies when renderAsTriangles()
|
||||
* is enabled. When the triangle filtering is enabled, triangles where at least one side is
|
||||
* horizontally longer than the threshold in horizontalTriangleFilterThreshold() do not get rendered.
|
||||
*
|
||||
* \see horizontalTriangleFilterThreshold()
|
||||
* \see horizontalTriangleFilterUnit()
|
||||
* \see setHorizontalTriangleFilter()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
bool horizontalTriangleFilter() const { return mHorizontalTriangleFilter; }
|
||||
|
||||
/**
|
||||
* Sets whether large triangles will get rendered. This only applies when renderAsTriangles()
|
||||
* is enabled. When the triangle filtering is enabled, triangles where at least one side is
|
||||
* horizontally longer than the threshold in horizontalTriangleFilterThreshold() do not get rendered.
|
||||
*
|
||||
* \see setHorizontalTriangleFilterThreshold()
|
||||
* \see setHorizontalTriangleFilterUnit()
|
||||
* \see horizontalTriangleFilter()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
void setHorizontalTriangleFilter( bool enabled ) { mHorizontalTriangleFilter = enabled; }
|
||||
|
||||
/**
|
||||
* Returns threshold for filtering of triangles. This only applies when renderAsTriangles() and
|
||||
* horizontalTriangleFilter() are both enabled. If any edge of a triangle is horizontally longer
|
||||
* than the threshold, such triangle will not get rendered. Units of the threshold value are
|
||||
* given by horizontalTriangleFilterUnits().
|
||||
*
|
||||
* \see horizontalTriangleFilter()
|
||||
* \see horizontalTriangleFilterUnit()
|
||||
* \see setHorizontalTriangleFilterThreshold()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
double horizontalTriangleFilterThreshold() const { return mHorizontalTriangleFilterThreshold; }
|
||||
|
||||
/**
|
||||
* Sets threshold for filtering of triangles. This only applies when renderAsTriangles() and
|
||||
* horizontalTriangleFilter() are both enabled. If any edge of a triangle is horizontally longer
|
||||
* than the threshold, such triangle will not get rendered. Units of the threshold value are
|
||||
* given by horizontalTriangleFilterUnits().
|
||||
*
|
||||
* \see horizontalTriangleFilter()
|
||||
* \see horizontalTriangleFilterUnit()
|
||||
* \see horizontalTriangleFilterThreshold()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
void setHorizontalTriangleFilterThreshold( double threshold ) { mHorizontalTriangleFilterThreshold = threshold; }
|
||||
|
||||
/**
|
||||
* Returns units of the threshold for filtering of triangles. This only applies when renderAsTriangles() and
|
||||
* horizontalTriangleFilter() are both enabled.
|
||||
*
|
||||
* \see horizontalTriangleFilter()
|
||||
* \see horizontalTriangleFilterThreshold()
|
||||
* \see setHorizontalTriangleFilterUnit()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
Qgis::RenderUnit horizontalTriangleFilterUnit() const { return mHorizontalTriangleFilterUnit; }
|
||||
|
||||
/**
|
||||
* Sets units of the threshold for filtering of triangles. This only applies when renderAsTriangles() and
|
||||
* horizontalTriangleFilter() are both enabled.
|
||||
*
|
||||
* \see horizontalTriangleFilter()
|
||||
* \see horizontalTriangleFilterThreshold()
|
||||
* \see horizontalTriangleFilterUnit()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
void setHorizontalTriangleFilterUnit( Qgis::RenderUnit unit ) { mHorizontalTriangleFilterUnit = unit; }
|
||||
|
||||
/**
|
||||
* Creates a set of legend nodes representing the renderer.
|
||||
*/
|
||||
@ -632,6 +742,21 @@ class CORE_EXPORT QgsPointCloudRenderer
|
||||
void drawPointToElevationMap( double x, double y, double z, QgsPointCloudRenderContext &context ) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Adds a point to the list of points to be triangulated (only used when renderAsTriangles() is enabled)
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
void addPointToTriangulation( double x, double y, double z, const QColor &color, QgsPointCloudRenderContext &context )
|
||||
{
|
||||
QgsPointXY p = context.renderContext().mapToPixel().transform( x, y ) * context.renderContext().devicePixelRatio();
|
||||
QgsPointCloudRenderContext::TriangulationData &triangulation = context.triangulationData();
|
||||
triangulation.points.push_back( p.x() );
|
||||
triangulation.points.push_back( p.y() );
|
||||
triangulation.colors.push_back( color.rgb() );
|
||||
if ( context.renderContext().elevationMap() )
|
||||
triangulation.elevations.push_back( static_cast<float>( z ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies common point cloud properties (such as point size and screen error) to the \a destination renderer.
|
||||
*/
|
||||
@ -673,6 +798,11 @@ class CORE_EXPORT QgsPointCloudRenderer
|
||||
Qgis::PointCloudSymbol mPointSymbol = Qgis::PointCloudSymbol::Square;
|
||||
int mPainterPenWidth = 1;
|
||||
Qgis::PointCloudDrawOrder mDrawOrder2d = Qgis::PointCloudDrawOrder::Default;
|
||||
|
||||
bool mRenderAsTriangles = false;
|
||||
bool mHorizontalTriangleFilter = false;
|
||||
double mHorizontalTriangleFilterThreshold = 5.0;
|
||||
Qgis::RenderUnit mHorizontalTriangleFilterUnit = Qgis::RenderUnit::Millimeters;
|
||||
};
|
||||
|
||||
#endif // QGSPOINTCLOUDRENDERER_H
|
||||
|
||||
@ -56,7 +56,13 @@ QgsPointCloudRenderer *QgsPointCloudRgbRenderer::clone() const
|
||||
|
||||
void QgsPointCloudRgbRenderer::renderBlock( const QgsPointCloudBlock *block, QgsPointCloudRenderContext &context )
|
||||
{
|
||||
const QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
QgsRectangle visibleExtent = context.renderContext().extent();
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
// we need to include also points slightly outside of the visible extent,
|
||||
// otherwise the triangulation may be missing triangles near the edges and corners
|
||||
visibleExtent.grow( std::max( visibleExtent.width(), visibleExtent.height() ) * 0.05 );
|
||||
}
|
||||
|
||||
const char *ptr = block->data();
|
||||
const int count = block->pointCount();
|
||||
@ -158,9 +164,16 @@ void QgsPointCloudRgbRenderer::renderBlock( const QgsPointCloudBlock *block, Qgs
|
||||
green = std::max( 0, std::min( 255, green ) );
|
||||
blue = std::max( 0, std::min( 255, blue ) );
|
||||
|
||||
drawPoint( x, y, QColor( red, green, blue ), context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
if ( renderAsTriangles() )
|
||||
{
|
||||
addPointToTriangulation( x, y, z, QColor( red, green, blue ), context );
|
||||
}
|
||||
else
|
||||
{
|
||||
drawPoint( x, y, QColor( red, green, blue ), context );
|
||||
if ( renderElevation )
|
||||
drawPointToElevationMap( x, y, z, context );
|
||||
}
|
||||
rendered++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
/***************************************************************************
|
||||
qgsarcgisrestutils.cpp
|
||||
----------------------
|
||||
begin : Nov 25, 2015
|
||||
copyright : (C) 2015 by Sandro Mani
|
||||
email : manisandro@gmail.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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
qgsarcgisrestutils.cpp
|
||||
----------------------
|
||||
begin : Nov 25, 2015
|
||||
copyright : (C) 2015 by Sandro Mani
|
||||
email : manisandro@gmail.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 "qgsarcgisrestutils.h"
|
||||
#include "qgsfields.h"
|
||||
@ -1412,6 +1412,8 @@ QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon )
|
||||
rings.push_back( lineStringToJsonPath( reversed.get() ) );
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1431,6 +1433,8 @@ QVariantList QgsArcGisRestUtils::polygonToJsonRings( const QgsPolygon *polygon )
|
||||
rings.push_back( lineStringToJsonPath( reversed.get() ) );
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rings;
|
||||
@ -1457,6 +1461,8 @@ QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon
|
||||
rings.push_back( curveToJsonCurve( reversed.get(), true ) );
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1476,6 +1482,8 @@ QVariantList QgsArcGisRestUtils::curvePolygonToJsonRings( const QgsCurvePolygon
|
||||
rings.push_back( curveToJsonCurve( reversed.get(), true ) );
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return rings;
|
||||
|
||||
@ -4265,10 +4265,10 @@ QList<QPair<QString, QString> > QgsGdalProviderMetadata::pyramidResamplingMethod
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "NEAREST" ), QObject::tr( "Nearest Neighbour" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "AVERAGE" ), QObject::tr( "Average" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "GAUSS" ), QObject::tr( "Gauss" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "CUBIC" ), QObject::tr( "Cubic" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "CUBICSPLINE" ), QObject::tr( "Cubic Spline" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "LANCZOS" ), QObject::tr( "Lanczos" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "BILINEAR" ), QObject::tr( "Bilinear" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "CUBIC" ), QObject::tr( "Cubic (4x4 Kernel)" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "CUBICSPLINE" ), QObject::tr( "Cubic B-Spline (4x4 Kernel)" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "LANCZOS" ), QObject::tr( "Lanczos (6x6 Kernel)" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "BILINEAR" ), QObject::tr( "Bilinear (2x2 Kernel)" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "MODE" ), QObject::tr( "Mode" ) ) );
|
||||
methods.append( QPair<QString, QString>( QStringLiteral( "NONE" ), QObject::tr( "None" ) ) );
|
||||
}
|
||||
|
||||
@ -1128,7 +1128,10 @@ QgsRectangle QgsOgrProvider::extent() const
|
||||
// get the extent_ (envelope) of the layer
|
||||
QgsDebugMsgLevel( QStringLiteral( "Starting get extent" ), 3 );
|
||||
|
||||
if ( mForceRecomputeExtent && mValid && mGDALDriverName == QLatin1String( "GPKG" ) && mOgrOrigLayer )
|
||||
if ( mForceRecomputeExtent && mValid && mWriteAccess &&
|
||||
( mGDALDriverName == QLatin1String( "GPKG" ) ||
|
||||
mGDALDriverName == QLatin1String( "ESRI Shapefile" ) ) &&
|
||||
mOgrOrigLayer )
|
||||
{
|
||||
// works with unquoted layerName
|
||||
QByteArray sql = QByteArray( "RECOMPUTE EXTENT ON " ) + mOgrOrigLayer->name();
|
||||
|
||||
@ -27,11 +27,11 @@
|
||||
#include "qgscoordinatetransformcontext.h"
|
||||
#include "qgslayermetadata.h"
|
||||
#include "qgserror.h"
|
||||
#include "qgsdataproviderelevationproperties.h"
|
||||
|
||||
class QgsRectangle;
|
||||
class QgsCoordinateReferenceSystem;
|
||||
class QgsDataProviderTemporalCapabilities;
|
||||
class QgsDataProviderElevationProperties;
|
||||
|
||||
|
||||
/**
|
||||
@ -275,6 +275,15 @@ class CORE_EXPORT QgsDataProvider : public QObject
|
||||
*/
|
||||
virtual QgsRectangle extent() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the 3D extent of the layer
|
||||
* \returns QgsBox3D containing the 3D extent of the layer
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
virtual QgsBox3D extent3D() const
|
||||
{
|
||||
return extent().toBox3d( std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if this is a valid layer. It is up to individual providers
|
||||
|
||||
@ -2565,6 +2565,7 @@ class CORE_EXPORT Qgis
|
||||
{
|
||||
Clockwise, //!< Clockwise direction
|
||||
CounterClockwise, //!< Counter-clockwise direction
|
||||
NoOrientation, //!< Unknown orientation or sentinel value
|
||||
};
|
||||
Q_ENUM( AngularDirection )
|
||||
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
/***************************************************************************
|
||||
qgscolorrampimpl.cpp
|
||||
---------------------
|
||||
begin : November 2009
|
||||
copyright : (C) 2009 by Martin Dobias
|
||||
email : wonder dot sk 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. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
qgscolorrampimpl.cpp
|
||||
---------------------
|
||||
begin : November 2009
|
||||
copyright : (C) 2009 by Martin Dobias
|
||||
email : wonder dot sk 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 "qgscolorrampimpl.h"
|
||||
#include "qgscolorbrewerpalette.h"
|
||||
@ -94,6 +94,8 @@ static QColor _interpolateHsv( const QColor &c1, const QColor &c2, const double
|
||||
hue -= 1;
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,6 +153,8 @@ static QColor _interpolateHsl( const QColor &c1, const QColor &c2, const double
|
||||
hue -= 1;
|
||||
break;
|
||||
}
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,6 +477,8 @@ QVariantMap QgsGradientColorRamp::properties() const
|
||||
case Qgis::AngularDirection::CounterClockwise:
|
||||
map[QStringLiteral( "direction" ) ] = QStringLiteral( "ccw" );
|
||||
break;
|
||||
case Qgis::AngularDirection::NoOrientation:
|
||||
break;
|
||||
}
|
||||
|
||||
map[QStringLiteral( "rampType" )] = type();
|
||||
|
||||
@ -19,3 +19,13 @@
|
||||
QgsDataProviderElevationProperties::QgsDataProviderElevationProperties() = default;
|
||||
|
||||
QgsDataProviderElevationProperties::~QgsDataProviderElevationProperties() = default;
|
||||
|
||||
bool QgsDataProviderElevationProperties::containsElevationData() const
|
||||
{
|
||||
return mContainsElevationData;
|
||||
}
|
||||
|
||||
void QgsDataProviderElevationProperties::setContainsElevationData( bool contains )
|
||||
{
|
||||
mContainsElevationData = contains;
|
||||
}
|
||||
|
||||
@ -52,7 +52,30 @@ class CORE_EXPORT QgsDataProviderElevationProperties
|
||||
*/
|
||||
QgsDataProviderElevationProperties();
|
||||
|
||||
/**
|
||||
* Returns TRUE if the data provider definitely contains elevation related data.
|
||||
*
|
||||
* \note Even if this method returns FALSE, the data may still relate to elevation values. TRUE will only
|
||||
* be returned in situations where elevation data is definitively present.
|
||||
*
|
||||
* \see setContainsElevationData()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
virtual bool containsElevationData() const;
|
||||
|
||||
/**
|
||||
* Sets whether the data provider definitely contains elevation related data.
|
||||
*
|
||||
* \see containsElevationData()
|
||||
* \since QGIS 3.36
|
||||
*/
|
||||
virtual void setContainsElevationData( bool contains );
|
||||
|
||||
virtual ~QgsDataProviderElevationProperties();
|
||||
|
||||
protected:
|
||||
bool mContainsElevationData = false;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSDATAPROVIDERELEVATIONPROPERTIES_H
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user