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