[OGR provider] Fix reading of OSM datasets when opening several layers at the same time (fixes #19477)

This commit is contained in:
Even Rouault 2018-09-22 11:59:14 +02:00
parent 9dd1406539
commit fabdc04764
No known key found for this signature in database
GPG Key ID: 33EBBFC47B3DD87D
6 changed files with 243 additions and 80 deletions

View File

@ -58,7 +58,7 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
else
{
//QgsDebugMsg( "Feature iterator of " + mSource->mLayerName + ": acquiring connection");
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource ), mRequest.timeout(), mRequest.requestMayBeNested() );
mConn = QgsOgrConnPool::instance()->acquireConnection( QgsOgrProviderUtils::connectionPoolId( mSource->mDataSource, mSource->mShareSameDatasetAmongLayers ), mRequest.timeout(), mRequest.requestMayBeNested() );
if ( !mConn || !mConn->ds )
{
return;
@ -468,6 +468,7 @@ bool QgsOgrFeatureIterator::readFeature( gdal::ogr_feature_unique_ptr fet, QgsFe
QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider *p )
: mDataSource( p->dataSourceUri( true ) )
, mShareSameDatasetAmongLayers( p->mShareSameDatasetAmongLayers )
, mLayerName( p->layerName() )
, mLayerIndex( p->layerIndex() )
, mSubsetString( p->mSubsetString )
@ -486,12 +487,12 @@ QgsOgrFeatureSource::QgsOgrFeatureSource( const QgsOgrProvider *p )
}
for ( int i = ( p->mFirstFieldIsFid ) ? 1 : 0; i < mFields.size(); i++ )
mFieldsWithoutFid.append( mFields.at( i ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( mDataSource ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( mDataSource, mShareSameDatasetAmongLayers ) );
}
QgsOgrFeatureSource::~QgsOgrFeatureSource()
{
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( mDataSource ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( mDataSource, mShareSameDatasetAmongLayers ) );
}
QgsFeatureIterator QgsOgrFeatureSource::getFeatures( const QgsFeatureRequest &request )

View File

@ -38,6 +38,7 @@ class QgsOgrFeatureSource : public QgsAbstractFeatureSource
private:
QString mDataSource;
bool mShareSameDatasetAmongLayers;
QString mLayerName;
int mLayerIndex;
QString mSubsetString;

View File

@ -538,15 +538,15 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio
setNativeTypes( nativeTypes );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
}
QgsOgrProvider::~QgsOgrProvider()
{
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
// We must also make sure to flush unusef cached connections so that
// the file can be removed (#15137)
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
// Do that as last step for final cleanup that might be prevented by
// still opened datasets.
@ -948,7 +948,7 @@ OGRwkbGeometryType QgsOgrProvider::getOgrGeomType( OGRLayerH ogrLayer )
void QgsOgrProvider::loadFields()
{
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
//the attribute fields need to be read again when the encoding changes
mAttributeFields.clear();
mDefaultValues.clear();
@ -1642,7 +1642,7 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
{
// adding attributes in mapinfo requires to be able to delete the .dat file
// so drop any cached connections.
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
}
bool returnvalue = true;
@ -1885,10 +1885,10 @@ bool QgsOgrProvider::_setSubsetString( const QString &theSQL, bool updateFeature
if ( uri != dataSourceUri() )
{
if ( hasExistingRef )
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
setDataSourceUri( uri );
if ( hasExistingRef )
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
}
mOgrLayer->ResetReading();
@ -2061,7 +2061,7 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
{
pushError( tr( "OGR error syncing to disk: %1" ).arg( CPLGetLastErrorMsg() ) );
}
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
return true;
}
@ -2140,7 +2140,7 @@ bool QgsOgrProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
if ( mTransaction )
mTransaction->dirtyLastSavePoint();
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
return syncToDisc();
}
@ -3595,21 +3595,24 @@ QByteArray QgsOgrProvider::quotedIdentifier( const QByteArray &field ) const
void QgsOgrProvider::forceReload()
{
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
}
QString QgsOgrProviderUtils::connectionPoolId( const QString &dataSourceURI )
QString QgsOgrProviderUtils::connectionPoolId( const QString &dataSourceURI, bool shareSameDatasetAmongLayers )
{
// If the file part of the URI is really a file, then use it as the
// connection pool id (for example, so that all layers of a .gpkg file can
// use the same GDAL dataset object)
// Otherwise use the datasourceURI
// Not completely sure about this logic. But at least, for GeoPackage this
// works fine with multi layer datasets.
QString filePath = dataSourceURI.left( dataSourceURI.indexOf( QLatin1String( "|" ) ) );
QFileInfo fi( filePath );
if ( fi.isFile() )
return filePath;
if ( shareSameDatasetAmongLayers )
{
// If the file part of the URI is really a file, then use it as the
// connection pool id (for example, so that all layers of a .gpkg file can
// use the same GDAL dataset object)
// Otherwise use the datasourceURI
// Not completely sure about this logic. But at least, for GeoPackage this
// works fine with multi layer datasets.
QString filePath = dataSourceURI.left( dataSourceURI.indexOf( QLatin1String( "|" ) ) );
QFileInfo fi( filePath );
if ( fi.isFile() )
return filePath;
}
return dataSourceURI;
}
@ -3877,7 +3880,7 @@ QString QgsOgrProviderUtils::quotedValue( const QVariant &value )
bool QgsOgrProvider::syncToDisc()
{
//for shapefiles, remove spatial index files and create a new index
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->unref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
bool shapeIndex = false;
if ( mGDALDriverName == QLatin1String( "ESRI Shapefile" ) )
{
@ -3892,7 +3895,7 @@ bool QgsOgrProvider::syncToDisc()
{
shapeIndex = true;
close();
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
QFile::remove( sbnIndexFile );
open( OpenModeSameAsCurrent );
if ( !mValid )
@ -3916,7 +3919,7 @@ bool QgsOgrProvider::syncToDisc()
}
#endif
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->ref( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
if ( shapeIndex )
{
return createSpatialIndex();
@ -3978,7 +3981,7 @@ void QgsOgrProvider::recalculateFeatureCount()
mOgrLayer->SetSpatialFilter( filter );
}
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ) ) );
QgsOgrConnPool::instance()->invalidateConnections( QgsOgrProviderUtils::connectionPoolId( dataSourceUri( true ), mShareSameDatasetAmongLayers ) );
}
bool QgsOgrProvider::doesStrictFeatureTypeCheck() const
@ -4010,13 +4013,13 @@ OGRwkbGeometryType QgsOgrProvider::ogrWkbSingleFlatten( OGRwkbGeometryType type
OGRLayerH QgsOgrProviderUtils::setSubsetString( OGRLayerH layer, GDALDatasetH ds, QTextCodec *encoding, const QString &subsetString, bool addOriginalFid, bool *origFidAdded )
{
QByteArray layerName = OGR_FD_GetName( OGR_L_GetLayerDefn( layer ) );
GDALDriverH mGDALDriver = GDALGetDatasetDriver( ds );
QString mGDALDriverName = GDALGetDriverShortName( mGDALDriver );
GDALDriverH driver = GDALGetDatasetDriver( ds );
QString driverName = GDALGetDriverShortName( driver );
bool origFidAddAttempted = false;
if ( origFidAdded )
*origFidAdded = false;
if ( mGDALDriverName == QLatin1String( "ODBC" ) ) //the odbc driver does not like schema names for subset
if ( driverName == QLatin1String( "ODBC" ) ) //the odbc driver does not like schema names for subset
{
QString layerNameString = encoding->toUnicode( layerName );
int dotIndex = layerNameString.indexOf( '.' );
@ -4037,7 +4040,7 @@ OGRLayerH QgsOgrProviderUtils::setSubsetString( OGRLayerH layer, GDALDatasetH ds
else
{
QByteArray sqlPart1 = "SELECT *";
QByteArray sqlPart3 = " FROM " + quotedIdentifier( layerName, mGDALDriverName );
QByteArray sqlPart3 = " FROM " + quotedIdentifier( layerName, driverName );
if ( !subsetString.isEmpty() )
sqlPart3 += " WHERE " + encoding->fromUnicode( subsetString );
@ -4179,6 +4182,7 @@ void QgsOgrProvider::open( OpenMode mode )
if ( mOgrOrigLayer )
{
mGDALDriverName = mOgrOrigLayer->driverName();
mShareSameDatasetAmongLayers = QgsOgrProviderUtils::canDriverShareSameDatasetAmongLayers( mGDALDriverName );
QgsDebugMsg( "OGR opened using Driver " + mGDALDriverName );
@ -4493,6 +4497,8 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
auto &datasetList = iter.value();
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *ds, datasetList )
{
if ( !ds->canBeShared )
continue;
Q_ASSERT( ds->refCount > 0 );
QString layerName;
@ -4546,6 +4552,8 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
auto datasetList = iter.value();
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *ds, datasetList )
{
if ( !ds->canBeShared )
continue;
Q_ASSERT( ds->refCount > 0 );
QString layerName;
@ -4590,6 +4598,10 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
new QgsOgrProviderUtils::DatasetWithLayers;
ds->hDS = hDS;
GDALDriverH driver = GDALGetDatasetDriver( hDS );
QString driverName = GDALGetDriverShortName( driver );
ds->canBeShared = canDriverShareSameDatasetAmongLayers( driverName );
QgsOgrLayerUniquePtr layer = QgsOgrLayer::CreateForLayer(
ident, layerName, ds, hLayer );
ds->setLayers[layerName] = layer.get();
@ -4616,6 +4628,8 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
auto &datasetList = iter.value();
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *ds, datasetList )
{
if ( !ds->canBeShared )
continue;
Q_ASSERT( ds->refCount > 0 );
auto iter2 = ds->setLayers.find( layerName );
@ -4979,6 +4993,45 @@ bool QgsOgrProviderUtils::canUseOpenedDatasets( const QString &dsName )
return getLastModified( dsName ) <= iter.value();
}
QgsOgrProviderUtils::DatasetWithLayers *QgsOgrProviderUtils::createDatasetWithLayers(
const QString &dsName,
bool updateMode,
const QStringList &options,
const QString &layerName,
const DatasetIdentification &ident,
QgsOgrLayerUniquePtr &layer,
QString &errCause )
{
GDALDatasetH hDS = OpenHelper( dsName, updateMode, options );
if ( !hDS )
{
errCause = QObject::tr( "Cannot open %1." ).arg( dsName );
return nullptr;
}
sMapDSNameToLastModifiedDate[dsName] = getLastModified( dsName );
OGRLayerH hLayer = GDALDatasetGetLayerByName(
hDS, layerName.toUtf8().constData() );
if ( !hLayer )
{
errCause = QObject::tr( "Cannot find layer %1." ).arg( layerName );
QgsOgrProviderUtils::GDALCloseWrapper( hDS );
return nullptr;
}
QgsOgrProviderUtils::DatasetWithLayers *ds =
new QgsOgrProviderUtils::DatasetWithLayers;
ds->hDS = hDS;
GDALDriverH driver = GDALGetDatasetDriver( hDS );
QString driverName = GDALGetDriverShortName( driver );
ds->canBeShared = canDriverShareSameDatasetAmongLayers( driverName );
layer = QgsOgrLayer::CreateForLayer(
ident, layerName, ds, hLayer );
ds->setLayers[layerName] = layer.get();
return ds;
}
QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
bool updateMode,
@ -5018,6 +5071,8 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
auto &datasetList = iter.value();
Q_FOREACH ( QgsOgrProviderUtils::DatasetWithLayers *ds, datasetList )
{
if ( !ds->canBeShared )
continue;
Q_ASSERT( ds->refCount > 0 );
auto iter2 = ds->setLayers.find( layerName );
@ -5045,60 +5100,22 @@ QgsOgrLayerUniquePtr QgsOgrProviderUtils::getLayer( const QString &dsName,
// All existing DatasetWithLayers* already reference our layer of
// interest, so instantiate a new DatasetWithLayers*
GDALDatasetH hDS = OpenHelper( dsName, updateMode, options );
if ( !hDS )
{
errCause = QObject::tr( "Cannot open %1." ).arg( dsName );
return nullptr;
}
sMapDSNameToLastModifiedDate[dsName] = getLastModified( dsName );
OGRLayerH hLayer = GDALDatasetGetLayerByName(
hDS, layerName.toUtf8().constData() );
if ( !hLayer )
{
QgsOgrProviderUtils::GDALCloseWrapper( hDS );
errCause = QObject::tr( "Cannot find layer %1." ).arg( layerName );
return nullptr;
}
QgsOgrLayerUniquePtr layer;
QgsOgrProviderUtils::DatasetWithLayers *ds =
new QgsOgrProviderUtils::DatasetWithLayers;
createDatasetWithLayers( dsName, updateMode, options, layerName, ident, layer, errCause );
if ( !ds )
return nullptr;
datasetList.push_back( ds );
ds->hDS = hDS;
QgsOgrLayerUniquePtr layer = QgsOgrLayer::CreateForLayer(
ident, layerName, ds, hLayer );
ds->setLayers[layerName] = layer.get();
return layer;
}
GDALDatasetH hDS = OpenHelper( dsName, updateMode, options );
if ( !hDS )
{
errCause = QObject::tr( "Cannot open %1." ).arg( dsName );
return nullptr;
}
sMapDSNameToLastModifiedDate[dsName] = getLastModified( dsName );
OGRLayerH hLayer = GDALDatasetGetLayerByName(
hDS, layerName.toUtf8().constData() );
if ( !hLayer )
{
errCause = QObject::tr( "Cannot find layer %1." ).arg( layerName );
QgsOgrProviderUtils::GDALCloseWrapper( hDS );
return nullptr;
}
QgsOgrLayerUniquePtr layer;
QgsOgrProviderUtils::DatasetWithLayers *ds =
new QgsOgrProviderUtils::DatasetWithLayers;
ds->hDS = hDS;
QgsOgrLayerUniquePtr layer = QgsOgrLayer::CreateForLayer(
ident, layerName, ds, hLayer );
ds->setLayers[layerName] = layer.get();
createDatasetWithLayers( dsName, updateMode, options, layerName, ident, layer, errCause );
if ( !ds )
return nullptr;
QList<DatasetWithLayers *> datasetList;
datasetList.push_back( ds );
@ -5192,6 +5209,11 @@ void QgsOgrProviderUtils::releaseDataset( QgsOgrDataset *&ds )
ds = nullptr;
}
bool QgsOgrProviderUtils::canDriverShareSameDatasetAmongLayers( const QString &driverName )
{
return driverName != QStringLiteral( "OSM" );
}
QgsOgrDatasetSharedPtr QgsOgrDataset::create( const QgsOgrProviderUtils::DatasetIdentification &ident,
QgsOgrProviderUtils::DatasetWithLayers *ds )

View File

@ -274,6 +274,9 @@ class QgsOgrProvider : public QgsVectorDataProvider
// Friendly name of the GDAL Driver that was actually used to open the layer
QString mGDALDriverName;
//! Whether we can share the same dataset handle among different layers
bool mShareSameDatasetAmongLayers = true;
bool mValid = false;
OGRwkbGeometryType mOGRGeomType = wkbUnknown;
@ -323,7 +326,6 @@ class QgsOgrProvider : public QgsVectorDataProvider
QgsOgrTransaction *mTransaction = nullptr;
void setTransaction( QgsTransaction *transaction ) override;
};
class QgsOgrDataset;
@ -365,6 +367,7 @@ class QgsOgrProviderUtils
GDALDatasetH hDS = nullptr;
QMap<QString, QgsOgrLayer *> setLayers;
int refCount = 0;
bool canBeShared = true;
DatasetWithLayers(): mutex( QMutex::Recursive ) {}
};
@ -390,6 +393,14 @@ class QgsOgrProviderUtils
DatasetWithLayers *ds,
bool removeFromDatasetList );
static DatasetWithLayers *createDatasetWithLayers(
const QString &dsName,
bool updateMode,
const QStringList &options,
const QString &layerName,
const DatasetIdentification &ident,
QgsOgrLayerUniquePtr &layer,
QString &errCause );
public:
//! Inject credentials into the dsName (if any)
@ -460,7 +471,7 @@ class QgsOgrProviderUtils
static void invalidateCachedDatasets( const QString &dsName );
//! Returns the string to provide to QgsOgrConnPool::instance() methods
static QString connectionPoolId( const QString &dataSourceURI );
static QString connectionPoolId( const QString &dataSourceURI, bool datasetSharedAmongLayers );
//! Invalidate the cached last modified date of a dataset
static void invalidateCachedLastModifiedDate( const QString &dsName );
@ -471,6 +482,8 @@ class QgsOgrProviderUtils
//! Converts a OGR WKB type to the corresponding QGIS wkb type
static QgsWkbTypes::Type qgisTypeFromOgrType( OGRwkbGeometryType type );
//! Whether a driver can share the same dataset handle among different layers
static bool canDriverShareSameDatasetAmongLayers( const QString &driverName );
};

View File

@ -20,6 +20,7 @@ import tempfile
from osgeo import gdal, ogr # NOQA
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsApplication,
QgsRectangle,
QgsFeature, QgsFeatureRequest, QgsField, QgsSettings, QgsDataProvider,
QgsVectorDataProvider, QgsVectorLayer, QgsWkbTypes, QgsNetworkAccessManager)
from qgis.testing import start_app, unittest
@ -433,6 +434,26 @@ class PyQgsOGRProvider(unittest.TestCase):
self.assertIn('testDataItems.gpkg|layername=Layer1', children[0].uri())
self.assertIn('testDataItems.gpkg|layername=Layer2', children[1].uri())
def testOSM(self):
""" Test that opening several layers of the same OSM datasource works properly """
datasource = os.path.join(TEST_DATA_DIR, 'test.osm')
vl_points = QgsVectorLayer(datasource + "|layername=points", 'test', 'ogr')
vl_multipolygons = QgsVectorLayer(datasource + "|layername=multipolygons", 'test', 'ogr')
f = QgsFeature()
# When sharing the same dataset handle, the spatial filter of test
# points layer would apply to the other layers
iter_points = vl_points.getFeatures(QgsFeatureRequest().setFilterRect(QgsRectangle(-200, -200, -200, -200)))
self.assertFalse(iter_points.nextFeature(f))
iter_multipolygons = vl_multipolygons.getFeatures(QgsFeatureRequest())
self.assertTrue(iter_multipolygons.nextFeature(f))
self.assertTrue(iter_multipolygons.nextFeature(f))
self.assertTrue(iter_multipolygons.nextFeature(f))
self.assertFalse(iter_multipolygons.nextFeature(f))
if __name__ == '__main__':
unittest.main()

105
tests/testdata/test.osm vendored Normal file
View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<osm version="0.6" generator="hand">
<bounds minlat="49" minlon="2" maxlat="50" maxlon="3"/>
<node id="1" lat="49" lon="2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="2" lat="50" lon="3" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="3" lat="49.5" lon="3" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="name" v="Some interesting point"/>
<tag k="foo" v="bar"/>
<tag k="bar" v="baz"/>
</node>
<node id="4" lat="49" lon="3" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="5" lat="50" lon="2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="6" lat="49.1" lon="2.1" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="7" lat="49.1" lon="2.2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="8" lat="49.2" lon="2.2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<node id="9" lat="49.2" lon="2.1" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z"/>
<way id="1" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<nd ref="1"/>
<nd ref="2"/>
<tag k="highway" v="motorway"/>
<tag k="foo" v="bar"/>
</way>
<way id="2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="area" v="yes"/>
<tag k="natural" v="wood"/>
<nd ref="1"/>
<nd ref="4"/>
<nd ref="2"/>
<nd ref="5"/>
<nd ref="1"/>
</way>
<way id="3" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="area" v="yes"/>
<nd ref="6"/>
<nd ref="7"/>
<nd ref="8"/>
<nd ref="9"/>
<nd ref="6"/>
</way>
<way id="4" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="name" v="id_4"/>
<!-- all nodes missing : skipped way -->
<nd ref="600"/>
<nd ref="700"/>
<nd ref="800"/>
<nd ref="900"/>
</way>
<way id="5" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="name" v="id_5"/>
<!-- only one node : skipped way -->
<nd ref="1"/>
</way>
<way id="6" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="name" v="id_6"/>
<nd ref="1"/>
<nd ref="4"/>
<nd ref="2"/>
<nd ref="5"/>
<nd ref="900"/> <!-- unexisting node -->
<nd ref="1"/>
</way>
<!-- no tag: will not be reported in lines layer -->
<way id="7" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<nd ref="1"/>
<nd ref="2"/>
</way>
<way id="8" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<tag k="area" v="yes"/>
<tag k="name" v="standalone_polygon"/>
<nd ref="1"/>
<nd ref="4"/>
<nd ref="2"/>
<nd ref="5"/>
<nd ref="1"/>
</way>
<relation id="1" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<member type="way" ref="2" role="outer"/>
<member type="way" ref="3" role="inner"/>
<tag k="type" v="multipolygon"/>
<tag k="natural" v="forest"/>
</relation>
<relation id="2" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<member type="way" ref="2" role="outer"/>
<!-- at least, one missing way : skipped whole relation -->
<member type="way" ref="300" role="inner"/>
<tag k="type" v="multipolygon"/>
</relation>
<relation id="3" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<member type="way" ref="1" role=""/>
<tag k="type" v="route"/>
</relation>
<relation id="4" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<member type="node" ref="1" role=""/>
<member type="way" ref="1" role=""/>
<tag k="type" v="other_type"/>
</relation>
<!-- inherit tags from outer way -->
<relation id="5" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
<member type="way" ref="2" role="outer"/>
<member type="way" ref="3" role="inner"/>
<tag k="type" v="multipolygon"/>
</relation>
</osm>