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