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 ¶ms, 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