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