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