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