QGIS API Documentation 4.3.0-Master (ca159e4aeaf)
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#if ( XML_MAJOR_VERSION > 2 || ( XML_MAJOR_VERSION == 2 && XML_MINOR_VERSION >= 9 ) )
477 errorMsg = QObject::tr( "Error: %1 on line %2, column %3" ).arg( XML_ErrorString( errorCode ) ).arg( XML_GetCurrentLineNumber64( mParser ) ).arg( XML_GetCurrentColumnNumber64( mParser ) );
478#else
479 errorMsg = QObject::tr( "Error: %1 on line %2, column %3" ).arg( XML_ErrorString( errorCode ) ).arg( XML_GetCurrentLineNumber( mParser ) ).arg( XML_GetCurrentColumnNumber( mParser ) );
480#endif
481
482 return false;
483 }
484
485 return true;
486}
487
488QVector<QgsGmlStreamingParser::QgsGmlFeaturePtrGmlIdPair> QgsGmlStreamingParser::getAndStealReadyFeatures()
489{
490 QVector<QgsGmlFeaturePtrGmlIdPair> ret = mFeatureList;
491 mFeatureList.clear();
492 return ret;
493}
494
499static json jsonFromString( const QString &s )
500{
501 bool conversionOk;
502
503 // Does it look like a floating-point value ?
504 if ( s.indexOf( '.' ) >= 0 || s.indexOf( 'e' ) >= 0 )
505 {
506 const auto doubleVal = s.toDouble( &conversionOk );
507 if ( conversionOk )
508 {
509 return json( doubleVal );
510 }
511 }
512 // Does it look like an integer? (but don't recognize strings starting with
513 // 0)
514 else if ( !s.isEmpty() && s[0] != '0' )
515 {
516 const auto longlongVal = s.toLongLong( &conversionOk );
517 if ( conversionOk )
518 {
519 return json( longlongVal );
520 }
521 }
522
523 return json( s.toStdString() );
524}
525
526#define LOCALNAME_EQUALS( string_constant ) ( localNameLen == static_cast<int>( strlen( string_constant ) ) && memcmp( pszLocalName, string_constant, localNameLen ) == 0 )
527
528void QgsGmlStreamingParser::startElement( const XML_Char *el, const XML_Char **attr )
529{
530 const int elLen = static_cast<int>( strlen( el ) );
531 const char *pszSep = strchr( el, NS_SEPARATOR );
532 const char *pszLocalName = ( pszSep ) ? pszSep + 1 : el;
533 const int nsLen = ( pszSep ) ? ( int ) ( pszSep - el ) : 0;
534 const int localNameLen = ( pszSep ) ? ( int ) ( elLen - nsLen ) - 1 : elLen;
535 const ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() );
536 int elDimension = 0;
537
538 // Figure out if the GML namespace is GML_NAMESPACE or GML32_NAMESPACE
539 if ( !mGMLNameSpaceURIPtr && pszSep )
540 {
541 if ( nsLen == static_cast<int>( strlen( GML_NAMESPACE ) ) && memcmp( el, GML_NAMESPACE, nsLen ) == 0 )
542 {
543 mGMLNameSpaceURI = GML_NAMESPACE;
544 mGMLNameSpaceURIPtr = GML_NAMESPACE;
545 }
546 else if ( nsLen == static_cast<int>( strlen( GML32_NAMESPACE ) ) && memcmp( el, GML32_NAMESPACE, nsLen ) == 0 )
547 {
548 mGMLNameSpaceURI = GML32_NAMESPACE;
549 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
550 }
551 }
552
553 const bool isGMLNS = ( nsLen == mGMLNameSpaceURI.size() && mGMLNameSpaceURIPtr && memcmp( el, mGMLNameSpaceURIPtr, nsLen ) == 0 );
554 bool isGeom = false;
555
556 if ( parseMode == Geometry || parseMode == Coordinate || parseMode == PosList || parseMode == MultiPoint || parseMode == MultiLine || parseMode == MultiPolygon )
557 {
558 mGeometryString.append( "<", 1 );
559 mGeometryString.append( pszLocalName, localNameLen );
560 mGeometryString.append( " ", 1 );
561 for ( const XML_Char **attrIter = attr; attrIter && *attrIter; attrIter += 2 )
562 {
563 const size_t nAttrLen = strlen( attrIter[0] );
564 const size_t GML32_NAMESPACE_LEN = strlen( GML32_NAMESPACE );
565 const size_t GML_NAMESPACE_LEN = strlen( GML_NAMESPACE );
566 if ( nAttrLen > GML32_NAMESPACE_LEN && attrIter[0][GML32_NAMESPACE_LEN] == '?' && memcmp( attrIter[0], GML32_NAMESPACE, GML32_NAMESPACE_LEN ) == 0 )
567 {
568 mGeometryString.append( "gml:" );
569 mGeometryString.append( attrIter[0] + GML32_NAMESPACE_LEN + 1 );
570 }
571 else if ( nAttrLen > GML_NAMESPACE_LEN && attrIter[0][GML_NAMESPACE_LEN] == '?' && memcmp( attrIter[0], GML_NAMESPACE, GML_NAMESPACE_LEN ) == 0 )
572 {
573 mGeometryString.append( "gml:" );
574 mGeometryString.append( attrIter[0] + GML_NAMESPACE_LEN + 1 );
575 }
576 else
577 {
578 mGeometryString.append( attrIter[0] );
579 }
580 mGeometryString.append( "=\"", 2 );
581 mGeometryString.append( attrIter[1] );
582 mGeometryString.append( "\" ", 2 );
583 }
584 mGeometryString.append( ">", 1 );
585 }
586
587 if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "coordinates" ) )
588 {
589 mParseModeStack.push( Coordinate );
590 mCoorMode = QgsGmlStreamingParser::Coordinate;
591 mStringCash.clear();
592 mCoordinateSeparator = readAttribute( u"cs"_s, attr );
593 if ( mCoordinateSeparator.isEmpty() )
594 {
595 mCoordinateSeparator = ',';
596 }
597 mTupleSeparator = readAttribute( u"ts"_s, attr );
598 if ( mTupleSeparator.isEmpty() )
599 {
600 mTupleSeparator = ' ';
601 }
602 }
603 else if ( !mAttributeValIsNested && isGMLNS && ( LOCALNAME_EQUALS( "pos" ) || LOCALNAME_EQUALS( "posList" ) ) )
604 {
605 mParseModeStack.push( QgsGmlStreamingParser::PosList );
606 if ( mCoorMode == QgsGmlStreamingParser::PosList )
607 {
608 if ( !mStringCash.isEmpty() )
609 {
610 mStringCash.append( ' '_L1 );
611 }
612 }
613 else
614 {
615 mStringCash.clear();
616 }
617 mCoorMode = QgsGmlStreamingParser::PosList;
618 elDimension = readSrsNameAndDimensionAttributes( attr );
619 }
620 else if ( ( parseMode == Feature || parseMode == FeatureTuple )
621 && mCurrentFeature
622 && localNameLen == static_cast<int>( mGeometryAttributeUTF8Len )
623 && memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
624 {
625 mParseModeStack.push( QgsGmlStreamingParser::Geometry );
626 mFoundUnhandledGeometryElement = false;
627 mGeometryString.clear();
628 mStringCash.clear();
629 }
630 //else if ( mParseModeStack.size() == 0 && elementName == mGMLNameSpaceURI + NS_SEPARATOR + "boundedBy" )
631 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "boundedBy" ) )
632 {
633 mParseModeStack.push( QgsGmlStreamingParser::BoundingBox );
634 mCurrentExtent = QgsRectangle();
635 mBoundedByNullFound = false;
636 }
637 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "null" ) )
638 {
639 mParseModeStack.push( QgsGmlStreamingParser::Null );
640 mBoundedByNullFound = true;
641 }
642 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "Envelope" ) )
643 {
644 isGeom = true;
645 mParseModeStack.push( QgsGmlStreamingParser::Envelope );
646 }
647 else if ( parseMode == Envelope && isGMLNS && LOCALNAME_EQUALS( "lowerCorner" ) )
648 {
649 mParseModeStack.push( QgsGmlStreamingParser::LowerCorner );
650 mStringCash.clear();
651 }
652 else if ( parseMode == Envelope && isGMLNS && LOCALNAME_EQUALS( "upperCorner" ) )
653 {
654 mParseModeStack.push( QgsGmlStreamingParser::UpperCorner );
655 mStringCash.clear();
656 }
657 else if ( parseMode == None && !mTypeNamePtr && LOCALNAME_EQUALS( "Tuple" ) )
658 {
659 Q_ASSERT( !mCurrentFeature );
660 mCurrentFeature = new QgsFeature( mFeatureCount );
661 mCurrentFeature->setFields( mFields ); // allow name-based attribute lookups
662 const QgsAttributes attributes( mThematicAttributes.size() ); //add empty attributes
663 mCurrentFeature->setAttributes( attributes );
664 mParseModeStack.push( QgsGmlStreamingParser::Tuple );
665 mCurrentFeatureId.clear();
666 }
667 else if ( parseMode == Tuple )
668 {
669 const QString currentTypename( QString::fromUtf8( pszLocalName, localNameLen ) );
670 const QMap< QString, LayerProperties >::const_iterator iter = mMapTypeNameToProperties.constFind( currentTypename );
671 if ( iter != mMapTypeNameToProperties.constEnd() )
672 {
673 mFeatureTupleDepth = mParseDepth;
674 mCurrentTypename = currentTypename;
675 mGeometryAttribute.clear();
676 if ( mCurrentWKB.size() == 0 )
677 {
678 mGeometryAttribute = iter.value().mGeometryAttribute;
679 }
680 mGeometryAttributeBA = mGeometryAttribute.toUtf8();
681 mGeometryAttributePtr = mGeometryAttributeBA.constData();
682 mGeometryAttributeUTF8Len = strlen( mGeometryAttributePtr );
683 mParseModeStack.push( QgsGmlStreamingParser::FeatureTuple );
684 QString id;
685 if ( mGMLNameSpaceURI.isEmpty() )
686 {
687 id = readAttribute( QString( GML_NAMESPACE ) + NS_SEPARATOR + "id", attr );
688 if ( !id.isEmpty() )
689 {
690 mGMLNameSpaceURI = GML_NAMESPACE;
691 mGMLNameSpaceURIPtr = GML_NAMESPACE;
692 }
693 else
694 {
695 id = readAttribute( QString( GML32_NAMESPACE ) + NS_SEPARATOR + "id", attr );
696 if ( !id.isEmpty() )
697 {
698 mGMLNameSpaceURI = GML32_NAMESPACE;
699 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
700 }
701 }
702 }
703 else
704 id = readAttribute( mGMLNameSpaceURI + NS_SEPARATOR + "id", attr );
705 if ( !mCurrentFeatureId.isEmpty() )
706 mCurrentFeatureId += '|';
707 mCurrentFeatureId += id;
708 }
709 }
710 else if ( parseMode == None && localNameLen == static_cast<int>( mTypeNameUTF8Len ) && mTypeNamePtr && memcmp( pszLocalName, mTypeNamePtr, mTypeNameUTF8Len ) == 0 )
711 {
712 Q_ASSERT( !mCurrentFeature );
713 mCurrentFeature = new QgsFeature( mFeatureCount );
714 mCurrentFeature->setFields( mFields ); // allow name-based attribute lookups
715 const QgsAttributes attributes( mThematicAttributes.size() ); //add empty attributes
716 mCurrentFeature->setAttributes( attributes );
717 mParseModeStack.push( QgsGmlStreamingParser::Feature );
718 mCurrentXPathWithinFeature.clear();
719 mCurrentFeatureId = readAttribute( u"fid"_s, attr );
720 if ( mCurrentFeatureId.isEmpty() )
721 {
722 // Figure out if the GML namespace is GML_NAMESPACE or GML32_NAMESPACE
723 // (should happen only for the first features if there's no gml: element
724 // encountered before
725 if ( mGMLNameSpaceURI.isEmpty() )
726 {
727 mCurrentFeatureId = readAttribute( QString( GML_NAMESPACE ) + NS_SEPARATOR + "id", attr );
728 if ( !mCurrentFeatureId.isEmpty() )
729 {
730 mGMLNameSpaceURI = GML_NAMESPACE;
731 mGMLNameSpaceURIPtr = GML_NAMESPACE;
732 }
733 else
734 {
735 mCurrentFeatureId = readAttribute( QString( GML32_NAMESPACE ) + NS_SEPARATOR + "id", attr );
736 if ( !mCurrentFeatureId.isEmpty() )
737 {
738 mGMLNameSpaceURI = GML32_NAMESPACE;
739 mGMLNameSpaceURIPtr = GML32_NAMESPACE;
740 }
741 }
742 }
743 else
744 mCurrentFeatureId = readAttribute( mGMLNameSpaceURI + NS_SEPARATOR + "id", attr );
745 }
746 }
747
748 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "Box" ) )
749 {
750 isGeom = true;
751 }
752 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "Point" ) )
753 {
754 isGeom = true;
755 }
756 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "LineString" ) )
757 {
758 isGeom = true;
759 }
760 else if ( !mAttributeValIsNested && isGMLNS && localNameLen == static_cast<int>( strlen( "Polygon" ) ) && memcmp( pszLocalName, "Polygon", localNameLen ) == 0 )
761 {
762 isGeom = true;
763 mCurrentWKBFragments.push_back( QList<QByteArray>() );
764 }
765 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "MultiPoint" ) )
766 {
767 isGeom = true;
768 mParseModeStack.push( QgsGmlStreamingParser::MultiPoint );
769 //we need one nested list for intermediate WKB
770 mCurrentWKBFragments.push_back( QList<QByteArray>() );
771 }
772 else if ( !mAttributeValIsNested && isGMLNS && ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
773 {
774 isGeom = true;
775 mParseModeStack.push( QgsGmlStreamingParser::MultiLine );
776 //we need one nested list for intermediate WKB
777 mCurrentWKBFragments.push_back( QList<QByteArray>() );
778 }
779 else if ( !mAttributeValIsNested && isGMLNS && ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
780 {
781 isGeom = true;
782 mParseModeStack.push( QgsGmlStreamingParser::MultiPolygon );
783 }
784 else if ( parseMode == FeatureTuple )
785 {
786 const QString localName( QString::fromUtf8( pszLocalName, localNameLen ) );
787 if ( mThematicAttributes.contains( mCurrentTypename + '|' + localName ) )
788 {
789 mParseModeStack.push( QgsGmlStreamingParser::AttributeTuple );
790 mAttributeName = mCurrentTypename + '|' + localName;
791 mStringCash.clear();
792 }
793 }
794 else if ( parseMode == Feature )
795 {
796 const QString localName( QString::fromUtf8( pszLocalName, localNameLen ) );
797 if ( !mMapXPathToFieldNameAndIsNestedContent.isEmpty() )
798 {
799 const QString nsURI( nsLen ? QString::fromUtf8( el, nsLen ) : QString() );
800 const auto nsIter = mMapNamespaceURIToNamespacePrefix.constFind( nsURI );
801 if ( !mCurrentXPathWithinFeature.isEmpty() )
802 mCurrentXPathWithinFeature.append( '/' );
803 if ( nsIter != mMapNamespaceURIToNamespacePrefix.constEnd() )
804 {
805 mCurrentXPathWithinFeature.append( *nsIter );
806 mCurrentXPathWithinFeature.append( ':' );
807 }
808 mCurrentXPathWithinFeature.append( localName );
809 const auto xpathIter = mMapXPathToFieldNameAndIsNestedContent.constFind( mCurrentXPathWithinFeature );
810 mAttributeValIsNested = false;
811 if ( xpathIter != mMapXPathToFieldNameAndIsNestedContent.end() )
812 {
813 mParseModeStack.push( QgsGmlStreamingParser::Attribute );
814 mAttributeDepth = mParseDepth;
815 mAttributeName = xpathIter->first;
816 mAttributeValIsNested = xpathIter->second;
817 if ( mAttributeValIsNested )
818 {
819 mAttributeJson = json::object();
820 mAttributeJsonCurrentStack.clear();
821 mAttributeJsonCurrentStack.push( &mAttributeJson );
822 }
823 mStringCash.clear();
824 }
825 }
826 else if ( mThematicAttributes.contains( localName ) )
827 {
828 mParseModeStack.push( QgsGmlStreamingParser::Attribute );
829 mAttributeDepth = mParseDepth;
830 mAttributeName = localName;
831 mStringCash.clear();
832 }
833 else
834 {
835 // QGIS server (2.2) is using:
836 // <Attribute value="My description" name="desc"/>
837 if ( localName.compare( "attribute"_L1, Qt::CaseInsensitive ) == 0 )
838 {
839 const QString name = readAttribute( u"name"_s, attr );
840 if ( mThematicAttributes.contains( name ) )
841 {
842 const QString value = readAttribute( u"value"_s, attr );
843 setAttribute( name, value );
844 }
845 }
846 }
847 }
848 else if ( parseMode == Attribute && mAttributeValIsNested )
849 {
850 const std::string localName( pszLocalName, localNameLen );
851 const QString nsURI( nsLen ? QString::fromUtf8( el, nsLen ) : QString() );
852 const auto nsIter = mMapNamespaceURIToNamespacePrefix.constFind( nsURI );
853 const std::string nodeName = nsIter != mMapNamespaceURIToNamespacePrefix.constEnd() ? ( *nsIter ).toStdString() + ':' + localName : localName;
854
855 addStringContentToJson();
856
857 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
858 auto iter = jsonParent.find( nodeName );
859 if ( iter != jsonParent.end() )
860 {
861 if ( iter->type() != json::value_t::array )
862 {
863 auto array = json::array();
864 array.emplace_back( std::move( *iter ) );
865 *iter = array;
866 }
867 iter->push_back( json::object() );
868 mAttributeJsonCurrentStack.push( &( iter->back() ) );
869 }
870 else
871 {
872 auto res = jsonParent.emplace( nodeName, json::object() );
873 // res.first is a json::iterator
874 // Dereferencing it leads to a json reference
875 // And taking a reference on it gets a pointer
876 nlohmann::json *ptr = &( *( res.first ) );
877 // cppcheck-suppress danglingLifetime
878 mAttributeJsonCurrentStack.push( ptr );
879 }
880 }
881 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "FeatureCollection" ) )
882 {
883 QString numberReturned = readAttribute( u"numberReturned"_s, attr ); // WFS 2.0
884 if ( numberReturned.isEmpty() )
885 numberReturned = readAttribute( u"numberOfFeatures"_s, attr ); // WFS 1.1
886 bool conversionOk;
887 mNumberReturned = numberReturned.toInt( &conversionOk );
888 if ( !conversionOk )
889 mNumberReturned = -1;
890
891 const QString numberMatched = readAttribute( u"numberMatched"_s, attr ); // WFS 2.0
892 mNumberMatched = numberMatched.toInt( &conversionOk );
893 if ( !conversionOk ) // likely since numberMatched="unknown" is legal
894 mNumberMatched = -1;
895 }
896 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
897 {
898 mIsException = true;
899 mParseModeStack.push( QgsGmlStreamingParser::ExceptionReport );
900 }
901 else if ( mIsException && LOCALNAME_EQUALS( "ExceptionText" ) )
902 {
903 mStringCash.clear();
904 mParseModeStack.push( QgsGmlStreamingParser::ExceptionText );
905 }
906 else if ( mParseDepth == 1 && LOCALNAME_EQUALS( "truncatedResponse" ) )
907 {
908 // e.g: http://services.cuzk.cz/wfs/inspire-cp-wfs.asp?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=cp:CadastralParcel
909 mTruncatedResponse = true;
910 }
911 else if ( !mGeometryString.empty()
912 && !LOCALNAME_EQUALS( "exterior" )
913 && !LOCALNAME_EQUALS( "interior" )
914 && !LOCALNAME_EQUALS( "innerBoundaryIs" )
915 && !LOCALNAME_EQUALS( "outerBoundaryIs" )
916 && !LOCALNAME_EQUALS( "LinearRing" )
917 && !LOCALNAME_EQUALS( "pointMember" )
918 && !LOCALNAME_EQUALS( "curveMember" )
919 && !LOCALNAME_EQUALS( "lineStringMember" )
920 && !LOCALNAME_EQUALS( "polygonMember" )
921 && !LOCALNAME_EQUALS( "surfaceMember" )
922 && !LOCALNAME_EQUALS( "Curve" )
923 && !LOCALNAME_EQUALS( "segments" )
924 && !LOCALNAME_EQUALS( "LineStringSegment" ) )
925 {
926 //QgsDebugError( "Found unhandled geometry element " + QString::fromUtf8( pszLocalName, localNameLen ) );
927 mFoundUnhandledGeometryElement = true;
928 }
929
930 // Handle XML attributes in XPath mode
931 if ( !mParseModeStack.isEmpty() && ( mParseModeStack.back() == Feature || mParseModeStack.back() == Attribute ) && !mMapXPathToFieldNameAndIsNestedContent.isEmpty() )
932 {
933 for ( const XML_Char **attrIter = attr; attrIter && *attrIter; attrIter += 2 )
934 {
935 const char *questionMark = strchr( attrIter[0], '?' );
936 QString key( '@' );
937 if ( questionMark )
938 {
939 const QString nsURI( QString::fromUtf8( attrIter[0], static_cast<int>( questionMark - attrIter[0] ) ) );
940 const QString localName( QString::fromUtf8( questionMark + 1 ) );
941 const auto nsIter = mMapNamespaceURIToNamespacePrefix.constFind( nsURI );
942 if ( nsIter != mMapNamespaceURIToNamespacePrefix.constEnd() )
943 {
944 key.append( *nsIter );
945 key.append( ':' );
946 }
947 key.append( localName );
948 }
949 else
950 {
951 const QString localName( QString::fromUtf8( attrIter[0] ) );
952 key.append( localName );
953 }
954
955 if ( mAttributeValIsNested && mParseModeStack.back() == Attribute )
956 {
957 mAttributeJsonCurrentStack.top()->emplace( key.toStdString(), jsonFromString( QString::fromUtf8( attrIter[1] ) ) );
958 }
959 else
960 {
961 QString xpath( mCurrentXPathWithinFeature );
962 if ( !xpath.isEmpty() )
963 xpath.append( '/' );
964 xpath.append( key );
965 const auto xpathIter = mMapXPathToFieldNameAndIsNestedContent.constFind( xpath );
966 if ( xpathIter != mMapXPathToFieldNameAndIsNestedContent.end() )
967 {
968 setAttribute( xpathIter->first, QString::fromUtf8( attrIter[1] ) );
969 }
970 }
971 }
972 }
973
974 if ( !mGeometryString.empty() )
975 isGeom = true;
976
977 if ( isGeom )
978 {
979 elDimension = readSrsNameAndDimensionAttributes( attr );
980 }
981
982 if ( elDimension != 0 || mDimensionStack.isEmpty() )
983 {
984 mDimensionStack.push( elDimension );
985 }
986 else
987 {
988 mDimensionStack.push( mDimensionStack.back() );
989 }
990
991 mParseDepth++;
992}
993
994void QgsGmlStreamingParser::endElement( const XML_Char *el )
995{
996 mParseDepth--;
997
998 const int elLen = static_cast<int>( strlen( el ) );
999 const char *pszSep = strchr( el, NS_SEPARATOR );
1000 const char *pszLocalName = ( pszSep ) ? pszSep + 1 : el;
1001 const int nsLen = ( pszSep ) ? ( int ) ( pszSep - el ) : 0;
1002 const int localNameLen = ( pszSep ) ? ( int ) ( elLen - nsLen ) - 1 : elLen;
1003 const ParseMode parseMode( mParseModeStack.isEmpty() ? None : mParseModeStack.top() );
1004
1005 const int lastDimension = mDimensionStack.isEmpty() ? 2 : mDimensionStack.pop();
1006
1007 const bool isGMLNS = ( nsLen == mGMLNameSpaceURI.size() && mGMLNameSpaceURIPtr && memcmp( el, mGMLNameSpaceURIPtr, nsLen ) == 0 );
1008
1009 if ( parseMode == Feature || ( parseMode == Attribute && mAttributeDepth == mParseDepth ) )
1010 {
1011 if ( !mMapXPathToFieldNameAndIsNestedContent.isEmpty() )
1012 {
1013 const auto nPos = mCurrentXPathWithinFeature.lastIndexOf( '/' );
1014 if ( nPos < 0 )
1015 mCurrentXPathWithinFeature.clear();
1016 else
1017 mCurrentXPathWithinFeature.resize( nPos );
1018 }
1019 }
1020
1021 if ( parseMode == Attribute && mAttributeValIsNested )
1022 {
1023 if ( !mStringCash.isEmpty() )
1024 {
1025 // If the attribute name is the geometryAttribute, we try to parse the text value as WKT
1026 if ( localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) && memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
1027 {
1028 QgsGeometry g;
1029 g = QgsGeometry::fromWkt( mStringCash );
1030 if ( !g.isNull() )
1031 {
1032 if ( mInvertAxisOrientation )
1033 {
1034 g.transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
1035 }
1036 Q_ASSERT( mCurrentFeature );
1037 // Always convert to multi-type
1039 // And append if a geometry already exists
1040 QgsGeometry currentGeometry = mCurrentFeature->geometry();
1041 if ( !currentGeometry.isNull() )
1042 {
1043 g = g.combine( currentGeometry );
1044 }
1045 mCurrentFeature->setGeometry( g );
1046 }
1047 }
1048
1049 // We still add the nested child to the JSON structure
1050 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
1051 if ( jsonParent.type() == json::value_t::object && jsonParent.empty() )
1052 {
1053 jsonParent = jsonFromString( mStringCash );
1054 }
1055 else if ( jsonParent.type() == json::value_t::object )
1056 {
1057 addStringContentToJson();
1058 }
1059 mStringCash.clear();
1060 }
1061 mAttributeJsonCurrentStack.pop();
1062 }
1063
1064 if ( parseMode == Coordinate && isGMLNS && LOCALNAME_EQUALS( "coordinates" ) )
1065 {
1066 mParseModeStack.pop();
1067 }
1068 else if ( parseMode == PosList && isGMLNS && ( LOCALNAME_EQUALS( "pos" ) || LOCALNAME_EQUALS( "posList" ) ) )
1069 {
1070 mDimension = lastDimension;
1071 mParseModeStack.pop();
1072 }
1073 else if ( parseMode == AttributeTuple && mCurrentTypename + '|' + QString::fromUtf8( pszLocalName, localNameLen ) == mAttributeName ) //add a thematic attribute to the feature
1074 {
1075 mParseModeStack.pop();
1076
1077 setAttribute( mAttributeName, mStringCash );
1078 }
1079 else if ( parseMode == Attribute && mAttributeDepth == mParseDepth ) //add a thematic attribute to the feature
1080 {
1081 mParseModeStack.pop();
1082 mParseDepth = -1;
1083
1084 if ( mAttributeValIsNested )
1085 {
1086 mAttributeValIsNested = false;
1087 auto iter = mMapFieldNameToJSONContent.find( mAttributeName );
1088 if ( iter == mMapFieldNameToJSONContent.end() )
1089 {
1090 mMapFieldNameToJSONContent[mAttributeName] = QString::fromStdString( mAttributeJson.dump() );
1091 }
1092 else
1093 {
1094 QString &str = iter.value();
1095 if ( str[0] == '[' && str.back() == ']' )
1096 {
1097 str.back() = ',';
1098 }
1099 else
1100 {
1101 str.insert( 0, '[' );
1102 str.append( ',' );
1103 }
1104 str.append( QString::fromStdString( mAttributeJson.dump() ) );
1105 str.append( ']' );
1106 }
1107 }
1108 else
1109 {
1110 setAttribute( mAttributeName, mStringCash );
1111 }
1112 }
1113 else if ( parseMode == Geometry && localNameLen == static_cast<int>( mGeometryAttributeUTF8Len ) && memcmp( pszLocalName, mGeometryAttributePtr, localNameLen ) == 0 )
1114 {
1115 mParseModeStack.pop();
1116 if ( mFoundUnhandledGeometryElement )
1117 {
1118 const gdal::ogr_geometry_unique_ptr hGeom( OGR_G_CreateFromGML( mGeometryString.c_str() ) );
1119 //QgsDebugMsgLevel( u"for OGR: %1 -> %2"_s.arg(mGeometryString.c_str()).arg(hGeom != nullptr), 2);
1120 if ( hGeom )
1121 {
1122 const int wkbSize = OGR_G_WkbSize( hGeom.get() );
1123 unsigned char *pabyBuffer = new unsigned char[wkbSize];
1124 OGR_G_ExportToIsoWkb( hGeom.get(), wkbNDR, pabyBuffer );
1125 QgsGeometry g;
1126 g.fromWkb( pabyBuffer, wkbSize );
1127 if ( mInvertAxisOrientation )
1128 {
1129 g.transform( QTransform( 0, 1, 1, 0, 0, 0 ) );
1130 }
1131 Q_ASSERT( mCurrentFeature );
1132 mCurrentFeature->setGeometry( g );
1133 }
1134 }
1135 mGeometryString.clear();
1136 }
1137 else if ( parseMode == BoundingBox && isGMLNS && LOCALNAME_EQUALS( "boundedBy" ) )
1138 {
1139 //create bounding box from mStringCash
1140 if ( mCurrentExtent.isNull() && !mBoundedByNullFound && !createBBoxFromCoordinateString( mCurrentExtent, mStringCash ) )
1141 {
1142 QgsDebugError( u"creation of bounding box failed"_s );
1143 }
1144 if ( !mCurrentExtent.isNull() && mLayerExtent.isNull() && !mCurrentFeature && mFeatureCount == 0 )
1145 {
1146 mLayerExtent = mCurrentExtent;
1147 mCurrentExtent = QgsRectangle();
1148 }
1149
1150 mParseModeStack.pop();
1151 }
1152 else if ( parseMode == Null && isGMLNS && LOCALNAME_EQUALS( "null" ) )
1153 {
1154 mParseModeStack.pop();
1155 }
1156 else if ( parseMode == Envelope && isGMLNS && LOCALNAME_EQUALS( "Envelope" ) )
1157 {
1158 mParseModeStack.pop();
1159 }
1160 else if ( parseMode == LowerCorner && isGMLNS && LOCALNAME_EQUALS( "lowerCorner" ) )
1161 {
1162 QList<QgsPoint> points;
1163 pointsFromPosListString( points, mStringCash, 2 );
1164 if ( points.size() == 1 )
1165 {
1166 mCurrentExtent.setXMinimum( points[0].x() );
1167 mCurrentExtent.setYMinimum( points[0].y() );
1168 }
1169 mParseModeStack.pop();
1170 }
1171 else if ( parseMode == UpperCorner && isGMLNS && LOCALNAME_EQUALS( "upperCorner" ) )
1172 {
1173 QList<QgsPoint> points;
1174 pointsFromPosListString( points, mStringCash, 2 );
1175 if ( points.size() == 1 )
1176 {
1177 mCurrentExtent.setXMaximum( points[0].x() );
1178 mCurrentExtent.setYMaximum( points[0].y() );
1179 }
1180 mParseModeStack.pop();
1181 }
1182 else if ( parseMode == FeatureTuple && mParseDepth == mFeatureTupleDepth )
1183 {
1184 mParseModeStack.pop();
1185 mFeatureTupleDepth = 0;
1186 }
1187 else if ( ( parseMode == Tuple && !mTypeNamePtr && LOCALNAME_EQUALS( "Tuple" ) )
1188 || ( parseMode == Feature && localNameLen == static_cast<int>( mTypeNameUTF8Len ) && memcmp( pszLocalName, mTypeNamePtr, mTypeNameUTF8Len ) == 0 ) )
1189 {
1190 Q_ASSERT( mCurrentFeature );
1191 if ( !mCurrentFeature->hasGeometry() )
1192 {
1193 if ( mCurrentWKB.size() > 0 )
1194 {
1195 QgsGeometry g;
1196 g.fromWkb( mCurrentWKB );
1197 mCurrentFeature->setGeometry( g );
1198 mCurrentWKB = QByteArray();
1199 }
1200 else if ( !mCurrentExtent.isEmpty() )
1201 {
1202 mCurrentFeature->setGeometry( QgsGeometry::fromRect( mCurrentExtent ) );
1203 }
1204 }
1205 mCurrentFeature->setValid( true );
1206
1207 for ( auto iter = mMapFieldNameToJSONContent.constBegin(); iter != mMapFieldNameToJSONContent.constEnd(); ++iter )
1208 {
1209 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( iter.key() );
1210 const int attrIndex = att_it.value().first;
1211 mCurrentFeature->setAttribute( attrIndex, iter.value() );
1212 }
1213 mMapFieldNameToJSONContent.clear();
1214
1215 mFeatureList.push_back( QgsGmlFeaturePtrGmlIdPair( mCurrentFeature, mCurrentFeatureId ) );
1216
1217 mCurrentFeature = nullptr;
1218 ++mFeatureCount;
1219 mParseModeStack.pop();
1220 }
1221 else if ( !mAttributeValIsNested && isGMLNS && LOCALNAME_EQUALS( "Point" ) )
1222 {
1223 QList<QgsPoint> pointList;
1224 int dimension = 0;
1225 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1226 {
1227 //error
1228 }
1229 mStringCash.clear();
1230 mDimension = dimension;
1231
1232 if ( pointList.isEmpty() )
1233 return; // error
1234
1235 if ( parseMode == QgsGmlStreamingParser::Geometry )
1236 {
1237 //directly add WKB point to the feature
1238 if ( getPointWKB( mCurrentWKB, *( pointList.constBegin() ), dimension ) != 0 )
1239 {
1240 //error
1241 }
1242
1243 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPoint ) //keep multitype in case of geometry type mix
1244 {
1245 mWkbType = dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point;
1246 }
1247 }
1248 else //multipoint, add WKB as fragment
1249 {
1250 QByteArray wkbPtr;
1251 if ( getPointWKB( wkbPtr, *( pointList.constBegin() ), dimension ) != 0 )
1252 {
1253 //error
1254 }
1255 if ( !mCurrentWKBFragments.isEmpty() )
1256 {
1257 mCurrentWKBFragments.last().push_back( wkbPtr );
1258 }
1259 else
1260 {
1261 QgsDebugError( u"No wkb fragments"_s );
1262 }
1263 }
1264 }
1265 else if ( !mAttributeValIsNested && isGMLNS && ( LOCALNAME_EQUALS( "LineString" ) || LOCALNAME_EQUALS( "LineStringSegment" ) ) )
1266 {
1267 //add WKB point to the feature
1268
1269 QList<QgsPoint> pointList;
1270 int dimension = 0;
1271 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1272 {
1273 //error
1274 }
1275 mStringCash.clear();
1276 mDimension = dimension;
1277
1278 if ( parseMode == QgsGmlStreamingParser::Geometry )
1279 {
1280 if ( getLineWKB( mCurrentWKB, pointList, dimension ) != 0 )
1281 {
1282 //error
1283 }
1284
1285 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiLineString ) //keep multitype in case of geometry type mix
1286 {
1287 mWkbType = dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString;
1288 }
1289 else if ( dimension > 2 )
1290 {
1292 }
1293 mDimension = dimension;
1294 }
1295 else //multiline, add WKB as fragment
1296 {
1297 QByteArray wkbPtr;
1298 if ( getLineWKB( wkbPtr, pointList, dimension ) != 0 )
1299 {
1300 //error
1301 }
1302 mDimension = dimension;
1303 if ( !mCurrentWKBFragments.isEmpty() )
1304 {
1305 mCurrentWKBFragments.last().push_back( wkbPtr );
1306 }
1307 else
1308 {
1309 QgsDebugError( u"no wkb fragments"_s );
1310 }
1311 }
1312 }
1313 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS && LOCALNAME_EQUALS( "LinearRing" ) )
1314 {
1315 QList<QgsPoint> pointList;
1316 int dimension = 0;
1317 if ( pointsFromString( pointList, mStringCash, &dimension ) != 0 )
1318 {
1319 //error
1320 }
1321 mStringCash.clear();
1322 mDimension = dimension;
1323
1324 QByteArray wkbPtr;
1325 if ( getRingWKB( wkbPtr, pointList, dimension ) != 0 )
1326 {
1327 //error
1328 }
1329
1330 if ( !mCurrentWKBFragments.isEmpty() )
1331 {
1332 mCurrentWKBFragments.last().push_back( wkbPtr );
1333 }
1334 else
1335 {
1336 QgsDebugError( u"no wkb fragments"_s );
1337 }
1338 }
1339 else if ( ( parseMode == Geometry || parseMode == MultiPolygon ) && isGMLNS && LOCALNAME_EQUALS( "Polygon" ) )
1340 {
1341 if ( QgsWkbTypes::flatType( mWkbType ) != Qgis::WkbType::MultiPolygon ) //keep multitype in case of geometry type mix
1342 {
1343 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1344 }
1345
1346 if ( parseMode == Geometry )
1347 {
1348 createPolygonFromFragments();
1349 }
1350 }
1351 else if ( parseMode == MultiPoint && isGMLNS && LOCALNAME_EQUALS( "MultiPoint" ) )
1352 {
1353 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1354 mParseModeStack.pop();
1355 createMultiPointFromFragments();
1356 }
1357 else if ( parseMode == MultiLine && isGMLNS && ( LOCALNAME_EQUALS( "MultiLineString" ) || LOCALNAME_EQUALS( "MultiCurve" ) ) )
1358 {
1360 mParseModeStack.pop();
1361 createMultiLineFromFragments();
1362 }
1363 else if ( parseMode == MultiPolygon && isGMLNS && ( LOCALNAME_EQUALS( "MultiPolygon" ) || LOCALNAME_EQUALS( "MultiSurface" ) ) )
1364 {
1365 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1366 mParseModeStack.pop();
1367 createMultiPolygonFromFragments();
1368 }
1369 else if ( mParseDepth == 0 && LOCALNAME_EQUALS( "ExceptionReport" ) )
1370 {
1371 mParseModeStack.pop();
1372 }
1373 else if ( parseMode == ExceptionText && LOCALNAME_EQUALS( "ExceptionText" ) )
1374 {
1375 mExceptionText = mStringCash;
1376 mParseModeStack.pop();
1377 }
1378
1379 if ( !mGeometryString.empty() )
1380 {
1381 mGeometryString.append( "</", 2 );
1382 mGeometryString.append( pszLocalName, localNameLen );
1383 mGeometryString.append( ">", 1 );
1384 }
1385}
1386
1387void QgsGmlStreamingParser::characters( const XML_Char *chars, int len )
1388{
1389 //save chars in mStringCash attribute mode or coordinate mode
1390 if ( mParseModeStack.isEmpty() )
1391 {
1392 return;
1393 }
1394
1395 if ( !mGeometryString.empty() )
1396 {
1397 mGeometryString.append( chars, len );
1398 }
1399
1400 const QgsGmlStreamingParser::ParseMode parseMode = mParseModeStack.top();
1401 if ( parseMode == QgsGmlStreamingParser::Attribute
1402 || parseMode == QgsGmlStreamingParser::AttributeTuple
1403 || parseMode == QgsGmlStreamingParser::Coordinate
1404 || parseMode == QgsGmlStreamingParser::PosList
1405 || parseMode == QgsGmlStreamingParser::LowerCorner
1406 || parseMode == QgsGmlStreamingParser::UpperCorner
1407 || parseMode == QgsGmlStreamingParser::ExceptionText )
1408 {
1409 mStringCash.append( QString::fromUtf8( chars, len ) );
1410 }
1411}
1412
1413void QgsGmlStreamingParser::addStringContentToJson()
1414{
1415 const QString s( mStringCash.trimmed() );
1416 if ( !s.isEmpty() )
1417 {
1418 auto &jsonParent = *( mAttributeJsonCurrentStack.top() );
1419 auto textIter = jsonParent.find( "_text" );
1420 if ( textIter != jsonParent.end() )
1421 {
1422 if ( textIter->type() != json::value_t::array )
1423 {
1424 auto array = json::array();
1425 array.emplace_back( std::move( *textIter ) );
1426 *textIter = array;
1427 }
1428 textIter->emplace_back( jsonFromString( s ) );
1429 }
1430 else
1431 {
1432 jsonParent.emplace( "_text", jsonFromString( s ) );
1433 }
1434 }
1435 mStringCash.clear();
1436}
1437
1438void QgsGmlStreamingParser::setAttribute( const QString &name, const QString &value )
1439{
1440 //find index with attribute name
1441 const QMap<QString, QPair<int, QgsField> >::const_iterator att_it = mThematicAttributes.constFind( name );
1442 bool conversionOk = true;
1443 if ( att_it != mThematicAttributes.constEnd() )
1444 {
1445 QVariant var;
1446 switch ( att_it.value().second.type() )
1447 {
1448 case QMetaType::Type::Double:
1449 var = QVariant( value.toDouble( &conversionOk ) );
1450 break;
1451 case QMetaType::Type::Int:
1452 var = QVariant( value.toInt( &conversionOk ) );
1453 break;
1454 case QMetaType::Type::LongLong:
1455 var = QVariant( value.toLongLong( &conversionOk ) );
1456 break;
1457 case QMetaType::Type::QDateTime:
1458 var = QVariant( QDateTime::fromString( value, Qt::ISODate ) );
1459 break;
1460 default: //string type is default
1461 var = QVariant( value );
1462 break;
1463 }
1464 if ( !conversionOk ) // Assume is NULL
1465 {
1466 var = QVariant();
1467 }
1468 Q_ASSERT( mCurrentFeature );
1469 mCurrentFeature->setAttribute( att_it.value().first, var );
1470 }
1471}
1472
1473int QgsGmlStreamingParser::readSrsNameAndDimensionAttributes( const XML_Char **attr )
1474{
1475 int elDimension = 0;
1476 for ( int i = 0; attr[i] && attr[i + 1]; i += 2 )
1477 {
1478 if ( strcmp( attr[i], "srsName" ) == 0 )
1479 {
1480 const QString srsName( attr[i + 1] );
1481 if ( mSrsName.isEmpty() )
1482 {
1483 QString authority;
1484 QString code;
1485 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( srsName, authority, code );
1486 if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
1487 {
1488 const bool bIsUrn = ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_URN || crsFlavor == QgsOgcCrsUtils::CRSFlavor::X_OGC_URN || crsFlavor == QgsOgcCrsUtils::CRSFlavor::OGC_HTTP_URI );
1489 const QgsCoordinateReferenceSystem crs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( srsName );
1490 if ( crs.isValid() )
1491 {
1492 mSrsName = srsName;
1493 if ( ( ( mAxisOrientationLogic == Honour_EPSG_if_urn && bIsUrn ) || mAxisOrientationLogic == Honour_EPSG ) && crs.hasAxisInverted() )
1494 {
1495 mInvertAxisOrientation = !mInvertAxisOrientationRequest;
1496 }
1497
1498 mDimensionForCurSrsName = crs.hasVerticalAxis() ? 3 : 2;
1499 if ( elDimension == 0 )
1500 elDimension = mDimensionForCurSrsName;
1501 }
1502 }
1503 }
1504 else if ( srsName == mSrsName && elDimension == 0 )
1505 {
1506 elDimension = mDimensionForCurSrsName;
1507 }
1508 }
1509 else if ( strcmp( attr[i], "srsDimension" ) == 0 )
1510 {
1511 const QString srsDimension( attr[i + 1] );
1512 bool ok = false;
1513 const int dimension = srsDimension.toInt( &ok );
1514 if ( ok )
1515 {
1516 elDimension = dimension;
1517 }
1518 }
1519 }
1520
1521 return elDimension;
1522}
1523
1524QString QgsGmlStreamingParser::readAttribute( const QString &attributeName, const XML_Char **attr ) const
1525{
1526 int i = 0;
1527 while ( attr[i] )
1528 {
1529 if ( attributeName.compare( attr[i] ) == 0 )
1530 {
1531 return QString::fromUtf8( attr[i + 1] );
1532 }
1533 i += 2;
1534 }
1535 return QString();
1536}
1537
1538bool QgsGmlStreamingParser::createBBoxFromCoordinateString( QgsRectangle &r, const QString &coordString ) const
1539{
1540 QList<QgsPoint> points;
1541 if ( pointsFromCoordinateString( points, coordString ) != 0 )
1542 {
1543 return false;
1544 }
1545
1546 if ( points.size() < 2 )
1547 {
1548 return false;
1549 }
1550
1551 r.set( points[0], points[1] );
1552
1553 return true;
1554}
1555
1556int QgsGmlStreamingParser::pointsFromCoordinateString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1557{
1558 //tuples are separated by space, x/y by ','
1559 const QStringList tuples = coordString.split( mTupleSeparator, Qt::SkipEmptyParts );
1560 QStringList tuples_coordinates;
1561 double x, y, z;
1562 bool conversionSuccess;
1563
1564 if ( dimension )
1565 *dimension = 0;
1566
1567 QStringList::const_iterator tupleIterator;
1568 for ( tupleIterator = tuples.constBegin(); tupleIterator != tuples.constEnd(); ++tupleIterator )
1569 {
1570 tuples_coordinates = tupleIterator->split( mCoordinateSeparator, Qt::SkipEmptyParts );
1571 if ( dimension )
1572 {
1573 *dimension = std::max( *dimension, static_cast<int>( tuples_coordinates.size() ) );
1574 }
1575 if ( tuples_coordinates.size() < 2 )
1576 {
1577 continue;
1578 }
1579 x = tuples_coordinates.at( 0 ).toDouble( &conversionSuccess );
1580 if ( !conversionSuccess )
1581 {
1582 continue;
1583 }
1584 y = tuples_coordinates.at( 1 ).toDouble( &conversionSuccess );
1585 if ( !conversionSuccess )
1586 {
1587 continue;
1588 }
1589 if ( tuples_coordinates.size() > 2 )
1590 {
1591 z = tuples_coordinates.at( 2 ).toDouble( &conversionSuccess );
1592 if ( !conversionSuccess )
1593 {
1594 continue;
1595 }
1596 }
1597 else
1598 {
1599 z = std::numeric_limits<double>::quiet_NaN(); // no Z coordinate
1600 }
1601 points.push_back( ( mInvertAxisOrientation ) ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1602 }
1603
1604 if ( dimension && *dimension == 0 )
1605 {
1606 *dimension = 2; // default dimension is 2D
1607 }
1608
1609 return 0;
1610}
1611
1612int QgsGmlStreamingParser::pointsFromPosListString( QList<QgsPoint> &points, const QString &coordString, int dimension ) const
1613{
1614 // coordinates separated by spaces
1615 const QStringList coordinates = coordString.split( ' ', Qt::SkipEmptyParts );
1616
1617 if ( coordinates.size() % dimension != 0 )
1618 {
1619 QgsDebugError( u"Wrong number of coordinates"_s );
1620 }
1621
1622 const int ncoor = coordinates.size() / dimension;
1623 for ( int i = 0; i < ncoor; i++ )
1624 {
1625 bool conversionSuccess;
1626 const double x = coordinates.value( i * dimension ).toDouble( &conversionSuccess );
1627 if ( !conversionSuccess )
1628 {
1629 continue;
1630 }
1631 const double y = coordinates.value( i * dimension + 1 ).toDouble( &conversionSuccess );
1632 if ( !conversionSuccess )
1633 {
1634 continue;
1635 }
1636 double z = std::numeric_limits<double>::quiet_NaN();
1637 if ( dimension > 2 )
1638 {
1639 z = coordinates.value( i * dimension + 2 ).toDouble( &conversionSuccess );
1640 if ( !conversionSuccess )
1641 {
1642 continue;
1643 }
1644 }
1645 points.append( mInvertAxisOrientation ? QgsPoint( y, x, z ) : QgsPoint( x, y, z ) );
1646 }
1647 return 0;
1648}
1649
1650int QgsGmlStreamingParser::pointsFromString( QList<QgsPoint> &points, const QString &coordString, int *dimension ) const
1651{
1652 if ( mCoorMode == QgsGmlStreamingParser::Coordinate )
1653 {
1654 return pointsFromCoordinateString( points, coordString, dimension );
1655 }
1656 else if ( mCoorMode == QgsGmlStreamingParser::PosList )
1657 {
1658 if ( dimension )
1659 {
1660 *dimension = mDimension ? mDimension : 2; // default dimension is 2D
1661 }
1662 return pointsFromPosListString( points, coordString, mDimension ? mDimension : 2 );
1663 }
1664 return 1;
1665}
1666
1667int QgsGmlStreamingParser::getPointWKB( QByteArray &wkbPtr, const QgsPoint &point, int dimension ) const
1668{
1669 const int wkbSize = 1 + static_cast<int>( sizeof( int ) ) + dimension * static_cast<int>( sizeof( double ) );
1670 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1671
1672 QgsWkbPtr fillPtr( wkbPtr );
1673 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::PointZ : Qgis::WkbType::Point ) << point.x() << point.y();
1674 if ( dimension > 2 )
1675 {
1676 fillPtr << point.z(); // add Z coordinate if available
1677 }
1678
1679 return 0;
1680}
1681
1682int QgsGmlStreamingParser::getLineWKB( QByteArray &wkbPtr, const QList<QgsPoint> &lineCoordinates, int dimension ) const
1683{
1684 const int wkbSize = 1 + 2 * static_cast<int>( sizeof( int ) ) + static_cast<int>( lineCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1685 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1686
1687 QgsWkbPtr fillPtr( wkbPtr );
1688
1689 fillPtr << mEndian << ( dimension > 2 ? Qgis::WkbType::LineStringZ : Qgis::WkbType::LineString ) << lineCoordinates.size();
1690
1691 QList<QgsPoint>::const_iterator iter;
1692 for ( iter = lineCoordinates.constBegin(); iter != lineCoordinates.constEnd(); ++iter )
1693 {
1694 fillPtr << iter->x() << iter->y();
1695 if ( dimension > 2 )
1696 {
1697 fillPtr << iter->z(); // add Z coordinate if available
1698 }
1699 }
1700
1701 return 0;
1702}
1703
1704int QgsGmlStreamingParser::getRingWKB( QByteArray &wkbPtr, const QList<QgsPoint> &ringCoordinates, int dimension ) const
1705{
1706 const int wkbSize = static_cast<int>( sizeof( int ) ) + static_cast<int>( ringCoordinates.size() ) * dimension * static_cast<int>( sizeof( double ) );
1707 wkbPtr = QByteArray( wkbSize, Qt::Uninitialized );
1708
1709 QgsWkbPtr fillPtr( wkbPtr );
1710
1711 fillPtr << ringCoordinates.size();
1712
1713 QList<QgsPoint>::const_iterator iter;
1714 for ( iter = ringCoordinates.constBegin(); iter != ringCoordinates.constEnd(); ++iter )
1715 {
1716 fillPtr << iter->x() << iter->y();
1717 if ( dimension > 2 )
1718 {
1719 fillPtr << iter->z(); // add Z coordinate if available
1720 }
1721 }
1722
1723 return 0;
1724}
1725
1726int QgsGmlStreamingParser::createMultiLineFromFragments()
1727{
1728 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1729 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1730
1731 QgsWkbPtr wkbPtr( mCurrentWKB );
1732
1733 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiLineStringZ : Qgis::WkbType::MultiLineString ) << mCurrentWKBFragments.constBegin()->size();
1734
1735 //copy (and delete) all the wkb fragments
1736 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1737 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1738 {
1739 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1740 wkbPtr += wkbIt->size();
1741 }
1742
1743 mCurrentWKBFragments.clear();
1745 return 0;
1746}
1747
1748int QgsGmlStreamingParser::createMultiPointFromFragments()
1749{
1750 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1751 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1752
1753 QgsWkbPtr wkbPtr( mCurrentWKB );
1754 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint ) << mCurrentWKBFragments.constBegin()->size();
1755
1756 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1757 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1758 {
1759 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1760 wkbPtr += wkbIt->size();
1761 }
1762
1763 mCurrentWKBFragments.clear();
1764 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPointZ : Qgis::WkbType::MultiPoint;
1765 return 0;
1766}
1767
1768
1769int QgsGmlStreamingParser::createPolygonFromFragments()
1770{
1771 const int size = 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) + totalWKBFragmentSize();
1772 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1773
1774 QgsWkbPtr wkbPtr( mCurrentWKB );
1775 wkbPtr << mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << mCurrentWKBFragments.constBegin()->size();
1776
1777 auto wkbIt = mCurrentWKBFragments.constBegin()->constBegin();
1778 for ( ; wkbIt != mCurrentWKBFragments.constBegin()->constEnd(); ++wkbIt )
1779 {
1780 memcpy( wkbPtr, *wkbIt, wkbIt->size() );
1781 wkbPtr += wkbIt->size();
1782 }
1783
1784 mCurrentWKBFragments.clear();
1785 mWkbType = mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon;
1786 return 0;
1787}
1788
1789int QgsGmlStreamingParser::createMultiPolygonFromFragments()
1790{
1791 int size = 0;
1792 size += 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) );
1793 size += totalWKBFragmentSize();
1794 size += mCurrentWKBFragments.size() * ( 1 + ( mDimension > 2 ? mDimension : 2 ) * static_cast<int>( sizeof( int ) ) ); //fragments are just the rings
1795
1796 mCurrentWKB = QByteArray( size, Qt::Uninitialized );
1797
1798 QgsWkbPtr wkbPtr( mCurrentWKB );
1799 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon ) << mCurrentWKBFragments.size();
1800
1801 //have outer and inner iterators
1802 auto outerWkbIt = mCurrentWKBFragments.constBegin();
1803
1804 for ( ; outerWkbIt != mCurrentWKBFragments.constEnd(); ++outerWkbIt )
1805 {
1806 //new polygon
1807 wkbPtr << ( char ) mEndian << ( mDimension > 2 ? Qgis::WkbType::PolygonZ : Qgis::WkbType::Polygon ) << outerWkbIt->size();
1808
1809 auto innerWkbIt = outerWkbIt->constBegin();
1810 for ( ; innerWkbIt != outerWkbIt->constEnd(); ++innerWkbIt )
1811 {
1812 memcpy( wkbPtr, *innerWkbIt, innerWkbIt->size() );
1813 wkbPtr += innerWkbIt->size();
1814 }
1815 }
1816
1817 mCurrentWKBFragments.clear();
1818 mWkbType = mDimension > 2 ? Qgis::WkbType::MultiPolygonZ : Qgis::WkbType::MultiPolygon;
1819 return 0;
1820}
1821
1822int QgsGmlStreamingParser::totalWKBFragmentSize() const
1823{
1824 int result = 0;
1825 for ( const QList<QByteArray> &list : std::as_const( mCurrentWKBFragments ) )
1826 {
1827 for ( const QByteArray &i : list )
1828 {
1829 result += i.size();
1830 }
1831 }
1832 return result;
1833}
1834
1835void QgsGmlStreamingParser::createParser( const QByteArray &encoding )
1836{
1837 Q_ASSERT( !mParser );
1838
1839 mParser = XML_ParserCreateNS( encoding.isEmpty() ? nullptr : encoding.data(), NS_SEPARATOR );
1840 XML_SetUserData( mParser, this );
1841 XML_SetElementHandler( mParser, QgsGmlStreamingParser::start, QgsGmlStreamingParser::end );
1842 XML_SetCharacterDataHandler( mParser, QgsGmlStreamingParser::chars );
1843}
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:314
@ MultiPointZ
MultiPointZ.
Definition qgis.h:337
@ Point
Point.
Definition qgis.h:316
@ LineString
LineString.
Definition qgis.h:317
@ MultiPoint
MultiPoint.
Definition qgis.h:320
@ Polygon
Polygon.
Definition qgis.h:318
@ MultiPolygon
MultiPolygon.
Definition qgis.h:322
@ MultiLineString
MultiLineString.
Definition qgis.h:321
@ Unknown
Unknown.
Definition qgis.h:315
@ PointZ
PointZ.
Definition qgis.h:333
@ MultiLineStringZ
MultiLineStringZ.
Definition qgis.h:338
@ MultiPolygonZ
MultiPolygonZ.
Definition qgis.h:339
@ LineStringZ
LineStringZ.
Definition qgis.h:334
@ PolygonZ
PolygonZ.
Definition qgis.h:335
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:45
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:488
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:526
#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)