From c27c89045c172ae1afc68a5571552e145d426f15 Mon Sep 17 00:00:00 2001 From: Marco Hugentobler Date: Tue, 20 Mar 2012 14:42:42 +0100 Subject: [PATCH] =?UTF-8?q?[FEATURE]:=20Add=20WFS=20support=20for=20QGIS?= =?UTF-8?q?=20server.=20Provided=20by=20Ren=C3=A9-Luc=20D'Hont?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/qgsprojectproperties.cpp | 48 +- src/core/qgsgeometry.cpp | 290 ++++++++ src/core/qgsgeometry.h | 5 + src/mapserver/CMakeLists.txt | 1 + src/mapserver/qgis_map_serv.cpp | 109 ++- src/mapserver/qgsconfigparser.h | 4 + src/mapserver/qgshttprequesthandler.cpp | 50 ++ src/mapserver/qgshttprequesthandler.h | 3 + src/mapserver/qgsprojectparser.cpp | 89 +++ src/mapserver/qgsprojectparser.h | 5 + src/mapserver/qgsrequesthandler.h | 3 + src/mapserver/qgssldparser.h | 2 + src/mapserver/qgswfsserver.cpp | 946 ++++++++++++++++++++++++ src/mapserver/qgswfsserver.h | 92 +++ src/ui/qgsprojectpropertiesbase.ui | 38 +- 15 files changed, 1676 insertions(+), 9 deletions(-) create mode 100644 src/mapserver/qgswfsserver.cpp create mode 100644 src/mapserver/qgswfsserver.h diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp index 8022c0524001..8fec88120f75 100644 --- a/src/app/qgsprojectproperties.cpp +++ b/src/app/qgsprojectproperties.cpp @@ -175,7 +175,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa twIdentifyLayers->setCellWidget( i, 2, cb ); } - grpWMSServiceCapabilities->setChecked( QgsProject::instance()->readBoolEntry( "WMSServiceCapabilities", "/", false ) ); + grpOWSServiceCapabilities->setChecked( QgsProject::instance()->readBoolEntry( "WMSServiceCapabilities", "/", false ) ); mWMSTitle->setText( QgsProject::instance()->readEntry( "WMSServiceTitle", "/" ) ); mWMSContactOrganization->setText( QgsProject::instance()->readEntry( "WMSContactOrganization", "/", "" ) ); mWMSContactPerson->setText( QgsProject::instance()->readEntry( "WMSContactPerson", "/", "" ) ); @@ -229,6 +229,38 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas* mapCanvas, QWidget *pa bool addWktGeometry = QgsProject::instance()->readBoolEntry( "WMSAddWktGeometry", "/" ); mAddWktGeometryCheckBox->setChecked( addWktGeometry ); + QStringList wfsLayerIdList = QgsProject::instance()->readListEntry( "WFSLayers", "/" ); + + twWFSLayers->setColumnCount( 2 ); + twWFSLayers->horizontalHeader()->setVisible( true ); + twWFSLayers->setRowCount( mapLayers.size() ); + + i = 0; + int j = 0; + for ( QMap::const_iterator it = mapLayers.constBegin(); it != mapLayers.constEnd(); it++, i++ ) + { + currentLayer = it.value(); + if ( currentLayer->type() == QgsMapLayer::VectorLayer ) + { + + QTableWidgetItem *twi = new QTableWidgetItem( QString::number( j ) ); + twWFSLayers->setVerticalHeaderItem( j, twi ); + + twi = new QTableWidgetItem( currentLayer->name() ); + twi->setData( Qt::UserRole, it.key() ); + twi->setFlags( twi->flags() & ~Qt::ItemIsEditable ); + twWFSLayers->setItem( j, 0, twi ); + + QCheckBox *cb = new QCheckBox(); + cb->setChecked( wfsLayerIdList.contains( currentLayer->id() ) ); + twWFSLayers->setCellWidget( j, 1, cb ); + j++; + + } + } + twWFSLayers->setRowCount( j ); + twWFSLayers->verticalHeader()->setResizeMode( QHeaderView::ResizeToContents ); + restoreState(); } @@ -380,7 +412,7 @@ void QgsProjectProperties::apply() QgsProject::instance()->writeEntry( "Identify", "/disabledLayers", noIdentifyLayerList ); - QgsProject::instance()->writeEntry( "WMSServiceCapabilities", "/", grpWMSServiceCapabilities->isChecked() ); + QgsProject::instance()->writeEntry( "WMSServiceCapabilities", "/", grpOWSServiceCapabilities->isChecked() ); QgsProject::instance()->writeEntry( "WMSServiceTitle", "/", mWMSTitle->text() ); QgsProject::instance()->writeEntry( "WMSContactOrganization", "/", mWMSContactOrganization->text() ); QgsProject::instance()->writeEntry( "WMSContactPerson", "/", mWMSContactPerson->text() ); @@ -428,6 +460,18 @@ void QgsProjectProperties::apply() QgsProject::instance()->writeEntry( "WMSAddWktGeometry", "/", mAddWktGeometryCheckBox->isChecked() ); + QStringList wfsLayerList; + for ( int i = 0; i < twWFSLayers->rowCount(); i++ ) + { + QCheckBox *cb = qobject_cast( twWFSLayers->cellWidget( i, 1 ) ); + if ( cb && cb->isChecked() ) + { + QString id = twWFSLayers->item( i, 0 )->data( Qt::UserRole ).toString(); + wfsLayerList << id; + } + } + QgsProject::instance()->writeEntry( "WFSLayers", "/", wfsLayerList ); + //todo XXX set canvas color emit refresh(); } diff --git a/src/core/qgsgeometry.cpp b/src/core/qgsgeometry.cpp index a83b5dc2c48d..9e92cb112cfe 100644 --- a/src/core/qgsgeometry.cpp +++ b/src/core/qgsgeometry.cpp @@ -4185,6 +4185,296 @@ QString QgsGeometry::exportToWkt() } } +QString QgsGeometry::exportToGeoJSON() +{ + QgsDebugMsg( "entered." ); + + // TODO: implement with GEOS + if ( mDirtyWkb ) + { + exportGeosToWkb(); + } + + if ( !mGeometry ) + { + QgsDebugMsg( "WKB geometry not available!" ); + return QString::null; + } + + QGis::WkbType wkbType; + bool hasZValue = false; + double *x, *y; + + QString mWkt; // TODO: rename + + // Will this really work when mGeometry[0] == 0 ???? I (gavin) think not. + //wkbType = (mGeometry[0] == 1) ? mGeometry[1] : mGeometry[4]; + memcpy( &wkbType, &( mGeometry[1] ), sizeof( int ) ); + + switch ( wkbType ) + { + case QGis::WKBPoint25D: + case QGis::WKBPoint: + { + mWkt += "{ \"type\": \"Point\", \"coordinates\": ["; + x = ( double * )( mGeometry + 5 ); + mWkt += QString::number( *x, 'f', 6 ); + mWkt += ", "; + y = ( double * )( mGeometry + 5 + sizeof( double ) ); + mWkt += QString::number( *y, 'f', 6 ); + mWkt += "] }"; + return mWkt; + } + + case QGis::WKBLineString25D: + hasZValue = true; + case QGis::WKBLineString: + { + QgsDebugMsg( "LINESTRING found" ); + unsigned char *ptr; + int *nPoints; + int idx; + + mWkt += "{ \"type\": \"LineString\", \"coordinates\": [ "; + // get number of points in the line + ptr = mGeometry + 5; + nPoints = ( int * ) ptr; + ptr = mGeometry + 1 + 2 * sizeof( int ); + for ( idx = 0; idx < *nPoints; ++idx ) + { + if ( idx != 0 ) + { + mWkt += ", "; + } + mWkt += "["; + x = ( double * ) ptr; + mWkt += QString::number( *x, 'f', 6 ); + mWkt += ", "; + ptr += sizeof( double ); + y = ( double * ) ptr; + mWkt += QString::number( *y, 'f', 6 ); + ptr += sizeof( double ); + if ( hasZValue ) + { + ptr += sizeof( double ); + } + mWkt += "]"; + } + mWkt += " ] }"; + return mWkt; + } + + case QGis::WKBPolygon25D: + hasZValue = true; + case QGis::WKBPolygon: + { + QgsDebugMsg( "POLYGON found" ); + unsigned char *ptr; + int idx, jdx; + int *numRings, *nPoints; + + mWkt += "{ \"type\": \"Polygon\", \"coordinates\": [ "; + // get number of rings in the polygon + numRings = ( int * )( mGeometry + 1 + sizeof( int ) ); + if ( !( *numRings ) ) // sanity check for zero rings in polygon + { + return QString(); + } + int *ringStart; // index of first point for each ring + int *ringNumPoints; // number of points in each ring + ringStart = new int[*numRings]; + ringNumPoints = new int[*numRings]; + ptr = mGeometry + 1 + 2 * sizeof( int ); // set pointer to the first ring + for ( idx = 0; idx < *numRings; idx++ ) + { + if ( idx != 0 ) + { + mWkt += ", "; + } + mWkt += "[ "; + // get number of points in the ring + nPoints = ( int * ) ptr; + ringNumPoints[idx] = *nPoints; + ptr += 4; + + for ( jdx = 0; jdx < *nPoints; jdx++ ) + { + if ( jdx != 0 ) + { + mWkt += ", "; + } + mWkt += "["; + x = ( double * ) ptr; + mWkt += QString::number( *x, 'f', 6 ); + mWkt += ", "; + ptr += sizeof( double ); + y = ( double * ) ptr; + mWkt += QString::number( *y, 'f', 6 ); + ptr += sizeof( double ); + if ( hasZValue ) + { + ptr += sizeof( double ); + } + mWkt += "]"; + } + mWkt += " ]"; + } + mWkt += " ] }"; + delete [] ringStart; + delete [] ringNumPoints; + return mWkt; + } + + case QGis::WKBMultiPoint25D: + hasZValue = true; + case QGis::WKBMultiPoint: + { + unsigned char *ptr; + int idx; + int *nPoints; + + mWkt += "{ \"type\": \"MultiPoint\", \"coordinates\": [ "; + nPoints = ( int* )( mGeometry + 5 ); + ptr = mGeometry + 5 + sizeof( int ); + for ( idx = 0; idx < *nPoints; ++idx ) + { + ptr += ( 1 + sizeof( int ) ); + if ( idx != 0 ) + { + mWkt += ", "; + } + mWkt += "["; + x = ( double * )( ptr ); + mWkt += QString::number( *x, 'f', 6 ); + mWkt += ", "; + ptr += sizeof( double ); + y = ( double * )( ptr ); + mWkt += QString::number( *y, 'f', 6 ); + ptr += sizeof( double ); + if ( hasZValue ) + { + ptr += sizeof( double ); + } + mWkt += "]"; + } + mWkt += " ] }"; + return mWkt; + } + + case QGis::WKBMultiLineString25D: + hasZValue = true; + case QGis::WKBMultiLineString: + { + QgsDebugMsg( "MULTILINESTRING found" ); + unsigned char *ptr; + int idx, jdx, numLineStrings; + int *nPoints; + + mWkt += "{ \"type\": \"MultiLineString\", \"coordinates\": [ "; + numLineStrings = ( int )( mGeometry[5] ); + ptr = mGeometry + 9; + for ( jdx = 0; jdx < numLineStrings; jdx++ ) + { + if ( jdx != 0 ) + { + mWkt += ", "; + } + mWkt += "[ "; + ptr += 5; // skip type since we know its 2 + nPoints = ( int * ) ptr; + ptr += sizeof( int ); + for ( idx = 0; idx < *nPoints; idx++ ) + { + if ( idx != 0 ) + { + mWkt += ", "; + } + mWkt += "["; + x = ( double * ) ptr; + mWkt += QString::number( *x, 'f', 6 ); + ptr += sizeof( double ); + mWkt += ", "; + y = ( double * ) ptr; + mWkt += QString::number( *y, 'f', 6 ); + ptr += sizeof( double ); + if ( hasZValue ) + { + ptr += sizeof( double ); + } + mWkt += "]"; + } + mWkt += " ]"; + } + mWkt += " ] }"; + return mWkt; + } + + case QGis::WKBMultiPolygon25D: + hasZValue = true; + case QGis::WKBMultiPolygon: + { + QgsDebugMsg( "MULTIPOLYGON found" ); + unsigned char *ptr; + int idx, jdx, kdx; + int *numPolygons, *numRings, *nPoints; + + mWkt += "{ \"type\": \"MultiPolygon\", \"coordinates\": [ "; + ptr = mGeometry + 5; + numPolygons = ( int * ) ptr; + ptr = mGeometry + 9; + for ( kdx = 0; kdx < *numPolygons; kdx++ ) + { + if ( kdx != 0 ) + { + mWkt += ", "; + } + mWkt += "[ "; + ptr += 5; + numRings = ( int * ) ptr; + ptr += 4; + for ( idx = 0; idx < *numRings; idx++ ) + { + if ( idx != 0 ) + { + mWkt += ", "; + } + mWkt += "[ "; + nPoints = ( int * ) ptr; + ptr += 4; + for ( jdx = 0; jdx < *nPoints; jdx++ ) + { + if ( jdx != 0 ) + { + mWkt += ", "; + } + mWkt += "["; + x = ( double * ) ptr; + mWkt += QString::number( *x, 'f', 6 ); + ptr += sizeof( double ); + mWkt += ", "; + y = ( double * ) ptr; + mWkt += QString::number( *y, 'f', 6 ); + ptr += sizeof( double ); + if ( hasZValue ) + { + ptr += sizeof( double ); + } + mWkt += "]"; + } + mWkt += " ]"; + } + mWkt += " ]"; + } + mWkt += " ] }"; + return mWkt; + } + + default: + QgsDebugMsg( "error: mGeometry type not recognized" ); + return QString::null; + } +} + bool QgsGeometry::exportWkbToGeos() { QgsDebugMsgLevel( "entered.", 3 ); diff --git a/src/core/qgsgeometry.h b/src/core/qgsgeometry.h index 4c7ee99a692f..6583c8963252 100644 --- a/src/core/qgsgeometry.h +++ b/src/core/qgsgeometry.h @@ -361,6 +361,11 @@ class CORE_EXPORT QgsGeometry */ QString exportToWkt(); + /** Exports the geometry to mGeoJSON + @return true in case of success and false else + */ + QString exportToGeoJSON(); + /* Accessor functions for getting geometry data */ /** return contents of the geometry as a point diff --git a/src/mapserver/CMakeLists.txt b/src/mapserver/CMakeLists.txt index a78a686938b7..c4e353bca34d 100644 --- a/src/mapserver/CMakeLists.txt +++ b/src/mapserver/CMakeLists.txt @@ -28,6 +28,7 @@ SET ( qgis_mapserv_SRCS qgssldparser.cpp qgssldrenderer.cpp qgswmsserver.cpp + qgswfsserver.cpp qgsmapserviceexception.cpp qgsmslayercache.cpp qgsfilter.cpp diff --git a/src/mapserver/qgis_map_serv.cpp b/src/mapserver/qgis_map_serv.cpp index 6be6e19d8191..abc0070c3fa8 100644 --- a/src/mapserver/qgis_map_serv.cpp +++ b/src/mapserver/qgis_map_serv.cpp @@ -26,6 +26,7 @@ map service syntax for SOAP/HTTP POST #include "qgsproviderregistry.h" #include "qgslogger.h" #include "qgswmsserver.h" +#include "qgswfsserver.h" #include "qgsmaprenderer.h" #include "qgsmapserviceexception.h" #include "qgsprojectparser.h" @@ -264,24 +265,124 @@ int main( int argc, char * argv[] ) //request to WMS? QString serviceString; -#ifndef QGISDEBUG - serviceString = parameterMap.value( "SERVICE", "WMS" ); -#else paramIt = parameterMap.find( "SERVICE" ); if ( paramIt == parameterMap.constEnd() ) { +#ifndef QGISDEBUG + serviceString = parameterMap.value( "SERVICE", "WMS" ); +#else QgsDebugMsg( "unable to find 'SERVICE' parameter, exiting..." ); theRequestHandler->sendServiceException( QgsMapServiceException( "ServiceNotSpecified", "Service not specified. The SERVICE parameter is mandatory" ) ); delete theRequestHandler; continue; +#endif } else { serviceString = paramIt.value(); } -#endif QgsWMSServer* theServer = 0; + if ( serviceString == "WFS" ) + { + delete theServer; + QgsWFSServer* theServer = 0; + try + { + theServer = new QgsWFSServer( 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 == "GetCapabilities" ) + { + 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 == "DescribeFeatureType" ) + { + QDomDocument describeDocument; + try + { + describeDocument = theServer->describeFeatureType(); + } + 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 == "GetFeature" ) + { + //output format for GetFeature + QString outputFormat = parameterMap.value( "OUTPUTFORMAT" ); + try + { + if ( theServer->getFeature( *theRequestHandler, outputFormat ) != 0 ) + { + delete theRequestHandler; + delete theServer; + continue; + } + else + { + delete theRequestHandler; + delete theServer; + continue; + } + } + catch ( QgsMapServiceException& ex ) + { + theRequestHandler->sendServiceException( ex ); + delete theRequestHandler; + delete theServer; + continue; + } + } + + return 0; + } + try { theServer = new QgsWMSServer( parameterMap, theMapRenderer ); diff --git a/src/mapserver/qgsconfigparser.h b/src/mapserver/qgsconfigparser.h index bea118d4889e..a165e8bf3e61 100644 --- a/src/mapserver/qgsconfigparser.h +++ b/src/mapserver/qgsconfigparser.h @@ -42,6 +42,8 @@ class QgsConfigParser /**Adds layer and style specific capabilities elements to the parent node. This includes the individual layers and styles, their description, native CRS, bounding boxes, etc.*/ virtual void layersAndStylesCapabilities( QDomElement& parentElement, QDomDocument& doc ) const = 0; + virtual void featureTypeList( QDomElement& parentElement, QDomDocument& doc ) 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*/ @@ -87,6 +89,8 @@ class QgsConfigParser /**Returns an ID-list of layers which are not queryable*/ virtual QStringList identifyDisabledLayers() const { return QStringList(); } + /**Returns an ID-list of layers which queryable in WFS service*/ + virtual QStringList wfsLayers() const { return QStringList(); } /**Returns a set of supported epsg codes for the capabilities document. An empty list means that all possible CRS should be advertised (which could result in very long capabilities documents)*/ diff --git a/src/mapserver/qgshttprequesthandler.cpp b/src/mapserver/qgshttprequesthandler.cpp index caa9fe9f7520..31180577b963 100644 --- a/src/mapserver/qgshttprequesthandler.cpp +++ b/src/mapserver/qgshttprequesthandler.cpp @@ -288,6 +288,56 @@ void QgsHttpRequestHandler::sendGetPrintResponse( QByteArray* ba ) const sendHttpResponse( ba, formatToMimeType( mFormat ) ); } +bool QgsHttpRequestHandler::startGetFeatureResponse( QByteArray* ba, const QString& infoFormat ) const +{ + if ( !ba ) + { + return false; + } + + if ( ba->size() < 1 ) + { + return false; + } + + QString format; + if ( infoFormat == "GeoJSON" ) + format = "text/plain"; + else + format = "text/xml"; + + printf( "Content-Type: " ); + printf( format.toLocal8Bit() ); + printf( "\n" ); + printf( "\n" ); + fwrite( ba->data(), ba->size(), 1, FCGI_stdout ); + return true; +} + +void QgsHttpRequestHandler::sendGetFeatureResponse( QByteArray* ba ) const +{ + if ( !ba ) + { + return; + } + + if ( ba->size() < 1 ) + { + return; + } + fwrite( ba->data(), ba->size(), 1, FCGI_stdout ); +} + +void QgsHttpRequestHandler::endGetFeatureResponse( QByteArray* ba ) const +{ + if ( !ba ) + { + return; + } + + fwrite( ba->data(), ba->size(), 1, FCGI_stdout ); +} + void QgsHttpRequestHandler::requestStringToParameterMap( const QString& request, QMap& parameters ) { parameters.clear(); diff --git a/src/mapserver/qgshttprequesthandler.h b/src/mapserver/qgshttprequesthandler.h index 435324304d4b..9d4bae44cd4e 100644 --- a/src/mapserver/qgshttprequesthandler.h +++ b/src/mapserver/qgshttprequesthandler.h @@ -34,6 +34,9 @@ class QgsHttpRequestHandler: public QgsRequestHandler virtual void sendServiceException( const QgsMapServiceException& ex ) const; virtual void sendGetStyleResponse( const QDomDocument& doc ) const; virtual void sendGetPrintResponse( QByteArray* ba ) const; + virtual bool startGetFeatureResponse( QByteArray* ba, const QString& infoFormat ) const; + virtual void sendGetFeatureResponse( QByteArray* ba ) const; + virtual void endGetFeatureResponse( 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 e1f1e2c8acde..e1b255f0c114 100644 --- a/src/mapserver/qgsprojectparser.cpp +++ b/src/mapserver/qgsprojectparser.cpp @@ -138,6 +138,64 @@ void QgsProjectParser::layersAndStylesCapabilities( QDomElement& parentElement, combineExtentAndCrsOfGroupChildren( layerParentElem, doc ); } +void QgsProjectParser::featureTypeList( QDomElement& parentElement, QDomDocument& doc ) const +{ + QStringList wfsLayersId = wfsLayers(); + + if ( mProjectLayerElements.size() < 1 ) + { + return; + } + + QMap layerMap; + + foreach( const QDomElement &elem, mProjectLayerElements ) + { + QString type = elem.attribute( "type" ); + if ( type == "vector" ) + { + //QgsMapLayer *layer = createLayerFromElement( *layerIt ); + QgsMapLayer *layer = createLayerFromElement( elem ); + if ( layer && wfsLayersId.contains( layer->id() ) ) + { + QgsDebugMsg( QString( "add layer %1 to map" ).arg( layer->id() ) ); + layerMap.insert( layer->id(), layer ); + + QDomElement layerElem = doc.createElement( "FeatureType" ); + 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 + QDomText nameText = doc.createTextNode( layer->name() ); + nameElem.appendChild( nameText ); + layerElem.appendChild( nameElem ); + + QDomElement titleElem = doc.createElement( "Title" ); + QDomText titleText = doc.createTextNode( layer->name() ); + titleElem.appendChild( titleText ); + layerElem.appendChild( titleElem ); + + //appendExGeographicBoundingBox( layerElem, doc, layer->extent(), layer->crs() ); + + QDomElement srsElem = doc.createElement( "SRS" ); + QDomText srsText = doc.createTextNode( layer->crs().authid() ); + srsElem.appendChild( srsText ); + layerElem.appendChild( srsElem ); + + QgsRectangle layerExtent = layer->extent(); + QDomElement bBoxElement = doc.createElement( "LatLongBoundingBox" ); + bBoxElement.setAttribute( "minx", QString::number( layerExtent.xMinimum() ) ); + bBoxElement.setAttribute( "miny", QString::number( layerExtent.yMinimum() ) ); + bBoxElement.setAttribute( "maxx", QString::number( layerExtent.xMaximum() ) ); + bBoxElement.setAttribute( "maxy", QString::number( layerExtent.yMaximum() ) ); + layerElem.appendChild( bBoxElement ); + + parentElement.appendChild( layerElem ); + } + } + } + return; +} + void QgsProjectParser::addLayers( QDomDocument &doc, QDomElement &parentElem, const QDomElement &legendElem, @@ -584,6 +642,37 @@ QStringList QgsProjectParser::identifyDisabledLayers() const return disabledList; } +QStringList QgsProjectParser::wfsLayers() const +{ + QStringList wfsList; + if ( !mXMLDoc ) + { + return wfsList; + } + + QDomElement qgisElem = mXMLDoc->documentElement(); + if ( qgisElem.isNull() ) + { + return wfsList; + } + QDomElement propertiesElem = qgisElem.firstChildElement( "properties" ); + if ( propertiesElem.isNull() ) + { + return wfsList; + } + QDomElement wfsLayersElem = propertiesElem.firstChildElement( "WFSLayers" ); + if ( wfsLayersElem.isNull() ) + { + return wfsList; + } + QDomNodeList valueList = wfsLayersElem.elementsByTagName( "value" ); + for ( int i = 0; i < valueList.size(); ++i ) + { + wfsList << valueList.at( i ).toElement().text(); + } + return wfsList; +} + QStringList QgsProjectParser::supportedOutputCrsList() const { QStringList crsList; diff --git a/src/mapserver/qgsprojectparser.h b/src/mapserver/qgsprojectparser.h index 6f44a662ba01..6064e286fb1f 100644 --- a/src/mapserver/qgsprojectparser.h +++ b/src/mapserver/qgsprojectparser.h @@ -38,6 +38,8 @@ class QgsProjectParser: public QgsConfigParser /**Adds layer and style specific capabilities elements to the parent node. This includes the individual layers and styles, their description, native CRS, bounding boxes, etc.*/ virtual void layersAndStylesCapabilities( QDomElement& parentElement, QDomDocument& doc ) const; + virtual void featureTypeList( QDomElement& parentElement, QDomDocument& doc ) 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*/ @@ -58,6 +60,9 @@ class QgsProjectParser: public QgsConfigParser /**Returns an ID-list of layers which are not queryable (comes from -> -> -> in the project file*/ + virtual QStringList wfsLayers() const; + /**Returns a set of supported epsg codes for the capabilities document. The list comes from the property in the project file. An empty set means that all possible CRS should be advertised (which could result in very long capabilities documents) Example: diff --git a/src/mapserver/qgsrequesthandler.h b/src/mapserver/qgsrequesthandler.h index a4aee3036eaa..b5381be93e72 100644 --- a/src/mapserver/qgsrequesthandler.h +++ b/src/mapserver/qgsrequesthandler.h @@ -41,6 +41,9 @@ class QgsRequestHandler virtual void sendServiceException( const QgsMapServiceException& ex ) const = 0; virtual void sendGetStyleResponse( const QDomDocument& doc ) const = 0; virtual void sendGetPrintResponse( QByteArray* ba ) const = 0; + virtual bool startGetFeatureResponse( QByteArray* ba, const QString& infoFormat ) const = 0; + virtual void sendGetFeatureResponse( QByteArray* ba ) const = 0; + virtual void endGetFeatureResponse( 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 7034efde8329..fbb9cd56dd9b 100644 --- a/src/mapserver/qgssldparser.h +++ b/src/mapserver/qgssldparser.h @@ -56,6 +56,8 @@ class QgsSLDParser: public QgsConfigParser /**Adds layer and style specific capabilities elements to the parent node. This includes the individual layers and styles, their description, native CRS, bounding boxes, etc.*/ void layersAndStylesCapabilities( QDomElement& parentElement, QDomDocument& doc ) const; + void featureTypeList( QDomElement& parentElement, QDomDocument& doc ) const {}; + /**Returns number of layers in configuration*/ int numberOfLayers() const; diff --git a/src/mapserver/qgswfsserver.cpp b/src/mapserver/qgswfsserver.cpp new file mode 100644 index 000000000000..2268158b651c --- /dev/null +++ b/src/mapserver/qgswfsserver.cpp @@ -0,0 +1,946 @@ +#include "qgswfsserver.h" +#include "qgsconfigparser.h" +#include "qgscrscache.h" +#include "qgsfield.h" +#include "qgsgeometry.h" +#include "qgsmaplayer.h" +#include "qgsmaplayerregistry.h" +#include "qgsmaprenderer.h" +#include "qgsmaptopixel.h" +#include "qgspallabeling.h" +#include "qgsproject.h" +#include "qgsrasterlayer.h" +#include "qgsscalecalculator.h" +#include "qgscoordinatereferencesystem.h" +#include "qgsvectordataprovider.h" +#include "qgsvectorlayer.h" +#include "qgsfilter.h" +#include "qgslogger.h" +#include "qgsmapserviceexception.h" +#include "qgssldparser.h" +#include "qgssymbol.h" +#include "qgssymbolv2.h" +#include "qgsrenderer.h" +#include "qgslegendmodel.h" +#include "qgscomposerlegenditem.h" +#include "qgslogger.h" +#include "qgsrequesthandler.h" +#include +#include +#include +#include +#include + +//for printing +#include "qgscomposition.h" +#include +#include +#include +#include +#include + +QgsWFSServer::QgsWFSServer( QMap parameters ) + : mParameterMap( parameters ) + , mConfigParser( 0 ) +{ +} + +QgsWFSServer::~QgsWFSServer() +{ +} + +QgsWFSServer::QgsWFSServer() +{ +} + +QDomDocument QgsWFSServer::getCapabilities() +{ + QgsDebugMsg( "Entering." ); + QDomDocument doc; + //wfs:WFS_Capabilities element + QDomElement wfsCapabilitiesElement = doc.createElement( "WFS_Capabilities"/*wms:WFS_Capabilities*/ ); + wfsCapabilitiesElement.setAttribute( "xmlns", "http://www.opengis.net/wfs" ); + wfsCapabilitiesElement.setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); + wfsCapabilitiesElement.setAttribute( "xsi:schemaLocation", "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd" ); + wfsCapabilitiesElement.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" ); + wfsCapabilitiesElement.setAttribute( "xmlns:gml", "http://www.opengis.net/gml" ); + wfsCapabilitiesElement.setAttribute( "xmlns:ows", "http://www.opengis.net/ows" ); + wfsCapabilitiesElement.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" ); + wfsCapabilitiesElement.setAttribute( "version", "1.0.0" ); + wfsCapabilitiesElement.setAttribute( "updateSequence", "0" ); + doc.appendChild( wfsCapabilitiesElement ); + + if ( mConfigParser ) + { + mConfigParser->serviceCapabilities( wfsCapabilitiesElement, doc ); + } + + //wfs:Capability element + QDomElement capabilityElement = doc.createElement( "Capability"/*wfs:Capability*/ ); + wfsCapabilitiesElement.appendChild( capabilityElement ); + + //wfs:Request element + QDomElement requestElement = doc.createElement( "Request"/*wfs:Request*/ ); + capabilityElement.appendChild( requestElement ); + //wfs:GetCapabilities + QDomElement getCapabilitiesElement = doc.createElement( "GetCapabilities"/*wfs:GetCapabilities*/ ); + requestElement.appendChild( getCapabilitiesElement ); + QDomElement capabilitiesFormatElement = doc.createElement( "Format" );/*wfs:Format*/ + getCapabilitiesElement.appendChild( capabilitiesFormatElement ); + QDomText capabilitiesFormatText = doc.createTextNode( "text/xml" ); + capabilitiesFormatElement.appendChild( capabilitiesFormatText ); + + QDomElement dcpTypeElement = doc.createElement( "DCPType"/*wfs:DCPType*/ ); + getCapabilitiesElement.appendChild( dcpTypeElement ); + QDomElement httpElement = doc.createElement( "HTTP"/*wfs:HTTP*/ ); + dcpTypeElement.appendChild( httpElement ); + + //Prepare url + //Some client requests already have http:// in the REQUEST_URI variable + QString hrefString; + QString requestUrl = getenv( "REQUEST_URI" ); + QUrl mapUrl( requestUrl ); + mapUrl.setHost( QString( getenv( "SERVER_NAME" ) ) ); + mapUrl.removeQueryItem( "REQUEST" ); + mapUrl.removeQueryItem( "VERSION" ); + mapUrl.removeQueryItem( "SERVICE" ); + hrefString = mapUrl.toString(); + + //only Get supported for the moment + QDomElement getElement = doc.createElement( "Get"/*wfs:Get*/ ); + httpElement.appendChild( getElement ); + QDomElement olResourceElement = doc.createElement( "OnlineResource"/*wfs:OnlineResource*/ ); + olResourceElement.setAttribute( "xlink:type", "simple" ); + requestUrl.truncate( requestUrl.indexOf( "?" ) + 1 ); + olResourceElement.setAttribute( "xlink:href", hrefString ); + getElement.appendChild( olResourceElement ); + + //wfs:DescribeFeatureType + QDomElement describeFeatureTypeElement = doc.createElement( "DescribeFeatureType"/*wfs:DescribeFeatureType*/ ); + requestElement.appendChild( describeFeatureTypeElement ); + QDomElement schemaDescriptionLanguageElement = doc.createElement( "SchemaDescriptionLanguage"/*wfs:SchemaDescriptionLanguage*/ ); + describeFeatureTypeElement.appendChild( schemaDescriptionLanguageElement ); + QDomElement xmlSchemaElement = doc.createElement( "XMLSCHEMA"/*wfs:XMLSCHEMA*/ ); + schemaDescriptionLanguageElement.appendChild( xmlSchemaElement ); + QDomElement describeFeatureTypeDhcTypeElement = dcpTypeElement.cloneNode().toElement();//this is the same as for 'GetCapabilities' + describeFeatureTypeElement.appendChild( describeFeatureTypeDhcTypeElement ); + + //wfs:GetFeature + QDomElement getFeatureElement = doc.createElement( "GetFeature"/*wfs:GetFeature*/ ); + requestElement.appendChild( getFeatureElement ); + QDomElement getFeatureFormatElement = doc.createElement( "ResultFormat" );/*wfs:ResultFormat*/ + getFeatureElement.appendChild( getFeatureFormatElement ); + QDomElement gmlFormatElement = doc.createElement( "GML2" );/*wfs:GML2*/ + getFeatureFormatElement.appendChild( gmlFormatElement ); + QDomElement geojsonFormatElement = doc.createElement( "GeoJSON" );/*wfs:GeoJSON*/ + getFeatureFormatElement.appendChild( geojsonFormatElement ); + QDomElement getFeatureDhcTypeElement = dcpTypeElement.cloneNode().toElement();//this is the same as for 'GetCapabilities' + getFeatureElement.appendChild( getFeatureDhcTypeElement ); + + //wfs:FeatureTypeList element + QDomElement featureTypeListElement = doc.createElement( "FeatureTypeList"/*wfs:FeatureTypeList*/ ); + capabilityElement.appendChild( featureTypeListElement ); + //wfs:Operations element + QDomElement operationsElement = doc.createElement( "Operations"/*wfs:Operations*/ ); + featureTypeListElement.appendChild( operationsElement ); + //wfs:Query element + QDomElement queryElement = doc.createElement( "Query"/*wfs:Query*/ ); + operationsElement.appendChild( queryElement ); + /* + * Adding layer liste in featureTypeListElement + */ + if ( mConfigParser ) + { + mConfigParser->featureTypeList( featureTypeListElement, doc ); + } + + /* + * Adding ogc:Filter_Capabilities in capabilityElement + */ + //ogc:Filter_Capabilities element + QDomElement filterCapabilitiesElement = doc.createElement( "ogc:Filter_Capabilities"/*ogc:Filter_Capabilities*/ ); + capabilityElement.appendChild( filterCapabilitiesElement ); + QDomElement spatialCapabilitiesElement = doc.createElement( "ogc:Spatial_Capabilities"/*ogc:Spatial_Capabilities*/ ); + filterCapabilitiesElement.appendChild( spatialCapabilitiesElement ); + QDomElement spatialOperatorsElement = doc.createElement( "ogc:Spatial_Operators"/*ogc:Spatial_Operators*/ ); + spatialCapabilitiesElement.appendChild( spatialOperatorsElement ); + QDomElement ogcBboxElement = doc.createElement( "ogc:BBOX"/*ogc:BBOX*/ ); + spatialOperatorsElement.appendChild( ogcBboxElement ); + QDomElement scalarCapabilitiesElement = doc.createElement( "ogc:Scalar_Capabilities"/*ogc:Scalar_Capabilities*/ ); + filterCapabilitiesElement.appendChild( scalarCapabilitiesElement ); + QDomElement comparisonOperatorsElement = doc.createElement( "ogc:Comparison_Operators"/*ogc:Comparison_Operators*/ ); + scalarCapabilitiesElement.appendChild( comparisonOperatorsElement ); + QDomElement simpleComparisonsElement = doc.createElement( "ogc:Simple_Comparisons"/*ogc:Simple_Comparisons*/ ); + comparisonOperatorsElement.appendChild( simpleComparisonsElement ); + return doc; +} + +QDomDocument QgsWFSServer::describeFeatureType() +{ + QgsDebugMsg( "Entering." ); + QDomDocument doc; + //xsd:schema + QDomElement schemaElement = doc.createElement( "schema"/*xsd:schema*/ ); + schemaElement.setAttribute( "xmlns", "http://www.w3.org/2001/XMLSchema" ); + schemaElement.setAttribute( "xmlns:xsd", "http://www.w3.org/2001/XMLSchema" ); + schemaElement.setAttribute( "xmlns:ogc", "http://www.opengis.net/ogc" ); + schemaElement.setAttribute( "xmlns:gml", "http://www.opengis.net/gml" ); + schemaElement.setAttribute( "xmlns:qgs", "http://www.qgis.org/gml" ); + schemaElement.setAttribute( "targetNamespace", "http://www.qgis.org/gml" ); + doc.appendChild( schemaElement ); + + //xsd:import + QDomElement importElement = doc.createElement( "import"/*xsd:import*/ ); + importElement.setAttribute( "namespace", "http://www.opengis.net/gml" ); + importElement.setAttribute( "schemaLocation", "http://schemas.opengis.net/gml/2.1.2/feature.xsd" ); + schemaElement.appendChild( importElement ); + + //read TYPENAME + QString typeName; + QMap::const_iterator type_name_it = mParameterMap.find( "TYPENAME" ); + if ( type_name_it != mParameterMap.end() ) + { + typeName = type_name_it.value(); + } + else + { + return doc; + } + + QStringList wfsLayersId = mConfigParser->wfsLayers(); + QMap< QString, QMap< int, QString > > aliasInfo = mConfigParser->layerAliasInfo(); + QMap< QString, QSet > hiddenAttributes = mConfigParser->hiddenAttributes(); + + QList layerList; + QgsMapLayer* currentLayer = 0; + + layerList = mConfigParser->mapLayerFromStyle( typeName, "" ); + currentLayer = layerList.at( 0 ); + + QgsVectorLayer* layer = dynamic_cast( currentLayer ); + if ( layer && wfsLayersId.contains( layer->id() ) ) + { + //is there alias info for this vector layer? + QMap< int, QString > layerAliasInfo; + QMap< QString, QMap< int, QString > >::const_iterator aliasIt = aliasInfo.find( currentLayer->id() ); + if ( aliasIt != aliasInfo.constEnd() ) + { + layerAliasInfo = aliasIt.value(); + } + + //hidden attributes for this layer + QSet layerHiddenAttributes; + QMap< QString, QSet >::const_iterator hiddenIt = hiddenAttributes.find( currentLayer->id() ); + if ( hiddenIt != hiddenAttributes.constEnd() ) + { + layerHiddenAttributes = hiddenIt.value(); + } + + //do a select with searchRect and go through all the features + QgsVectorDataProvider* provider = layer->dataProvider(); + if ( !provider ) + { + return doc; + } + + typeName = typeName.replace( QString( " " ), QString( "_" ) ); + + //xsd:element + QDomElement elementElem = doc.createElement( "element"/*xsd:element*/ ); + elementElem.setAttribute( "name", typeName ); + elementElem.setAttribute( "type", "qgs:" + typeName + "Type" ); + elementElem.setAttribute( "substitutionGroup", "gml:_Feature" ); + schemaElement.appendChild( elementElem ); + + //xsd:complexType + QDomElement complexTypeElem = doc.createElement( "complexType"/*xsd:complexType*/ ); + complexTypeElem.setAttribute( "name", typeName + "Type" ); + schemaElement.appendChild( complexTypeElem ); + + //xsd:complexType + QDomElement complexContentElem = doc.createElement( "complexContent"/*xsd:complexContent*/ ); + complexTypeElem.appendChild( complexContentElem ); + + //xsd:extension + QDomElement extensionElem = doc.createElement( "extension"/*xsd:extension*/ ); + extensionElem.setAttribute( "base", "gml:AbstractFeatureType" ); + complexContentElem.appendChild( extensionElem ); + + //xsd:sequence + QDomElement sequenceElem = doc.createElement( "sequence"/*xsd:sequence*/ ); + extensionElem.appendChild( sequenceElem ); + + //xsd:element + QDomElement geomElem = doc.createElement( "element"/*xsd:element*/ ); + geomElem.setAttribute( "name", "geometry" ); + geomElem.setAttribute( "type", "gml:GeometryPropertyType" ); + geomElem.setAttribute( "minOccurs", "0" ); + geomElem.setAttribute( "maxOccurs", "1" ); + sequenceElem.appendChild( geomElem ); + + const QgsFieldMap& fields = provider->fields(); + for ( QgsFieldMap::const_iterator it = fields.begin(); it != fields.end(); ++it ) + { + + QString attributeName = it.value().name(); + //skip attribute if it has edit type 'hidden' + if ( layerHiddenAttributes.contains( attributeName ) ) + { + continue; + } + + //xsd:element + QDomElement geomElem = doc.createElement( "element"/*xsd:element*/ ); + geomElem.setAttribute( "name", attributeName ); + if ( it.value().type() == 2 ) + geomElem.setAttribute( "type", "integer" ); + else if ( it.value().type() == 6 ) + geomElem.setAttribute( "type", "double" ); + else + geomElem.setAttribute( "type", "string" ); + + sequenceElem.appendChild( geomElem ); + + //check if the attribute name should be replaced with an alias + QMap::const_iterator aliasIt = layerAliasInfo.find( it.key() ); + if ( aliasIt != layerAliasInfo.constEnd() ) + { + geomElem.setAttribute( "alias", aliasIt.value() ); + } + + } + } + + return doc; +} + +int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format ) +{ + QgsDebugMsg( "Info format is:" + format ); + + //read TYPENAME + QMap::const_iterator type_name_it = mParameterMap.find( "TYPENAME" ); + if ( type_name_it != mParameterMap.end() ) + { + mTypeName = type_name_it.value(); + } + else + { + return 1; + } + + QStringList wfsLayersId = mConfigParser->wfsLayers(); + QMap< QString, QMap< int, QString > > aliasInfo = mConfigParser->layerAliasInfo(); + QMap< QString, QSet > hiddenAttributes = mConfigParser->hiddenAttributes(); + + QList layerList; + QgsMapLayer* currentLayer = 0; + + layerList = mConfigParser->mapLayerFromStyle( mTypeName, "" ); + currentLayer = layerList.at( 0 ); + + QgsVectorLayer* layer = dynamic_cast( currentLayer ); + if ( layer && wfsLayersId.contains( layer->id() ) ) + { + //is there alias info for this vector layer? + QMap< int, QString > layerAliasInfo; + QMap< QString, QMap< int, QString > >::const_iterator aliasIt = aliasInfo.find( currentLayer->id() ); + if ( aliasIt != aliasInfo.constEnd() ) + { + layerAliasInfo = aliasIt.value(); + } + + //hidden attributes for this layer + QSet layerHiddenAttributes; + QMap< QString, QSet >::const_iterator hiddenIt = hiddenAttributes.find( currentLayer->id() ); + if ( hiddenIt != hiddenAttributes.constEnd() ) + { + layerHiddenAttributes = hiddenIt.value(); + } + + //do a select with searchRect and go through all the features + QgsVectorDataProvider* provider = layer->dataProvider(); + if ( !provider ) + { + return 2; + } + + QgsFeature feature; + QgsAttributeMap featureAttributes; + const QgsFieldMap& fields = provider->fields(); + + //map extent + QgsRectangle searchRect = layer->extent(); + + //read FEATUREDID + bool fidOk = false; + QString fid; + QMap::const_iterator fidIt = mParameterMap.find( "FEATUREID" ); + if ( fidIt != mParameterMap.end() ) + { + fidOk = true; + fid = fidIt.value(); + } + + //read FILTER + bool filterOk = false; + QDomDocument filter; + QMap::const_iterator filterIt = mParameterMap.find( "FILTER" ); + if ( filterIt != mParameterMap.end() ) + { + try + { + QString errorMsg; + if ( !filter.setContent( filterIt.value(), true, &errorMsg ) ) + { + QgsDebugMsg( "soap request parse error" ); + QgsDebugMsg( "error message: " + errorMsg ); + QgsDebugMsg( "the xml string was:" ); + QgsDebugMsg( filterIt.value() ); + } + else + { + filterOk = true; + } + } + catch ( QgsMapServiceException& e ) + { + filterOk = false; + } + } + + + bool conversionSuccess; + double minx, miny, maxx, maxy; + bool bboxOk = false; + //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;} + } + + //read MAXFEATURES + long maxFeat = layer->featureCount(); + long featureCounter = 0; + QMap::const_iterator mfIt = mParameterMap.find( "MAXFEATURES" ); + if ( mfIt != mParameterMap.end() ) + { + QString mfString = mfIt.value(); + bool mfOk; + maxFeat = mfString.toLong( &mfOk, 10 ); + if ( !mfOk ) { maxFeat = layer->featureCount(); } + } + + //read PROPERTYNAME + mWithGeom = true; + QgsAttributeList attrIndexes = provider->attributeIndexes(); + QMap::const_iterator pnIt = mParameterMap.find( "PROPERTYNAME" ); + if ( pnIt != mParameterMap.end() ) + { + QStringList attrList = pnIt.value().split( "," ); + if ( attrList.size() > 0 ) + { + mWithGeom = false; + QStringList::const_iterator alstIt; + QList idxList; + QMap fieldMap = provider->fieldNameMap(); + QMap::const_iterator fieldIt; + QString fieldName; + for ( alstIt = attrList.begin(); alstIt != attrList.end(); ++alstIt ) + { + fieldName = *alstIt; + fieldIt = fieldMap.find( fieldName ); + if ( fieldIt != fieldMap.end() ) + { + idxList.append( fieldIt.value() ); + } + else if ( fieldName == "geometry" ) + { + mWithGeom = true; + } + } + if ( idxList.size() > 0 || mWithGeom ) + { + attrIndexes = idxList; + } + else + { + mWithGeom = true; + } + } + } + + QgsCoordinateReferenceSystem layerCrs = layer->crs(); + + startGetFeature( request, format ); + + if ( fidOk ) + { + provider->featureAtId( fid.toInt(), feature, mWithGeom, attrIndexes ); + sendGetFeature( request, format, &feature, 0, layerCrs, fields, layerHiddenAttributes ); + } + else if ( filterOk ) + { + provider->select( attrIndexes, searchRect, mWithGeom, true ); + try + { + QgsFilter* mFilter = QgsFilter::createFilterFromXml( filter.firstChild().toElement().firstChild().toElement(), layer ); + while ( provider->nextFeature( feature ) && featureCounter < maxFeat ) + { + if ( mFilter ) + { + if ( mFilter->evaluate( feature ) ) + { + sendGetFeature( request, format, &feature, featureCounter, layerCrs, fields, layerHiddenAttributes ); + ++featureCounter; + } + } + else + { + sendGetFeature( request, format, &feature, featureCounter, layerCrs, fields, layerHiddenAttributes ); + ++featureCounter; + } + } + delete mFilter; + } + catch ( QgsMapServiceException& e ) + { + while ( provider->nextFeature( feature ) && featureCounter < maxFeat ) + { + sendGetFeature( request, format, &feature, featureCounter, layerCrs, fields, layerHiddenAttributes ); + ++featureCounter; + } + } + } + else + { + if ( bboxOk ) + searchRect.set( minx, miny, maxx, maxy ); + provider->select( attrIndexes, searchRect, mWithGeom, true ); + while ( provider->nextFeature( feature ) && featureCounter < maxFeat ) + { + sendGetFeature( request, format, &feature, featureCounter, layerCrs, fields, layerHiddenAttributes ); + ++featureCounter; + } + } + + endGetFeature( request, format ); + } + else + { + return 2; + } + return 0; +} + +void QgsWFSServer::startGetFeature( QgsRequestHandler& request, const QString& format ) +{ + QByteArray result; + QString fcString; + if ( format == "GeoJSON" ) + { + fcString = "{\"type\": \"FeatureCollection\",\n"; + fcString += " \"features\": [\n"; + result = fcString.toUtf8(); + request.startGetFeatureResponse( &result, format ); + } + else + { + //wfs:FeatureCollection + fcString = " fields, QSet hiddenAttributes ) /*const*/ +{ + QByteArray result; + if ( format == "GeoJSON" ) + { + QString fcString; + if ( featIdx == 0 ) + fcString += " "; + else + fcString += " ,"; + fcString += createFeatureGeoJSON( feat, crs, fields, hiddenAttributes ); + fcString += "\n"; + + result = fcString.toUtf8(); + request.sendGetFeatureResponse( &result ); + fcString = ""; + } + else + { + QDomDocument gmlDoc; + QDomElement featureElement = createFeatureElem( feat, gmlDoc, crs, fields, hiddenAttributes ); + gmlDoc.appendChild( featureElement ); + + result = gmlDoc.toByteArray(); + request.sendGetFeatureResponse( &result ); + gmlDoc.removeChild( featureElement ); + } +} + +void QgsWFSServer::endGetFeature( QgsRequestHandler& request, const QString& format ) +{ + QByteArray result; + QString fcString; + if ( format == "GeoJSON" ) + { + fcString += " ]\n"; + fcString += "}"; + + result = fcString.toUtf8(); + request.endGetFeatureResponse( &result ); + fcString = ""; + } + else + { + fcString = ""; + result = fcString.toUtf8(); + request.endGetFeatureResponse( &result ); + fcString = ""; + } +} + +QString QgsWFSServer::createFeatureGeoJSON( QgsFeature* feat, QgsCoordinateReferenceSystem& crs, QMap< int, QgsField > fields, QSet hiddenAttributes ) /*const*/ +{ + QString fStr = "{\"type\": \"Feature\",\n"; + + fStr += " \"id\": "; + fStr += QString::number( feat->id() ); + fStr += ",\n"; + + QgsGeometry* geom = feat->geometry(); + if ( geom && mWithGeom ) + { + fStr += " \"geometry\": "; + fStr += geom->exportToGeoJSON(); + fStr += ",\n"; + } + + //read all attribute values from the feature + fStr += " \"properties\": {\n"; + QgsAttributeMap featureAttributes = feat->attributeMap(); + int attributeCounter = 0; + for ( QgsAttributeMap::const_iterator it = featureAttributes.begin(); it != featureAttributes.end(); ++it ) + { + QString attributeName = fields[it.key()].name(); + //skip attribute if it has edit type 'hidden' + if ( hiddenAttributes.contains( attributeName ) ) + { + continue; + } + + if ( attributeCounter == 0 ) + fStr += " \""; + else + fStr += " ,\""; + fStr += attributeName; + fStr += "\": "; + if ( it->type() == 6 || it->type() == 2 ) + { + fStr += it->toString(); + } + else + { + fStr += "\""; + fStr += it->toString().replace( QString( "\"" ), QString( "\\\"" ) ); + fStr += "\""; + } + fStr += "\n"; + ++attributeCounter; + } + + fStr += " }\n"; + + fStr += " }"; + + return fStr; +} + +QDomElement QgsWFSServer::createFeatureElem( QgsFeature* feat, QDomDocument& doc, QgsCoordinateReferenceSystem& crs, QMap< int, QgsField > fields, QSet hiddenAttributes ) /*const*/ +{ + //gml:FeatureMember + QDomElement featureElement = doc.createElement( "gml:featureMember"/*wfs:FeatureMember*/ ); + + //qgs:%TYPENAME% + QDomElement typeNameElement = doc.createElement( "qgs:" + mTypeName.replace( QString( " " ), QString( "_" ) )/*qgs:%TYPENAME%*/ ); + typeNameElement.setAttribute( "fid", QString::number( feat->id() ) ); + featureElement.appendChild( typeNameElement ); + + if ( mWithGeom ) + { + //add geometry column (as gml) + QDomElement geomElem = doc.createElement( "qgs:geometry" ); + QDomElement gmlElem = createGeometryElem( feat->geometry(), doc ); + if ( !gmlElem.isNull() ) + { + if ( crs.isValid() ) + { + gmlElem.setAttribute( "srsName", crs.authid() ); + } + geomElem.appendChild( gmlElem ); + typeNameElement.appendChild( geomElem ); + } + } + + //read all attribute values from the feature + QgsAttributeMap featureAttributes = feat->attributeMap(); + for ( QgsAttributeMap::const_iterator it = featureAttributes.begin(); it != featureAttributes.end(); ++it ) + { + + QString attributeName = fields[it.key()].name(); + //skip attribute if it has edit type 'hidden' + if ( hiddenAttributes.contains( attributeName ) ) + { + continue; + } + + QDomElement fieldElem = doc.createElement( "qgs:" + attributeName.replace( QString( " " ), QString( "_" ) ) ); + QDomText fieldText = doc.createTextNode( it->toString() ); + fieldElem.appendChild( fieldText ); + typeNameElement.appendChild( fieldElem ); + } + + return featureElement; +} + +QDomElement QgsWFSServer::createGeometryElem( QgsGeometry* geom, QDomDocument& doc ) /*const*/ +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement geomElement; + + QString geomTypeName; + QGis::WkbType wkbType = geom->wkbType(); + switch ( wkbType ) + { + case QGis::WKBPoint: + case QGis::WKBPoint25D: + geomElement = createPointElem( geom, doc ); + break; + case QGis::WKBMultiPoint: + case QGis::WKBMultiPoint25D: + geomElement = createMultiPointElem( geom, doc ); + break; + case QGis::WKBLineString: + case QGis::WKBLineString25D: + geomElement = createLineStringElem( geom, doc ); + break; + case QGis::WKBMultiLineString: + case QGis::WKBMultiLineString25D: + geomElement = createMultiLineStringElem( geom, doc ); + break; + case QGis::WKBPolygon: + case QGis::WKBPolygon25D: + geomElement = createPolygonElem( geom, doc ); + break; + case QGis::WKBMultiPolygon: + case QGis::WKBMultiPolygon25D: + geomElement = createMultiPolygonElem( geom, doc ); + break; + default: + return QDomElement(); + } + return geomElement; +} + +QDomElement QgsWFSServer::createLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement lineStringElem = doc.createElement( "gml:LineString" ); + QDomElement coordElem = createCoordinateElem( geom->asPolyline(), doc ); + lineStringElem.appendChild( coordElem ); + return lineStringElem; +} + +QDomElement QgsWFSServer::createMultiLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement multiLineStringElem = doc.createElement( "gml:MultiLineString" ); + QgsMultiPolyline multiline = geom->asMultiPolyline(); + + QgsMultiPolyline::const_iterator multiLineIt = multiline.constBegin(); + for ( ; multiLineIt != multiline.constEnd(); ++multiLineIt ) + { + QgsGeometry* lineGeom = QgsGeometry::fromPolyline( *multiLineIt ); + if ( lineGeom ) + { + QDomElement lineStringMemberElem = doc.createElement( "gml:lineStringMember" ); + QDomElement lineElem = createLineStringElem( lineGeom, doc ); + lineStringMemberElem.appendChild( lineElem ); + multiLineStringElem.appendChild( lineStringMemberElem ); + } + delete lineGeom; + } + + return multiLineStringElem; +} + +QDomElement QgsWFSServer::createPointElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement pointElem = doc.createElement( "gml:Point" ); + QgsPoint p = geom->asPoint(); + QVector v; + v.append( p ); + QDomElement coordElem = createCoordinateElem( v, doc ); + pointElem.appendChild( coordElem ); + return pointElem; +} + +QDomElement QgsWFSServer::createMultiPointElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement multiPointElem = doc.createElement( "gml:MultiPoint" ); + QgsMultiPoint multiPoint = geom->asMultiPoint(); + + QgsMultiPoint::const_iterator multiPointIt = multiPoint.constBegin(); + for ( ; multiPointIt != multiPoint.constEnd(); ++multiPointIt ) + { + QgsGeometry* pointGeom = QgsGeometry::fromPoint( *multiPointIt ); + if ( pointGeom ) + { + QDomElement multiPointMemberElem = doc.createElement( "gml:pointMember" ); + QDomElement pointElem = createPointElem( pointGeom, doc ); + multiPointMemberElem.appendChild( pointElem ); + multiPointElem.appendChild( multiPointMemberElem ); + } + } + return multiPointElem; +} + +QDomElement QgsWFSServer::createPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + + QDomElement polygonElem = doc.createElement( "gml:Polygon" ); + QgsPolygon poly = geom->asPolygon(); + for ( int i = 0; i < poly.size(); ++i ) + { + QString boundaryName; + if ( i == 0 ) + { + boundaryName = "outerBoundaryIs"; + } + else + { + boundaryName = "innerBoundaryIs"; + } + QDomElement boundaryElem = doc.createElementNS( "http://www.opengis.net/gml", boundaryName ); + QDomElement ringElem = doc.createElement( "gml:LinearRing" ); + QDomElement coordElem = createCoordinateElem( poly.at( i ), doc ); + ringElem.appendChild( coordElem ); + boundaryElem.appendChild( ringElem ); + polygonElem.appendChild( boundaryElem ); + } + return polygonElem; +} + +QDomElement QgsWFSServer::createMultiPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const +{ + if ( !geom ) + { + return QDomElement(); + } + QDomElement multiPolygonElem = doc.createElement( "gml:MultiPolygon" ); + QgsMultiPolygon multipoly = geom->asMultiPolygon(); + + QgsMultiPolygon::const_iterator polyIt = multipoly.constBegin(); + for ( ; polyIt != multipoly.constEnd(); ++polyIt ) + { + QgsGeometry* polygonGeom = QgsGeometry::fromPolygon( *polyIt ); + if ( polygonGeom ) + { + QDomElement polygonMemberElem = doc.createElement( "gml:polygonMember" ); + QDomElement polygonElem = createPolygonElem( polygonGeom, doc ); + delete polygonGeom; + polygonMemberElem.appendChild( polygonElem ); + multiPolygonElem.appendChild( polygonMemberElem ); + } + } + return multiPolygonElem; +} + +QDomElement QgsWFSServer::createCoordinateElem( const QVector points, QDomDocument& doc ) const +{ + QDomElement coordElem = doc.createElement( "gml:coordinates" ); + coordElem.setAttribute( "cs", "," ); + coordElem.setAttribute( "ts", " " ); + + //precision 4 for meters / feet, precision 8 for degrees + int precision = 8; + /* + if ( mSourceCRS.mapUnits() == QGis::Meters + || mSourceCRS.mapUnits() == QGis::Feet ) + { + precision = 4; + } + */ + + QString coordString; + QVector::const_iterator pointIt = points.constBegin(); + for ( ; pointIt != points.constEnd(); ++pointIt ) + { + if ( pointIt != points.constBegin() ) + { + coordString += " "; + } + coordString += QString::number( pointIt->x(), 'f', precision ); + coordString += ","; + coordString += QString::number( pointIt->y(), 'f', precision ); + } + + QDomText coordText = doc.createTextNode( coordString ); + coordElem.appendChild( coordText ); + return coordElem; +} diff --git a/src/mapserver/qgswfsserver.h b/src/mapserver/qgswfsserver.h new file mode 100644 index 000000000000..45aae86fb514 --- /dev/null +++ b/src/mapserver/qgswfsserver.h @@ -0,0 +1,92 @@ + +#ifndef QGSWFSSERVER_H +#define QGSWFSSERVER_H + +#include +#include +#include +#include + +class QgsCoordinateReferenceSystem; +class QgsComposerLayerItem; +class QgsComposerLegendItem; +class QgsComposition; +class QgsMapLayer; +class QgsMapRenderer; +class QgsPoint; +class QgsRasterLayer; +class QgsConfigParser; +class QgsVectorLayer; +class QgsCoordinateReferenceSystem; +class QgsField; +class QgsFeature; +class QgsGeometry; +class QgsSymbol; +class QgsRequestHandler; +class QFile; +class QFont; +class QImage; +class QPaintDevice; +class QPainter; + +/**This class handles all the wms 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 QgsWFSServer +independent from any server side technology*/ + +class QgsWFSServer +{ + public: + /**Constructor. Takes parameter map and a pointer to a renderer object (does not take ownership)*/ + QgsWFSServer( QMap parameters ); + ~QgsWFSServer(); + /**Returns an XML file with the capabilities description (as described in the WFS specs)*/ + QDomDocument getCapabilities(); + + /**Returns an XML file with the describe feature type (as described in the WFS specs)*/ + QDomDocument describeFeatureType(); + + /**Creates a document that describes the result of the getFeature request. + @return 0 in case of success*/ + int getFeature( QgsRequestHandler& request, const QString& format ); + + /**Sets configuration parser for administration settings. Does not take ownership*/ + void setAdminConfigParser( QgsConfigParser* parser ) { mConfigParser = parser; } + + private: + /**Don't use the default constructor*/ + QgsWFSServer(); + + /**Map containing the WMS parameters*/ + QMap mParameterMap; + QgsConfigParser* mConfigParser; + QString mTypeName; + bool mWithGeom; + + protected: + + void startGetFeature( QgsRequestHandler& request, const QString& format ); + void sendGetFeature( QgsRequestHandler& request, const QString& format, QgsFeature* feat, int featIdx, QgsCoordinateReferenceSystem& crs, QMap< int, QgsField > fields, QSet hiddenAttributes ); + void endGetFeature( QgsRequestHandler& request, const QString& format ); + + //methods to write GeoJSON + QString createFeatureGeoJSON( QgsFeature* feat, QgsCoordinateReferenceSystem& crs, QMap< int, QgsField > fields, QSet hiddenAttributes ) /*const*/; + + //methods to write GML2 + QDomElement createFeatureElem( QgsFeature* feat, QDomDocument& doc, QgsCoordinateReferenceSystem& crs, QMap< int, QgsField > fields, QSet hiddenAttributes ) /*const*/; + + QDomElement createGeometryElem( QgsGeometry* g, QDomDocument& doc ) /*const*/; + QDomElement createLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const; + QDomElement createMultiLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const; + QDomElement createPointElem( QgsGeometry* geom, QDomDocument& doc ) const; + QDomElement createMultiPointElem( QgsGeometry* geom, QDomDocument& doc ) const; + QDomElement createPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const; + QDomElement createMultiPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const; + + /**Create a GML coordinate string from a point list. + @param points list of data points + @param coordString out: GML coord string + @return 0 in case of success*/ + QDomElement createCoordinateElem( const QVector points, QDomDocument& doc ) const; +}; + +#endif diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui index 55a531d7ed38..bd26d497e9d3 100644 --- a/src/ui/qgsprojectpropertiesbase.ui +++ b/src/ui/qgsprojectpropertiesbase.ui @@ -363,9 +363,9 @@ - + - WMS Server + OWS Server @@ -384,7 +384,7 @@ - + Service Capabilitities @@ -483,6 +483,12 @@ + + + + WMS Capabilitities + + @@ -631,6 +637,32 @@ Add WKT geometry to feature info response + + + + + + + + WFS Capabilitities + + + + + + + Layer + + + + + Published + + + + + +