QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsgmlschema.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgmlschema.cpp
3  --------------------------------------
4  Date : February 2013
5  Copyright : (C) 2013 by Radim Blazek
6  Email : [email protected]
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 "qgsgmlschema.h"
16 #include "qgsrectangle.h"
18 #include "qgsgeometry.h"
19 #include "qgslogger.h"
21 #include <QBuffer>
22 #include <QList>
23 #include <QNetworkRequest>
24 #include <QNetworkReply>
25 #include <QProgressDialog>
26 #include <QSet>
27 #include <QSettings>
28 #include <QUrl>
29 
30 #include <limits>
31 
32 const char NS_SEPARATOR = '?';
33 const QString GML_NAMESPACE = "http://www.opengis.net/gml";
34 
36 {
37 }
38 
39 QgsGmlFeatureClass::QgsGmlFeatureClass( QString name, QString path )
40  : mName( name )
41  , mPath( path )
42 {
43 }
44 
46 {
47 }
48 
49 int QgsGmlFeatureClass::fieldIndex( const QString & name )
50 {
51  for ( int i = 0; i < mFields.size(); i++ )
52  {
53  if ( mFields[i].name() == name ) return i;
54  }
55  return -1;
56 }
57 
58 // --------------------------- QgsGmlSchema -------------------------------
60  : QObject()
61 {
62  mGeometryTypes << "Point" << "MultiPoint"
63  << "LineString" << "MultiLineString"
64  << "Polygon" << "MultiPolygon";
65 }
66 
68 {
69 
70 }
71 
72 QString QgsGmlSchema::readAttribute( const QString& attributeName, const XML_Char** attr ) const
73 {
74  int i = 0;
75  while ( attr[i] != NULL )
76  {
77  if ( attributeName.compare( attr[i] ) == 0 )
78  {
79  return QString( attr[i+1] );
80  }
81  ++i;
82  }
83  return QString();
84 }
85 
86 bool QgsGmlSchema::parseXSD( const QByteArray &xml )
87 {
88  QDomDocument dom;
89  QString errorMsg;
90  int errorLine;
91  int errorColumn;
92  if ( !dom.setContent( xml, false, &errorMsg, &errorLine, &errorColumn ) )
93  {
94  // TODO: error
95  return false;
96  }
97 
98  QDomElement docElem = dom.documentElement();
99 
100  QList<QDomElement> elementElements = domElements( docElem, "element" );
101 
102  //QgsDebugMsg( QString( "%1 elemets read" ).arg( elementElements.size() ) );
103 
104  foreach ( QDomElement elementElement, elementElements )
105  {
106  QString name = elementElement.attribute( "name" );
107  QString type = elementElement.attribute( "type" );
108 
109  QString gmlBaseType = xsdComplexTypeGmlBaseType( docElem, stripNS( type ) );
110  //QgsDebugMsg( QString( "gmlBaseType = %1" ).arg( gmlBaseType ) );
111  //QgsDebugMsg( QString( "name = %1 gmlBaseType = %2" ).arg( name ).arg( gmlBaseType ) );
112  // We should only use gml:AbstractFeatureType descendants which have
113  // ancestor listed in gml:FeatureAssociationType (featureMember) descendant
114  // But we could only loose some data if XSD was not correct, I think.
115 
116  if ( gmlBaseType == "AbstractFeatureType" )
117  {
118  // Get feature type definition
119  QgsGmlFeatureClass featureClass( name, "" );
120  xsdFeatureClass( docElem, stripNS( type ), featureClass );
121  mFeatureClassMap.insert( name, featureClass );
122  }
123  // A feature may have more geometries, we take just the first one
124  }
125 
126  return true;
127 }
128 
129 bool QgsGmlSchema::xsdFeatureClass( const QDomElement &element, const QString & typeName, QgsGmlFeatureClass & featureClass )
130 {
131  //QgsDebugMsg("typeName = " + typeName );
132  QDomElement complexTypeElement = domElement( element, "complexType", "name", typeName );
133  if ( complexTypeElement.isNull() ) return false;
134 
135  // extension or restriction
136  QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
137  if ( extrest.isNull() )
138  {
139  extrest = domElement( complexTypeElement, "complexContent.restriction" );
140  }
141  if ( extrest.isNull() ) return false;
142 
143  QString extrestName = extrest.attribute( "base" );
144  if ( extrestName == "gml:AbstractFeatureType" )
145  {
146  // In theory we should add gml:AbstractFeatureType default attributes gml:description
147  // and gml:name but it does not seem to be a common practice and we would probably
148  // confuse most users
149  }
150  else
151  {
152  // Get attributes from extrest
153  if ( !xsdFeatureClass( element, stripNS( extrestName ), featureClass ) ) return false;
154  }
155 
156  // Supported geometry types
157  QStringList geometryPropertyTypes;
158  foreach ( QString geom, mGeometryTypes )
159  {
160  geometryPropertyTypes << geom + "PropertyType";
161  }
162 
163  QStringList geometryAliases;
164  geometryAliases << "location" << "centerOf" << "position" << "extentOf"
165  << "coverage" << "edgeOf" << "centerLineOf" << "multiLocation"
166  << "multiCenterOf" << "multiPosition" << "multiCenterLineOf"
167  << "multiEdgeOf" << "multiCoverage" << "multiExtentOf";
168 
169  // Add attributes from current comple type
170  QList<QDomElement> sequenceElements = domElements( extrest, "sequence.element" );
171  foreach ( QDomElement sequenceElement, sequenceElements )
172  {
173  QString fieldName = sequenceElement.attribute( "name" );
174  QString fieldTypeName = stripNS( sequenceElement.attribute( "type" ) );
175  QString ref = sequenceElement.attribute( "ref" );
176  //QgsDebugMsg ( QString("fieldName = %1 fieldTypeName = %2 ref = %3").arg(fieldName).arg(fieldTypeName).arg(ref) );
177 
178  if ( !ref.isEmpty() )
179  {
180  if ( ref.startsWith( "gml:" ) )
181  {
182  if ( geometryAliases.contains( stripNS( ref ) ) )
183  {
184  featureClass.geometryAttributes().append( stripNS( ref ) );
185  }
186  else
187  {
188  QgsDebugMsg( QString( "Unknown referenced GML element: %1" ).arg( ref ) );
189  }
190  }
191  else
192  {
193  // TODO: get type from referenced element
194  QgsDebugMsg( QString( "field %1.%2 is referencing %3 - not supported" ).arg( typeName ).arg( fieldName ) );
195  }
196  continue;
197  }
198 
199  if ( fieldName.isEmpty() )
200  {
201  QgsDebugMsg( QString( "field in %1 without name" ).arg( typeName ) );
202  continue;
203  }
204 
205  // type is either type attribute
206  if ( fieldTypeName.isEmpty() )
207  {
208  // or type is inheriting from xs:simpleType
209  QDomElement sequenceElementRestriction = domElement( sequenceElement, "simpleType.restriction" );
210  fieldTypeName = stripNS( sequenceElementRestriction.attribute( "base" ) );
211  }
212 
213  QVariant::Type fieldType = QVariant::String;
214  if ( fieldTypeName.isEmpty() )
215  {
216  QgsDebugMsg( QString( "Cannot get %1.%2 field type" ).arg( typeName ).arg( fieldName ) );
217  }
218  else
219  {
220  if ( geometryPropertyTypes.contains( fieldTypeName ) )
221  {
222  // Geometry attribute
223  featureClass.geometryAttributes().append( fieldName );
224  continue;
225  }
226 
227  if ( fieldTypeName == "decimal" )
228  {
229  fieldType = QVariant::Double;
230  }
231  else if ( fieldTypeName == "integer" )
232  {
233  fieldType = QVariant::Int;
234  }
235  }
236 
237  QgsField field( fieldName, fieldType );
238  featureClass.fields().append( field );
239  }
240 
241  return true;
242 }
243 
244 QString QgsGmlSchema::xsdComplexTypeGmlBaseType( const QDomElement &element, const QString & name )
245 {
246  //QgsDebugMsg("name = " + name );
247  QDomElement complexTypeElement = domElement( element, "complexType", "name", name );
248  if ( complexTypeElement.isNull() ) return "";
249 
250  QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
251  if ( extrest.isNull() )
252  {
253  extrest = domElement( complexTypeElement, "complexContent.restriction" );
254  }
255  if ( extrest.isNull() ) return "";
256 
257  QString extrestName = extrest.attribute( "base" );
258  if ( extrestName.startsWith( "gml:" ) )
259  {
260  // GML base type found
261  return stripNS( extrestName );
262  }
263  // Continue recursively until GML base type is reached
264  return xsdComplexTypeGmlBaseType( element, stripNS( extrestName ) );
265 }
266 
267 QString QgsGmlSchema::stripNS( const QString & name )
268 {
269  return name.contains( ":" ) ? name.section( ':', 1 ) : name;
270 }
271 
272 QList<QDomElement> QgsGmlSchema::domElements( const QDomElement &element, const QString & path )
273 {
274  QList<QDomElement> list;
275 
276  QStringList names = path.split( "." );
277  if ( names.size() == 0 ) return list;
278  QString name = names.value( 0 );
279  names.removeFirst();
280 
281  QDomNode n1 = element.firstChild();
282  while ( !n1.isNull() )
283  {
284  QDomElement el = n1.toElement();
285  if ( !el.isNull() )
286  {
287  QString tagName = stripNS( el.tagName() );
288  if ( tagName == name )
289  {
290  if ( names.size() == 0 )
291  {
292  list.append( el );
293  }
294  else
295  {
296  list.append( domElements( el, names.join( "." ) ) );
297  }
298  }
299  }
300  n1 = n1.nextSibling();
301  }
302 
303  return list;
304 }
305 
306 QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path )
307 {
308  return domElements( element, path ).value( 0 );
309 }
310 
311 QList<QDomElement> QgsGmlSchema::domElements( QList<QDomElement> &elements, const QString & attr, const QString & attrVal )
312 {
313  QList<QDomElement> list;
314  foreach ( QDomElement el, elements )
315  {
316  if ( el.attribute( attr ) == attrVal )
317  {
318  list << el;
319  }
320  }
321  return list;
322 }
323 
324 QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path, const QString & attr, const QString & attrVal )
325 {
326  QList<QDomElement> list = domElements( element, path );
327  return domElements( list, attr, attrVal ).value( 0 );
328 }
329 
330 bool QgsGmlSchema::guessSchema( const QByteArray &data )
331 {
332  QgsDebugMsg( "Entered" );
333  mLevel = 0;
335  XML_Parser p = XML_ParserCreateNS( NULL, NS_SEPARATOR );
336  XML_SetUserData( p, this );
337  XML_SetElementHandler( p, QgsGmlSchema::start, QgsGmlSchema::end );
338  XML_SetCharacterDataHandler( p, QgsGmlSchema::chars );
339  int atEnd = 1;
340  XML_Parse( p, data.constData(), data.size(), atEnd );
341  return 0;
342 }
343 
344 void QgsGmlSchema::startElement( const XML_Char* el, const XML_Char** attr )
345 {
346  Q_UNUSED( attr );
347  mLevel++;
348 
349  QString elementName( el );
350  QgsDebugMsgLevel( QString( "-> %1 %2 %3" ).arg( mLevel ).arg( elementName ).arg( mLevel >= mSkipLevel ? "skip" : "" ), 5 );
351 
352  if ( mLevel >= mSkipLevel )
353  {
354  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
355  return;
356  }
357 
358  mParsePathStack.append( elementName );
359  QString path = mParsePathStack.join( "." );
360 
361  QStringList splitName = elementName.split( NS_SEPARATOR );
362  QString localName = splitName.last();
363  QString ns = splitName.size() > 1 ? splitName.first() : "";
364  //QgsDebugMsg( "ns = " + ns + " localName = " + localName );
365 
366  ParseMode parseMode = modeStackTop();
367 
368  if ( ns == GML_NAMESPACE && localName == "boundedBy" )
369  {
370  // gml:boundedBy in feature or feature collection -> skip
371  mSkipLevel = mLevel + 1;
372  }
373  // GML does not specify that gml:FeatureAssociationType elements should end
374  // with 'Member' apart standard gml:featureMember, but it is quite usual to
375  // that the names ends with 'Member', e.g.: osgb:topographicMember, cityMember,...
376  // so this is really fail if the name does not contain 'Member'
377  else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
378  {
380  }
381  // UMN Mapserver simple GetFeatureInfo response layer element (ends with _layer)
382  else if ( elementName.endsWith( "_layer" ) )
383  {
384  // do nothing, we catch _feature children
385  }
386  // UMN Mapserver simple GetFeatureInfo response feature element (ends with _feature)
387  // or featureMember children
388  else if ( elementName.endsWith( "_feature" )
389  || parseMode == QgsGmlSchema::featureMember )
390  {
391  //QgsDebugMsg ( "is feature path = " + path );
392  if ( mFeatureClassMap.count( localName ) == 0 )
393  {
394  mFeatureClassMap.insert( localName, QgsGmlFeatureClass( localName, path ) );
395  }
396  mCurrentFeatureName = localName;
398  }
399  else if ( parseMode == QgsGmlSchema::attribute && ns == GML_NAMESPACE && mGeometryTypes.indexOf( localName ) >= 0 )
400  {
401  // Geometry (Point,MultiPoint,...) in geometry attribute
402  QStringList &geometryAttributes = mFeatureClassMap[mCurrentFeatureName].geometryAttributes();
403  if ( geometryAttributes.count( mAttributeName ) == 0 )
404  {
405  geometryAttributes.append( mAttributeName );
406  }
407  mSkipLevel = mLevel + 1; // no need to parse children
408  }
409  else if ( parseMode == QgsGmlSchema::feature )
410  {
411  // An element in feature should be ordinary or geometry attribute
412  //QgsDebugMsg( "is attribute");
414  mAttributeName = localName;
415  mStringCash.clear();
416  }
417 }
418 
419 void QgsGmlSchema::endElement( const XML_Char* el )
420 {
421  QString elementName( el );
422  QgsDebugMsgLevel( QString( "<- %1 %2" ).arg( mLevel ).arg( elementName ), 5 );
423 
424  if ( mLevel >= mSkipLevel )
425  {
426  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
427  mLevel--;
428  return;
429  }
430  else
431  {
432  // clear possible skip level
434  }
435 
436  QStringList splitName = elementName.split( NS_SEPARATOR );
437  QString localName = splitName.last();
438  QString ns = splitName.size() > 1 ? splitName.first() : "";
439 
441 
442  if ( parseMode == QgsGmlSchema::attribute && localName == mAttributeName )
443  {
444  // End of attribute
445  //QgsDebugMsg("end attribute");
446  modeStackPop(); // go up to feature
447 
449  {
450  // It is not geometry attribute -> analyze value
451  bool ok;
452  mStringCash.toInt( &ok );
453  QVariant::Type type = QVariant::String;
454  if ( ok )
455  {
456  type = QVariant::Int;
457  }
458  else
459  {
460  mStringCash.toDouble( &ok );
461  if ( ok )
462  {
463  type = QVariant::Double;
464  }
465  }
466  //QgsDebugMsg( "mStringCash = " + mStringCash + " type = " + QVariant::typeToName( type ) );
467  //QMap<QString, QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
468  QList<QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
469  int fieldIndex = mFeatureClassMap[mCurrentFeatureName].fieldIndex( mAttributeName );
470  if ( fieldIndex == -1 )
471  {
472  QgsField field( mAttributeName, type );
473  fields.append( field );
474  }
475  else
476  {
477  QgsField &field = fields[fieldIndex];
478  // check if type is sufficient
479  if (( field.type() == QVariant::Int && ( type == QVariant::String || type == QVariant::Double ) ) ||
480  ( field.type() == QVariant::Double && type == QVariant::String ) )
481  {
482  field.setType( type );
483  }
484  }
485  }
486  }
487  else if ( ns == GML_NAMESPACE && localName == "boundedBy" )
488  {
489  // was skipped
490  }
491  else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
492  {
494  modeStackPop();
495  }
496  mParsePathStack.removeLast();
497  mLevel--;
498 }
499 
500 void QgsGmlSchema::characters( const XML_Char* chars, int len )
501 {
502  //QgsDebugMsg( QString("level %1 : %2").arg( mLevel ).arg( QString::fromUtf8( chars, len ) ) );
503  if ( mLevel >= mSkipLevel )
504  {
505  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
506  return;
507  }
508 
509  //save chars in mStringCash attribute mode for value type analysis
511  {
512  mStringCash.append( QString::fromUtf8( chars, len ) );
513  }
514 }
515 
516 QStringList QgsGmlSchema::typeNames() const
517 {
518  return mFeatureClassMap.keys();
519 }
520 
521 QList<QgsField> QgsGmlSchema::fields( const QString & typeName )
522 {
523  if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
524  return mFeatureClassMap[typeName].fields();
525 }
526 
527 QStringList QgsGmlSchema::geometryAttributes( const QString & typeName )
528 {
529  if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
530  return mFeatureClassMap[typeName].geometryAttributes();
531 }