597 changes: 597 additions & 0 deletions src/analysis/openstreetmap/qgsosmdatabase.cpp

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions src/analysis/openstreetmap/qgsosmdatabase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#ifndef OSMDATABASE_H
#define OSMDATABASE_H

#include <QString>
#include <QStringList>

#include "qgsosmbase.h"

#include "qgsgeometry.h"

class QgsOSMNodeIterator;
class QgsOSMWayIterator;

typedef QPair<QString, int> QgsOSMTagCountPair;

/**
* Class that encapsulates access to OpenStreetMap data stored in a database
* previously imported from XML file.
*/
class ANALYSIS_EXPORT QgsOSMDatabase
{
public:
explicit QgsOSMDatabase( const QString& dbFileName = QString() );
~QgsOSMDatabase();

void setFileName( const QString& dbFileName ) { mDbFileName = dbFileName; }
QString filename() const { return mDbFileName; }
bool isOpen() const;

bool open();
bool close();

QString errorString() const { return mError; }

// data access

int countNodes() const;
int countWays() const;

QgsOSMNodeIterator listNodes() const;
QgsOSMWayIterator listWays() const;

QgsOSMNode node( QgsOSMId id ) const;
QgsOSMWay way( QgsOSMId id ) const;
//OSMRelation relation( OSMId id ) const;

QgsOSMTags tags( bool way, QgsOSMId id ) const;

QList<QgsOSMTagCountPair> usedTags( bool ways ) const;

QgsPolyline wayPoints( QgsOSMId id ) const;

// export to spatialite

enum ExportType { Point, Polyline, Polygon };
bool exportSpatiaLite( ExportType type, const QString& tableName, const QStringList& tagKeys = QStringList() );

protected:
bool prepareStatements();
int runCountStatement( const char* sql ) const;
void deleteStatement( sqlite3_stmt*& stmt );

void exportSpatiaLiteNodes( const QString& tableName, const QStringList& tagKeys );
void exportSpatiaLiteWays( bool closed, const QString& tableName, const QStringList& tagKeys );
bool createSpatialTable( const QString& tableName, const QString& geometryType, const QStringList& tagKeys );
bool createSpatialIndex( const QString& tableName );

QString quotedIdentifier( QString id );
QString quotedValue( QString value );

private:
//! database file name
QString mDbFileName;

QString mError;

//! pointer to sqlite3 database that keeps OSM data
sqlite3* mDatabase;

sqlite3_stmt* mStmtNode;
sqlite3_stmt* mStmtNodeTags;
sqlite3_stmt* mStmtWay;
sqlite3_stmt* mStmtWayNode;
sqlite3_stmt* mStmtWayNodePoints;
sqlite3_stmt* mStmtWayTags;
};


/** Encapsulate iteration over table of nodes */
class ANALYSIS_EXPORT QgsOSMNodeIterator
{
public:
~QgsOSMNodeIterator();

QgsOSMNode next();
void close();

protected:
QgsOSMNodeIterator( sqlite3* handle );

sqlite3_stmt* mStmt;

friend class QgsOSMDatabase;
};



/** Encapsulate iteration over table of ways */
class ANALYSIS_EXPORT QgsOSMWayIterator
{
public:
~QgsOSMWayIterator();

QgsOSMWay next();
void close();

protected:
QgsOSMWayIterator( sqlite3* handle );

sqlite3_stmt* mStmt;

friend class QgsOSMDatabase;
};



#endif // OSMDATABASE_H
129 changes: 129 additions & 0 deletions src/analysis/openstreetmap/qgsosmdownload.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#include "qgsosmdownload.h"

#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>

#include "qgsnetworkaccessmanager.h"
#include "qgsrectangle.h"


QString QgsOSMDownload::defaultServiceUrl()
{
return "http://overpass-api.de/api/interpreter";
}


QString QgsOSMDownload::queryFromRect( const QgsRectangle& rect )
{
return QString( "(node(%1,%2,%3,%4);<;);out;" ).arg( rect.yMinimum() ).arg( rect.xMinimum() )
.arg( rect.yMaximum() ).arg( rect.xMaximum() );
}


QgsOSMDownload::QgsOSMDownload()
: mServiceUrl( defaultServiceUrl() ), mReply( 0 )
{
}

QgsOSMDownload::~QgsOSMDownload()
{
if ( mReply )
{
mReply->abort();
mReply->deleteLater();
mReply = 0;
}
}


bool QgsOSMDownload::start()
{
mError.clear();

if ( mQuery.isEmpty() )
{
mError = tr( "No query has been specified." );
return false;
}

if ( mReply )
{
mError = tr( "There is already a pending request for data." );
return false;
}

if ( !mFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
{
mError = tr( "Cannot open output file: %1" ).arg( mFile.fileName() );
return false;
}

QgsNetworkAccessManager* nwam = QgsNetworkAccessManager::instance();

QUrl url( mServiceUrl );
url.addQueryItem( "data", mQuery );

QNetworkRequest request( url );
request.setRawHeader( "User-Agent", "QGIS" );

mReply = nwam->get( request );

connect( mReply, SIGNAL( readyRead() ), this, SLOT( onReadyRead() ) );
connect( mReply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( onError( QNetworkReply::NetworkError ) ) );
connect( mReply, SIGNAL( finished() ), this, SLOT( onFinished() ) );
connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SIGNAL( downloadProgress( qint64, qint64 ) ) );

return true;
}


bool QgsOSMDownload::abort()
{
if ( !mReply )
return false;

mReply->abort();
return true;
}


void QgsOSMDownload::onReadyRead()
{
Q_ASSERT( mReply );

QByteArray data = mReply->read( 1024 * 1024 );
mFile.write( data );
}


void QgsOSMDownload::onFinished()
{
qDebug( "finished" );
Q_ASSERT( mReply );

mReply->deleteLater();
mReply = 0;

mFile.close();

emit finished();
}


void QgsOSMDownload::onError( QNetworkReply::NetworkError err )
{
qDebug( "error: %d", err );
Q_ASSERT( mReply );

mError = mReply->errorString();
}


bool QgsOSMDownload::isFinished() const
{
if ( !mReply )
return true;

return mReply->isFinished();
}
87 changes: 87 additions & 0 deletions src/analysis/openstreetmap/qgsosmdownload.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#ifndef OSMDOWNLOAD_H
#define OSMDOWNLOAD_H


#include <QObject>
#include <QFile>
#include <QNetworkReply>

class QgsRectangle;

/**
* @brief OSMDownload is a utility class for downloading OpenStreetMap via Overpass API.
*
* To use this class, it is necessary to set query, output file name and start the request.
* The interface is asynchronous, the caller has to wait for finished() signal that is
* emitted whe the request has finished (successfully or with an error).
*
* To check whether the the request has been successful, check hasError() and use errorString()
* to retreive error message. An error may happen either directly in start() method
* or during the network communication.
*
* By default OSMDownload uses remote service at location returned by defaultServiceUrl() method.
*/
class ANALYSIS_EXPORT QgsOSMDownload : public QObject
{
Q_OBJECT
public:

//! Return URL of the service that is used by default
static QString defaultServiceUrl();

//! Create query (in Overpass Query Language) that fetches everything in given rectangle
static QString queryFromRect( const QgsRectangle& rect );

QgsOSMDownload();
~QgsOSMDownload();

void setServiceUrl( const QString& serviceUrl ) { mServiceUrl = serviceUrl; }
QString serviceUrl() const { return mServiceUrl; }

void setQuery( const QString& query ) { mQuery = query; }
QString query() const { return mQuery; }

void setOutputFileName( const QString& outputFileName ) { mFile.setFileName( outputFileName ); }
QString outputFileName() const { return mFile.fileName(); }

bool hasError() const { return !mError.isNull(); }
QString errorString() const { return mError; }

/**
* @brief Starts network request for data. The prerequisite is that the query string and output
* file name have been set.
*
* Only one request may be pending at one point - if you need more requests at once, use several instances.
*
* @return true if the network request has been issued, false otherwise (and sets error string)
*/
bool start();

/**
* @brief Aborts current pending request
* @return true if there is a pending request and has been aborted, false otherwise
*/
bool abort();

//! Returns true if the request has already finished
bool isFinished() const;

signals:
void finished(); //!< emitted when the network reply has finished (with success or with an error)
void downloadProgress( qint64, qint64 ); //!< normally the total length is not known (until we reach end)

private slots:
void onReadyRead();
void onFinished();
void onError( QNetworkReply::NetworkError err );

private:
QString mServiceUrl;
QString mQuery;
QString mError;

QNetworkReply* mReply;
QFile mFile;
};

#endif // OSMDOWNLOAD_H
371 changes: 371 additions & 0 deletions src/analysis/openstreetmap/qgsosmimport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,371 @@
#include "qgsosmimport.h"

#include <spatialite.h>

#include <QXmlStreamReader>


QgsOSMXmlImport::QgsOSMXmlImport( const QString& xmlFilename, const QString& dbFilename )
: mXmlFileName( xmlFilename )
, mDbFileName( dbFilename )
, mDatabase( 0 )
, mStmtInsertNode( 0 )
, mStmtInsertNodeTag( 0 )
, mStmtInsertWay( 0 )
, mStmtInsertWayNode( 0 )
, mStmtInsertWayTag( 0 )
{

}

bool QgsOSMXmlImport::import()
{
mError.clear();

// open input
mInputFile.setFileName( mXmlFileName );
if ( !mInputFile.open( QIODevice::ReadOnly ) )
{
mError = QString( "Cannot open input file: %1" ).arg( mXmlFileName );
return false;
}

// open output

if ( QFile::exists( mDbFileName ) )
{
if ( !QFile( mDbFileName ).remove() )
{
mError = QString( "Database file cannot be overwritten: %1" ).arg( mDbFileName );
return false;
}
}

// load spatialite extension
spatialite_init( 0 );

if ( !createDatabase() )
{
// mError is set in createDatabase()
return false;
}

qDebug( "starting import" );

int retX = sqlite3_exec( mDatabase, "BEGIN", NULL, NULL, 0 );
Q_ASSERT( retX == SQLITE_OK );

// start parsing

QXmlStreamReader xml( &mInputFile );

while ( !xml.atEnd() )
{
xml.readNext();

if ( xml.isEndDocument() )
break;

if ( xml.isStartElement() )
{
if ( xml.name() == "osm" )
readRoot( xml );
else
xml.raiseError( "Invalid root tag" );
}
}

int retY = sqlite3_exec( mDatabase, "COMMIT", NULL, NULL, 0 );
Q_ASSERT( retY == SQLITE_OK );

createIndexes();

if ( xml.hasError() )
{
mError = QString( "XML error: %1" ).arg( xml.errorString() );
return false;
}

closeDatabase();

return true;
}

bool QgsOSMXmlImport::createIndexes()
{
// index on tags for faster access
const char* sqlIndexes[] =
{
"CREATE INDEX nodes_tags_idx ON nodes_tags(id)",
"CREATE INDEX ways_tags_idx ON ways_tags(id)",
"CREATE INDEX ways_nodes_way ON ways_nodes(way_id)"
};
int count = sizeof( sqlIndexes ) / sizeof( const char* );
for ( int i = 0; i < count; ++i )
{
int ret = sqlite3_exec( mDatabase, sqlIndexes[i], 0, 0, 0 );
if ( ret != SQLITE_OK )
{
mError = "Error creating indexes!";
return false;
}
}

return true;
}


bool QgsOSMXmlImport::createDatabase()
{
if ( sqlite3_open_v2( mDbFileName.toUtf8().data(), &mDatabase, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0 ) != SQLITE_OK )
return false;

const char* sqlInitStatements[] =
{
"PRAGMA cache_size = 100000", // TODO!!!
"PRAGMA synchronous = OFF", // TODO!!!
"SELECT InitSpatialMetadata('WGS84')",
"CREATE TABLE nodes ( id INTEGER PRIMARY KEY, lat REAL, lon REAL )",
"CREATE TABLE nodes_tags ( id INTEGER, k TEXT, v TEXT )",
"CREATE TABLE ways ( id INTEGER PRIMARY KEY )",
"CREATE TABLE ways_nodes ( way_id INTEGER, node_id INTEGER, way_pos INTEGER )",
"CREATE TABLE ways_tags ( id INTEGER, k TEXT, v TEXT )",
};

int initCount = sizeof( sqlInitStatements ) / sizeof( const char* );
for ( int i = 0; i < initCount; ++i )
{
char* errMsg;
if ( sqlite3_exec( mDatabase, sqlInitStatements[i], 0, 0, &errMsg ) != SQLITE_OK )
{
mError = QString( "Error executing SQL command:\n%1\nSQL:\n%2" )
.arg( QString::fromUtf8( errMsg ) ).arg( QString::fromUtf8( sqlInitStatements[i] ) );
sqlite3_free( errMsg );
closeDatabase();
return false;
}
}

const char* sqlInsertStatements[] =
{
"INSERT INTO nodes ( id, lat, lon ) VALUES (?,?,?)",
"INSERT INTO nodes_tags ( id, k, v ) VALUES (?,?,?)",
"INSERT INTO ways ( id ) VALUES (?)",
"INSERT INTO ways_nodes ( way_id, node_id, way_pos ) VALUES (?,?,?)",
"INSERT INTO ways_tags ( id, k, v ) VALUES (?,?,?)"
};
sqlite3_stmt** sqliteInsertStatements[] =
{
&mStmtInsertNode,
&mStmtInsertNodeTag,
&mStmtInsertWay,
&mStmtInsertWayNode,
&mStmtInsertWayTag
};
Q_ASSERT( sizeof( sqlInsertStatements ) / sizeof( const char* ) == sizeof( sqliteInsertStatements ) / sizeof( sqlite3_stmt** ) );
int insertCount = sizeof( sqlInsertStatements ) / sizeof( const char* );

for ( int i = 0; i < insertCount; ++i )
{
if ( sqlite3_prepare_v2( mDatabase, sqlInsertStatements[i], -1, sqliteInsertStatements[i], 0 ) != SQLITE_OK )
{
const char* errMsg = sqlite3_errmsg( mDatabase ); // does not require free
mError = QString( "Error preparing SQL command:\n%1\nSQL:\n%2" )
.arg( QString::fromUtf8( errMsg ) ).arg( QString::fromUtf8( sqlInsertStatements[i] ) );
closeDatabase();
return false;
}
}

return true;
}


void QgsOSMXmlImport::deleteStatement( sqlite3_stmt*& stmt )
{
if ( stmt )
{
sqlite3_finalize( stmt );
stmt = 0;
}
}


bool QgsOSMXmlImport::closeDatabase()
{
if ( !mDatabase )
return false;

deleteStatement( mStmtInsertNode );
deleteStatement( mStmtInsertNodeTag );
deleteStatement( mStmtInsertWay );
deleteStatement( mStmtInsertWayNode );
deleteStatement( mStmtInsertWayTag );

Q_ASSERT( mStmtInsertNode == 0 );

sqlite3_close( mDatabase );
mDatabase = 0;
return true;
}


void QgsOSMXmlImport::readRoot( QXmlStreamReader& xml )
{
int i = 0;
int percent = -1;

while ( !xml.atEnd() )
{
xml.readNext();

if ( xml.isEndElement() ) // </osm>
break;

if ( xml.isStartElement() )
{
if ( ++i == 500 )
{
int new_percent = 100 * mInputFile.pos() / mInputFile.size();
if ( new_percent > percent )
{
emit progress( new_percent );
percent = new_percent;
}
i = 0;
}

if ( xml.name() == "node" )
readNode( xml );
else if ( xml.name() == "way" )
readWay( xml );
else
xml.skipCurrentElement();
}
}
}


void QgsOSMXmlImport::readNode( QXmlStreamReader& xml )
{
// <node id="2197214" lat="50.0682113" lon="14.4348483" user="viduka" uid="595326" visible="true" version="10" changeset="10714591" timestamp="2012-02-17T19:58:49Z">
QXmlStreamAttributes attrs = xml.attributes();
QgsOSMId id = attrs.value( "id" ).toString().toLongLong();
double lat = attrs.value( "lat" ).toString().toDouble();
double lon = attrs.value( "lon" ).toString().toDouble();

// insert to DB
sqlite3_bind_int64( mStmtInsertNode, 1, id );
sqlite3_bind_double( mStmtInsertNode, 2, lat );
sqlite3_bind_double( mStmtInsertNode, 3, lon );

if ( sqlite3_step( mStmtInsertNode ) != SQLITE_DONE )
{
xml.raiseError( QString( "Storing node %1 failed." ).arg( id ) );
}

sqlite3_reset( mStmtInsertNode );

while ( !xml.atEnd() )
{
xml.readNext();

if ( xml.isEndElement() ) // </node>
break;

if ( xml.isStartElement() )
{
if ( xml.name() == "tag" )
readTag( false, id, xml );
else
xml.raiseError( "Invalid tag in <node>" );
}
}
}

void QgsOSMXmlImport::readTag( bool way, QgsOSMId id, QXmlStreamReader& xml )
{
QXmlStreamAttributes attrs = xml.attributes();
QByteArray k = attrs.value( "k" ).toUtf8();
QByteArray v = attrs.value( "v" ).toUtf8();
xml.skipCurrentElement();

sqlite3_stmt* stmtInsertTag = way ? mStmtInsertWayTag : mStmtInsertNodeTag;

sqlite3_bind_int64( stmtInsertTag, 1, id );
sqlite3_bind_text( stmtInsertTag, 2, k.constData(), -1, SQLITE_STATIC );
sqlite3_bind_text( stmtInsertTag, 3, v.constData(), -1, SQLITE_STATIC );

int res = sqlite3_step( stmtInsertTag );
if ( res != SQLITE_DONE )
{
xml.raiseError( QString( "Storing tag failed [%1]" ).arg( res ) );
}

sqlite3_reset( stmtInsertTag );
}

void QgsOSMXmlImport::readWay( QXmlStreamReader& xml )
{
/*
<way id="141756602" user="Vratislav Filler" uid="527259" visible="true" version="1" changeset="10145142" timestamp="2011-12-18T10:43:14Z">
<nd ref="318529958"/>
<nd ref="1551725779"/>
<nd ref="1551725792"/>
<nd ref="809695938"/>
<nd ref="1551725689"/>
<nd ref="809695935"/>
<tag k="highway" v="service"/>
<tag k="oneway" v="yes"/>
</way>
*/
QXmlStreamAttributes attrs = xml.attributes();
QgsOSMId id = attrs.value( "id" ).toString().toLongLong();

// insert to DB
sqlite3_bind_int64( mStmtInsertWay, 1, id );

if ( sqlite3_step( mStmtInsertWay ) != SQLITE_DONE )
{
xml.raiseError( QString( "Storing way %1 failed." ).arg( id ) );
}

sqlite3_reset( mStmtInsertWay );

int way_pos = 0;

while ( !xml.atEnd() )
{
xml.readNext();

if ( xml.isEndElement() ) // </way>
break;

if ( xml.isStartElement() )
{
if ( xml.name() == "nd" )
{
QgsOSMId node_id = xml.attributes().value( "ref" ).toString().toLongLong();

sqlite3_bind_int64( mStmtInsertWayNode, 1, id );
sqlite3_bind_int64( mStmtInsertWayNode, 2, node_id );
sqlite3_bind_int( mStmtInsertWayNode, 3, way_pos );

if ( sqlite3_step( mStmtInsertWayNode ) != SQLITE_DONE )
{
xml.raiseError( QString( "Storing ways_nodes %1 - %2 failed." ).arg( id ).arg( node_id ) );
}

sqlite3_reset( mStmtInsertWayNode );

way_pos++;

xml.skipCurrentElement();
}
else if ( xml.name() == "tag" )
readTag( true, id, xml );
else
xml.skipCurrentElement();
}
}
}
63 changes: 63 additions & 0 deletions src/analysis/openstreetmap/qgsosmimport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#ifndef OSMIMPORT_H
#define OSMIMPORT_H

#include <QFile>
#include <QObject>

#include "qgsosmbase.h"

class QXmlStreamReader;


class ANALYSIS_EXPORT QgsOSMXmlImport : public QObject
{
Q_OBJECT
public:
explicit QgsOSMXmlImport( const QString& xmlFileName = QString(), const QString& dbFileName = QString() );

void setInputXmlFileName( const QString& xmlFileName ) { mXmlFileName = xmlFileName; }
QString inputXmlFileName() const { return mXmlFileName; }

void setOutputDbFileName( const QString& dbFileName ) { mDbFileName = dbFileName; }
QString outputDbFileName() const { return mDbFileName; }

bool import();

bool hasError() const { return !mError.isEmpty(); }
QString errorString() const { return mError; }

signals:
void progress( int percent );

protected:

bool createDatabase();
bool closeDatabase();
void deleteStatement( sqlite3_stmt*& stmt );

bool createIndexes();

void readRoot( QXmlStreamReader& xml );
void readNode( QXmlStreamReader& xml );
void readWay( QXmlStreamReader& xml );
void readTag( bool way, QgsOSMId id, QXmlStreamReader& xml );

private:
QString mXmlFileName;
QString mDbFileName;

QString mError;

QFile mInputFile;

sqlite3* mDatabase;
sqlite3_stmt* mStmtInsertNode;
sqlite3_stmt* mStmtInsertNodeTag;
sqlite3_stmt* mStmtInsertWay;
sqlite3_stmt* mStmtInsertWayNode;
sqlite3_stmt* mStmtInsertWayTag;
};



#endif // OSMIMPORT_H
11 changes: 10 additions & 1 deletion src/app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ SET(QGIS_APP_SRCS

gps/qgsgpsinformationwidget.cpp
gps/qgsgpsmarker.cpp

openstreetmap/qgsosmdownloaddialog.cpp
openstreetmap/qgsosmimportdialog.cpp
openstreetmap/qgsosmexportdialog.cpp
)

IF (ANDROID)
Expand Down Expand Up @@ -292,6 +296,10 @@ SET (QGIS_APP_MOC_HDRS
ogr/qgsvectorlayersaveasdialog.h

gps/qgsgpsinformationwidget.h

openstreetmap/qgsosmdownloaddialog.h
openstreetmap/qgsosmimportdialog.h
openstreetmap/qgsosmexportdialog.h
)

IF(WITH_INTERNAL_QWTPOLAR)
Expand Down Expand Up @@ -419,14 +427,15 @@ INCLUDE_DIRECTORIES(
${QWT_INCLUDE_DIR}
${QT_QTUITOOLS_INCLUDE_DIR}
${QEXTSERIALPORT_INCLUDE_DIR}
../analysis/raster
../analysis/raster ../analysis/openstreetmap
../core
../core/gps
../core/composer ../core/raster ../core/renderer ../core/symbology ../core/symbology-ng
../gui ../gui/symbology-ng ../gui/attributetable ../gui/raster
../plugins
../python
gps
openstreetmap
)

IF (ANDROID)
Expand Down
175 changes: 175 additions & 0 deletions src/app/openstreetmap/qgsosmdownloaddialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#include "qgsosmdownloaddialog.h"

#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>

#include "qgisapp.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsrectangle.h"

#include "qgsosmdownload.h"

QgsOSMDownloadDialog::QgsOSMDownloadDialog( QWidget* parent )
: QDialog( parent ), mDownload( new QgsOSMDownload )
{
setupUi( this );

editXMin->setValidator( new QDoubleValidator( -180, 180, 6 ) );
editXMax->setValidator( new QDoubleValidator( -180, 180, 6 ) );
editYMin->setValidator( new QDoubleValidator( -90, 90, 6 ) );
editYMax->setValidator( new QDoubleValidator( -90, 90, 6 ) );

populateLayers();
onExtentCanvas();

connect( radExtentCanvas, SIGNAL( clicked() ), this, SLOT( onExtentCanvas() ) );
connect( radExtentLayer, SIGNAL( clicked() ), this, SLOT( onExtentLayer() ) );
connect( radExtentManual, SIGNAL( clicked() ), this, SLOT( onExtentManual() ) );
connect( cboLayers, SIGNAL( currentIndexChanged( int ) ), this, SLOT( onCurrentLayerChanged( int ) ) );
connect( btnBrowse, SIGNAL( clicked() ), this, SLOT( onBrowseClicked() ) );
connect( buttonBox, SIGNAL( accepted() ), this, SLOT( onOK() ) );
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( onClose() ) );

connect( mDownload, SIGNAL( finished() ), this, SLOT( onFinished() ) );
connect( mDownload, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( onDownloadProgress( qint64, qint64 ) ) );
}

QgsOSMDownloadDialog::~QgsOSMDownloadDialog()
{
delete mDownload;
}


void QgsOSMDownloadDialog::populateLayers()
{
QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
QMap<QString, QgsMapLayer*>::iterator it;
for ( it = layers.begin(); it != layers.end(); ++it )
{
cboLayers->addItem( it.value()->name(), it.key() );
}
cboLayers->setCurrentIndex( 0 );
}

void QgsOSMDownloadDialog::setRect( const QgsRectangle& rect )
{
// these coords should be already lat/lon
editXMin->setText( QString::number( rect.xMinimum() ) );
editXMax->setText( QString::number( rect.xMaximum() ) );
editYMin->setText( QString::number( rect.yMinimum() ) );
editYMax->setText( QString::number( rect.yMaximum() ) );
}

QgsRectangle QgsOSMDownloadDialog::rect() const
{
return QgsRectangle( editXMin->text().toDouble(), editYMin->text().toDouble(),
editXMax->text().toDouble(), editYMax->text().toDouble() );
}


void QgsOSMDownloadDialog::setRectReadOnly( bool readonly )
{
editXMin->setReadOnly( readonly );
editXMax->setReadOnly( readonly );
editYMin->setReadOnly( readonly );
editYMax->setReadOnly( readonly );
}


void QgsOSMDownloadDialog::onExtentCanvas()
{
setRect( QgisApp::instance()->mapCanvas()->extent() ); // TODO: transform to WGS84
setRectReadOnly( true );
cboLayers->setEnabled( false );
}

void QgsOSMDownloadDialog::onExtentLayer()
{
onCurrentLayerChanged( cboLayers->currentIndex() );
setRectReadOnly( true );
cboLayers->setEnabled( true );
}

void QgsOSMDownloadDialog::onExtentManual()
{
setRectReadOnly( false );
cboLayers->setEnabled( false );
}

void QgsOSMDownloadDialog::onCurrentLayerChanged( int index )
{
if ( index < 0 )
return;

QString layerId = cboLayers->itemData( index ).toString();
QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerId );
if ( !layer )
return;

setRect( layer->extent() ); // TODO: transform to WGS84
}

void QgsOSMDownloadDialog::onBrowseClicked()
{
QSettings settings;
QString lastDir = settings.value( "/osm/lastDir" ).toString();

QString fileName = QFileDialog::getSaveFileName( this, QString(), lastDir, tr( "OpenStreetMap files (*.osm)" ) );
if ( fileName.isNull() )
return;

settings.setValue( "/osm/lastDir", QFileInfo( fileName ).absolutePath() );
editFileName->setText( fileName );
}

void QgsOSMDownloadDialog::onOK()
{
mDownload->setQuery( QgsOSMDownload::queryFromRect( rect() ) );
mDownload->setOutputFileName( editFileName->text() );
if ( !mDownload->start() )
{
QMessageBox::critical( this, tr( "Download error" ), mDownload->errorString() );
return;
}

buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
progress->setRange( 0, 0 ); // this will start animating progress bar
}

void QgsOSMDownloadDialog::onClose()
{
if ( !mDownload->isFinished() )
{
int res = QMessageBox::question( this, tr( "OpenStreetMap download" ),
tr( "Would you like to abort download?" ), QMessageBox::Yes | QMessageBox::No );
if ( res != QMessageBox::Yes )
return;
}

reject();
}

void QgsOSMDownloadDialog::onFinished()
{
buttonBox->button( QDialogButtonBox::Ok )->setEnabled( true );
progress->setRange( 0, 1 );

if ( mDownload->hasError() )
{
QMessageBox::critical( this, tr( "OpenStreetMap download" ), tr( "Download failed.\n%1" ).arg( mDownload->errorString() ) );
}
else
{
QMessageBox::information( this, tr( "OpenStreetMap download" ), tr( "Download has been successful." ) );
}
}

void QgsOSMDownloadDialog::onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
{
Q_UNUSED( bytesTotal ); // it's -1 anyway (= unknown)
double mbytesReceived = ( double )bytesReceived / ( 1024 * 1024 );
editSize->setText( QString( "%1 MB" ).arg( QString::number( mbytesReceived, 'f', 1 ) ) );
}
41 changes: 41 additions & 0 deletions src/app/openstreetmap/qgsosmdownloaddialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef QGSOSMDOWNLOADDIALOG_H
#define QGSOSMDOWNLOADDIALOG_H

#include <QDialog>

#include "ui_qgsosmdownloaddialog.h"

class QgsRectangle;

class QgsOSMDownload;

class QgsOSMDownloadDialog : public QDialog, private Ui::QgsOSMDownloadDialog
{
Q_OBJECT
public:
explicit QgsOSMDownloadDialog( QWidget* parent = 0 );
~QgsOSMDownloadDialog();

void setRect( const QgsRectangle& rect );
void setRectReadOnly( bool readonly );
QgsRectangle rect() const;

private:
void populateLayers();

private slots:
void onExtentCanvas();
void onExtentLayer();
void onExtentManual();
void onCurrentLayerChanged( int index );
void onBrowseClicked();
void onOK();
void onClose();
void onFinished();
void onDownloadProgress( qint64, qint64 );

private:
QgsOSMDownload* mDownload;
};

#endif // QGSOSMDOWNLOADDIALOG_H
154 changes: 154 additions & 0 deletions src/app/openstreetmap/qgsosmexportdialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include "qgsosmexportdialog.h"

#include "qgsosmdatabase.h"

#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
#include <QStandardItemModel>

QgsOSMExportDialog::QgsOSMExportDialog( QWidget *parent ) :
QDialog( parent ), mDatabase( new QgsOSMDatabase )
{
setupUi( this );

connect( btnBrowseDb, SIGNAL( clicked() ), this, SLOT( onBrowse() ) );
connect( buttonBox, SIGNAL( accepted() ), this, SLOT( onOK() ) );
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( onClose() ) );
connect( editDbFileName, SIGNAL( textChanged( QString ) ), this, SLOT( updateLayerName() ) );
connect( radPoints, SIGNAL( clicked() ), this, SLOT( updateLayerName() ) );
connect( radPolylines, SIGNAL( clicked() ), this, SLOT( updateLayerName() ) );
connect( radPolygons, SIGNAL( clicked() ), this, SLOT( updateLayerName() ) );
connect( btnLoadTags, SIGNAL( clicked() ), this, SLOT( onLoadTags() ) );

mTagsModel = new QStandardItemModel( this );
mTagsModel->setHorizontalHeaderLabels( QStringList() << tr( "Tag" ) << tr( "Count" ) );
viewTags->setModel( mTagsModel );
}

QgsOSMExportDialog::~QgsOSMExportDialog()
{
delete mDatabase;
}


void QgsOSMExportDialog::onBrowse()
{
QSettings settings;
QString lastDir = settings.value( "/osm/lastDir" ).toString();

QString fileName = QFileDialog::getOpenFileName( this, QString(), lastDir, tr( "SQLite databases (*.db)" ) );
if ( fileName.isNull() )
return;

settings.setValue( "/osm/lastDir", QFileInfo( fileName ).absolutePath() );
editDbFileName->setText( fileName );
}

void QgsOSMExportDialog::updateLayerName()
{
QString baseName = QFileInfo( editDbFileName->text() ).baseName();

QString layerType;
if ( radPoints->isChecked() )
layerType = "points";
else if ( radPolylines->isChecked() )
layerType = "polylines";
else
layerType = "polygons";
editLayerName->setText( QString( "%1_%2" ).arg( baseName ).arg( layerType ) );
}


bool QgsOSMExportDialog::openDatabase()
{
mDatabase->setFileName( editDbFileName->text() );

if ( !mDatabase->open() )
{
QMessageBox::critical( this, QString(), tr( "Unable to open database:\n%1" ).arg( mDatabase->errorString() ) );
return false;
}

return true;
}


void QgsOSMExportDialog::onLoadTags()
{
if ( !openDatabase() )
return;

QApplication::setOverrideCursor( Qt::WaitCursor );

QList<QgsOSMTagCountPair> pairs = mDatabase->usedTags( !radPoints->isChecked() );
mDatabase->close();

mTagsModel->setColumnCount( 2 );
mTagsModel->setRowCount( pairs.count() );

for ( int i = 0; i < pairs.count(); ++i )
{
const QgsOSMTagCountPair& p = pairs[i];
QStandardItem* item = new QStandardItem( p.first );
item->setCheckable( true );
mTagsModel->setItem( i, 0, item );
QStandardItem* item2 = new QStandardItem();
item2->setData( p.second, Qt::DisplayRole );
mTagsModel->setItem( i, 1, item2 );
}

viewTags->resizeColumnToContents( 0 );
viewTags->sortByColumn( 1, Qt::DescendingOrder );

QApplication::restoreOverrideCursor();
}


void QgsOSMExportDialog::onOK()
{
if ( !openDatabase() )
return;

QgsOSMDatabase::ExportType type;
if ( radPoints->isChecked() )
type = QgsOSMDatabase::Point;
else if ( radPolylines->isChecked() )
type = QgsOSMDatabase::Polyline;
else
type = QgsOSMDatabase::Polygon;

buttonBox->setEnabled( false );
QApplication::setOverrideCursor( Qt::WaitCursor );

QStringList tagKeys;

for ( int i = 0; i < mTagsModel->rowCount(); ++i )
{
QStandardItem* item = mTagsModel->item( i, 0 );
if ( item->checkState() == Qt::Checked )
tagKeys << item->text();
}

bool res = mDatabase->exportSpatiaLite( type, editLayerName->text(), tagKeys );

QApplication::restoreOverrideCursor();
buttonBox->setEnabled( true );

if ( res )
{
QMessageBox::information( this, tr( "OpenStreetMap export" ), tr( "Export has been successful." ) );
}
else
{
QMessageBox::critical( this, tr( "OpenStreetMap import" ), tr( "Failed to export OSM data:\n%1" ).arg( mDatabase->errorString() ) );
}

mDatabase->close();
}

void QgsOSMExportDialog::onClose()
{
reject();
}
35 changes: 35 additions & 0 deletions src/app/openstreetmap/qgsosmexportdialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef QGSOSMEXPORTDIALOG_H
#define QGSOSMEXPORTDIALOG_H

#include <QDialog>

#include "ui_qgsosmexportdialog.h"

class QgsOSMDatabase;

class QStandardItemModel;

class QgsOSMExportDialog : public QDialog, private Ui::QgsOSMExportDialog
{
Q_OBJECT
public:
explicit QgsOSMExportDialog( QWidget *parent = 0 );
~QgsOSMExportDialog();

protected:
bool openDatabase();

private slots:
void onBrowse();
void updateLayerName();
void onLoadTags();

void onOK();
void onClose();

private:
QgsOSMDatabase* mDatabase;
QStandardItemModel* mTagsModel;
};

#endif // QGSOSMEXPORTDIALOG_H
116 changes: 116 additions & 0 deletions src/app/openstreetmap/qgsosmimportdialog.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "qgsosmimportdialog.h"

#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>

#include "qgsosmimport.h"

QgsOSMImportDialog::QgsOSMImportDialog( QWidget* parent )
: QDialog( parent ), mImport( new QgsOSMXmlImport )
{
setupUi( this );

connect( btnBrowseXml, SIGNAL( clicked() ), this, SLOT( onBrowseXml() ) );
connect( btnBrowseDb, SIGNAL( clicked() ), this, SLOT( onBrowseDb() ) );
connect( editXmlFileName, SIGNAL( textChanged( const QString& ) ), this, SLOT( xmlFileNameChanged( const QString& ) ) );
connect( editDbFileName, SIGNAL( textChanged( const QString& ) ), this, SLOT( dbFileNameChanged( const QString& ) ) );
connect( buttonBox, SIGNAL( accepted() ), this, SLOT( onOK() ) );
connect( buttonBox, SIGNAL( rejected() ), this, SLOT( onClose() ) );

connect( mImport, SIGNAL( progress( int ) ), this, SLOT( onProgress( int ) ) );
}

QgsOSMImportDialog::~QgsOSMImportDialog()
{
delete mImport;
}


void QgsOSMImportDialog::onBrowseXml()
{
QSettings settings;
QString lastDir = settings.value( "/osm/lastDir" ).toString();

QString fileName = QFileDialog::getOpenFileName( this, QString(), lastDir, tr( "OpenStreetMap files (*.osm)" ) );
if ( fileName.isNull() )
return;

settings.setValue( "/osm/lastDir", QFileInfo( fileName ).absolutePath() );
editXmlFileName->setText( fileName );
}

void QgsOSMImportDialog::onBrowseDb()
{
QSettings settings;
QString lastDir = settings.value( "/osm/lastDir" ).toString();

QString fileName = QFileDialog::getSaveFileName( this, QString(), lastDir, tr( "SQLite databases (*.db)" ) );
if ( fileName.isNull() )
return;

settings.setValue( "/osm/lastDir", QFileInfo( fileName ).absolutePath() );
editDbFileName->setText( fileName );
}


void QgsOSMImportDialog::xmlFileNameChanged( const QString& fileName )
{
editDbFileName->setText( fileName + ".db" );
}

void QgsOSMImportDialog::dbFileNameChanged( const QString& fileName )
{
editConnName->setText( QFileInfo( fileName ).baseName() );
}

void QgsOSMImportDialog::onOK()
{
// output file exists?
if ( QFileInfo( editDbFileName->text() ).exists() )
{
int res = QMessageBox::question( this, tr( "OpenStreetMap import" ), tr( "Output database file exists already. Overwrite?" ), QMessageBox::Yes | QMessageBox::No );
if ( res != QMessageBox::Yes )
return;
}

mImport->setInputXmlFileName( editXmlFileName->text() );
mImport->setOutputDbFileName( editDbFileName->text() );

buttonBox->setEnabled( false );
QApplication::setOverrideCursor( Qt::WaitCursor );

bool res = mImport->import();

QApplication::restoreOverrideCursor();
buttonBox->setEnabled( true );

progressBar->setValue( 0 );

if ( !res )
{
QMessageBox::critical( this, tr( "OpenStreetMap import" ), tr( "Failed to import import OSM data:\n%1" ).arg( mImport->errorString() ) );
return;
}

if ( groupCreateConn->isChecked() )
{
// create connection - this is a bit hacky, sorry for that.
QSettings settings;
settings.setValue( QString( "/SpatiaLite/connections/%1/sqlitepath" ).arg( editConnName->text() ), mImport->outputDbFileName() );
}

QMessageBox::information( this, tr( "OpenStreetMap import" ), tr( "Import has been successful." ) );
}

void QgsOSMImportDialog::onClose()
{
reject();
}

void QgsOSMImportDialog::onProgress( int percent )
{
progressBar->setValue( percent );
qApp->processEvents( QEventLoop::ExcludeSocketNotifiers );
}
33 changes: 33 additions & 0 deletions src/app/openstreetmap/qgsosmimportdialog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef QGSOSMIMPORTDIALOG_H
#define QGSOSMIMPORTDIALOG_H

#include <QDialog>

#include "ui_qgsosmimportdialog.h"

class QgsOSMXmlImport;

class QgsOSMImportDialog : public QDialog, private Ui::QgsOSMImportDialog
{
Q_OBJECT
public:
explicit QgsOSMImportDialog( QWidget* parent = 0 );
~QgsOSMImportDialog();

private slots:
void onBrowseXml();
void onBrowseDb();

void xmlFileNameChanged( const QString& fileName );
void dbFileNameChanged( const QString& fileName );

void onOK();
void onClose();

void onProgress( int percent );

private:
QgsOSMXmlImport* mImport;
};

#endif // QGSOSMIMPORTDIALOG_H
34 changes: 34 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
#include "ogr/qgsogrsublayersdialog.h"
#include "ogr/qgsopenvectorlayerdialog.h"
#include "ogr/qgsvectorlayersaveasdialog.h"

#include "qgsosmdownloaddialog.h"
#include "qgsosmimportdialog.h"
#include "qgsosmexportdialog.h"

//
// GDAL/OGR includes
//
Expand Down Expand Up @@ -525,6 +530,16 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
mSaveRollbackInProgress = false;
activateDeactivateLayerRelatedActions( NULL );

QAction* actionOSMDownload = new QAction( tr( "Download data" ), this );
connect( actionOSMDownload, SIGNAL( triggered() ), this, SLOT( osmDownloadDialog() ) );
QAction* actionOSMImport = new QAction( tr( "Import topology from XML" ), this );
connect( actionOSMImport, SIGNAL( triggered() ), this, SLOT( osmImportDialog() ) );
QAction* actionOSMExport = new QAction( tr( "Export topology to SpatiaLite" ), this );
connect( actionOSMExport, SIGNAL( triggered() ), this, SLOT( osmExportDialog() ) );
addPluginToVectorMenu( "OpenStreetMap", actionOSMDownload );
addPluginToVectorMenu( "OpenStreetMap", actionOSMImport );
addPluginToVectorMenu( "OpenStreetMap", actionOSMExport );

addDockWidget( Qt::LeftDockWidgetArea, mUndoWidget );
mUndoWidget->hide();

Expand Down Expand Up @@ -8868,6 +8883,25 @@ QMenu* QgisApp::createPopupMenu()
return menu;
}

void QgisApp::osmDownloadDialog()
{
QgsOSMDownloadDialog dlg;
dlg.exec();
}

void QgisApp::osmImportDialog()
{
QgsOSMImportDialog dlg;
dlg.exec();
}

void QgisApp::osmExportDialog()
{
QgsOSMExportDialog dlg;
dlg.exec();
}


#ifdef HAVE_TOUCH
bool QgisApp::gestureEvent( QGestureEvent *event )
{
Expand Down
4 changes: 4 additions & 0 deletions src/app/qgisapp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,10 @@ class QgisApp : public QMainWindow, private Ui::MainWindow
//! trust and load project macros
void enableProjectMacros();

void osmDownloadDialog();
void osmImportDialog();
void osmExportDialog();

signals:
/** emitted when a key is pressed and we want non widget sublasses to be able
to pick up on this (e.g. maplayer) */
Expand Down
185 changes: 185 additions & 0 deletions src/ui/qgsosmdownloaddialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsOSMDownloadDialog</class>
<widget class="QDialog" name="QgsOSMDownloadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>312</height>
</rect>
</property>
<property name="windowTitle">
<string>Download OpenStreetMap data</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Extent</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="radExtentCanvas">
<property name="text">
<string>From map canvas</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="radExtentLayer">
<property name="text">
<string>From layer</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cboLayers"/>
</item>
</layout>
</item>
<item>
<widget class="QRadioButton" name="radExtentManual">
<property name="text">
<string>Manual</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="editYMax"/>
</item>
<item row="0" column="3">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLineEdit" name="editXMin"/>
</item>
<item row="1" column="2" colspan="2">
<widget class="QLineEdit" name="editXMax"/>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="editYMin"/>
</item>
<item row="2" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Output file</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="editFileName"/>
</item>
<item>
<widget class="QToolButton" name="btnBrowse">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="editSize">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progress"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>radExtentCanvas</tabstop>
<tabstop>radExtentLayer</tabstop>
<tabstop>cboLayers</tabstop>
<tabstop>radExtentManual</tabstop>
<tabstop>editYMax</tabstop>
<tabstop>editXMin</tabstop>
<tabstop>editXMax</tabstop>
<tabstop>editYMin</tabstop>
<tabstop>editFileName</tabstop>
<tabstop>btnBrowse</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
157 changes: 157 additions & 0 deletions src/ui/qgsosmexportdialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsOSMExportDialog</class>
<widget class="QDialog" name="QgsOSMExportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>458</width>
<height>436</height>
</rect>
</property>
<property name="windowTitle">
<string>Export OpenStreetMap topology to SpatiaLite</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Input DB file</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="editDbFileName"/>
</item>
<item>
<widget class="QToolButton" name="btnBrowseDb">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Export type</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="radPoints">
<property name="text">
<string>Points (nodes)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radPolylines">
<property name="text">
<string>Polylines (open ways)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radPolygons">
<property name="text">
<string>Polygons (closed ways)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Output layer name</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="editLayerName"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Exported tags</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btnLoadTags">
<property name="text">
<string>Load from DB</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="viewTags">
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>editDbFileName</tabstop>
<tabstop>btnBrowseDb</tabstop>
<tabstop>radPoints</tabstop>
<tabstop>radPolylines</tabstop>
<tabstop>radPolygons</tabstop>
<tabstop>editLayerName</tabstop>
<tabstop>btnLoadTags</tabstop>
<tabstop>viewTags</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
124 changes: 124 additions & 0 deletions src/ui/qgsosmimportdialog.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsOSMImportDialog</class>
<widget class="QDialog" name="QgsOSMImportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>257</height>
</rect>
</property>
<property name="windowTitle">
<string>OpenStreetMap Import</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Input XML file (.osm)</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="editXmlFileName"/>
</item>
<item>
<widget class="QToolButton" name="btnBrowseXml">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Output SpatiaLite DB file</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="editDbFileName"/>
</item>
<item>
<widget class="QToolButton" name="btnBrowseDb">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupCreateConn">
<property name="title">
<string>Create connection (SpatiaLite) after import</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Connection name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="editConnName"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>21</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<tabstops>
<tabstop>editXmlFileName</tabstop>
<tabstop>btnBrowseXml</tabstop>
<tabstop>editDbFileName</tabstop>
<tabstop>btnBrowseDb</tabstop>
<tabstop>groupCreateConn</tabstop>
<tabstop>editConnName</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
1 change: 1 addition & 0 deletions tests/src/analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:

ADD_QGIS_TEST(analyzertest testqgsvectoranalyzer.cpp)
ADD_QGIS_TEST(openstreetmaptest testopenstreetmap.cpp)



194 changes: 194 additions & 0 deletions tests/src/analysis/testopenstreetmap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/***************************************************************************
testopenstreetmap.cpp
--------------------------------------
Date : January 2013
Copyright : (C) 2013 by Martin Dobias
Email : wonder dot sk at gmail 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. *
* *
***************************************************************************/

#include <QtTest>
#include <QSignalSpy>

#include <qgsapplication.h>
//#include <qgsproviderregistry.h>

#include "openstreetmap/qgsosmdatabase.h"
#include "openstreetmap/qgsosmdownload.h"
#include "openstreetmap/qgsosmimport.h"

class TestOpenStreetMap : public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init() ;// will be called before each testfunction is executed.
void cleanup() ;// will be called after every testfunction.
/** Our tests proper begin here */
void download();
void importAndQueries();
private:

};

void TestOpenStreetMap::initTestCase()
{
//
// Runs once before any tests are run
//
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
//QgsApplication::initQgis();
//QgsApplication::showSettings();

//create some objects that will be used in all tests...
//create a map layer that will be used in all tests...
//QString myBaseFileName( TEST_DATA_DIR ); //defined in CmakeLists.txt
}
void TestOpenStreetMap::cleanupTestCase()
{

}
void TestOpenStreetMap::init()
{

}
void TestOpenStreetMap::cleanup()
{
}


void TestOpenStreetMap::download()
{
QgsRectangle rect( 7.148, 51.249, 7.152, 51.251 );

// start download
OSMDownload download;
download.setQuery( OSMDownload::queryFromRect( rect ) );
download.setOutputFileName( "/tmp/dl-test.osm" );
bool res = download.start();
QVERIFY( res );

// wait for finished() signal
int timeout = 15000; // in miliseconds - max waiting time
int waitTime = 500; // in miliseconds - unit waiting time
QSignalSpy spy( &download, SIGNAL( finished() ) );
while ( timeout > 0 && spy.count() == 0 )
{
QTest::qWait( waitTime );
timeout -= waitTime;
}

QVERIFY( spy.count() != 0 );

if ( download.hasError() )
qDebug( "ERROR: %s", download.errorString().toAscii().data() );
}


void TestOpenStreetMap::importAndQueries()
{
QString dbFilename = "/tmp/testdata.db";
//QString xmlFilename = "/tmp/130127_023233_downloaded.osm";
QString xmlFilename = TEST_DATA_DIR "/openstreetmap/testdata.xml";

QgsOSMXmlImport import( xmlFilename, dbFilename );
bool res = import.import();
if ( import.hasError() )
qDebug( "XML ERR: %s", import.errorString().toAscii().data() );
QCOMPARE( res, true );
QCOMPARE( import.hasError(), false );

qDebug( "import finished" );

QgsOSMDatabase db( dbFilename );
bool dbopenRes = db.open();
if ( !db.errorString().isEmpty() )
qDebug( "DB ERR: %s", db.errorString().toAscii().data() );
QCOMPARE( dbopenRes, true );

// query node

QgsOSMNode n = db.node( 11111 );
QCOMPARE( n.isValid(), true );
QCOMPARE( n.point().x(), 14.4277148 );
QCOMPARE( n.point().y(), 50.0651387 );

QgsOSMNode nNot = db.node( 22222 );
QCOMPARE( nNot.isValid(), false );

// query node tags

QgsOSMTags tags = db.tags( false, 11111 );
QCOMPARE( tags.count(), 7 );
QCOMPARE( tags.value( "addr:postcode" ), QString( "12800" ) );

QgsOSMTags tags2 = db.tags( false, 360769661 );
QCOMPARE( tags2.count(), 0 );
QCOMPARE( tags2.value( "addr:postcode" ), QString() );

QgsOSMTags tagsNot = db.tags( false, 22222 );
QCOMPARE( tagsNot.count(), 0 );

// list nodes

QgsOSMNodeIterator nodes = db.listNodes();
QCOMPARE( nodes.next().id(), 11111 );
QCOMPARE( nodes.next().id(), 360769661 );
nodes.close();

// query way

QgsOSMWay w = db.way( 32137532 );
QCOMPARE( w.isValid(), true );
QCOMPARE( w.nodes().count(), 5 );
QCOMPARE( w.nodes()[0], ( qint64 )360769661 );
QCOMPARE( w.nodes()[1], ( qint64 )360769664 );

QgsOSMWay wNot = db.way( 1234567 );
QCOMPARE( wNot.isValid(), false );

// query way tags

QgsOSMTags tagsW = db.tags( true, 32137532 );
QCOMPARE( tagsW.count(), 3 );
QCOMPARE( tagsW.value( "building" ), QString( "yes" ) );

QgsOSMTags tagsWNot = db.tags( true, 1234567 );
QCOMPARE( tagsWNot.count(), 0 );

// list ways

QgsOSMWayIterator ways = db.listWays();
QCOMPARE( ways.next().id(), 32137532 );
QCOMPARE( ways.next().isValid(), false );
ways.close();

bool exportRes1 = db.exportSpatiaLite( QgsOSMDatabase::Point, "sl_points", QStringList( "addr:postcode" ) );
//bool exportRes = db.exportSpatiaLite( QStringList("amenity") << "name" << "highway" );
if ( !db.errorString().isEmpty() )
qDebug( "EXPORT-1 ERR: %s", db.errorString().toAscii().data() );
QCOMPARE( exportRes1, true );


bool exportRes2 = db.exportSpatiaLite( QgsOSMDatabase::Polyline, "sl_lines", QStringList( "building" ) );
//bool exportRes2 = db.exportSpatiaLite( QStringList("amenity") << "name" << "highway" );
if ( !db.errorString().isEmpty() )
qDebug( "EXPORT-2 ERR: %s", db.errorString().toAscii().data() );
QCOMPARE( exportRes2, true );


// TODO: test exported data
}


QTEST_MAIN( TestOpenStreetMap )

#include "moc_testopenstreetmap.cxx"
41 changes: 41 additions & 0 deletions tests/testdata/openstreetmap/testdata.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="CGImap 0.0.2" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">

<bounds minlat="50.0607644" minlon="14.4217296" maxlat="50.0705202" maxlon="14.4366398"/>

<!-- node -->
<node id="11111" lat="50.0651387" lon="14.4277148" user="Radomír Černoch" uid="51295" visible="true" version="2" changeset="1984279" timestamp="2009-07-30T13:22:24Z">
<tag k="addr:conscriptionnumber" v="455"/>
<tag k="addr:housenumber" v="455/23"/>
<tag k="addr:postcode" v="12800"/>
<tag k="addr:street" v="Jaromírova"/>
<tag k="addr:streetnumber" v="23"/>
<tag k="source:addr" v="uir_adr"/>
<tag k="uir_adr:ADRESA_KOD" v="21738092"/>
</node>

<!-- closed way -->
<node id="360769661" lat="50.0665514" lon="14.4270245" user="BiIbo" uid="3516" visible="true" version="1" changeset="819377" timestamp="2009-03-15T16:58:35Z"/>
<node id="360769664" lat="50.0665121" lon="14.4270254" user="BiIbo" uid="3516" visible="true" version="1" changeset="819377" timestamp="2009-03-15T16:58:36Z"/>
<node id="360769666" lat="50.0665127" lon="14.4270765" user="BiIbo" uid="3516" visible="true" version="1" changeset="819377" timestamp="2009-03-15T16:58:36Z"/>
<node id="360769670" lat="50.0665514" lon="14.4270765" user="BiIbo" uid="3516" visible="true" version="1" changeset="819377" timestamp="2009-03-15T16:58:36Z"/>
<way id="32137532" user="BiIbo" uid="3516" visible="true" version="1" changeset="819377" timestamp="2009-03-15T16:58:37Z">
<nd ref="360769661"/>
<nd ref="360769664"/>
<nd ref="360769666"/>
<nd ref="360769670"/>
<nd ref="360769661"/>
<tag k="building" v="yes"/>
<tag k="layer" v="1"/>
<tag k="source" v="cuzk:km"/>
</way>

<!-- relation -->
<relation id="126753" user="BiIbo" uid="3516" visible="true" version="1" changeset="1078773" timestamp="2009-05-04T19:51:58Z">
<member type="way" ref="32137532" role="outer"/>
<!-- todo <member type="way" ref="34075795" role="inner"/> -->
<!--<member type="way" ref="34075796" role="inner"/>-->
<tag k="type" v="multipolygon"/>
</relation>

</osm>