Merge remote-tracking branch 'upstream/master'

This commit is contained in:
tomass 2023-11-25 18:46:16 +02:00
commit c504439ebf
595 changed files with 2718 additions and 923 deletions

View File

@ -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
#

View File

@ -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 \

View File

@ -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 --

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -1508,6 +1508,7 @@ The development version
{
Clockwise,
CounterClockwise,
NoOrientation,
};
enum class RendererUsage

View File

@ -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:
};
/************************************************************************

View File

@ -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

View File

@ -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;

View File

@ -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 );

View File

@ -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" ) );

View File

@ -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
};

View File

@ -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;

View File

@ -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

View File

@ -7,6 +7,7 @@
************************************************************************/
class QgsQmlWidgetWrapper : QgsWidgetWrapper
{
%Docstring(signature="appended")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'))

View File

@ -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'),

View File

@ -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'))

View File

@ -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,

View File

@ -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'),

View File

@ -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']

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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():

View File

@ -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):

View File

@ -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 = []

View File

@ -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"]
}

View File

@ -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"]
}

View File

@ -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++ )

View File

@ -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() );
}
}

View File

@ -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;
};

View File

@ -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])

View File

@ -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;
};

View File

@ -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" ) ) );

View File

@ -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;

View File

@ -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;
};

View File

@ -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 );

View File

@ -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;

View File

@ -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;

View File

@ -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 );

View File

@ -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();

View File

@ -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 );

View File

@ -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 );
}

View File

@ -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 )

View File

@ -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();

View File

@ -46,6 +46,7 @@ class QgsAdvancedSettingsWidget : public QgsOptionsPageWidget, private Ui::QgsAd
*/
QgsAdvancedSettingsWidget( QWidget *parent );
~QgsAdvancedSettingsWidget() override;
QString helpKey() const override;
void apply() override;
private:

View File

@ -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()

View File

@ -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" );
}

View File

@ -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(

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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();

View File

@ -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:

View File

@ -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() )

View File

@ -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:

View File

@ -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 )
{

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -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" ) );
}

View File

@ -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

View File

@ -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 );
}

View File

@ -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;

View File

@ -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)

View File

@ -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
{

View File

@ -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 )

View File

@ -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
*/

View File

@ -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;

View File

@ -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 )
{

View File

@ -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 )
{

View File

@ -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

View File

@ -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++;
}

View File

@ -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++;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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++;
}
}

View File

@ -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;

View File

@ -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" ) ) );
}

View File

@ -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();

View File

@ -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

View File

@ -2565,6 +2565,7 @@ class CORE_EXPORT Qgis
{
Clockwise, //!< Clockwise direction
CounterClockwise, //!< Counter-clockwise direction
NoOrientation, //!< Unknown orientation or sentinel value
};
Q_ENUM( AngularDirection )

View File

@ -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();

View File

@ -19,3 +19,13 @@
QgsDataProviderElevationProperties::QgsDataProviderElevationProperties() = default;
QgsDataProviderElevationProperties::~QgsDataProviderElevationProperties() = default;
bool QgsDataProviderElevationProperties::containsElevationData() const
{
return mContainsElevationData;
}
void QgsDataProviderElevationProperties::setContainsElevationData( bool contains )
{
mContainsElevationData = contains;
}

View File

@ -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