mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
94f8f73643
commit
4512133f61
@ -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(
|
||||
|
3
src/analysis/openstreetmap/qgsosmbase.cpp
Normal file
3
src/analysis/openstreetmap/qgsosmbase.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
#include "qgsosmbase.h"
|
||||
|
||||
// nothing here now
|
130
src/analysis/openstreetmap/qgsosmbase.h
Normal file
130
src/analysis/openstreetmap/qgsosmbase.h
Normal 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
|
597
src/analysis/openstreetmap/qgsosmdatabase.cpp
Normal file
597
src/analysis/openstreetmap/qgsosmdatabase.cpp
Normal 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;
|
||||
}
|
||||
}
|
127
src/analysis/openstreetmap/qgsosmdatabase.h
Normal file
127
src/analysis/openstreetmap/qgsosmdatabase.h
Normal 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
|
129
src/analysis/openstreetmap/qgsosmdownload.cpp
Normal file
129
src/analysis/openstreetmap/qgsosmdownload.cpp
Normal 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();
|
||||
}
|
87
src/analysis/openstreetmap/qgsosmdownload.h
Normal file
87
src/analysis/openstreetmap/qgsosmdownload.h
Normal 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
|
371
src/analysis/openstreetmap/qgsosmimport.cpp
Normal file
371
src/analysis/openstreetmap/qgsosmimport.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
63
src/analysis/openstreetmap/qgsosmimport.h
Normal file
63
src/analysis/openstreetmap/qgsosmimport.h
Normal 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
|
@ -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)
|
||||
|
175
src/app/openstreetmap/qgsosmdownloaddialog.cpp
Normal file
175
src/app/openstreetmap/qgsosmdownloaddialog.cpp
Normal 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 ) ) );
|
||||
}
|
41
src/app/openstreetmap/qgsosmdownloaddialog.h
Normal file
41
src/app/openstreetmap/qgsosmdownloaddialog.h
Normal 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
|
154
src/app/openstreetmap/qgsosmexportdialog.cpp
Normal file
154
src/app/openstreetmap/qgsosmexportdialog.cpp
Normal 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();
|
||||
}
|
35
src/app/openstreetmap/qgsosmexportdialog.h
Normal file
35
src/app/openstreetmap/qgsosmexportdialog.h
Normal 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
|
116
src/app/openstreetmap/qgsosmimportdialog.cpp
Normal file
116
src/app/openstreetmap/qgsosmimportdialog.cpp
Normal 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 );
|
||||
}
|
33
src/app/openstreetmap/qgsosmimportdialog.h
Normal file
33
src/app/openstreetmap/qgsosmimportdialog.h
Normal 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
|
@ -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 )
|
||||
{
|
||||
|
@ -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) */
|
||||
|
185
src/ui/qgsosmdownloaddialog.ui
Normal file
185
src/ui/qgsosmdownloaddialog.ui
Normal 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>
|
157
src/ui/qgsosmexportdialog.ui
Normal file
157
src/ui/qgsosmexportdialog.ui
Normal 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>
|
124
src/ui/qgsosmimportdialog.ui
Normal file
124
src/ui/qgsosmimportdialog.ui
Normal 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>
|
@ -72,6 +72,7 @@ ENDMACRO (ADD_QGIS_TEST)
|
||||
# Tests:
|
||||
|
||||
ADD_QGIS_TEST(analyzertest testqgsvectoranalyzer.cpp)
|
||||
ADD_QGIS_TEST(openstreetmaptest testopenstreetmap.cpp)
|
||||
|
||||
|
||||
|
||||
|
194
tests/src/analysis/testopenstreetmap.cpp
Normal file
194
tests/src/analysis/testopenstreetmap.cpp
Normal 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"
|
41
tests/testdata/openstreetmap/testdata.xml
vendored
Normal file
41
tests/testdata/openstreetmap/testdata.xml
vendored
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user