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