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