1 /***************************************************************************
2 testqgsvectortilelayer.cpp
3 --------------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgstest.h"
17 #include <QObject>
18 #include <QString>
19
20 //qgis includes...
21 #include "qgsapplication.h"
22 #include "qgsproject.h"
23 #include "qgsrenderchecker.h"
24 #include "qgstiles.h"
25 #include "qgsvectortilebasicrenderer.h"
26 #include "qgsvectortilelayer.h"
27 #include "qgsvectortilebasiclabeling.h"
28 #include "qgsfontutils.h"
29 #include "qgslinesymbollayer.h"
30
31 /**
32 * \ingroup UnitTests
33 * This is a unit test for a vector tile layer
34 */
35 class TestQgsVectorTileLayer : public QObject
36 {
37 Q_OBJECT
38
39 public:
40 TestQgsVectorTileLayer() = default;
41
42 private:
43 QString mDataDir;
44 QgsVectorTileLayer *mLayer = nullptr;
45 QString mReport;
46 QgsMapSettings *mMapSettings = nullptr;
47
48 bool imageCheck( const QString &testType, QgsVectorTileLayer *layer, QgsRectangle extent );
49
50 private slots:
51 void initTestCase();// will be called before the first testfunction is executed.
52 void cleanupTestCase();// will be called after the last testfunction was executed.
init()53 void init() {} // will be called before each testfunction is executed.
cleanup()54 void cleanup() {} // will be called after every testfunction.
55
56 void test_basic();
57 void test_render();
58 void test_render_withClip();
59 void test_labeling();
60 void test_relativePaths();
61 void test_polygonWithLineStyle();
62 };
63
64
initTestCase()65 void TestQgsVectorTileLayer::initTestCase()
66 {
67 // init QGIS's paths - true means that all path will be inited from prefix
68 QgsApplication::init();
69 QgsApplication::initQgis();
70 QgsApplication::showSettings();
71 mDataDir = QString( TEST_DATA_DIR ); //defined in CmakeLists.txt
72 mDataDir += "/vector_tile";
73
74 QgsDataSourceUri ds;
75 ds.setParam( "type", "xyz" );
76 ds.setParam( "url", QString( "file://%1/{z}-{x}-{y}.pbf" ).arg( mDataDir ) );
77 ds.setParam( "zmax", "1" );
78 mLayer = new QgsVectorTileLayer( ds.encodedUri(), "Vector Tiles Test" );
79 QVERIFY( mLayer->isValid() );
80
81 QgsProject::instance()->addMapLayer( mLayer );
82
83 mMapSettings = new QgsMapSettings();
84 mMapSettings->setLayers( QList<QgsMapLayer *>() << mLayer );
85
86 // let's have some standard style config for the layer
87 QColor polygonFillColor = Qt::blue;
88 QColor polygonStrokeColor = polygonFillColor;
89 polygonFillColor.setAlpha( 100 );
90 double polygonStrokeWidth = DEFAULT_LINE_WIDTH * 2;
91 QColor lineStrokeColor = Qt::blue;
92 double lineStrokeWidth = DEFAULT_LINE_WIDTH * 2;
93 QColor pointFillColor = Qt::red;
94 QColor pointStrokeColor = pointFillColor;
95 pointFillColor.setAlpha( 100 );
96 double pointSize = DEFAULT_POINT_SIZE;
97
98 QgsVectorTileBasicRenderer *rend = new QgsVectorTileBasicRenderer;
99 rend->setStyles( QgsVectorTileBasicRenderer::simpleStyle(
100 polygonFillColor, polygonStrokeColor, polygonStrokeWidth,
101 lineStrokeColor, lineStrokeWidth,
102 pointFillColor, pointStrokeColor, pointSize ) );
103 mLayer->setRenderer( rend ); // takes ownership
104
105 mReport += QLatin1String( "<h1>Vector Tile Layer Tests</h1>\n" );
106 }
107
cleanupTestCase()108 void TestQgsVectorTileLayer::cleanupTestCase()
109 {
110 QString myReportFile = QDir::tempPath() + "/qgistest.html";
111 QFile myFile( myReportFile );
112 if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
113 {
114 QTextStream myQTextStream( &myFile );
115 myQTextStream << mReport;
116 myFile.close();
117 }
118
119 QgsApplication::exitQgis();
120 }
121
test_basic()122 void TestQgsVectorTileLayer::test_basic()
123 {
124 // tile fetch test
125 QByteArray tile0rawData = mLayer->getRawTile( QgsTileXYZ( 0, 0, 0 ) );
126 QCOMPARE( tile0rawData.length(), 64822 );
127
128 QByteArray invalidTileRawData = mLayer->getRawTile( QgsTileXYZ( 0, 0, 99 ) );
129 QCOMPARE( invalidTileRawData.length(), 0 );
130 }
131
132
imageCheck(const QString & testType,QgsVectorTileLayer * layer,QgsRectangle extent)133 bool TestQgsVectorTileLayer::imageCheck( const QString &testType, QgsVectorTileLayer *layer, QgsRectangle extent )
134 {
135 mReport += "<h2>" + testType + "</h2>\n";
136 mMapSettings->setExtent( extent );
137 mMapSettings->setDestinationCrs( layer->crs() );
138 mMapSettings->setOutputDpi( 96 );
139 QgsRenderChecker myChecker;
140 myChecker.setControlPathPrefix( QStringLiteral( "vector_tile" ) );
141 myChecker.setControlName( "expected_" + testType );
142 myChecker.setMapSettings( *mMapSettings );
143 myChecker.setColorTolerance( 15 );
144 bool myResultFlag = myChecker.runTest( testType, 0 );
145 mReport += myChecker.report();
146 return myResultFlag;
147 }
148
test_render()149 void TestQgsVectorTileLayer::test_render()
150 {
151 QVERIFY( imageCheck( "render_test_basic", mLayer, mLayer->extent() ) );
152 }
153
test_render_withClip()154 void TestQgsVectorTileLayer::test_render_withClip()
155 {
156 QgsMapClippingRegion region( QgsGeometry::fromWkt( "Polygon ((-3584104.41462873760610819 9642431.51156153343617916, -3521836.1401221314445138 -3643384.67029104987159371, -346154.14028519613202661 -10787760.6154897827655077, 11515952.15322335436940193 -10530608.51481428928673267, 11982964.21202290244400501 11308099.1972544826567173, -3584104.41462873760610819 9642431.51156153343617916))" ) );
157 region.setFeatureClip( QgsMapClippingRegion::FeatureClippingType::ClipPainterOnly );
158 QgsMapClippingRegion region2( QgsGeometry::fromWkt( "Polygon ((836943.07534032803960145 12108307.34630974195897579, 1179418.58512666448950768 -8011790.66139839310199022, 17306901.68233776465058327 -8130936.37545258551836014, 17680511.32937740534543991 14072993.65374799631536007, 836943.07534032803960145 12108307.34630974195897579))" ) );
159 region2.setFeatureClip( QgsMapClippingRegion::FeatureClippingType::ClipToIntersection );
160 mMapSettings->addClippingRegion( region );
161 mMapSettings->addClippingRegion( region2 );
162 const bool res = imageCheck( "render_painterclip", mLayer, mLayer->extent() );
163 mMapSettings->setClippingRegions( QList< QgsMapClippingRegion >() );
164 QVERIFY( res );
165 }
166
test_labeling()167 void TestQgsVectorTileLayer::test_labeling()
168 {
169 QgsTextFormat format;
170 format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
171 format.setSize( 12 );
172 format.setNamedStyle( QStringLiteral( "Bold" ) );
173 format.setColor( QColor( 200, 0, 200 ) );
174
175 QgsPalLayerSettings labelSettings;
176 labelSettings.drawLabels = true;
177 labelSettings.fieldName = "name:en";
178 labelSettings.placement = QgsPalLayerSettings::OverPoint;
179 labelSettings.setFormat( format );
180
181 QgsVectorTileBasicLabelingStyle st;
182 st.setStyleName( "st1" );
183 st.setLayerName( "place" );
184 st.setFilterExpression( "rank = 1 AND class = 'country'" );
185 st.setGeometryType( QgsWkbTypes::PointGeometry );
186 st.setLabelSettings( labelSettings );
187
188 QgsVectorTileBasicLabeling *labeling = new QgsVectorTileBasicLabeling;
189 QList<QgsVectorTileBasicLabelingStyle> lst;
190 lst << st;
191 labeling->setStyles( lst );
192
193 mLayer->setLabeling( labeling );
194
195 QgsVectorTileRenderer *oldRenderer = mLayer->renderer()->clone();
196
197 // use a different renderer to make the labels stand out more
198 QgsVectorTileBasicRenderer *rend = new QgsVectorTileBasicRenderer;
199 rend->setStyles( QgsVectorTileBasicRenderer::simpleStyle(
200 Qt::transparent, Qt::white, DEFAULT_LINE_WIDTH * 2,
201 Qt::transparent, 0,
202 Qt::transparent, Qt::transparent, 0 ) );
203 mLayer->setRenderer( rend ); // takes ownership
204
205 bool res = imageCheck( "render_test_labeling", mLayer, mLayer->extent() );
206
207 mLayer->setRenderer( oldRenderer );
208
209 QVERIFY( res );
210 }
211
test_relativePaths()212 void TestQgsVectorTileLayer::test_relativePaths()
213 {
214 QgsReadWriteContext contextRel;
215 contextRel.setPathResolver( QgsPathResolver( "/home/qgis/project.qgs" ) );
216 QgsReadWriteContext contextAbs;
217
218 QString srcXyzLocal = "type=xyz&url=file:///home/qgis/%7Bz%7D/%7Bx%7D/%7By%7D.pbf";
219 QString srcXyzRemote = "type=xyz&url=http://www.example.com/%7Bz%7D/%7Bx%7D/%7By%7D.pbf";
220 QString srcMbtiles = "type=mbtiles&url=/home/qgis/test/map.mbtiles";
221
222 QgsVectorTileLayer layer;
223
224 // encode source: converting absolute paths to relative
225 QString srcXyzLocalRel = layer.encodedSource( srcXyzLocal, contextRel );
226 QCOMPARE( srcXyzLocalRel, QStringLiteral( "type=xyz&url=file:./%7Bz%7D/%7Bx%7D/%7By%7D.pbf" ) );
227 QString srcMbtilesRel = layer.encodedSource( srcMbtiles, contextRel );
228 QCOMPARE( srcMbtilesRel, QStringLiteral( "type=mbtiles&url=./test/map.mbtiles" ) );
229 QCOMPARE( layer.encodedSource( srcXyzRemote, contextRel ), srcXyzRemote );
230
231 // encode source: keeping absolute paths
232 QCOMPARE( layer.encodedSource( srcXyzLocal, contextAbs ), srcXyzLocal );
233 QCOMPARE( layer.encodedSource( srcXyzRemote, contextAbs ), srcXyzRemote );
234 QCOMPARE( layer.encodedSource( srcMbtiles, contextAbs ), srcMbtiles );
235
236 // decode source: converting relative paths to absolute
237 QCOMPARE( layer.decodedSource( srcXyzLocalRel, QString(), contextRel ), srcXyzLocal );
238 QCOMPARE( layer.decodedSource( srcMbtilesRel, QString(), contextRel ), srcMbtiles );
239 QCOMPARE( layer.decodedSource( srcXyzRemote, QString(), contextRel ), srcXyzRemote );
240
241 // decode source: keeping absolute paths
242 QCOMPARE( layer.decodedSource( srcXyzLocal, QString(), contextAbs ), srcXyzLocal );
243 QCOMPARE( layer.decodedSource( srcXyzRemote, QString(), contextAbs ), srcXyzRemote );
244 QCOMPARE( layer.decodedSource( srcMbtiles, QString(), contextAbs ), srcMbtiles );
245 }
246
test_polygonWithLineStyle()247 void TestQgsVectorTileLayer::test_polygonWithLineStyle()
248 {
249 QgsDataSourceUri ds;
250 ds.setParam( "type", "xyz" );
251 ds.setParam( "url", QString( "file://%1/{z}-{x}-{y}.pbf" ).arg( mDataDir ) );
252 ds.setParam( "zmax", "1" );
253 std::unique_ptr< QgsVectorTileLayer > layer = qgis::make_unique< QgsVectorTileLayer >( ds.encodedUri(), "Vector Tiles Test" );
254 QVERIFY( layer->isValid() );
255
256 mMapSettings->setLayers( QList<QgsMapLayer *>() << layer.get() );
257
258 QColor lineStrokeColor = Qt::blue;
259 double lineStrokeWidth = DEFAULT_LINE_WIDTH * 2;
260
261 QgsVectorTileBasicRenderer *rend = new QgsVectorTileBasicRenderer;
262
263 QgsSimpleLineSymbolLayer *lineSymbolLayer = new QgsSimpleLineSymbolLayer;
264 lineSymbolLayer->setColor( lineStrokeColor );
265 lineSymbolLayer->setWidth( lineStrokeWidth );
266 QgsLineSymbol *lineSymbol = new QgsLineSymbol( QgsSymbolLayerList() << lineSymbolLayer );
267
268 QgsVectorTileBasicRendererStyle st( QStringLiteral( "Polygons" ), QString(), QgsWkbTypes::LineGeometry );
269 st.setSymbol( lineSymbol );
270
271 rend->setStyles( QList<QgsVectorTileBasicRendererStyle>() << st );
272 layer->setRenderer( rend ); // takes ownership
273
274 QVERIFY( imageCheck( "render_test_polygon_with_line_style", layer.get(), layer->extent() ) );
275 }
276
277
278 QGSTEST_MAIN( TestQgsVectorTileLayer )
279 #include "testqgsvectortilelayer.moc"
280