[FEATURE] add support to SVG images from url.

Work done for Regione Toscana-SITA
This commit is contained in:
Giuseppe Sucameli 2012-11-27 00:49:38 +01:00
parent bc75a39475
commit 454c732a3d
8 changed files with 228 additions and 35 deletions

View File

@ -34,7 +34,7 @@ class QgsSvgCacheEntry
/**A cache for images / pictures derived from svg files. This class supports parameter replacement in svg files
according to the svg params specification (http://www.w3.org/TR/2009/WD-SVGParamPrimer-20090616/). Supported are
the parameters 'fill-color', 'pen-color', 'outline-width', 'stroke-width'. E.g. <circle fill="param(fill-color red)" stroke="param(pen-color black)" stroke-width="param(outline-width 1)"*/
class QgsSvgCache
class QgsSvgCache : QObject
{
%TypeHeaderCode
#include <qgssvgcache.h>
@ -56,7 +56,8 @@ class QgsSvgCache
double& defaultOutlineWidth ) const;
protected:
QgsSvgCache();
//! protected constructor
QgsSvgCache( QObject* parent = 0 );
/**Creates new cache entry and returns pointer to it*/
QgsSvgCacheEntry* insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,

View File

@ -320,6 +320,7 @@ SET(QGIS_CORE_MOC_HDRS
gps/qgsgpsdconnection.h
symbology-ng/qgscptcityarchive.h
symbology-ng/qgssvgcache.h
)
IF (WITH_INTERNAL_QEXTSERIALPORT)

View File

@ -309,13 +309,9 @@ QgsSVGFillSymbolLayer::~QgsSVGFillSymbolLayer()
void QgsSVGFillSymbolLayer::setSvgFilePath( const QString& svgPath )
{
QFile svgFile( svgPath );
if ( svgFile.open( QFile::ReadOnly ) )
{
mSvgData = svgFile.readAll();
mSvgData = QgsSvgCache::instance()->getImageData( svgPath );
storeViewBox();
storeViewBox();
}
mSvgFilePath = svgPath;
setDefaultSvgParams();
}

View File

@ -674,7 +674,7 @@ void QgsSvgMarkerSymbolLayerV2::renderPoint( const QPointF& point, QgsSymbolV2Re
}
else
{
p->setOpacity( context.alpha( ) );
p->setOpacity( context.alpha() );
const QPicture& pct = QgsSvgCache::instance()->svgAsPicture( mPath, size, mFillColor, mOutlineColor, mOutlineWidth,
context.renderContext().scaleFactor(), context.renderContext().rasterScaleFactor() );
p->drawPicture( 0, 0, pct );
@ -842,6 +842,26 @@ QString QgsSvgMarkerSymbolLayerV2::symbolNameToPath( QString name )
if ( QFile( name ).exists() )
return QFileInfo( name ).canonicalFilePath();
// or it might be an url...
QUrl url( name );
if ( url.isValid() )
{
if ( url.isLocalFile() )
{
// it's a url to a local file
name = url.toLocalFile();
if ( QFile( name ).exists() )
{
return QFileInfo( name ).canonicalFilePath();
}
}
else
{
// it's a url pointing to a online resource
return name;
}
}
// SVG symbol not found - probably a relative path was used
QStringList svgPaths = QgsApplication::svgPaths();

View File

@ -17,6 +17,11 @@
#include "qgssvgcache.h"
#include "qgslogger.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
#include <QApplication>
#include <QCoreApplication>
#include <QCursor>
#include <QDomDocument>
#include <QDomElement>
#include <QFile>
@ -24,6 +29,9 @@
#include <QPainter>
#include <QPicture>
#include <QSvgRenderer>
#include <QFileInfo>
#include <QNetworkReply>
#include <QNetworkRequest>
QgsSvgCacheEntry::QgsSvgCacheEntry(): file( QString() ), size( 0 ), outlineWidth( 0 ), widthScaleFactor( 1.0 ), rasterScaleFactor( 1.0 ), fill( Qt::black ),
outline( Qt::black ), image( 0 ), picture( 0 )
@ -81,7 +89,11 @@ QgsSvgCache* QgsSvgCache::instance()
return mInstance;
}
QgsSvgCache::QgsSvgCache(): mTotalSize( 0 ), mLeastRecentEntry( 0 ), mMostRecentEntry( 0 )
QgsSvgCache::QgsSvgCache( QObject *parent )
: QObject( parent )
, mTotalSize( 0 )
, mLeastRecentEntry( 0 )
, mMostRecentEntry( 0 )
{
}
@ -163,14 +175,8 @@ void QgsSvgCache::containsParams( const QString& path, bool& hasFillParam, QColo
defaultOutlineColor = QColor( Qt::black );
defaultOutlineWidth = 1.0;
QFile svgFile( path );
if ( !svgFile.open( QIODevice::ReadOnly ) )
{
return;
}
QDomDocument svgDoc;
if ( !svgDoc.setContent( &svgFile ) )
if ( !svgDoc.setContent( getImageData( path ) ) )
{
return;
}
@ -186,14 +192,8 @@ void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry )
return;
}
QFile svgFile( entry->file );
if ( !svgFile.open( QIODevice::ReadOnly ) )
{
return;
}
QDomDocument svgDoc;
if ( !svgDoc.setContent( &svgFile ) )
if ( !svgDoc.setContent( getImageData( entry->file ) ) )
{
return;
}
@ -206,6 +206,118 @@ void QgsSvgCache::replaceParamsAndCacheSvg( QgsSvgCacheEntry* entry )
mTotalSize += entry->svgContent.size();
}
QByteArray QgsSvgCache::getImageData( const QString &path ) const
{
// is it a path to local file?
QFile svgFile( path );
if ( svgFile.exists() )
{
if ( svgFile.open( QIODevice::ReadOnly ) )
{
return svgFile.readAll();
}
else
{
return QByteArray();
}
}
// maybe it's a url...
QUrl svgUrl( path );
if ( !svgUrl.isValid() )
{
return QByteArray();
}
// check whether it's a url pointing to a local file
if ( svgUrl.isLocalFile() )
{
svgFile.setFileName( svgUrl.toLocalFile() );
if ( svgFile.exists() )
{
if ( svgFile.open( QIODevice::ReadOnly ) )
{
return svgFile.readAll();
}
}
// not found...
return QByteArray();
}
// the url points to a remote resource, download it!
QNetworkReply *reply = 0;
// The following code blocks until the file is downloaded...
// TODO: use signals to get reply finished notification, in this moment
// it's executed while rendering.
while ( 1 )
{
QgsDebugMsg( QString( "get svg: %1" ).arg( svgUrl.toString() ) );
QNetworkRequest request( svgUrl );
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
reply = QgsNetworkAccessManager::instance()->get( request );
connect( reply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) );
//emit statusChanged( tr( "Downloading svg." ) );
// wait until the image download finished
// TODO: connect to the reply->finished() signal
QApplication::setOverrideCursor( QCursor( Qt::WaitCursor ) );
while ( !reply->isFinished() )
{
QCoreApplication::processEvents( QEventLoop::ExcludeUserInputEvents, 500 );
}
QApplication::restoreOverrideCursor();
if ( reply->error() != QNetworkReply::NoError )
{
QgsMessageLog::logMessage( tr( "SVG request failed [error: %1 - url: %2]" ).arg( reply->errorString() ).arg( reply->url().toString() ), tr( "SVG" ) );
reply->deleteLater();
return QByteArray();
}
QVariant redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute );
if ( redirect.isNull() )
{
// neither network error nor redirection
// TODO: cache the image
break;
}
// do a new request to the redirect url
svgUrl = redirect.toUrl();
reply->deleteLater();
}
QVariant status = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
if ( !status.isNull() && status.toInt() >= 400 )
{
QVariant phrase = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute );
QgsMessageLog::logMessage( tr( "SVG request error [status: %1 - reason phrase: %2]" ).arg( status.toInt() ).arg( phrase.toString() ), tr( "SVG" ) );
reply->deleteLater();
return QByteArray();
}
QString contentType = reply->header( QNetworkRequest::ContentTypeHeader ).toString();
QgsDebugMsg( "contentType: " + contentType );
if ( !contentType.startsWith( "image/svg+xml", Qt::CaseInsensitive ) )
{
reply->deleteLater();
return QByteArray();
}
// read the image data
QByteArray ba = reply->readAll();
reply->deleteLater();
return ba;
}
void QgsSvgCache::cacheImage( QgsSvgCacheEntry* entry )
{
if ( !entry )
@ -553,3 +665,9 @@ void QgsSvgCache::takeEntryFromList( QgsSvgCacheEntry* entry )
}
}
void QgsSvgCache::downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
{
QString msg = tr( "%1 of %2 bytes of svg image downloaded." ).arg( bytesReceived ).arg( bytesTotal < 0 ? QString( "unknown number of" ) : QString::number( bytesTotal ) );
QgsDebugMsg( msg );
emit statusChanged( msg );
}

View File

@ -22,6 +22,7 @@
#include <QMap>
#include <QMultiHash>
#include <QString>
#include <QUrl>
class QDomElement;
class QImage;
@ -59,8 +60,10 @@ class CORE_EXPORT QgsSvgCacheEntry
/**A cache for images / pictures derived from svg files. This class supports parameter replacement in svg files
according to the svg params specification (http://www.w3.org/TR/2009/WD-SVGParamPrimer-20090616/). Supported are
the parameters 'fill-color', 'pen-color', 'outline-width', 'stroke-width'. E.g. <circle fill="param(fill-color red)" stroke="param(pen-color black)" stroke-width="param(outline-width 1)"*/
class CORE_EXPORT QgsSvgCache
class CORE_EXPORT QgsSvgCache : public QObject
{
Q_OBJECT
public:
static QgsSvgCache* instance();
@ -76,8 +79,16 @@ class CORE_EXPORT QgsSvgCache
void containsParams( const QString& path, bool& hasFillParam, QColor& defaultFillColor, bool& hasOutlineParam, QColor& defaultOutlineColor, bool& hasOutlineWidthParam,
double& defaultOutlineWidth ) const;
/**Get image data*/
QByteArray getImageData( const QString &path ) const;
signals:
/** Emit a signal to be caught by qgisapp and display a msg on status bar */
void statusChanged( QString const & theStatusQString );
protected:
QgsSvgCache();
//! protected constructor
QgsSvgCache( QObject * parent = 0 );
/**Creates new cache entry and returns pointer to it*/
QgsSvgCacheEntry* insertSVG( const QString& file, int size, const QColor& fill, const QColor& outline, double outlineWidth,
@ -96,6 +107,9 @@ class CORE_EXPORT QgsSvgCache
//Removes entry from the ordered list (but does not delete the entry itself)
void takeEntryFromList( QgsSvgCacheEntry* entry );
private slots:
void downloadProgress( qint64, qint64 );
private:
static QgsSvgCache* mInstance;

View File

@ -683,12 +683,16 @@ void QgsSvgMarkerSymbolLayerV2Widget::setGuiForSvg( const QgsSvgMarkerSymbolLaye
mBorderWidthSpinBox->blockSignals( true );
mBorderWidthSpinBox->setValue( layer->outlineWidth() );
mBorderWidthSpinBox->blockSignals( false );
}
void QgsSvgMarkerSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
{
if ( !layer )
{
return;
}
if ( layer->layerType() != "SvgMarker" )
return;
@ -711,8 +715,6 @@ void QgsSvgMarkerSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
}
}
spinSize->setValue( mLayer->size() );
spinAngle->setValue( mLayer->angle() );
@ -725,7 +727,6 @@ void QgsSvgMarkerSymbolLayerV2Widget::setSymbolLayer( QgsSymbolLayerV2* layer )
spinOffsetY->blockSignals( false );
setGuiForSvg( mLayer );
}
QgsSymbolLayerV2* QgsSvgMarkerSymbolLayerV2Widget::symbolLayer()
@ -790,6 +791,21 @@ void QgsSvgMarkerSymbolLayerV2Widget::on_mFileLineEdit_textEdited( const QString
emit changed();
}
void QgsSvgMarkerSymbolLayerV2Widget::on_mFileLineEdit_editingFinished()
{
if ( !QFileInfo( mFileLineEdit->text() ).exists() )
{
QUrl url( mFileLineEdit->text() );
if ( !url.isValid() )
{
return;
}
}
mLayer->setPath( mFileLineEdit->text() );
setGuiForSvg( mLayer );
emit changed();
}
void QgsSvgMarkerSymbolLayerV2Widget::on_mChangeColorButton_clicked()
{
if ( !mLayer )
@ -945,7 +961,7 @@ void QgsSVGFillSymbolLayerWidget::on_mTextureWidthSpinBox_valueChanged( double d
}
}
void QgsSVGFillSymbolLayerWidget::on_mSVGLineEdit_textChanged( const QString & text )
void QgsSVGFillSymbolLayerWidget::on_mSVGLineEdit_textEdited( const QString & text )
{
if ( !mLayer )
{
@ -958,14 +974,39 @@ void QgsSVGFillSymbolLayerWidget::on_mSVGLineEdit_textChanged( const QString & t
return;
}
mLayer->setSvgFilePath( text );
emit changed();
updateParamGui();
emit changed();
}
void QgsSVGFillSymbolLayerWidget::on_mSVGLineEdit_editingFinished()
{
if ( !mLayer )
{
return;
}
QFileInfo fi( mSVGLineEdit->text() );
if ( !fi.exists() )
{
QUrl url( mSVGLineEdit->text() );
if ( !url.isValid() )
{
return;
}
}
mLayer->setSvgFilePath( mSVGLineEdit->text() );
updateParamGui();
emit changed();
}
void QgsSVGFillSymbolLayerWidget::setFile( const QModelIndex& item )
{
mSVGLineEdit->setText( item.data( Qt::UserRole ).toString() );
QString file = item.data( Qt::UserRole ).toString();
mLayer->setSvgFilePath( file );
mSVGLineEdit->setText( file );
updateParamGui();
emit changed();
}
void QgsSVGFillSymbolLayerWidget::insertIcons()
@ -1000,8 +1041,8 @@ void QgsSVGFillSymbolLayerWidget::on_mRotationSpinBox_valueChanged( double d )
if ( mLayer )
{
mLayer->setAngle( d );
emit changed();
}
emit changed();
}
void QgsSVGFillSymbolLayerWidget::updateParamGui()

View File

@ -198,6 +198,7 @@ class GUI_EXPORT QgsSvgMarkerSymbolLayerV2Widget : public QgsSymbolLayerV2Widget
void setOffset();
void on_mFileToolButton_clicked();
void on_mFileLineEdit_textEdited( const QString& text );
void on_mFileLineEdit_editingFinished();
void on_mChangeColorButton_clicked();
void on_mChangeBorderColorButton_clicked();
void on_mBorderWidthSpinBox_valueChanged( double d );
@ -266,7 +267,8 @@ class GUI_EXPORT QgsSVGFillSymbolLayerWidget : public QgsSymbolLayerV2Widget, pr
private slots:
void on_mBrowseToolButton_clicked();
void on_mTextureWidthSpinBox_valueChanged( double d );
void on_mSVGLineEdit_textChanged( const QString & text );
void on_mSVGLineEdit_textEdited( const QString & text );
void on_mSVGLineEdit_editingFinished();
void setFile( const QModelIndex& item );
void populateIcons( const QModelIndex& item );
void on_mRotationSpinBox_valueChanged( double d );