QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgsgml.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgml.cpp
3 ---------------------
4 begin : February 2013
5 copyright : (C) 2013 by Radim Blazek
6 email : radim dot blazek at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15#include "qgsgml.h"
16#include "qgsauthmanager.h"
17#include "qgsrectangle.h"
19#include "qgsgeometry.h"
20#include "qgslogger.h"
21#include "qgsmessagelog.h"
23#include "qgswkbptr.h"
24#include "qgsogcutils.h"
25#include "qgsogrutils.h"
26#include "qgsapplication.h"
27#include <QBuffer>
28#include <QList>
29#include <QNetworkRequest>
30#include <QNetworkReply>
31#include <QProgressDialog>
32#include <QSet>
33#include <QSettings>
34#include <QUrl>
35#include <QTextCodec>
36#include <QRegularExpression>
37
38#include "ogr_api.h"
39
40#include <limits>
41
42static const char NS_SEPARATOR = '?';
43static const char *GML_NAMESPACE = "http://www.opengis.net/gml";
44static const char *GML32_NAMESPACE = "http://www.opengis.net/gml/3.2";
45
47 const QString &typeName,
48 const QString &geometryAttribute,
49 const QgsFields &fields )
50 : mParser( typeName, geometryAttribute, fields )
51 , mTypeName( typeName )
52 , mFinished( false )
53{
54 const int index = mTypeName.indexOf( ':' );
55 if ( index != -1 && index < mTypeName.length() )
56 {
57 mTypeName = mTypeName.mid( index + 1 );
58 }
59}
60
61int QgsGml::getFeatures( const QString &uri, Qgis::WkbType *wkbType, QgsRectangle *extent, const QString &userName, const QString &password, const QString &authcfg )
62{
63 //start with empty extent
64 mExtent.setMinimal();
65
66 QNetworkRequest request( uri );
67 QgsSetRequestInitiatorClass( request, QStringLiteral( "QgsGml" ) );
68
69 if ( !authcfg.isEmpty() )
70 {
71 if ( !QgsApplication::authManager()->updateNetworkRequest( request, authcfg ) )
72 {
74 tr( "GML Getfeature network request update failed for authcfg %1" ).arg( authcfg ),
75 tr( "Network" ),
76 Qgis::MessageLevel::Critical
77 );
78 return 1;
79 }
80 }
81 else if ( !userName.isNull() || !password.isNull() )
82 {
83 request.setRawHeader( "Authorization", "Basic " + QStringLiteral( "%1:%2" ).arg( userName, password ).toLatin1().toBase64() );
84 }
85 QNetworkReply *reply = QgsNetworkAccessManager::instance()->get( request );
86
87 if ( !authcfg.isEmpty() )
88 {
89 if ( !QgsApplication::authManager()->updateNetworkReply( reply, authcfg ) )
90 {
91 reply->deleteLater();
93 tr( "GML Getfeature network reply update failed for authcfg %1" ).arg( authcfg ),
94 tr( "Network" ),
95 Qgis::MessageLevel::Critical
96 );
97 return 1;
98 }
99 }
100
101 connect( reply, &QNetworkReply::finished, this, &QgsGml::setFinished );
102 connect( reply, &QNetworkReply::downloadProgress, this, &QgsGml::handleProgressEvent );
103
104 //find out if there is a QGIS main window. If yes, display a progress dialog
105 QProgressDialog *progressDialog = nullptr;
106 QWidget *mainWindow = nullptr;
107 const QWidgetList topLevelWidgets = qApp->topLevelWidgets();
108 for ( QWidgetList::const_iterator it = topLevelWidgets.constBegin(); it != topLevelWidgets.constEnd(); ++it )
109 {
110 if ( ( *it )->objectName() == QLatin1String( "QgisApp" ) )
111 {
112 mainWindow = *it;
113 break;
114 }
115 }
116 if ( mainWindow )
117 {
118 progressDialog = new QProgressDialog( tr( "Loading GML data\n%1" ).arg( mTypeName ), tr( "Abort" ), 0, 0, mainWindow );
119 progressDialog->setWindowModality( Qt::ApplicationModal );
120 connect( this, &QgsGml::dataReadProgress, progressDialog, &QProgressDialog::setValue );
121 connect( this, &QgsGml::totalStepsUpdate, progressDialog, &QProgressDialog::setMaximum );
122 connect( progressDialog, &QProgressDialog::canceled, this, &QgsGml::setFinished );
123 progressDialog->show();
124 }
125
126 int atEnd = 0;
127 while ( !atEnd )
128 {
129 if ( mFinished )
130 {
131 atEnd = 1;
132 }
133 const QByteArray readData = reply->readAll();
134 if ( !readData.isEmpty() )
135 {
136 QString errorMsg;
137 if ( !mParser.processData( readData, atEnd, errorMsg ) )
138 QgsMessageLog::logMessage( errorMsg, QObject::tr( "WFS" ) );
139
140 }
141 QCoreApplication::processEvents();
142 }
143
144 fillMapsFromParser();
145
146 const QNetworkReply::NetworkError replyError = reply->error();
147 const QString replyErrorString = reply->errorString();
148
149 delete reply;
150 delete progressDialog;
151
152 if ( replyError )
153 {
155 tr( "GML Getfeature network request failed with error: %1" ).arg( replyErrorString ),
156 tr( "Network" ),
157 Qgis::MessageLevel::Critical
158 );
159 return 1;
160 }
161
162 *wkbType = mParser.wkbType();
163
164 if ( *wkbType != Qgis::WkbType::Unknown )
165 {
166 if ( mExtent.isEmpty() )
167 {
168 //reading of bbox from the server failed, so we calculate it less efficiently by evaluating the features
169 calculateExtentFromFeatures();
170 }
171 }
172
173 if ( extent )
174 *extent = mExtent;
175
176 return 0;
177}
178
179int QgsGml::getFeatures( const QByteArray &data, Qgis::WkbType *wkbType, QgsRectangle *extent )
180{
181 mExtent.setMinimal();
182
183 QString errorMsg;
184 if ( !mParser.processData( data, true /* atEnd */, errorMsg ) )
185 QgsMessageLog::logMessage( errorMsg, QObject::tr( "WFS" ) );
186
187 fillMapsFromParser();
188
189 *wkbType = mParser.wkbType();
190
191 if ( extent )
192 *extent = mExtent;
193
194 return 0;
195}
196
197void QgsGml::fillMapsFromParser()
198{
199 const QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> features = mParser.getAndStealReadyFeatures();
200 const auto constFeatures = features;
201 for ( const QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair &featPair : constFeatures )
202 {
203 QgsFeature *feat = featPair.first;
204 const QString &gmlId = featPair.second;
205 mFeatures.insert( feat->id(), feat );
206 if ( !gmlId.isEmpty() )
207 {
208 mIdMap.insert( feat->id(), gmlId );
209 }
210 }
211}
212
213void QgsGml::setFinished()
214{
215 mFinished = true;
216}
217
218void QgsGml::handleProgressEvent( qint64 progress, qint64 totalSteps )
219{
220 if ( totalSteps < 0 )
221 {
222 totalSteps = 0;
223 progress = 0;
224 }
225 emit totalStepsUpdate( totalSteps );
226 emit dataReadProgress( progress );
227 emit dataProgressAndSteps( progress, totalSteps );
228}
229
230void QgsGml::calculateExtentFromFeatures()
231{
232 if ( mFeatures.empty() )
233 {
234 return;
235 }
236
237 QgsFeature *currentFeature = nullptr;
238 QgsGeometry currentGeometry;
239 bool bboxInitialized = false; //gets true once bbox has been set to the first geometry
240
241 for ( int i = 0; i < mFeatures.size(); ++i )
242 {
243 currentFeature = mFeatures[i];
244 if ( !currentFeature )
245 {
246 continue;
247 }
248 currentGeometry = currentFeature->geometry();
249 if ( !currentGeometry.isNull() )
250 {
251 if ( !bboxInitialized )
252 {
253 mExtent = currentGeometry.boundingBox();
254 bboxInitialized = true;
255 }
256 else
257 {
258 mExtent.combineExtentWith( currentGeometry.boundingBox() );
259 }
260 }
261 }
262}
263
265{
267 if ( mParser.getEPSGCode() != 0 )
268 {
269 crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( mParser.getEPSGCode() ) );
270 }
271 return crs;
272}
273
274
275
276
277
279 const QString &geometryAttribute,
280 const QgsFields &fields,
281 AxisOrientationLogic axisOrientationLogic,
282 bool invertAxisOrientation )
283 : mTypeName( typeName )
284 , mTypeNameBA( mTypeName.toUtf8() )
285 , mTypeNamePtr( mTypeNameBA.constData() )
286 , mTypeNameUTF8Len( strlen( mTypeNamePtr ) )
287 , mWkbType( Qgis::WkbType::Unknown )
288 , mGeometryAttribute( geometryAttribute )
289 , mGeometryAttributeBA( geometryAttribute.toUtf8() )
290 , mGeometryAttributePtr( mGeometryAttributeBA.constData() )
291 , mGeometryAttributeUTF8Len( strlen( mGeometryAttributePtr ) )
292 , mFields( fields )
293 , mIsException( false )
294 , mTruncatedResponse( false )
295 , mParseDepth( 0 )
296 , mFeatureTupleDepth( 0 )
297 , mFeatureCount( 0 )
298 , mCurrentWKB( nullptr, 0 )
299 , mBoundedByNullFound( false )
300 , mDimension( 0 )
301 , mCoorMode( Coordinate )
302 , mEpsg( 0 )
303 , mAxisOrientationLogic( axisOrientationLogic )
304 , mInvertAxisOrientationRequest( invertAxisOrientation )
305 , mInvertAxisOrientation( invertAxisOrientation )
306 , mNumberReturned( -1 )
307 , mNumberMatched( -1 )
308 , mFoundUnhandledGeometryElement( false )
309{
310 mThematicAttributes.clear();
311 for ( int i = 0; i < fields.size(); i++ )
312 {
313 mThematicAttributes.insert( fields.at( i ).name(), qMakePair( i, fields.at( i ) ) );
314 }
315
316 mEndian = QgsApplication::endian();
317
318 const int index = mTypeName.indexOf( ':' );
319 if ( index != -1 && index < mTypeName.length() )
320 {
321 mTypeName = mTypeName.mid( index + 1 );
322 mTypeNameBA = mTypeName.toUtf8();
323 mTypeNamePtr = mTypeNameBA.constData();
324 mTypeNameUTF8Len = strlen( mTypeNamePtr );
325 }
326
327 createParser();
328}
329
330static QString stripNS( const QString &string )
331{
332 const int index = string.indexOf( ':' );
333 if ( index != -1 && index < string.length() )
334 {
335 return string.mid( index + 1 );
336 }
337 return string;
338}
339
340QgsGmlStreamingParser::QgsGmlStreamingParser( const QList<LayerProperties> &layerProperties,
341 const QgsFields &fields,
342 const QMap< QString, QPair<QString, QString> > &mapFieldNameToSrcLayerNameFieldName,
343 AxisOrientationLogic axisOrientationLogic,
344 bool invertAxisOrientation )
345 : mLayerProperties( layerProperties )
346 , mTypeNameUTF8Len( 0 )
347 , mWkbType( Qgis::WkbType::Unknown )
348 , mGeometryAttributeUTF8Len( 0 )
349 , mFields( fields )
350 , mIsException( false )
351 , mTruncatedResponse( false )
352 , mParseDepth( 0 )
353 , mFeatureTupleDepth( 0 )
354 , mFeatureCount( 0 )
355 , mCurrentWKB( nullptr, 0 )
356 , mBoundedByNullFound( false )
357 , mDimension( 0 )
358 , mCoorMode( Coordinate )
359 , mEpsg( 0 )
360 , mAxisOrientationLogic( axisOrientationLogic )
361 , mInvertAxisOrientationRequest( invertAxisOrientation )
362 , mInvertAxisOrientation( invertAxisOrientation )
363 , mNumberReturned( -1 )
364 , mNumberMatched( -1 )
365 , mFoundUnhandledGeometryElement( false )
366{
367 mThematicAttributes.clear();
368 for ( int i = 0; i < fields.size(); i++ )
369 {
370 const QMap< QString, QPair<QString, QString> >::const_iterator att_it = mapFieldNameToSrcLayerNameFieldName.constFind( fields.at( i ).name() );
371 if ( att_it != mapFieldNameToSrcLayerNameFieldName.constEnd() )
372 {
373 if ( mLayerProperties.size() == 1 )
374 mThematicAttributes.insert( att_it.value().second, qMakePair( i, fields.at( i ) ) );
375 else
376 mThematicAttributes.insert( stripNS( att_it.value().first ) + "|" + att_it.value().second, qMakePair( i, fields.at( i ) ) );
377 }
378 }
379 bool alreadyFoundGeometry = false;
380 for ( int i = 0; i < mLayerProperties.size(); i++ )
381 {
382 // We only support one geometry field per feature
383 if ( !mLayerProperties[i].mGeometryAttribute.isEmpty() )
384 {
385 if ( alreadyFoundGeometry )
386 {
387 QgsDebugMsgLevel( QStringLiteral( "Will ignore geometry field %1 from typename %2" ).
388 arg( mLayerProperties[i].mGeometryAttribute, mLayerProperties[i].mName ), 2 );
389 mLayerProperties[i].mGeometryAttribute.clear();
390 }
391 alreadyFoundGeometry = true;
392 }
393 mMapTypeNameToProperties.insert( stripNS( mLayerProperties[i].mName ), mLayerProperties[i] );
394 }
395
396 if ( mLayerProperties.size() == 1 )
397 {
398 mTypeName = mLayerProperties[0].mName;
399 mGeometryAttribute = mLayerProperties[0].mGeometryAttribute;
400 mGeometryAttributeBA = mGeometryAttribute.toUtf8();
401 mGeometryAttributePtr = mGeometryAttributeBA.constData();
402 mGeometryAttributeUTF8Len = strlen( mGeometryAttributePtr );
403 const int index = mTypeName.indexOf( ':' );
404 if ( index != -1 && index < mTypeName.length() )
405 {
406 mTypeName = mTypeName.mid( index + 1 );
407 }
408 mTypeNameBA = mTypeName.toUtf8();
409 mTypeNamePtr = mTypeNameBA.constData();
410 mTypeNameUTF8Len = strlen( mTypeNamePtr );
411 }
412
413 mEndian = QgsApplication::endian();
414
415 createParser();
416}
417
418
420{
421 XML_ParserFree( mParser );
422
423 // Normally a sane user of this class should have consumed everything...
424 const auto constMFeatureList = mFeatureList;
425 for ( const QgsGmlFeaturePtrGmlIdPair &featPair : constMFeatureList )
426 {
427 delete featPair.first;
428 }
429
430 delete mCurrentFeature;
431}
432
433bool QgsGmlStreamingParser::processData( const QByteArray &data, bool atEnd )
434{
435 QString errorMsg;
436 if ( !processData( data, atEnd, errorMsg ) )
437 {
438 QgsMessageLog::logMessage( errorMsg, QObject::tr( "WFS" ) );
439 return false;
440 }
441 return true;
442}
443
444bool QgsGmlStreamingParser::processData( const QByteArray &pdata, bool atEnd, QString &errorMsg )
445{
446 QByteArray data = pdata;
447
448 if ( mCodec )
449 {
450 // convert data to UTF-8
451 QString strData = mCodec->toUnicode( pdata );
452 data = strData.toUtf8();
453 }
454
455 if ( XML_Parse( mParser, data, data.size(), atEnd ) == XML_STATUS_ERROR )
456 {
457 const XML_Error errorCode = XML_GetErrorCode( mParser );
458 if ( !mCodec && errorCode == XML_ERROR_UNKNOWN_ENCODING )
459 {
460 // Specified encoding is unknown, Expat only accepts UTF-8, UTF-16, ISO-8859-1
461 // Try to get encoding string and convert data to utf-8
462 QRegularExpression reEncoding( QStringLiteral( "<?xml.*encoding=['\"]([^'\"]*)['\"].*?>" ),
463 QRegularExpression::CaseInsensitiveOption );
464 QRegularExpressionMatch match = reEncoding.match( pdata );
465 const QString encoding = match.hasMatch() ? match.captured( 1 ) : QString();
466 mCodec = !encoding.isEmpty() ? QTextCodec::codecForName( encoding.toLatin1() ) : nullptr;
467 if ( mCodec )
468 {
469 // recreate parser with UTF-8 encoding
470 XML_ParserFree( mParser );
471 mParser = nullptr;
472 createParser( QByteArrayLiteral( "UTF-8" ) );
473
474 return processData( data, atEnd, errorMsg );
475 }
476 }
477
478 errorMsg = QObject::tr( "Error: %1 on line %2, column %3" )
479 .arg( XML_ErrorString( errorCode ) )
480 .arg( XML_GetCurrentLineNumber( mParser ) )
481 .arg( XML_GetCurrentColumnNumber( mParser ) );
482
483 return false;
484 }
485
486 return true;
487}
488
489QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> QgsGmlStreamingParser::getAndStealReadyFeatures()
490{
491 QVector<QgsGmlFeaturePtrGmlIdPair> ret = mFeatureList;
492 mFeatureList.clear();
493 return ret;
494}
495
496#define LOCALNAME_EQUALS(string_constant) \
497 ( localNameLen == static_cast<int>(strlen( string_constant )) && memcmp(pszLocalName, string_constant, localNameLen) == 0 )
498
499void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **attr )
500{
501 const int elLen = static_cast<int>( strlen( el ) );
502 const char *pszSep = strchr( el, NS_SEPARATOR );
503 const char *pszLocalName = ( pszSep ) ? pszSep + 1 : el;
504 const int nsLen = ( pszSep ) ? ( int )( pszSep - el ) : 0;
505 const int localNameLen = ( pszSep ) ? ( int )( elLen - nsLen ) - 1 : elLen;
506 const ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() );
507 int elDimension = 0;
508
509 // Figure out if the GML namespace is GML_NAMESPACE or GML32_NAMESPACE
510 if ( !mGMLNameSpaceURIPtr && pszSep )
511 {
512 if ( nsLen == static_cast<int>( strlen( GML_NAMESPACE ) ) && memcmp( el, GML_NAMESPACE, nsLen ) == 0 )
513 {
514 mGMLNameSpaceURI = GML_NAMESPACE;
515 mGMLNameSpaceURIPtr = GML_NAMESPACE;
516 }
517 else if ( nsLen == static_cast<int>( strlen( GML32_NAMESPACE ) ) && memcmp( el, GML32_NAMESPACE, nsLen ) == 0 )
518 {
519 mGMLNameSpaceURI = GML32_NAMESPACE;
520 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
521 }
522 }
523
524 const bool isGMLNS = ( nsLen == mGMLNameSpaceURI.size() && mGMLNameSpaceURIPtr && memcmp( el, mGMLNameSpaceURIPtr, nsLen ) == 0 );
525 bool isGeom = false;
526
527 if ( parseMode == Geometry || parseMode == Coordinate || parseMode == PosList ||
528 parseMode == MultiPoint || parseMode == MultiLine || parseMode == MultiPolygon )
529 {
530 mGeometryString.append( "<", 1 );
531 mGeometryString.append( pszLocalName, localNameLen );
532 mGeometryString.append( " ", 1 );
533 for ( const XML_Char **attrIter = attr; attrIter && *attrIter; attrIter += 2 )
534 {
535 const size_t nAttrLen = strlen( attrIter[0] );
536 const size_t GML32_NAMESPACE_LEN = strlen( GML32_NAMESPACE );
537 const size_t GML_NAMESPACE_LEN = strlen( GML_NAMESPACE );
538 if ( nAttrLen > GML32_NAMESPACE_LEN &&
539 attrIter[0][GML32_NAMESPACE_LEN] == '?' &&
540 memcmp( attrIter[0], GML32_NAMESPACE, GML32_NAMESPACE_LEN ) == 0 )
541 {
542 mGeometryString.append( "gml:" );
543 mGeometryString.append( attrIter[0] + GML32_NAMESPACE_LEN + 1 );
544 }
545 else if ( nAttrLen > GML_NAMESPACE_LEN &&
546 attrIter[0][GML_NAMESPACE_LEN] == '?' &&
547 memcmp( attrIter[0], GML_NAMESPACE, GML_NAMESPACE_LEN ) == 0 )
548 {
549 mGeometryString.append( "gml:" );
550 mGeometryString.append( attrIter[0] + GML_NAMESPACE_LEN + 1 );
551 }
552 else
553 {
554 mGeometryString.append( attrIter[0] );
555 }
556 mGeometryString.append( "=\"", 2 );
557 mGeometryString.append( attrIter[1] );
558 mGeometryString.append( "\" ", 2 );
559
560 }
561 mGeometryString.append( ">", 1 );
562 }
563
564 if ( isGMLNS && LOCALNAME_EQUALS( "coordinates" ) )
565 {
566 mParseModeStack.push( Coordinate );
567 mCoorMode = QgsGmlStreamingParser::Coordinate;
568 mStringCash.clear();
569 mCoordinateSeparator = readAttribute( QStringLiteral( "cs" ), attr );
570 if ( mCoordinateSeparator.isEmpty() )
571 {
572 mCoordinateSeparator = ',';
573 }
574 mTupleSeparator = readAttribute( QStringLiteral( "ts" ), attr );
575 if ( mTupleSeparator.isEmpty() )
576 {
577 mTupleSeparator = ' ';
578 }
579 }
580 else if ( isGMLNS &&
581 ( LOCALNAME_EQUALS( "pos" ) || LOCALNAME_EQUALS( "posList" ) ) )
582 {
583 mParseModeStack.push( QgsGmlStreamingParser::PosList );
584 mCoorMode = QgsGmlStreamingParser::PosList;
585 mStringCash.clear();
586 if ( elDimension == 0 )
587 {
588 const QString srsDimension = readAttribute( QStringLiteral( "srsDimension" ), attr );
589 bool ok;
590 const int dimension = srsDimension.toInt( &ok );
591 if ( ok )
592 {
593 elDimension = dimension;
594 }
595 }
596 }
597 else if ( ( parseMode == Feature || parseMode == FeatureTuple ) &&
598 mCurrentFeature &&
599 localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) &&
600 memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
601 {
602 mParseModeStack.push( QgsGmlStreamingParser::Geometry );
603 mFoundUnhandledGeometryElement = false;
604 mGeometryString.clear();
605 }
606 //else if ( mParseModeStack.size() == 0 && elementName == mGMLNameSpaceURI + NS_SEPARATOR + "boundedBy" )
607 else if ( isGMLNS && LOCALNAME_EQUALS( "boundedBy" ) )
608 {
609 mParseModeStack.push( QgsGmlStreamingParser::BoundingBox );
610 mCurrentExtent = QgsRectangle();
611 mBoundedByNullFound = false;
612 }
613 else if ( parseMode == BoundingBox &&
614 isGMLNS && LOCALNAME_EQUALS( "null" ) )
615 {
616 mParseModeStack.push( QgsGmlStreamingParser::Null );
617 mBoundedByNullFound = true;
618 }
619 else if ( parseMode == BoundingBox &&
620 isGMLNS && LOCALNAME_EQUALS( "Envelope" ) )
621 {
622 isGeom = true;
623 mParseModeStack.push( QgsGmlStreamingParser::Envelope );
624 }
625 else if ( parseMode == Envelope &&
626 isGMLNS && LOCALNAME_EQUALS( "lowerCorner" ) )
627 {
628 mParseModeStack.push( QgsGmlStreamingParser::LowerCorner );
629 mStringCash.clear();
630 }
631 else if ( parseMode == Envelope &&
632 isGMLNS && LOCALNAME_EQUALS( "upperCorner" ) )
633 {
634 mParseModeStack.push( QgsGmlStreamingParser::UpperCorner );
635 mStringCash.clear();
636 }
637 else if ( parseMode == None && !mTypeNamePtr &&
638 LOCALNAME_EQUALS( "Tuple" ) )
639 {
640 Q_ASSERT( !mCurrentFeature );
641 mCurrentFeature = new QgsFeature( mFeatureCount );
642 mCurrentFeature->setFields( mFields ); // allow name-based attribute lookups
643 const QgsAttributes attributes( mThematicAttributes.size() ); //add empty attributes
644 mCurrentFeature->setAttributes( attributes );
645 mParseModeStack.push( QgsGmlStreamingParser::Tuple );
646 mCurrentFeatureId.clear();
647 }
648 else if ( parseMode == Tuple )
649 {
650 const QString currentTypename( QString::fromUtf8( pszLocalName, localNameLen ) );
651 const QMap< QString, LayerProperties >::const_iterator iter = mMapTypeNameToProperties.constFind( currentTypename );
652 if ( iter != mMapTypeNameToProperties.constEnd() )
653 {
654 mFeatureTupleDepth = mParseDepth;
655 mCurrentTypename = currentTypename;
656 mGeometryAttribute.clear();
657 if ( mCurrentWKB.size() == 0 )
658 {
659 mGeometryAttribute = iter.value().mGeometryAttribute;
660 }
661 mGeometryAttributeBA = mGeometryAttribute.toUtf8();
662 mGeometryAttributePtr = mGeometryAttributeBA.constData();
663 mGeometryAttributeUTF8Len = strlen( mGeometryAttributePtr );
664 mParseModeStack.push( QgsGmlStreamingParser::FeatureTuple );
665 QString id;
666 if ( mGMLNameSpaceURI.isEmpty() )
667 {
668 id = readAttribute( QString( GML_NAMESPACE ) + NS_SEPARATOR + "id", attr );
669 if ( !id.isEmpty() )
670 {
671 mGMLNameSpaceURI = GML_NAMESPACE;
672 mGMLNameSpaceURIPtr = GML_NAMESPACE;
673 }
674 else
675 {
676 id = readAttribute( QString( GML32_NAMESPACE ) + NS_SEPARATOR + "id", attr );
677 if ( !id.isEmpty() )
678 {
679 mGMLNameSpaceURI = GML32_NAMESPACE;
680 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
681 }
682 }
683 }
684 else
685 id = readAttribute( mGMLNameSpaceURI + NS_SEPARATOR + "id", attr );
686 if ( !mCurrentFeatureId.isEmpty() )
687 mCurrentFeatureId += '|';
688 mCurrentFeatureId += id;
689 }
690 }
691 else if ( parseMode == None &&
692 localNameLen == static_cast<int>( mTypeNameUTF8Len ) &&
693 memcmp( pszLocalName, mTypeNamePtr, mTypeNameUTF8Len ) == 0 )
694 {
695 Q_ASSERT( !mCurrentFeature );
696 mCurrentFeature = new QgsFeature( mFeatureCount );
697 mCurrentFeature->setFields( mFields ); // allow name-based attribute lookups
698 const QgsAttributes attributes( mThematicAttributes.size() ); //add empty attributes
699 mCurrentFeature->setAttributes( attributes );
700 mParseModeStack.push( QgsGmlStreamingParser::Feature );
701 mCurrentFeatureId = readAttribute( QStringLiteral( "fid" ), attr );
702 if ( mCurrentFeatureId.isEmpty() )
703 {
704 // Figure out if the GML namespace is GML_NAMESPACE or GML32_NAMESPACE
705 // (should happen only for the first features if there's no gml: element
706 // encountered before
707 if ( mGMLNameSpaceURI.isEmpty() )
708 {
709 mCurrentFeatureId = readAttribute( QString( GML_NAMESPACE ) + NS_SEPARATOR + "id", attr );
710 if ( !mCurrentFeatureId.isEmpty() )
711 {
712 mGMLNameSpaceURI = GML_NAMESPACE;
713 mGMLNameSpaceURIPtr = GML_NAMESPACE;
714 }
715 else
716 {
717 mCurrentFeatureId = readAttribute( QString( GML32_NAMESPACE ) + NS_SEPARATOR + "id", attr );
718 if ( !mCurrentFeatureId.isEmpty() )
719 {
720 mGMLNameSpaceURI = GML32_NAMESPACE;
721 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
722 }
723 }
724 }
725 else
726 mCurrentFeatureId = readAttribute( mGMLNameSpaceURI + NS_SEPARATOR + "id", attr );
727 }
728 }
729
730 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "Box" ) )
731 {
732 isGeom = true;
733 }
734 else if ( isGMLNS && LOCALNAME_EQUALS( "Point" ) )
735 {
736 isGeom = true;
737 }
738 else if ( isGMLNS && LOCALNAME_EQUALS( "LineString" ) )
739 {
740 isGeom = true;
741 }
742 else if ( isGMLNS &&
743 localNameLen == static_cast<int>( strlen( "Polygon" ) ) && memcmp( pszLocalName, "Polygon", localNameLen ) == 0 )
744 {
745 isGeom = true;
746 mCurrentWKBFragments.push_back( QList<QgsWkbPtr>() );
747 }
748 else if ( isGMLNS && LOCALNAME_EQUALS( "MultiPoint" ) )
749 {
750 isGeom = true;
751 mParseModeStack.push( QgsGmlStreamingParser::MultiPoint );
752 //we need one nested list for intermediate WKB
753 mCurrentWKBFragments.push_back( QList<QgsWkbPtr>() );
754 }
755 else if ( isGMLNS && ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
756 {
757 isGeom = true;
758 mParseModeStack.push( QgsGmlStreamingParser::MultiLine );
759 //we need one nested list for intermediate WKB
760 mCurrentWKBFragments.push_back( QList<QgsWkbPtr>() );
761 }
762 else if ( isGMLNS && ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
763 {
764 isGeom = true;
765 mParseModeStack.push( QgsGmlStreamingParser::MultiPolygon );
766 }
767 else if ( parseMode == FeatureTuple )
768 {
769 const QString localName( QString::fromUtf8( pszLocalName, localNameLen ) );
770 if ( mThematicAttributes.contains( mCurrentTypename + '|' + localName ) )
771 {
772 mParseModeStack.push( QgsGmlStreamingParser::AttributeTuple );
773 mAttributeName = mCurrentTypename + '|' + localName;
774 mStringCash.clear();
775 }
776 }
777 else if ( parseMode == Feature )
778 {
779 const QString localName( QString::fromUtf8( pszLocalName, localNameLen ) );
780 if ( mThematicAttributes.contains( localName ) )
781 {
782 mParseModeStack.push( QgsGmlStreamingParser::Attribute );
783 mAttributeName = localName;
784 mStringCash.clear();
785 }
786 else
787 {
788 // QGIS server (2.2) is using:
789 // <Attribute value="My description" name="desc"/>
790 if ( localName.compare( QLatin1String( "attribute" ), Qt::CaseInsensitive ) == 0 )
791 {
792 const QString name = readAttribute( QStringLiteral( "name" ), attr );
793 if ( mThematicAttributes.contains( name ) )
794 {
795 const QString value = readAttribute( QStringLiteral( "value" ), attr );
796 setAttribute( name, value );
797 }
798 }
799 }
800 }
801 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "FeatureCollection" ) )
802 {
803 QString numberReturned = readAttribute( QStringLiteral( "numberReturned" ), attr ); // WFS 2.0
804 if ( numberReturned.isEmpty() )
805 numberReturned = readAttribute( QStringLiteral( "numberOfFeatures" ), attr ); // WFS 1.1
806 bool conversionOk;
807 mNumberReturned = numberReturned.toInt( &conversionOk );
808 if ( !conversionOk )
809 mNumberReturned = -1;
810
811 const QString numberMatched = readAttribute( QStringLiteral( "numberMatched" ), attr ); // WFS 2.0
812 mNumberMatched = numberMatched.toInt( &conversionOk );
813 if ( !conversionOk ) // likely since numberMatched="unknown" is legal
814 mNumberMatched = -1;
815 }
816 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
817 {
818 mIsException = true;
819 mParseModeStack.push( QgsGmlStreamingParser::ExceptionReport );
820 }
821 else if ( mIsException && LOCALNAME_EQUALS( "ExceptionText" ) )
822 {
823 mStringCash.clear();
824 mParseModeStack.push( QgsGmlStreamingParser::ExceptionText );
825 }
826 else if ( mParseDepth == 1 && LOCALNAME_EQUALS( "truncatedResponse" ) )
827 {
828 // e.g: http://services.cuzk.cz/wfs/inspire-cp-wfs.asp?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cp:CadastralParcel
829 mTruncatedResponse = true;
830 }
831 else if ( !mGeometryString.empty() &&
832 !LOCALNAME_EQUALS( "exterior" ) &&
833 !LOCALNAME_EQUALS( "interior" ) &&
834 !LOCALNAME_EQUALS( "innerBoundaryIs" ) &&
835 !LOCALNAME_EQUALS( "outerBoundaryIs" ) &&
836 !LOCALNAME_EQUALS( "LinearRing" ) &&
837 !LOCALNAME_EQUALS( "pointMember" ) &&
838 !LOCALNAME_EQUALS( "curveMember" ) &&
839 !LOCALNAME_EQUALS( "lineStringMember" ) &&
840 !LOCALNAME_EQUALS( "polygonMember" ) &&
841 !LOCALNAME_EQUALS( "surfaceMember" ) &&
842 !LOCALNAME_EQUALS( "Curve" ) &&
843 !LOCALNAME_EQUALS( "segments" ) &&
844 !LOCALNAME_EQUALS( "LineStringSegment" ) )
845 {
846 //QgsDebugMsg( "Found unhandled geometry element " + QString::fromUtf8( pszLocalName, localNameLen ) );
847 mFoundUnhandledGeometryElement = true;
848 }
849
850 if ( !mGeometryString.empty() )
851 isGeom = true;
852
853 if ( elDimension == 0 && isGeom )
854 {
855 // srsDimension can also be set on the top geometry element
856 // e.g. https://data.linz.govt.nz/services;key=XXXXXXXX/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=data.linz.govt.nz:layer-524
857 const QString srsDimension = readAttribute( QStringLiteral( "srsDimension" ), attr );
858 bool ok;
859 const int dimension = srsDimension.toInt( &ok );
860 if ( ok )
861 {
862 elDimension = dimension;
863 }
864 }
865
866 if ( elDimension != 0 || mDimensionStack.isEmpty() )
867 {
868 mDimensionStack.push( elDimension );
869 }
870 else
871 {
872 mDimensionStack.push( mDimensionStack.back() );
873 }
874
875 if ( mEpsg == 0 && isGeom )
876 {
877 if ( readEpsgFromAttribute( mEpsg, attr ) != 0 )
878 {
879 QgsDebugMsg( QStringLiteral( "error, could not get epsg id" ) );
880 }
881 else
882 {
883 QgsDebugMsgLevel( QStringLiteral( "mEpsg = %1" ).arg( mEpsg ), 2 );
884 }
885 }
886
887 mParseDepth ++;
888}
889
890void QgsGmlStreamingParser::endElement( const XML_Char *el )
891{
892 mParseDepth --;
893
894 const int elLen = static_cast<int>( strlen( el ) );
895 const char *pszSep = strchr( el, NS_SEPARATOR );
896 const char *pszLocalName = ( pszSep ) ? pszSep + 1 : el;
897 const int nsLen = ( pszSep ) ? ( int )( pszSep - el ) : 0;
898 const int localNameLen = ( pszSep ) ? ( int )( elLen - nsLen ) - 1 : elLen;
899 const ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() );
900
901 const int lastDimension = mDimensionStack.isEmpty() ? 0 : mDimensionStack.pop();
902
903 const bool isGMLNS = ( nsLen == mGMLNameSpaceURI.size() && mGMLNameSpaceURIPtr && memcmp( el, mGMLNameSpaceURIPtr, nsLen ) == 0 );
904
905 if ( parseMode == Coordinate && isGMLNS && LOCALNAME_EQUALS( "coordinates" ) )
906 {
907 mParseModeStack.pop();
908 }
909 else if ( parseMode == PosList && isGMLNS &&
910 ( LOCALNAME_EQUALS( "pos" ) || LOCALNAME_EQUALS( "posList" ) ) )
911 {
912 mDimension = lastDimension;
913 mParseModeStack.pop();
914 }
915 else if ( parseMode == AttributeTuple &&
916 mCurrentTypename + '|' + QString::fromUtf8( pszLocalName, localNameLen ) == mAttributeName ) //add a thematic attribute to the feature
917 {
918 mParseModeStack.pop();
919
920 setAttribute( mAttributeName, mStringCash );
921 }
922 else if ( parseMode == Attribute && QString::fromUtf8( pszLocalName, localNameLen ) == mAttributeName ) //add a thematic attribute to the feature
923 {
924 mParseModeStack.pop();
925
926 setAttribute( mAttributeName, mStringCash );
927 }
928 else if ( parseMode == Geometry &&
929 localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) &&
930 memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
931 {
932 mParseModeStack.pop();
933 if ( mFoundUnhandledGeometryElement )
934 {
935 const gdal::ogr_geometry_unique_ptr hGeom( OGR_G_CreateFromGML( mGeometryString.c_str() ) );
936 //QgsDebugMsg( QStringLiteral("for OGR: %1 -> %2").arg(mGeometryString.c_str()).arg(hGeom != nullptr));
937 if ( hGeom )
938 {
939 const int wkbSize = OGR_G_WkbSize( hGeom.get() );
940 unsigned char *pabyBuffer = new unsigned char[ wkbSize ];
941 OGR_G_ExportToIsoWkb( hGeom.get(), wkbNDR, pabyBuffer );
942 QgsGeometry g;
943 g.fromWkb( pabyBuffer, wkbSize );
944 if ( mInvertAxisOrientation )
945 {
946 g.transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
947 }
948 Q_ASSERT( mCurrentFeature );
949 mCurrentFeature->setGeometry( g );
950 }
951 }
952 mGeometryString.clear();
953 }
954 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "boundedBy" ) )
955 {
956 //create bounding box from mStringCash
957 if ( mCurrentExtent.isNull() &&
958 !mBoundedByNullFound &&
959 !createBBoxFromCoordinateString( mCurrentExtent, mStringCash ) )
960 {
961 QgsDebugMsg( QStringLiteral( "creation of bounding box failed" ) );
962 }
963 if ( !mCurrentExtent.isNull() && mLayerExtent.isNull() &&
964 !mCurrentFeature && mFeatureCount == 0 )
965 {
966 mLayerExtent = mCurrentExtent;
967 mCurrentExtent = QgsRectangle();
968 }
969
970 mParseModeStack.pop();
971 }
972 else if ( parseMode == Null && isGMLNS && LOCALNAME_EQUALS( "null" ) )
973 {
974 mParseModeStack.pop();
975 }
976 else if ( parseMode == Envelope && isGMLNS && LOCALNAME_EQUALS( "Envelope" ) )
977 {
978 mParseModeStack.pop();
979 }
980 else if ( parseMode == LowerCorner && isGMLNS && LOCALNAME_EQUALS( "lowerCorner" ) )
981 {
982 QList<QgsPointXY> points;
983 pointsFromPosListString( points, mStringCash, 2 );
984 if ( points.size() == 1 )
985 {
986 mCurrentExtent.setXMinimum( points[0].x() );
987 mCurrentExtent.setYMinimum( points[0].y() );
988 }
989 mParseModeStack.pop();
990 }
991 else if ( parseMode == UpperCorner && isGMLNS && LOCALNAME_EQUALS( "upperCorner" ) )
992 {
993 QList<QgsPointXY> points;
994 pointsFromPosListString( points, mStringCash, 2 );
995 if ( points.size() == 1 )
996 {
997 mCurrentExtent.setXMaximum( points[0].x() );
998 mCurrentExtent.setYMaximum( points[0].y() );
999 }
1000 mParseModeStack.pop();
1001 }
1002 else if ( parseMode == FeatureTuple && mParseDepth == mFeatureTupleDepth )
1003 {
1004 mParseModeStack.pop();
1005 mFeatureTupleDepth = 0;
1006 }
1007 else if ( ( parseMode == Tuple && !mTypeNamePtr &&
1008 LOCALNAME_EQUALS( "Tuple" ) ) ||
1009 ( parseMode == Feature &&
1010 localNameLen == static_cast<int>( mTypeNameUTF8Len ) &&
1011 memcmp( pszLocalName, mTypeNamePtr, mTypeNameUTF8Len ) == 0 ) )
1012 {
1013 Q_ASSERT( mCurrentFeature );
1014 if ( !mCurrentFeature->hasGeometry() )
1015 {
1016 if ( mCurrentWKB.size() > 0 )
1017 {
1018 QgsGeometry g;
1019 g.fromWkb( mCurrentWKB, mCurrentWKB.size() );
1020 mCurrentFeature->setGeometry( g );
1021 mCurrentWKB = QgsWkbPtr( nullptr, 0 );
1022 }
1023 else if ( !mCurrentExtent.isEmpty() )
1024 {
1025 mCurrentFeature->setGeometry( QgsGeometry::fromRect( mCurrentExtent ) );
1026 }
1027 }
1028 mCurrentFeature->setValid( true );
1029
1030 mFeatureList.push_back( QgsGmlFeaturePtrGmlIdPair( mCurrentFeature, mCurrentFeatureId ) );
1031
1032 mCurrentFeature = nullptr;
1033 ++mFeatureCount;
1034 mParseModeStack.pop();
1035 }
1036 else if ( isGMLNS && LOCALNAME_EQUALS( "Point" ) )
1037 {
1038 QList<QgsPointXY> pointList;
1039 if ( pointsFromString( pointList, mStringCash ) != 0 )
1040 {
1041 //error
1042 }
1043
1044 if ( pointList.isEmpty() )
1045 return; // error
1046
1047 if ( parseMode == QgsGmlStreamingParser::Geometry )
1048 {
1049 //directly add WKB point to the feature
1050 if ( getPointWKB( mCurrentWKB, *( pointList.constBegin() ) ) != 0 )
1051 {
1052 //error
1053 }
1054
1055 if ( mWkbType != Qgis::WkbType::MultiPoint ) //keep multitype in case of geometry type mix
1056 {
1057 mWkbType = Qgis::WkbType::Point;
1058 }
1059 }
1060 else //multipoint, add WKB as fragment
1061 {
1062 QgsWkbPtr wkbPtr( nullptr, 0 );
1063 if ( getPointWKB( wkbPtr, *( pointList.constBegin() ) ) != 0 )
1064 {
1065 //error
1066 }
1067 if ( !mCurrentWKBFragments.isEmpty() )
1068 {
1069 mCurrentWKBFragments.last().push_back( wkbPtr );
1070 }
1071 else
1072 {
1073 QgsDebugMsg( QStringLiteral( "No wkb fragments" ) );
1074 delete [] wkbPtr;
1075 }
1076 }
1077 }
1078 else if ( isGMLNS && ( LOCALNAME_EQUALS( "LineString" ) || LOCALNAME_EQUALS( "LineStringSegment" ) ) )
1079 {
1080 //add WKB point to the feature
1081
1082 QList<QgsPointXY> pointList;
1083 if ( pointsFromString( pointList, mStringCash ) != 0 )
1084 {
1085 //error
1086 }
1087 if ( parseMode == QgsGmlStreamingParser::Geometry )
1088 {
1089 if ( getLineWKB( mCurrentWKB, pointList ) != 0 )
1090 {
1091 //error
1092 }
1093
1094 if ( mWkbType != Qgis::WkbType::MultiLineString )//keep multitype in case of geometry type mix
1095 {
1096 mWkbType = Qgis::WkbType::LineString;
1097 }
1098 }
1099 else //multiline, add WKB as fragment
1100 {
1101 QgsWkbPtr wkbPtr( nullptr, 0 );
1102 if ( getLineWKB( wkbPtr, pointList ) != 0 )
1103 {
1104 //error
1105 }
1106 if ( !mCurrentWKBFragments.isEmpty() )
1107 {
1108 mCurrentWKBFragments.last().push_back( wkbPtr );
1109 }
1110 else
1111 {
1112 QgsDebugMsg( QStringLiteral( "no wkb fragments" ) );
1113 delete [] wkbPtr;
1114 }
1115 }
1116 }
1117 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) &&
1118 isGMLNS && LOCALNAME_EQUALS( "LinearRing" ) )
1119 {
1120 QList<QgsPointXY> pointList;
1121 if ( pointsFromString( pointList, mStringCash ) != 0 )
1122 {
1123 //error
1124 }
1125
1126 QgsWkbPtr wkbPtr( nullptr, 0 );
1127 if ( getRingWKB( wkbPtr, pointList ) != 0 )
1128 {
1129 //error
1130 }
1131
1132 if ( !mCurrentWKBFragments.isEmpty() )
1133 {
1134 mCurrentWKBFragments.last().push_back( wkbPtr );
1135 }
1136 else
1137 {
1138 delete[] wkbPtr;
1139 QgsDebugMsg( QStringLiteral( "no wkb fragments" ) );
1140 }
1141 }
1142 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS &&
1143 LOCALNAME_EQUALS( "Polygon" ) )
1144 {
1145 if ( mWkbType != Qgis::WkbType::MultiPolygon )//keep multitype in case of geometry type mix
1146 {
1147 mWkbType = Qgis::WkbType::Polygon;
1148 }
1149
1150 if ( parseMode == Geometry )
1151 {
1152 createPolygonFromFragments();
1153 }
1154 }
1155 else if ( parseMode == MultiPoint && isGMLNS &&
1156 LOCALNAME_EQUALS( "MultiPoint" ) )
1157 {
1158 mWkbType = Qgis::WkbType::MultiPoint;
1159 mParseModeStack.pop();
1160 createMultiPointFromFragments();
1161 }
1162 else if ( parseMode == MultiLine && isGMLNS &&
1163 ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
1164 {
1166 mParseModeStack.pop();
1167 createMultiLineFromFragments();
1168 }
1169 else if ( parseMode == MultiPolygon && isGMLNS &&
1170 ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
1171 {
1172 mWkbType = Qgis::WkbType::MultiPolygon;
1173 mParseModeStack.pop();
1174 createMultiPolygonFromFragments();
1175 }
1176 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
1177 {
1178 mParseModeStack.pop();
1179 }
1180 else if ( parseMode == ExceptionText && LOCALNAME_EQUALS( "ExceptionText" ) )
1181 {
1182 mExceptionText = mStringCash;
1183 mParseModeStack.pop();
1184 }
1185
1186 if ( !mGeometryString.empty() )
1187 {
1188 mGeometryString.append( "</", 2 );
1189 mGeometryString.append( pszLocalName, localNameLen );
1190 mGeometryString.append( ">", 1 );
1191 }
1192
1193}
1194
1195void QgsGmlStreamingParser::characters( const XML_Char *chars, int len )
1196{
1197 //save chars in mStringCash attribute mode or coordinate mode
1198 if ( mParseModeStack.isEmpty() )
1199 {
1200 return;
1201 }
1202
1203 if ( !mGeometryString.empty() )
1204 {
1205 mGeometryString.append( chars, len );
1206 }
1207
1208 const QgsGmlStreamingParser::ParseMode parseMode = mParseModeStack.top();
1209 if ( parseMode == QgsGmlStreamingParser::Attribute ||
1210 parseMode == QgsGmlStreamingParser::AttributeTuple ||
1211 parseMode == QgsGmlStreamingParser::Coordinate ||
1212 parseMode == QgsGmlStreamingParser::PosList ||
1213 parseMode == QgsGmlStreamingParser::LowerCorner ||
1214 parseMode == QgsGmlStreamingParser::UpperCorner ||
1215 parseMode == QgsGmlStreamingParser::ExceptionText )
1216 {
1217 mStringCash.append( QString::fromUtf8( chars, len ) );
1218 }
1219}
1220
1221void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &value )
1222{
1223 //find index with attribute name
1224 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
1225 bool conversionOk = true;
1226 if ( att_it != mThematicAttributes.constEnd() )
1227 {
1228 QVariant var;
1229 switch ( att_it.value().second.type() )
1230 {
1231 case QVariant::Double:
1232 var = QVariant( value.toDouble( &conversionOk ) );
1233 break;
1234 case QVariant::Int:
1235 var = QVariant( value.toInt( &conversionOk ) );
1236 break;
1237 case QVariant::LongLong:
1238 var = QVariant( value.toLongLong( &conversionOk ) );
1239 break;
1240 case QVariant::DateTime:
1241 var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
1242 break;
1243 default: //string type is default
1244 var = QVariant( value );
1245 break;
1246 }
1247 if ( ! conversionOk ) // Assume is NULL
1248 {
1249 var = QVariant();
1250 }
1251 Q_ASSERT( mCurrentFeature );
1252 mCurrentFeature->setAttribute( att_it.value().first, var );
1253 }
1254}
1255
1256int QgsGmlStreamingParser::readEpsgFromAttribute( int &epsgNr, const XML_Char **attr )
1257{
1258 int i = 0;
1259 while ( attr[i] )
1260 {
1261 if ( strcmp( attr[i], "srsName" ) == 0 )
1262 {
1263 const QString srsName( attr[i + 1] );
1264 QString authority;
1265 QString code;
1266 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( srsName, authority, code );
1267 if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
1268 {
1269 return 1;
1270 }
1271 const bool bIsUrn = ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_URN ||
1274 bool conversionOk;
1275 const int eNr = code.toInt( &conversionOk );
1276 if ( !conversionOk )
1277 {
1278 return 1;
1279 }
1280 epsgNr = eNr;
1281 mSrsName = srsName;
1282
1283 const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( epsgNr ) );
1284 if ( crs.isValid() )
1285 {
1286 if ( ( ( mAxisOrientationLogic == Honour_EPSG_if_urn && bIsUrn ) ||
1287 mAxisOrientationLogic == Honour_EPSG ) && crs.hasAxisInverted() )
1288 {
1289 mInvertAxisOrientation = !mInvertAxisOrientationRequest;
1290 }
1291 }
1292
1293 return 0;
1294 }
1295 ++i;
1296 }
1297 return 2;
1298}
1299
1300QString QgsGmlStreamingParser::readAttribute( const QString &attributeName, const XML_Char **attr ) const
1301{
1302 int i = 0;
1303 while ( attr[i] )
1304 {
1305 if ( attributeName.compare( attr[i] ) == 0 )
1306 {
1307 return QString::fromUtf8( attr[i + 1] );
1308 }
1309 i += 2;
1310 }
1311 return QString();
1312}
1313
1314bool QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString &coordString ) const
1315{
1316 QList<QgsPointXY> points;
1317 if ( pointsFromCoordinateString( points, coordString ) != 0 )
1318 {
1319 return false;
1320 }
1321
1322 if ( points.size() < 2 )
1323 {
1324 return false;
1325 }
1326
1327 r.set( points[0], points[1] );
1328
1329 return true;
1330}
1331
1332int QgsGmlStreamingParser::pointsFromCoordinateString( QList<QgsPointXY> &points, const QString &coordString ) const
1333{
1334 //tuples are separated by space, x/y by ','
1335#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1336 QStringList tuples = coordString.split( mTupleSeparator, QString::SkipEmptyParts );
1337#else
1338 const QStringList tuples = coordString.split( mTupleSeparator, Qt::SkipEmptyParts );
1339#endif
1340 QStringList tuples_coordinates;
1341 double x, y;
1342 bool conversionSuccess;
1343
1344 QStringList::const_iterator tupleIterator;
1345 for ( tupleIterator = tuples.constBegin(); tupleIterator != tuples.constEnd(); ++tupleIterator )
1346 {
1347#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1348 tuples_coordinates = tupleIterator->split( mCoordinateSeparator, QString::SkipEmptyParts );
1349#else
1350 tuples_coordinates = tupleIterator->split( mCoordinateSeparator, Qt::SkipEmptyParts );
1351#endif
1352 if ( tuples_coordinates.size() < 2 )
1353 {
1354 continue;
1355 }
1356 x = tuples_coordinates.at( 0 ).toDouble( &conversionSuccess );
1357 if ( !conversionSuccess )
1358 {
1359 continue;
1360 }
1361 y = tuples_coordinates.at( 1 ).toDouble( &conversionSuccess );
1362 if ( !conversionSuccess )
1363 {
1364 continue;
1365 }
1366 points.push_back( ( mInvertAxisOrientation ) ? QgsPointXY( y, x ) : QgsPointXY( x, y ) );
1367 }
1368 return 0;
1369}
1370
1371int QgsGmlStreamingParser::pointsFromPosListString( QList<QgsPointXY> &points, const QString &coordString, int dimension ) const
1372{
1373 // coordinates separated by spaces
1374#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
1375 QStringList coordinates = coordString.split( ' ', QString::SkipEmptyParts );
1376#else
1377 const QStringList coordinates = coordString.split( ' ', Qt::SkipEmptyParts );
1378#endif
1379
1380 if ( coordinates.size() % dimension != 0 )
1381 {
1382 QgsDebugMsg( QStringLiteral( "Wrong number of coordinates" ) );
1383 }
1384
1385 const int ncoor = coordinates.size() / dimension;
1386 for ( int i = 0; i < ncoor; i++ )
1387 {
1388 bool conversionSuccess;
1389 const double x = coordinates.value( i * dimension ).toDouble( &conversionSuccess );
1390 if ( !conversionSuccess )
1391 {
1392 continue;
1393 }
1394 const double y = coordinates.value( i * dimension + 1 ).toDouble( &conversionSuccess );
1395 if ( !conversionSuccess )
1396 {
1397 continue;
1398 }
1399 points.append( ( mInvertAxisOrientation ) ? QgsPointXY( y, x ) : QgsPointXY( x, y ) );
1400 }
1401 return 0;
1402}
1403
1404int QgsGmlStreamingParser::pointsFromString( QList<QgsPointXY> &points, const QString &coordString ) const
1405{
1406 if ( mCoorMode == QgsGmlStreamingParser::Coordinate )
1407 {
1408 return pointsFromCoordinateString( points, coordString );
1409 }
1410 else if ( mCoorMode == QgsGmlStreamingParser::PosList )
1411 {
1412 return pointsFromPosListString( points, coordString, mDimension ? mDimension : 2 );
1413 }
1414 return 1;
1415}
1416
1417int QgsGmlStreamingParser::getPointWKB( QgsWkbPtr &wkbPtr, const QgsPointXY &point ) const
1418{
1419 const int wkbSize = 1 + sizeof( int ) + 2 * sizeof( double );
1420 wkbPtr = QgsWkbPtr( new unsigned char[wkbSize], wkbSize );
1421
1422 QgsWkbPtr fillPtr( wkbPtr );
1423 fillPtr << mEndian << Qgis::WkbType::Point << point.x() << point.y();
1424
1425 return 0;
1426}
1427
1428int QgsGmlStreamingParser::getLineWKB( QgsWkbPtr &wkbPtr, const QList<QgsPointXY> &lineCoordinates ) const
1429{
1430 const int wkbSize = 1 + 2 * sizeof( int ) + lineCoordinates.size() * 2 * sizeof( double );
1431 wkbPtr = QgsWkbPtr( new unsigned char[wkbSize], wkbSize );
1432
1433 QgsWkbPtr fillPtr( wkbPtr );
1434
1435 fillPtr << mEndian << Qgis::WkbType::LineString << lineCoordinates.size();
1436
1437 QList<QgsPointXY>::const_iterator iter;
1438 for ( iter = lineCoordinates.constBegin(); iter != lineCoordinates.constEnd(); ++iter )
1439 {
1440 fillPtr << iter->x() << iter->y();
1441 }
1442
1443 return 0;
1444}
1445
1446int QgsGmlStreamingParser::getRingWKB( QgsWkbPtr &wkbPtr, const QList<QgsPointXY> &ringCoordinates ) const
1447{
1448 const int wkbSize = sizeof( int ) + ringCoordinates.size() * 2 * sizeof( double );
1449 wkbPtr = QgsWkbPtr( new unsigned char[wkbSize], wkbSize );
1450
1451 QgsWkbPtr fillPtr( wkbPtr );
1452
1453 fillPtr << ringCoordinates.size();
1454
1455 QList<QgsPointXY>::const_iterator iter;
1456 for ( iter = ringCoordinates.constBegin(); iter != ringCoordinates.constEnd(); ++iter )
1457 {
1458 fillPtr << iter->x() << iter->y();
1459 }
1460
1461 return 0;
1462}
1463
1464int QgsGmlStreamingParser::createMultiLineFromFragments()
1465{
1466 const int size = 1 + 2 * sizeof( int ) + totalWKBFragmentSize();
1467 mCurrentWKB = QgsWkbPtr( new unsigned char[size], size );
1468
1469 QgsWkbPtr wkbPtr( mCurrentWKB );
1470
1471 wkbPtr << mEndian << Qgis::WkbType::MultiLineString << mCurrentWKBFragments.constBegin()->size();
1472
1473 //copy (and delete) all the wkb fragments
1474 QList<QgsWkbPtr>::const_iterator wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1475 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1476 {
1477 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1478 wkbPtr += wkbIt->size();
1479 delete[] *wkbIt;
1480 }
1481
1482 mCurrentWKBFragments.clear();
1484 return 0;
1485}
1486
1487int QgsGmlStreamingParser::createMultiPointFromFragments()
1488{
1489 const int size = 1 + 2 * sizeof( int ) + totalWKBFragmentSize();
1490 mCurrentWKB = QgsWkbPtr( new unsigned char[size], size );
1491
1492 QgsWkbPtr wkbPtr( mCurrentWKB );
1493 wkbPtr << mEndian << Qgis::WkbType::MultiPoint << mCurrentWKBFragments.constBegin()->size();
1494
1495 QList<QgsWkbPtr>::const_iterator wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1496 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1497 {
1498 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1499 wkbPtr += wkbIt->size();
1500 delete[] *wkbIt;
1501 }
1502
1503 mCurrentWKBFragments.clear();
1504 mWkbType = Qgis::WkbType::MultiPoint;
1505 return 0;
1506}
1507
1508
1509int QgsGmlStreamingParser::createPolygonFromFragments()
1510{
1511 const int size = 1 + 2 * sizeof( int ) + totalWKBFragmentSize();
1512 mCurrentWKB = QgsWkbPtr( new unsigned char[size], size );
1513
1514 QgsWkbPtr wkbPtr( mCurrentWKB );
1515 wkbPtr << mEndian << Qgis::WkbType::Polygon << mCurrentWKBFragments.constBegin()->size();
1516
1517 QList<QgsWkbPtr>::const_iterator wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1518 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1519 {
1520 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1521 wkbPtr += wkbIt->size();
1522 delete[] *wkbIt;
1523 }
1524
1525 mCurrentWKBFragments.clear();
1526 mWkbType = Qgis::WkbType::Polygon;
1527 return 0;
1528}
1529
1530int QgsGmlStreamingParser::createMultiPolygonFromFragments()
1531{
1532 int size = 0;
1533 size += 1 + 2 * sizeof( int );
1534 size += totalWKBFragmentSize();
1535 size += mCurrentWKBFragments.size() * ( 1 + 2 * sizeof( int ) ); //fragments are just the rings
1536
1537 mCurrentWKB = QgsWkbPtr( new unsigned char[size], size );
1538
1539 QgsWkbPtr wkbPtr( mCurrentWKB );
1540 wkbPtr << ( char ) mEndian << Qgis::WkbType::MultiPolygon << mCurrentWKBFragments.size();
1541
1542 //have outer and inner iterators
1543 QList< QList<QgsWkbPtr> >::const_iterator outerWkbIt = mCurrentWKBFragments.constBegin();
1544
1545 for ( ; outerWkbIt != mCurrentWKBFragments.constEnd(); ++outerWkbIt )
1546 {
1547 //new polygon
1548 wkbPtr << ( char ) mEndian << Qgis::WkbType::Polygon << outerWkbIt->size();
1549
1550 QList<QgsWkbPtr>::const_iterator innerWkbIt = outerWkbIt->constBegin();
1551 for ( ; innerWkbIt != outerWkbIt->constEnd(); ++innerWkbIt )
1552 {
1553 memcpy( wkbPtr, *innerWkbIt, innerWkbIt->size() );
1554 wkbPtr += innerWkbIt->size();
1555 delete[] *innerWkbIt;
1556 }
1557 }
1558
1559 mCurrentWKBFragments.clear();
1560 mWkbType = Qgis::WkbType::MultiPolygon;
1561 return 0;
1562}
1563
1564int QgsGmlStreamingParser::totalWKBFragmentSize() const
1565{
1566 int result = 0;
1567 const auto constMCurrentWKBFragments = mCurrentWKBFragments;
1568 for ( const QList<QgsWkbPtr> &list : constMCurrentWKBFragments )
1569 {
1570 const auto constList = list;
1571 for ( const QgsWkbPtr &i : constList )
1572 {
1573 result += i.size();
1574 }
1575 }
1576 return result;
1577}
1578
1579void QgsGmlStreamingParser::createParser( const QByteArray &encoding )
1580{
1581 Q_ASSERT( !mParser );
1582
1583 mParser = XML_ParserCreateNS( encoding.isEmpty() ? nullptr : encoding.data(), NS_SEPARATOR );
1584 XML_SetUserData( mParser, this );
1585 XML_SetElementHandler( mParser, QgsGmlStreamingParser::start, QgsGmlStreamingParser::end );
1586 XML_SetCharacterDataHandler( mParser, QgsGmlStreamingParser::chars );
1587}
The Qgis class provides global constants for use throughout the application.
Definition: qgis.h:55
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:155
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
@ Unknown
Unknown.
static QgsAuthManager * authManager()
Returns the application's authentication manager instance.
static endian_t endian()
Returns whether this machine uses big or little endian.
A vector of attributes.
Definition: qgsattributes.h:59
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool setAttribute(int field, const QVariant &attr)
Sets an attribute's value by field index.
Definition: qgsfeature.cpp:265
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:198
QgsGeometry geometry
Definition: qgsfeature.h:67
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:224
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:233
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Definition: qgsfeature.cpp:170
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
QString name
Definition: qgsfield.h:61
Container of fields for a vector layer.
Definition: qgsfields.h:45
int size() const
Returns number of items.
Definition: qgsfields.cpp:138
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:163
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
void fromWkb(unsigned char *wkb, int length)
Set the geometry, feeding in the buffer containing OGC Well-Known Binary and the buffer's length.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QPair< QgsFeature *, QString > QgsGmlFeaturePtrGmlIdPair
Definition: qgsgml.h:55
int numberReturned() const
Returns WFS 2.0 "numberReturned" or WFS 1.1 "numberOfFeatures" attribute, or -1 if invalid/not found.
Definition: qgsgml.h:140
Qgis::WkbType wkbType() const
Returns the geometry type.
Definition: qgsgml.h:134
AxisOrientationLogic
Axis orientation logic.
Definition: qgsgml.h:75
@ Honour_EPSG
Honour EPSG axis order.
Definition: qgsgml.h:79
@ Honour_EPSG_if_urn
Honour EPSG axis order only if srsName is of the form urn:ogc:def:crs:EPSG:
Definition: qgsgml.h:77
int numberMatched() const
Returns WFS 2.0 "numberMatched" attribute, or -1 if invalid/not found.
Definition: qgsgml.h:137
QgsGmlStreamingParser(const QString &typeName, const QString &geometryAttribute, const QgsFields &fields, AxisOrientationLogic axisOrientationLogic=Honour_EPSG_if_urn, bool invertAxisOrientation=false)
Constructor.
Definition: qgsgml.cpp:278
bool processData(const QByteArray &data, bool atEnd, QString &errorMsg)
Process a new chunk of data.
Definition: qgsgml.cpp:444
int getEPSGCode() const
Returns the EPSG code, or 0 if unknown.
Definition: qgsgml.h:125
QVector< QgsGmlFeaturePtrGmlIdPair > getAndStealReadyFeatures()
Returns the list of features that have been completely parsed.
Definition: qgsgml.cpp:489
QString srsName() const
Returns the value of the srsName attribute.
Definition: qgsgml.h:128
void totalStepsUpdate(int totalSteps)
void dataReadProgress(int progress)
QgsGml(const QString &typeName, const QString &geometryAttribute, const QgsFields &fields)
Definition: qgsgml.cpp:46
QgsCoordinateReferenceSystem crs() const
Returns features spatial reference system.
Definition: qgsgml.cpp:264
int getFeatures(const QString &uri, Qgis::WkbType *wkbType, QgsRectangle *extent=nullptr, const QString &userName=QString(), const QString &password=QString(), const QString &authcfg=QString())
Does the Http GET request to the wfs server.
Definition: qgsgml.cpp:61
void dataProgressAndSteps(int progress, int totalSteps)
Also emit signal with progress and totalSteps together (this is better for the status message)
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
static QgsNetworkAccessManager * instance(Qt::ConnectionType connectionType=Qt::BlockingQueuedConnection)
Returns a pointer to the active QgsNetworkAccessManager for the current thread.
CRSFlavor
CRS flavor.
Definition: qgsogcutils.h:594
@ OGC_HTTP_URI
e.g. urn:x-ogc:def:crs:EPSG::4326
@ X_OGC_URN
e.g. urn:ogc:def:crs:EPSG::4326
@ OGC_URN
e.g. http://www.opengis.net/gml/srs/epsg.xml#4326 (called "OGC HTTP URL" in GeoServer WFS configurati...
static CRSFlavor parseCrsName(const QString &crsName, QString &authority, QString &code)
Parse a CRS name in one of the flavors of OGC services, and decompose it as authority and code.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void set(const QgsPointXY &p1, const QgsPointXY &p2, bool normalize=true)
Sets the rectangle from two QgsPoints.
Definition: qgsrectangle.h:122
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
WKB pointer handler.
Definition: qgswkbptr.h:44
int size() const
size
Definition: qgswkbptr.h:119
std::unique_ptr< std::remove_pointer< OGRGeometryH >::type, OGRGeometryDeleter > ogr_geometry_unique_ptr
Scoped OGR geometry.
Definition: qgsogrutils.h:142
#define LOCALNAME_EQUALS(string_constant)
Definition: qgsgml.cpp:496
#define GML_NAMESPACE
const char NS_SEPARATOR
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)
#define GML32_NAMESPACE
Definition: qgsogcutils.cpp:44
const QgsCoordinateReferenceSystem & crs
const QString & typeName