void QgsWcsCapabilities::parseContentMetadata( QDomElement const &e, QgsWcsCoverageSummary &coverageSummary ) { QDomNode n1 = e.firstChild(); while ( !n1.isNull() ) { QDomElement el = n1.toElement(); if ( !el.isNull() ) { QString tagName = stripNS( el.tagName() ); if ( tagName == QLatin1String( "CoverageOfferingBrief" ) ) { QgsWcsCoverageSummary subCoverageSummary; initCoverageSummary( subCoverageSummary ); parseCoverageOfferingBrief( el, subCoverageSummary, &coverageSummary ); subCoverageSummary.valid = true; coverageSummary.coverageSummary.push_back( subCoverageSummary ); } } n1 = n1.nextSibling(); } }
QList<QDomElement> QgsWcsCapabilities::domElements( const QDomElement &element, const QString &path ) { QList<QDomElement> list; QStringList names = path.split( '.' ); if ( names.isEmpty() ) return list; QString name = names.value( 0 ); names.removeFirst(); QDomNode n1 = element.firstChild(); while ( !n1.isNull() ) { QDomElement el = n1.toElement(); if ( !el.isNull() ) { QString tagName = stripNS( el.tagName() ); if ( tagName == name ) { if ( names.isEmpty() ) { list.append( el ); } else { list.append( domElements( el, names.join( QStringLiteral( "." ) ) ) ); } } } n1 = n1.nextSibling(); } return list; }
bool QgsGmlSchema::parseXSD( const QByteArray &xml ) { QDomDocument dom; QString errorMsg; int errorLine; int errorColumn; if ( !dom.setContent( xml, false, &errorMsg, &errorLine, &errorColumn ) ) { // TODO: error return false; } QDomElement docElem = dom.documentElement(); QList<QDomElement> elementElements = domElements( docElem, "element" ); //QgsDebugMsg( QString( "%1 elemets read" ).arg( elementElements.size() ) ); foreach ( QDomElement elementElement, elementElements ) { QString name = elementElement.attribute( "name" ); QString type = elementElement.attribute( "type" ); QString gmlBaseType = xsdComplexTypeGmlBaseType( docElem, stripNS( type ) ); //QgsDebugMsg( QString( "gmlBaseType = %1" ).arg( gmlBaseType ) ); //QgsDebugMsg( QString( "name = %1 gmlBaseType = %2" ).arg( name ).arg( gmlBaseType ) ); // We should only use gml:AbstractFeatureType descendants which have // ancestor listed in gml:FeatureAssociationType (featureMember) descendant // But we could only loose some data if XSD was not correct, I think. if ( gmlBaseType == "AbstractFeatureType" ) { // Get feature type definition QgsGmlFeatureClass featureClass( name, "" ); xsdFeatureClass( docElem, stripNS( type ), featureClass ); mFeatureClassMap.insert( name, featureClass ); } // A feature may have more geometries, we take just the first one }
QDomElement QgsWcsCapabilities::firstChild( const QDomElement &element, const QString &name ) { QDomNode n1 = element.firstChild(); while ( !n1.isNull() ) { QDomElement el = n1.toElement(); if ( !el.isNull() ) { QString tagName = stripNS( el.tagName() ); if ( tagName == name ) { return el; } } n1 = n1.nextSibling(); } return QDomElement(); }
bool QgsWcsCapabilities::parseCapabilitiesDom( QByteArray const &xml, QgsWcsCapabilitiesProperty &capabilities ) { QgsDebugMsg( "Entered." ); #ifdef QGISDEBUG QFile file( QDir::tempPath() + "/qgis-wcs-capabilities.xml" ); if ( file.open( QIODevice::WriteOnly ) ) { file.write( xml ); file.close(); } #endif if ( ! convertToDom( xml ) ) return false; QDomElement docElem = mCapabilitiesDom.documentElement(); // Assert that the DTD is what we expected (i.e. a WCS Capabilities document) QgsDebugMsg( "testing tagName " + docElem.tagName() ); QString tagName = stripNS( docElem.tagName() ); if ( // We don't support 1.0, but try WCS_Capabilities tag to get version tagName != "WCS_Capabilities" && // 1.0 tagName != "Capabilities" // 1.1, tags seen: Capabilities, wcs:Capabilities ) { if ( tagName == "ExceptionReport" ) { mErrorTitle = tr( "Exception" ); mErrorFormat = "text/plain"; mError = tr( "Could not get WCS capabilities: %1" ).arg( domElementText( docElem, "Exception.ExceptionText" ) ); } else { mErrorTitle = tr( "Dom Exception" ); mErrorFormat = "text/plain"; mError = tr( "Could not get WCS capabilities in the expected format (DTD): no %1 found.\nThis might be due to an incorrect WCS Server URL.\nTag:%3\nResponse was:\n%4" ) .arg( "Capabilities" ) .arg( docElem.tagName() ) .arg( QString( xml ) ); } QgsLogger::debug( "Dom Exception: " + mError ); return false; } capabilities.version = docElem.attribute( "version" ); mVersion = capabilities.version; if ( !mVersion.startsWith( "1.0" ) && !mVersion.startsWith( "1.1" ) ) { mErrorTitle = tr( "Version not supported" ); mErrorFormat = "text/plain"; mError = tr( "WCS server version %1 is not supported by QGIS (supported versions: 1.0.0, 1.1.0, 1.1.2)" ) .arg( mVersion ); QgsLogger::debug( "WCS version: " + mError ); return false; } if ( mVersion.startsWith( "1.0" ) ) { capabilities.title = domElementText( docElem, "Service.name" ); capabilities.abstract = domElementText( docElem, "Service.description" ); // There is also "label" in 1.0 capabilities.getCoverageGetUrl = domElement( docElem, "Capability.Request.GetCoverage.DCPType.HTTP.Get.OnlineResource" ).attribute( "xlink:href" ); parseContentMetadata( domElement( docElem, "ContentMetadata" ), capabilities.contents ); } else if ( mVersion.startsWith( "1.1" ) ) { capabilities.title = domElementText( docElem, "ServiceIdentification.Title" ); capabilities.abstract = domElementText( docElem, "ServiceIdentification.Abstract" ); QList<QDomElement> operationElements = domElements( docElem, "OperationsMetadata.Operation" ); foreach ( QDomElement el, operationElements ) { if ( el.attribute( "name" ) == "GetCoverage" ) { capabilities.getCoverageGetUrl = domElement( el, "DCP.HTTP.Get" ).attribute( "xlink:href" ); } } parseCoverageSummary( domElement( docElem, "Contents" ), capabilities.contents ); }
// ------------------------ 1.1 ---------------------------------------------- bool QgsWcsCapabilities::parseDescribeCoverageDom11( QByteArray const &xml, QgsWcsCoverageSummary *coverage ) { QgsDebugMsg( "coverage->identifier = " + coverage->identifier ); if ( ! convertToDom( xml ) ) return false; QDomElement docElem = mCapabilitiesDom.documentElement(); QgsDebugMsg( "testing tagName " + docElem.tagName() ); QString tagName = stripNS( docElem.tagName() ); if ( tagName != QLatin1String( "CoverageDescriptions" ) ) { mErrorTitle = tr( "Dom Exception" ); mErrorFormat = QStringLiteral( "text/plain" ); mError = tr( "Could not get WCS capabilities in the expected format (DTD): no %1 found.\nThis might be due to an incorrect WCS Server URL.\nTag: %3\nResponse was:\n%4" ) .arg( QStringLiteral( "CoverageDescriptions" ), docElem.tagName(), QString( xml ) ); QgsLogger::debug( "Dom Exception: " + mError ); return false; } // Get image size, we can get it from BoundingBox with crs=urn:ogc:def:crs:OGC::imageCRS // but while at least one BoundingBox is mandatory, it does not have to be urn:ogc:def:crs:OGC::imageCRS // TODO: if BoundingBox with crs=urn:ogc:def:crs:OGC::imageCRS is not found, // we could calculate image size from GridCRS.GridOffsets (if available) QList<QDomElement> boundingBoxElements = domElements( docElem, QStringLiteral( "CoverageDescription.Domain.SpatialDomain.BoundingBox" ) ); QgsDebugMsg( QStringLiteral( "%1 BoundingBox found" ).arg( boundingBoxElements.size() ) ); const auto constBoundingBoxElements = boundingBoxElements; for ( const QDomElement &el : constBoundingBoxElements ) { QString authid = crsUrnToAuthId( el.attribute( QStringLiteral( "crs" ) ) ); QList<double> low = parseDoubles( domElementText( el, QStringLiteral( "LowerCorner" ) ) ); QList<double> high = parseDoubles( domElementText( el, QStringLiteral( "UpperCorner" ) ) ); if ( low.size() != 2 && high.size() != 2 ) continue; if ( el.attribute( QStringLiteral( "crs" ) ) == QLatin1String( "urn:ogc:def:crs:OGC::imageCRS" ) ) { coverage->width = ( int )( high[0] - low[0] + 1 ); coverage->height = ( int )( high[1] - low[1] + 1 ); coverage->hasSize = true; } else { QgsRectangle box; QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( authid ); if ( crs.isValid() && crs.hasAxisInverted() ) { box = QgsRectangle( low[1], low[0], high[1], high[0] ); } else { box = QgsRectangle( low[0], low[1], high[0], high[1] ); } coverage->boundingBoxes.insert( authid, box ); QgsDebugMsg( "crs: " + crs.authid() + ' ' + crs.description() + QString( " axisInverted = %1" ).arg( crs.hasAxisInverted() ) ); QgsDebugMsg( "BoundingBox: " + authid + " : " + box.toString() ); } } QgsDebugMsg( QStringLiteral( "width = %1 height = %2" ).arg( coverage->width ).arg( coverage->height ) ); // Each georectified coverage should have GridCRS QDomElement gridCRSElement = domElement( docElem, QStringLiteral( "CoverageDescription.Domain.SpatialDomain.GridCRS" ) ); if ( !gridCRSElement.isNull() ) { QString crsUrn = firstChildText( gridCRSElement, QStringLiteral( "GridBaseCRS" ) ); coverage->nativeCrs = crsUrnToAuthId( crsUrn ); QgsDebugMsg( "nativeCrs = " + coverage->nativeCrs ); // TODO: consider getting coverage size from GridOffsets (resolution) // if urn:ogc:def:crs:OGC::imageCRS BoundingBox was not found } coverage->times = domElementsTexts( docElem, QStringLiteral( "CoverageDescription.Domain.TemporalDomain.timePosition" ) ); QList<QDomElement> timePeriodElements = domElements( docElem, QStringLiteral( "CoverageDescription.Domain.TemporalDomain.timePeriod" ) ); QgsDebugMsg( QStringLiteral( "%1 timePeriod found" ).arg( timePeriodElements.size() ) ); const auto constTimePeriodElements = timePeriodElements; for ( const QDomElement &el : constTimePeriodElements ) { QString beginPosition = domElementText( el, QStringLiteral( "beginTime" ) ); QString endPosition = domElementText( el, QStringLiteral( "endTime" ) ); QString timeResolution = domElementText( el, QStringLiteral( "timeResolution" ) ); // Format used in request QString time = beginPosition + '/' + endPosition; if ( !timeResolution.isEmpty() ) { time += '/' + timeResolution; } coverage->times << time; } // NULL / no data values // TODO: handle multiple fields / ranges (?) Q_FOREACH ( const QString &text, domElementsTexts( docElem, "CoverageDescription.Range.Field.NullValue" ) ) { bool ok; double val = text.toDouble( &ok ); if ( ok ) { coverage->nullValues.append( val ); } } QStringList formats = domElementsTexts( docElem, QStringLiteral( "CoverageDescription.SupportedFormat" ) ); // There could be formats from GetCapabilities if ( !formats.isEmpty() ) { coverage->supportedFormat = formats; } QStringList crss = domElementsTexts( docElem, QStringLiteral( "CoverageDescription.SupportedCRS" ) ); QSet<QString> authids; // Set, in case one CRS is in more formats (URN, non URN) const auto constCrss = crss; for ( const QString &crs : constCrss ) { authids.insert( crsUrnToAuthId( crs ) ); } if ( !authids.isEmpty() ) { coverage->supportedCrs = authids.toList(); } coverage->described = true; return true; }
bool QgsWcsCapabilities::parseDescribeCoverageDom10( QByteArray const &xml, QgsWcsCoverageSummary *coverage ) { QgsDebugMsg( "coverage->identifier = " + coverage->identifier ); if ( ! convertToDom( xml ) ) return false; QDomElement docElem = mCapabilitiesDom.documentElement(); QgsDebugMsg( "testing tagName " + docElem.tagName() ); QString tagName = stripNS( docElem.tagName() ); if ( tagName != QLatin1String( "CoverageDescription" ) ) { mErrorTitle = tr( "Dom Exception" ); mErrorFormat = QStringLiteral( "text/plain" ); mError = tr( "Could not get WCS capabilities in the expected format (DTD): no %1 found.\nThis might be due to an incorrect WCS Server URL.\nTag: %3\nResponse was:\n%4" ) .arg( QStringLiteral( "CoverageDescription" ), docElem.tagName(), QString( xml ) ); QgsLogger::debug( "Dom Exception: " + mError ); return false; } QDomElement coverageOfferingElement = firstChild( docElem, QStringLiteral( "CoverageOffering" ) ); if ( coverageOfferingElement.isNull() ) return false; QDomElement supportedCRSsElement = firstChild( coverageOfferingElement, QStringLiteral( "supportedCRSs" ) ); // requestResponseCRSs and requestCRSs + responseCRSs are alternatives // we try to parse one or the other QStringList crsList; crsList = domElementsTexts( coverageOfferingElement, QStringLiteral( "supportedCRSs.requestResponseCRSs" ) ); if ( crsList.isEmpty() ) { crsList = domElementsTexts( coverageOfferingElement, QStringLiteral( "supportedCRSs.requestCRSs" ) ); crsList << domElementsTexts( coverageOfferingElement, QStringLiteral( "supportedCRSs.responseCRSs" ) ); } // exclude invalid CRSs from the lists for ( const QString &crsid : qgis::as_const( crsList ) ) { if ( QgsCoordinateReferenceSystem::fromOgcWmsCrs( crsid ).isValid() ) { coverage->supportedCrs << crsid; } } // TODO: requestCRSs, responseCRSs - must be then implemented also in provider //QgsDebugMsg( "supportedCrs = " + coverage->supportedCrs.join( "," ) ); coverage->nativeCrs = domElementText( coverageOfferingElement, QStringLiteral( "supportedCRSs.nativeCRSs" ) ); // may be GTiff, GeoTIFF, TIFF, GIF, .... coverage->supportedFormat = domElementsTexts( coverageOfferingElement, QStringLiteral( "supportedFormats.formats" ) ); QgsDebugMsg( "supportedFormat = " + coverage->supportedFormat.join( "," ) ); // spatialDomain and Grid/RectifiedGrid are optional according to specificationi. // If missing, we cannot get native resolution and size. QDomElement gridElement = domElement( coverageOfferingElement, QStringLiteral( "domainSet.spatialDomain.RectifiedGrid" ) ); if ( gridElement.isNull() ) { // Grid has also GridEnvelope from which we can get coverage size but it does not gridElement = domElement( coverageOfferingElement, QStringLiteral( "domainSet.spatialDomain.Grid" ) ); } // If supportedCRSs.nativeCRSs is not defined we try to get it from RectifiedGrid if ( coverage->nativeCrs.isEmpty() ) { QString crs = gridElement.attribute( QStringLiteral( "srsName" ) ); if ( QgsCoordinateReferenceSystem::fromOgcWmsCrs( crs ).isValid() ) { coverage->nativeCrs = crs; } } if ( !gridElement.isNull() ) { QList<int> low = parseInts( domElementText( gridElement, QStringLiteral( "limits.GridEnvelope.low" ) ) ); QList<int> high = parseInts( domElementText( gridElement, QStringLiteral( "limits.GridEnvelope.high" ) ) ); if ( low.size() == 2 && high.size() == 2 ) { // low/high are indexes in grid -> size is height - low + 1 double width = high[0] - low[0] + 1; double height = high[1] - low[1] + 1; if ( width > 0 && height > 0 ) { coverage->width = width; coverage->height = height; coverage->hasSize = true; } } // RectifiedGrid has also gml:origin which we don't need I think (attention however // it should contain gml:Point but mapserver 6.0.3 / WCS 1.0.0 is using gml:pos instead) // RectifiedGrid also contains 2 gml:offsetVector which could be used to get resolution // but it should be sufficient to calc resolution from size // TODO: check if coverage is rotated, in that case probably treat as without size // or recalc resolution from rotated grid to base CRS } QList<QDomElement> envelopeElements = domElements( coverageOfferingElement, QStringLiteral( "domainSet.spatialDomain.Envelope" ) ); QgsDebugMsg( QStringLiteral( "%1 envelopeElements found" ).arg( envelopeElements.size() ) ); const auto constEnvelopeElements = envelopeElements; for ( const QDomElement &el : constEnvelopeElements ) { QString srsName = el.attribute( QStringLiteral( "srsName" ) ); QList<QDomElement> posElements = domElements( el, QStringLiteral( "pos" ) ); if ( posElements.size() != 2 ) { QgsDebugMsg( QStringLiteral( "Wrong number of pos elements" ) ); continue; } QList<double> low = parseDoubles( posElements.value( 0 ).text() ); QList<double> high = parseDoubles( posElements.value( 1 ).text() ); if ( low.size() == 2 && high.size() == 2 ) { QgsRectangle box( low[0], low[1], high[0], high[1] ); coverage->boundingBoxes.insert( srsName, box ); QgsDebugMsg( "Envelope: " + srsName + " : " + box.toString() ); } } coverage->times = domElementsTexts( coverageOfferingElement, QStringLiteral( "domainSet.temporalDomain.timePosition" ) ); QList<QDomElement> timePeriodElements = domElements( coverageOfferingElement, QStringLiteral( "domainSet.temporalDomain.timePeriod" ) ); QgsDebugMsg( QStringLiteral( "%1 timePeriod found" ).arg( timePeriodElements.size() ) ); const auto constTimePeriodElements = timePeriodElements; for ( const QDomElement &el : constTimePeriodElements ) { QString beginPosition = domElementText( el, QStringLiteral( "beginPosition" ) ); QString endPosition = domElementText( el, QStringLiteral( "endPosition" ) ); QString timeResolution = domElementText( el, QStringLiteral( "timeResolution" ) ); // Format used in request QString time = beginPosition + '/' + endPosition; if ( !timeResolution.isEmpty() ) { time += '/' + timeResolution; } coverage->times << time; } // Find native bounding box if ( !coverage->nativeCrs.isEmpty() ) { const auto boundingBoxes = coverage->boundingBoxes; for ( auto it = boundingBoxes.constBegin(); it != boundingBoxes.constEnd(); ++it ) { if ( it.key() == coverage->nativeCrs ) { coverage->nativeBoundingBox = it.value(); } } } // NULL / no data values // TODO: handle multiple range sets Q_FOREACH ( const QString &text, domElementsTexts( coverageOfferingElement, "rangeSet.RangeSet.nullValue.singleValue" ) ) { bool ok; double val = text.toDouble( &ok ); if ( ok ) { coverage->nullValues.append( val ); } } coverage->described = true; return true; }
void QgsWcsCapabilities::parseCoverageSummary( QDomElement const &e, QgsWcsCoverageSummary &coverageSummary, QgsWcsCoverageSummary *parent ) { coverageSummary.orderId = ++mCoverageCount; coverageSummary.identifier = firstChildText( e, QStringLiteral( "Identifier" ) ); coverageSummary.title = firstChildText( e, QStringLiteral( "Title" ) ); coverageSummary.abstract = firstChildText( e, QStringLiteral( "Abstract" ) ); QDomNode n1 = e.firstChild(); while ( !n1.isNull() ) { QDomElement el = n1.toElement(); if ( !el.isNull() ) { QString tagName = stripNS( el.tagName() ); QgsDebugMsg( tagName + " : " + el.text() ); if ( tagName == QLatin1String( "SupportedFormat" ) ) { // image/tiff, ... // Formats may be here (UMN Mapserver) or may not (GeoServer) coverageSummary.supportedFormat << el.text(); } else if ( tagName == QLatin1String( "SupportedCRS" ) ) { // TODO: SupportedCRS may be URL referencing a document coverageSummary.supportedCrs << crsUrnToAuthId( el.text() ); } else if ( tagName == QLatin1String( "WGS84BoundingBox" ) ) { QList<double> low = parseDoubles( domElementText( el, QStringLiteral( "LowerCorner" ) ) ); QList<double> high = parseDoubles( domElementText( el, QStringLiteral( "UpperCorner" ) ) ); if ( low.size() == 2 && high.size() == 2 ) { coverageSummary.wgs84BoundingBox = QgsRectangle( low[0], low[1], high[0], high[1] ); } } } n1 = n1.nextSibling(); } //QgsDebugMsg( "supportedFormat = " + coverageSummary.supportedFormat.join( "," ) ); // We collected params to be inherited, do children n1 = e.firstChild(); while ( !n1.isNull() ) { QDomElement el = n1.toElement(); if ( !el.isNull() ) { QString tagName = stripNS( el.tagName() ); if ( tagName == QLatin1String( "CoverageSummary" ) ) { QgsDebugMsg( QStringLiteral( " Nested coverage." ) ); QgsWcsCoverageSummary subCoverageSummary; initCoverageSummary( subCoverageSummary ); // Inherit subCoverageSummary.supportedCrs = coverageSummary.supportedCrs; subCoverageSummary.supportedFormat = coverageSummary.supportedFormat; parseCoverageSummary( el, subCoverageSummary, &coverageSummary ); subCoverageSummary.valid = true; coverageSummary.coverageSummary.push_back( subCoverageSummary ); } } n1 = n1.nextSibling(); } if ( parent && parent->orderId > 1 ) // ignore Contents to put them on top level { QgsDebugMsg( QStringLiteral( "coverage orderId = %1 identifier = %2 has parent %3" ).arg( coverageSummary.orderId ).arg( coverageSummary.identifier ).arg( parent->orderId ) ); mCoverageParents[ coverageSummary.orderId ] = parent->orderId; } if ( !coverageSummary.identifier.isEmpty() ) { QgsDebugMsg( "add coverage " + coverageSummary.identifier + " to supported" ); mCoveragesSupported.push_back( coverageSummary ); } if ( !coverageSummary.coverageSummary.empty() ) { mCoverageParentIdentifiers[ coverageSummary.orderId ] = QStringList() << coverageSummary.identifier << coverageSummary.title << coverageSummary.abstract; } QgsDebugMsg( QStringLiteral( "coverage orderId = %1 identifier = %2" ).arg( coverageSummary.orderId ).arg( coverageSummary.identifier ) ); }