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