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