1 /***************************************************************************
2                               qgswcsutils.cpp
3                               -------------------------
4   begin                : December 9, 2013
5   copyright            : (C) 2013 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 "qgswcsutils.h"
19 #include "qgsconfigcache.h"
20 #include "qgsserverprojectutils.h"
21 #include "qgscoordinatetransform.h"
22 #include "qgsproject.h"
23 #include "qgsexception.h"
24 #include "qgsrasterlayer.h"
25 #include "qgsmapserviceexception.h"
26 #include "qgscoordinatereferencesystem.h"
27 
28 namespace QgsWcs
29 {
implementationVersion()30   QString implementationVersion()
31   {
32     return QStringLiteral( "1.0.0" );
33   }
34 
getCoverageOffering(QDomDocument & doc,const QgsRasterLayer * layer,const QgsProject * project,bool brief)35   QDomElement getCoverageOffering( QDomDocument &doc, const QgsRasterLayer *layer, const QgsProject *project, bool brief )
36   {
37     QDomElement layerElem;
38     if ( brief )
39       layerElem = doc.createElement( QStringLiteral( "CoverageOfferingBrief" ) );
40     else
41       layerElem = doc.createElement( QStringLiteral( "CoverageOffering" ) );
42 
43     // create name
44     QDomElement nameElem = doc.createElement( QStringLiteral( "name" ) );
45     QString name = layer->name();
46     if ( !layer->shortName().isEmpty() )
47       name = layer->shortName();
48     name = name.replace( ' ', '_' );
49     const QDomText nameText = doc.createTextNode( name );
50     nameElem.appendChild( nameText );
51     layerElem.appendChild( nameElem );
52 
53     // create label
54     QDomElement labelElem = doc.createElement( QStringLiteral( "label" ) );
55     QString title = layer->title();
56     if ( title.isEmpty() )
57     {
58       title = layer->name();
59     }
60     const QDomText labelText = doc.createTextNode( title );
61     labelElem.appendChild( labelText );
62     layerElem.appendChild( labelElem );
63 
64     //create description
65     const QString abstract = layer->abstract();
66     if ( !abstract.isEmpty() )
67     {
68       QDomElement descriptionElem = doc.createElement( QStringLiteral( "description" ) );
69       const QDomText descriptionText = doc.createTextNode( abstract );
70       descriptionElem.appendChild( descriptionText );
71       layerElem.appendChild( descriptionElem );
72     }
73 
74     //lonLatEnvelope
75     const QgsCoordinateReferenceSystem layerCrs = layer->crs();
76     const QgsCoordinateReferenceSystem wgs84 = QgsCoordinateReferenceSystem::fromOgcWmsCrs( geoEpsgCrsAuthId() );
77     const int wgs84precision = 6;
78     const QgsCoordinateTransform t( layerCrs, wgs84, project );
79     //transform
80     QgsRectangle BBox;
81     try
82     {
83       BBox = t.transformBoundingBox( layer->extent() );
84     }
85     catch ( QgsCsException &e )
86     {
87       QgsDebugMsg( QStringLiteral( "Transform error caught: %1. Using original layer extent." ).arg( e.what() ) );
88       BBox = layer->extent();
89     }
90     QDomElement lonLatElem = doc.createElement( QStringLiteral( "lonLatEnvelope" ) );
91     lonLatElem.setAttribute( QStringLiteral( "srsName" ), QStringLiteral( "urn:ogc:def:crs:OGC:1.3:CRS84" ) );
92     QDomElement lowerPosElem = doc.createElement( QStringLiteral( "gml:pos" ) );
93     const QDomText lowerPosText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( BBox.xMinimum(), wgs84precision ), wgs84precision ) + " " + qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( BBox.yMinimum(), wgs84precision ), wgs84precision ) );
94     lowerPosElem.appendChild( lowerPosText );
95     lonLatElem.appendChild( lowerPosElem );
96     QDomElement upperPosElem = doc.createElement( QStringLiteral( "gml:pos" ) );
97     const QDomText upperPosText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( BBox.xMaximum(), wgs84precision ), wgs84precision ) + " " +  qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( BBox.yMaximum(), wgs84precision ), wgs84precision ) );
98     upperPosElem.appendChild( upperPosText );
99     lonLatElem.appendChild( upperPosElem );
100     layerElem.appendChild( lonLatElem );
101 
102     if ( brief )
103       return layerElem;
104 
105     //Defines the spatial-temporal domain set of a coverage offering. The domainSet shall include a SpatialDomain
106     // (describing the spatial locations for which coverages can be requested), a TemporalDomain (describing the
107     // time instants or inter-vals for which coverages can be requested), or both.
108     QDomElement domainSetElem = doc.createElement( QStringLiteral( "domainSet" ) );
109     layerElem.appendChild( domainSetElem );
110 
111     QDomElement spatialDomainElem = doc.createElement( QStringLiteral( "spatialDomain" ) );
112     domainSetElem.appendChild( spatialDomainElem );
113 
114     // Define precision
115     int precision = 3;
116     if ( layer->crs().isGeographic() )
117     {
118       precision = 6;
119     }
120     //create Envelope
121     const QgsRectangle layerBBox = layer->extent();
122     QDomElement envelopeElem = doc.createElement( QStringLiteral( "gml:Envelope" ) );
123     envelopeElem.setAttribute( QStringLiteral( "srsName" ), layerCrs.authid() );
124     QDomElement lowerCornerElem = doc.createElement( QStringLiteral( "gml:pos" ) );
125     const QDomText lowerCornerText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( layerBBox.xMinimum(), precision ), wgs84precision ) + " " + qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( layerBBox.yMinimum(), wgs84precision ), precision ) );
126     lowerCornerElem.appendChild( lowerCornerText );
127     envelopeElem.appendChild( lowerCornerElem );
128     QDomElement upperCornerElem = doc.createElement( QStringLiteral( "gml:pos" ) );
129     const QDomText upperCornerText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( layerBBox.xMaximum(), precision ), wgs84precision ) + " " + qgsDoubleToString( QgsServerProjectUtils::ceilWithPrecision( layerBBox.yMaximum(), wgs84precision ), precision ) );
130     upperCornerElem.appendChild( upperCornerText );
131     envelopeElem.appendChild( upperCornerElem );
132     spatialDomainElem.appendChild( envelopeElem );
133 
134     QDomElement rectGridElem = doc.createElement( QStringLiteral( "gml:RectifiedGrid" ) );
135     rectGridElem.setAttribute( QStringLiteral( "dimension" ), 2 );
136     QDomElement limitsElem = doc.createElement( QStringLiteral( "gml:limits" ) );
137     rectGridElem.appendChild( limitsElem );
138     QDomElement gridEnvElem = doc.createElement( QStringLiteral( "gml:GridEnvelope" ) );
139     limitsElem.appendChild( gridEnvElem );
140     QDomElement lowElem = doc.createElement( QStringLiteral( "gml:low" ) );
141     const QDomText lowText = doc.createTextNode( QStringLiteral( "0 0" ) );
142     lowElem.appendChild( lowText );
143     gridEnvElem.appendChild( lowElem );
144     QDomElement highElem = doc.createElement( QStringLiteral( "gml:high" ) );
145     const QDomText highText = doc.createTextNode( QString::number( layer->width() ) + " " + QString::number( layer->height() ) );
146     highElem.appendChild( highText );
147     gridEnvElem.appendChild( highElem );
148     spatialDomainElem.appendChild( rectGridElem );
149 
150     QDomElement xAxisElem = doc.createElement( QStringLiteral( "gml:axisName" ) );
151     const QDomText xAxisText = doc.createTextNode( QStringLiteral( "x" ) );
152     xAxisElem.appendChild( xAxisText );
153     rectGridElem.appendChild( xAxisElem );
154 
155     QDomElement yAxisElem = doc.createElement( QStringLiteral( "gml:axisName" ) );
156     const QDomText yAxisText = doc.createTextNode( QStringLiteral( "y" ) );
157     yAxisElem.appendChild( yAxisText );
158     rectGridElem.appendChild( yAxisElem );
159 
160     QDomElement originElem = doc.createElement( QStringLiteral( "gml:origin" ) );
161     QDomElement originPosElem = doc.createElement( QStringLiteral( "gml:pos" ) );
162     originElem.appendChild( originPosElem );
163     const QDomText originPosText = doc.createTextNode( qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( layerBBox.xMinimum(), precision ), precision ) + " " + qgsDoubleToString( QgsServerProjectUtils::floorWithPrecision( layerBBox.yMinimum(), precision ), precision ) );
164     originPosElem.appendChild( originPosText );
165     rectGridElem.appendChild( originElem );
166 
167     QDomElement xOffsetElem = doc.createElement( QStringLiteral( "gml:offsetVector" ) );
168     const QDomText xOffsetText = doc.createTextNode( QString::number( layer->rasterUnitsPerPixelX() ) + " 0" );
169     xOffsetElem.appendChild( xOffsetText );
170     rectGridElem.appendChild( xOffsetElem );
171 
172     QDomElement yOffsetElem = doc.createElement( QStringLiteral( "gml:offsetVector" ) );
173     const QDomText yOffsetText = doc.createTextNode( "0 " + QString::number( layer->rasterUnitsPerPixelY() ) );
174     yOffsetElem.appendChild( yOffsetText );
175     rectGridElem.appendChild( yOffsetElem );
176 
177     //GML property containing one RangeSet GML object.
178     QDomElement rangeSetElem = doc.createElement( QStringLiteral( "rangeSet" ) );
179     layerElem.appendChild( rangeSetElem );
180 
181     //Defines the properties (categories, measures, or values) assigned to each location in the domain. Any such
182     // property may be a scalar (numeric or text) value, such as population density, or a compound (vector or tensor)
183     // value, such as incomes by race, or radiances by wavelength. The semantic of the range set is typically an
184     // observable and is referenced by a URI. A rangeSet also has a reference system that is referred by the URI in
185     // the refSys attribute. The refSys is either qualitative (classification) or quantitative (uom). The three attributes
186     // can be included either here and in each axisDescription. If included in both places, the values in the axisDescription
187     // over-ride those included in the RangeSet.
188     QDomElement RangeSetElem = doc.createElement( QStringLiteral( "RangeSet" ) );
189     rangeSetElem.appendChild( RangeSetElem );
190 
191     QDomElement rsNameElem = doc.createElement( QStringLiteral( "name" ) );
192     const QDomText rsNameText = doc.createTextNode( QStringLiteral( "Bands" ) );
193     rsNameElem.appendChild( rsNameText );
194     RangeSetElem.appendChild( rsNameElem );
195 
196     QDomElement rsLabelElem = doc.createElement( QStringLiteral( "label" ) );
197     const QDomText rsLabelText = doc.createTextNode( QStringLiteral( "Bands" ) );
198     rsLabelElem.appendChild( rsLabelText );
199     RangeSetElem.appendChild( rsLabelElem );
200 
201     QDomElement axisDescElem = doc.createElement( QStringLiteral( "axisDescription" ) );
202     RangeSetElem.appendChild( axisDescElem );
203 
204     QDomElement AxisDescElem = doc.createElement( QStringLiteral( "AxisDescription" ) );
205     axisDescElem.appendChild( AxisDescElem );
206 
207     QDomElement adNameElem = doc.createElement( QStringLiteral( "name" ) );
208     const QDomText adNameText = doc.createTextNode( QStringLiteral( "bands" ) );
209     adNameElem.appendChild( adNameText );
210     AxisDescElem.appendChild( adNameElem );
211 
212     QDomElement adLabelElem = doc.createElement( QStringLiteral( "label" ) );
213     const QDomText adLablelText = doc.createTextNode( QStringLiteral( "bands" ) );
214     adLabelElem.appendChild( adLablelText );
215     AxisDescElem.appendChild( adLabelElem );
216 
217     QDomElement adValuesElem = doc.createElement( QStringLiteral( "values" ) );
218     for ( int idx = 0; idx < layer->bandCount(); ++idx )
219     {
220       QDomElement adValueElem = doc.createElement( QStringLiteral( "singleValue" ) );
221       const QDomText adValueText = doc.createTextNode( QString::number( idx + 1 ) );
222       adValueElem.appendChild( adValueText );
223       adValuesElem.appendChild( adValueElem );
224     }
225     AxisDescElem.appendChild( adValuesElem );
226 
227     //The coordinate reference system(s) in which the server can accept requests against
228     // this coverage offering and produce coverages from it.
229     QDomElement sCRSElem = doc.createElement( QStringLiteral( "supportedCRSs" ) );
230     QDomElement rCRSElem = doc.createElement( QStringLiteral( "requestResponseCRSs" ) );
231     const QDomText rCRSText = doc.createTextNode( layerCrs.authid() );
232     rCRSElem.appendChild( rCRSText );
233     sCRSElem.appendChild( rCRSElem );
234     QDomElement nCRSElem = doc.createElement( QStringLiteral( "nativeCRSs" ) );
235     const QDomText nCRSText = doc.createTextNode( layerCrs.authid() );
236     nCRSElem.appendChild( nCRSText );
237     sCRSElem.appendChild( nCRSElem );
238     layerElem.appendChild( sCRSElem );
239 
240     //The formats (file encodings) in which the server can produce coverages from this
241     // coverage offering.
242     QDomElement sFormatsElem = doc.createElement( QStringLiteral( "supportedFormats" ) );
243     sFormatsElem.setAttribute( QStringLiteral( "nativeFormat" ), QStringLiteral( "raw binary" ) );
244     QDomElement formatsElem = doc.createElement( QStringLiteral( "formats" ) );
245     const QDomText formatsText = doc.createTextNode( QStringLiteral( "GeoTIFF" ) );
246     formatsElem.appendChild( formatsText );
247     sFormatsElem.appendChild( formatsElem );
248     layerElem.appendChild( sFormatsElem );
249 
250     return layerElem;
251   }
252 
253 
serviceUrl(const QgsServerRequest & request,const QgsProject * project,const QgsServerSettings & settings)254   QString serviceUrl( const QgsServerRequest &request, const QgsProject *project, const QgsServerSettings &settings )
255   {
256     static const QSet< QString > sFilter
257     {
258       QStringLiteral( "REQUEST" ),
259       QStringLiteral( "VERSION" ),
260       QStringLiteral( "SERVICE" ),
261       QStringLiteral( "_DC" )
262     };
263 
264     QString href = QgsServerProjectUtils::wcsServiceUrl( project ? *project : *QgsProject::instance(), request, settings );
265 
266     // Build default url
267     if ( href.isEmpty() )
268     {
269       QUrl url = request.originalUrl();
270       QUrlQuery q( url );
271 
272       const QList<QPair<QString, QString> > queryItems = q.queryItems();
273       for ( const QPair<QString, QString> &param : queryItems )
274       {
275         if ( sFilter.contains( param.first.toUpper() ) )
276           q.removeAllQueryItems( param.first );
277       }
278 
279       url.setQuery( q );
280       href = url.toString();
281 
282     }
283 
284     return  href;
285   }
286 
parseBbox(const QString & bboxStr)287   QgsRectangle parseBbox( const QString &bboxStr )
288   {
289     QStringList lst = bboxStr.split( ',' );
290     if ( lst.count() != 4 )
291       return QgsRectangle();
292 
293     double d[4];
294     bool ok;
295     for ( int i = 0; i < 4; i++ )
296     {
297       lst[i].replace( ' ', '+' );
298       d[i] = lst[i].toDouble( &ok );
299       if ( !ok )
300         return QgsRectangle();
301     }
302     return QgsRectangle( d[0], d[1], d[2], d[3] );
303   }
304 
305 } // namespace QgsWfs
306 
307 
308