QGIS API Documentation 3.99.0-Master (51df526a401)
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() ? 2 : 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<QgsPoint> 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<QgsPoint> 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<QgsPoint> pointList;
1283 int dimension = 0;
1284 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1285 {
1286 //error
1287 }
1288 mStringCash.clear();
1289 mDimension = dimension;
1290
1291 if ( pointList.isEmpty() )
1292 return; // error
1293
1294 if ( parseMode == QgsGmlStreamingParser::Geometry )
1295 {
1296 //directly add WKB point to the feature
1297 if ( getPointWKB( mCurrentWKB, *( pointList.constBegin() ), dimension ) != 0 )
1298 {
1299 //error
1300 }
1301
1302 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPoint ) //keep multitype in case of geometry type mix
1303 {
1304 mWkbType = dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point;
1305 }
1306
1307 }
1308 else //multipoint, add WKB as fragment
1309 {
1310 QByteArray wkbPtr;
1311 if ( getPointWKB( wkbPtr, *( pointList.constBegin() ), dimension ) != 0 )
1312 {
1313 //error
1314 }
1315 if ( !mCurrentWKBFragments.isEmpty() )
1316 {
1317 mCurrentWKBFragments.last().push_back( wkbPtr );
1318 }
1319 else
1320 {
1321 QgsDebugError( QStringLiteral( "No wkb fragments" ) );
1322 }
1323 }
1324 }
1325 else if ( !mAttributeValIsNested &&
1326 isGMLNS && ( LOCALNAME_EQUALS( "LineString" ) || LOCALNAME_EQUALS( "LineStringSegment" ) ) )
1327 {
1328 //add WKB point to the feature
1329
1330 QList<QgsPoint> pointList;
1331 int dimension = 0;
1332 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1333 {
1334 //error
1335 }
1336 mStringCash.clear();
1337 mDimension = dimension;
1338
1339 if ( parseMode == QgsGmlStreamingParser::Geometry )
1340 {
1341 if ( getLineWKB( mCurrentWKB, pointList, dimension ) != 0 )
1342 {
1343 //error
1344 }
1345
1346 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiLineString )//keep multitype in case of geometry type mix
1347 {
1348 mWkbType = dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString;
1349 }
1350 else if ( dimension > 2 )
1351 {
1353 }
1354 mDimension = dimension;
1355 }
1356 else //multiline, add WKB as fragment
1357 {
1358 QByteArray wkbPtr;
1359 if ( getLineWKB( wkbPtr, pointList, dimension ) != 0 )
1360 {
1361 //error
1362 }
1363 mDimension = dimension;
1364 if ( !mCurrentWKBFragments.isEmpty() )
1365 {
1366 mCurrentWKBFragments.last().push_back( wkbPtr );
1367 }
1368 else
1369 {
1370 QgsDebugError( QStringLiteral( "no wkb fragments" ) );
1371 }
1372 }
1373 }
1374 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) &&
1375 isGMLNS && LOCALNAME_EQUALS( "LinearRing" ) )
1376 {
1377 QList<QgsPoint> pointList;
1378 int dimension = 0;
1379 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1380 {
1381 //error
1382 }
1383 mStringCash.clear();
1384 mDimension = dimension;
1385
1386 QByteArray wkbPtr;
1387 if ( getRingWKB( wkbPtr, pointList, dimension ) != 0 )
1388 {
1389 //error
1390 }
1391
1392 if ( !mCurrentWKBFragments.isEmpty() )
1393 {
1394 mCurrentWKBFragments.last().push_back( wkbPtr );
1395 }
1396 else
1397 {
1398 QgsDebugError( QStringLiteral( "no wkb fragments" ) );
1399 }
1400 }
1401 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS &&
1402 LOCALNAME_EQUALS( "Polygon" ) )
1403 {
1404 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPolygon ) //keep multitype in case of geometry type mix
1405 {
1406 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1407 }
1408
1409 if ( parseMode == Geometry )
1410 {
1411 createPolygonFromFragments();
1412 }
1413 }
1414 else if ( parseMode == MultiPoint && isGMLNS &&
1415 LOCALNAME_EQUALS( "MultiPoint" ) )
1416 {
1417 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1418 mParseModeStack.pop();
1419 createMultiPointFromFragments();
1420 }
1421 else if ( parseMode == MultiLine && isGMLNS &&
1422 ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
1423 {
1425 mParseModeStack.pop();
1426 createMultiLineFromFragments();
1427 }
1428 else if ( parseMode == MultiPolygon && isGMLNS &&
1429 ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
1430 {
1431 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1432 mParseModeStack.pop();
1433 createMultiPolygonFromFragments();
1434 }
1435 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
1436 {
1437 mParseModeStack.pop();
1438 }
1439 else if ( parseMode == ExceptionText && LOCALNAME_EQUALS( "ExceptionText" ) )
1440 {
1441 mExceptionText = mStringCash;
1442 mParseModeStack.pop();
1443 }
1444
1445 if ( !mGeometryString.empty() )
1446 {
1447 mGeometryString.append( "</", 2 );
1448 mGeometryString.append( pszLocalName, localNameLen );
1449 mGeometryString.append( ">", 1 );
1450 }
1451
1452}
1453
1454void QgsGmlStreamingParser::characters( const XML_Char *chars, int len )
1455{
1456 //save chars in mStringCash attribute mode or coordinate mode
1457 if ( mParseModeStack.isEmpty() )
1458 {
1459 return;
1460 }
1461
1462 if ( !mGeometryString.empty() )
1463 {
1464 mGeometryString.append( chars, len );
1465 }
1466
1467 const QgsGmlStreamingParser::ParseMode parseMode = mParseModeStack.top();
1468 if ( parseMode == QgsGmlStreamingParser::Attribute ||
1469 parseMode == QgsGmlStreamingParser::AttributeTuple ||
1470 parseMode == QgsGmlStreamingParser::Coordinate ||
1471 parseMode == QgsGmlStreamingParser::PosList ||
1472 parseMode == QgsGmlStreamingParser::LowerCorner ||
1473 parseMode == QgsGmlStreamingParser::UpperCorner ||
1474 parseMode == QgsGmlStreamingParser::ExceptionText )
1475 {
1476 mStringCash.append( QString::fromUtf8( chars, len ) );
1477 }
1478}
1479
1480void QgsGmlStreamingParser::addStringContentToJson()
1481{
1482 const QString s( mStringCash.trimmed() );
1483 if ( !s.isEmpty() )
1484 {
1485 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
1486 auto textIter = jsonParent.find( "_text" );
1487 if ( textIter != jsonParent.end() )
1488 {
1489 if ( textIter->type() != json::value_t::array )
1490 {
1491 auto array = json::array();
1492 array.emplace_back( std::move( *textIter ) );
1493 *textIter = array;
1494 }
1495 textIter->emplace_back( jsonFromString( s ) );
1496 }
1497 else
1498 {
1499 jsonParent.emplace( "_text", jsonFromString( s ) );
1500 }
1501 }
1502 mStringCash.clear();
1503}
1504
1505void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &value )
1506{
1507 //find index with attribute name
1508 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
1509 bool conversionOk = true;
1510 if ( att_it != mThematicAttributes.constEnd() )
1511 {
1512 QVariant var;
1513 switch ( att_it.value().second.type() )
1514 {
1515 case QMetaType::Type::Double:
1516 var = QVariant( value.toDouble( &conversionOk ) );
1517 break;
1518 case QMetaType::Type::Int:
1519 var = QVariant( value.toInt( &conversionOk ) );
1520 break;
1521 case QMetaType::Type::LongLong:
1522 var = QVariant( value.toLongLong( &conversionOk ) );
1523 break;
1524 case QMetaType::Type::QDateTime:
1525 var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
1526 break;
1527 default: //string type is default
1528 var = QVariant( value );
1529 break;
1530 }
1531 if ( ! conversionOk ) // Assume is NULL
1532 {
1533 var = QVariant();
1534 }
1535 Q_ASSERT( mCurrentFeature );
1536 mCurrentFeature->setAttribute( att_it.value().first, var );
1537 }
1538}
1539
1540int QgsGmlStreamingParser::readEpsgFromAttribute( int &epsgNr, const XML_Char **attr )
1541{
1542 int i = 0;
1543 while ( attr[i] )
1544 {
1545 if ( strcmp( attr[i], "srsName" ) == 0 )
1546 {
1547 const QString srsName( attr[i + 1] );
1548 QString authority;
1549 QString code;
1550 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( srsName, authority, code );
1551 if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
1552 {
1553 return 1;
1554 }
1555 const bool bIsUrn = ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_URN ||
1558 bool conversionOk;
1559 const int eNr = code.toInt( &conversionOk );
1560 if ( !conversionOk )
1561 {
1562 return 1;
1563 }
1564 epsgNr = eNr;
1565 mSrsName = srsName;
1566
1567 const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( epsgNr ) );
1568 if ( crs.isValid() )
1569 {
1570 if ( ( ( mAxisOrientationLogic == Honour_EPSG_if_urn && bIsUrn ) ||
1571 mAxisOrientationLogic == Honour_EPSG ) && crs.hasAxisInverted() )
1572 {
1573 mInvertAxisOrientation = !mInvertAxisOrientationRequest;
1574 }
1575 }
1576
1577 return 0;
1578 }
1579 ++i;
1580 }
1581 return 2;
1582}
1583
1584QString QgsGmlStreamingParser::readAttribute( const QString &attributeName, const XML_Char **attr ) const
1585{
1586 int i = 0;
1587 while ( attr[i] )
1588 {
1589 if ( attributeName.compare( attr[i] ) == 0 )
1590 {
1591 return QString::fromUtf8( attr[i + 1] );
1592 }
1593 i += 2;
1594 }
1595 return QString();
1596}
1597
1598bool QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString &coordString ) const
1599{
1600 QList<QgsPoint> points;
1601 if ( pointsFromCoordinateString( points, coordString ) != 0 )
1602 {
1603 return false;
1604 }
1605
1606 if ( points.size() < 2 )
1607 {
1608 return false;
1609 }
1610
1611 r.set( points[0], points[1] );
1612
1613 return true;
1614}
1615
1616int QgsGmlStreamingParser::pointsFromCoordinateString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1617{
1618 //tuples are separated by space, x/y by ','
1619 const QStringList tuples = coordString.split( mTupleSeparator, Qt::SkipEmptyParts );
1620 QStringList tuples_coordinates;
1621 double x, y, z;
1622 bool conversionSuccess;
1623
1624 if ( dimension )
1625 *dimension = 0;
1626
1627 QStringList::const_iterator tupleIterator;
1628 for ( tupleIterator = tuples.constBegin(); tupleIterator != tuples.constEnd(); ++tupleIterator )
1629 {
1630 tuples_coordinates = tupleIterator->split( mCoordinateSeparator, Qt::SkipEmptyParts );
1631 if ( dimension )
1632 {
1633 *dimension = std::max( *dimension, static_cast<int>( tuples_coordinates.size() ) );
1634 }
1635 if ( tuples_coordinates.size() < 2 )
1636 {
1637 continue;
1638 }
1639 x = tuples_coordinates.at( 0 ).toDouble( &conversionSuccess );
1640 if ( !conversionSuccess )
1641 {
1642 continue;
1643 }
1644 y = tuples_coordinates.at( 1 ).toDouble( &conversionSuccess );
1645 if ( !conversionSuccess )
1646 {
1647 continue;
1648 }
1649 if ( tuples_coordinates.size() > 2 )
1650 {
1651 z = tuples_coordinates.at( 2 ).toDouble( &conversionSuccess );
1652 if ( !conversionSuccess )
1653 {
1654 continue;
1655 }
1656 }
1657 else
1658 {
1659 z = std::numeric_limits<double>::quiet_NaN(); // no Z coordinate
1660 }
1661 points.push_back( ( mInvertAxisOrientation ) ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1662 }
1663
1664 if ( dimension && *dimension == 0 )
1665 {
1666 *dimension = 2; // default dimension is 2D
1667 }
1668
1669 return 0;
1670}
1671
1672int QgsGmlStreamingParser::pointsFromPosListString( QList<QgsPoint> &points, const QString &coordString, int dimension ) const
1673{
1674 // coordinates separated by spaces
1675 const QStringList coordinates = coordString.split( ' ', Qt::SkipEmptyParts );
1676
1677 if ( coordinates.size() % dimension != 0 )
1678 {
1679 QgsDebugError( QStringLiteral( "Wrong number of coordinates" ) );
1680 }
1681
1682 const int ncoor = coordinates.size() / dimension;
1683 for ( int i = 0; i < ncoor; i++ )
1684 {
1685 bool conversionSuccess;
1686 const double x = coordinates.value( i * dimension ).toDouble( &conversionSuccess );
1687 if ( !conversionSuccess )
1688 {
1689 continue;
1690 }
1691 const double y = coordinates.value( i * dimension + 1 ).toDouble( &conversionSuccess );
1692 if ( !conversionSuccess )
1693 {
1694 continue;
1695 }
1696 double z = std::numeric_limits<double>::quiet_NaN();
1697 if ( dimension > 2 )
1698 {
1699 z = coordinates.value( i * dimension + 2 ).toDouble( &conversionSuccess );
1700 if ( !conversionSuccess )
1701 {
1702 continue;
1703 }
1704 }
1705 points.append( mInvertAxisOrientation ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1706 }
1707 return 0;
1708}
1709
1710int QgsGmlStreamingParser::pointsFromString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1711{
1712 if ( mCoorMode == QgsGmlStreamingParser::Coordinate )
1713 {
1714 return pointsFromCoordinateString( points, coordString, dimension );
1715 }
1716 else if ( mCoorMode == QgsGmlStreamingParser::PosList )
1717 {
1718 if ( dimension )
1719 {
1720 *dimension = mDimension ? mDimension : 2; // default dimension is 2D
1721 }
1722 return pointsFromPosListString( points, coordString, mDimension ? mDimension : 2 );
1723 }
1724 return 1;
1725}
1726
1727int QgsGmlStreamingParser::getPointWKB( QByteArray &wkbPtr, const QgsPoint &point, int dimension ) const
1728{
1729 const int wkbSize = 1 + static_cast<int>( sizeof( int ) ) + dimension * static_cast<int>( sizeof( double ) );
1730 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1731
1732 QgsWkbPtr fillPtr( wkbPtr );
1733 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point ) << point.x() << point.y();
1734 if ( dimension > 2 )
1735 {
1736 fillPtr << point.z(); // add Z coordinate if available
1737 }
1738
1739 return 0;
1740}
1741
1742int QgsGmlStreamingParser::getLineWKB( QByteArray &wkbPtr, const QList<QgsPoint> &lineCoordinates, int dimension ) const
1743{
1744 const int wkbSize = 1 + 2 * static_cast<int>( sizeof( int ) ) + static_cast<int>( lineCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1745 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1746
1747 QgsWkbPtr fillPtr( wkbPtr );
1748
1749 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString ) << lineCoordinates.size();
1750
1751 QList<QgsPoint>::const_iterator iter;
1752 for ( iter = lineCoordinates.constBegin(); iter != lineCoordinates.constEnd(); ++iter )
1753 {
1754 fillPtr << iter->x() << iter->y();
1755 if ( dimension > 2 )
1756 {
1757 fillPtr << iter->z(); // add Z coordinate if available
1758 }
1759 }
1760
1761 return 0;
1762}
1763
1764int QgsGmlStreamingParser::getRingWKB( QByteArray &wkbPtr, const QList<QgsPoint> &ringCoordinates, int dimension ) const
1765{
1766 const int wkbSize = static_cast<int>( sizeof( int ) ) + static_cast<int>( ringCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1767 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1768
1769 QgsWkbPtr fillPtr( wkbPtr );
1770
1771 fillPtr << ringCoordinates.size();
1772
1773 QList<QgsPoint>::const_iterator iter;
1774 for ( iter = ringCoordinates.constBegin(); iter != ringCoordinates.constEnd(); ++iter )
1775 {
1776 fillPtr << iter->x() << iter->y();
1777 if ( dimension > 2 )
1778 {
1779 fillPtr << iter->z(); // add Z coordinate if available
1780 }
1781 }
1782
1783 return 0;
1784}
1785
1786int QgsGmlStreamingParser::createMultiLineFromFragments()
1787{
1788 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1789 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1790
1791 QgsWkbPtr wkbPtr( mCurrentWKB );
1792
1793 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiLineStringZ : Qgis::WkbType::MultiLineString ) << mCurrentWKBFragments.constBegin()->size();
1794
1795 //copy (and delete) all the wkb fragments
1796 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1797 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1798 {
1799 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1800 wkbPtr += wkbIt->size();
1801 }
1802
1803 mCurrentWKBFragments.clear();
1805 return 0;
1806}
1807
1808int QgsGmlStreamingParser::createMultiPointFromFragments()
1809{
1810 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1811 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1812
1813 QgsWkbPtr wkbPtr( mCurrentWKB );
1814 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint ) << mCurrentWKBFragments.constBegin()->size();
1815
1816 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1817 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1818 {
1819 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1820 wkbPtr += wkbIt->size();
1821 }
1822
1823 mCurrentWKBFragments.clear();
1824 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1825 return 0;
1826}
1827
1828
1829int QgsGmlStreamingParser::createPolygonFromFragments()
1830{
1831 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1832 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1833
1834 QgsWkbPtr wkbPtr( mCurrentWKB );
1835 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << mCurrentWKBFragments.constBegin()->size();
1836
1837 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1838 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1839 {
1840 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1841 wkbPtr += wkbIt->size();
1842 }
1843
1844 mCurrentWKBFragments.clear();
1845 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1846 return 0;
1847}
1848
1849int QgsGmlStreamingParser::createMultiPolygonFromFragments()
1850{
1851 int size = 0;
1852 size += 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) );
1853 size += totalWKBFragmentSize();
1854 size += mCurrentWKBFragments.size() * ( 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) ); //fragments are just the rings
1855
1856 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1857
1858 QgsWkbPtr wkbPtr( mCurrentWKB );
1859 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon ) << mCurrentWKBFragments.size();
1860
1861 //have outer and inner iterators
1862 auto outerWkbIt = mCurrentWKBFragments.constBegin();
1863
1864 for ( ; outerWkbIt != mCurrentWKBFragments.constEnd(); ++outerWkbIt )
1865 {
1866 //new polygon
1867 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << outerWkbIt->size();
1868
1869 auto innerWkbIt = outerWkbIt->constBegin();
1870 for ( ; innerWkbIt != outerWkbIt->constEnd(); ++innerWkbIt )
1871 {
1872 memcpy( wkbPtr, *innerWkbIt, innerWkbIt->size() );
1873 wkbPtr += innerWkbIt->size();
1874 }
1875 }
1876
1877 mCurrentWKBFragments.clear();
1878 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1879 return 0;
1880}
1881
1882int QgsGmlStreamingParser::totalWKBFragmentSize() const
1883{
1884 int result = 0;
1885 for ( const QList<QByteArray> &list : std::as_const( mCurrentWKBFragments ) )
1886 {
1887 for ( const QByteArray &i : list )
1888 {
1889 result += i.size();
1890 }
1891 }
1892 return result;
1893}
1894
1895void QgsGmlStreamingParser::createParser( const QByteArray &encoding )
1896{
1897 Q_ASSERT( !mParser );
1898
1899 mParser = XML_ParserCreateNS( encoding.isEmpty() ? nullptr : encoding.data(), NS_SEPARATOR );
1900 XML_SetUserData( mParser, this );
1901 XML_SetElementHandler( mParser, QgsGmlStreamingParser::start, QgsGmlStreamingParser::end );
1902 XML_SetCharacterDataHandler( mParser, QgsGmlStreamingParser::chars );
1903}
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
@ MultiPointZ
MultiPointZ.
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ MultiPolygon
MultiPolygon.
@ MultiLineString
MultiLineString.
@ Unknown
Unknown.
@ PointZ
PointZ.
@ MultiLineStringZ
MultiLineStringZ.
@ MultiPolygonZ
MultiPolygonZ.
@ LineStringZ
LineStringZ.
@ PolygonZ
PolygonZ.
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.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:49
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
static Qgis::WkbType flatType(Qgis::WkbType type)
Returns the flat type for a WKB type.
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