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