Start porting map item

This commit is contained in:
Nyall Dawson 2017-10-20 16:42:35 +10:00
parent 71c41d49b7
commit 23d0617bac
19 changed files with 2646 additions and 57 deletions

View File

@ -183,6 +183,17 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
.. seealso:: parentGroup()
%End
virtual int numberExportLayers() const;
%Docstring
Returns the number of layers that this item requires for exporting during layered exports (e.g. SVG).
Returns 0 if this item is to be placed on the same layer as the previous item,
1 if it should be placed on its own layer, and >1 if it requires multiple export layers.
Items which require multiply layers should check QgsLayoutContext.currentExportLayer() during
their rendering to determine which layer should be drawn.
:rtype: int
%End
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget );
%Docstring
@ -514,6 +525,21 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
:rtype: QRectF
%End
virtual void moveContent( double dx, double dy );
%Docstring
Moves the content of the item, by a specified ``dx`` and ``dy`` in layout units.
The default implementation has no effect.
.. seealso:: zoomContent()
%End
virtual void zoomContent( double factor, QPointF point );
%Docstring
Zooms content of item. Does nothing by default.
\param factor zoom factor, where > 1 results in a zoom in and < 1 results in a zoom out
\param point item point for zoom center
.. seealso:: moveContent()
%End
public slots:
virtual void refresh();
@ -716,6 +742,12 @@ class QgsLayoutItem : QgsLayoutObject, QGraphicsRectItem, QgsLayoutUndoObjectInt
:rtype: bool
%End
bool shouldDrawItem() const;
%Docstring
Returns whether the item should be drawn in the current context.
:rtype: bool
%End
};

View File

@ -8,6 +8,7 @@
class QgsLayoutItemMap : QgsLayoutItem
{
%Docstring
@ -20,15 +21,33 @@ class QgsLayoutItemMap : QgsLayoutItem
%End
public:
enum AtlasScalingMode
{
Fixed,
Predefined,
Auto
};
explicit QgsLayoutItemMap( QgsLayout *layout );
%Docstring
Constructor for QgsLayoutItemMap, with the specified parent ``layout``.
%End
~QgsLayoutItemMap();
virtual int type() const;
virtual QString stringType() const;
void assignFreeId();
%Docstring
Sets the map id() to a number not yet used in the layout. The existing id() is kept if it is not in use.
%End
virtual QString displayName() const;
static QgsLayoutItemMap *create( QgsLayout *layout ) /Factory/;
%Docstring
Returns a new map item for the specified ``layout``.
@ -37,10 +56,410 @@ class QgsLayoutItemMap : QgsLayoutItem
:rtype: QgsLayoutItemMap
%End
virtual void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget );
virtual int numberExportLayers() const;
virtual void setFrameStrokeWidth( const QgsLayoutMeasurement &width );
double scale() const;
%Docstring
Returns the map scale.
The scale value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
.. seealso:: setScale()
:rtype: float
%End
void setScale( double scale, bool forceUpdate = true );
%Docstring
Sets new map ``scale`` and changes only the map extent.
The ``scale`` value indicates the scale denominator, e.g. 1000.0 for a 1:1000 map.
.. seealso:: scale()
%End
void setExtent( const QgsRectangle &extent );
%Docstring
Sets a new ``extent`` for the map. This method may change the width or height of the map
item to ensure that the extent exactly matches the specified extent, with no
overlap or margin. This method implicitly alters the map scale.
.. seealso:: zoomToExtent()
%End
void zoomToExtent( const QgsRectangle &extent );
%Docstring
Zooms the map so that the specified ``extent`` is fully visible within the map item.
This method will not change the width or height of the map, and may result in
an overlap or margin from the specified extent. This method implicitly alters the
map scale.
.. seealso:: setExtent()
%End
QgsRectangle extent() const;
%Docstring
Returns the current map extent.
.. seealso:: visibleExtentPolygon()
:rtype: QgsRectangle
%End
QPolygonF visibleExtentPolygon() const;
%Docstring
Returns a polygon representing the current visible map extent, considering map extents and rotation.
If the map rotation is 0, the result is the same as currentMapExtent
:return: polygon with the four corner points representing the visible map extent. The points are
clockwise, starting at the top-left point
.. seealso:: extent()
:rtype: QPolygonF
%End
QgsCoordinateReferenceSystem crs() const;
%Docstring
Returns coordinate reference system used for rendering the map.
This will match the presetCrs() if that is set, or if a preset
CRS is not set then the map's CRS will follow the composition's
project's CRS.
.. seealso:: presetCrs()
.. seealso:: setCrs()
:rtype: QgsCoordinateReferenceSystem
%End
QgsCoordinateReferenceSystem presetCrs() const;
%Docstring
Returns the map's preset coordinate reference system. If set, this
CRS will be used to render the map regardless of any project CRS
setting. If the returned CRS is not valid then the project CRS
will be used to render the map.
.. seealso:: crs()
.. seealso:: setCrs()
:rtype: QgsCoordinateReferenceSystem
%End
void setCrs( const QgsCoordinateReferenceSystem &crs );
%Docstring
Sets the map's preset ``crs`` (coordinate reference system). If a valid CRS is
set, this CRS will be used to render the map regardless of any project CRS
setting. If the CRS is not valid then the project CRS will be used to render the map.
.. seealso:: crs()
.. seealso:: presetCrs()
%End
bool keepLayerSet() const;
%Docstring
Returns whether a stored layer set should be used
or the current layer set from the project associated with the layout. This is just a GUI flag,
and itself does not change which layers are rendered in the map.
Instead, use setLayers() to control which layers are rendered.
.. seealso:: setKeepLayerSet()
.. seealso:: layers()
:rtype: bool
%End
void setKeepLayerSet( bool enabled );
%Docstring
Sets whether the stored layer set should be used
or the current layer set of the associated project. This is just a GUI flag,
and itself does not change which layers are rendered in the map.
Instead, use setLayers() to control which layers are rendered.
.. seealso:: keepLayerSet()
.. seealso:: layers()
%End
QList<QgsMapLayer *> layers() const;
%Docstring
Returns the stored layer set. If empty, the current project layers will
be used instead.
.. seealso:: setLayers()
.. seealso:: keepLayerSet()
:rtype: list of QgsMapLayer
%End
void setLayers( const QList<QgsMapLayer *> &layers );
%Docstring
Sets the stored ``layers`` set. If empty, the current project layers will
be used instead.
.. seealso:: layers()
.. seealso:: keepLayerSet()
%End
bool keepLayerStyles() const;
%Docstring
Returns whether current styles of layers should be overridden by previously stored styles.
.. seealso:: setKeepLayerStyles()
:rtype: bool
%End
void setKeepLayerStyles( bool enabled );
%Docstring
Sets whether current styles of layers should be overridden by previously stored styles.
.. seealso:: keepLayerStyles()
%End
QMap<QString, QString> layerStyleOverrides() const;
%Docstring
Returns stored overrides of styles for layers.
.. seealso:: setLayerStyleOverrides()
:rtype: QMap<str, QString>
%End
void setLayerStyleOverrides( const QMap<QString, QString> &overrides );
%Docstring
Sets the stored overrides of styles for layers.
.. seealso:: layerStyleOverrides()
%End
void storeCurrentLayerStyles();
%Docstring
Stores the current project layer styles into style overrides.
%End
bool followVisibilityPreset() const;
%Docstring
Returns whether the map should follow a map theme. If true, the layers and layer styles
will be used from given preset name (configured with setFollowVisibilityPresetName() method).
This means when preset's settings are changed, the new settings are automatically
picked up next time when rendering, without having to explicitly update them.
At most one of the flags keepLayerSet() and followVisibilityPreset() should be enabled
at any time since they are alternative approaches - if both are enabled,
following map theme has higher priority. If neither is enabled (or if preset name is not set),
map will use the same configuration as the map canvas uses.
:rtype: bool
%End
void setFollowVisibilityPreset( bool follow );
%Docstring
Sets whether the map should follow a map theme. See followVisibilityPreset() for more details.
%End
QString followVisibilityPresetName() const;
%Docstring
Preset name that decides which layers and layer styles are used for map rendering. It is only
used when followVisibilityPreset() returns true.
.. seealso:: setFollowVisibilityPresetName()
:rtype: str
%End
void setFollowVisibilityPresetName( const QString &name );
%Docstring
Sets preset name for map rendering. See followVisibilityPresetName() for more details.
.. seealso:: followVisibilityPresetName()
%End
virtual void moveContent( double dx, double dy );
virtual void zoomContent( double factor, QPointF point );
bool containsWmsLayer() const;
%Docstring
Returns true if the map contains a WMS layer.
:rtype: bool
%End
bool containsAdvancedEffects() const;
%Docstring
Returns true if the map contains layers with blend modes or flattened layers for vectors
:rtype: bool
%End
void setMapRotation( double rotation );
%Docstring
Sets the ``rotation`` for the map - this does not affect the composer item shape, only the
way the map is drawn within the item. Rotation is in degrees, clockwise.
.. seealso:: mapRotation()
%End
double mapRotation( QgsLayoutObject::PropertyValueType valueType = QgsLayoutObject::EvaluatedValue ) const;
%Docstring
Returns the rotation used for drawing the map within the composer item, in degrees clockwise.
\param valueType controls whether the returned value is the user specified rotation,
or the current evaluated rotation (which may be affected by data driven rotation
settings).
.. seealso:: setMapRotation()
:rtype: float
%End
void setDrawAnnotations( bool draw );
%Docstring
Sets whether annotations are drawn within the composer map.
.. seealso:: drawAnnotations()
%End
bool drawAnnotations() const;
%Docstring
Returns whether annotations are drawn within the composer map.
.. seealso:: setDrawAnnotations()
:rtype: bool
%End
bool atlasDriven() const;
%Docstring
Returns whether the map extent is set to follow the current atlas feature.
:return: true if map will follow the current atlas feature.
.. seealso:: setAtlasDriven
.. seealso:: atlasScalingMode
:rtype: bool
%End
void setAtlasDriven( bool enabled );
%Docstring
Sets whether the map extent will follow the current atlas feature.
\param enabled set to true if the map extents should be set by the current atlas feature.
.. seealso:: atlasDriven
.. seealso:: setAtlasScalingMode
%End
AtlasScalingMode atlasScalingMode() const;
%Docstring
Returns the current atlas scaling mode. This controls how the map's extents
are calculated for the current atlas feature when an atlas composition
is enabled.
:return: the current scaling mode
.. note::
this parameter is only used if atlasDriven() is true
.. seealso:: setAtlasScalingMode
.. seealso:: atlasDriven
:rtype: AtlasScalingMode
%End
void setAtlasScalingMode( AtlasScalingMode mode );
%Docstring
Sets the current atlas scaling mode. This controls how the map's extents
are calculated for the current atlas feature when an atlas composition
is enabled.
\param mode atlas scaling mode to set
.. note::
this parameter is only used if atlasDriven() is true
.. seealso:: atlasScalingMode
.. seealso:: atlasDriven
%End
double atlasMargin( const QgsLayoutObject::PropertyValueType valueType = QgsLayoutObject::EvaluatedValue );
%Docstring
Returns the margin size (percentage) used when the map is in atlas mode.
\param valueType controls whether the returned value is the user specified atlas margin,
or the current evaluated atlas margin (which may be affected by data driven atlas margin
settings).
:return: margin size in percentage to leave around the atlas feature's extent
.. note::
this is only used if atlasScalingMode() is Auto.
.. seealso:: atlasScalingMode
.. seealso:: setAtlasMargin
:rtype: float
%End
void setAtlasMargin( double margin );
%Docstring
Sets the margin size (percentage) used when the map is in atlas mode.
\param margin size in percentage to leave around the atlas feature's extent
.. note::
this is only used if atlasScalingMode() is Auto.
.. seealso:: atlasScalingMode
.. seealso:: atlasMargin
%End
protected:
virtual void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = 0 );
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
%Docstring
Return map settings that will be used for drawing of the map.
:rtype: QgsMapSettings
%End
bool isDrawing() const;
%Docstring
True if a draw is already in progress
:rtype: bool
%End
void setOffset( double xOffset, double yOffset );
%Docstring
Sets offset values to shift image (useful for live updates when moving item content)
%End
virtual QRectF boundingRect() const;
virtual QgsExpressionContext createExpressionContext() const;
double mapUnitsToLayoutUnits() const;
%Docstring
Returns the conversion factor from map units to layout units.
This is calculated using the width of the map item and the width of the
current visible map extent.
:rtype: float
%End
QPolygonF transformedMapPolygon() const;
%Docstring
Returns extent that considers rotation and shift with mOffsetX / mOffsetY
:rtype: QPolygonF
%End
QPointF mapToItemCoords( QPointF mapCoords ) const;
%Docstring
Transforms map coordinates to item coordinates (considering rotation and move offset)
:rtype: QPointF
%End
QgsRectangle requestedExtent() const;
%Docstring
Calculates the extent to request and the yShift of the top-left point in case of rotation.
:rtype: QgsRectangle
%End
signals:
void extentChanged();
void mapRotationChanged( double newRotation );
%Docstring
Is emitted on rotation change to notify north arrow pictures
%End
void preparedForAtlas();
%Docstring
Is emitted when the map has been prepared for atlas rendering, just before actual rendering
%End
void layerStyleOverridesChanged();
%Docstring
Emitted when layer style overrides are changed... a means to let
associated legend items know they should update
%End
public slots:
void invalidateCache();
%Docstring
Forces a deferred update of the cached map image on next paint.
%End
void updateBoundingRect();
%Docstring
Updates the bounding rect of this item. Call this function before doing any changes related to annotation out of the map rectangle
%End
virtual void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties );
};
/************************************************************************

View File

@ -72,6 +72,12 @@ class QgsLayoutObject: QObject, QgsExpressionContextGenerator
ScalebarLineWidth,
};
enum PropertyValueType
{
EvaluatedValue,
OriginalValue
};
static const QgsPropertiesDefinition &propertyDefinitions();
%Docstring
Returns the layout object property definitions.

View File

@ -21,6 +21,14 @@ class QgsLayoutUtils
%End
public:
static void rotate( double angle, double &x, double &y );
%Docstring
Rotates a point / vector around the origin.
\param angle rotation angle in degrees, counterclockwise
\param x in/out: x coordinate before / after the rotation
\param y in/out: y coordinate before / after the rotation
%End
static double normalizedAngle( const double angle, const bool allowNegative = false );
%Docstring
Ensures that an ``angle`` (in degrees) is in the range 0 <= angle < 360.

View File

@ -203,6 +203,16 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
void setParentGroup( QgsLayoutItemGroup *group );
/**
* Returns the number of layers that this item requires for exporting during layered exports (e.g. SVG).
* Returns 0 if this item is to be placed on the same layer as the previous item,
* 1 if it should be placed on its own layer, and >1 if it requires multiple export layers.
*
* Items which require multiply layers should check QgsLayoutContext::currentExportLayer() during
* their rendering to determine which layer should be drawn.
*/
virtual int numberExportLayers() const { return 0; }
/**
* Handles preparing a paint surface for the layout item and painting the item's
* content. Derived classes must not override this method, but instead implement
@ -724,6 +734,11 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
virtual bool readPropertiesFromElement( const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context );
/**
* Returns whether the item should be drawn in the current context.
*/
bool shouldDrawItem() const;
private:
// true if layout manages the z value for this item
@ -800,11 +815,6 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
void setScenePos( const QPointF &destinationPos );
bool shouldBlockUndoCommands() const;
/**
* Returns whether the item should be drawn in the current context.
*/
bool shouldDrawItem() const;
friend class TestQgsLayoutItem;
friend class TestQgsLayoutView;
friend class QgsLayoutItemGroup;

File diff suppressed because it is too large Load Diff

View File

@ -69,6 +69,12 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
int type() const override;
QString stringType() const override;
/**
* Sets the map id() to a number not yet used in the layout. The existing id() is kept if it is not in use.
*/
void assignFreeId();
//overridden to show "Map 1" type names
virtual QString displayName() const override;
@ -79,6 +85,11 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
static QgsLayoutItemMap *create( QgsLayout *layout ) SIP_FACTORY;
// for now, map items behave a bit differently and don't implement draw. TODO - see if we can avoid this
void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override;
int numberExportLayers() const override;
void setFrameStrokeWidth( const QgsLayoutMeasurement &width ) override;
/**
* Returns the map scale.
@ -351,26 +362,12 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
void setAtlasMargin( double margin ) { mAtlasMargin = margin; }
protected:
void draw( QgsRenderContext &context, const QStyleOptionGraphicsItem *itemStyle = nullptr ) override;
/**
* \brief Draw to paint device
* \param painter painter
* \param extent map extent
* \param size size in scene coordinates
* \param dpi scene dpi
* \param forceWidthScale force wysiwyg line widths / marker sizes
*/
void draw( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi, double *forceWidthScale = nullptr );
void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override;
/**
* Return map settings that would be used for drawing of the map
* Return map settings that will be used for drawing of the map.
*/
QgsMapSettings mapSettings( const QgsRectangle &extent, QSizeF size, int dpi ) const;
@ -397,6 +394,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
void setOffset( double xOffset, double yOffset );
#if 0
/**
* Stores state in Dom node
* \param elem is Dom element corresponding to 'Composer' tag
@ -451,32 +449,15 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
#endif
QgsExpressionContext createExpressionContext() const override;
signals:
void extentChanged();
//! Is emitted on rotation change to notify north arrow pictures
void mapRotationChanged( double newRotation );
//! Is emitted when the map has been prepared for atlas rendering, just before actual rendering
void preparedForAtlas();
/**
* Emitted when layer style overrides are changed... a means to let
* associated legend items know they should update
* Returns the conversion factor from map units to layout units.
* This is calculated using the width of the map item and the width of the
* current visible map extent.
*/
void layerStyleOverridesChanged();
double mapUnitsToLayoutUnits() const;
#if 0
//! Returns the conversion factor map units -> mm
double mapUnitsToMM() const;
/**
* Sets mId to a number not yet used in the composition. mId is kept if it is not in use.
Usually, this function is called before adding the composer map to the composition*/
void assignFreeId();
/**
* Get the number of layers that this item requires for exporting as layers
* \returns 0 if this item is to be placed on the same layer as the previous item,
@ -494,7 +475,23 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
/**
* Calculates the extent to request and the yShift of the top-left point in case of rotation.
*/
void requestedExtent( QgsRectangle &extent ) const;
QgsRectangle requestedExtent() const;
signals:
void extentChanged();
//! Is emitted on rotation change to notify north arrow pictures
void mapRotationChanged( double newRotation );
//! Is emitted when the map has been prepared for atlas rendering, just before actual rendering
void preparedForAtlas();
/**
* Emitted when layer style overrides are changed... a means to let
* associated legend items know they should update
*/
void layerStyleOverridesChanged();
public slots:
@ -505,18 +502,19 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
//! Updates the bounding rect of this item. Call this function before doing any changes related to annotation out of the map rectangle
void updateBoundingRect();
#if 0
virtual void refreshDataDefinedProperty( const QgsComposerObject::DataDefinedProperty property = QgsComposerObject::AllProperties, const QgsExpressionContext *context = nullptr ) override;
#endif
void refreshDataDefinedProperty( const QgsLayoutObject::DataDefinedProperty property = QgsLayoutObject::AllProperties ) override;
private slots:
void layersAboutToBeRemoved( QList<QgsMapLayer *> layers );
void layersAboutToBeRemoved( const QList<QgsMapLayer *> &layers );
void painterJobFinished();
private:
//! Unique identifier
int mId = 0;
int mMapId = 1;
#if 0
QgsComposerMapGridStack *mGridStack = nullptr;
@ -544,6 +542,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
// background
std::unique_ptr< QImage > mCacheFinalImage;
std::unique_ptr< QImage > mCacheRenderingImage;
bool mUpdatesEnabled = true;
//! True if cached map image must be recreated
bool mCacheInvalidated = true;
@ -591,8 +590,18 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
* is true. May be overridden by data-defined expression. */
QString mFollowVisibilityPresetName;
//! \brief Create cache image
void recreateCachedImageInBackground();
/**
* \brief Draw to paint device
* \param painter painter
* \param extent map extent
* \param size size in scene coordinates
* \param dpi scene dpi
*/
void drawMap( QPainter *painter, const QgsRectangle &extent, QSizeF size, double dpi );
//! Create cache image
void recreateCachedImageInBackground( double viewScaleFactor );
//! Establishes signal/slot connection for update in case of layer change
void connectUpdateSlot();
@ -647,14 +656,18 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
void mapPolygon( const QgsRectangle &extent, QPolygonF &poly ) const;
/**
* Scales a composer map shift (in MM) and rotates it by mRotation
\param xShift in: shift in x direction (in item units), out: xShift in map units
\param yShift in: shift in y direction (in item units), out: yShift in map units*/
* Scales a layout map shift (in layout units) and rotates it by mRotation
* \param xShift in: shift in x direction (in item units), out: xShift in map units
* \param yShift in: shift in y direction (in item units), out: yShift in map units
*/
void transformShift( double &xShift, double &yShift ) const;
void drawAnnotations( QPainter *painter );
void drawAnnotation( const QgsAnnotation *item, QgsRenderContext &context );
QPointF composerMapPosForItem( const QgsAnnotation *item ) const;
QPointF layoutMapPosForItem( const QgsAnnotation *item ) const;
void drawMapFrame( QPainter *p );
void drawMapBackground( QPainter *p );
enum PartType
{
@ -666,7 +679,7 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
SelectionBoxes
};
//! Test if a part of the copmosermap needs to be drawn, considering mCurrentExportLayer
//! Test if a part of the item needs to be drawn, considering the context's current export layer
bool shouldDrawPart( PartType part ) const;
/**
@ -675,8 +688,8 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem
*/
void refreshMapExtents( const QgsExpressionContext *context = nullptr );
friend class QgsComposerMapOverview; //to access mXOffset, mYOffset
friend class TestQgsComposerMap;
friend class QgsLayoutMapOverview; //to access mXOffset, mYOffset
friend class TestQgsLayoutMap;
};

View File

@ -22,6 +22,16 @@
#include <QPainter>
#include <cmath>
void QgsLayoutUtils::rotate( double angle, double &x, double &y )
{
double rotToRad = angle * M_PI / 180.0;
double xRot, yRot;
xRot = x * std::cos( rotToRad ) - y * std::sin( rotToRad );
yRot = x * std::sin( rotToRad ) + y * std::cos( rotToRad );
x = xRot;
y = yRot;
}
double QgsLayoutUtils::normalizedAngle( const double angle, const bool allowNegative )
{
double clippedAngle = angle;

View File

@ -34,6 +34,14 @@ class CORE_EXPORT QgsLayoutUtils
{
public:
/**
* Rotates a point / vector around the origin.
* \param angle rotation angle in degrees, counterclockwise
* \param x in/out: x coordinate before / after the rotation
* \param y in/out: y coordinate before / after the rotation
*/
static void rotate( double angle, double &x, double &y );
/**
* Ensures that an \a angle (in degrees) is in the range 0 <= angle < 360.
* If \a allowNegative is true then angles between (-360, 360) are allowed. If false,

View File

@ -135,6 +135,7 @@ SET(TESTS
testqgslayoutcontext.cpp
testqgslayoutitem.cpp
testqgslayoutitemgroup.cpp
testqgslayoutmap.cpp
testqgslayoutmodel.cpp
testqgslayoutobject.cpp
testqgslayoutpage.cpp

View File

@ -0,0 +1,433 @@
/***************************************************************************
testqgslayoutmap.cpp
----------------------
begin : October 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsapplication.h"
#include "qgslayout.h"
#include "qgsmultirenderchecker.h"
#include "qgslayoutitemmap.h"
#include "qgsmultibandcolorrenderer.h"
#include "qgsrasterlayer.h"
#include "qgsrasterdataprovider.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsproject.h"
#include "qgsmapthemecollection.h"
#include "qgsproperty.h"
#include <QObject>
#include "qgstest.h"
class TestQgsLayoutMap : public QObject
{
Q_OBJECT
public:
TestQgsLayoutMap() = default;
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void id();
void render();
#if 0
void uniqueId(); //test if map id is adapted when doing copy paste
void worldFileGeneration(); // test world file generation
#endif
void mapPolygonVertices(); // test mapPolygon function with no map rotation
void dataDefinedLayers(); //test data defined layer string
void dataDefinedStyles(); //test data defined styles
private:
QgsRasterLayer *mRasterLayer = nullptr;
QgsVectorLayer *mPointsLayer = nullptr;
QgsVectorLayer *mPolysLayer = nullptr;
QgsVectorLayer *mLinesLayer = nullptr;
QString mReport;
};
void TestQgsLayoutMap::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
//create maplayers from testdata and add to layer registry
QFileInfo rasterFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/landsat.tif" );
mRasterLayer = new QgsRasterLayer( rasterFileInfo.filePath(),
rasterFileInfo.completeBaseName() );
QgsMultiBandColorRenderer *rasterRenderer = new QgsMultiBandColorRenderer( mRasterLayer->dataProvider(), 2, 3, 4 );
mRasterLayer->setRenderer( rasterRenderer );
QFileInfo pointFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/points.shp" );
mPointsLayer = new QgsVectorLayer( pointFileInfo.filePath(),
pointFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
QFileInfo polyFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/polys.shp" );
mPolysLayer = new QgsVectorLayer( polyFileInfo.filePath(),
polyFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
QFileInfo lineFileInfo( QStringLiteral( TEST_DATA_DIR ) + "/lines.shp" );
mLinesLayer = new QgsVectorLayer( lineFileInfo.filePath(),
lineFileInfo.completeBaseName(), QStringLiteral( "ogr" ) );
// some layers need to be in project for data-defined layers functionality
QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << mRasterLayer << mPointsLayer << mPolysLayer << mLinesLayer );
}
void TestQgsLayoutMap::cleanupTestCase()
{
QString myReportFile = QDir::tempPath() + "/qgistest.html";
QFile myFile( myReportFile );
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
QTextStream myQTextStream( &myFile );
myQTextStream << mReport;
myFile.close();
}
QgsApplication::exitQgis();
}
void TestQgsLayoutMap::init()
{
#if 0
//create composition with composer map
mComposition = new QgsComposition( QgsProject::instance() );
mComposition->setPaperSize( 297, 210 ); //A4 landscape
mComposerMap = new QgsComposerMap( mComposition, 20, 20, 200, 100 );
mComposerMap->setFrameEnabled( true );
mComposerMap->setLayers( QList<QgsMapLayer *>() << mRasterLayer );
mComposition->addComposerMap( mComposerMap );
#endif
mReport = QStringLiteral( "<h1>Composer Map Tests</h1>\n" );
}
void TestQgsLayoutMap::cleanup()
{
}
void TestQgsLayoutMap::id()
{
QgsLayout l( QgsProject::instance( ) );
QgsLayoutItemMap *map1 = new QgsLayoutItemMap( &l );
QCOMPARE( map1->displayName(), QStringLiteral( "Map 1" ) );
l.addLayoutItem( map1 );
QgsLayoutItemMap *map2 = new QgsLayoutItemMap( &l );
QCOMPARE( map2->displayName(), QStringLiteral( "Map 2" ) );
l.addLayoutItem( map2 );
map1->setId( "my map" );
QCOMPARE( map1->displayName(), QStringLiteral( "my map" ) );
// existing name should be recycled
l.removeLayoutItem( map1 );
QgsLayoutItemMap *map3 = new QgsLayoutItemMap( &l );
QCOMPARE( map3->displayName(), QStringLiteral( "Map 1" ) );
l.addLayoutItem( map3 );
}
void TestQgsLayoutMap::render()
{
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
map->setFrameEnabled( true );
map->setLayers( QList<QgsMapLayer *>() << mRasterLayer );
l.addLayoutItem( map );
map->setExtent( QgsRectangle( 781662.375, 3339523.125, 793062.375, 3345223.125 ) );
QgsLayoutChecker checker( QStringLiteral( "composermap_render" ), &l );
checker.setControlPathPrefix( QStringLiteral( "composer_map" ) );
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
}
#if 0
void TestQgsLayoutMap::uniqueId()
{
QDomDocument doc;
QDomElement documentElement = doc.createElement( QStringLiteral( "ComposerItemClipboard" ) );
mComposerMap->writeXml( documentElement, doc );
mComposition->addItemsFromXml( documentElement, doc, false );
//test if both composer maps have different ids
const QgsComposerMap *newMap = 0;
QList<const QgsComposerMap *> mapList = mComposition->composerMapItems();
QList<const QgsComposerMap *>::const_iterator mapIt = mapList.constBegin();
for ( ; mapIt != mapList.constEnd(); ++mapIt )
{
if ( *mapIt != mComposerMap )
{
newMap = *mapIt;
break;
}
}
QVERIFY( newMap );
int oldId = mComposerMap->id();
int newId = newMap->id();
mComposition->removeComposerItem( const_cast<QgsComposerMap *>( newMap ) );
QVERIFY( oldId != newId );
}
void TestQgsLayoutMap::worldFileGeneration()
{
mComposerMap->setNewExtent( QgsRectangle( 781662.375, 3339523.125, 793062.375, 3345223.125 ) );
mComposerMap->setMapRotation( 30.0 );
mComposition->setGenerateWorldFile( true );
mComposition->setReferenceMap( mComposerMap );
double a, b, c, d, e, f;
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
QGSCOMPARENEAR( a, 4.18048, 0.001 );
QGSCOMPARENEAR( b, 2.41331, 0.001 );
QGSCOMPARENEAR( c, 779444, 1 );
QGSCOMPARENEAR( d, 2.4136, 0.001 );
QGSCOMPARENEAR( e, -4.17997, 0.001 );
QGSCOMPARENEAR( f, 3.34241e+06, 1e+03 );
//test with map on second page. Parameters should be the same
mComposerMap->setItemPosition( 20, 20, QgsComposerItem::UpperLeft, 2 );
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
QGSCOMPARENEAR( a, 4.18048, 0.001 );
QGSCOMPARENEAR( b, 2.41331, 0.001 );
QGSCOMPARENEAR( c, 779444, 1 );
QGSCOMPARENEAR( d, 2.4136, 0.001 );
QGSCOMPARENEAR( e, -4.17997, 0.001 );
QGSCOMPARENEAR( f, 3.34241e+06, 1e+03 );
//test computing parameters for specific region
mComposerMap->setItemPosition( 20, 20, QgsComposerItem::UpperLeft, 2 );
mComposition->computeWorldFileParameters( QRectF( 10, 5, 260, 200 ), a, b, c, d, e, f );
QGSCOMPARENEAR( a, 4.18061, 0.001 );
QGSCOMPARENEAR( b, 2.41321, 0.001 );
QGSCOMPARENEAR( c, 773810, 1 );
QGSCOMPARENEAR( d, 2.4137, 0.001 );
QGSCOMPARENEAR( e, -4.1798, 0.001 );
QGSCOMPARENEAR( f, 3.35331e+06, 1e+03 );
mComposition->setGenerateWorldFile( false );
mComposerMap->setMapRotation( 0.0 );
}
#endif
void TestQgsLayoutMap::mapPolygonVertices()
{
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptSetSceneRect( QRectF( 20, 20, 200, 100 ) );
map->setFrameEnabled( true );
map->setLayers( QList<QgsMapLayer *>() << mRasterLayer );
l.addLayoutItem( map );
map->setExtent( QgsRectangle( 781662.375, 3339523.125, 793062.375, 3345223.125 ) );
QPolygonF visibleExtent = map->visibleExtentPolygon();
//vertices should be returned in clockwise order starting at the top-left point
QVERIFY( std::fabs( visibleExtent[0].x() - 781662.375 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[0].y() - 3345223.125 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[1].x() - 793062.375 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[1].y() - 3345223.125 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[2].x() - 793062.375 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[2].y() - 3339523.125 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[3].x() - 781662.375 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[3].y() - 3339523.125 ) < 0.001 );
//polygon should be closed
QVERIFY( visibleExtent.isClosed() );
//now test with rotated map
map->setMapRotation( 10 );
visibleExtent = map->visibleExtentPolygon();
//vertices should be returned in clockwise order starting at the top-left point
QVERIFY( std::fabs( visibleExtent[0].x() - 781254.0735015 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[0].y() - 3344190.0324834 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[1].x() - 792480.881886 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[1].y() - 3346169.62171 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[2].x() - 793470.676499 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[2].y() - 3340556.21752 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[3].x() - 782243.868114 ) < 0.001 );
QVERIFY( std::fabs( visibleExtent[3].y() - 3338576.62829 ) < 0.001 );
//polygon should be closed
QVERIFY( visibleExtent.isClosed() );
map->setMapRotation( 0 );
}
void TestQgsLayoutMap::dataDefinedLayers()
{
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptMove( QgsLayoutPoint( 20, 20 ) );
map->attemptResize( QgsLayoutSize( 200, 100 ) );
map->setFrameEnabled( true );
l.addLayoutItem( map );
//test malformed layer set string
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression( QStringLiteral( "'x'" ) ) );
QList<QgsMapLayer *> result = map->layersToRender();
QVERIFY( result.isEmpty() );
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression( QStringLiteral( "'x|'" ) ) );
result = map->layersToRender();
QVERIFY( result.isEmpty() );
//test subset of valid layers
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression(
QStringLiteral( "'%1|%2'" ).arg( mPolysLayer->name(), mRasterLayer->name() ) ) );
result = map->layersToRender();
QCOMPARE( result.count(), 2 );
QVERIFY( result.contains( mPolysLayer ) );
QVERIFY( result.contains( mRasterLayer ) );
//test non-existent layer
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression(
QStringLiteral( "'x|%1|%2'" ).arg( mLinesLayer->name(), mPointsLayer->name() ) ) );
result = map->layersToRender();
QCOMPARE( result.count(), 2 );
QVERIFY( result.contains( mLinesLayer ) );
QVERIFY( result.contains( mPointsLayer ) );
//test no layers
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression(
QStringLiteral( "''" ) ) );
result = map->layersToRender();
QVERIFY( result.isEmpty() );
//test with atlas feature evaluation
QgsVectorLayer *atlasLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:string" ), QStringLiteral( "atlas" ), QStringLiteral( "memory" ) );
QVERIFY( atlasLayer->isValid() );
QgsFeature f1( atlasLayer->dataProvider()->fields(), 1 );
f1.setAttribute( QStringLiteral( "col1" ), mLinesLayer->name() );
QgsFeature f2( atlasLayer->dataProvider()->fields(), 1 );
f2.setAttribute( QStringLiteral( "col1" ), mPointsLayer->name() );
atlasLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 );
#if 0 //TODO
mComposition->atlasComposition().setCoverageLayer( atlasLayer );
mComposition->atlasComposition().setEnabled( true );
mComposition->setAtlasMode( QgsComposition::ExportAtlas );
mComposition->atlasComposition().beginRender();
mComposition->atlasComposition().prepareForFeature( 0 );
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromField( QStringLiteral( "col1" ) ) );
result = map->layersToRender();
QCOMPARE( result.count(), 1 );
QCOMPARE( result.at( 0 ), mLinesLayer );
mComposition->atlasComposition().prepareForFeature( 1 );
result = map->layersToRender();
QCOMPARE( result.count(), 1 );
QCOMPARE( result.at( 0 ), mPointsLayer );
mComposition->atlasComposition().setEnabled( false );
delete atlasLayer;
#endif
//render test
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression(
QStringLiteral( "'%1|%2'" ).arg( mPolysLayer->name(), mPointsLayer->name() ) ) );
map->setExtent( QgsRectangle( -110.0, 25.0, -90, 40.0 ) );
QgsLayoutChecker checker( QStringLiteral( "composermap_ddlayers" ), &l );
checker.setControlPathPrefix( QStringLiteral( "composer_map" ) );
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
}
void TestQgsLayoutMap::dataDefinedStyles()
{
QList<QgsMapLayer *> layers = QList<QgsMapLayer *>() << mRasterLayer << mPolysLayer << mPointsLayer << mLinesLayer;
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptMove( QgsLayoutPoint( 20, 20 ) );
map->attemptResize( QgsLayoutSize( 200, 100 ) );
map->setFrameEnabled( true );
map->setLayers( layers );
l.addLayoutItem( map );
QgsMapThemeCollection::MapThemeRecord rec;
rec.setLayerRecords( QList<QgsMapThemeCollection::MapThemeLayerRecord>()
<< QgsMapThemeCollection::MapThemeLayerRecord( mPointsLayer )
<< QgsMapThemeCollection::MapThemeLayerRecord( mLinesLayer )
);
QgsProject::instance()->mapThemeCollection()->insert( QStringLiteral( "test preset" ), rec );
// test following of preset
map->setFollowVisibilityPreset( true );
map->setFollowVisibilityPresetName( QStringLiteral( "test preset" ) );
QSet<QgsMapLayer *> result = map->layersToRender().toSet();
QCOMPARE( result.count(), 2 );
map->setFollowVisibilityPresetName( QString() );
//test malformed style string
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapStylePreset, QgsProperty::fromExpression( QStringLiteral( "5" ) ) );
result = map->layersToRender().toSet();
QCOMPARE( result, layers.toSet() );
//test valid preset
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapStylePreset, QgsProperty::fromExpression( QStringLiteral( "'test preset'" ) ) );
result = map->layersToRender().toSet();
QCOMPARE( result.count(), 2 );
QVERIFY( result.contains( mLinesLayer ) );
QVERIFY( result.contains( mPointsLayer ) );
//test non-existent preset
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapStylePreset, QgsProperty::fromExpression( QStringLiteral( "'bad preset'" ) ) );
result = map->layersToRender().toSet();
QCOMPARE( result, layers.toSet() );
//test that dd layer set overrides style layers
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapStylePreset, QgsProperty::fromExpression( QStringLiteral( "'test preset'" ) ) );
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty::fromExpression(
QStringLiteral( "'%1'" ).arg( mPolysLayer->name() ) ) );
result = map->layersToRender().toSet();
QCOMPARE( result.count(), 1 );
QVERIFY( result.contains( mPolysLayer ) );
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapLayers, QgsProperty() );
//render test
map->dataDefinedProperties().setProperty( QgsLayoutObject::MapStylePreset, QgsProperty::fromExpression( QStringLiteral( "'test preset'" ) ) );
map->setExtent( QgsRectangle( -110.0, 25.0, -90, 40.0 ) );
QgsLayoutChecker checker( QStringLiteral( "composermap_ddstyles" ), &l );
checker.setControlPathPrefix( QStringLiteral( "composer_map" ) );
QVERIFY( checker.testLayout( mReport, 0, 0 ) );
}
QGSTEST_MAIN( TestQgsLayoutMap )
#include "testqgslayoutmap.moc"

View File

@ -30,6 +30,7 @@ class TestQgsLayoutUtils: public QObject
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void rotate();
void normalizedAngle(); //test normalised angle function
void snappedAngle();
void createRenderContextFromLayout();
@ -69,6 +70,28 @@ void TestQgsLayoutUtils::cleanup()
}
void TestQgsLayoutUtils::rotate()
{
// pairs of lines from before -> expected after position and angle to rotate
QList< QPair< QLineF, double > > testVals;
testVals << qMakePair( QLineF( 0, 1, 0, 1 ), 0.0 );
testVals << qMakePair( QLineF( 0, 1, -1, 0 ), 90.0 );
testVals << qMakePair( QLineF( 0, 1, 0, -1 ), 180.0 );
testVals << qMakePair( QLineF( 0, 1, 1, 0 ), 270.0 );
testVals << qMakePair( QLineF( 0, 1, 0, 1 ), 360.0 );
//test rotate helper function
QList< QPair< QLineF, double > >::const_iterator it = testVals.constBegin();
for ( ; it != testVals.constEnd(); ++it )
{
double x = ( *it ).first.x1();
double y = ( *it ).first.y1();
QgsLayoutUtils::rotate( ( *it ).second, x, y );
QGSCOMPARENEAR( x, ( *it ).first.x2(), 4 * DBL_EPSILON );
QGSCOMPARENEAR( y, ( *it ).first.y2(), 4 * DBL_EPSILON );
}
}
void TestQgsLayoutUtils::normalizedAngle()
{
QList< QPair< double, double > > testVals;

View File

@ -86,6 +86,7 @@ ADD_PYTHON_TEST(PyQgsLayoutGridSettings test_qgslayoutgridsettings.py)
ADD_PYTHON_TEST(PyQgsLayoutGuide test_qgslayoutguides.py)
ADD_PYTHON_TEST(PyQgsLayoutItem test_qgslayoutitem.py)
ADD_PYTHON_TEST(PyQgsLayoutItemPropertiesDialog test_qgslayoutitempropertiesdialog.py)
ADD_PYTHON_TEST(PyQgsLayoutMap test_qgslayoutmap.py)
ADD_PYTHON_TEST(PyQgsLayoutPolygon test_qgslayoutpolygon.py)
ADD_PYTHON_TEST(PyQgsLayoutPolyline test_qgslayoutpolyline.py)
ADD_PYTHON_TEST(PyQgsLayoutSnapper test_qgslayoutsnapper.py)

View File

@ -0,0 +1,237 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsLayoutItemMap.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = '(C) 2017 Nyall Dawson'
__date__ = '20/10/2017'
__copyright__ = 'Copyright 2017, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis # NOQA
import os
from qgis.PyQt.QtCore import QFileInfo, QRectF
from qgis.PyQt.QtXml import QDomDocument
from qgis.PyQt.QtGui import QPainter
from qgis.core import (QgsLayoutItemMap,
QgsRectangle,
QgsRasterLayer,
QgsVectorLayer,
QgsLayout,
QgsMapSettings,
QgsProject,
QgsMultiBandColorRenderer,
QgsCoordinateReferenceSystem
)
from qgis.testing import start_app, unittest
from utilities import unitTestDataPath
from qgslayoutchecker import QgsLayoutChecker
start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsComposerMap(unittest.TestCase):
def __init__(self, methodName):
"""Run once on class initialization."""
unittest.TestCase.__init__(self, methodName)
myPath = os.path.join(TEST_DATA_DIR, 'rgb256x256.png')
rasterFileInfo = QFileInfo(myPath)
self.raster_layer = QgsRasterLayer(rasterFileInfo.filePath(),
rasterFileInfo.completeBaseName())
rasterRenderer = QgsMultiBandColorRenderer(
self.raster_layer.dataProvider(), 1, 2, 3)
self.raster_layer.setRenderer(rasterRenderer)
myPath = os.path.join(TEST_DATA_DIR, 'points.shp')
vector_file_info = QFileInfo(myPath)
self.vector_layer = QgsVectorLayer(vector_file_info.filePath(),
vector_file_info.completeBaseName(), 'ogr')
assert self.vector_layer.isValid()
# pipe = mRasterLayer.pipe()
# assert pipe.set(rasterRenderer), 'Cannot set pipe renderer'
QgsProject.instance().addMapLayers([self.raster_layer, self.vector_layer])
# create composition with composer map
self.layout = QgsLayout(QgsProject.instance())
self.layout.initializeDefaults()
self.map = QgsLayoutItemMap(self.layout)
self.map.setFrameEnabled(True)
self.map.setLayers([self.raster_layer])
self.layout.addLayoutItem(self.map)
def testOverviewMap(self):
return
overviewMap = QgsLayoutItemMap(self.layout) # , 20, 130, 70, 70)
overviewMap.setFrameEnabled(True)
overviewMap.setLayers([self.raster_layer])
self.layout.addComposerMap(overviewMap)
# zoom in
myRectangle = QgsRectangle(96, -152, 160, -120)
self.map.setNewExtent(myRectangle)
myRectangle2 = QgsRectangle(0, -256, 256, 0)
overviewMap.setNewExtent(myRectangle2)
overviewMap.overview().setFrameMap(self.map.id())
checker = QgsLayoutChecker('composermap_overview', self.layout)
checker.setControlPathPrefix("composer_mapoverview")
myTestResult, myMessage = checker.testComposition()
self.layout.removeComposerItem(overviewMap)
assert myTestResult, myMessage
def testOverviewMapBlend(self):
return
overviewMap = QgsComposerMap(self.layout, 20, 130, 70, 70)
overviewMap.setFrameEnabled(True)
overviewMap.setLayers([self.raster_layer])
self.layout.addComposerMap(overviewMap)
# zoom in
myRectangle = QgsRectangle(96, -152, 160, -120)
self.map.setNewExtent(myRectangle)
myRectangle2 = QgsRectangle(0, -256, 256, 0)
overviewMap.setNewExtent(myRectangle2)
overviewMap.overview().setFrameMap(self.map.id())
overviewMap.overview().setBlendMode(QPainter.CompositionMode_Multiply)
checker = QgsLayoutChecker('composermap_overview_blending', self.layout)
checker.setControlPathPrefix("composer_mapoverview")
myTestResult, myMessage = checker.testComposition()
self.layout.removeComposerItem(overviewMap)
assert myTestResult, myMessage
def testOverviewMapInvert(self):
return
overviewMap = QgsComposerMap(self.layout, 20, 130, 70, 70)
overviewMap.setFrameEnabled(True)
overviewMap.setLayers([self.raster_layer])
self.layout.addComposerMap(overviewMap)
# zoom in
myRectangle = QgsRectangle(96, -152, 160, -120)
self.map.setNewExtent(myRectangle)
myRectangle2 = QgsRectangle(0, -256, 256, 0)
overviewMap.setNewExtent(myRectangle2)
overviewMap.overview().setFrameMap(self.map.id())
overviewMap.overview().setInverted(True)
checker = QgsLayoutChecker('composermap_overview_invert', self.layout)
checker.setControlPathPrefix("composer_mapoverview")
myTestResult, myMessage = checker.testComposition()
self.layout.removeComposerItem(overviewMap)
assert myTestResult, myMessage
def testOverviewMapCenter(self):
return
overviewMap = QgsComposerMap(self.layout, 20, 130, 70, 70)
overviewMap.setFrameEnabled(True)
overviewMap.setLayers([self.raster_layer])
self.layout.addComposerMap(overviewMap)
# zoom in
myRectangle = QgsRectangle(192, -288, 320, -224)
self.map.setNewExtent(myRectangle)
myRectangle2 = QgsRectangle(0, -256, 256, 0)
overviewMap.setNewExtent(myRectangle2)
overviewMap.overview().setFrameMap(self.map.id())
overviewMap.overview().setInverted(False)
overviewMap.overview().setCentered(True)
checker = QgsLayoutChecker('composermap_overview_center', self.layout)
checker.setControlPathPrefix("composer_mapoverview")
myTestResult, myMessage = checker.testComposition()
self.layout.removeComposerItem(overviewMap)
assert myTestResult, myMessage
def testMapCrs(self):
# create composition with composer map
map_settings = QgsMapSettings()
map_settings.setLayers([self.vector_layer])
layout = QgsLayout(QgsProject.instance())
layout.initializeDefaults()
# check that new maps inherit project CRS
QgsProject.instance().setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
map = QgsLayoutItemMap(layout)
map.attemptSetSceneRect(QRectF(20, 20, 200, 100))
map.setFrameEnabled(True)
rectangle = QgsRectangle(-13838977, 2369660, -8672298, 6250909)
map.setExtent(rectangle)
map.setLayers([self.vector_layer])
layout.addLayoutItem(map)
self.assertEqual(map.crs().authid(), 'EPSG:4326')
self.assertFalse(map.presetCrs().isValid())
# overwrite CRS
map.setCrs(QgsCoordinateReferenceSystem('EPSG:3857'))
self.assertEqual(map.crs().authid(), 'EPSG:3857')
self.assertEqual(map.presetCrs().authid(), 'EPSG:3857')
checker = QgsLayoutChecker('composermap_crs3857', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.assertTrue(result, message)
# overwrite CRS
map.setCrs(QgsCoordinateReferenceSystem('EPSG:4326'))
self.assertEqual(map.presetCrs().authid(), 'EPSG:4326')
self.assertEqual(map.crs().authid(), 'EPSG:4326')
rectangle = QgsRectangle(-124, 17, -78, 52)
map.zoomToExtent(rectangle)
checker = QgsLayoutChecker('composermap_crs4326', layout)
checker.setControlPathPrefix("composer_map")
result, message = checker.testLayout()
self.assertTrue(result, message)
# change back to project CRS
map.setCrs(QgsCoordinateReferenceSystem())
self.assertEqual(map.crs().authid(), 'EPSG:4326')
self.assertFalse(map.presetCrs().isValid())
def testuniqueId(self):
return
doc = QDomDocument()
documentElement = doc.createElement('ComposerItemClipboard')
self.layout.writeXml(documentElement, doc)
self.layout.addItemsFromXml(documentElement, doc)
# test if both composer maps have different ids
newMap = QgsComposerMap(self.layout, 0, 0, 10, 10)
mapList = self.layout.composerMapItems()
for mapIt in mapList:
if mapIt != self.map:
newMap = mapIt
break
oldId = self.map.id()
newId = newMap.id()
self.layout.removeComposerItem(newMap)
myMessage = 'old: %s new: %s' % (oldId, newId)
assert oldId != newId, myMessage
def testWorldFileGeneration(self):
return
myRectangle = QgsRectangle(781662.375, 3339523.125, 793062.375, 3345223.125)
self.map.setNewExtent(myRectangle)
self.map.setMapRotation(30.0)
self.layout.setGenerateWorldFile(True)
self.layout.setReferenceMap(self.map)
p = self.layout.computeWorldFileParameters()
pexpected = (4.180480199790922, 2.4133064516129026, 779443.7612381146,
2.4136013686911886, -4.179969388427311, 3342408.5663611)
ptolerance = (0.001, 0.001, 1, 0.001, 0.001, 1e+03)
for i in range(0, 6):
assert abs(p[i] - pexpected[i]) < ptolerance[i]
if __name__ == '__main__':
unittest.main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 61 KiB