QGIS API Documentation 4.1.0-Master (70f46ec8b69)
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 // If the attribute name is the geometryAttribute, we try to parse the text value as WKT
1022 if ( localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) && memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
1023 {
1024 QgsGeometry g;
1025 g = QgsGeometry::fromWkt( mStringCash );
1026 if ( !g.isNull() )
1027 {
1028 if ( mInvertAxisOrientation )
1029 {
1030 g.transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
1031 }
1032 Q_ASSERT( mCurrentFeature );
1033 // Always convert to multi-type
1035 // And append if a geometry already exists
1036 QgsGeometry currentGeometry = mCurrentFeature->geometry();
1037 if ( !currentGeometry.isNull() )
1038 {
1039 g = g.combine( currentGeometry );
1040 }
1041 mCurrentFeature->setGeometry( g );
1042 }
1043 }
1044
1045 // We still add the nested child to the JSON structure
1046 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
1047 if ( jsonParent.type() == json::value_t::object && jsonParent.empty() )
1048 {
1049 jsonParent = jsonFromString( mStringCash );
1050 }
1051 else if ( jsonParent.type() == json::value_t::object )
1052 {
1053 addStringContentToJson();
1054 }
1055 mStringCash.clear();
1056 }
1057 mAttributeJsonCurrentStack.pop();
1058 }
1059
1060 if ( parseMode == Coordinate && isGMLNS && LOCALNAME_EQUALS( "coordinates" ) )
1061 {
1062 mParseModeStack.pop();
1063 }
1064 else if ( parseMode == PosList && isGMLNS && ( LOCALNAME_EQUALS( "pos" ) || LOCALNAME_EQUALS( "posList" ) ) )
1065 {
1066 mDimension = lastDimension;
1067 mParseModeStack.pop();
1068 }
1069 else if ( parseMode == AttributeTuple && mCurrentTypename + '|' + QString::fromUtf8( pszLocalName, localNameLen ) == mAttributeName ) //add a thematic attribute to the feature
1070 {
1071 mParseModeStack.pop();
1072
1073 setAttribute( mAttributeName, mStringCash );
1074 }
1075 else if ( parseMode == Attribute && mAttributeDepth == mParseDepth ) //add a thematic attribute to the feature
1076 {
1077 mParseModeStack.pop();
1078 mParseDepth = -1;
1079
1080 if ( mAttributeValIsNested )
1081 {
1082 mAttributeValIsNested = false;
1083 auto iter = mMapFieldNameToJSONContent.find( mAttributeName );
1084 if ( iter == mMapFieldNameToJSONContent.end() )
1085 {
1086 mMapFieldNameToJSONContent[mAttributeName] = QString::fromStdString( mAttributeJson.dump() );
1087 }
1088 else
1089 {
1090 QString &str = iter.value();
1091 if ( str[0] == '[' && str.back() == ']' )
1092 {
1093 str.back() = ',';
1094 }
1095 else
1096 {
1097 str.insert( 0, '[' );
1098 str.append( ',' );
1099 }
1100 str.append( QString::fromStdString( mAttributeJson.dump() ) );
1101 str.append( ']' );
1102 }
1103 }
1104 else
1105 {
1106 setAttribute( mAttributeName, mStringCash );
1107 }
1108 }
1109 else if ( parseMode == Geometry && localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) && memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
1110 {
1111 mParseModeStack.pop();
1112 if ( mFoundUnhandledGeometryElement )
1113 {
1114 const gdal::ogr_geometry_unique_ptr hGeom( OGR_G_CreateFromGML( mGeometryString.c_str() ) );
1115 //QgsDebugMsgLevel( u"for OGR: %1 -> %2"_s.arg(mGeometryString.c_str()).arg(hGeom != nullptr), 2);
1116 if ( hGeom )
1117 {
1118 const int wkbSize = OGR_G_WkbSize( hGeom.get() );
1119 unsigned char *pabyBuffer = new unsigned char[wkbSize];
1120 OGR_G_ExportToIsoWkb( hGeom.get(), wkbNDR, pabyBuffer );
1121 QgsGeometry g;
1122 g.fromWkb( pabyBuffer, wkbSize );
1123 if ( mInvertAxisOrientation )
1124 {
1125 g.transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
1126 }
1127 Q_ASSERT( mCurrentFeature );
1128 mCurrentFeature->setGeometry( g );
1129 }
1130 }
1131 mGeometryString.clear();
1132 }
1133 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "boundedBy" ) )
1134 {
1135 //create bounding box from mStringCash
1136 if ( mCurrentExtent.isNull() && !mBoundedByNullFound && !createBBoxFromCoordinateString( mCurrentExtent, mStringCash ) )
1137 {
1138 QgsDebugError( u"creation of bounding box failed"_s );
1139 }
1140 if ( !mCurrentExtent.isNull() && mLayerExtent.isNull() && !mCurrentFeature && mFeatureCount == 0 )
1141 {
1142 mLayerExtent = mCurrentExtent;
1143 mCurrentExtent = QgsRectangle();
1144 }
1145
1146 mParseModeStack.pop();
1147 }
1148 else if ( parseMode == Null && isGMLNS && LOCALNAME_EQUALS( "null" ) )
1149 {
1150 mParseModeStack.pop();
1151 }
1152 else if ( parseMode == Envelope && isGMLNS && LOCALNAME_EQUALS( "Envelope" ) )
1153 {
1154 mParseModeStack.pop();
1155 }
1156 else if ( parseMode == LowerCorner && isGMLNS && LOCALNAME_EQUALS( "lowerCorner" ) )
1157 {
1158 QList<QgsPoint> points;
1159 pointsFromPosListString( points, mStringCash, 2 );
1160 if ( points.size() == 1 )
1161 {
1162 mCurrentExtent.setXMinimum( points[0].x() );
1163 mCurrentExtent.setYMinimum( points[0].y() );
1164 }
1165 mParseModeStack.pop();
1166 }
1167 else if ( parseMode == UpperCorner && isGMLNS && LOCALNAME_EQUALS( "upperCorner" ) )
1168 {
1169 QList<QgsPoint> points;
1170 pointsFromPosListString( points, mStringCash, 2 );
1171 if ( points.size() == 1 )
1172 {
1173 mCurrentExtent.setXMaximum( points[0].x() );
1174 mCurrentExtent.setYMaximum( points[0].y() );
1175 }
1176 mParseModeStack.pop();
1177 }
1178 else if ( parseMode == FeatureTuple && mParseDepth == mFeatureTupleDepth )
1179 {
1180 mParseModeStack.pop();
1181 mFeatureTupleDepth = 0;
1182 }
1183 else if ( ( parseMode == Tuple && !mTypeNamePtr && LOCALNAME_EQUALS( "Tuple" ) )
1184 || ( parseMode == Feature && localNameLen == static_cast<int>( mTypeNameUTF8Len ) && memcmp( pszLocalName, mTypeNamePtr, mTypeNameUTF8Len ) == 0 ) )
1185 {
1186 Q_ASSERT( mCurrentFeature );
1187 if ( !mCurrentFeature->hasGeometry() )
1188 {
1189 if ( mCurrentWKB.size() > 0 )
1190 {
1191 QgsGeometry g;
1192 g.fromWkb( mCurrentWKB );
1193 mCurrentFeature->setGeometry( g );
1194 mCurrentWKB = QByteArray();
1195 }
1196 else if ( !mCurrentExtent.isEmpty() )
1197 {
1198 mCurrentFeature->setGeometry( QgsGeometry::fromRect( mCurrentExtent ) );
1199 }
1200 }
1201 mCurrentFeature->setValid( true );
1202
1203 for ( auto iter = mMapFieldNameToJSONContent.constBegin(); iter != mMapFieldNameToJSONContent.constEnd(); ++iter )
1204 {
1205 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( iter.key() );
1206 const int attrIndex = att_it.value().first;
1207 mCurrentFeature->setAttribute( attrIndex, iter.value() );
1208 }
1209 mMapFieldNameToJSONContent.clear();
1210
1211 mFeatureList.push_back( QgsGmlFeaturePtrGmlIdPair( mCurrentFeature, mCurrentFeatureId ) );
1212
1213 mCurrentFeature = nullptr;
1214 ++mFeatureCount;
1215 mParseModeStack.pop();
1216 }
1217 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "Point" ) )
1218 {
1219 QList<QgsPoint> pointList;
1220 int dimension = 0;
1221 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1222 {
1223 //error
1224 }
1225 mStringCash.clear();
1226 mDimension = dimension;
1227
1228 if ( pointList.isEmpty() )
1229 return; // error
1230
1231 if ( parseMode == QgsGmlStreamingParser::Geometry )
1232 {
1233 //directly add WKB point to the feature
1234 if ( getPointWKB( mCurrentWKB, *( pointList.constBegin() ), dimension ) != 0 )
1235 {
1236 //error
1237 }
1238
1239 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPoint ) //keep multitype in case of geometry type mix
1240 {
1241 mWkbType = dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point;
1242 }
1243 }
1244 else //multipoint, add WKB as fragment
1245 {
1246 QByteArray wkbPtr;
1247 if ( getPointWKB( wkbPtr, *( pointList.constBegin() ), dimension ) != 0 )
1248 {
1249 //error
1250 }
1251 if ( !mCurrentWKBFragments.isEmpty() )
1252 {
1253 mCurrentWKBFragments.last().push_back( wkbPtr );
1254 }
1255 else
1256 {
1257 QgsDebugError( u"No wkb fragments"_s );
1258 }
1259 }
1260 }
1261 else if ( !mAttributeValIsNested && isGMLNS && ( LOCALNAME_EQUALS( "LineString" ) || LOCALNAME_EQUALS( "LineStringSegment" ) ) )
1262 {
1263 //add WKB point to the feature
1264
1265 QList<QgsPoint> pointList;
1266 int dimension = 0;
1267 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1268 {
1269 //error
1270 }
1271 mStringCash.clear();
1272 mDimension = dimension;
1273
1274 if ( parseMode == QgsGmlStreamingParser::Geometry )
1275 {
1276 if ( getLineWKB( mCurrentWKB, pointList, dimension ) != 0 )
1277 {
1278 //error
1279 }
1280
1281 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiLineString ) //keep multitype in case of geometry type mix
1282 {
1283 mWkbType = dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString;
1284 }
1285 else if ( dimension > 2 )
1286 {
1288 }
1289 mDimension = dimension;
1290 }
1291 else //multiline, add WKB as fragment
1292 {
1293 QByteArray wkbPtr;
1294 if ( getLineWKB( wkbPtr, pointList, dimension ) != 0 )
1295 {
1296 //error
1297 }
1298 mDimension = dimension;
1299 if ( !mCurrentWKBFragments.isEmpty() )
1300 {
1301 mCurrentWKBFragments.last().push_back( wkbPtr );
1302 }
1303 else
1304 {
1305 QgsDebugError( u"no wkb fragments"_s );
1306 }
1307 }
1308 }
1309 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS && LOCALNAME_EQUALS( "LinearRing" ) )
1310 {
1311 QList<QgsPoint> pointList;
1312 int dimension = 0;
1313 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1314 {
1315 //error
1316 }
1317 mStringCash.clear();
1318 mDimension = dimension;
1319
1320 QByteArray wkbPtr;
1321 if ( getRingWKB( wkbPtr, pointList, dimension ) != 0 )
1322 {
1323 //error
1324 }
1325
1326 if ( !mCurrentWKBFragments.isEmpty() )
1327 {
1328 mCurrentWKBFragments.last().push_back( wkbPtr );
1329 }
1330 else
1331 {
1332 QgsDebugError( u"no wkb fragments"_s );
1333 }
1334 }
1335 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS && LOCALNAME_EQUALS( "Polygon" ) )
1336 {
1337 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPolygon ) //keep multitype in case of geometry type mix
1338 {
1339 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1340 }
1341
1342 if ( parseMode == Geometry )
1343 {
1344 createPolygonFromFragments();
1345 }
1346 }
1347 else if ( parseMode == MultiPoint && isGMLNS && LOCALNAME_EQUALS( "MultiPoint" ) )
1348 {
1349 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1350 mParseModeStack.pop();
1351 createMultiPointFromFragments();
1352 }
1353 else if ( parseMode == MultiLine && isGMLNS && ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
1354 {
1356 mParseModeStack.pop();
1357 createMultiLineFromFragments();
1358 }
1359 else if ( parseMode == MultiPolygon && isGMLNS && ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
1360 {
1361 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1362 mParseModeStack.pop();
1363 createMultiPolygonFromFragments();
1364 }
1365 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
1366 {
1367 mParseModeStack.pop();
1368 }
1369 else if ( parseMode == ExceptionText && LOCALNAME_EQUALS( "ExceptionText" ) )
1370 {
1371 mExceptionText = mStringCash;
1372 mParseModeStack.pop();
1373 }
1374
1375 if ( !mGeometryString.empty() )
1376 {
1377 mGeometryString.append( "</", 2 );
1378 mGeometryString.append( pszLocalName, localNameLen );
1379 mGeometryString.append( ">", 1 );
1380 }
1381}
1382
1383void QgsGmlStreamingParser::characters( const XML_Char *chars, int len )
1384{
1385 //save chars in mStringCash attribute mode or coordinate mode
1386 if ( mParseModeStack.isEmpty() )
1387 {
1388 return;
1389 }
1390
1391 if ( !mGeometryString.empty() )
1392 {
1393 mGeometryString.append( chars, len );
1394 }
1395
1396 const QgsGmlStreamingParser::ParseMode parseMode = mParseModeStack.top();
1397 if ( parseMode == QgsGmlStreamingParser::Attribute
1398 || parseMode == QgsGmlStreamingParser::AttributeTuple
1399 || parseMode == QgsGmlStreamingParser::Coordinate
1400 || parseMode == QgsGmlStreamingParser::PosList
1401 || parseMode == QgsGmlStreamingParser::LowerCorner
1402 || parseMode == QgsGmlStreamingParser::UpperCorner
1403 || parseMode == QgsGmlStreamingParser::ExceptionText )
1404 {
1405 mStringCash.append( QString::fromUtf8( chars, len ) );
1406 }
1407}
1408
1409void QgsGmlStreamingParser::addStringContentToJson()
1410{
1411 const QString s( mStringCash.trimmed() );
1412 if ( !s.isEmpty() )
1413 {
1414 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
1415 auto textIter = jsonParent.find( "_text" );
1416 if ( textIter != jsonParent.end() )
1417 {
1418 if ( textIter->type() != json::value_t::array )
1419 {
1420 auto array = json::array();
1421 array.emplace_back( std::move( *textIter ) );
1422 *textIter = array;
1423 }
1424 textIter->emplace_back( jsonFromString( s ) );
1425 }
1426 else
1427 {
1428 jsonParent.emplace( "_text", jsonFromString( s ) );
1429 }
1430 }
1431 mStringCash.clear();
1432}
1433
1434void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &value )
1435{
1436 //find index with attribute name
1437 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
1438 bool conversionOk = true;
1439 if ( att_it != mThematicAttributes.constEnd() )
1440 {
1441 QVariant var;
1442 switch ( att_it.value().second.type() )
1443 {
1444 case QMetaType::Type::Double:
1445 var = QVariant( value.toDouble( &conversionOk ) );
1446 break;
1447 case QMetaType::Type::Int:
1448 var = QVariant( value.toInt( &conversionOk ) );
1449 break;
1450 case QMetaType::Type::LongLong:
1451 var = QVariant( value.toLongLong( &conversionOk ) );
1452 break;
1453 case QMetaType::Type::QDateTime:
1454 var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
1455 break;
1456 default: //string type is default
1457 var = QVariant( value );
1458 break;
1459 }
1460 if ( !conversionOk ) // Assume is NULL
1461 {
1462 var = QVariant();
1463 }
1464 Q_ASSERT( mCurrentFeature );
1465 mCurrentFeature->setAttribute( att_it.value().first, var );
1466 }
1467}
1468
1469int QgsGmlStreamingParser::readSrsNameAndDimensionAttributes( const XML_Char **attr )
1470{
1471 int elDimension = 0;
1472 for ( int i = 0; attr[i] && attr[i + 1]; i += 2 )
1473 {
1474 if ( strcmp( attr[i], "srsName" ) == 0 )
1475 {
1476 const QString srsName( attr[i + 1] );
1477 if ( mSrsName.isEmpty() )
1478 {
1479 QString authority;
1480 QString code;
1481 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( srsName, authority, code );
1482 if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
1483 {
1484 const bool bIsUrn = ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_URN || crsFlavor == QgsOgcCrsUtils::CRSFlavor::X_OGC_URN || crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_HTTP_URI );
1485 const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( srsName );
1486 if ( crs.isValid() )
1487 {
1488 mSrsName = srsName;
1489 if ( ( ( mAxisOrientationLogic == Honour_EPSG_if_urn && bIsUrn ) || mAxisOrientationLogic == Honour_EPSG ) && crs.hasAxisInverted() )
1490 {
1491 mInvertAxisOrientation = !mInvertAxisOrientationRequest;
1492 }
1493
1494 mDimensionForCurSrsName = crs.hasVerticalAxis() ? 3 : 2;
1495 if ( elDimension == 0 )
1496 elDimension = mDimensionForCurSrsName;
1497 }
1498 }
1499 }
1500 else if ( srsName == mSrsName && elDimension == 0 )
1501 {
1502 elDimension = mDimensionForCurSrsName;
1503 }
1504 }
1505 else if ( strcmp( attr[i], "srsDimension" ) == 0 )
1506 {
1507 const QString srsDimension( attr[i + 1] );
1508 bool ok = false;
1509 const int dimension = srsDimension.toInt( &ok );
1510 if ( ok )
1511 {
1512 elDimension = dimension;
1513 }
1514 }
1515 }
1516
1517 return elDimension;
1518}
1519
1520QString QgsGmlStreamingParser::readAttribute( const QString &attributeName, const XML_Char **attr ) const
1521{
1522 int i = 0;
1523 while ( attr[i] )
1524 {
1525 if ( attributeName.compare( attr[i] ) == 0 )
1526 {
1527 return QString::fromUtf8( attr[i + 1] );
1528 }
1529 i += 2;
1530 }
1531 return QString();
1532}
1533
1534bool QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString &coordString ) const
1535{
1536 QList<QgsPoint> points;
1537 if ( pointsFromCoordinateString( points, coordString ) != 0 )
1538 {
1539 return false;
1540 }
1541
1542 if ( points.size() < 2 )
1543 {
1544 return false;
1545 }
1546
1547 r.set( points[0], points[1] );
1548
1549 return true;
1550}
1551
1552int QgsGmlStreamingParser::pointsFromCoordinateString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1553{
1554 //tuples are separated by space, x/y by ','
1555 const QStringList tuples = coordString.split( mTupleSeparator, Qt::SkipEmptyParts );
1556 QStringList tuples_coordinates;
1557 double x, y, z;
1558 bool conversionSuccess;
1559
1560 if ( dimension )
1561 *dimension = 0;
1562
1563 QStringList::const_iterator tupleIterator;
1564 for ( tupleIterator = tuples.constBegin(); tupleIterator != tuples.constEnd(); ++tupleIterator )
1565 {
1566 tuples_coordinates = tupleIterator->split( mCoordinateSeparator, Qt::SkipEmptyParts );
1567 if ( dimension )
1568 {
1569 *dimension = std::max( *dimension, static_cast<int>( tuples_coordinates.size() ) );
1570 }
1571 if ( tuples_coordinates.size() < 2 )
1572 {
1573 continue;
1574 }
1575 x = tuples_coordinates.at( 0 ).toDouble( &conversionSuccess );
1576 if ( !conversionSuccess )
1577 {
1578 continue;
1579 }
1580 y = tuples_coordinates.at( 1 ).toDouble( &conversionSuccess );
1581 if ( !conversionSuccess )
1582 {
1583 continue;
1584 }
1585 if ( tuples_coordinates.size() > 2 )
1586 {
1587 z = tuples_coordinates.at( 2 ).toDouble( &conversionSuccess );
1588 if ( !conversionSuccess )
1589 {
1590 continue;
1591 }
1592 }
1593 else
1594 {
1595 z = std::numeric_limits<double>::quiet_NaN(); // no Z coordinate
1596 }
1597 points.push_back( ( mInvertAxisOrientation ) ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1598 }
1599
1600 if ( dimension && *dimension == 0 )
1601 {
1602 *dimension = 2; // default dimension is 2D
1603 }
1604
1605 return 0;
1606}
1607
1608int QgsGmlStreamingParser::pointsFromPosListString( QList<QgsPoint> &points, const QString &coordString, int dimension ) const
1609{
1610 // coordinates separated by spaces
1611 const QStringList coordinates = coordString.split( ' ', Qt::SkipEmptyParts );
1612
1613 if ( coordinates.size() % dimension != 0 )
1614 {
1615 QgsDebugError( u"Wrong number of coordinates"_s );
1616 }
1617
1618 const int ncoor = coordinates.size() / dimension;
1619 for ( int i = 0; i < ncoor; i++ )
1620 {
1621 bool conversionSuccess;
1622 const double x = coordinates.value( i * dimension ).toDouble( &conversionSuccess );
1623 if ( !conversionSuccess )
1624 {
1625 continue;
1626 }
1627 const double y = coordinates.value( i * dimension + 1 ).toDouble( &conversionSuccess );
1628 if ( !conversionSuccess )
1629 {
1630 continue;
1631 }
1632 double z = std::numeric_limits<double>::quiet_NaN();
1633 if ( dimension > 2 )
1634 {
1635 z = coordinates.value( i * dimension + 2 ).toDouble( &conversionSuccess );
1636 if ( !conversionSuccess )
1637 {
1638 continue;
1639 }
1640 }
1641 points.append( mInvertAxisOrientation ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1642 }
1643 return 0;
1644}
1645
1646int QgsGmlStreamingParser::pointsFromString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1647{
1648 if ( mCoorMode == QgsGmlStreamingParser::Coordinate )
1649 {
1650 return pointsFromCoordinateString( points, coordString, dimension );
1651 }
1652 else if ( mCoorMode == QgsGmlStreamingParser::PosList )
1653 {
1654 if ( dimension )
1655 {
1656 *dimension = mDimension ? mDimension : 2; // default dimension is 2D
1657 }
1658 return pointsFromPosListString( points, coordString, mDimension ? mDimension : 2 );
1659 }
1660 return 1;
1661}
1662
1663int QgsGmlStreamingParser::getPointWKB( QByteArray &wkbPtr, const QgsPoint &point, int dimension ) const
1664{
1665 const int wkbSize = 1 + static_cast<int>( sizeof( int ) ) + dimension * static_cast<int>( sizeof( double ) );
1666 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1667
1668 QgsWkbPtr fillPtr( wkbPtr );
1669 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point ) << point.x() << point.y();
1670 if ( dimension > 2 )
1671 {
1672 fillPtr << point.z(); // add Z coordinate if available
1673 }
1674
1675 return 0;
1676}
1677
1678int QgsGmlStreamingParser::getLineWKB( QByteArray &wkbPtr, const QList<QgsPoint> &lineCoordinates, int dimension ) const
1679{
1680 const int wkbSize = 1 + 2 * static_cast<int>( sizeof( int ) ) + static_cast<int>( lineCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1681 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1682
1683 QgsWkbPtr fillPtr( wkbPtr );
1684
1685 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString ) << lineCoordinates.size();
1686
1687 QList<QgsPoint>::const_iterator iter;
1688 for ( iter = lineCoordinates.constBegin(); iter != lineCoordinates.constEnd(); ++iter )
1689 {
1690 fillPtr << iter->x() << iter->y();
1691 if ( dimension > 2 )
1692 {
1693 fillPtr << iter->z(); // add Z coordinate if available
1694 }
1695 }
1696
1697 return 0;
1698}
1699
1700int QgsGmlStreamingParser::getRingWKB( QByteArray &wkbPtr, const QList<QgsPoint> &ringCoordinates, int dimension ) const
1701{
1702 const int wkbSize = static_cast<int>( sizeof( int ) ) + static_cast<int>( ringCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1703 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1704
1705 QgsWkbPtr fillPtr( wkbPtr );
1706
1707 fillPtr << ringCoordinates.size();
1708
1709 QList<QgsPoint>::const_iterator iter;
1710 for ( iter = ringCoordinates.constBegin(); iter != ringCoordinates.constEnd(); ++iter )
1711 {
1712 fillPtr << iter->x() << iter->y();
1713 if ( dimension > 2 )
1714 {
1715 fillPtr << iter->z(); // add Z coordinate if available
1716 }
1717 }
1718
1719 return 0;
1720}
1721
1722int QgsGmlStreamingParser::createMultiLineFromFragments()
1723{
1724 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1725 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1726
1727 QgsWkbPtr wkbPtr( mCurrentWKB );
1728
1729 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiLineStringZ : Qgis::WkbType::MultiLineString ) << mCurrentWKBFragments.constBegin()->size();
1730
1731 //copy (and delete) all the wkb fragments
1732 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1733 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1734 {
1735 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1736 wkbPtr += wkbIt->size();
1737 }
1738
1739 mCurrentWKBFragments.clear();
1741 return 0;
1742}
1743
1744int QgsGmlStreamingParser::createMultiPointFromFragments()
1745{
1746 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1747 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1748
1749 QgsWkbPtr wkbPtr( mCurrentWKB );
1750 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint ) << mCurrentWKBFragments.constBegin()->size();
1751
1752 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1753 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1754 {
1755 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1756 wkbPtr += wkbIt->size();
1757 }
1758
1759 mCurrentWKBFragments.clear();
1760 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1761 return 0;
1762}
1763
1764
1765int QgsGmlStreamingParser::createPolygonFromFragments()
1766{
1767 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1768 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1769
1770 QgsWkbPtr wkbPtr( mCurrentWKB );
1771 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << mCurrentWKBFragments.constBegin()->size();
1772
1773 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1774 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1775 {
1776 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1777 wkbPtr += wkbIt->size();
1778 }
1779
1780 mCurrentWKBFragments.clear();
1781 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1782 return 0;
1783}
1784
1785int QgsGmlStreamingParser::createMultiPolygonFromFragments()
1786{
1787 int size = 0;
1788 size += 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) );
1789 size += totalWKBFragmentSize();
1790 size += mCurrentWKBFragments.size() * ( 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) ); //fragments are just the rings
1791
1792 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1793
1794 QgsWkbPtr wkbPtr( mCurrentWKB );
1795 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon ) << mCurrentWKBFragments.size();
1796
1797 //have outer and inner iterators
1798 auto outerWkbIt = mCurrentWKBFragments.constBegin();
1799
1800 for ( ; outerWkbIt != mCurrentWKBFragments.constEnd(); ++outerWkbIt )
1801 {
1802 //new polygon
1803 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << outerWkbIt->size();
1804
1805 auto innerWkbIt = outerWkbIt->constBegin();
1806 for ( ; innerWkbIt != outerWkbIt->constEnd(); ++innerWkbIt )
1807 {
1808 memcpy( wkbPtr, *innerWkbIt, innerWkbIt->size() );
1809 wkbPtr += innerWkbIt->size();
1810 }
1811 }
1812
1813 mCurrentWKBFragments.clear();
1814 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1815 return 0;
1816}
1817
1818int QgsGmlStreamingParser::totalWKBFragmentSize() const
1819{
1820 int result = 0;
1821 for ( const QList<QByteArray> &list : std::as_const( mCurrentWKBFragments ) )
1822 {
1823 for ( const QByteArray &i : list )
1824 {
1825 result += i.size();
1826 }
1827 }
1828 return result;
1829}
1830
1831void QgsGmlStreamingParser::createParser( const QByteArray &encoding )
1832{
1833 Q_ASSERT( !mParser );
1834
1835 mParser = XML_ParserCreateNS( encoding.isEmpty() ? nullptr : encoding.data(), NS_SEPARATOR );
1836 XML_SetUserData( mParser, this );
1837 XML_SetElementHandler( mParser, QgsGmlStreamingParser::start, QgsGmlStreamingParser::end );
1838 XML_SetCharacterDataHandler( mParser, QgsGmlStreamingParser::chars );
1839}
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:63
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:66
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.
static Q_INVOKABLE QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
QgsGeometry combine(const QgsGeometry &geometry, const QgsGeometryParameters &parameters=QgsGeometryParameters(), QgsFeedback *feedback=nullptr) const
Returns a geometry representing all the points in this geometry and other (a union geometry operation...
void fromWkb(unsigned char *wkb, int length)
Set the geometry, feeding in the buffer containing OGC Well-Known Binary and the buffer's length.
bool convertToMultiType()
Converts single type geometry into multitype geometry e.g.
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:80
#define QgsDebugError(str)
Definition qgslogger.h:71
#define GML32_NAMESPACE
#define QgsSetRequestInitiatorClass(request, _class)