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