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