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