[FEATURE] new OpenStreetMap data access API and GUI

The idea is to replace the current OSM provider+plugin by this new code. Differences from old code:
- read-only access - without editing and upload support
- no special provider (using SpatiaLite provider)
- underlying OSM topology accessible from API
- download using Overpass API: fast, customizable, nearly unlimited download
- OSM XML files have to be first imported to a Sqlite3 database, then SpatiaLite layers can be exported
This commit is contained in:
Martin Dobias 2013-02-23 01:14:16 +01:00
parent 94f8f73643
commit 4512133f61
24 changed files with 2821 additions and 1 deletions

View File

@ -37,6 +37,10 @@ SET(QGIS_ANALYSIS_SRCS
vector/qgsgeometryanalyzer.cpp
vector/qgszonalstatistics.cpp
vector/qgsoverlayanalyzer.cpp
openstreetmap/qgsosmbase.cpp
openstreetmap/qgsosmdatabase.cpp
openstreetmap/qgsosmdownload.cpp
openstreetmap/qgsosmimport.cpp
)
INCLUDE_DIRECTORIES(BEFORE raster)
@ -64,6 +68,8 @@ IF ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
ENDIF ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
SET(QGIS_ANALYSIS_MOC_HDRS
openstreetmap/qgsosmdownload.h
openstreetmap/qgsosmimport.h
)
QT4_WRAP_CPP(QGIS_ANALYSIS_MOC_SRCS ${QGIS_ANALYSIS_MOC_HDRS})
@ -85,6 +91,10 @@ SET(QGIS_ANALYSIS_HDRS
interpolation/qgsgridfilewriter.h
interpolation/qgsidwinterpolator.h
interpolation/qgstininterpolator.h
openstreetmap/qgsosmbase.h
openstreetmap/qgsosmdatabase.h
openstreetmap/qgsosmdownload.h
openstreetmap/qgsosmimport.h
)
INCLUDE_DIRECTORIES(

View File

@ -0,0 +1,3 @@
#include "qgsosmbase.h"
// nothing here now

View File

@ -0,0 +1,130 @@
#ifndef OSMBASE_H
#define OSMBASE_H
#include <QString>
#include "qgspoint.h"
#include <sqlite3.h>
typedef qint64 QgsOSMId;
class QgsOSMDatabase;
struct QgsOSMElementID
{
enum Type { Invalid, Node, Way, Relation };
Type type;
QgsOSMId id;
};
/**
Elements (also data primitives) are the basic components in OpenStreetMap from which everything else
is defined. These consist of Nodes (which define a point in space), Ways (which define a linear features
and areas), and Relations - with an optional role - which are sometimes used to define the relation
between other elements. All of the above can have one of more associated tags.
*/
class ANALYSIS_EXPORT QgsOSMElement
{
public:
QgsOSMElement() { mElemID.type = QgsOSMElementID::Invalid; mElemID.id = 0; }
QgsOSMElement( QgsOSMElementID::Type t, QgsOSMId id ) { mElemID.type = t; mElemID.id = id; }
bool isValid() const { return mElemID.type != QgsOSMElementID::Invalid; }
QgsOSMDatabase* database() const;
// fetched automatically from DB
QgsOSMElementID elemID() const { return mElemID; }
int id() const { return mElemID.id; }
//QString username() const;
//QDateTime timestamp() const;
//int version() const;
private:
QgsOSMElementID mElemID;
};
/**
A node is one of the core elements in the OpenStreetMap data model. It consists of a single geospatial
point using a latitude and longitude. A third optional dimension, altitude, can be recorded; key:ele
and a node can also be defined at a particular layer=* or level=*. Nodes can be used to define standalone
point features or be used to define the path of a way.
*/
class ANALYSIS_EXPORT QgsOSMNode : public QgsOSMElement
{
public:
QgsOSMNode() : mPoint() {}
QgsOSMNode( QgsOSMId id, const QgsPoint& point ) : QgsOSMElement( QgsOSMElementID::Node, id ), mPoint( point ) {}
QgsPoint point() const { return mPoint; }
// fetched on-demand
QList<QgsOSMElementID> ways() const; // where the node participates?
QList<QgsOSMElementID> relations() const;
private:
QgsPoint mPoint;
};
/**
A way is an ordered list of nodes which normally also has at least one tag or is included within
a Relation. A way can have between 2 and 2,000 nodes, although it's possible that faulty ways with zero
or a single node exist. A way can be open or closed. A closed way is one whose last node on the way
is also the first on that way. A closed way may be interpreted either as a closed polyline, or an area,
or both.
*/
class ANALYSIS_EXPORT QgsOSMWay : public QgsOSMElement
{
public:
QgsOSMWay() {}
QgsOSMWay( QgsOSMId id, const QList<QgsOSMId> nodes ) : QgsOSMElement( QgsOSMElementID::Way, id ), mNodes( nodes ) {}
QList<QgsOSMId> nodes() const { return mNodes; }
// fetched on-demand
//QList<OSMElementID> relations() const;
private:
QList<QgsOSMId> mNodes;
};
#if 0
/**
A relation is one of the core data elements that consists of one or more tags and also an ordered list
of one or more nodes and/or ways as members which is used to define logical or geographic relationships
between other elements. A member of a relation can optionally have a role which describe the part that
a particular feature plays within a relation.
*/
class ANALYSIS_EXPORT QgsOSMRelation : public QgsOSMElement
{
public:
QString relationType() const;
QList< QPair<QgsOSMElementID, QString> > members() const;
};
#endif
class ANALYSIS_EXPORT QgsOSMTags
{
public:
QgsOSMTags() {}
int count() const { return mMap.count(); }
QList<QString> keys() const { return mMap.keys(); }
bool contains( const QString& k ) const { return mMap.contains( k ); }
void insert( const QString& k, const QString& v ) { mMap.insert( k, v ); }
QString value( const QString& k ) const { return mMap.value( k ); }
private:
QMap<QString, QString> mMap;
};
#endif // OSMBASE_H

View File

@ -0,0 +1,597 @@
#include "qgsosmdatabase.h"
#include <spatialite.h>
#include "qgsgeometry.h"
#include "qgslogger.h"
QgsOSMDatabase::QgsOSMDatabase( const QString& dbFileName )
: mDbFileName( dbFileName )
, mDatabase( 0 )
, mStmtNode( 0 )
, mStmtNodeTags( 0 )
, mStmtWay( 0 )
, mStmtWayNode( 0 )
, mStmtWayNodePoints( 0 )
, mStmtWayTags( 0 )
{
}
QgsOSMDatabase::~QgsOSMDatabase()
{
if ( isOpen() )
close();
}
bool QgsOSMDatabase::isOpen() const
{
return mDatabase != 0;
}
bool QgsOSMDatabase::open()
{
// load spatialite extension
spatialite_init( 0 );
// open database
int res = sqlite3_open_v2( mDbFileName.toUtf8().data(), &mDatabase, SQLITE_OPEN_READWRITE, 0 );
if ( res != SQLITE_OK )
{
mError = QString( "Failed to open database [%1]: %2" ).arg( res ).arg( mDbFileName );
close();
return false;
}
if ( !prepareStatements() )
{
close();
return false;
}
return true;
}
void QgsOSMDatabase::deleteStatement( sqlite3_stmt*& stmt )
{
if ( stmt )
{
sqlite3_finalize( stmt );
stmt = 0;
}
}
bool QgsOSMDatabase::close()
{
deleteStatement( mStmtNode );
deleteStatement( mStmtNodeTags );
deleteStatement( mStmtWay );
deleteStatement( mStmtWayNode );
deleteStatement( mStmtWayNodePoints );
deleteStatement( mStmtWayTags );
Q_ASSERT( mStmtNode == 0 );
// close database
if ( sqlite3_close( mDatabase ) != SQLITE_OK )
{
//mError = ( char * ) "Closing SQLite3 database failed.";
//return false;
}
mDatabase = 0;
return true;
}
int QgsOSMDatabase::runCountStatement( const char* sql ) const
{
sqlite3_stmt* stmt;
int res = sqlite3_prepare_v2( mDatabase, sql, -1, &stmt, 0 );
if ( res != SQLITE_OK )
return -1;
res = sqlite3_step( stmt );
if ( res != SQLITE_ROW )
return -1;
int count = sqlite3_column_int( stmt, 0 );
sqlite3_finalize( stmt );
return count;
}
int QgsOSMDatabase::countNodes() const
{
return runCountStatement( "SELECT count(*) FROM nodes" );
}
int QgsOSMDatabase::countWays() const
{
return runCountStatement( "SELECT count(*) FROM ways" );
}
QgsOSMNodeIterator QgsOSMDatabase::listNodes() const
{
return QgsOSMNodeIterator( mDatabase );
}
QgsOSMWayIterator QgsOSMDatabase::listWays() const
{
return QgsOSMWayIterator( mDatabase );
}
QgsOSMNode QgsOSMDatabase::node( QgsOSMId id ) const
{
// bind the way identifier
sqlite3_bind_int64( mStmtNode, 1, id );
if ( sqlite3_step( mStmtNode ) != SQLITE_ROW )
{
//QgsDebugMsg( "Cannot get number of way members." );
sqlite3_reset( mStmtNode );
return QgsOSMNode();
}
double lon = sqlite3_column_double( mStmtNode, 0 );
double lat = sqlite3_column_double( mStmtNode, 1 );
QgsOSMNode node( id, QgsPoint( lon, lat ) );
sqlite3_reset( mStmtNode );
return node;
}
QgsOSMTags QgsOSMDatabase::tags( bool way, QgsOSMId id ) const
{
QgsOSMTags t;
sqlite3_stmt* stmtTags = way ? mStmtWayTags : mStmtNodeTags;
sqlite3_bind_int64( stmtTags, 1, id );
while ( sqlite3_step( stmtTags ) == SQLITE_ROW )
{
QString k = QString::fromUtf8(( const char* ) sqlite3_column_text( stmtTags, 0 ) );
QString v = QString::fromUtf8(( const char* ) sqlite3_column_text( stmtTags, 1 ) );
t.insert( k, v );
}
sqlite3_reset( stmtTags );
return t;
}
QList<QgsOSMTagCountPair> QgsOSMDatabase::usedTags( bool ways ) const
{
QList<QgsOSMTagCountPair> pairs;
QString sql = QString( "SELECT k, count(k) FROM %1_tags GROUP BY k" ).arg( ways ? "ways" : "nodes" );
sqlite3_stmt* stmt;
if ( sqlite3_prepare_v2( mDatabase, sql.toUtf8().data(), -1, &stmt, 0 ) != SQLITE_OK )
return pairs;
while ( sqlite3_step( stmt ) == SQLITE_ROW )
{
QString k = QString::fromUtf8(( const char* ) sqlite3_column_text( stmt, 0 ) );
int count = sqlite3_column_int( stmt, 1 );
pairs.append( qMakePair( k, count ) );
}
sqlite3_finalize( stmt );
return pairs;
}
QgsOSMWay QgsOSMDatabase::way( QgsOSMId id ) const
{
// TODO: first check that way exists!
// mStmtWay
// bind the way identifier
sqlite3_bind_int64( mStmtWayNode, 1, id );
QList<QgsOSMId> nodes;
while ( sqlite3_step( mStmtWayNode ) == SQLITE_ROW )
{
QgsOSMId nodeId = sqlite3_column_int64( mStmtWayNode, 0 );
nodes.append( nodeId );
}
sqlite3_reset( mStmtWayNode );
if ( nodes.isEmpty() )
return QgsOSMWay();
return QgsOSMWay( id, nodes );
}
/*
OSMRelation OSMDatabase::relation( OSMId id ) const
{
// todo
Q_UNUSED(id);
return OSMRelation();
}*/
QgsPolyline QgsOSMDatabase::wayPoints( QgsOSMId id ) const
{
QgsPolyline points;
// bind the way identifier
sqlite3_bind_int64( mStmtWayNodePoints, 1, id );
while ( sqlite3_step( mStmtWayNodePoints ) == SQLITE_ROW )
{
if ( sqlite3_column_type( mStmtWayNodePoints, 0 ) == SQLITE_NULL )
return QgsPolyline(); // missing some nodes
double lon = sqlite3_column_double( mStmtWayNodePoints, 0 );
double lat = sqlite3_column_double( mStmtWayNodePoints, 1 );
points.append( QgsPoint( lon, lat ) );
}
sqlite3_reset( mStmtWayNodePoints );
return points;
}
bool QgsOSMDatabase::prepareStatements()
{
const char* sql[] =
{
"SELECT lon,lat FROM nodes WHERE id=?",
"SELECT k,v FROM nodes_tags WHERE id=?",
"SELECT id FROM ways WHERE id=?",
"SELECT node_id FROM ways_nodes WHERE way_id=? ORDER BY way_pos",
"SELECT n.lon, n.lat FROM ways_nodes wn LEFT JOIN nodes n ON wn.node_id = n.id WHERE wn.way_id=? ORDER BY wn.way_pos",
"SELECT k,v FROM ways_tags WHERE id=?"
};
sqlite3_stmt** sqlite[] =
{
&mStmtNode,
&mStmtNodeTags,
&mStmtWay,
&mStmtWayNode,
&mStmtWayNodePoints,
&mStmtWayTags
};
int count = sizeof( sql ) / sizeof( const char* );
Q_ASSERT( count == sizeof( sqlite ) / sizeof( sqlite3_stmt** ) );
for ( int i = 0; i < count; ++i )
{
if ( sqlite3_prepare_v2( mDatabase, sql[i], -1, sqlite[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( sql[i] ) );
return false;
}
}
return true;
}
bool QgsOSMDatabase::exportSpatiaLite( ExportType type, const QString& tableName, const QStringList& tagKeys )
{
mError.clear();
// create SpatiaLite table
QString geometryType;
if ( type == Point ) geometryType = "POINT";
else if ( type == Polyline ) geometryType = "LINESTRING";
else if ( type == Polygon ) geometryType = "POLYGON";
else Q_ASSERT( false && "Unknown export type" );
if ( !createSpatialTable( tableName, geometryType, tagKeys ) )
return false;
// import data
int retX = sqlite3_exec( mDatabase, "BEGIN", NULL, NULL, 0 );
Q_ASSERT( retX == SQLITE_OK );
if ( type == Polyline || type == Polygon )
exportSpatiaLiteWays( type == Polygon, tableName, tagKeys );
else if ( type == Point )
exportSpatiaLiteNodes( tableName, tagKeys );
else
Q_ASSERT( false && "Unknown export type" );
int retY = sqlite3_exec( mDatabase, "COMMIT", NULL, NULL, 0 );
Q_ASSERT( retY == SQLITE_OK );
if ( !createSpatialIndex( tableName ) )
return false;
return mError.isEmpty();
}
bool QgsOSMDatabase::createSpatialTable( const QString& tableName, const QString& geometryType, const QStringList& tagKeys )
{
QString sqlCreateTable = QString( "CREATE TABLE %1 (id INTEGER PRIMARY KEY" ).arg( quotedIdentifier( tableName ) );
for ( int i = 0; i < tagKeys.count(); ++i )
sqlCreateTable += QString( ", %1 TEXT" ).arg( quotedIdentifier( tagKeys[i] ) );
sqlCreateTable += ")";
char *errMsg = NULL;
int ret = sqlite3_exec( mDatabase, sqlCreateTable.toUtf8().constData(), NULL, NULL, &errMsg );
if ( ret != SQLITE_OK )
{
mError = "Unable to create table:\n" + QString::fromUtf8( errMsg );
sqlite3_free( errMsg );
return false;
}
QString sqlAddGeomColumn = QString( "SELECT AddGeometryColumn(%1, 'geometry', 4326, %2, 'XY')" )
.arg( quotedValue( tableName ) )
.arg( quotedValue( geometryType ) );
ret = sqlite3_exec( mDatabase, sqlAddGeomColumn.toUtf8().constData(), NULL, NULL, &errMsg );
if ( ret != SQLITE_OK )
{
mError = "Unable to add geometry column:\n" + QString::fromUtf8( errMsg );
sqlite3_free( errMsg );
return false;
}
return true;
}
bool QgsOSMDatabase::createSpatialIndex( const QString& tableName )
{
QString sqlSpatialIndex = QString( "SELECT CreateSpatialIndex(%1, 'geometry')" ).arg( quotedValue( tableName ) );
char *errMsg = NULL;
int ret = sqlite3_exec( mDatabase, sqlSpatialIndex.toUtf8().constData(), NULL, NULL, &errMsg );
if ( ret != SQLITE_OK )
{
mError = "Unable to create spatial index:\n" + QString::fromUtf8( errMsg );
sqlite3_free( errMsg );
return false;
}
return true;
}
void QgsOSMDatabase::exportSpatiaLiteNodes( const QString& tableName, const QStringList& tagKeys )
{
QString sqlInsertPoint = QString( "INSERT INTO %1 VALUES (?" ).arg( quotedIdentifier( tableName ) );
for ( int i = 0; i < tagKeys.count(); ++i )
sqlInsertPoint += QString( ",?" );
sqlInsertPoint += ", GeomFromWKB(?, 4326))";
sqlite3_stmt* stmtInsert;
if ( sqlite3_prepare_v2( mDatabase, sqlInsertPoint.toUtf8().constData(), -1, &stmtInsert, 0 ) != SQLITE_OK )
{
mError = "Prepare SELECT FROM nodes failed.";
return;
}
QgsOSMNodeIterator nodes = listNodes();
QgsOSMNode n;
while (( n = nodes.next() ).isValid() )
{
QgsOSMTags t = tags( false, n.id() );
// skip untagged nodes: probably they form a part of ways
if ( t.count() == 0 )
continue;
QgsGeometry* geom = QgsGeometry::fromPoint( n.point() );
int col = 0;
sqlite3_bind_int64( stmtInsert, ++col, n.id() );
// tags
for ( int i = 0; i < tagKeys.count(); ++i )
{
if ( t.contains( tagKeys[i] ) )
sqlite3_bind_text( stmtInsert, ++col, t.value( tagKeys[i] ).toUtf8().constData(), -1, SQLITE_TRANSIENT );
else
sqlite3_bind_null( stmtInsert, ++col );
}
sqlite3_bind_blob( stmtInsert, ++col, geom->asWkb(), geom->wkbSize(), SQLITE_STATIC );
int insertRes = sqlite3_step( stmtInsert );
if ( insertRes != SQLITE_DONE )
{
mError = QString( "Error inserting node %1 [%2]" ).arg( n.id() ).arg( insertRes );
break;
}
sqlite3_reset( stmtInsert );
sqlite3_clear_bindings( stmtInsert );
delete geom;
}
sqlite3_finalize( stmtInsert );
}
void QgsOSMDatabase::exportSpatiaLiteWays( bool closed, const QString& tableName, const QStringList& tagKeys )
{
Q_UNUSED( tagKeys );
QString sqlInsertLine = QString( "INSERT INTO %1 VALUES (?" ).arg( quotedIdentifier( tableName ) );
for ( int i = 0; i < tagKeys.count(); ++i )
sqlInsertLine += QString( ",?" );
sqlInsertLine += ", GeomFromWKB(?, 4326))";
sqlite3_stmt* stmtInsert;
if ( sqlite3_prepare_v2( mDatabase, sqlInsertLine.toUtf8().constData(), -1, &stmtInsert, 0 ) != SQLITE_OK )
{
mError = "Prepare SELECT FROM ways failed.";
return;
}
QgsOSMWayIterator ways = listWays();
QgsOSMWay w;
while (( w = ways.next() ).isValid() )
{
QgsOSMTags t = tags( true, w.id() );
QgsPolyline polyline = wayPoints( w.id() );
if ( polyline.count() < 2 )
continue; // invalid way
bool isArea = ( polyline.first() == polyline.last() ); // closed way?
// some closed ways are not really areas
if ( isArea && ( t.contains( "highway" ) || t.contains( "barrier" ) ) )
{
if ( t.value( "area" ) != "yes" ) // even though "highway" is line by default, "area"="yes" may override that
isArea = false;
}
if ( closed != isArea )
continue; // skip if it's not what we're looking for
QgsGeometry* geom = closed ? QgsGeometry::fromPolygon( QgsPolygon() << polyline ) : QgsGeometry::fromPolyline( polyline );
int col = 0;
sqlite3_bind_int64( stmtInsert, ++col, w.id() );
// tags
for ( int i = 0; i < tagKeys.count(); ++i )
{
if ( t.contains( tagKeys[i] ) )
sqlite3_bind_text( stmtInsert, ++col, t.value( tagKeys[i] ).toUtf8().constData(), -1, SQLITE_TRANSIENT );
else
sqlite3_bind_null( stmtInsert, ++col );
}
sqlite3_bind_blob( stmtInsert, ++col, geom->asWkb(), geom->wkbSize(), SQLITE_STATIC );
int insertRes = sqlite3_step( stmtInsert );
if ( insertRes != SQLITE_DONE )
{
mError = QString( "Error inserting way %1 [%2]" ).arg( w.id() ).arg( insertRes );
break;
}
sqlite3_reset( stmtInsert );
sqlite3_clear_bindings( stmtInsert );
delete geom;
}
sqlite3_finalize( stmtInsert );
}
QString QgsOSMDatabase::quotedIdentifier( QString id )
{
id.replace( "\"", "\"\"" );
return QString( "\"%1\"" ).arg( id );
}
QString QgsOSMDatabase::quotedValue( QString value )
{
if ( value.isNull() )
return "NULL";
value.replace( "'", "''" );
return QString( "'%1'" ).arg( value );
}
///////////////////////////////////
QgsOSMNodeIterator::QgsOSMNodeIterator( sqlite3* handle )
: mStmt( 0 )
{
const char* sql = "SELECT id,lon,lat FROM nodes";
if ( sqlite3_prepare_v2( handle, sql, -1, &mStmt, 0 ) != SQLITE_OK )
{
qDebug( "OSMNodeIterator: error prepare" );
}
}
QgsOSMNodeIterator::~QgsOSMNodeIterator()
{
close();
}
QgsOSMNode QgsOSMNodeIterator::next()
{
if ( !mStmt )
return QgsOSMNode();
if ( sqlite3_step( mStmt ) != SQLITE_ROW )
{
close();
return QgsOSMNode();
}
QgsOSMId id = sqlite3_column_int64( mStmt, 0 );
double lon = sqlite3_column_double( mStmt, 1 );
double lat = sqlite3_column_double( mStmt, 2 );
return QgsOSMNode( id, QgsPoint( lon, lat ) );
}
void QgsOSMNodeIterator::close()
{
if ( mStmt )
{
sqlite3_finalize( mStmt );
mStmt = 0;
}
}
///////////////////////////////////
QgsOSMWayIterator::QgsOSMWayIterator( sqlite3* handle )
: mStmt( 0 )
{
const char* sql = "SELECT id FROM ways";
if ( sqlite3_prepare_v2( handle, sql, -1, &mStmt, 0 ) != SQLITE_OK )
{
qDebug( "OSMWayIterator: error prepare" );
}
}
QgsOSMWayIterator::~QgsOSMWayIterator()
{
close();
}
QgsOSMWay QgsOSMWayIterator::next()
{
if ( !mStmt )
return QgsOSMWay();
if ( sqlite3_step( mStmt ) != SQLITE_ROW )
{
close();
return QgsOSMWay();
}
QgsOSMId id = sqlite3_column_int64( mStmt, 0 );
return QgsOSMWay( id, QList<QgsOSMId>() ); // TODO[MD]: ?
}
void QgsOSMWayIterator::close()
{
if ( mStmt )
{
sqlite3_finalize( mStmt );
mStmt = 0;
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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

View File

@ -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)
@ -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)
@ -419,7 +427,7 @@ 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
@ -427,6 +435,7 @@ INCLUDE_DIRECTORIES(
../plugins
../python
gps
openstreetmap
)
IF (ANDROID)

View File

@ -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 ) ) );
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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 );
}

View File

@ -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

View File

@ -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
//
@ -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();
@ -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 )
{

View File

@ -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) */

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -72,6 +72,7 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:
ADD_QGIS_TEST(analyzertest testqgsvectoranalyzer.cpp)
ADD_QGIS_TEST(openstreetmaptest testopenstreetmap.cpp)

View File

@ -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"

View File

@ -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>