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