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