Skip to content

Commit

Permalink
[FEATURE] add support to SVG images from url.
Browse files Browse the repository at this point in the history
Work done for Regione Toscana-SITA
  • Loading branch information
brushtyler committed Nov 27, 2012
1 parent bc75a39 commit 454c732
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 34 deletions.
5 changes: 3 additions & 2 deletions python/core/symbology-ng/qgssvgcache.sip
Expand Up @@ -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>
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -320,6 +320,7 @@ SET(QGIS_CORE_MOC_HDRS
gps/qgsgpsdconnection.h

symbology-ng/qgscptcityarchive.h
symbology-ng/qgssvgcache.h
)

IF (WITH_INTERNAL_QEXTSERIALPORT)
Expand Down
8 changes: 2 additions & 6 deletions src/core/symbology-ng/qgsfillsymbollayerv2.cpp
Expand Up @@ -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();
}
Expand Down
22 changes: 21 additions & 1 deletion src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
Expand Up @@ -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 );
Expand Down Expand Up @@ -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();
Expand Down
148 changes: 133 additions & 15 deletions src/core/symbology-ng/qgssvgcache.cpp
Expand Up @@ -17,13 +17,21 @@

#include "qgssvgcache.h"
#include "qgslogger.h"
#include "qgsnetworkaccessmanager.h"
#include "qgsmessagelog.h"
#include <QApplication>
#include <QCoreApplication>
#include <QCursor>
#include <QDomDocument>
#include <QDomElement>
#include <QFile>
#include <QImage>
#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 )
Expand Down Expand Up @@ -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 )
{
}

Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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 )
Expand Down Expand Up @@ -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 );
}
18 changes: 16 additions & 2 deletions src/core/symbology-ng/qgssvgcache.h
Expand Up @@ -22,6 +22,7 @@
#include <QMap>
#include <QMultiHash>
#include <QString>
#include <QUrl>

class QDomElement;
class QImage;
Expand Down Expand Up @@ -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();
Expand All @@ -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,
Expand All @@ -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;

Expand Down

0 comments on commit 454c732

Please sign in to comment.