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