diff --git a/src/mapserver/CMakeLists.txt b/src/mapserver/CMakeLists.txt index 75ecaecf3333..990a139d1279 100644 --- a/src/mapserver/CMakeLists.txt +++ b/src/mapserver/CMakeLists.txt @@ -26,6 +26,7 @@ SET ( qgis_mapserv_SRCS qgssldparser.cpp qgswmsserver.cpp qgswfsserver.cpp + qgswcsserver.cpp qgsmapserviceexception.cpp qgsmslayercache.cpp qgsfilter.cpp diff --git a/src/mapserver/qgis_map_serv.cpp b/src/mapserver/qgis_map_serv.cpp index 65748a26e3a9..89c77b04907e 100644 --- a/src/mapserver/qgis_map_serv.cpp +++ b/src/mapserver/qgis_map_serv.cpp @@ -27,6 +27,7 @@ map service syntax for SOAP/HTTP POST #include "qgslogger.h" #include "qgswmsserver.h" #include "qgswfsserver.h" +#include "qgswcsserver.h" #include "qgsmaprenderer.h" #include "qgsmapserviceexception.h" #include "qgspallabeling.h" @@ -346,7 +347,99 @@ int main( int argc, char * argv[] ) } QgsWMSServer* theServer = 0; - if ( serviceString == "WFS" ) + if ( serviceString == "WCS" ) + { + delete theServer; + QgsWCSServer* theServer = 0; + try + { + theServer = new QgsWCSServer( parameterMap ); + } + catch ( QgsMapServiceException e ) //admin.sld may be invalid + { + theRequestHandler->sendServiceException( e ); + continue; + } + + theServer->setAdminConfigParser( adminConfigParser ); + + + //request type + QString request = parameterMap.value( "REQUEST" ); + if ( request.isEmpty() ) + { + //do some error handling + QgsDebugMsg( "unable to find 'REQUEST' parameter, exiting..." ); + theRequestHandler->sendServiceException( QgsMapServiceException( "OperationNotSupported", "Please check the value of the REQUEST parameter" ) ); + delete theRequestHandler; + delete theServer; + continue; + } + + if ( request.compare( "GetCapabilities", Qt::CaseInsensitive ) == 0 ) + { + QDomDocument capabilitiesDocument; + try + { + capabilitiesDocument = theServer->getCapabilities(); + } + catch ( QgsMapServiceException& ex ) + { + theRequestHandler->sendServiceException( ex ); + delete theRequestHandler; + delete theServer; + continue; + } + QgsDebugMsg( "sending GetCapabilities response" ); + theRequestHandler->sendGetCapabilitiesResponse( capabilitiesDocument ); + delete theRequestHandler; + delete theServer; + continue; + } + else if ( request.compare( "DescribeCoverage", Qt::CaseInsensitive ) == 0 ) + { + QDomDocument describeDocument; + try + { + describeDocument = theServer->describeCoverage(); + } + catch ( QgsMapServiceException& ex ) + { + theRequestHandler->sendServiceException( ex ); + delete theRequestHandler; + delete theServer; + continue; + } + QgsDebugMsg( "sending GetCapabilities response" ); + theRequestHandler->sendGetCapabilitiesResponse( describeDocument ); + delete theRequestHandler; + delete theServer; + continue; + } + else if ( request.compare( "GetCoverage", Qt::CaseInsensitive ) == 0 ) + { + QByteArray* coverageOutput; + try + { + coverageOutput = theServer->getCoverage(); + } + catch ( QgsMapServiceException& ex ) + { + theRequestHandler->sendServiceException( ex ); + delete theRequestHandler; + delete theServer; + continue; + } + if ( coverageOutput ) + { + theRequestHandler->sendGetCoverageResponse( coverageOutput ); + } + delete theRequestHandler; + delete theServer; + continue; + } + } + else if ( serviceString == "WFS" ) { delete theServer; QgsWFSServer* theServer = 0; diff --git a/src/mapserver/qgsconfigparser.h b/src/mapserver/qgsconfigparser.h index c5eaafe3bc0b..b7cc1a10b90f 100644 --- a/src/mapserver/qgsconfigparser.h +++ b/src/mapserver/qgsconfigparser.h @@ -49,12 +49,19 @@ class QgsConfigParser virtual void featureTypeList( QDomElement& parentElement, QDomDocument& doc ) const = 0; + virtual void wcsContentMetadata( QDomElement& parentElement, QDomDocument& doc ) const = 0; + virtual void owsGeneralAndResourceList( QDomElement& parentElement, QDomDocument& doc, const QString& strHref ) const = 0; virtual void describeFeatureType( const QString& aTypeName, QDomElement& parentElement, QDomDocument& doc ) const = 0; + + virtual void describeCoverage( const QString& aCoveName, QDomElement& parentElement, QDomDocument& doc ) const = 0; /**Returns one or possibly several maplayers for a given type name. If no layers are found, an empty list is returned*/ virtual QList mapLayerFromTypeName( const QString& tName, bool useCache = true ) const = 0; + /**Returns one or possibly several maplayers for a given type name. If no layers are found, an empty list is returned*/ + virtual QList mapLayerFromCoverage( const QString& cName, bool useCache = true ) const = 0; + /**Returns one or possibly several maplayers for a given layer name and style. If there are several layers, the layers should be drawn in inverse list order. If no layers/style are found, an empty list is returned @param allowCache true if layer can be read from / written to cache*/ diff --git a/src/mapserver/qgshttprequesthandler.cpp b/src/mapserver/qgshttprequesthandler.cpp index 18bd181be0c3..f26b66b6fdc5 100644 --- a/src/mapserver/qgshttprequesthandler.cpp +++ b/src/mapserver/qgshttprequesthandler.cpp @@ -362,6 +362,11 @@ void QgsHttpRequestHandler::endGetFeatureResponse( QByteArray* ba ) const fwrite( ba->data(), ba->size(), 1, FCGI_stdout ); } +void QgsHttpRequestHandler::sendGetCoverageResponse( QByteArray* ba ) const +{ + sendHttpResponse( ba, "image/tiff" ); +} + void QgsHttpRequestHandler::requestStringToParameterMap( const QString& request, QMap& parameters ) { parameters.clear(); diff --git a/src/mapserver/qgshttprequesthandler.h b/src/mapserver/qgshttprequesthandler.h index 90bbb386f665..1bb245b44842 100644 --- a/src/mapserver/qgshttprequesthandler.h +++ b/src/mapserver/qgshttprequesthandler.h @@ -42,6 +42,7 @@ class QgsHttpRequestHandler: public QgsRequestHandler virtual bool startGetFeatureResponse( QByteArray* ba, const QString& infoFormat ) const; virtual void sendGetFeatureResponse( QByteArray* ba ) const; virtual void endGetFeatureResponse( QByteArray* ba ) const; + virtual void sendGetCoverageResponse( QByteArray* ba ) const; protected: void sendHttpResponse( QByteArray* ba, const QString& format ) const; diff --git a/src/mapserver/qgsprojectparser.cpp b/src/mapserver/qgsprojectparser.cpp index b26737a1e9d3..2eda2f38839b 100644 --- a/src/mapserver/qgsprojectparser.cpp +++ b/src/mapserver/qgsprojectparser.cpp @@ -277,6 +277,81 @@ void QgsProjectParser::featureTypeList( QDomElement& parentElement, QDomDocument return; } +void QgsProjectParser::wcsContentMetadata( QDomElement& parentElement, QDomDocument& doc ) const +{ + + if ( mProjectLayerElements.size() < 1 ) + { + return; + } + + QMap layerMap; + + foreach ( const QDomElement &elem, mProjectLayerElements ) + { + QString type = elem.attribute( "type" ); + if ( type == "raster" ) + { + //QgsMapLayer *layer = createLayerFromElement( *layerIt ); + QgsMapLayer *layer = createLayerFromElement( elem ); + if ( layer ) + { + QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) ); + layerMap.insert( layer->id(), layer ); + + QDomElement layerElem = doc.createElement( "CoverageOfferingBrief" ); + QDomElement nameElem = doc.createElement( "name" ); + //We use the layer name even though it might not be unique. + //Because the id sometimes contains user/pw information and the name is more descriptive + QString typeName = layer->name(); + typeName = typeName.replace( QString( " " ), QString( "_" ) ); + QDomText nameText = doc.createTextNode( typeName ); + nameElem.appendChild( nameText ); + layerElem.appendChild( nameElem ); + + QDomElement labelElem = doc.createElement( "label" ); + QString titleName = layer->title(); + if ( titleName.isEmpty() ) + { + titleName = layer->name(); + } + QDomText labelText = doc.createTextNode( titleName ); + labelElem.appendChild( labelText ); + layerElem.appendChild( labelElem ); + + QDomElement descriptionElem = doc.createElement( "description" ); + QString abstractName = layer->abstract(); + if ( abstractName.isEmpty() ) + { + abstractName = ""; + } + QDomText descriptionText = doc.createTextNode( abstractName ); + descriptionElem.appendChild( descriptionText ); + layerElem.appendChild( descriptionElem ); + + //lonLatEnvelope + const QgsCoordinateReferenceSystem& layerCrs = layer->crs(); + QgsCoordinateTransform t( layerCrs, QgsCoordinateReferenceSystem(4326) ); + //transform + QgsRectangle BBox = t.transformBoundingBox( layer->extent() ); + QDomElement lonLatElem = doc.createElement( "lonLatEnvelope" ); + lonLatElem.setAttribute( "srsName", "urn:ogc:def:crs:OGC:1.3:CRS84" ); + QDomElement lowerPosElem = doc.createElement( "gml:pos" ); + QDomText lowerPosText = doc.createTextNode( QString::number( BBox.xMinimum() ) + " " + QString::number( BBox.yMinimum() ) ); + lowerPosElem.appendChild( lowerPosText ); + lonLatElem.appendChild( lowerPosElem ); + QDomElement upperPosElem = doc.createElement( "gml:pos" ); + QDomText upperPosText = doc.createTextNode( QString::number( BBox.xMaximum() ) + " " + QString::number( BBox.yMaximum() ) ); + upperPosElem.appendChild( upperPosText ); + lonLatElem.appendChild( upperPosElem ); + layerElem.appendChild( lonLatElem ); + + parentElement.appendChild( layerElem ); + } + } + } +} + void QgsProjectParser::owsGeneralAndResourceList( QDomElement& parentElement, QDomDocument& doc, const QString& strHref ) const { // set parentElement id @@ -552,6 +627,207 @@ void QgsProjectParser::describeFeatureType( const QString& aTypeName, QDomElemen } QgsMapLayerRegistry::instance()->removeAllMapLayers(); return; + +} + +void QgsProjectParser::describeCoverage( const QString& aCoveName, QDomElement& parentElement, QDomDocument& doc ) const +{ + + if ( mProjectLayerElements.size() < 1 ) + { + return; + } + + QStringList coveNameList; + if ( aCoveName != "" ) + { + QStringList coveNameSplit = aCoveName.split( "," ); + foreach ( const QString &str, coveNameSplit ) + { + coveNameList << str; + } + } + + QMap layerMap; + + foreach ( const QDomElement &elem, mProjectLayerElements ) + { + QString type = elem.attribute( "type" ); + if ( type == "raster" ) + { + //QgsMapLayer *layer = createLayerFromElement( *layerIt ); + QgsMapLayer *layer = createLayerFromElement( elem ); + QString coveName = layer->name(); + coveName = coveName.replace( QString( " " ), QString( "_" ) ); + if ( layer && ( aCoveName == "" || coveNameList.contains( coveName ) ) ) + { + QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) ); + layerMap.insert( layer->id(), layer ); + + QDomElement layerElem = doc.createElement( "CoverageOffering" ); + QDomElement nameElem = doc.createElement( "name" ); + //We use the layer name even though it might not be unique. + //Because the id sometimes contains user/pw information and the name is more descriptive + QString typeName = layer->name(); + typeName = typeName.replace( QString( " " ), QString( "_" ) ); + QDomText nameText = doc.createTextNode( typeName ); + nameElem.appendChild( nameText ); + layerElem.appendChild( nameElem ); + + QDomElement labelElem = doc.createElement( "label" ); + QString titleName = layer->title(); + if ( titleName.isEmpty() ) + { + titleName = layer->name(); + } + QDomText labelText = doc.createTextNode( titleName ); + labelElem.appendChild( labelText ); + layerElem.appendChild( labelElem ); + + QDomElement descriptionElem = doc.createElement( "description" ); + QString abstractName = layer->abstract(); + if ( abstractName.isEmpty() ) + { + abstractName = ""; + } + QDomText descriptionText = doc.createTextNode( abstractName ); + descriptionElem.appendChild( descriptionText ); + layerElem.appendChild( descriptionElem ); + + //lonLatEnvelope + const QgsCoordinateReferenceSystem& layerCrs = layer->crs(); + QgsCoordinateTransform t( layerCrs, QgsCoordinateReferenceSystem(4326) ); + //transform + QgsRectangle BBox = t.transformBoundingBox( layer->extent() ); + QDomElement lonLatElem = doc.createElement( "lonLatEnvelope" ); + lonLatElem.setAttribute( "srsName", "urn:ogc:def:crs:OGC:1.3:CRS84" ); + QDomElement lowerPosElem = doc.createElement( "gml:pos" ); + QDomText lowerPosText = doc.createTextNode( QString::number( BBox.xMinimum() ) + " " + QString::number( BBox.yMinimum() ) ); + lowerPosElem.appendChild( lowerPosText ); + lonLatElem.appendChild( lowerPosElem ); + QDomElement upperPosElem = doc.createElement( "gml:pos" ); + QDomText upperPosText = doc.createTextNode( QString::number( BBox.xMaximum() ) + " " + QString::number( BBox.yMaximum() ) ); + upperPosElem.appendChild( upperPosText ); + lonLatElem.appendChild( upperPosElem ); + layerElem.appendChild( lonLatElem ); + + QgsRasterLayer* rLayer = dynamic_cast( layer ); + QDomElement domainSetElem = doc.createElement( "domainSet" ); + layerElem.appendChild( domainSetElem ); + + QDomElement spatialDomainElem = doc.createElement( "spatialDomain" ); + domainSetElem.appendChild( spatialDomainElem ); + + QgsRectangle layerBBox = layer->extent(); + QDomElement envelopeElem = doc.createElement( "gml:Envelope" ); + envelopeElem.setAttribute( "srsName", layerCrs.authid() ); + QDomElement lowerCornerElem = doc.createElement( "gml:pos" ); + QDomText lowerCornerText = doc.createTextNode( QString::number( layerBBox.xMinimum() ) + " " + QString::number( layerBBox.yMinimum() ) ); + lowerCornerElem.appendChild( lowerCornerText ); + envelopeElem.appendChild( lowerCornerElem ); + QDomElement upperCornerElem = doc.createElement( "gml:pos" ); + QDomText upperCornerText = doc.createTextNode( QString::number( layerBBox.xMaximum() ) + " " + QString::number( layerBBox.yMaximum() ) ); + upperCornerElem.appendChild( upperCornerText ); + envelopeElem.appendChild( upperCornerElem ); + spatialDomainElem.appendChild( envelopeElem ); + + QDomElement rectGridElem = doc.createElement( "gml:RectifiedGrid" ); + rectGridElem.setAttribute( "dimension", 2 ); + QDomElement limitsElem = doc.createElement( "gml:limits" ); + rectGridElem.appendChild( limitsElem ); + QDomElement gridEnvElem = doc.createElement( "gml:GridEnvelope" ); + limitsElem.appendChild( gridEnvElem ); + QDomElement lowElem = doc.createElement( "gml:low" ); + QDomText lowText = doc.createTextNode( "0 0" ); + lowElem.appendChild( lowText ); + gridEnvElem.appendChild( lowElem ); + QDomElement highElem = doc.createElement( "gml:high" ); + QDomText highText = doc.createTextNode( QString::number( rLayer->width() )+" "+QString::number( rLayer->height() ) ); + highElem.appendChild( highText ); + gridEnvElem.appendChild( highElem ); + spatialDomainElem.appendChild( rectGridElem ); + + QDomElement xAxisElem = doc.createElement( "gml:axisName" ); + QDomText xAxisText = doc.createTextNode( "x" ); + xAxisElem.appendChild( xAxisText ); + spatialDomainElem.appendChild( xAxisElem ); + + QDomElement yAxisElem = doc.createElement( "gml:axisName" ); + QDomText yAxisText = doc.createTextNode( "y" ); + yAxisElem.appendChild( yAxisText ); + spatialDomainElem.appendChild( yAxisElem ); + + QDomElement originElem = doc.createElement( "gml:origin" ); + QDomElement originPosElem = doc.createElement( "gml:pos" ); + QDomText originPosText = doc.createTextNode( QString::number( layerBBox.xMinimum() ) + " " + QString::number( layerBBox.yMaximum() ) ); + originPosElem.appendChild( originPosText ); + spatialDomainElem.appendChild( originElem ); + + QDomElement xOffsetElem = doc.createElement( "gml:offsetVector" ); + QDomText xOffsetText = doc.createTextNode( QString::number( rLayer->rasterUnitsPerPixelX() )+" 0" ); + xOffsetElem.appendChild( xOffsetText ); + spatialDomainElem.appendChild( xOffsetElem ); + + QDomElement yOffsetElem = doc.createElement( "gml:offsetVector" ); + QDomText yOffsetText = doc.createTextNode( "0 "+QString::number( rLayer->rasterUnitsPerPixelY() ) ); + yOffsetElem.appendChild( yOffsetText ); + spatialDomainElem.appendChild( yOffsetElem ); + + QDomElement rangeSetElem = doc.createElement( "rangeSet" ); + layerElem.appendChild( rangeSetElem ); + + QDomElement RangeSetElem = doc.createElement( "RangeSet" ); + rangeSetElem.appendChild( RangeSetElem ); + + QDomElement rsNameElem = doc.createElement( "name" ); + QDomText rsNameText = doc.createTextNode( "Bands" ); + rsNameElem.appendChild( rsNameText ); + RangeSetElem.appendChild( rsNameElem ); + + QDomElement axisDescElem = doc.createElement( "axisDescription" ); + RangeSetElem.appendChild( axisDescElem ); + + QDomElement AxisDescElem = doc.createElement( "AxisDescription" ); + axisDescElem.appendChild( AxisDescElem ); + + QDomElement adNameElem = doc.createElement( "name" ); + QDomText adNameText = doc.createTextNode( "bands" ); + adNameElem.appendChild( adNameText ); + AxisDescElem.appendChild( adNameElem ); + + QDomElement adValuesElem = doc.createElement( "values" ); + for ( int idx = 0; idx < rLayer->bandCount(); ++idx ) + { + QDomElement adValueElem = doc.createElement( "value" ); + QDomText adValueText = doc.createTextNode( QString::number( idx + 1 ) ); + adValueElem.appendChild( adValueText ); + adValuesElem.appendChild( adValueElem ); + } + AxisDescElem.appendChild( adValuesElem ); + + QDomElement sCRSElem = doc.createElement( "supportedCRSs" ); + QDomElement rCRSElem = doc.createElement( "requestResponseCRSs" ); + QDomText rCRSText = doc.createTextNode( layerCrs.authid() ); + rCRSElem.appendChild( rCRSText ); + sCRSElem.appendChild( rCRSElem ); + QDomElement nCRSElem = doc.createElement( "nativeCRSs" ); + QDomText nCRSText = doc.createTextNode( layerCrs.authid() ); + nCRSElem.appendChild( nCRSText ); + sCRSElem.appendChild( nCRSElem ); + layerElem.appendChild( sCRSElem ); + + QDomElement sFormatsElem = doc.createElement( "supportedFormats" ); + sFormatsElem.setAttribute( "nativeFormat", "raw binary" ); + QDomElement formatsElem = doc.createElement( "formats" ); + QDomText formatsText = doc.createTextNode( "GeoTIFF" ); + formatsElem.appendChild( formatsText ); + sFormatsElem.appendChild( formatsElem ); + layerElem.appendChild( sFormatsElem ); + + parentElement.appendChild( layerElem ); + } + } + } } QList QgsProjectParser::mapLayerFromTypeName( const QString& tName, bool useCache ) const @@ -585,6 +861,35 @@ QList QgsProjectParser::mapLayerFromTypeName( const QString& tName return layerList; } +QList QgsProjectParser::mapLayerFromCoverage( const QString& cName, bool useCache ) const +{ + QList layerList; + + if ( mProjectLayerElements.size() < 1 ) + { + return layerList; + } + + foreach ( const QDomElement &elem, mProjectLayerElements ) + { + QString type = elem.attribute( "type" ); + if ( type == "raster" ) + { + QgsMapLayer *mLayer = createLayerFromElement( elem, useCache ); + QgsRasterLayer* layer = dynamic_cast( mLayer ); + + QString coveName = layer->name(); + coveName = coveName.replace( QString( " " ), QString( "_" ) ); + if ( cName == coveName ) + { + layerList.push_back( mLayer ); + return layerList; + } + } + } + return layerList; +} + void QgsProjectParser::addLayers( QDomDocument &doc, QDomElement &parentElem, const QDomElement &legendElem, @@ -2451,6 +2756,11 @@ void QgsProjectParser::serviceCapabilities( QDomElement& parentElement, QDomDocu serviceWFSCapabilities( parentElement, doc ); return; } + if ( docElementTagName == "WCS_Capabilities" ) + { + serviceWCSCapabilities( parentElement, doc ); + return; + } QDomElement serviceElem = doc.createElement( "Service" ); @@ -2733,6 +3043,92 @@ void QgsProjectParser::serviceWFSCapabilities( QDomElement& parentElement, QDomD parentElement.appendChild( serviceElem ); } +void QgsProjectParser::serviceWCSCapabilities( QDomElement& parentElement, QDomDocument& doc ) const +{ + QDomElement serviceElem = doc.createElement( "Service" ); + + QDomElement propertiesElem = mXMLDoc->documentElement().firstChildElement( "properties" ); + if ( propertiesElem.isNull() ) + { + QgsConfigParser::serviceCapabilities( parentElement, doc ); + return; + } + + QDomElement serviceCapabilityElem = propertiesElem.firstChildElement( "WMSServiceCapabilities" ); + if ( serviceCapabilityElem.isNull() || serviceCapabilityElem.text().compare( "true", Qt::CaseInsensitive ) != 0 ) + { + QgsConfigParser::serviceCapabilities( parentElement, doc ); + return; + } + + //Service name is always WCS + QDomElement wcsNameElem = doc.createElement( "name" ); + QDomText wcsNameText = doc.createTextNode( "WCS" ); + wcsNameElem.appendChild( wcsNameText ); + serviceElem.appendChild( wcsNameElem ); + + //WMS title + QDomElement titleElem = propertiesElem.firstChildElement( "WMSServiceTitle" ); + if ( !titleElem.isNull() ) + { + QDomElement wcsLabelElem = doc.createElement( "label" ); + QDomText wcsLabelText = doc.createTextNode( titleElem.text() ); + wcsLabelElem.appendChild( wcsLabelText ); + serviceElem.appendChild( wcsLabelElem ); + } + + //WMS abstract + QDomElement abstractElem = propertiesElem.firstChildElement( "WMSServiceAbstract" ); + if ( !abstractElem.isNull() ) + { + QDomElement wcsDescriptionElem = doc.createElement( "description" ); + QDomText wcsDescriptionText = doc.createTextNode( abstractElem.text() ); + wcsDescriptionElem.appendChild( wcsDescriptionText ); + serviceElem.appendChild( wcsDescriptionElem ); + } + + //keyword list + QDomElement keywordListElem = propertiesElem.firstChildElement( "WMSKeywordList" ); + if ( !keywordListElem.isNull() && !keywordListElem.text().isEmpty() ) + { + QDomNodeList keywordList = keywordListElem.elementsByTagName( "value" ); + if ( keywordList.size() > 0 ) + { + QDomElement wcsKeywordsElem = doc.createElement( "keywords" ); + for ( int i = 0; i < keywordList.size(); ++i ) + { + QDomElement wcsKeywordElem = doc.createElement( "keyword" ); + QDomText keywordText = doc.createTextNode( keywordList.at( i ).toElement().text() ); + wcsKeywordElem.appendChild( keywordText ); + wcsKeywordsElem.appendChild( wcsKeywordElem ); + } + serviceElem.appendChild( wcsKeywordsElem ); + } + } + + //Fees + QDomElement feesElem = propertiesElem.firstChildElement( "WMSFees" ); + if ( !feesElem.isNull() ) + { + QDomElement wcsFeesElem = doc.createElement( "fees" ); + QDomText wcsFeesText = doc.createTextNode( feesElem.text() ); + wcsFeesElem.appendChild( wcsFeesText ); + serviceElem.appendChild( wcsFeesElem ); + } + + //AccessConstraints + QDomElement accessConstraintsElem = propertiesElem.firstChildElement( "WMSAccessConstraints" ); + if ( !accessConstraintsElem.isNull() ) + { + QDomElement wcsAccessConstraintsElem = doc.createElement( "accessConstraints" ); + QDomText wcsAccessConstraintsText = doc.createTextNode( accessConstraintsElem.text() ); + wcsAccessConstraintsElem.appendChild( wcsAccessConstraintsText ); + serviceElem.appendChild( wcsAccessConstraintsElem ); + } + + parentElement.appendChild( serviceElem ); +} + QString QgsProjectParser::serviceUrl() const { QString url; diff --git a/src/mapserver/qgsprojectparser.h b/src/mapserver/qgsprojectparser.h index 13c088e68881..6ca5440bdf97 100644 --- a/src/mapserver/qgsprojectparser.h +++ b/src/mapserver/qgsprojectparser.h @@ -45,12 +45,19 @@ class QgsProjectParser: public QgsConfigParser virtual void featureTypeList( QDomElement& parentElement, QDomDocument& doc ) const; + virtual void wcsContentMetadata( QDomElement& parentElement, QDomDocument& doc ) const; + virtual void owsGeneralAndResourceList( QDomElement& parentElement, QDomDocument& doc, const QString& strHref ) const; virtual void describeFeatureType( const QString& aTypeName, QDomElement& parentElement, QDomDocument& doc ) const; + + virtual void describeCoverage( const QString& aCoveName, QDomElement& parentElement, QDomDocument& doc ) const; /**Returns one or possibly several maplayers for a given type name. If no layers/style are found, an empty list is returned*/ virtual QList mapLayerFromTypeName( const QString& tName, bool useCache = true ) const; + /**Returns one or possibly several maplayers for a given type name. If no layers/style are found, an empty list is returned*/ + virtual QList mapLayerFromCoverage( const QString& cName, bool useCache = true ) const; + int numberOfLayers() const; /**Returns one or possibly several maplayers for a given layer name and style. If no layers/style are found, an empty list is returned*/ @@ -274,6 +281,10 @@ class QgsProjectParser: public QgsConfigParser * This is for WFS Services **/ void serviceWFSCapabilities( QDomElement& parentElement, QDomDocument& doc ) const; + /**Reads service metadata from projectfile or falls back to parent class method if not there + * This is for WCS Services + **/ + void serviceWCSCapabilities( QDomElement& parentElement, QDomDocument& doc ) const; }; #endif // QGSPROJECTPARSER_H diff --git a/src/mapserver/qgsrequesthandler.h b/src/mapserver/qgsrequesthandler.h index 144daf01729f..3fb4f38add47 100644 --- a/src/mapserver/qgsrequesthandler.h +++ b/src/mapserver/qgsrequesthandler.h @@ -44,6 +44,7 @@ class QgsRequestHandler virtual bool startGetFeatureResponse( QByteArray* ba, const QString& infoFormat ) const = 0; virtual void sendGetFeatureResponse( QByteArray* ba ) const = 0; virtual void endGetFeatureResponse( QByteArray* ba ) const = 0; + virtual void sendGetCoverageResponse( QByteArray* ba ) const = 0; QString format() const { return mFormat; } protected: /**This is set by the parseInput methods of the subclasses (parameter FORMAT, e.g. 'FORMAT=PNG')*/ diff --git a/src/mapserver/qgssldparser.h b/src/mapserver/qgssldparser.h index fc75502a2eab..fee99aa6316b 100644 --- a/src/mapserver/qgssldparser.h +++ b/src/mapserver/qgssldparser.h @@ -50,12 +50,19 @@ class QgsSLDParser: public QgsConfigParser void featureTypeList( QDomElement&, QDomDocument& ) const {} + void wcsContentMetadata( QDomElement&, QDomDocument& ) const {} + void owsGeneralAndResourceList( QDomElement&, QDomDocument& , const QString& ) const {} void describeFeatureType( const QString& , QDomElement& , QDomDocument& ) const {} + + void describeCoverage( const QString& , QDomElement& , QDomDocument& ) const {} /**Returns one or possibly several maplayers for a given type name. If no layers/style are found, an empty list is returned*/ QList mapLayerFromTypeName( const QString&, bool ) const { QList layerList; return layerList; } + /**Returns one or possibly several maplayers for a given type name. If no layers/style are found, an empty list is returned*/ + QList mapLayerFromCoverage( const QString&, bool ) const { QList layerList; return layerList; } + /**Returns number of layers in configuration*/ int numberOfLayers() const; diff --git a/src/mapserver/qgswcsserver.cpp b/src/mapserver/qgswcsserver.cpp new file mode 100644 index 000000000000..976105c6598b --- /dev/null +++ b/src/mapserver/qgswcsserver.cpp @@ -0,0 +1,365 @@ +/*************************************************************************** + qgswcsserver.cpp + ------------------- + begin : December 9, 2013 + copyright : (C) 2013 by René-Luc D'Hont + email : rldhont at 3liz 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 "qgswcsserver.h" +#include "qgsconfigparser.h" +#include "qgscrscache.h" +#include "qgsrasterlayer.h" +#include "qgsrasterpipe.h" +#include "qgsrasterprojector.h" +#include "qgsrasterfilewriter.h" +#include "qgslogger.h" +#include "qgsmapserviceexception.h" + +#include + +#ifndef Q_WS_WIN +#include +#else +#include +#endif + +static const QString WCS_NAMESPACE = "http://www.opengis.net/wcs"; +static const QString GML_NAMESPACE = "http://www.opengis.net/gml"; +static const QString OGC_NAMESPACE = "http://www.opengis.net/ogc"; + +QgsWCSServer::QgsWCSServer( QMap parameters ) + : mParameterMap( parameters ) + , mConfigParser( 0 ) +{ +} + +QgsWCSServer::~QgsWCSServer() +{ +} + +QgsWCSServer::QgsWCSServer() +{ +} + +QDomDocument QgsWCSServer::getCapabilities() +{ + QgsDebugMsg( "Entering." ); + QDomDocument doc; + //wcs:WCS_Capabilities element + QDomElement wcsCapabilitiesElement = doc.createElement( "WCS_Capabilities"/*wcs:WCS_Capabilities*/ ); + wcsCapabilitiesElement.setAttribute( "xmlns", WCS_NAMESPACE ); + wcsCapabilitiesElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); + wcsCapabilitiesElement.setAttribute( "xsi:schemaLocation", WCS_NAMESPACE + " http://schemas.opengis.net/wcs/1.0.0/wcsCapabilities.xsd" ); + wcsCapabilitiesElement.setAttribute( "xmlns:gml", GML_NAMESPACE ); + wcsCapabilitiesElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + wcsCapabilitiesElement.setAttribute( "version", "1.0.0" ); + wcsCapabilitiesElement.setAttribute( "updateSequence", "0" ); + doc.appendChild( wcsCapabilitiesElement ); + + if ( mConfigParser ) + { + mConfigParser->serviceCapabilities( wcsCapabilitiesElement, doc ); + } + + //INSERT Service + + //wcs:Capability element + QDomElement capabilityElement = doc.createElement( "Capability"/*wcs:Capability*/ ); + wcsCapabilitiesElement.appendChild( capabilityElement ); + + //wcs:Request element + QDomElement requestElement = doc.createElement( "Request"/*wcs:Request*/ ); + capabilityElement.appendChild( requestElement ); + + //wcs:GetCapabilities + QDomElement getCapabilitiesElement = doc.createElement( "GetCapabilities"/*wcs:GetCapabilities*/ ); + requestElement.appendChild( getCapabilitiesElement ); + + QDomElement dcpTypeElement = doc.createElement( "DCPType"/*wcs:DCPType*/ ); + getCapabilitiesElement.appendChild( dcpTypeElement ); + QDomElement httpElement = doc.createElement( "HTTP"/*wcs:HTTP*/ ); + dcpTypeElement.appendChild( httpElement ); + + //Prepare url + QString hrefString = mConfigParser->wfsServiceUrl(); + if ( hrefString.isEmpty() ) + { + hrefString = mConfigParser->serviceUrl(); + } + if ( hrefString.isEmpty() ) + { + hrefString = serviceUrl(); + } + + QDomElement getElement = doc.createElement( "Get"/*wcs:Get*/ ); + httpElement.appendChild( getElement ); + QDomElement onlineResourceElement = doc.createElement( "OnlineResource"/*wcs:OnlineResource*/ ); + onlineResourceElement.setAttribute( "xlink:type", "simple" ); + onlineResourceElement.setAttribute( "xlink:href", hrefString ); + getElement.appendChild( onlineResourceElement ); + + QDomElement getCapabilitiesDhcTypePostElement = dcpTypeElement.cloneNode().toElement();//this is the same as for 'GetCapabilities' + getCapabilitiesDhcTypePostElement.firstChild().firstChild().toElement().setTagName( "Post" ); + getCapabilitiesElement.appendChild( getCapabilitiesDhcTypePostElement ); + + QDomElement describeCoverageElement = getCapabilitiesElement.cloneNode().toElement();//this is the same as 'GetCapabilities' + describeCoverageElement.setTagName( "DescribeCoverage" ); + requestElement.appendChild( describeCoverageElement ); + + QDomElement getCoverageElement = getCapabilitiesElement.cloneNode().toElement();//this is the same as 'GetCapabilities' + getCoverageElement.setTagName( "GetCoverage" ); + requestElement.appendChild( getCoverageElement ); + + /* + * Adding layer list in ContentMetadata + */ + QDomElement contentMetadataElement = doc.createElement( "ContentMetadata"/*wcs:ContentMetadata*/ ); + wcsCapabilitiesElement.appendChild( contentMetadataElement ); + /* + * Adding layer liste in contentMetadataElement + */ + if ( mConfigParser ) + { + mConfigParser->wcsContentMetadata( contentMetadataElement, doc ); + } + + return doc; +} + +QDomDocument QgsWCSServer::describeCoverage() +{ + QgsDebugMsg( "Entering." ); + QDomDocument doc; + //wcs:WCS_Capabilities element + QDomElement coveDescElement = doc.createElement( "CoverageDescription"/*wcs:CoverageDescription*/ ); + coveDescElement.setAttribute( "xmlns", WCS_NAMESPACE ); + coveDescElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); + coveDescElement.setAttribute( "xsi:schemaLocation", WCS_NAMESPACE + " http://schemas.opengis.net/wcs/1.0.0/describeCoverage.xsd" ); + coveDescElement.setAttribute( "xmlns:gml", GML_NAMESPACE ); + coveDescElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + coveDescElement.setAttribute( "version", "1.0.0" ); + coveDescElement.setAttribute( "updateSequence", "0" ); + doc.appendChild( coveDescElement ); + + //defining coverage name + QString coveName = ""; + //read COVERAGE + QMap::const_iterator cove_name_it = mParameterMap.find( "COVERAGE" ); + if ( cove_name_it != mParameterMap.end() ) + { + coveName = cove_name_it.value(); + } + if ( coveName == "" ) { + QMap::const_iterator cove_name_it = mParameterMap.find( "IDENTIFIER" ); + if ( cove_name_it != mParameterMap.end() ) + { + coveName = cove_name_it.value(); + } + } + mConfigParser->describeCoverage( coveName, coveDescElement, doc ); + return doc; +} + +QByteArray* QgsWCSServer::getCoverage() +{ + QList layerList; + + QStringList mErrors = QStringList(); + + //defining coverage name + QString coveName = ""; + //read COVERAGE + QMap::const_iterator cove_name_it = mParameterMap.find( "COVERAGE" ); + if ( cove_name_it != mParameterMap.end() ) + { + coveName = cove_name_it.value(); + } + if ( coveName == "" ) { + QMap::const_iterator cove_name_it = mParameterMap.find( "IDENTIFIER" ); + if ( cove_name_it != mParameterMap.end() ) + { + coveName = cove_name_it.value(); + } + } + + if ( coveName == "" ) { + mErrors << QString( "COVERAGE is mandatory" ); + } + + layerList = mConfigParser->mapLayerFromCoverage( coveName ); + if ( layerList.size() < 1 ) + { + mErrors << QString( "The layer for the COVERAGE '%1' is not found" ).arg( coveName ); + } + + bool conversionSuccess; + // BBOX + bool bboxOk = false; + double minx = 0.0, miny = 0.0, maxx = 0.0, maxy = 0.0; + // WIDTh and HEIGHT + int width = 0, height = 0; + // CRS + QString crs = ""; + + // read BBOX + QMap::const_iterator bbIt = mParameterMap.find( "BBOX" ); + if ( bbIt == mParameterMap.end() ) + { + minx = 0; miny = 0; maxx = 0; maxy = 0; + } + else + { + bboxOk = true; + QString bbString = bbIt.value(); + minx = bbString.section( ",", 0, 0 ).toDouble( &conversionSuccess ); + if ( !conversionSuccess ) {bboxOk = false;} + miny = bbString.section( ",", 1, 1 ).toDouble( &conversionSuccess ); + if ( !conversionSuccess ) {bboxOk = false;} + maxx = bbString.section( ",", 2, 2 ).toDouble( &conversionSuccess ); + if ( !conversionSuccess ) {bboxOk = false;} + maxy = bbString.section( ",", 3, 3 ).toDouble( &conversionSuccess ); + if ( !conversionSuccess ) {bboxOk = false;} + } + if ( !bboxOk ) + { + mErrors << QString( "The BBOX is mandatory and has to be xx.xxx,yy.yyy,xx.xxx,yy.yyy" ); + } + + // read WIDTH + width = mParameterMap.value( "WIDTH", "0" ).toInt( &conversionSuccess ); + if ( !conversionSuccess ) + width = 0; + // read HEIGHT + height = mParameterMap.value( "HEIGHT", "0" ).toInt( &conversionSuccess ); + if ( !conversionSuccess ) + { + height = 0; + } + + if ( width < 0 || height < 0 ) + { + mErrors << QString( "The WIDTH and HEIGHT are mandatory and have to be integer" ); + } + + crs = mParameterMap.value( "CRS", "" ); + if ( crs == "" ) + { + mErrors << QString( "The CRS is mandatory" ); + } + QgsCoordinateReferenceSystem outputCRS = QgsCRSCache::instance()->crsByAuthId( crs ); + if ( !outputCRS.isValid() ) + { + mErrors << QString( "Could not create output CRS" ); + } + + if ( mErrors.count() != 0 ) + { + throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) ); + } + + QgsRectangle rect( minx, miny, maxx, maxy ); + + QgsMapLayer* layer = layerList.at( 0 ); + QgsRasterLayer* rLayer = dynamic_cast( layer ); + if ( rLayer ) { + QTemporaryFile tempFile; + tempFile.open(); + QgsRasterFileWriter fileWriter( tempFile.fileName() ); + + // clone pipe/provider + QgsRasterPipe* pipe = new QgsRasterPipe(); + if ( !pipe->set( rLayer->dataProvider()->clone() ) ) + { + mErrors << QString( "Cannot set pipe provider" ); + throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) ); + } + + // add projector if necessary + if ( outputCRS != rLayer->crs() ) + { + QgsRasterProjector * projector = new QgsRasterProjector; + projector->setCRS( rLayer->crs(), outputCRS ); + if ( !pipe->insert( 2, projector ) ) + { + mErrors << QString( "Cannot set pipe projector" ); + throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) ); + } + } + + QgsRasterFileWriter::WriterError err = fileWriter.writeRaster( pipe, width, height, rect, outputCRS ); + if ( err != QgsRasterFileWriter::NoError ) + { + mErrors << QString( "Cannot write raster error code: %1" ).arg( err ); + throw QgsMapServiceException( "RequestNotWellFormed", mErrors.join( ". " ) ); + } + delete pipe; + QByteArray* ba = 0; + ba = new QByteArray(); + *ba = tempFile.readAll(); + + return ba; + } + return 0; +} + +QString QgsWCSServer::serviceUrl() const +{ + QUrl mapUrl( getenv( "REQUEST_URI" ) ); + mapUrl.setHost( getenv( "SERVER_NAME" ) ); + + //Add non-default ports to url + QString portString = getenv( "SERVER_PORT" ); + if ( !portString.isEmpty() ) + { + bool portOk; + int portNumber = portString.toInt( &portOk ); + if ( portOk ) + { + if ( portNumber != 80 ) + { + mapUrl.setPort( portNumber ); + } + } + } + + if ( QString( getenv( "HTTPS" ) ).compare( "on", Qt::CaseInsensitive ) == 0 ) + { + mapUrl.setScheme( "https" ); + } + else + { + mapUrl.setScheme( "http" ); + } + + QList > queryItems = mapUrl.queryItems(); + QList >::const_iterator queryIt = queryItems.constBegin(); + for ( ; queryIt != queryItems.constEnd(); ++queryIt ) + { + if ( queryIt->first.compare( "REQUEST", Qt::CaseInsensitive ) == 0 ) + { + mapUrl.removeQueryItem( queryIt->first ); + } + else if ( queryIt->first.compare( "VERSION", Qt::CaseInsensitive ) == 0 ) + { + mapUrl.removeQueryItem( queryIt->first ); + } + else if ( queryIt->first.compare( "SERVICE", Qt::CaseInsensitive ) == 0 ) + { + mapUrl.removeQueryItem( queryIt->first ); + } + else if ( queryIt->first.compare( "_DC", Qt::CaseInsensitive ) == 0 ) + { + mapUrl.removeQueryItem( queryIt->first ); + } + } + return mapUrl.toString(); +} diff --git a/src/mapserver/qgswcsserver.h b/src/mapserver/qgswcsserver.h new file mode 100644 index 000000000000..8275ab242105 --- /dev/null +++ b/src/mapserver/qgswcsserver.h @@ -0,0 +1,64 @@ +/*************************************************************************** + qgswcsserver.h + ------------------- + begin : December 9, 2013 + copyright : (C) 2013 by René-Luc D'Hont + email : rldhont at 3liz 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. * + * * + ***************************************************************************/ + +#ifndef QGSWCSSERVER_H +#define QGSWCSSERVER_H + +#include +#include +#include +#include +#include "qgis.h" + +class QgsConfigParser; +class QgsRequestHandler; + +/**This class handles all the wcs server requests. The parameters and values have to be passed in the form of +a map. This map is usually generated by a subclass of QgsWMSRequestHandler, which makes QgsWCSServer +independent from any server side technology*/ + +class QgsWCSServer +{ + public: + /**Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/ + QgsWCSServer( QMap parameters ); + ~QgsWCSServer(); + /**Returns an XML file with the capabilities description (as described in the WFS specs)*/ + QDomDocument getCapabilities(); + + /**Returns an XML file with the describe Coverage (as described in the WCS specs)*/ + QDomDocument describeCoverage(); + + /**Creates a file which is the result of the getCoverage request.*/ + QByteArray* getCoverage(); + + /**Sets configuration parser for administration settings. Does not take ownership*/ + void setAdminConfigParser( QgsConfigParser* parser ) { mConfigParser = parser; } + + private: + /**Don't use the default constructor*/ + QgsWCSServer(); + + /**Get service address from REQUEST_URI if not specified in the configuration*/ + QString serviceUrl() const; + + /**Map containing the WMS parameters*/ + QMap mParameterMap; + QgsConfigParser* mConfigParser; +}; + +#endif