1 /***************************************************************************
2                               qgswmtsutils.cpp
3                               -------------------------
4   begin                : July 23, 2018
5   copyright            : (C) 2018 by René-Luc D'Hont
6   email                : rldhont at 3liz dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                  *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "qgswmtsutils.h"
19 #include "qgswmtsparameters.h"
20 #include "qgsserverprojectutils.h"
21 
22 #include "qgsproject.h"
23 #include "qgsexception.h"
24 #include "qgscoordinatereferencesystem.h"
25 #include "qgslayertree.h"
26 #include "qgssettings.h"
27 #include "qgsprojectviewsettings.h"
28 #include "qgscoordinatetransform.h"
29 
30 namespace QgsWmts
31 {
32   namespace
33   {
34     QMap< QString, tileMatrixInfo> populateFixedTileMatrixInfoMap();
35 
36     QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( geoEpsgCrsAuthId() );
37 
38     // Constant
39     const int tileSize = 256;
40     const double POINTS_TO_M = 2.800005600011068 / 10000.0;
41 
42     QMap< QString, tileMatrixInfo> fixedTileMatrixInfoMap = populateFixedTileMatrixInfoMap();
43     QMap< QString, tileMatrixInfo> calculatedTileMatrixInfoMap; // for project without WMTSGrids configuration
44   }
45 
implementationVersion()46   QString implementationVersion()
47   {
48     return QStringLiteral( "1.0.0" );
49   }
50 
51 
serviceUrl(const QgsServerRequest & request,const QgsProject * project,const QgsServerSettings & settings)52   QString serviceUrl( const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings )
53   {
54     QString href = QgsServerProjectUtils::wmtsServiceUrl( project ? *project : *QgsProject::instance(), request, settings );
55 
56     // Build default url
57     if ( href.isEmpty() )
58     {
59       QUrl url = request.originalUrl();
60 
61       QgsWmtsParameters params;
62       params.load( QUrlQuery( url ) );
63       params.remove( QgsServerParameter::REQUEST );
64       params.remove( QgsServerParameter::VERSION_SERVICE );
65       params.remove( QgsServerParameter::SERVICE );
66 
67       url.setQuery( params.urlQuery() );
68       href = url.toString();
69     }
70 
71     return  href;
72   }
73 
calculateTileMatrixInfo(const QString & crsStr,const QgsProject * project)74   tileMatrixInfo calculateTileMatrixInfo( const QString &crsStr, const QgsProject *project )
75   {
76     // Does the CRS have fixed tile matrices
77     if ( fixedTileMatrixInfoMap.contains( crsStr ) )
78       return fixedTileMatrixInfoMap[crsStr];
79 
80     // Does the CRS have already calculated tile matrices
81     if ( calculatedTileMatrixInfoMap.contains( crsStr ) )
82       return calculatedTileMatrixInfoMap[crsStr];
83 
84     tileMatrixInfo tmi;
85     tmi.ref = crsStr;
86 
87     const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsStr );
88     const QgsCoordinateTransform crsTransform( wgs84, crs, project );
89     try
90     {
91       tmi.extent = crsTransform.transformBoundingBox( crs.bounds() );
92     }
93     catch ( QgsCsException &cse )
94     {
95       Q_UNUSED( cse )
96     }
97 
98     tmi.unit = crs.mapUnits();
99     tmi.hasAxisInverted = crs.hasAxisInverted();
100 
101     // calculate tile matrix scale denominator
102     double scaleDenominator = 0.0;
103     const int colRes = ( tmi.extent.xMaximum() - tmi.extent.xMinimum() ) / tileSize;
104     const int rowRes = ( tmi.extent.yMaximum() - tmi.extent.yMinimum() ) / tileSize;
105     const double UNIT_TO_M = QgsUnitTypes::fromUnitToUnitFactor( tmi.unit, QgsUnitTypes::DistanceMeters );
106     if ( colRes > rowRes )
107       scaleDenominator = std::ceil( colRes * UNIT_TO_M / POINTS_TO_M );
108     else
109       scaleDenominator = std::ceil( rowRes * UNIT_TO_M / POINTS_TO_M );
110 
111     // Update extent to get a square one
112     const QgsRectangle extent = tmi.extent;
113     double res = POINTS_TO_M * scaleDenominator / UNIT_TO_M;
114     int col = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileSize * res ) );
115     int row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileSize * res ) );
116     if ( col > 1 || row > 1 )
117     {
118       // Update scale
119       if ( col > row )
120       {
121         res = col * res;
122         scaleDenominator = col * scaleDenominator;
123       }
124       else
125       {
126         res = row * res;
127         scaleDenominator = row * scaleDenominator;
128       }
129       // set col and row to 1 for the square
130       col = 1;
131       row = 1;
132     }
133     // Calculate extent
134     const double left = ( extent.xMinimum() + ( extent.xMaximum() - extent.xMinimum() ) / 2.0 ) - ( col / 2.0 ) * ( tileSize * res );
135     const double bottom = ( extent.yMinimum() + ( extent.yMaximum() - extent.yMinimum() ) / 2.0 ) - ( row / 2.0 ) * ( tileSize * res );
136     const double right = ( extent.xMinimum() + ( extent.xMaximum() - extent.xMinimum() ) / 2.0 ) + ( col / 2.0 ) * ( tileSize * res );
137     const double top = ( extent.yMinimum() + ( extent.yMaximum() - extent.yMinimum() ) / 2.0 ) + ( row / 2.0 ) * ( tileSize * res );
138     tmi.extent = QgsRectangle( left, bottom, right, top );
139 
140     tmi.resolution = res;
141     tmi.scaleDenominator = scaleDenominator;
142 
143     calculatedTileMatrixInfoMap[crsStr] = tmi;
144 
145     return tmi;
146   }
147 
calculateTileMatrixSet(tileMatrixInfo tmi,double minScale)148   tileMatrixSetDef calculateTileMatrixSet( tileMatrixInfo tmi, double minScale )
149   {
150     QList< tileMatrixDef > tileMatrixList;
151     double scaleDenominator = tmi.scaleDenominator;
152     const QgsRectangle extent = tmi.extent;
153     const QgsUnitTypes::DistanceUnit unit = tmi.unit;
154 
155     // constant
156     double resolution = tmi.resolution;
157     int column = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileSize * resolution ) );
158     int row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileSize * resolution ) );
159 
160     while ( scaleDenominator >= minScale )
161     {
162       const double scale = scaleDenominator;
163       // Calculate resolution based on scale denominator
164       const double res = resolution;
165       const int col = column;
166       const int r = row;
167 
168       tileMatrixDef tm;
169       tm.resolution = res;
170       tm.scaleDenominator = scale;
171       tm.col = col;
172       tm.row = r;
173       tm.left = extent.xMinimum();
174       tm.top = extent.yMaximum();
175       tileMatrixList.append( tm );
176 
177       scaleDenominator = scale / 2;
178       resolution = res / 2;
179       column = col * 2;
180       row = r * 2;
181     }
182 
183     tileMatrixSetDef tms;
184     tms.ref = tmi.ref;
185     tms.extent = extent;
186     tms.unit = unit;
187     tms.hasAxisInverted = tmi.hasAxisInverted;
188     tms.tileMatrixList = tileMatrixList;
189 
190     return tms;
191   }
192 
getProjectMinScale(const QgsProject * project)193   double getProjectMinScale( const QgsProject *project )
194   {
195     double scale = -1.0;
196 
197     // default scales
198     const QgsSettings settings;
199     const QStringList scaleList = settings.value( QStringLiteral( "Map/scales" ), Qgis::defaultProjectScales() ).toString().split( ',' );
200     //load project scales
201     const bool useProjectScales = project->viewSettings()->useProjectScales();
202     const QVector< double >projectScales = project->viewSettings()->mapScales();
203     if ( useProjectScales && projectScales.empty() )
204     {
205       scale = *std::min_element( projectScales.begin(), projectScales.end() );
206     }
207     else
208     {
209       // get min and max scales
210       if ( !scaleList.isEmpty() )
211       {
212         for ( const QString &scaleText : scaleList )
213         {
214           const double scaleValue = scaleText.toDouble();
215           if ( scale == -1.0 )
216           {
217             scale = scaleValue;
218           }
219           else if ( scaleValue < scale )
220           {
221             scale = scaleValue;
222           }
223         }
224       }
225     }
226     if ( scale < 500.0 )
227     {
228       return 500.0;
229     }
230     return scale;
231   }
232 
getTileMatrixSetList(const QgsProject * project,const QString & tms_ref)233   QList< tileMatrixSetDef > getTileMatrixSetList( const QgsProject *project, const QString &tms_ref )
234   {
235     QList< tileMatrixSetDef > tmsList;
236 
237     bool gridsDefined = false;
238     const QStringList wmtsGridList = project->readListEntry( QStringLiteral( "WMTSGrids" ), QStringLiteral( "CRS" ), QStringList(), &gridsDefined );
239     if ( gridsDefined )
240     {
241       if ( !tms_ref.isEmpty() && !wmtsGridList.contains( tms_ref ) )
242       {
243         throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) );
244       }
245 
246       const QStringList wmtsGridConfigList = project->readListEntry( QStringLiteral( "WMTSGrids" ), QStringLiteral( "Config" ) );
247       for ( const QString &c : wmtsGridConfigList )
248       {
249         QStringList config = c.split( ',' );
250         const QString crsStr = config[0];
251         if ( !tms_ref.isEmpty() && tms_ref != crsStr )
252         {
253           continue;
254         }
255 
256         tileMatrixInfo tmi;
257         double fixedTop = 0.0;
258         double fixedLeft = 0.0;
259         double resolution = -1.0;
260         int col = -1;
261         int row = -1;
262         // Does the CRS have fixed tile matrices
263         if ( fixedTileMatrixInfoMap.contains( crsStr ) )
264         {
265           tmi = fixedTileMatrixInfoMap[crsStr];
266           // Calculate resolution based on scale denominator
267           resolution = tmi.resolution;
268           // Get fixed corner
269           const QgsRectangle extent = tmi.extent;
270           fixedTop = extent.yMaximum();
271           fixedLeft = extent.xMinimum();
272           // Get numbers of column and row for the resolution to cover the extent
273           col = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileSize * resolution ) );
274           row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileSize * resolution ) );
275         }
276         else
277         {
278           tmi.ref = crsStr;
279 
280           fixedTop = QVariant( config[1] ).toDouble();
281           fixedLeft = QVariant( config[2] ).toDouble();
282           const double minScale = QVariant( config[3] ).toDouble();
283 
284           tmi.scaleDenominator = minScale;
285 
286           const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsStr );
287           tmi.unit = crs.mapUnits();
288           tmi.hasAxisInverted = crs.hasAxisInverted();
289 
290           const QgsCoordinateTransform crsTransform( QgsCoordinateReferenceSystem::fromOgcWmsCrs( geoEpsgCrsAuthId() ), crs, project );
291           try
292           {
293             // firstly transform CRS bounds expressed in WGS84 to CRS
294             const QgsRectangle extent = crsTransform.transformBoundingBox( crs.bounds() );
295             // Calculate resolution based on scale denominator
296             resolution = POINTS_TO_M * minScale / QgsUnitTypes::fromUnitToUnitFactor( tmi.unit, QgsUnitTypes::DistanceMeters );
297             // Get numbers of column and row for the resolution to cover the extent
298             col = std::ceil( ( extent.xMaximum() - extent.xMinimum() ) / ( tileSize * resolution ) );
299             row = std::ceil( ( extent.yMaximum() - extent.yMinimum() ) / ( tileSize * resolution ) );
300             // Calculate extent
301             const double bottom =  fixedTop - row * tileSize * resolution;
302             const double right =  fixedLeft + col * tileSize * resolution;
303             tmi.extent = QgsRectangle( fixedLeft, bottom, right, fixedTop );
304           }
305           catch ( QgsCsException &cse )
306           {
307             Q_UNUSED( cse )
308             continue;
309           }
310         }
311         // get lastLevel
312         tmi.lastLevel = QVariant( config[4] ).toInt();
313 
314         QList< tileMatrixDef > tileMatrixList;
315         for ( int i = 0; i <= tmi.lastLevel; ++i )
316         {
317           const double scale = tmi.scaleDenominator / std::pow( 2, i );
318           const double res = resolution / std::pow( 2, i );
319 
320           tileMatrixDef tm;
321           tm.resolution = res;
322           tm.scaleDenominator = scale;
323           tm.col = col * std::pow( 2, i );
324           tm.row = row * std::pow( 2, i );
325           tm.left = fixedLeft;
326           tm.top = fixedTop;
327           tileMatrixList.append( tm );
328         }
329 
330         tileMatrixSetDef tms;
331         tms.ref = tmi.ref;
332         tms.extent = tmi.extent;
333         tms.unit = tmi.unit;
334         tms.hasAxisInverted = tmi.hasAxisInverted;
335         tms.tileMatrixList = tileMatrixList;
336 
337         tmsList.append( tms );
338       }
339       return tmsList;
340     }
341 
342     double minScale = project->readNumEntry( QStringLiteral( "WMTSMinScale" ), QStringLiteral( "/" ), -1.0 );
343     if ( minScale == -1.0 )
344     {
345       minScale = getProjectMinScale( project );
346     }
347 
348     const QStringList crsList = QgsServerProjectUtils::wmsOutputCrsList( *project );
349     if ( !tms_ref.isEmpty() && !crsList.contains( tms_ref ) )
350     {
351       throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) );
352     }
353     for ( const QString &crsStr : crsList )
354     {
355       if ( !tms_ref.isEmpty() && tms_ref != crsStr )
356       {
357         continue;
358       }
359       const tileMatrixInfo tmi = calculateTileMatrixInfo( crsStr, project );
360       if ( tmi.scaleDenominator > 0.0 )
361       {
362         tmsList.append( calculateTileMatrixSet( tmi, minScale ) );
363       }
364     }
365 
366     return tmsList;
367   }
368 
getWmtsLayerList(QgsServerInterface * serverIface,const QgsProject * project)369   QList< layerDef > getWmtsLayerList( QgsServerInterface *serverIface, const QgsProject *project )
370   {
371     QList< layerDef > wmtsLayers;
372 #ifdef HAVE_SERVER_PYTHON_PLUGINS
373     QgsAccessControl *accessControl = serverIface->accessControls();
374 #else
375     ( void )serverIface;
376 #endif
377 
378     // WMTS Project configuration
379     const bool wmtsProject = project->readBoolEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ) );
380 
381     // Root Layer name
382     QString rootLayerName = QgsServerProjectUtils::wmsRootName( *project );
383     if ( rootLayerName.isEmpty() && !project->title().isEmpty() )
384     {
385       rootLayerName = project->title();
386     }
387 
388     if ( wmtsProject && !rootLayerName.isEmpty() )
389     {
390       layerDef pLayer;
391       pLayer.id = rootLayerName;
392 
393       if ( !project->title().isEmpty() )
394       {
395         pLayer.title = project->title();
396         pLayer.abstract = project->title();
397       }
398 
399       //transform the project native CRS into WGS84
400       const QgsRectangle projRect = QgsServerProjectUtils::wmsExtent( *project );
401       const QgsCoordinateReferenceSystem projCrs = project->crs();
402       const QgsCoordinateTransform exGeoTransform( projCrs, wgs84, project );
403       try
404       {
405         pLayer.wgs84BoundingRect = exGeoTransform.transformBoundingBox( projRect );
406       }
407       catch ( const QgsCsException & )
408       {
409         pLayer.wgs84BoundingRect = QgsRectangle( -180, -90, 180, 90 );
410       }
411 
412       // Formats
413       const bool wmtsPngProject = project->readBoolEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Project" ) );
414       if ( wmtsPngProject )
415         pLayer.formats << QStringLiteral( "image/png" );
416       const bool wmtsJpegProject = project->readBoolEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Project" ) );
417       if ( wmtsJpegProject )
418         pLayer.formats << QStringLiteral( "image/jpeg" );
419 
420       // Project is not queryable in WMS
421       //pLayer.queryable = ( nonIdentifiableLayers.count() != project->count() );
422       pLayer.queryable = false;
423 
424       wmtsLayers.append( pLayer );
425     }
426 
427     const QStringList wmtsGroupNameList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ) );
428     if ( !wmtsGroupNameList.isEmpty() )
429     {
430       QgsLayerTreeGroup *treeRoot = project->layerTreeRoot();
431 
432       const QStringList wmtsPngGroupNameList = project->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Group" ) );
433       const QStringList wmtsJpegGroupNameList = project->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Group" ) );
434 
435       for ( const QString &gName : wmtsGroupNameList )
436       {
437         QgsLayerTreeGroup *treeGroup = treeRoot->findGroup( gName );
438         if ( !treeGroup )
439         {
440           continue;
441         }
442 
443         layerDef pLayer;
444         pLayer.id = treeGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
445         if ( pLayer.id.isEmpty() )
446           pLayer.id = gName;
447 
448         pLayer.title = treeGroup->customProperty( QStringLiteral( "wmsTitle" ) ).toString();
449         if ( pLayer.title.isEmpty() )
450           pLayer.title = gName;
451 
452         pLayer.abstract = treeGroup->customProperty( QStringLiteral( "wmsAbstract" ) ).toString();
453 
454         QgsRectangle wgs84BoundingRect;
455         bool queryable = false;
456         double maxScale = 0.0;
457         double minScale = 0.0;
458         for ( QgsLayerTreeLayer *layer : treeGroup->findLayers() )
459         {
460           QgsMapLayer *l = layer->layer();
461           if ( !l )
462           {
463             continue;
464           }
465           //transform the layer native CRS into WGS84
466           const QgsCoordinateReferenceSystem layerCrs = l->crs();
467           const QgsCoordinateTransform exGeoTransform( layerCrs, wgs84, project );
468           try
469           {
470             wgs84BoundingRect.combineExtentWith( exGeoTransform.transformBoundingBox( l->extent() ) );
471           }
472           catch ( const QgsCsException & )
473           {
474             wgs84BoundingRect.combineExtentWith( QgsRectangle( -180, -90, 180, 90 ) );
475           }
476           if ( !queryable && l->flags().testFlag( QgsMapLayer::Identifiable ) )
477           {
478             queryable = true;
479           }
480 
481           if ( l->hasScaleBasedVisibility() )
482           {
483             const double lMaxScale = l->maximumScale();
484             if ( lMaxScale > 0.0 && lMaxScale > maxScale )
485             {
486               maxScale = lMaxScale;
487             }
488             const double lMinScale = l->minimumScale();
489             if ( lMinScale > 0.0 && ( minScale == 0.0 || lMinScale < minScale ) )
490             {
491               minScale = lMinScale;
492             }
493           }
494         }
495         pLayer.wgs84BoundingRect = wgs84BoundingRect;
496         pLayer.queryable = queryable;
497         pLayer.maxScale = maxScale;
498         pLayer.minScale = minScale;
499 
500         // Formats
501         if ( wmtsPngGroupNameList.contains( gName ) )
502           pLayer.formats << QStringLiteral( "image/png" );
503         if ( wmtsJpegGroupNameList.contains( gName ) )
504           pLayer.formats << QStringLiteral( "image/jpeg" );
505 
506         wmtsLayers.append( pLayer );
507       }
508     }
509 
510     const QStringList wmtsLayerIdList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ) );
511     const QStringList wmtsPngLayerIdList = project->readListEntry( QStringLiteral( "WMTSPngLayers" ), QStringLiteral( "Layer" ) );
512     const QStringList wmtsJpegLayerIdList = project->readListEntry( QStringLiteral( "WMTSJpegLayers" ), QStringLiteral( "Layer" ) );
513 
514     for ( const QString &lId : wmtsLayerIdList )
515     {
516       QgsMapLayer *l = project->mapLayer( lId );
517       if ( !l )
518       {
519         continue;
520       }
521 #ifdef HAVE_SERVER_PYTHON_PLUGINS
522       if ( !accessControl->layerReadPermission( l ) )
523       {
524         continue;
525       }
526 #endif
527 
528       layerDef pLayer;
529       pLayer.id = l->name();
530       if ( !l->shortName().isEmpty() )
531         pLayer.id = l->shortName();
532       pLayer.id = pLayer.id.replace( ' ', '_' );
533 
534       pLayer.title = l->title();
535       pLayer.abstract = l->abstract();
536 
537       //transform the layer native CRS into WGS84
538       const QgsCoordinateReferenceSystem layerCrs = l->crs();
539       const QgsCoordinateTransform exGeoTransform( layerCrs, wgs84, project );
540       try
541       {
542         pLayer.wgs84BoundingRect = exGeoTransform.transformBoundingBox( l->extent() );
543       }
544       catch ( const QgsCsException & )
545       {
546         pLayer.wgs84BoundingRect = QgsRectangle( -180, -90, 180, 90 );
547       }
548 
549       // Formats
550       if ( wmtsPngLayerIdList.contains( lId ) )
551         pLayer.formats << QStringLiteral( "image/png" );
552       if ( wmtsJpegLayerIdList.contains( lId ) )
553         pLayer.formats << QStringLiteral( "image/jpeg" );
554 
555       pLayer.queryable = ( l->flags().testFlag( QgsMapLayer::Identifiable ) );
556 
557       if ( l->hasScaleBasedVisibility() )
558       {
559         pLayer.maxScale = l->maximumScale();
560         pLayer.minScale = l->minimumScale();
561       }
562       else
563       {
564         pLayer.maxScale = 0.0;
565         pLayer.minScale = 0.0;
566       }
567 
568       wmtsLayers.append( pLayer );
569     }
570     return wmtsLayers;
571   }
572 
getLayerTileMatrixSetLink(const layerDef layer,const tileMatrixSetDef tms,const QgsProject * project)573   tileMatrixSetLinkDef getLayerTileMatrixSetLink( const layerDef layer, const tileMatrixSetDef tms, const QgsProject *project )
574   {
575     tileMatrixSetLinkDef tmsl;
576 
577     QMap< int, tileMatrixLimitDef > tileMatrixLimits;
578 
579     QgsRectangle rect( layer.wgs84BoundingRect );
580     if ( tms.ref != QLatin1String( "EPSG:4326" ) )
581     {
582       const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( tms.ref );
583       const QgsCoordinateTransform exGeoTransform( wgs84, crs, project );
584       try
585       {
586         rect = exGeoTransform.transformBoundingBox( layer.wgs84BoundingRect );
587       }
588       catch ( const QgsCsException & )
589       {
590         return tmsl;
591       }
592     }
593     tmsl.ref = tms.ref;
594 
595     rect = rect.intersect( tms.extent );
596 
597     int tmIdx = -1;
598     for ( const tileMatrixDef &tm : tms.tileMatrixList )
599     {
600       ++tmIdx;
601 
602       if ( layer.maxScale > 0.0 && tm.scaleDenominator > layer.maxScale )
603       {
604         continue;
605       }
606       if ( layer.minScale > 0.0 && tm.scaleDenominator < layer.minScale )
607       {
608         continue;
609       }
610 
611       const double res = tm.resolution;
612 
613       tileMatrixLimitDef tml;
614       tml.minCol = std::floor( ( rect.xMinimum() - tm.left ) / ( tileSize * res ) );
615       tml.maxCol = std::ceil( ( rect.xMaximum() - tm.left ) / ( tileSize * res ) ) - 1;
616       tml.minRow = std::floor( ( tm.top - rect.yMaximum() ) / ( tileSize * res ) );
617       tml.maxRow = std::ceil( ( tm.top - rect.yMinimum() ) / ( tileSize * res ) ) - 1;
618 
619       tileMatrixLimits[tmIdx] = tml;
620     }
621 
622     tmsl.tileMatrixLimits = tileMatrixLimits;
623     return tmsl;
624   }
625 
translateWmtsParamToWmsQueryItem(const QString & request,const QgsWmtsParameters & params,const QgsProject * project,QgsServerInterface * serverIface)626   QUrlQuery translateWmtsParamToWmsQueryItem( const QString &request, const QgsWmtsParameters &params,
627       const QgsProject *project, QgsServerInterface *serverIface )
628   {
629 #ifndef HAVE_SERVER_PYTHON_PLUGINS
630     ( void )serverIface;
631 #endif
632 
633     //defining Layer
634     const QString layer = params.layer();
635     //read Layer
636     if ( layer.isEmpty() )
637     {
638       throw QgsRequestNotWellFormedException( QStringLiteral( "Layer is mandatory" ) );
639     }
640     //check layer value
641     const bool wmtsProject = project->readBoolEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Project" ) );
642     const QStringList wmtsGroupNameList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Group" ) );
643     const QStringList wmtsLayerIdList = project->readListEntry( QStringLiteral( "WMTSLayers" ), QStringLiteral( "Layer" ) );
644     QStringList wmtsLayerIds;
645     if ( wmtsProject )
646     {
647       // Root Layer name
648       QString rootLayerId = QgsServerProjectUtils::wmsRootName( *project );
649       if ( rootLayerId.isEmpty() )
650       {
651         rootLayerId = project->title();
652       }
653       if ( !rootLayerId.isEmpty() )
654       {
655         wmtsLayerIds << rootLayerId;
656       }
657     }
658     if ( !wmtsGroupNameList.isEmpty() )
659     {
660       QgsLayerTreeGroup *treeRoot = project->layerTreeRoot();
661       for ( const QString &gName : wmtsGroupNameList )
662       {
663         QgsLayerTreeGroup *treeGroup = treeRoot->findGroup( gName );
664         if ( !treeGroup )
665         {
666           continue;
667         }
668         QString groupLayerId = treeGroup->customProperty( QStringLiteral( "wmsShortName" ) ).toString();
669         if ( groupLayerId.isEmpty() )
670         {
671           groupLayerId = gName;
672         }
673         wmtsLayerIds << groupLayerId;
674       }
675     }
676     if ( !wmtsLayerIdList.isEmpty() )
677     {
678 #ifdef HAVE_SERVER_PYTHON_PLUGINS
679       QgsAccessControl *accessControl = serverIface->accessControls();
680 #endif
681       for ( const QString &lId : wmtsLayerIdList )
682       {
683         QgsMapLayer *l = project->mapLayer( lId );
684         if ( !l )
685         {
686           continue;
687         }
688 #ifdef HAVE_SERVER_PYTHON_PLUGINS
689         if ( !accessControl->layerReadPermission( l ) )
690         {
691           continue;
692         }
693 #endif
694         QString layerLayerId = l->shortName();
695         if ( layerLayerId.isEmpty() )
696         {
697           layerLayerId = l->name();
698         }
699         wmtsLayerIds << layerLayerId;
700       }
701     }
702     if ( !wmtsLayerIds.contains( layer ) )
703     {
704       const QString msg = QObject::tr( "Layer '%1' not found" ).arg( layer );
705       throw QgsBadRequestException( QStringLiteral( "LayerNotDefined" ), msg );
706     }
707 
708     //defining Format
709     const QString format = params.formatAsString();
710     //read Format
711     if ( format.isEmpty() )
712     {
713       throw QgsRequestNotWellFormedException( QStringLiteral( "Format is mandatory" ) );
714     }
715 
716     //defining TileMatrixSet ref
717     const QString tms_ref = params.tileMatrixSet();
718     //read TileMatrixSet
719     if ( tms_ref.isEmpty() )
720     {
721       throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is mandatory" ) );
722     }
723 
724     // verifying TileMatrixSet value
725     QList< tileMatrixSetDef > tmsList = getTileMatrixSetList( project, tms_ref );
726     if ( tmsList.isEmpty() )
727     {
728       throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) );
729     }
730     const tileMatrixSetDef tms = tmsList[0];
731     if ( tms.ref != tms_ref )
732     {
733       throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrixSet is unknown" ) );
734     }
735 
736     //defining TileMatrix idx
737     const int tm_idx = params.tileMatrixAsInt();
738     //read TileMatrix
739     if ( tm_idx < 0 || tms.tileMatrixList.count() < tm_idx )
740     {
741       throw QgsRequestNotWellFormedException( QStringLiteral( "TileMatrix is unknown" ) );
742     }
743     const tileMatrixDef tm = tms.tileMatrixList.at( tm_idx );
744 
745     //defining TileRow
746     const int tr = params.tileRowAsInt();
747     //read TileRow
748     if ( tr < 0 || tm.row <= tr )
749     {
750       throw QgsRequestNotWellFormedException( QStringLiteral( "TileRow is unknown" ) );
751     }
752 
753     //defining TileCol
754     const int tc = params.tileColAsInt();
755     //read TileCol
756     if ( tc < 0 || tm.col <= tc )
757     {
758       throw QgsRequestNotWellFormedException( QStringLiteral( "TileCol is unknown" ) );
759     }
760 
761     const double res = tm.resolution;
762     const double minx = tm.left + tc * ( tileSize * res );
763     const double miny = tm.top - ( tr + 1 ) * ( tileSize * res );
764     const double maxx = tm.left + ( tc + 1 ) * ( tileSize * res );
765     const double maxy = tm.top - tr * ( tileSize * res );
766     QString bbox;
767     if ( tms.hasAxisInverted )
768     {
769       bbox = qgsDoubleToString( miny, 6 ) + ',' +
770              qgsDoubleToString( minx, 6 ) + ',' +
771              qgsDoubleToString( maxy, 6 ) + ',' +
772              qgsDoubleToString( maxx, 6 );
773     }
774     else
775     {
776       bbox = qgsDoubleToString( minx, 6 ) + ',' +
777              qgsDoubleToString( miny, 6 ) + ',' +
778              qgsDoubleToString( maxx, 6 ) + ',' +
779              qgsDoubleToString( maxy, 6 );
780     }
781 
782     QUrlQuery query;
783     if ( !params.value( QStringLiteral( "MAP" ) ).isEmpty() )
784     {
785       query.addQueryItem( QgsServerParameter::name( QgsServerParameter::MAP ), params.value( QStringLiteral( "MAP" ) ) );
786     }
787     query.addQueryItem( QgsServerParameter::name( QgsServerParameter::SERVICE ), QStringLiteral( "WMS" ) );
788     query.addQueryItem( QgsServerParameter::name( QgsServerParameter::VERSION_SERVICE ), QStringLiteral( "1.3.0" ) );
789     query.addQueryItem( QgsServerParameter::name( QgsServerParameter::REQUEST ), request );
790     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::LAYERS ), layer );
791     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::STYLES ), QString() );
792     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::CRS ), tms.ref );
793     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::BBOX ), bbox );
794     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::WIDTH ), QStringLiteral( "256" ) );
795     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::HEIGHT ), QStringLiteral( "256" ) );
796     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::FORMAT ), format );
797     if ( params.format() == QgsWmtsParameters::Format::PNG )
798     {
799       query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::TRANSPARENT ), QStringLiteral( "true" ) );
800     }
801     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::DPI ), QStringLiteral( "96" ) );
802     query.addQueryItem( QgsWmsParameterForWmts::name( QgsWmsParameterForWmts::TILED ), QStringLiteral( "true" ) );
803 
804     return query;
805   }
806 
807   namespace
808   {
809 
populateFixedTileMatrixInfoMap()810     QMap< QString, tileMatrixInfo> populateFixedTileMatrixInfoMap()
811     {
812       QMap< QString, tileMatrixInfo> m;
813 
814       // Tile matrix information
815       // to build tile matrix set like Google Mercator or TMS
816       // some references for resolution
817       // https://github.com/mapserver/mapcache/blob/master/lib/configuration.c#L94
818       tileMatrixInfo tmi3857;
819       tmi3857.ref = QStringLiteral( "EPSG:3857" );
820       tmi3857.extent = QgsRectangle( -20037508.3427892480, -20037508.3427892480, 20037508.3427892480, 20037508.3427892480 );
821       tmi3857.resolution = 156543.0339280410;
822       tmi3857.scaleDenominator = 559082264.0287179;
823       tmi3857.unit = QgsUnitTypes::DistanceMeters;
824       m[tmi3857.ref] = tmi3857;
825 
826       // To build tile matrix set like mapcache for WGS84
827       // some references for resolution
828       // https://github.com/mapserver/mapcache/blob/master/lib/configuration.c#L73
829       tileMatrixInfo tmi4326;
830       tmi4326.ref = QStringLiteral( "EPSG:4326" );
831       tmi4326.extent = QgsRectangle( -180, -90, 180, 90 );
832       tmi4326.resolution = 0.703125000000000;
833       tmi4326.scaleDenominator = 279541132.0143588675418869;
834       tmi4326.unit = QgsUnitTypes::DistanceDegrees;
835       tmi4326.hasAxisInverted = true;
836       m[tmi4326.ref] = tmi4326;
837 
838       return m;
839     }
840 
841   }
842 
843 } // namespace QgsWmts
844 
845