WMS identify feature support - get vector features from GML GetFeatureInfo

This commit is contained in:
Radim Blazek 2013-02-06 15:14:16 +01:00
parent 1fe82b25c8
commit 50d4bb6d4d
38 changed files with 2217 additions and 356 deletions

View File

@ -18,6 +18,7 @@ which are not wrapped by PyQt:
- QMap<qint64, QgsOverlayObject*>
- QList< QPair< QString, QList<QString> > >
- QVector<TYPE*>
- QMap<qint64, QgsFeature*>
*/
%Feature QSETINT_CONVERSION
@ -1386,7 +1387,8 @@ template<double, TYPE2>
while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
{
int state;
int t1 = (int)(PyFloat_AsDouble(t1obj));
//int t1 = (int)(PyFloat_AsDouble(t1obj));
qint64 t1 = PyLong_AsLongLong(t1obj);
QgsOverlayObject* t2 = reinterpret_cast<QgsOverlayObject*>(sipConvertToInstance(t2obj, sipClass_QgsOverlayObject, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
if (*sipIsErr)
@ -1581,3 +1583,86 @@ template <TYPE>
return sipGetState(sipTransferObj);
%End
};
%MappedType QMap<qint64, QgsFeature*>
{
%TypeHeaderCode
#include <QMap>
#if (SIP_VERSION >= 0x040900)
#define sipClass_QString ((sipWrapperType *) sipTypeAsPyTypeObject (sipType_QString))
#endif
#if (SIP_VERSION >= 0x040900 && SIP_VERSION < 0x040c01)
#define sipClass_QVariant ((sipWrapperType *) sipTypeAsPyTypeObject (sipType_QVariant))
#endif
%End
%ConvertFromTypeCode
//convert map to a python dictionary
PyObject *d;
if ((d = PyDict_New()) == NULL)
return NULL;
for (QMap<qint64, QgsFeature*>::iterator it = sipCpp->begin(); it != sipCpp->end(); ++it)
{
QgsFeature* oobj = new QgsFeature(*it.value());
PyObject* keyobj = PyInt_FromLong(it.key());
PyObject* pyOobj = sipConvertFromInstance(oobj, sipClass_QgsFeature, sipTransferObj);
PyDict_SetItem(d, keyobj, pyOobj);
if(pyOobj == NULL || keyobj == NULL || PyDict_SetItem(d, keyobj, pyOobj) < 0)
{
Py_DECREF(d);
if (pyOobj)
{
Py_DECREF(pyOobj);
}
if (keyobj)
{
Py_DECREF(keyobj);
}
return NULL;
}
Py_DECREF(pyOobj);
Py_DECREF(keyobj);
}
return d;
%End
%ConvertToTypeCode
PyObject *t1obj, *t2obj;
#if PY_VERSION_HEX >= 0x02050000
Py_ssize_t i = 0;
#else
int i = 0;
#endif
QMap<qint64, QgsFeature*> *qm = new QMap<qint64, QgsFeature*>;
while (PyDict_Next(sipPy, &i, &t1obj, &t2obj))
{
int state;
qint64 t1 = PyLong_AsLongLong(t1obj);
QgsFeature* t2 = reinterpret_cast<QgsFeature*>(sipConvertToInstance(t2obj, sipClass_QgsFeature, sipTransferObj, SIP_NOT_NONE, &state, sipIsErr));
if (*sipIsErr)
{
sipReleaseInstance(t2, sipClass_QgsFeature, state);
delete qm;
return 0;
}
qm->insert(t1, t2);
sipReleaseInstance(t2, sipClass_QgsFeature, state);
}
*sipCppPtr = qm;
return sipGetState(sipTransferObj);
%End
};

View File

@ -31,6 +31,8 @@
%Include qgsfield.sip
%Include qgsgeometry.sip
%Include qgsgeometryvalidator.sip
%Include qgsgml.sip
%Include qgsgmlschema.sip
%Include qgshttptransaction.sip
%Include qgslabel.sip
%Include qgslabelattributes.sip

22
python/core/qgsgml.sip Normal file
View File

@ -0,0 +1,22 @@
class QgsGml: QObject
{
%TypeHeaderCode
#include <qgsgml.h>
%End
public:
QgsGml(
const QString& typeName,
const QString& geometryAttribute,
const QgsFields & fields );
~QgsGml();
/** Read from GML data. */
int getFeatures( const QByteArray &data, QGis::WkbType* wkbType, QgsRectangle* extent = 0 );
QMap<qint64, QgsFeature* > featuresMap() const;
};

View File

@ -0,0 +1,28 @@
//typedef QMap<int, QgsField> QgsFieldMap;
class QgsGmlSchema: QObject
{
%TypeHeaderCode
#include <qgsgmlschema.h>
%End
public:
QgsGmlSchema();
~QgsGmlSchema();
/** Get fields info from XSD */
bool parseXSD( const QByteArray &xml );
/** Guess GML schema from data if XSD does not exist.
* Currently only recognizes UMN Mapserver GetFeatureInfo GML response.
* @param data GML data
* @return true in case of success */
bool guessSchema( const QByteArray &data );
QStringList typeNames() const;
QList<QgsField> fields( const QString & typeName );
QStringList geometryAttributes( const QString & typeName );
};

View File

@ -41,10 +41,11 @@ class QgsRasterDataProvider : QgsDataProvider, QgsRasterInterface
enum IdentifyFormat
{
IdentifyFormatValue,
IdentifyFormatText,
IdentifyFormatHtml,
IdentifyFormatFeature
IdentifyFormatUndefined = 0,
IdentifyFormatValue = 1,
IdentifyFormatText = 2,
IdentifyFormatHtml = 4,
IdentifyFormatFeature = 8
};
// Progress types

View File

@ -32,12 +32,14 @@ class QgsMapToolIdentify : QgsMapTool
struct RasterResult
{
RasterResult();
RasterResult( QgsRasterLayer* layer, QString label, QMap< QString, QString > attributes, QMap< QString, QString > derivedAttributes);
QgsRasterLayer* mLayer;
QString mLabel;
QMap< QString, QString > mAttributes;
QMap< QString, QString > mDerivedAttributes;
RasterResult();
RasterResult( QgsRasterLayer * layer, QString label, QgsFields fields, QgsFeature feature, QMap< QString, QString > derivedAttributes );
QgsRasterLayer* mLayer;
QString mLabel;
QgsFields mFields;
QgsFeature mFeature;
QMap< QString, QString > mAttributes;
QMap< QString, QString > mDerivedAttributes;
};
struct IdentifyResults

View File

@ -1863,6 +1863,8 @@ void QgisApp::createCanvasTools()
#endif
mMapTools.mIdentify = new QgsMapToolIdentifyAction( mMapCanvas );
mMapTools.mIdentify->setAction( mActionIdentify );
connect( mMapTools.mIdentify, SIGNAL( copyToClipboard( QgsFeatureStore & ) ),
this, SLOT( copyFeatures( QgsFeatureStore & ) ) );
mMapTools.mFeatureAction = new QgsMapToolFeatureAction( mMapCanvas );
mMapTools.mFeatureAction->setAction( mActionFeatureAction );
mMapTools.mMeasureDist = new QgsMeasureTool( mMapCanvas, false /* area */ );
@ -5260,6 +5262,11 @@ void QgisApp::pasteTransformations()
}
#endif
void QgisApp::copyFeatures( QgsFeatureStore & featureStore )
{
clipboard()->replaceWithCopyOf( featureStore );
}
void QgisApp::refreshMapCanvas()
{
//clear all caches first

View File

@ -87,6 +87,7 @@ class QgsTileScaleWidget;
#include "qgsconfig.h"
#include "qgsfeature.h"
#include "qgsfeaturestore.h"
#include "qgspoint.h"
#include "qgsrasterlayer.h"
#include "qgssnappingdialog.h"
@ -522,6 +523,10 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
(defaults to the active layer on the legend)
*/
void pasteStyle( QgsMapLayer * destinationLayer = 0 );
//! copies features to internal clipboard
void copyFeatures( QgsFeatureStore & featureStore );
void loadOGRSublayers( QString layertype, QString uri, QStringList list );
void loadGDALSublayers( QString uri, QStringList list );

View File

@ -47,9 +47,6 @@ void QgsClipboard::replaceWithCopyOf( QgsVectorLayer *src )
if ( !src )
return;
QSettings settings;
bool copyWKT = settings.value( "qgis/copyGeometryAsWKT", true ).toBool();
// Replace the QGis clipboard.
mFeatureFields = src->pendingFields();
mFeatureClipboard = src->selectedFeatures();
@ -57,7 +54,24 @@ void QgsClipboard::replaceWithCopyOf( QgsVectorLayer *src )
QgsDebugMsg( "replaced QGis clipboard." );
setSystemClipboard();
}
void QgsClipboard::replaceWithCopyOf( QgsFeatureStore & featureStore )
{
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );
mFeatureFields = featureStore.fields();
mFeatureClipboard = featureStore.features();
mCRS = featureStore.crs();
setSystemClipboard();
}
void QgsClipboard::setSystemClipboard()
{
// Replace the system clipboard.
QSettings settings;
bool copyWKT = settings.value( "qgis/copyGeometryAsWKT", true ).toBool();
QStringList textLines;
QStringList textFields;

View File

@ -23,6 +23,7 @@
#include "qgsfield.h"
#include "qgsfeature.h"
#include "qgsfeaturestore.h"
#include "qgscoordinatereferencesystem.h"
/**
@ -65,6 +66,12 @@ class QgsClipboard
*/
void replaceWithCopyOf( QgsVectorLayer *src );
/*
* Place a copy of features on the internal clipboard,
* destroying the previous contents.
*/
void replaceWithCopyOf( QgsFeatureStore & featureStore );
/*
* Returns a copy of features on the internal clipboard,
* the caller assumes responsibility for destroying the contents
@ -133,6 +140,10 @@ class QgsClipboard
const QgsFields &fields() { return mFeatureFields; }
private:
/*
* Set system clipboard from previously set features.
*/
void setSystemClipboard();
/** QGIS-internal vector feature clipboard.
Stored as values not pointers as each clipboard operation

View File

@ -47,15 +47,22 @@
#include <QPrintDialog>
#include <QDesktopServices>
#include <QMessageBox>
#include <QComboBox>
#include <QWebFrame>
QgsWebView::QgsWebView( QWidget *parent ) : QWebView( parent )
{
setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
#ifdef QGISDEBUG
settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
#endif
connect( this->page()->mainFrame(), SIGNAL( contentsSizeChanged( const QSize & ) ),
this, SLOT( handleContentsSizeChanged( const QSize & ) ) );
connect( this->page(), SIGNAL( loadFinished( bool ) ) ,
this, SLOT( loadFinished( bool ) ) );
}
void QgsWebView::print( void )
@ -79,6 +86,72 @@ void QgsWebView::contextMenuEvent( QContextMenuEvent *e )
}
}
// QgsWebView size:
// ---------------
//
// 1) QTreeWidget is not able to scroll continuously through the content of large widgets// iserted into items via setItemWidget, it always jumps to the top of next item
// and it is not able to scroll to the bottom of an inserted large widget (until
// the QTreeWidget itself is large enough to show the whole inserted widget).
// => We have to keep the height of QgsWebView smaller than the height of QTreeWidget
// so that a user can see it entire, even if this height is smaller than QgsWebView
// content (i.e. QgsWebView scroolbar is added). We make it even a bit smaller so
// that a user can see a bit of context (items above/below) when scrolling which
// is more pleasant.
//
// 2) contentsSize() is 0,0 until a page is loaded. If there are no external resources
// (like images) used, contentsSize() is available immediately after setHtml(),
// otherwise the contentSize() is 0,0 until the page is loaded and contentsSizeChanged ()
// is emited.
//
// 3) If QgsWebView is resized (on page load) after it was inserted into QTreeWidget,
// the row does not reflect that change automaticaly and consecutive resize
// of QTreeWidget will cause to shrink QgsWebView to the original row height.
// That is expected, Qt: "setItemWidget() should only be used to display static
// content...
// => we must not change QgsWebView size after it was inserted to QTreeWidget
// TODO(?): Sometimes it may happen that if multiple QgsWebView are inserted to
// QTreeWidget for the first time, and both share the same external source (image)
// the layout gets somehow confused - wrong positions, overlapped (Qt bug?)
// until next QTreeWidget resize.
// TODO(?): if the results dialog is resized to smaller heigh, existing QgsWebView
// are not (and must not be!) resized and scrolling becomes a bit unpleasant
// until next identify. AFAIK it could only be solved using QItemDelegate.
// size hint according to content
QSize QgsWebView::sizeHint() const
{
QSize s = this->page()->mainFrame()->contentsSize();
QgsDebugMsg( QString( "content size: %1 x %2" ).arg( s.width() ).arg( s.height() ) );
int height = s.height();
// if page is not yet loaded set some minimum height
if ( height == 0 )
{
height = 100;
}
else
{
// parent is qt_scrollarea_viewport
// parent is not available the first time - before results dialog was shown
QWidget *widget = qobject_cast<QWidget *>( parent() );
if ( widget )
{
int max = widget->size().height() * 0.9;
QgsDebugMsg( QString( "parent widget height = %1 max height = %2" ).arg( widget->size().height() ).arg( max ) );
if ( height > max ) height = max;
}
else
{
QgsDebugMsg( "parent not available" ) ;
}
}
s = QSize( size().width(), height );
QgsDebugMsg( QString( "size: %1 x %2" ).arg( s.width() ).arg( s.height() ) );
return s;
}
class QgsIdentifyResultsDock : public QDockWidget
{
public:
@ -95,6 +168,40 @@ class QgsIdentifyResultsDock : public QDockWidget
}
};
QgsIdentifyResultsFeatureItem::QgsIdentifyResultsFeatureItem( const QgsFields &fields, const QgsFeature &feature, const QgsCoordinateReferenceSystem &crs, const QStringList & strings )
: QTreeWidgetItem( strings )
, mFields( fields )
, mFeature( feature )
, mCrs( crs )
{
}
void QgsIdentifyResultsWebViewItem::setHtml( const QString &html )
{
mWebView->setHtml( html );
}
QgsIdentifyResultsWebViewItem::QgsIdentifyResultsWebViewItem( QTreeWidget *treeWidget )
{
mWebView = new QgsWebView( treeWidget );
mWebView->hide();
setText( 0, tr( "Loading..." ) );
connect( mWebView, SIGNAL( loadFinished( bool ) ) ,
this, SLOT( loadFinished( bool ) ) );
}
void QgsIdentifyResultsWebViewItem::loadFinished( bool ok )
{
Q_UNUSED( ok );
QgsDebugMsg( "Entered" );
mWebView->show();
treeWidget()->setItemWidget( this, 0, mWebView );
// Span columns to save some space, must be after setItemWidget() to take efect.
setFirstColumnSpanned( true );
}
// Tree hierarchy
//
// layer [userrole: QgsMapLayer]
@ -125,6 +232,7 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge
mExpandToolButton->setIcon( QgsApplication::getThemeIcon( "/mActionExpandTree.png" ) );
mCollapseToolButton->setIcon( QgsApplication::getThemeIcon( "/mActionCollapseTree.png" ) );
mExpandNewToolButton->setIcon( QgsApplication::getThemeIcon( "/mActionExpandNewTree.png" ) );
mCopyToolButton->setIcon( QgsApplication::getThemeIcon( "/mActionEditCopy.png" ) );
mPrintToolButton->setIcon( QgsApplication::getThemeIcon( "/mActionFilePrint.png" ) );
QSettings mySettings;
@ -138,10 +246,17 @@ QgsIdentifyResultsDialog::QgsIdentifyResultsDialog( QgsMapCanvas *canvas, QWidge
QgisApp::instance()->addDockWidget( Qt::LeftDockWidgetArea, mDock );
}
mExpandNewToolButton->setChecked( mySettings.value( "/Map/identifyExpand", false ).toBool() );
mCopyToolButton->setEnabled( false );
lstResults->setColumnCount( 2 );
setColumnText( 0, tr( "Feature" ) );
setColumnText( 1, tr( "Value" ) );
int width = mySettings.value( "/Windows/Identify/columnWidth", "0" ).toInt();
if ( width > 0 )
{
lstResults->setColumnWidth( 0, width );
}
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( close() ) );
connect( lstResults, SIGNAL( itemExpanded( QTreeWidgetItem* ) ),
@ -198,7 +313,8 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer,
connect( vlayer, SIGNAL( editingStopped() ), this, SLOT( editingToggled() ) );
}
QTreeWidgetItem *featItem = new QTreeWidgetItem;
//QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( fields, f, crs );
QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( vlayer->pendingFields(), f, vlayer->crs() );
featItem->setData( 0, Qt::UserRole, FID_TO_STRING( f.id() ) );
featItem->setData( 0, Qt::UserRole + 1, mFeatures.size() );
mFeatures << f;
@ -293,36 +409,89 @@ void QgsIdentifyResultsDialog::addFeature( QgsVectorLayer *vlayer,
void QgsIdentifyResultsDialog::addFeature( QgsRasterLayer *layer,
QString label,
const QMap<QString, QString> &attributes,
const QMap<QString, QString> &derivedAttributes )
const QMap<QString, QString> &derivedAttributes,
const QgsFields &fields,
const QgsFeature &feature )
{
QgsDebugMsg( QString( "feature.isValid() = %1" ).arg( feature.isValid() ) );
QTreeWidgetItem *layItem = layerItem( layer );
QgsRasterDataProvider::IdentifyFormat currentFormat = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( "identify/format" ).toString() );
if ( layItem == 0 )
{
layItem = new QTreeWidgetItem( QStringList() << QString::number( lstResults->topLevelItemCount() ) << layer->name() );
layItem->setData( 0, Qt::UserRole, QVariant::fromValue( qobject_cast<QObject *>( layer ) ) );
lstResults->addTopLevelItem( layItem );
QComboBox *formatCombo = new QComboBox();
// Add all supported formats, best first. Note that all providers should support
// IdentifyFormatHtml and IdentifyFormatText because other formats may be
// converted to them
int capabilities = layer->dataProvider()->capabilities();
QList<QgsRasterDataProvider::IdentifyFormat> formats;
formats << QgsRasterDataProvider::IdentifyFormatFeature
<< QgsRasterDataProvider::IdentifyFormatValue
<< QgsRasterDataProvider::IdentifyFormatHtml
<< QgsRasterDataProvider::IdentifyFormatText;
foreach ( QgsRasterDataProvider::IdentifyFormat f, formats )
{
if ( !( QgsRasterDataProvider::identifyFormatToCapability( f ) & capabilities ) ) continue;
formatCombo->addItem( QgsRasterDataProvider::identifyFormatLabel( f ), f );
formatCombo->setItemData( formatCombo->count() - 1, qVariantFromValue(( void * )layer ), Qt::UserRole + 1 );
if ( currentFormat == f ) formatCombo->setCurrentIndex( formatCombo->count() - 1 );
}
if ( formatCombo->count() > 1 )
{
// Add format combo box item
QTreeWidgetItem *formatItem = new QTreeWidgetItem( QStringList() << tr( "Format" ) );
layItem->addChild( formatItem );
lstResults->setItemWidget( formatItem, 1, formatCombo );
connect( formatCombo, SIGNAL( currentIndexChanged( int ) ),
this, SLOT( formatChanged( int ) ) );
}
else
{
delete formatCombo;
}
connect( layer, SIGNAL( destroyed() ), this, SLOT( layerDestroyed() ) );
connect( layer, SIGNAL( layerCrsChanged() ), this, SLOT( layerDestroyed() ) );
}
QTreeWidgetItem *featItem = new QTreeWidgetItem( QStringList() << label << "" );
featItem->setData( 0, Qt::UserRole, -1 );
QgsIdentifyResultsFeatureItem *featItem = new QgsIdentifyResultsFeatureItem( fields, feature, layer->crs(), QStringList() << label << "" );
layItem->addChild( featItem );
if ( layer && layer->providerType() == "wms" )
// add feature attributes
if ( feature.isValid() )
{
QTreeWidgetItem *attrItem = new QTreeWidgetItem( QStringList() << attributes.begin().key() << "" );
featItem->addChild( attrItem );
QgsDebugMsg( QString( "fields size = %1 attributes size = %2" ).arg( fields.size() ).arg( feature.attributes().size() ) );
const QgsAttributes& attrs = feature.attributes();
for ( int i = 0; i < attrs.count(); ++i )
{
if ( i >= fields.count() )
continue;
QgsWebView *wv = new QgsWebView( attrItem->treeWidget() );
wv->setHtml( attributes.begin().value() );
QTreeWidgetItem *attrItem = new QTreeWidgetItem( QStringList() << QString::number( i ) << attrs[i].toString() );
attrItem->setData( 0, Qt::DisplayRole, fields[i].name() );
QVariant value = attrs[i];
attrItem->setData( 1, Qt::DisplayRole, value );
featItem->addChild( attrItem );
}
}
if ( currentFormat == QgsRasterDataProvider::IdentifyFormatHtml )
{
QgsIdentifyResultsWebViewItem *attrItem = new QgsIdentifyResultsWebViewItem( lstResults );
featItem->addChild( attrItem ); // before setHtml()!
attrItem->setHtml( attributes.begin().value() );
connect( attrItem->webView(), SIGNAL( linkClicked( const QUrl & ) ), this, SLOT( openUrl( const QUrl & ) ) );
mPrintToolButton->setVisible( true );
connect( wv, SIGNAL( linkClicked( const QUrl & ) ), this, SLOT( openUrl( const QUrl & ) ) );
attrItem->treeWidget()->setItemWidget( attrItem, 1, wv );
}
else
{
@ -384,7 +553,8 @@ void QgsIdentifyResultsDialog::show()
{
// Enforce a few things before showing the dialog box
lstResults->sortItems( 0, Qt::AscendingOrder );
expandColumnsToFit();
// column width is now stored in settings
//expandColumnsToFit();
if ( lstResults->topLevelItemCount() > 0 )
{
@ -539,6 +709,8 @@ void QgsIdentifyResultsDialog::saveWindowLocation()
{
QSettings settings;
settings.setValue( "/Windows/Identify/geometry", saveGeometry() );
// first column width
settings.setValue( "/Windows/Identify/columnWidth", lstResults->columnWidth( 0 ) );
}
void QgsIdentifyResultsDialog::setColumnText( int column, const QString & label )
@ -638,7 +810,7 @@ QTreeWidgetItem *QgsIdentifyResultsDialog::featureItem( QTreeWidgetItem *item )
if ( !item )
return 0;
QTreeWidgetItem *featItem;
QTreeWidgetItem *featItem = 0;
if ( item->parent() )
{
if ( item->parent()->parent() )
@ -662,11 +834,24 @@ QTreeWidgetItem *QgsIdentifyResultsDialog::featureItem( QTreeWidgetItem *item )
}
else
{
// layer item
if ( item->childCount() > 1 )
return 0;
// top level layer item, return feature item if only one
featItem = item->child( 0 );
//if ( item->childCount() > 1 )
// return 0;
//featItem = item->child( 0 );
int count = 0;
for ( int i = 0; i < item->childCount(); i++ )
{
QgsIdentifyResultsFeatureItem *fi = dynamic_cast<QgsIdentifyResultsFeatureItem *>( item->child( i ) );
if ( fi )
{
count++;
if ( !featItem ) featItem = fi;
}
}
if ( count != 1 ) return 0;
}
return featItem;
@ -691,6 +876,13 @@ QgsVectorLayer *QgsIdentifyResultsDialog::vectorLayer( QTreeWidgetItem *item )
return qobject_cast<QgsVectorLayer *>( item->data( 0, Qt::UserRole ).value<QObject *>() );
}
QgsRasterLayer *QgsIdentifyResultsDialog::rasterLayer( QTreeWidgetItem *item )
{
item = layerItem( item );
if ( !item )
return NULL;
return qobject_cast<QgsRasterLayer *>( item->data( 0, Qt::UserRole ).value<QObject *>() );
}
QTreeWidgetItem *QgsIdentifyResultsDialog::retrieveAttributes( QTreeWidgetItem *item, QgsAttributeMap &attributes, int &idx )
{
@ -717,12 +909,17 @@ QTreeWidgetItem *QgsIdentifyResultsDialog::retrieveAttributes( QTreeWidgetItem *
void QgsIdentifyResultsDialog::itemExpanded( QTreeWidgetItem *item )
{
Q_UNUSED( item );
expandColumnsToFit();
// column width is now stored in settings
//expandColumnsToFit();
}
void QgsIdentifyResultsDialog::handleCurrentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
{
Q_UNUSED( previous );
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( current ) );
mCopyToolButton->setEnabled( featItem && featItem->feature().isValid() );
if ( !current )
{
emit selectedFeatureChanged( 0, 0 );
@ -733,7 +930,6 @@ void QgsIdentifyResultsDialog::handleCurrentItemChanged( QTreeWidgetItem *curren
mPrintToolButton->setEnabled( wv != 0 );
QTreeWidgetItem *layItem = layerItem( current );
if ( current == layItem )
{
highlightLayer( layItem );
@ -858,30 +1054,21 @@ void QgsIdentifyResultsDialog::attributeValueChanged( QgsFeatureId fid, int idx,
void QgsIdentifyResultsDialog::highlightFeature( QTreeWidgetItem *item )
{
QgsVectorLayer *layer = vectorLayer( item );
if ( !layer )
QgsRasterLayer *rlayer = rasterLayer( item );
if ( !layer && !rlayer )
return;
QTreeWidgetItem *featItem = featureItem( item );
QgsIdentifyResultsFeatureItem *featItem = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( item ) );
if ( !featItem )
return;
if ( mHighlights.contains( featItem ) )
return;
QgsFeatureId fid = STRING_TO_FID( featItem->data( 0, Qt::UserRole ) );
if ( !featItem->feature().geometry() || featItem->feature().geometry()->wkbType() == QGis::WKBUnknown ) return;
QgsFeature feat;
if ( !layer->getFeatures( QgsFeatureRequest().setFilterFid( fid ).setSubsetOfAttributes( QgsAttributeList() ) ).nextFeature( feat ) )
{
return;
}
if ( !feat.geometry() )
{
return;
}
QgsHighlight *h = new QgsHighlight( mCanvas, feat.geometry(), layer );
QgsHighlight *h = new QgsHighlight( mCanvas, featItem->feature().geometry(), layer );
if ( h )
{
h->setWidth( 2 );
@ -1078,8 +1265,69 @@ void QgsIdentifyResultsDialog::printCurrentItem()
wv->print( &printer );
}
void QgsIdentifyResultsDialog:: on_mExpandNewToolButton_toggled( bool checked )
void QgsIdentifyResultsDialog::on_mExpandNewToolButton_toggled( bool checked )
{
QSettings settings;
settings.setValue( "/Map/identifyExpand", checked );
}
void QgsIdentifyResultsDialog::on_mCopyToolButton_clicked( bool checked )
{
Q_UNUSED( checked );
QgsDebugMsg( "Entered" );
QgsIdentifyResultsFeatureItem *item = dynamic_cast<QgsIdentifyResultsFeatureItem *>( featureItem( lstResults->selectedItems().value( 0 ) ) );
if ( !item ) // should not happen
{
QgsDebugMsg( "Selected item is not feature" );
return;
}
QgsFeatureStore featureStore( item->fields(), item->crs() );
featureStore.features().append( item->feature() );
emit copyToClipboard( featureStore );
}
void QgsIdentifyResultsDialog::formatChanged( int index )
{
QgsDebugMsg( "Entered" );
QComboBox *combo = qobject_cast<QComboBox*>( sender() );
if ( !combo )
{
QgsDebugMsg( "sender is not QComboBox" );
return;
}
QgsRasterDataProvider::IdentifyFormat format = ( QgsRasterDataProvider::IdentifyFormat ) combo->itemData( index, Qt::UserRole ).toInt();
QgsDebugMsg( QString( "format = %1" ).arg( format ) );
QgsRasterLayer *layer = ( QgsRasterLayer * )combo->itemData( index, Qt::UserRole + 1 ).value<void *>();
if ( !layer )
{
QgsDebugMsg( "cannot get raster layer" );
return;
}
// Store selected identify format in layer
layer->setCustomProperty( "identify/format", QgsRasterDataProvider::identifyFormatName( format ) );
// remove all childs of that layer from results, except the first (format)
QTreeWidgetItem *layItem = layerItem(( QObject * )layer );
for ( int i = layItem->childCount() - 1; i > 0; i-- )
{
layItem->removeChild( layItem->child( i ) );
}
// let know QgsMapToolIdentify that format changed
emit formatChanged( layer );
// Expand
layItem->setExpanded( true );
for ( int i = 1; i < layItem->childCount(); i++ )
{
QTreeWidgetItem *subItem = layItem->child( i );
subItem->setExpanded( true );
for ( int j = 0; j < subItem->childCount(); j++ )
{
subItem->child( j )->setExpanded( true );
}
}
}

View File

@ -22,6 +22,9 @@
#include "qgsattributeaction.h"
#include "qgscontexthelp.h"
#include "qgsfeature.h"
#include "qgsfeaturestore.h"
#include "qgsfield.h"
#include "qgscoordinatereferencesystem.h"
#include <QWidget>
#include <QList>
@ -37,11 +40,54 @@ class QgsRasterLayer;
class QgsHighlight;
class QgsMapCanvas;
class QDockWidget;
class QgsWebView;
/**
*@author Gary E.Sherman
*/
class QgsWebView : public QWebView
{
Q_OBJECT;
public:
QgsWebView( QWidget *parent = 0 );
QSize sizeHint() const;
public slots:
void print( void );
protected:
void contextMenuEvent( QContextMenuEvent* );
};
class QgsIdentifyResultsFeatureItem: public QTreeWidgetItem
{
public:
QgsIdentifyResultsFeatureItem( const QgsFields &fields, const QgsFeature &feature, const QgsCoordinateReferenceSystem &crs, const QStringList & strings = QStringList() );
QgsFields fields() const { return mFields; }
QgsFeature feature() const { return mFeature; }
QgsCoordinateReferenceSystem crs() { return mCrs; }
private:
QgsFields mFields;
QgsFeature mFeature;
QgsCoordinateReferenceSystem mCrs;
};
class QgsIdentifyResultsWebViewItem: public QObject, public QTreeWidgetItem
{
Q_OBJECT
public:
QgsIdentifyResultsWebViewItem( QTreeWidget *treeWidget = 0 );
QgsWebView *webView() { return mWebView; }
void setHtml( const QString &html );
public slots:
void loadFinished( bool ok );
private:
QgsWebView *mWebView;
};
class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBase
{
Q_OBJECT
@ -60,9 +106,12 @@ class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBa
const QMap< QString, QString > &derivedAttributes );
/** Add add feature from other layer */
void addFeature( QgsRasterLayer *layer, QString label,
void addFeature( QgsRasterLayer *layer,
QString label,
const QMap< QString, QString > &attributes,
const QMap< QString, QString > &derivedAttributes );
const QMap< QString, QString > &derivedAttributes,
const QgsFields &fields = QgsFields(),
const QgsFeature &feature = QgsFeature() );
/** map tool was deactivated */
void deactivate();
@ -75,6 +124,11 @@ class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBa
signals:
void selectedFeatureChanged( QgsVectorLayer *, QgsFeatureId featureId );
// Emited when raster identify format of a layer changed
void formatChanged( QgsRasterLayer *layer );
void copyToClipboard( QgsFeatureStore& featureStore );
public slots:
/** Remove results */
void clear();
@ -106,7 +160,6 @@ class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBa
//! sends signal if current feature id has changed
void handleCurrentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous );
/* Item in tree was clicked */
void itemClicked( QTreeWidgetItem *lvi, int column );
@ -119,16 +172,22 @@ class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBa
void on_mExpandToolButton_clicked( bool checked ) { Q_UNUSED( checked ); expandAll(); }
void on_mCollapseToolButton_clicked( bool checked ) { Q_UNUSED( checked ); collapseAll(); }
void on_mCopyToolButton_clicked( bool checked );
void formatChanged( int index );
void openUrl( const QUrl &url );
void printCurrentItem();
private:
QMenu *mActionPopup;
QMap<QTreeWidgetItem *, QgsHighlight * > mHighlights;
QgsMapCanvas *mCanvas;
QList<QgsFeature> mFeatures;
QgsVectorLayer *vectorLayer( QTreeWidgetItem *item );
QgsRasterLayer *rasterLayer( QTreeWidgetItem *item );
QTreeWidgetItem *featureItem( QTreeWidgetItem *item );
QTreeWidgetItem *layerItem( QTreeWidgetItem *item );
QTreeWidgetItem *layerItem( QObject *layer );
@ -148,16 +207,4 @@ class QgsIdentifyResultsDialog: public QDialog, private Ui::QgsIdentifyResultsBa
QDockWidget *mDock;
};
class QgsWebView : public QWebView
{
Q_OBJECT;
public:
QgsWebView( QWidget *parent = 0 );
public slots:
void print( void );
protected:
void contextMenuEvent( QContextMenuEvent* );
};
#endif

View File

@ -47,6 +47,8 @@ QgsMapToolIdentifyAction::QgsMapToolIdentifyAction( QgsMapCanvas* canvas )
// set cursor
QPixmap myIdentifyQPixmap = QPixmap(( const char ** ) identify_cursor );
mCursor = QCursor( myIdentifyQPixmap, 1, 1 );
connect( this, SIGNAL( changedRasterResults( QList<RasterResult>& ) ), this, SLOT( handleChangedRasterResults( QList<RasterResult>& ) ) );
}
QgsMapToolIdentifyAction::~QgsMapToolIdentifyAction()
@ -60,8 +62,13 @@ QgsMapToolIdentifyAction::~QgsMapToolIdentifyAction()
QgsIdentifyResultsDialog *QgsMapToolIdentifyAction::resultsDialog()
{
if ( !mResultsDialog )
{
mResultsDialog = new QgsIdentifyResultsDialog( mCanvas, mCanvas->window() );
connect( mResultsDialog, SIGNAL( formatChanged( QgsRasterLayer * ) ), this, SLOT( formatChanged( QgsRasterLayer * ) ) );
connect( mResultsDialog, SIGNAL( copyToClipboard( QgsFeatureStore & ) ), this, SLOT( handleCopyToClipboard( QgsFeatureStore & ) ) );
}
return mResultsDialog;
}
@ -93,10 +100,14 @@ void QgsMapToolIdentifyAction::canvasReleaseEvent( QMouseEvent *e )
QList<VectorResult>::const_iterator vresult;
for ( vresult = results().mVectorResults.begin(); vresult != results().mVectorResults.end(); ++vresult )
{
resultsDialog()->addFeature( vresult->mLayer, vresult->mFeature, vresult->mDerivedAttributes );
}
QList<RasterResult>::const_iterator rresult;
for ( rresult = results().mRasterResults.begin(); rresult != results().mRasterResults.end(); ++rresult )
resultsDialog()->addFeature( rresult->mLayer, rresult->mLabel, rresult->mAttributes, rresult->mDerivedAttributes );
{
resultsDialog()->addFeature( rresult->mLayer, rresult->mLabel, rresult->mAttributes, rresult->mDerivedAttributes, rresult->mFields, rresult->mFeature );
}
if ( res )
{
@ -118,6 +129,17 @@ void QgsMapToolIdentifyAction::canvasReleaseEvent( QMouseEvent *e )
}
}
void QgsMapToolIdentifyAction::handleChangedRasterResults( QList<RasterResult>& rasterResults )
{
// Add new result after raster format change
QgsDebugMsg( QString( "%1 raster results" ).arg( rasterResults.size() ) );
QList<RasterResult>::const_iterator rresult;
for ( rresult = rasterResults.begin(); rresult != rasterResults.end(); ++rresult )
{
resultsDialog()->addFeature( rresult->mLayer, rresult->mLabel, rresult->mAttributes, rresult->mDerivedAttributes, rresult->mFields, rresult->mFeature );
}
}
void QgsMapToolIdentifyAction::activate()
{
resultsDialog()->activate();
@ -137,3 +159,9 @@ QGis::UnitType QgsMapToolIdentifyAction::displayUnits()
return QGis::fromLiteral( settings.value( "/qgis/measure/displayunits", QGis::toLiteral( QGis::Meters ) ).toString() );
}
void QgsMapToolIdentifyAction::handleCopyToClipboard( QgsFeatureStore & featureStore )
{
QgsDebugMsg( QString( "features count = %1" ).arg( featureStore.features().size() ) );
emit copyToClipboard( featureStore );
}

View File

@ -20,6 +20,8 @@
#include "qgsmaptoolidentify.h"
#include "qgspoint.h"
#include "qgsfeature.h"
#include "qgsfeaturestore.h"
#include "qgsfield.h"
#include "qgsdistancearea.h"
#include <QObject>
@ -61,14 +63,20 @@ class QgsMapToolIdentifyAction : public QgsMapToolIdentify
virtual void deactivate();
public slots:
void handleCopyToClipboard( QgsFeatureStore & );
void handleChangedRasterResults( QList<RasterResult>& rasterResults );
signals:
void identifyProgress( int, int );
void identifyMessage( QString );
void copyToClipboard( QgsFeatureStore & );
private:
bool identifyLayer( QgsMapLayer *layer, int x, int y );
bool identifyRasterLayer( QgsRasterLayer *layer, int x, int y );
bool identifyVectorLayer( QgsVectorLayer *layer, int x, int y );
void identify( QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel );
bool identifyLayer( QgsMapLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel );
bool identifyRasterLayer( QgsRasterLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel );
bool identifyVectorLayer( QgsVectorLayer *layer, QgsPoint point );
//! Pointer to the identify results dialog for name/value pairs
QPointer<QgsIdentifyResultsDialog> mResultsDialog;

View File

@ -65,9 +65,12 @@ SET(QGIS_CORE_SRCS
qgsfeature.cpp
qgsfeatureiterator.cpp
qgsfeaturerequest.cpp
qgsfeaturestore.cpp
qgsfield.cpp
qgsgeometry.cpp
qgsgeometryvalidator.cpp
qgsgml.cpp
qgsgmlschema.cpp
qgshttptransaction.cpp
qgslabel.cpp
qgslabelattributes.cpp
@ -119,7 +122,6 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerjoinbuffer.cpp
qgsvectorlayerundocommand.cpp
qgsvectoroverlay.cpp
qgswfsdata.cpp
qgsnetworkaccessmanager.cpp
@ -295,6 +297,8 @@ SET(QGIS_CORE_MOC_HDRS
qgscoordinatetransform.h
qgsdataitem.h
qgsdataprovider.h
qgsgml.h
qgsgmlschema.h
qgshttptransaction.h
qgsmaplayer.h
qgsmaplayerregistry.h
@ -311,7 +315,6 @@ SET(QGIS_CORE_MOC_HDRS
qgsvectorlayereditbuffer.h
qgsnetworkaccessmanager.h
qgsvectordataprovider.h
qgswfsdata.h
qgsgeometryvalidator.h
composer/qgsaddremoveitemcommand.h
@ -383,8 +386,11 @@ SET(QGIS_CORE_HDRS
qgsfeature.h
qgsfeatureiterator.h
qgsfeaturerequest.h
qgsfeaturestore.h
qgsfield.h
qgsgeometry.h
qgsgml.h
qgsgmlschema.h
qgshttptransaction.h
qgslabel.h
qgslabelattributes.h
@ -431,7 +437,6 @@ SET(QGIS_CORE_HDRS
qgsvectorlayerimport.h
qgsvectorlayerundocommand.h
qgsvectoroverlay.h
qgswfsdata.h
qgstolerance.h
diagram/qgsdiagram.h

View File

@ -358,7 +358,7 @@ class CORE_EXPORT QgsExpression
static const int HOUR = 60 * 60;
static const int MINUTE = 60;
public:
Interval( double seconds = 0 ): mSeconds(seconds), mValid(true) { }
Interval( double seconds = 0 ): mSeconds( seconds ), mValid( true ) { }
~Interval();
double years() { return mSeconds / YEARS;}
double months() { return mSeconds / MONTHS; }
@ -610,6 +610,5 @@ class CORE_EXPORT QgsExpression
};
Q_DECLARE_METATYPE( QgsExpression::Interval );
Q_DECLARE_METATYPE( QgsGeometry );
#endif // QGSEXPRESSION_H

View File

@ -264,4 +264,6 @@ typedef QMap<int, QString> QgsFieldNameMap;
typedef QList<QgsFeature> QgsFeatureList;
Q_DECLARE_METATYPE( QgsFeatureList );
#endif

View File

@ -0,0 +1,39 @@
/***************************************************************************
qgsfeaturestore.cpp
--------------------------------------
Date : February 2013
Copyright : (C) 2013 by Radim Blazek
Email : radim.blazek@gmail.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsfeaturestore.h"
QgsFeatureStore::QgsFeatureStore( )
{
}
QgsFeatureStore::QgsFeatureStore( const QgsFeatureStore &rhs )
: mFields( rhs.mFields )
, mCrs( rhs.mCrs )
, mFeatures( rhs.mFeatures )
, mParams( rhs.mParams )
{
}
QgsFeatureStore::QgsFeatureStore( const QgsFields& fields, const QgsCoordinateReferenceSystem& crs )
: mFields( fields )
, mCrs( crs )
{
}
QgsFeatureStore::~QgsFeatureStore( )
{
}

View File

@ -0,0 +1,80 @@
/***************************************************************************
qgsfeaturestore.h
--------------------------------------
Date : February 2013
Copyright : (C) 2013 by Radim Blazek
Email : radim.blazek@gmail.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSFEATURESTORE_H
#define QGSFEATURESTORE_H
#include "qgis.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgslogger.h"
#include "qgscoordinatereferencesystem.h"
#include <QList>
#include <QMetaType>
#include <QVariant>
/** \ingroup core
* Container for features with the same fields and crs.
*/
class CORE_EXPORT QgsFeatureStore
{
public:
//! Constructor
QgsFeatureStore();
//! Constructor
QgsFeatureStore( const QgsFeatureStore &rhs );
//! Constructor
QgsFeatureStore( const QgsFields& fields, const QgsCoordinateReferenceSystem& crs );
//! Destructor
~QgsFeatureStore();
/** Get fields list */
QgsFields& fields() { return mFields; }
/** Set fields */
void setFields( const QgsFields & fields ) { mFields = fields; }
/** Get crs */
QgsCoordinateReferenceSystem crs() const { return mCrs; }
/** Set crs */
void setCrs( const QgsCoordinateReferenceSystem& crs ) { mCrs = crs; }
/** Get features list reference */
QgsFeatureList& features() { return mFeatures; }
/** Get map of optional parameters */
QMap<QString, QVariant>& params() { return mParams; }
private:
QgsFields mFields;
QgsCoordinateReferenceSystem mCrs;
QgsFeatureList mFeatures;
// Optional parameters
QMap<QString, QVariant> mParams;
};
typedef QList<QgsFeatureStore> QgsFeatureStoreList;
Q_DECLARE_METATYPE( QgsFeatureStore );
Q_DECLARE_METATYPE( QgsFeatureStoreList );
#endif

View File

@ -624,4 +624,6 @@ class CORE_EXPORT QgsGeometry
double leftOf( double x, double y, double& x1, double& y1, double& x2, double& y2 );
}; // class QgsGeometry
Q_DECLARE_METATYPE( QgsGeometry );
#endif

View File

@ -1,5 +1,5 @@
/***************************************************************************
qgswfsdata.cpp
qgsgml.cpp
--------------------------------------
Date : Sun Sep 16 12:19:51 AKDT 2007
Copyright : (C) 2007 by Gary E. Sherman
@ -12,7 +12,7 @@
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgswfsdata.h"
#include "qgsgml.h"
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsgeometry.h"
@ -27,60 +27,44 @@
#include <QSettings>
#include <QUrl>
#include <limits>
const char NS_SEPARATOR = '?';
const QString GML_NAMESPACE = "http://www.opengis.net/gml";
QgsWFSData::QgsWFSData(
const QString& uri,
QgsRectangle* extent,
QMap<QgsFeatureId, QgsFeature*> &features,
QMap<QgsFeatureId, QString > &idMap,
QgsGml::QgsGml(
const QString& typeName,
const QString& geometryAttribute,
const QMap<QString, QPair<int, QgsField> >& thematicAttributes,
QGis::WkbType* wkbType )
const QgsFields & fields )
: QObject(),
mUri( uri ),
mExtent( extent ),
mFeatures( features ),
mIdMap( idMap ),
mTypeName( typeName ),
mGeometryAttribute( geometryAttribute ),
mThematicAttributes( thematicAttributes ),
mWkbType( wkbType ),
mFinished( false ),
mFeatureCount( 0 )
{
//find out mTypeName from uri
QStringList arguments = uri.split( "&" );
QStringList::const_iterator it;
for ( it = arguments.constBegin(); it != arguments.constEnd(); ++it )
mThematicAttributes.clear();
for ( int i = 0; i < fields.size(); i++ )
{
if ( it->startsWith( "TYPENAME", Qt::CaseInsensitive ) )
{
mTypeName = it->section( "=", 1, 1 );
//and strip away namespace prefix
QStringList splitList = mTypeName.split( ":" );
if ( splitList.size() > 1 )
{
mTypeName = splitList.at( 1 );
}
QgsDebugMsg( QString( "mTypeName is: %1" ).arg( mTypeName ) );
}
mThematicAttributes.insert( fields[i].name(), qMakePair( i, fields[i] ) );
}
mEndian = QgsApplication::endian();
}
QgsWFSData::~QgsWFSData()
QgsGml::~QgsGml()
{
}
int QgsWFSData::getWFSData()
int QgsGml::getFeatures( const QString& uri, QGis::WkbType* wkbType, QgsRectangle* extent )
{
mUri = uri;
mWkbType = wkbType;
mExtent = extent;
XML_Parser p = XML_ParserCreateNS( NULL, NS_SEPARATOR );
XML_SetUserData( p, this );
XML_SetElementHandler( p, QgsWFSData::start, QgsWFSData::end );
XML_SetCharacterDataHandler( p, QgsWFSData::chars );
XML_SetElementHandler( p, QgsGml::start, QgsGml::end );
XML_SetCharacterDataHandler( p, QgsGml::chars );
//start with empty extent
if ( mExtent )
@ -88,7 +72,6 @@ int QgsWFSData::getWFSData()
mExtent->set( 0, 0, 0, 0 );
}
//QUrl requestUrl( mUri );
QNetworkRequest request( mUri );
QNetworkReply* reply = QgsNetworkAccessManager::instance()->get( request );
@ -140,12 +123,30 @@ int QgsWFSData::getWFSData()
return 0;
}
void QgsWFSData::setFinished( )
int QgsGml::getFeatures( const QByteArray &data, QGis::WkbType* wkbType, QgsRectangle* extent )
{
QgsDebugMsg( "Entered" );
mWkbType = wkbType;
mExtent = extent;
if ( mExtent )
{
mExtent->set( 0, 0, 0, 0 );
}
XML_Parser p = XML_ParserCreateNS( NULL, NS_SEPARATOR );
XML_SetUserData( p, this );
XML_SetElementHandler( p, QgsGml::start, QgsGml::end );
XML_SetCharacterDataHandler( p, QgsGml::chars );
int atEnd = 1;
XML_Parse( p, data.constData(), data.size(), atEnd );
return 0;
}
void QgsGml::setFinished( )
{
mFinished = true;
}
void QgsWFSData::handleProgressEvent( qint64 progress, qint64 totalSteps )
void QgsGml::handleProgressEvent( qint64 progress, qint64 totalSteps )
{
emit dataReadProgress( progress );
if ( totalSteps < 0 )
@ -156,13 +157,15 @@ void QgsWFSData::handleProgressEvent( qint64 progress, qint64 totalSteps )
emit dataProgressAndSteps( progress, totalSteps );
}
void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
void QgsGml::startElement( const XML_Char* el, const XML_Char** attr )
{
QString elementName( el );
QString localName = elementName.section( NS_SEPARATOR, 1, 1 );
QStringList splitName = elementName.split( NS_SEPARATOR );
QString localName = splitName.last();
QString ns = splitName.size() > 1 ? splitName.first() : "";
if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "coordinates" )
{
mParseModeStack.push( QgsWFSData::coordinate );
mParseModeStack.push( QgsGml::coordinate );
mStringCash.clear();
mCoordinateSeparator = readAttribute( "cs", attr );
if ( mCoordinateSeparator.isEmpty() )
@ -177,24 +180,24 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
}
else if ( localName == mGeometryAttribute )
{
mParseModeStack.push( QgsWFSData::geometry );
mParseModeStack.push( QgsGml::geometry );
}
else if ( mParseModeStack.size() == 0 && elementName == GML_NAMESPACE + NS_SEPARATOR + "boundedBy" )
{
mParseModeStack.push( QgsWFSData::boundingBox );
mParseModeStack.push( QgsGml::boundingBox );
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "featureMember" )
{
}
else if ( localName == mTypeName )
{
mCurrentFeature = new QgsFeature( mFeatureCount );
QgsAttributes attributes( mThematicAttributes.size() ); //add empty attributes
mCurrentFeature->setAttributes( attributes );
mParseModeStack.push( QgsWFSData::featureMember );
}
else if ( localName == mTypeName )
{
mParseModeStack.push( QgsGml::feature );
mCurrentFeatureId = readAttribute( "fid", attr );
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Box" && mParseModeStack.top() == QgsWFSData::boundingBox )
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Box" && mParseModeStack.top() == QgsGml::boundingBox )
{
//read attribute srsName="EPSG:26910"
int epsgNr;
@ -212,7 +215,7 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiPoint" )
{
mParseModeStack.push( QgsWFSData::multiPoint );
mParseModeStack.push( QgsGml::multiPoint );
//we need one nested list for intermediate WKB
std::list<unsigned char*> wkbList;
std::list<int> wkbSizeList;
@ -221,7 +224,7 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiLineString" )
{
mParseModeStack.push( QgsWFSData::multiLine );
mParseModeStack.push( QgsGml::multiLine );
//we need one nested list for intermediate WKB
std::list<unsigned char*> wkbList;
std::list<int> wkbSizeList;
@ -230,21 +233,23 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "MultiPolygon" )
{
mParseModeStack.push( QgsWFSData::multiPolygon );
mParseModeStack.push( QgsGml::multiPolygon );
}
else if ( mParseModeStack.size() == 1 && mParseModeStack.top() == QgsWFSData::featureMember && mThematicAttributes.find( localName ) != mThematicAttributes.end() )
else if ( mParseModeStack.size() == 1 && mParseModeStack.top() == QgsGml::feature && mThematicAttributes.find( localName ) != mThematicAttributes.end() )
{
mParseModeStack.push( QgsWFSData::attribute );
mParseModeStack.push( QgsGml::attribute );
mAttributeName = localName;
mStringCash.clear();
}
}
void QgsWFSData::endElement( const XML_Char* el )
void QgsGml::endElement( const XML_Char* el )
{
QString elementName( el );
QString localName = elementName.section( NS_SEPARATOR, 1, 1 );
QStringList splitName = elementName.split( NS_SEPARATOR );
QString localName = splitName.last();
QString ns = splitName.size() > 1 ? splitName.first() : "";
if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "coordinates" )
{
if ( !mParseModeStack.empty() )
@ -279,7 +284,6 @@ void QgsWFSData::endElement( const XML_Char* el )
var = QVariant( mStringCash );
break;
}
mCurrentFeature->setAttribute( att_it.value().first, QVariant( mStringCash ) );
}
}
@ -290,7 +294,7 @@ void QgsWFSData::endElement( const XML_Char* el )
mParseModeStack.pop();
}
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "boundedBy" && mParseModeStack.top() == QgsWFSData::boundingBox )
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "boundedBy" && mParseModeStack.top() == QgsGml::boundingBox )
{
//create bounding box from mStringCash
if ( createBBoxFromCoordinateString( mExtent, mStringCash ) != 0 )
@ -303,12 +307,14 @@ void QgsWFSData::endElement( const XML_Char* el )
mParseModeStack.pop();
}
}
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "featureMember" )
//else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "featureMember" )
else if ( localName == mTypeName )
{
if ( mCurrentWKBSize > 0 )
{
mCurrentFeature->setGeometryAndOwnership( mCurrentWKB, mCurrentWKBSize );
// TODO: what QgsFfeature.isValid() really means? Feature could be valid even without geometry?
mCurrentFeature->setValid( true );
}
mFeatures.insert( mCurrentFeature->id(), mCurrentFeature );
if ( !mCurrentFeatureId.isEmpty() )
@ -326,7 +332,7 @@ void QgsWFSData::endElement( const XML_Char* el )
//error
}
if ( mParseModeStack.top() != QgsWFSData::multiPoint )
if ( mParseModeStack.top() != QgsGml::multiPoint )
{
//directly add WKB point to the feature
if ( getPointWKB( &mCurrentWKB, &mCurrentWKBSize, *( pointList.begin() ) ) != 0 )
@ -366,7 +372,7 @@ void QgsWFSData::endElement( const XML_Char* el )
{
//error
}
if ( mParseModeStack.top() != QgsWFSData::multiLine )
if ( mParseModeStack.top() != QgsGml::multiLine )
{
if ( getLineWKB( &mCurrentWKB, &mCurrentWKBSize, pointList ) != 0 )
{
@ -418,7 +424,7 @@ void QgsWFSData::endElement( const XML_Char* el )
{
*mWkbType = QGis::WKBPolygon;
}
if ( mParseModeStack.top() != QgsWFSData::multiPolygon )
if ( mParseModeStack.top() != QgsGml::multiPolygon )
{
createPolygonFromFragments();
}
@ -452,7 +458,7 @@ void QgsWFSData::endElement( const XML_Char* el )
}
}
void QgsWFSData::characters( const XML_Char* chars, int len )
void QgsGml::characters( const XML_Char* chars, int len )
{
//save chars in mStringCash attribute mode or coordinate mode
if ( mParseModeStack.size() == 0 )
@ -460,15 +466,14 @@ void QgsWFSData::characters( const XML_Char* chars, int len )
return;
}
QgsWFSData::parseMode theParseMode = mParseModeStack.top();
if ( theParseMode == QgsWFSData::attribute || theParseMode == QgsWFSData::coordinate )
QgsGml::ParseMode theParseMode = mParseModeStack.top();
if ( theParseMode == QgsGml::attribute || theParseMode == QgsGml::coordinate )
{
mStringCash.append( QString::fromUtf8( chars, len ) );
}
}
int QgsWFSData::readEpsgFromAttribute( int& epsgNr, const XML_Char** attr ) const
int QgsGml::readEpsgFromAttribute( int& epsgNr, const XML_Char** attr ) const
{
int i = 0;
while ( attr[i] != NULL )
@ -499,7 +504,7 @@ int QgsWFSData::readEpsgFromAttribute( int& epsgNr, const XML_Char** attr ) cons
return 2;
}
QString QgsWFSData::readAttribute( const QString& attributeName, const XML_Char** attr ) const
QString QgsGml::readAttribute( const QString& attributeName, const XML_Char** attr ) const
{
int i = 0;
while ( attr[i] != NULL )
@ -513,7 +518,7 @@ QString QgsWFSData::readAttribute( const QString& attributeName, const XML_Char*
return QString();
}
int QgsWFSData::createBBoxFromCoordinateString( QgsRectangle* bb, const QString& coordString ) const
int QgsGml::createBBoxFromCoordinateString( QgsRectangle* bb, const QString& coordString ) const
{
if ( !bb )
{
@ -537,7 +542,7 @@ int QgsWFSData::createBBoxFromCoordinateString( QgsRectangle* bb, const QString&
return 0;
}
int QgsWFSData::pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString ) const
int QgsGml::pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString ) const
{
//tuples are separated by space, x/y by ','
QStringList tuples = coordString.split( mTupleSeparator, QString::SkipEmptyParts );
@ -568,7 +573,7 @@ int QgsWFSData::pointsFromCoordinateString( std::list<QgsPoint>& points, const Q
return 0;
}
int QgsWFSData::getPointWKB( unsigned char** wkb, int* size, const QgsPoint& point ) const
int QgsGml::getPointWKB( unsigned char** wkb, int* size, const QgsPoint& point ) const
{
int wkbSize = 1 + sizeof( int ) + 2 * sizeof( double );
*size = wkbSize;
@ -588,7 +593,7 @@ int QgsWFSData::getPointWKB( unsigned char** wkb, int* size, const QgsPoint& poi
return 0;
}
int QgsWFSData::getLineWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& lineCoordinates ) const
int QgsGml::getLineWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& lineCoordinates ) const
{
int wkbSize = 1 + 2 * sizeof( int ) + lineCoordinates.size() * 2 * sizeof( double );
*size = wkbSize;
@ -619,7 +624,7 @@ int QgsWFSData::getLineWKB( unsigned char** wkb, int* size, const std::list<QgsP
return 0;
}
int QgsWFSData::getRingWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& ringCoordinates ) const
int QgsGml::getRingWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& ringCoordinates ) const
{
int wkbSize = sizeof( int ) + ringCoordinates.size() * 2 * sizeof( double );
*size = wkbSize;
@ -643,7 +648,7 @@ int QgsWFSData::getRingWKB( unsigned char** wkb, int* size, const std::list<QgsP
return 0;
}
int QgsWFSData::createMultiLineFromFragments()
int QgsGml::createMultiLineFromFragments()
{
mCurrentWKBSize = 0;
mCurrentWKBSize += 1 + 2 * sizeof( int );
@ -677,7 +682,7 @@ int QgsWFSData::createMultiLineFromFragments()
return 0;
}
int QgsWFSData::createMultiPointFromFragments()
int QgsGml::createMultiPointFromFragments()
{
mCurrentWKBSize = 0;
mCurrentWKBSize += 1 + 2 * sizeof( int );
@ -712,7 +717,7 @@ int QgsWFSData::createMultiPointFromFragments()
}
int QgsWFSData::createPolygonFromFragments()
int QgsGml::createPolygonFromFragments()
{
mCurrentWKBSize = 0;
mCurrentWKBSize += 1 + 2 * sizeof( int );
@ -744,7 +749,7 @@ int QgsWFSData::createPolygonFromFragments()
return 0;
}
int QgsWFSData::createMultiPolygonFromFragments()
int QgsGml::createMultiPolygonFromFragments()
{
mCurrentWKBSize = 0;
mCurrentWKBSize += 1 + 2 * sizeof( int );
@ -800,7 +805,7 @@ int QgsWFSData::createMultiPolygonFromFragments()
return 0;
}
int QgsWFSData::totalWKBFragmentSize() const
int QgsGml::totalWKBFragmentSize() const
{
int result = 0;
for ( std::list<std::list<int> >::const_iterator it = mCurrentWKBFragmentSizes.begin(); it != mCurrentWKBFragmentSizes.end(); ++it )
@ -813,7 +818,7 @@ int QgsWFSData::totalWKBFragmentSize() const
return result;
}
QWidget* QgsWFSData::findMainWindow() const
QWidget* QgsGml::findMainWindow() const
{
QWidget* mainWindow = 0;
@ -830,7 +835,7 @@ QWidget* QgsWFSData::findMainWindow() const
return mainWindow;
}
void QgsWFSData::calculateExtentFromFeatures() const
void QgsGml::calculateExtentFromFeatures() const
{
if ( mFeatures.size() < 1 )
{

View File

@ -1,5 +1,5 @@
/***************************************************************************
qgswfsdata.h
qgsgml.h
--------------------------------------
Date : Sun Sep 16 12:19:55 AKDT 2007
Copyright : (C) 2007 by Gary E. Sherman
@ -12,8 +12,8 @@
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSWFSDATA_H
#define QGSWFSDATA_H
#ifndef QGSGML_H
#define QGSGML_H
#include <expat.h>
#include "qgis.h"
@ -21,43 +21,48 @@
#include "qgsdataprovider.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgslogger.h"
#include "qgspoint.h"
#include <list>
#include <set>
#include <stack>
#include <QPair>
#include <QByteArray>
#include <QDomElement>
#include <QStringList>
#include <QStack>
class QgsRectangle;
class QgsCoordinateReferenceSystem;
/**This class reads data from a WFS server or alternatively from a GML file. It uses the expat XML parser and an event based model to keep performance high. The parsing starts when the first data arrives, it does not wait until the request is finished*/
class CORE_EXPORT QgsWFSData: public QObject
class CORE_EXPORT QgsGml: public QObject
{
Q_OBJECT
public:
/** Constructor.
@param uri request uri
@param extent the extent of the WFS layer
@param features the features of the layer
@param idMap
@param geometryAttribute
@param thematicAttributes
@param wkbType */
QgsWFSData(
const QString& uri,
QgsRectangle* extent,
QMap<QgsFeatureId, QgsFeature* > &features,
QMap<QgsFeatureId, QString > &idMap,
QgsGml(
const QString& typeName,
const QString& geometryAttribute,
const QMap<QString, QPair<int, QgsField> >& thematicAttributes,
QGis::WkbType* wkbType );
~QgsWFSData();
const QgsFields & fields );
/**Does the Http GET request to the wfs server
@return 0 in case of success */
int getWFSData();
~QgsGml();
/** Does the Http GET request to the wfs server
* @param uri GML URL
* @return 0 in case of success
*/
int getFeatures( const QString& uri, QGis::WkbType* wkbType, QgsRectangle* extent = 0 );
/** Read from GML data. Constructor uri param is ignored */
int getFeatures( const QByteArray &data, QGis::WkbType* wkbType, QgsRectangle* extent = 0 );
/** Get parsed features for given type name */
QMap<QgsFeatureId, QgsFeature* > featuresMap() const { return mFeatures; }
/** Get feature ids map */
QMap<QgsFeatureId, QString > idsMap() const { return mIdMap; }
private slots:
void setFinished();
/**Takes progress value and total steps and emit signals 'dataReadProgress' and 'totalStepUpdate'*/
@ -71,10 +76,12 @@ class CORE_EXPORT QgsWFSData: public QObject
private:
enum parseMode
enum ParseMode
{
none,
boundingBox,
featureMember,
//featureMember, // gml:featureMember
feature, // feature element containint attrs and geo (inside gml:featureMember)
attribute,
geometry,
coordinate,
@ -86,26 +93,25 @@ class CORE_EXPORT QgsWFSData: public QObject
multiPolygon
};
QgsWFSData();
/**XML handler methods*/
void startElement( const XML_Char* el, const XML_Char** attr );
void endElement( const XML_Char* el );
void characters( const XML_Char* chars, int len );
static void start( void* data, const XML_Char* el, const XML_Char** attr )
{
static_cast<QgsWFSData*>( data )->startElement( el, attr );
static_cast<QgsGml*>( data )->startElement( el, attr );
}
static void end( void* data, const XML_Char* el )
{
static_cast<QgsWFSData*>( data )->endElement( el );
static_cast<QgsGml*>( data )->endElement( el );
}
static void chars( void* data, const XML_Char* chars, int len )
{
static_cast<QgsWFSData*>( data )->characters( chars, len );
static_cast<QgsGml*>( data )->characters( chars, len );
}
//helper routines
/**Reads attribute srsName="EpsgCrsId:..."
@param epsgNr result
@param attr attribute strings
@ -143,22 +149,36 @@ class CORE_EXPORT QgsWFSData: public QObject
does not provider extent information.*/
void calculateExtentFromFeatures() const;
/** Get safely (if empty) top from mode stack */
ParseMode modeStackTop() { return mParseModeStack.isEmpty() ? none : mParseModeStack.top(); }
/** Safely (if empty) pop from mode stack */
ParseMode modeStackPop() { return mParseModeStack.isEmpty() ? none : mParseModeStack.pop(); }
QString mTypeName;
QString mUri;
//results are members such that handler routines are able to manipulate them
/**Bounding box of the layer*/
QgsRectangle* mExtent;
/**The features of the layer*/
QMap<QgsFeatureId, QgsFeature* > &mFeatures;
/**The features of the layer, map of feature maps for each feature type*/
//QMap<QgsFeatureId, QgsFeature* > &mFeatures;
QMap<QgsFeatureId, QgsFeature* > mFeatures;
//QMap<QString, QMap<QgsFeatureId, QgsFeature* > > mFeatures;
/**Stores the relation between provider ids and WFS server ids*/
QMap<QgsFeatureId, QString > &mIdMap;
//QMap<QgsFeatureId, QString > &mIdMap;
QMap<QgsFeatureId, QString > mIdMap;
//QMap<QString, QMap<QgsFeatureId, QString > > mIdMap;
/**Name of geometry attribute*/
QString mGeometryAttribute;
const QMap<QString, QPair<int, QgsField> > &mThematicAttributes;
//const QMap<QString, QPair<int, QgsField> > &mThematicAttributes;
QMap<QString, QPair<int, QgsField> > mThematicAttributes;
QGis::WkbType* mWkbType;
/**True if the request is finished*/
bool mFinished;
/**Keep track about the most important nested elements*/
std::stack<parseMode> mParseModeStack;
//std::stack<ParseMode> mParseModeStack;
QStack<ParseMode> mParseModeStack;
/**This contains the character data if an important element has been encountered*/
QString mStringCash;
QgsFeature* mCurrentFeature;
@ -174,7 +194,6 @@ class CORE_EXPORT QgsWFSData: public QObject
/**Similar to mCurrentWKB, but only the size*/
std::list< std::list<int> > mCurrentWKBFragmentSizes;
QString mAttributeName;
QString mTypeName;
QgsApplication::endian_t mEndian;
/**Coordinate separator for coordinate strings. Usually "," */
QString mCoordinateSeparator;

531
src/core/qgsgmlschema.cpp Normal file
View File

@ -0,0 +1,531 @@
/***************************************************************************
qgsgmlschema.cpp
--------------------------------------
Date : February 2013
Copyright : (C) 2013 by Radim Blazek
Email : radim.blazek@gmail.com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsgmlschema.h"
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
#include "qgsnetworkaccessmanager.h"
#include <QBuffer>
#include <QList>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QProgressDialog>
#include <QSet>
#include <QSettings>
#include <QUrl>
#include <limits>
const char NS_SEPARATOR = '?';
const QString GML_NAMESPACE = "http://www.opengis.net/gml";
QgsGmlFeatureClass::QgsGmlFeatureClass( )
{
}
QgsGmlFeatureClass::QgsGmlFeatureClass( QString name, QString path )
: mName( name )
, mPath( path )
{
}
QgsGmlFeatureClass::~QgsGmlFeatureClass()
{
}
int QgsGmlFeatureClass::fieldIndex( const QString & name )
{
for ( int i = 0; i < mFields.size(); i++ )
{
if ( mFields[i].name() == name ) return i;
}
return -1;
}
// --------------------------- QgsGmlSchema -------------------------------
QgsGmlSchema::QgsGmlSchema()
: QObject()
{
mGeometryTypes << "Point" << "MultiPoint"
<< "LineString" << "MultiLineString"
<< "Polygon" << "MultiPolygon";
}
QgsGmlSchema::~QgsGmlSchema()
{
}
QString QgsGmlSchema::readAttribute( const QString& attributeName, const XML_Char** attr ) const
{
int i = 0;
while ( attr[i] != NULL )
{
if ( attributeName.compare( attr[i] ) == 0 )
{
return QString( attr[i+1] );
}
++i;
}
return QString();
}
bool QgsGmlSchema::parseXSD( const QByteArray &xml )
{
QDomDocument dom;
QString errorMsg;
int errorLine;
int errorColumn;
if ( !dom.setContent( xml, false, &errorMsg, &errorLine, &errorColumn ) )
{
// TODO: error
return false;
}
QDomElement docElem = dom.documentElement();
QList<QDomElement> elementElements = domElements( docElem, "element" );
//QgsDebugMsg( QString( "%1 elemets read" ).arg( elementElements.size() ) );
foreach ( QDomElement elementElement, elementElements )
{
QString name = elementElement.attribute( "name" );
QString type = elementElement.attribute( "type" );
QString gmlBaseType = xsdComplexTypeGmlBaseType( docElem, stripNS( type ) );
//QgsDebugMsg( QString( "gmlBaseType = %1" ).arg( gmlBaseType ) );
//QgsDebugMsg( QString( "name = %1 gmlBaseType = %2" ).arg( name ).arg( gmlBaseType ) );
// We should only use gml:AbstractFeatureType descendants which have
// ancestor listed in gml:FeatureAssociationType (featureMember) descendant
// But we could only loose some data if XSD was not correct, I think.
if ( gmlBaseType == "AbstractFeatureType" )
{
// Get feature type definition
QgsGmlFeatureClass featureClass( name, "" );
xsdFeatureClass( docElem, stripNS( type ), featureClass );
mFeatureClassMap.insert( name, featureClass );
}
// A feature may have more geometries, we take just the first one
}
return true;
}
bool QgsGmlSchema::xsdFeatureClass( const QDomElement &element, const QString & typeName, QgsGmlFeatureClass & featureClass )
{
//QgsDebugMsg("typeName = " + typeName );
QDomElement complexTypeElement = domElement( element, "complexType", "name", typeName );
if ( complexTypeElement.isNull() ) return false;
// extension or restriction
QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
if ( extrest.isNull() )
{
extrest = domElement( complexTypeElement, "complexContent.restriction" );
}
if ( extrest.isNull() ) return false;
QString extrestName = extrest.attribute( "base" );
if ( extrestName == "gml:AbstractFeatureType" )
{
// In theory we should add gml:AbstractFeatureType default attributes gml:description
// and gml:name but it does not seem to be a common practice and we would probably
// confuse most users
}
else
{
// Get attributes from extrest
if ( !xsdFeatureClass( element, stripNS( extrestName ), featureClass ) ) return false;
}
// Supported geometry types
QStringList geometryPropertyTypes;
foreach ( QString geom, mGeometryTypes )
{
geometryPropertyTypes << geom + "PropertyType";
}
QStringList geometryAliases;
geometryAliases << "location" << "centerOf" << "position" << "extentOf"
<< "coverage" << "edgeOf" << "centerLineOf" << "multiLocation"
<< "multiCenterOf" << "multiPosition" << "multiCenterLineOf"
<< "multiEdgeOf" << "multiCoverage" << "multiExtentOf";
// Add attributes from current comple type
QList<QDomElement> sequenceElements = domElements( extrest, "sequence.element" );
foreach ( QDomElement sequenceElement, sequenceElements )
{
QString fieldName = sequenceElement.attribute( "name" );
QString fieldTypeName = stripNS( sequenceElement.attribute( "type" ) );
QString ref = sequenceElement.attribute( "ref" );
//QgsDebugMsg ( QString("fieldName = %1 fieldTypeName = %2 ref = %3").arg(fieldName).arg(fieldTypeName).arg(ref) );
if ( !ref.isEmpty() )
{
if ( ref.startsWith( "gml:" ) )
{
if ( geometryAliases.contains( stripNS( ref ) ) )
{
featureClass.geometryAttributes().append( stripNS( ref ) );
}
else
{
QgsDebugMsg( QString( "Unknown referenced GML element: %1" ).arg( ref ) );
}
}
else
{
// TODO: get type from referenced element
QgsDebugMsg( QString( "field %1.%2 is referencing %3 - not supported" ).arg( typeName ).arg( fieldName ) );
}
continue;
}
if ( fieldName.isEmpty() )
{
QgsDebugMsg( QString( "field in %1 without name" ).arg( typeName ) );
continue;
}
// type is either type attribute
if ( fieldTypeName.isEmpty() )
{
// or type is inheriting from xs:simpleType
QDomElement sequenceElementRestriction = domElement( sequenceElement, "simpleType.restriction" );
fieldTypeName = stripNS( sequenceElementRestriction.attribute( "base" ) );
}
QVariant::Type fieldType = QVariant::String;
if ( fieldTypeName.isEmpty() )
{
QgsDebugMsg( QString( "Cannot get %1.%2 field type" ).arg( typeName ).arg( fieldName ) );
}
else
{
if ( geometryPropertyTypes.contains( fieldTypeName ) )
{
// Geometry attribute
featureClass.geometryAttributes().append( fieldName );
continue;
}
if ( fieldTypeName == "decimal" )
{
fieldType = QVariant::Double;
}
else if ( fieldTypeName == "integer" )
{
fieldType = QVariant::Int;
}
}
QgsField field( fieldName, fieldType );
featureClass.fields().append( field );
}
return true;
}
QString QgsGmlSchema::xsdComplexTypeGmlBaseType( const QDomElement &element, const QString & name )
{
//QgsDebugMsg("name = " + name );
QDomElement complexTypeElement = domElement( element, "complexType", "name", name );
if ( complexTypeElement.isNull() ) return "";
QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
if ( extrest.isNull() )
{
extrest = domElement( complexTypeElement, "complexContent.restriction" );
}
if ( extrest.isNull() ) return "";
QString extrestName = extrest.attribute( "base" );
if ( extrestName.startsWith( "gml:" ) )
{
// GML base type found
return stripNS( extrestName );
}
// Continue recursively until GML base type is reached
return xsdComplexTypeGmlBaseType( element, stripNS( extrestName ) );
}
QString QgsGmlSchema::stripNS( const QString & name )
{
return name.contains( ":" ) ? name.section( ':', 1 ) : name;
}
QList<QDomElement> QgsGmlSchema::domElements( const QDomElement &element, const QString & path )
{
QList<QDomElement> list;
QStringList names = path.split( "." );
if ( names.size() == 0 ) return list;
QString name = names.value( 0 );
names.removeFirst();
QDomNode n1 = element.firstChild();
while ( !n1.isNull() )
{
QDomElement el = n1.toElement();
if ( !el.isNull() )
{
QString tagName = stripNS( el.tagName() );
if ( tagName == name )
{
if ( names.size() == 0 )
{
list.append( el );
}
else
{
list.append( domElements( el, names.join( "." ) ) );
}
}
}
n1 = n1.nextSibling();
}
return list;
}
QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path )
{
return domElements( element, path ).value( 0 );
}
QList<QDomElement> QgsGmlSchema::domElements( QList<QDomElement> &elements, const QString & attr, const QString & attrVal )
{
QList<QDomElement> list;
foreach ( QDomElement el, elements )
{
if ( el.attribute( attr ) == attrVal )
{
list << el;
}
}
return list;
}
QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path, const QString & attr, const QString & attrVal )
{
QList<QDomElement> list = domElements( element, path );
return domElements( list, attr, attrVal ).value( 0 );
}
bool QgsGmlSchema::guessSchema( const QByteArray &data )
{
QgsDebugMsg( "Entered" );
mLevel = 0;
mSkipLevel = std::numeric_limits<int>::max();
XML_Parser p = XML_ParserCreateNS( NULL, NS_SEPARATOR );
XML_SetUserData( p, this );
XML_SetElementHandler( p, QgsGmlSchema::start, QgsGmlSchema::end );
XML_SetCharacterDataHandler( p, QgsGmlSchema::chars );
int atEnd = 1;
XML_Parse( p, data.constData(), data.size(), atEnd );
return 0;
}
void QgsGmlSchema::startElement( const XML_Char* el, const XML_Char** attr )
{
Q_UNUSED( attr );
mLevel++;
QString elementName( el );
QgsDebugMsgLevel( QString( "-> %1 %2 %3" ).arg( mLevel ).arg( elementName ).arg( mLevel >= mSkipLevel ? "skip" : "" ), 5 );
if ( mLevel >= mSkipLevel )
{
//QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
return;
}
mParsePathStack.append( elementName );
QString path = mParsePathStack.join( "." );
QStringList splitName = elementName.split( NS_SEPARATOR );
QString localName = splitName.last();
QString ns = splitName.size() > 1 ? splitName.first() : "";
//QgsDebugMsg( "ns = " + ns + " localName = " + localName );
ParseMode parseMode = modeStackTop();
if ( ns == GML_NAMESPACE && localName == "boundedBy" )
{
// gml:boundedBy in feature or feature collection -> skip
mSkipLevel = mLevel + 1;
}
// GML does not specify that gml:FeatureAssociationType elements should end
// with 'Member' apart standard gml:featureMember, but it is quite usual to
// that the names ends with 'Member', e.g.: osgb:topographicMember, cityMember,...
// so this is really fail if the name does not contain 'Member'
else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
{
mParseModeStack.push( QgsGmlSchema::featureMember );
}
// UMN Mapserver simple GetFeatureInfo response layer element (ends with _layer)
else if ( elementName.endsWith( "_layer" ) )
{
// do nothing, we catch _feature children
}
// UMN Mapserver simple GetFeatureInfo response feature element (ends with _feature)
// or featureMember children
else if ( elementName.endsWith( "_feature" )
|| parseMode == QgsGmlSchema::featureMember )
{
//QgsDebugMsg ( "is feature path = " + path );
if ( mFeatureClassMap.count( localName ) == 0 )
{
mFeatureClassMap.insert( localName, QgsGmlFeatureClass( localName, path ) );
}
mCurrentFeatureName = localName;
mParseModeStack.push( QgsGmlSchema::feature );
}
else if ( parseMode == QgsGmlSchema::attribute && ns == GML_NAMESPACE && mGeometryTypes.indexOf( localName ) >= 0 )
{
// Geometry (Point,MultiPoint,...) in geometry attribute
QStringList &geometryAttributes = mFeatureClassMap[mCurrentFeatureName].geometryAttributes();
if ( geometryAttributes.count( mAttributeName ) == 0 )
{
geometryAttributes.append( mAttributeName );
}
mSkipLevel = mLevel + 1; // no need to parse children
}
else if ( parseMode == QgsGmlSchema::feature )
{
// An element in feature should be ordinary or geometry attribute
//QgsDebugMsg( "is attribute");
mParseModeStack.push( QgsGmlSchema::attribute );
mAttributeName = localName;
mStringCash.clear();
}
}
void QgsGmlSchema::endElement( const XML_Char* el )
{
QString elementName( el );
QgsDebugMsgLevel( QString( "<- %1 %2" ).arg( mLevel ).arg( elementName ), 5 );
if ( mLevel >= mSkipLevel )
{
//QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
mLevel--;
return;
}
else
{
// clear possible skip level
mSkipLevel = std::numeric_limits<int>::max();
}
QStringList splitName = elementName.split( NS_SEPARATOR );
QString localName = splitName.last();
QString ns = splitName.size() > 1 ? splitName.first() : "";
QgsGmlSchema::ParseMode parseMode = modeStackTop();
if ( parseMode == QgsGmlSchema::attribute && localName == mAttributeName )
{
// End of attribute
//QgsDebugMsg("end attribute");
modeStackPop(); // go up to feature
if ( mFeatureClassMap[mCurrentFeatureName].geometryAttributes().count( mAttributeName ) == 0 )
{
// It is not geometry attribute -> analyze value
bool ok;
mStringCash.toInt( &ok );
QVariant::Type type = QVariant::String;
if ( ok )
{
type = QVariant::Int;
}
else
{
mStringCash.toDouble( &ok );
if ( ok )
{
type = QVariant::Double;
}
}
//QgsDebugMsg( "mStringCash = " + mStringCash + " type = " + QVariant::typeToName( type ) );
//QMap<QString, QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
QList<QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
int fieldIndex = mFeatureClassMap[mCurrentFeatureName].fieldIndex( mAttributeName );
if ( fieldIndex == -1 )
{
QgsField field( mAttributeName, type );
fields.append( field );
}
else
{
QgsField &field = fields[fieldIndex];
// check if type is sufficient
if (( field.type() == QVariant::Int && ( type == QVariant::String || type == QVariant::Double ) ) ||
( field.type() == QVariant::Double && type == QVariant::String ) )
{
field.setType( type );
}
}
}
}
else if ( ns == GML_NAMESPACE && localName == "boundedBy" )
{
// was skipped
}
else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
{
mParseModeStack.push( QgsGmlSchema::featureMember );
modeStackPop();
}
mParsePathStack.removeLast();
mLevel--;
}
void QgsGmlSchema::characters( const XML_Char* chars, int len )
{
//QgsDebugMsg( QString("level %1 : %2").arg( mLevel ).arg( QString::fromUtf8( chars, len ) ) );
if ( mLevel >= mSkipLevel )
{
//QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
return;
}
//save chars in mStringCash attribute mode for value type analysis
if ( modeStackTop() == QgsGmlSchema::attribute )
{
mStringCash.append( QString::fromUtf8( chars, len ) );
}
}
QStringList QgsGmlSchema::typeNames() const
{
return mFeatureClassMap.keys();
}
QList<QgsField> QgsGmlSchema::fields( const QString & typeName )
{
if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
return mFeatureClassMap[typeName].fields();
}
QStringList QgsGmlSchema::geometryAttributes( const QString & typeName )
{
if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
return mFeatureClassMap[typeName].geometryAttributes();
}

206
src/core/qgsgmlschema.h Normal file
View File

@ -0,0 +1,206 @@
/***************************************************************************
qgsgmlschema.h
--------------------------------------
Date : Sun Sep 16 12:19:55 AKDT 2007
Copyright : (C) 2007 by Gary E. Sherman
Email : sherman at mrcc dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSGMLSCHEMA_H
#define QGSGMLSCHEMA_H
#include <expat.h>
#include "qgis.h"
#include "qgsapplication.h"
#include "qgsdataprovider.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgslogger.h"
#include "qgspoint.h"
#include <list>
#include <set>
#include <stack>
#include <QPair>
#include <QByteArray>
#include <QDomElement>
#include <QStringList>
#include <QStack>
class QgsRectangle;
class QgsCoordinateReferenceSystem;
/* Description of feature class in GML */
class CORE_EXPORT QgsGmlFeatureClass
{
public:
QgsGmlFeatureClass( );
QgsGmlFeatureClass( QString name, QString path );
~QgsGmlFeatureClass();
QList<QgsField> & fields() { return mFields; }
int fieldIndex( const QString & name );
QString path() const { return mPath; }
QStringList & geometryAttributes() { return mGeometryAttributes; }
private:
/* Feature class name:
* - element name without NS or known prefix/suffix (_feature)
* - typeName attribute name */
QString mName;
//QString mElementName;
/* Dot separated path to element including the name */
QString mPath;
/* Fields */
// Do not use QMap to keep original fields order. If it gets to performance,
// add a field index map
QList<QgsField> mFields;
/* Geometry attribute */
QStringList mGeometryAttributes;
};
class CORE_EXPORT QgsGmlSchema: public QObject
{
Q_OBJECT
public:
QgsGmlSchema();
~QgsGmlSchema();
/** Get fields info from XSD */
bool parseXSD( const QByteArray &xml );
/** Guess GML schema from data if XSD does not exist.
* Currently only recognizes UMN Mapserver GetFeatureInfo GML response.
* @param data GML data
* @return true in case of success */
bool guessSchema( const QByteArray &data );
/** Get list of dot separated paths to feature classes parsed from GML or XSD */
QStringList typeNames() const;
/** Get map of fields parsed from XSD by parseXSD */
//QMap<int, QgsField> fields();
/** Get fields for type/class name parsed from GML or XSD */
QList<QgsField> fields( const QString & typeName );
/** Get list of geometry attributes for type/class name */
QStringList geometryAttributes( const QString & typeName );
private:
enum ParseMode
{
none,
boundingBox,
featureMember, // gml:featureMember
feature, // feature element containint attrs and geo (inside gml:featureMember)
attribute,
geometry
};
/**XML handler methods*/
void startElement( const XML_Char* el, const XML_Char** attr );
void endElement( const XML_Char* el );
void characters( const XML_Char* chars, int len );
static void start( void* data, const XML_Char* el, const XML_Char** attr )
{
static_cast<QgsGmlSchema*>( data )->startElement( el, attr );
}
static void end( void* data, const XML_Char* el )
{
static_cast<QgsGmlSchema*>( data )->endElement( el );
}
static void chars( void* data, const XML_Char* chars, int len )
{
static_cast<QgsGmlSchema*>( data )->characters( chars, len );
}
//helper routines
/**Reads attribute as string
@return attribute value or an empty string if no such attribute*/
QString readAttribute( const QString& attributeName, const XML_Char** attr ) const;
/**Returns pointer to main window or 0 if it does not exist*/
QWidget* findMainWindow() const;
/** Get dom elements by path */
QList<QDomElement> domElements( const QDomElement &element, const QString & path );
/** Get dom element by path */
QDomElement domElement( const QDomElement &element, const QString & path );
/** Filter list of elements by attribute value */
QList<QDomElement> domElements( QList<QDomElement> &elements, const QString & attr, const QString & attrVal );
/** Get dom element by path and attribute value */
QDomElement domElement( const QDomElement &element, const QString & path, const QString & attr, const QString & attrVal );
/** Strip namespace from element name */
QString stripNS( const QString & name );
/** Find GML base type for complex type of given name
* @param name complex type name
* @return name of GML base type without NS, e.g. AbstractFeatureType or empty string if not pased on GML type
*/
QString xsdComplexTypeGmlBaseType( const QDomElement &element, const QString & name );
/** Get feature class information from complex type recursively */
bool xsdFeatureClass( const QDomElement &element, const QString & typeName, QgsGmlFeatureClass & featureClass );
/** Get safely (if empty) top from mode stack */
ParseMode modeStackTop() { return mParseModeStack.isEmpty() ? none : mParseModeStack.top(); }
/** Safely (if empty) pop from mode stack */
ParseMode modeStackPop() { return mParseModeStack.isEmpty() ? none : mParseModeStack.pop(); }
/**Keep track about the most important nested elements*/
//std::stack<ParseMode> mParseModeStack;
QStack<ParseMode> mParseModeStack;
/**This contains the character data if an important element has been encountered*/
QString mStringCash;
QgsFeature* mCurrentFeature;
QString mCurrentFeatureId;
int mFeatureCount;
QString mAttributeName;
/**Coordinate separator for coordinate strings. Usually "," */
QString mCoordinateSeparator;
/**Tuple separator for coordinate strings. Usually " " */
QString mTupleSeparator;
/* Schema informations guessed/parsed from GML in getSchema() */
/** Depth level, root element is 0 */
int mLevel;
/** Skip all levels under this */
int mSkipLevel;
/** Path to current level */
QStringList mParsePathStack;
QString mCurrentFeatureName;
// List of know geometries (Point, Multipoint,...)
QStringList mGeometryTypes;
/* Feature classes map with element paths as keys */
QMap<QString, QgsGmlFeatureClass> mFeatureClassMap;
};
#endif

View File

@ -26,9 +26,10 @@
#include <QStringList>
QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
: mReply(reply)
, mValid(false)
: mReply( reply )
, mValid( false )
{
QgsDebugMsg( "Entered." );
if ( !mReply ) return;
// Content type examples:
@ -37,7 +38,7 @@ QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
if ( !isMultipart( mReply ) )
{
// reply is not multipart, copy body and headers
QMap<QByteArray,QByteArray> headers;
QMap<QByteArray, QByteArray> headers;
foreach ( QByteArray h, mReply->rawHeaderList() )
{
headers.insert( h, mReply->rawHeader( h ) );
@ -59,7 +60,7 @@ QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
}
QString boundary = re.cap( 1 );
QgsDebugMsg( "boundary = " + boundary );
QgsDebugMsg( QString( "boundary = %1 size = %2" ).arg( boundary ).arg( boundary.size() ) );
boundary = "--" + boundary;
// Lines should be terminated by CRLF ("\r\n") but any new line combination may appear
@ -70,11 +71,19 @@ QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
//QVector<QByteArray> partBodies;
while ( true )
{
// 'to' is not really 'to', but index of the next byte after the end of part
to = data.indexOf( boundary.toAscii(), from );
if ( to < 0 )
{
// It may happent that bondary is missing at the end (GeoServer)
// in that case, take everything to th end
QgsDebugMsg( QString( "No more boundaries, rest size = %1" ).arg( data.size() - from - 1 ) );
// It may be end, last boundary is followed by '--'
if ( data.size() - from - 1 == 2 && QString( data.mid( from, 2 ) ) == "--" ) // end
{
break;
}
// It may happen that boundary is missing at the end (GeoServer)
// in that case, take everything to the end
if ( data.size() - from > 1 )
{
to = data.size(); // out of range OK
@ -105,10 +114,10 @@ QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
pos++;
}
// parse headers
QMap<QByteArray,QByteArray> headersMap;
RawHeaderMap headersMap;
QByteArray headers = part.left( pos );
QgsDebugMsg( "headers:\n" + headers );
QStringList headerRows = QString( headers ).split( QRegExp( "[\n\r]+" ) );
foreach ( QString row, headerRows )
{
@ -120,10 +129,10 @@ QgsNetworkReplyParser::QgsNetworkReplyParser( QNetworkReply *reply )
mBodies.append( part.mid( pos ) );
from = to + boundary.length() + 1;
from = to + boundary.length();
}
}
mValid = true;
mValid = true;
}
bool QgsNetworkReplyParser::isMultipart( QNetworkReply *reply )

View File

@ -35,6 +35,8 @@ class CORE_EXPORT QgsNetworkReplyParser : public QObject
Q_OBJECT
public:
typedef QMap<QByteArray, QByteArray> RawHeaderMap;
/** Constructor
* @param reply */
QgsNetworkReplyParser( QNetworkReply *reply );
@ -53,11 +55,17 @@ class CORE_EXPORT QgsNetworkReplyParser : public QObject
* @return raw header */
QByteArray rawHeader( int part, const QByteArray & headerName ) const { return mHeaders.value( part ).value( headerName ); }
/** Get headers */
QList< RawHeaderMap > headers() const { return mHeaders; }
/** Get part part body
* @param part part index
* @return part body */
QByteArray body( int part ) const { return mBodies.value( part ); }
/** Get bodies */
QList<QByteArray> bodies() const { return mBodies; }
/** Parsing error */
QString error() const { return mError; }
@ -73,7 +81,7 @@ class CORE_EXPORT QgsNetworkReplyParser : public QObject
QString mError;
/* List of header maps */
QList< QMap<QByteArray, QByteArray> > mHeaders;
QList< RawHeaderMap > mHeaders;
/* List of part bodies */
QList<QByteArray> mBodies;

View File

@ -559,4 +559,64 @@ QgsRasterDataProvider* QgsRasterDataProvider::create( const QString &providerKey
return createFn( uri, format, nBands, type, width, height, geoTransform, crs, createOptions );
}
QString QgsRasterDataProvider::identifyFormatName( IdentifyFormat format )
{
switch ( format )
{
case IdentifyFormatValue:
return "Value";
case IdentifyFormatText:
return "Text";
case IdentifyFormatHtml:
return "Html";
case IdentifyFormatFeature:
return "Feature";
default:
return "Undefined";
}
}
QString QgsRasterDataProvider::identifyFormatLabel( IdentifyFormat format )
{
switch ( format )
{
case IdentifyFormatValue:
return tr( "Value" );
case IdentifyFormatText:
return ( "Text" );
case IdentifyFormatHtml:
return tr( "Html" );
case IdentifyFormatFeature:
return tr( "Feature" );
default:
return "Undefined";
}
}
QgsRasterDataProvider::IdentifyFormat QgsRasterDataProvider::identifyFormatFromName( QString formatName )
{
if ( formatName == "Value" ) return IdentifyFormatValue;
if ( formatName == "Text" ) return IdentifyFormatText;
if ( formatName == "Html" ) return IdentifyFormatHtml;
if ( formatName == "Feature" ) return IdentifyFormatFeature;
return IdentifyFormatUndefined;
}
QgsRasterInterface::Capability QgsRasterDataProvider::identifyFormatToCapability( IdentifyFormat format )
{
switch ( format )
{
case IdentifyFormatValue:
return IdentifyValue;
case IdentifyFormatText:
return IdentifyText;
case IdentifyFormatHtml:
return IdentifyHtml;
case IdentifyFormatFeature:
return IdentifyFeature;
default:
return NoCapabilities;
}
}
// ENDS

View File

@ -26,6 +26,8 @@
#include "qgsrectangle.h"
#include "qgsdataprovider.h"
#include "qgserror.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgsrasterinterface.h"
#include "qgscolorrampshader.h"
#include "qgsrasterpyramid.h"
@ -84,13 +86,14 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
enum IdentifyFormat
{
IdentifyFormatValue = 0,
IdentifyFormatText = 1,
IdentifyFormatHtml = 1 << 1,
IdentifyFormatUndefined = 0,
IdentifyFormatValue = 1,
IdentifyFormatText = 1 << 1,
IdentifyFormatHtml = 1 << 2,
// In future it should be possible to get from GetFeatureInfo (WMS) in GML
// vector features. It is possible to use a user type with QVariant if
// a class is declared with Q_DECLARE_METATYPE
IdentifyFormatFeature = 1 << 2
IdentifyFormatFeature = 1 << 3
};
// Progress types
@ -334,8 +337,12 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
* @param theExtent context extent
* @param theWidth context width
* @param theHeight context height
* @return map of values for all bands, keys are band numbers (from 1), empty
* if failed
* @return IdentifyFormatValue: map of values for each band, keys are band numbers
* (from 1).
* IdentifyFormatFeature: map of QgsRasterFeatureList for each sublayer
* (WMS) - TODO: it is not consistent with IdentifyFormatValue.
* IdentifyFormatHtml: map of HTML strings for each sublayer (WMS).
* Empty if failed or there are no results (TODO: better error reporting).
*/
virtual QMap<int, QVariant> identify( const QgsPoint & thePoint, IdentifyFormat theFormat, const QgsRectangle &theExtent = QgsRectangle(), int theWidth = 0, int theHeight = 0 );
@ -462,6 +469,11 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
const QStringList & theConfigOptions, const QString & fileFormat )
{ Q_UNUSED( pyramidsFormat ); Q_UNUSED( theConfigOptions ); Q_UNUSED( fileFormat ); return QString(); }
static QString identifyFormatName( IdentifyFormat format );
static IdentifyFormat identifyFormatFromName( QString formatName );
static QString identifyFormatLabel( IdentifyFormat format );
static Capability identifyFormatToCapability( IdentifyFormat format );
signals:
/** Emit a signal to notify of the progress event.
* Emited theProgress is in percents (0.0-100.0) */

View File

@ -1706,6 +1706,27 @@ void QgsRasterLayer::setDataProvider( QString const & provider )
QgsRasterProjector * projector = new QgsRasterProjector;
mPipe.set( projector );
// Set default identify format - use the richest format available
int capabilities = mDataProvider->capabilities();
QgsRasterDataProvider::IdentifyFormat identifyFormat = QgsRasterDataProvider::IdentifyFormatUndefined;
if ( capabilities & QgsRasterInterface::IdentifyFeature )
{
identifyFormat = QgsRasterDataProvider::IdentifyFormatFeature;
}
else if ( capabilities & QgsRasterInterface::IdentifyValue )
{
identifyFormat = QgsRasterDataProvider::IdentifyFormatValue;
}
else if ( capabilities & QgsRasterInterface::IdentifyHtml )
{
identifyFormat = QgsRasterDataProvider::IdentifyFormatHtml;
}
else if ( capabilities & QgsRasterInterface::IdentifyText )
{
identifyFormat = QgsRasterDataProvider::IdentifyFormatText;
}
setCustomProperty( "identify/format", QgsRasterDataProvider::identifyFormatName( identifyFormat ) );
// Store timestamp
// TODO move to provider
mLastModified = lastModified( mDataSource );

View File

@ -16,6 +16,7 @@
#include "qgscursors.h"
#include "qgsdistancearea.h"
#include "qgsfeature.h"
#include "qgsfeaturestore.h"
#include "qgsfield.h"
#include "qgsgeometry.h"
#include "qgslogger.h"
@ -79,8 +80,15 @@ bool QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, LayerType la
bool QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType )
{
bool res = false;
mLastPoint = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
mLastExtent = mCanvas->extent();
mLastMapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
return identify( mLastPoint, mLastExtent, mLastMapUnitsPerPixel, mode, layerList, layerType );
}
bool QgsMapToolIdentify::identify( QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType )
{
bool res = false;
if ( !mCanvas || mCanvas->isDrawing() )
{
return res;
@ -95,7 +103,7 @@ bool QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMap
mode = static_cast<IdentifyMode>( settings.value( "/Map/identifyMode", 0 ).toInt() );
}
if ( mode == ActiveLayer && !layerList.isEmpty() )
if ( mode == ActiveLayer && layerList.isEmpty() )
{
QgsMapLayer *layer = mCanvas->currentLayer();
@ -107,7 +115,7 @@ bool QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMap
QApplication::setOverrideCursor( Qt::WaitCursor );
res = identifyLayer( layer, x, y, layerType );
res = identifyLayer( layer, point, viewExtent, mapUnitsPerPixel, layerType );
}
else
{
@ -137,7 +145,7 @@ bool QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMap
if ( noIdentifyLayerIdList.contains( layer->id() ) )
continue;
if ( identifyLayer( layer, x, y, layerType ) )
if ( identifyLayer( layer, point, viewExtent, mapUnitsPerPixel, layerType ) )
{
res = true;
if ( mode == TopDownStopAtFirst )
@ -164,15 +172,15 @@ void QgsMapToolIdentify::deactivate()
QgsMapTool::deactivate();
}
bool QgsMapToolIdentify::identifyLayer( QgsMapLayer *layer, int x, int y, LayerType layerType )
bool QgsMapToolIdentify::identifyLayer( QgsMapLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, LayerType layerType )
{
if ( layer->type() == QgsMapLayer::RasterLayer && ( layerType == AllLayers || layerType == RasterLayer ) )
{
return identifyRasterLayer( qobject_cast<QgsRasterLayer *>( layer ), x, y );
return identifyRasterLayer( qobject_cast<QgsRasterLayer *>( layer ), point, viewExtent, mapUnitsPerPixel, mResultData.mRasterResults );
}
else if ( layer->type() == QgsMapLayer::VectorLayer && ( layerType == AllLayers || layerType == VectorLayer ) )
{
return identifyVectorLayer( qobject_cast<QgsVectorLayer *>( layer ), x, y );
return identifyVectorLayer( qobject_cast<QgsVectorLayer *>( layer ), point );
}
else
{
@ -180,9 +188,9 @@ bool QgsMapToolIdentify::identifyLayer( QgsMapLayer *layer, int x, int y, LayerT
}
}
bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int y )
bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, QgsPoint point )
{
QgsDebugMsg( "point = " + point.toString() );
if ( !layer )
return false;
@ -196,15 +204,12 @@ bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int
QMap< QString, QString > derivedAttributes;
QgsPoint point = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
// load identify radius from settings
QSettings settings;
double identifyValue = settings.value( "/Map/identifyRadius", QGis::DEFAULT_IDENTIFY_RADIUS ).toDouble();
QString ellipsoid = QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE );
if ( identifyValue <= 0.0 )
identifyValue = QGis::DEFAULT_IDENTIFY_RADIUS;
@ -241,15 +246,6 @@ bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int
QgsDebugMsg( QString( "Caught CRS exception %1" ).arg( cse.what() ) );
}
// init distance/area calculator
QgsDistanceArea calc;
if ( !featureList.count() == 0 )
{
calc.setEllipsoidalMode( mCanvas->hasCrsTransformEnabled() );
calc.setEllipsoid( ellipsoid );
calc.setSourceCrs( layer->crs().srsid() );
}
QgsFeatureList::iterator f_it = featureList.begin();
bool filter = false;
@ -271,54 +267,7 @@ bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int
featureCount++;
// Calculate derived attributes and insert:
// measure distance or area depending on geometry type
if ( layer->geometryType() == QGis::Line )
{
double dist = calc.measure( f_it->geometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, dist, myDisplayUnits, false );
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
derivedAttributes.insert( tr( "Length" ), str );
if ( f_it->geometry()->wkbType() == QGis::WKBLineString ||
f_it->geometry()->wkbType() == QGis::WKBLineString25D )
{
// Add the start and end points in as derived attributes
QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, f_it->geometry()->asPolyline().first() );
str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( tr( "firstY" ), str );
pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, f_it->geometry()->asPolyline().last() );
str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( tr( "lastY" ), str );
}
}
else if ( layer->geometryType() == QGis::Polygon )
{
double area = calc.measure( f_it->geometry() );
double perimeter = calc.measurePerimeter( f_it->geometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
derivedAttributes.insert( tr( "Area" ), str );
convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
derivedAttributes.insert( tr( "Perimeter" ), str );
}
else if ( layer->geometryType() == QGis::Point &&
( f_it->geometry()->wkbType() == QGis::WKBPoint ||
f_it->geometry()->wkbType() == QGis::WKBPoint25D ) )
{
// Include the x and y coordinates of the point as a derived attribute
QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, f_it->geometry()->asPoint() );
QString str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( "X", str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( "Y", str );
}
derivedAttributes.unite( featureDerivedAttributes( &( *f_it ), layer ) );
derivedAttributes.insert( tr( "feature id" ), fid < 0 ? tr( "new feature" ) : FID_TO_STRING( fid ) );
@ -335,22 +284,85 @@ bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int
return featureCount > 0;
}
bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, int x, int y )
QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer )
{
bool res = true;
// Calculate derived attributes and insert:
// measure distance or area depending on geometry type
QMap< QString, QString > derivedAttributes;
// init distance/area calculator
QString ellipsoid = QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE );
QgsDistanceArea calc;
calc.setEllipsoidalMode( mCanvas->hasCrsTransformEnabled() );
calc.setEllipsoid( ellipsoid );
calc.setSourceCrs( layer->crs().srsid() );
QGis::GeometryType geometryType = feature->geometry()->type();
if ( geometryType == QGis::Line )
{
double dist = calc.measure( feature->geometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, dist, myDisplayUnits, false );
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
derivedAttributes.insert( tr( "Length" ), str );
if ( feature->geometry()->wkbType() == QGis::WKBLineString ||
feature->geometry()->wkbType() == QGis::WKBLineString25D )
{
// Add the start and end points in as derived attributes
QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPolyline().first() );
str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( tr( "firstY" ), str );
pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPolyline().last() );
str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( tr( "lastY" ), str );
}
}
else if ( geometryType == QGis::Polygon )
{
double area = calc.measure( feature->geometry() );
double perimeter = calc.measurePerimeter( feature->geometry() );
QGis::UnitType myDisplayUnits;
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
derivedAttributes.insert( tr( "Area" ), str );
convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
derivedAttributes.insert( tr( "Perimeter" ), str );
}
else if ( geometryType == QGis::Point &&
( feature->geometry()->wkbType() == QGis::WKBPoint ||
feature->geometry()->wkbType() == QGis::WKBPoint25D ) )
{
// Include the x and y coordinates of the point as a derived attribute
QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPoint() );
QString str = QLocale::system().toString( pnt.x(), 'g', 10 );
derivedAttributes.insert( "X", str );
str = QLocale::system().toString( pnt.y(), 'g', 10 );
derivedAttributes.insert( "Y", str );
}
return derivedAttributes;
}
bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, QList<RasterResult>& rasterResults )
{
QgsDebugMsg( "point = " + point.toString() );
if ( !layer ) return false;
QgsRasterDataProvider *dprovider = layer->dataProvider();
if ( !dprovider || !( dprovider->capabilities() & QgsRasterDataProvider::Identify ) )
int capabilities = dprovider->capabilities();
if ( !dprovider || !( capabilities & QgsRasterDataProvider::Identify ) )
{
return false;
}
QgsPoint idPoint = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
try
{
idPoint = toLayerCoordinates( layer, idPoint );
point = toLayerCoordinates( layer, point );
}
catch ( QgsCsException &cse )
{
@ -358,20 +370,32 @@ bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, int x, int
QgsDebugMsg( QString( "coordinate not reprojectable: %1" ).arg( cse.what() ) );
return false;
}
QgsDebugMsg( QString( "idPoint = %1 %2" ).arg( idPoint.x() ).arg( idPoint.y() ) );
QgsDebugMsg( QString( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) );
if ( !layer->extent().contains( idPoint ) ) return false;
QgsRectangle viewExtent = mCanvas->extent();
if ( !layer->extent().contains( point ) ) return false;
QMap< QString, QString > attributes, derivedAttributes;
QMap<int, QVariant> values;
QgsRasterDataProvider::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( "identify/format" ).toString() );
// check if the format is really supported otherwise use first supported format
if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) )
{
if ( capabilities & QgsRasterInterface::IdentifyFeature ) format = QgsRasterDataProvider::IdentifyFormatFeature;
else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRasterDataProvider::IdentifyFormatValue;
else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRasterDataProvider::IdentifyFormatHtml;
else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRasterDataProvider::IdentifyFormatText;
else return false;
}
// We can only use context (extent, width, heigh) if layer is not reprojected,
// otherwise we don't know source resolution (size).
if ( mCanvas->hasCrsTransformEnabled() && dprovider->crs() != mCanvas->mapRenderer()->destinationCrs() )
{
viewExtent = toLayerCoordinates( layer, viewExtent );
attributes = dprovider->identify( idPoint );
values = dprovider->identify( point, format );
}
else
{
@ -383,7 +407,6 @@ bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, int x, int
// and thus shifted point calculated back in QGIS WMS (using average resolution)
//viewExtent = dprovider->extent().intersect( &viewExtent );
double mapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
// Width and height are calculated from not projected extent and we hope that
// are similar to source width and height used to reproject layer for drawing.
// TODO: may be very dangerous, because it may result in different resolutions
@ -395,19 +418,84 @@ bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, int x, int
QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
QgsDebugMsg( QString( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) );
attributes = dprovider->identify( idPoint, viewExtent, width, height );
values = dprovider->identify( point, format, viewExtent, width, height );
}
QString type;
type = tr( "Raster" );
derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
if ( attributes.size() > 0 )
//QString type = tr( "Raster" );
QgsGeometry geometry;
if ( format == QgsRasterDataProvider::IdentifyFormatValue )
{
derivedAttributes.insert( tr( "(clicked coordinate)" ), idPoint.toString() );
mResultData.mRasterResults.append( RasterResult( layer, type, attributes, derivedAttributes ) );
foreach ( int bandNo, values.keys() )
{
double value = values.value( bandNo ).toDouble();
QString valueString;
if ( dprovider->isNoDataValue( bandNo, value ) )
{
valueString = tr( "no data" );
}
else
{
valueString = QgsRasterBlock::printValue( value );
}
attributes.insert( dprovider->generateBandName( bandNo ), valueString );
}
QString label = layer->name();
rasterResults.append( RasterResult( layer, label, attributes, derivedAttributes ) );
}
else if ( format == QgsRasterDataProvider::IdentifyFormatFeature )
{
foreach ( int i, values.keys() )
{
// list of feature stores for a single sublayer
QgsFeatureStoreList featureStoreList = values.value( i ).value<QgsFeatureStoreList>();
foreach ( QgsFeatureStore featureStore, featureStoreList )
{
foreach ( QgsFeature feature, featureStore.features() )
{
attributes.clear();
// WMS sublayer and feature type, a sublayer may contain multiple feature types.
// Sublayer name may be the same as layer name and feature type name
// may be the same as sublayer. We try to avoid duplicities in label.
QString sublayer = featureStore.params().value( "sublayer" ).toString();
QString featureType = featureStore.params().value( "featureType" ).toString();
// Strip UMN MapServer '_feature'
featureType.remove( "_feature" );
QStringList labels;
if ( sublayer.compare( layer->name(), Qt::CaseInsensitive ) != 0 )
{
labels << sublayer;
}
if ( featureType.compare( sublayer, Qt::CaseInsensitive ) != 0 || labels.isEmpty() )
{
labels << featureType;
}
QMap< QString, QString > derAttributes = derivedAttributes;
derAttributes.unite( featureDerivedAttributes( &feature, layer ) );
rasterResults.append( RasterResult( layer, labels.join( " / " ), featureStore.fields(), feature, derAttributes ) );
}
}
}
}
else // text or html
{
QgsDebugMsg( QString( "%1 html or text values" ).arg( values.size() ) );
foreach ( int bandNo, values.keys() )
{
QString value = values.value( bandNo ).toString();
attributes.clear();
attributes.insert( "", value );
QString label = layer->subLayers().value( bandNo );
rasterResults.append( RasterResult( layer, label, attributes, derivedAttributes ) );
}
}
return res;
return true;
}
void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
@ -431,3 +519,12 @@ QgsMapToolIdentify::IdentifyResults &QgsMapToolIdentify::results()
{
return mResultData;
}
void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
{
QgsDebugMsg( "Entered" );
QList<RasterResult> rasterResults;
identifyRasterLayer( layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel, rasterResults );
emit changedRasterResults( rasterResults );
}

View File

@ -20,6 +20,7 @@
#include "qgsmaptool.h"
#include "qgspoint.h"
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgsdistancearea.h"
#include "qgsmaplayer.h"
@ -74,8 +75,13 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
RasterResult() {}
RasterResult( QgsRasterLayer * layer, QString label, QMap< QString, QString > attributes, QMap< QString, QString > derivedAttributes ):
mLayer( layer ), mLabel( label ), mAttributes( attributes ), mDerivedAttributes( derivedAttributes ) {}
RasterResult( QgsRasterLayer * layer, QString label, QgsFields fields, QgsFeature feature, QMap< QString, QString > derivedAttributes ):
mLayer( layer ), mLabel( label ), mFields( fields ), mFeature( feature ), mDerivedAttributes( derivedAttributes ) {}
QgsRasterLayer* mLayer;
QString mLabel;
QgsFields mFields;
QgsFeature mFeature;
QMap< QString, QString > mAttributes;
QMap< QString, QString > mDerivedAttributes;
};
@ -130,9 +136,13 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
/** Access to results */
IdentifyResults &results();
public slots:
void formatChanged( QgsRasterLayer *layer );
signals:
void identifyProgress( int, int );
void identifyMessage( QString );
void changedRasterResults( QList<RasterResult>& );
private:
/** Performs the identification.
@ -145,9 +155,12 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
@param layerType Only performs identification in a certain type of layers (raster, vector).
@return true if identification succeeded and a feature has been found, false otherwise.*/
bool identify( int x, int y, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType = AllLayers );
bool identifyLayer( QgsMapLayer *layer, int x, int y, LayerType layerType = AllLayers );
bool identifyRasterLayer( QgsRasterLayer *layer, int x, int y );
bool identifyVectorLayer( QgsVectorLayer *layer, int x, int y );
bool identify( QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType = AllLayers );
bool identifyLayer( QgsMapLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, LayerType layerType = AllLayers );
bool identifyRasterLayer( QgsRasterLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, QList<RasterResult>& rasterResults );
bool identifyVectorLayer( QgsVectorLayer *layer, QgsPoint point );
//! Private helper
virtual void convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea );
@ -155,7 +168,16 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
/** Transforms the measurements of derived attributes in the desired units*/
virtual QGis::UnitType displayUnits();
QMap< QString, QString > featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer );
IdentifyResults mResultData;
// Last point in canvas CRS
QgsPoint mLastPoint;
double mLastMapUnitsPerPixel;
QgsRectangle mLastExtent;
};
#endif

View File

@ -23,8 +23,8 @@
#include "qgsfeature.h"
#include "qgsfield.h"
#include "qgsgeometry.h"
#include "qgsgml.h"
#include "qgscoordinatereferencesystem.h"
#include "qgswfsdata.h"
#include "qgswfsfeatureiterator.h"
#include "qgswfsprovider.h"
#include "qgsspatialindex.h"
@ -754,7 +754,11 @@ int QgsWFSProvider::getFeatureGET( const QString& uri, const QString& geometryAt
thematicAttributes.insert( mFields.at( i ).name(), qMakePair( i, mFields.at( i ) ) );
}
QgsWFSData dataReader( uri, &mExtent, mFeatures, mIdMap, geometryAttribute, thematicAttributes, &mWKBType );
QString typeName = parameterFromUrl( "typename" );
//QgsWFSData dataReader( uri, &mExtent, mFeatures, mIdMap, geometryAttribute, thematicAttributes, &mWKBType );
QgsGml dataReader( typeName, geometryAttribute, mFields );
//dataReader.setFeatureType( typeName, geometryAttribute, mFields );
QObject::connect( &dataReader, SIGNAL( dataProgressAndSteps( int , int ) ), this, SLOT( handleWFSProgressMessage( int, int ) ) );
//also connect to statusChanged signal of qgisapp (if it exists)
@ -775,11 +779,14 @@ int QgsWFSProvider::getFeatureGET( const QString& uri, const QString& geometryAt
QObject::connect( this, SIGNAL( dataReadProgressMessage( QString ) ), mainWindow, SLOT( showStatusMessage( QString ) ) );
}
if ( dataReader.getWFSData() != 0 )
//if ( dataReader.getWFSData() != 0 )
if ( dataReader.getFeatures( uri, &mWKBType, &mExtent ) != 0 )
{
QgsDebugMsg( "getWFSData returned with error" );
return 1;
}
mFeatures = dataReader.featuresMap();
mIdMap = dataReader.idsMap();
QgsDebugMsg( QString( "feature count after request is: %1" ).arg( mFeatures.size() ) );
QgsDebugMsg( QString( "mExtent after request is: %1" ).arg( mExtent.toString() ) );
@ -792,7 +799,6 @@ int QgsWFSProvider::getFeatureGET( const QString& uri, const QString& geometryAt
mSpatialIndex->insertFeature( *( it.value() ) );
}
}
mFeatureCount = mFeatures.size();
return 0;

View File

@ -29,12 +29,16 @@
#include "qgswmsconnection.h"
#include "qgscoordinatetransform.h"
#include "qgsdatasourceuri.h"
#include "qgsfeaturestore.h"
#include "qgsrasterlayer.h"
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessageoutput.h"
#include "qgsmessagelog.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsnetworkreplyparser.h"
#include "qgsgml.h"
#include "qgsgmlschema.h"
#include <QNetworkRequest>
#include <QNetworkReply>
@ -96,6 +100,8 @@ QgsWmsProvider::QgsWmsProvider( QString const &uri )
{
QgsDebugMsg( "constructing with uri '" + mHttpUri + "'." );
mSupportedGetFeatureFormats = QStringList() << "text/html" << "text/plain" << "text/xml" << "application/vnd.ogc.gml";
mValid = false;
// URL may contain username/password information for a WMS
@ -118,9 +124,6 @@ QgsWmsProvider::QgsWmsProvider( QString const &uri )
// 2) http://xxx.xxx.xx/yyy/yyy?
// 3) http://xxx.xxx.xx/yyy/yyy?zzz=www
mSupportedGetFeatureFormats = QStringList() << "text/html" << "text/plain" << "text/xml";
mValid = true;
QgsDebugMsg( "exiting constructor." );
}
@ -986,7 +989,6 @@ QImage *QgsWmsProvider::draw( QgsRectangle const &viewExtent, int pixelWidth, i
return mCachedImage;
}
//void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, QgsCoordinateReferenceSystem theSrcCRS, QgsCoordinateReferenceSystem theDestCRS, void *block )
void QgsWmsProvider::readBlock( int bandNo, QgsRectangle const & viewExtent, int pixelWidth, int pixelHeight, void *block )
{
Q_UNUSED( bandNo );
@ -1386,7 +1388,30 @@ bool QgsWmsProvider::retrieveServerCapabilities( bool forceRefresh )
return false;
}
else
{
// get identify formats
foreach ( QString f, mCapabilities.capability.request.getFeatureInfo.format )
{
// Don't use mSupportedGetFeatureFormats, there are too many possibilities
//if ( mSupportedGetFeatureFormats.contains( f ) )
//{
QgsDebugMsg( "supported format = " + f );
// 1.0: MIME - server shall choose format, we presume it to be plain text
// GML.1, GML.2, or GML.3
// 1.1.0, 1.3.0 - mime types, GML should use application/vnd.ogc.gml
// but in UMN Mapserver it may be also OUTPUTFORMAT, e.g. OGRGML
IdentifyFormat format;
if ( f == "MIME" ) format = IdentifyFormatText; // 1.0
else if ( f == "text/plain" ) format = IdentifyFormatText;
else if ( f == "text/html" ) format = IdentifyFormatHtml;
else if ( f.startsWith( "GML." ) ) format = IdentifyFormatFeature; // 1.0
else if ( f == "application/vnd.ogc.gml" ) format = IdentifyFormatFeature;
else if ( f.contains( "gml", Qt::CaseInsensitive ) ) format = IdentifyFormatFeature;
mIdentifyFormats.insert( format, f );
//}
}
}
}
QgsDebugMsg( "exiting." );
@ -3236,20 +3261,25 @@ int QgsWmsProvider::capabilities() const
if ( canIdentify )
{
foreach ( QString f, mCapabilities.capability.request.getFeatureInfo.format )
if ( identifyCapabilities() )
{
if ( mSupportedGetFeatureFormats.contains( f ) )
{
// Collect all the test results into one bitmask
capability |= QgsRasterDataProvider::Identify;
if ( f == "text/html" ) capability |= QgsRasterDataProvider::IdentifyHtml;
else if ( f == "text/plain" ) capability |= QgsRasterDataProvider::IdentifyText;
}
capability |= identifyCapabilities() | Identify;
}
}
QgsDebugMsg( QString( "capability = %1" ).arg( capability ) );
return capability;
}
QgsDebugMsg( "exiting with '" + QString::number( capability, 2 ) + "'." );
int QgsWmsProvider::identifyCapabilities() const
{
int capability = NoCapabilities;
foreach ( IdentifyFormat f, mIdentifyFormats.keys() )
{
capability |= identifyFormatToCapability( f );
}
QgsDebugMsg( QString( "capability = %1" ).arg( capability ) );
return capability;
}
@ -3807,25 +3837,15 @@ QString QgsWmsProvider::metadata()
QMap<int, QVariant> QgsWmsProvider::identify( const QgsPoint & thePoint, IdentifyFormat theFormat, const QgsRectangle &theExtent, int theWidth, int theHeight )
{
QgsDebugMsg( "Entering." );
QgsDebugMsg( QString( "theFormat = %1" ).arg( theFormat ) );
QStringList resultStrings;
QMap<int, QVariant> results;
QString format;
if ( theFormat == IdentifyFormatHtml )
{
if ( !( capabilities() & IdentifyHtml ) ) return results;
format = "text/html";
}
else if ( theFormat == IdentifyFormatText )
{
if ( !( capabilities() & IdentifyText ) ) return results;
format = "text/plain";
}
else
{
return results;
}
format = mIdentifyFormats.value( theFormat );
if ( format.isEmpty() ) return results;
QgsDebugMsg( "format = " + format );
if ( !extent().contains( thePoint ) )
{
@ -3897,6 +3917,9 @@ QMap<int, QVariant> QgsWmsProvider::identify( const QgsPoint & thePoint, Identif
.arg( myExtent.xMaximum(), 0, 'f', 16 )
.arg( myExtent.yMaximum(), 0, 'f', 16 );
//QgsFeatureList featureList;
int count = 0;
// Test for which layers are suitable for querying with
for ( QStringList::const_iterator
layers = mActiveSubLayers.begin(),
@ -3955,27 +3978,153 @@ QMap<int, QVariant> QgsWmsProvider::identify( const QgsPoint & thePoint, Identif
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
}
resultStrings << mIdentifyResult;
if ( mIdentifyResultBodies.size() == 0 ) // no result
{
QgsDebugMsg( "mIdentifyResultBodies is empty" );
continue;
}
if ( theFormat == IdentifyFormatHtml || theFormat == IdentifyFormatText )
{
//resultStrings << mIdentifyResult;
//results.insert( count, mIdentifyResult );
results.insert( count, QString::fromUtf8( mIdentifyResultBodies.value( 0 ) ) );
}
else if ( theFormat == IdentifyFormatFeature ) // GML
{
// The response maybe
// 1) simple GML
// To get also geometry from UMN Mapserver, it must be enabled for layer, e.g.:
// LAYER
// METADATA
// "ows_geometries" "mygeom"
// "ows_mygeom_type" "polygon"
// END
// END
// 2) multipart GML + XSD
// Multipart is supplied by UMN Mapserver following format is used
// OUTPUTFORMAT
// NAME "OGRGML"
// DRIVER "OGR/GML"
// FORMATOPTION "FORM=multipart"
// END
// WEB
// METADATA
// "wms_getfeatureinfo_formatlist" "OGRGML,text/html"
// END
// END
// GetFeatureInfo multipart response does not seem to be defined in
// OGC specification.
int gmlPart = -1;
int xsdPart = -1;
if ( mIdentifyResultBodies.size() == 1 )
{
QgsDebugMsg( "Simple GML" );
gmlPart = 0;
}
else if ( mIdentifyResultBodies.size() == 2 ) // GML+XSD
{
QgsDebugMsg( "Multipart with 2 parts - expected GML + XSD" );
// How to find which part is GML and which XSD? Both have
// Content-Type: application/binary
// different are Content-Disposition but it is not reliable.
// We could analyze begining of bodies...
gmlPart = 0;
xsdPart = 1;
}
else
{
QgsDebugMsg( QString( "%1 parts in multipart response not supported" ).arg( mIdentifyResultBodies.size() ) );
continue;
}
QgsDebugMsg( "GML (first 2000 bytes):\n" + QString::fromUtf8( mIdentifyResultBodies.value( gmlPart ).left( 2000 ) ) );
QGis::WkbType wkbType;
QgsGmlSchema gmlSchema;
if ( xsdPart >= 0 ) // XSD available
{
gmlSchema.parseXSD( mIdentifyResultBodies.value( xsdPart ) );
}
else
{
// guess from GML
gmlSchema.guessSchema( mIdentifyResultBodies.value( gmlPart ) );
}
QStringList featureTypeNames = gmlSchema.typeNames();
QgsDebugMsg( QString( "%1 featureTypeNames found" ).arg( featureTypeNames.size() ) );
// Each sublayer may have more features of different types, for example
// if GROUP of multiple vector layers is used with UMN MapServer
// Note: GROUP of layers in UMN MapServer is not queryable by default
// (and I could not find a way to force it), it is possible however
// to add another RASTER layer with the same name as group which is queryable
// and has no DATA defined. Then such a layer may be add to QGIS and both
// GetMap and GetFeatureInfo will return data for the group of the same name.
// https://github.com/mapserver/mapserver/issues/318#issuecomment-4923208
QgsFeatureStoreList featureStoreList;
foreach ( QString featureTypeName, featureTypeNames )
{
QgsDebugMsg( QString( "featureTypeName = %1" ).arg( featureTypeName ) );
QString geometryAttribute = gmlSchema.geometryAttributes( featureTypeName ).value( 0 );
QList<QgsField> fieldList = gmlSchema.fields( featureTypeName );
QgsDebugMsg( QString( "%1 fields found" ).arg( fieldList.size() ) );
QgsFields fields;
for ( int i = 0; i < fieldList.size(); i++ )
{
fields.append( fieldList[i] );
}
QgsGml gml( featureTypeName, geometryAttribute, fields );
// TODO: avoid converting to string and back
int ret = gml.getFeatures( mIdentifyResultBodies.value( gmlPart ), &wkbType );
QgsDebugMsg( QString( "parsing result = %1" ).arg( ret ) );
QMap<QgsFeatureId, QgsFeature* > features = gml.featuresMap();
QgsDebugMsg( QString( "%1 features read" ).arg( features.size() ) );
QgsFeatureStore featureStore( fields, crs() );
featureStore.params().insert( "sublayer", *layers );
featureStore.params().insert( "featureType", featureTypeName );
foreach ( QgsFeatureId id, features.keys() )
{
QgsFeature * feature = features.value( id );
QgsDebugMsg( QString( "feature id = %1 : %2 attributes" ).arg( id ).arg( feature->attributes().size() ) );
featureStore.features().append( QgsFeature( *feature ) );
}
featureStoreList.append( featureStore );
}
results.insert( count, qVariantFromValue( featureStoreList ) );
}
count++;
}
QString str;
if ( theFormat == IdentifyFormatHtml )
{
str = "<table>\n<tr><td>" + resultStrings.join( "</td></tr>\n<tr><td>" ) + "</td></tr>\n</table>";
//str = "<table>\n<tr><td>" + resultStrings.join( "</td></tr>\n<tr><td>" ) + "</td></tr>\n</table>";
//results.insert( 1, str );
}
else if ( theFormat == IdentifyFormatText )
{
str = resultStrings.join( "\n-------------\n" );
//str = resultStrings.join( "\n-------------\n" );
//results.insert( 1, str );
}
results.insert( 1, str );
return results;
}
void QgsWmsProvider::identifyReplyFinished()
{
mIdentifyResultHeaders.clear();
mIdentifyResultBodies.clear();
if ( mIdentifyReply->error() == QNetworkReply::NoError )
{
QVariant redirect = mIdentifyReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
@ -4001,15 +4150,44 @@ void QgsWmsProvider::identifyReplyFinished()
mError = tr( "Map getfeatureinfo error %1: %2" ).arg( status.toInt() ).arg( phrase.toString() );
emit statusChanged( mError );
mIdentifyResult = "";
//mIdentifyResult = "";
}
mIdentifyResult = QString::fromUtf8( mIdentifyReply->readAll() );
if ( QgsNetworkReplyParser::isMultipart( mIdentifyReply ) )
{
QgsNetworkReplyParser parser( mIdentifyReply );
if ( !parser.isValid() )
{
QgsDebugMsg( "Cannot parse multipart" );
mErrorFormat = "text/plain";
mError = tr( "Cannot parse multipart getfeatureinfo: %1" ).arg( parser.error() );
emit statusChanged( mError );
//mIdentifyResult = "";
}
else
{
// TODO: check headers, xsd ...
// take first body - GML for now
QgsDebugMsg( QString( "%1 parts in multipart" ).arg( parser.parts() ) );
//mIdentifyResult = parser.body( 0 );
//mIdentifyResultXsd = parser.body( 1 );
mIdentifyResultBodies = parser.bodies();
mIdentifyResultHeaders = parser.headers();
}
}
else
{
//mIdentifyResult = QString::fromUtf8( mIdentifyReply->readAll() );
mIdentifyResultBodies << mIdentifyReply->readAll();
}
}
else
{
mIdentifyResult = tr( "ERROR: GetFeatureInfo failed" );
QgsMessageLog::logMessage( tr( "Map getfeatureinfo error: %1 [%2]" ).arg( mIdentifyReply->errorString() ).arg( mIdentifyReply->url().toString() ), tr( "WMS" ) );
//mIdentifyResult = tr( "ERROR: GetFeatureInfo failed" );
mErrorFormat = "text/plain";
mError = tr( "Map getfeatureinfo error: %1 [%2]" ).arg( mIdentifyReply->errorString() ).arg( mIdentifyReply->url().toString() );
emit statusChanged( mError );
QgsMessageLog::logMessage( mError, tr( "WMS" ) );
}
mIdentifyReply->deleteLater();

View File

@ -21,6 +21,7 @@
#define QGSWMSPROVIDER_H
#include "qgsrasterdataprovider.h"
#include "qgsnetworkreplyparser.h"
#include "qgsrectangle.h"
#include <QString>
@ -642,11 +643,13 @@ class QgsWmsProvider : public QgsRasterDataProvider
*/
int capabilities() const;
/** Server identify capabilities, used by source select. */
int identifyCapabilities() const;
QGis::DataType dataType( int bandNo ) const;
QGis::DataType srcDataType( int bandNo ) const;
int bandCount() const;
/**
* Get metadata in a format suitable for feeding directly
* into a subset of the GUI raster properties "Metadata" tab.
@ -997,7 +1000,12 @@ class QgsWmsProvider : public QgsRasterDataProvider
/**
* The result of the identify reply
*/
QString mIdentifyResult;
//QString mIdentifyResult;
QList< QgsNetworkReplyParser::RawHeaderMap > mIdentifyResultHeaders;
QList<QByteArray> mIdentifyResultBodies;
// TODO: better
QString mIdentifyResultXsd;
/**
* The previous parameters to draw().
@ -1077,7 +1085,11 @@ class QgsWmsProvider : public QgsRasterDataProvider
//! supported formats for GetFeatureInfo in order of preference
QStringList mSupportedGetFeatureFormats;
//! Formats supported by server and provider
QMap<IdentifyFormat, QString> mIdentifyFormats;
QgsCoordinateReferenceSystem mCrs;
};

View File

@ -139,10 +139,11 @@ QgsWMSSourceSelect::QgsWMSSourceSelect( QWidget * parent, Qt::WFlags fl, bool ma
tabLayers->layout()->removeWidget( gbCRS );
}
clear();
// set up the WMS connections we already know about
populateConnectionList();
QSettings settings;
QgsDebugMsg( "restoring geometry" );
restoreGeometry( settings.value( "/Windows/WMSSourceSelect/geometry" ).toByteArray() );
@ -270,19 +271,27 @@ QgsNumericSortTreeWidgetItem *QgsWMSSourceSelect::createItem(
return item;
}
bool QgsWMSSourceSelect::populateLayerList( QgsWmsProvider *wmsProvider )
void QgsWMSSourceSelect::clear()
{
mCRSs.clear();
lstLayers->clear();
lstTilesets->clearContents();
QVector<QgsWmsLayerProperty> layers;
if ( !wmsProvider->supportedLayers( layers ) )
return false;
mCRSs.clear();
foreach ( QAbstractButton *b, mImageFormatGroup->buttons() )
{
b->setHidden( true );
}
mFeatureCount->setEnabled( false );
}
bool QgsWMSSourceSelect::populateLayerList( QgsWmsProvider *wmsProvider )
{
QVector<QgsWmsLayerProperty> layers;
if ( !wmsProvider->supportedLayers( layers ) )
return false;
foreach ( QString encoding, wmsProvider->supportedImageEncodings() )
{
int id = mMimeMap.value( encoding, -1 );
@ -302,7 +311,6 @@ bool QgsWMSSourceSelect::populateLayerList( QgsWmsProvider *wmsProvider )
QMap<int, QStringList> layerParentNames;
wmsProvider->layerParents( layerParents, layerParentNames );
lstLayers->clear();
lstLayers->setSortingEnabled( true );
int layerAndStyleCount = -1;
@ -418,6 +426,8 @@ bool QgsWMSSourceSelect::populateLayerList( QgsWmsProvider *wmsProvider )
void QgsWMSSourceSelect::on_btnConnect_clicked()
{
clear();
mConnName = cmbConnections->currentText();
QgsWMSConnection connection( cmbConnections->currentText() );
@ -437,6 +447,15 @@ void QgsWMSSourceSelect::on_btnConnect_clicked()
{
showError( wmsProvider );
}
else
{
int capabilities = wmsProvider->identifyCapabilities();
QgsDebugMsg( "capabilities = " + QString::number( capabilities ) );
if ( capabilities ) // at least one identify capability
{
mFeatureCount->setEnabled( true );
}
}
delete wmsProvider;
@ -531,15 +550,13 @@ void QgsWMSSourceSelect::addClicked()
uri.setParam( "styles", styles );
uri.setParam( "format", format );
uri.setParam( "crs", crs );
QgsDebugMsg( QString( "crs=%2 " ).arg( crs ) );
if ( mFeatureCount->text().toInt() > 0 )
{
uri.setParam( "featureCount", mFeatureCount->text() );
}
QgsDebugMsg( QString( "crs=%2 " ).arg( crs ) );
QgsDebugMsg( "uri = " + uri.encodedUri() );
emit addRasterLayer( uri.encodedUri(),
leLayerName->text().isEmpty() ? layers.join( "/" ) : leLayerName->text(),
"wms" );

View File

@ -149,6 +149,10 @@ class QgsWMSSourceSelect : public QDialog, private Ui::QgsWMSSourceSelectBase
//! Map mime types to supported formats
QMap<QString, int> mMimeMap;
// Clear layers list, crs, encodings ...
void clear();
/**
* \brief Populate the layer list - private for now.
*

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>288</width>
<width>304</width>
<height>306</height>
</rect>
</property>
@ -82,6 +82,23 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mCopyToolButton">
<property name="toolTip">
<string>Copy selected feature to clipboard.</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionEditCopy.png</normaloff>:/images/themes/default/mActionEditCopy.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="mPrintToolButton">
<property name="enabled">
@ -136,7 +153,9 @@
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<resources>
<include location="../../images/images.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>

View File

@ -434,7 +434,7 @@ void TestQgsWcsPublicServers::test( )
myGdalXmlFile.close();
}
QgsRasterLayer * myLayer = new QgsRasterLayer( uri, myCoverage.identifier, provider, true );
QgsRasterLayer * myLayer = new QgsRasterLayer( uri, myCoverage.identifier, provider );
if ( myLayer->isValid() )
{
myLog << provider + "_crs:" + myLayer->dataProvider()->crs().authid();