File indexing completed on 2024-04-14 03:48:51

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 #include "OsmParser.h"
0007 
0008 #include "GeoDataLatLonAltBox.h"
0009 #include "GeoDataLinearRing.h"
0010 #include "GeoDataLineString.h"
0011 #include "GeoDataPolygon.h"
0012 #include "GeoDataDocument.h"
0013 #include "GeoDataPlacemark.h"
0014 #include "GeoDataMultiGeometry.h"
0015 #include "GeoDataStyle.h"
0016 #include "GeoDataStyleMap.h"
0017 #include "GeoDataLineStyle.h"
0018 #include "geodata/writer/GeoDataDocumentWriter.h"
0019 #include <GeoDataExtendedData.h>
0020 #include <GeoDataData.h>
0021 #include <geodata/handlers/kml/KmlElementDictionary.h>
0022 #include <MarbleColors.h>
0023 
0024 #include <QDebug>
0025 #include <QTime>
0026 
0027 namespace Marble
0028 {
0029 
0030 using namespace Oxygen;
0031 
0032 namespace {
0033     struct GrahamScanHelper {
0034         Coordinate coordinate;
0035         qreal direction;
0036 
0037         GrahamScanHelper( const Coordinate &coordinate_=Coordinate(), qreal direction_=0.0 )
0038             : coordinate( coordinate_ ), direction( direction_ )
0039         {
0040             // nothing to do
0041         }
0042 
0043         double turnDirection( const GrahamScanHelper &two, const GrahamScanHelper &three )
0044         {
0045             return    ( two.coordinate.lat - coordinate.lat ) * ( three.coordinate.lon - coordinate.lon )
0046                     - ( two.coordinate.lon - coordinate.lon ) * ( three.coordinate.lat - coordinate.lat );
0047         }
0048 
0049         static bool directionLessThan( const GrahamScanHelper &one, const GrahamScanHelper &two )
0050         {
0051             return one.direction < two.direction;
0052         }
0053     };
0054 }
0055 
0056 bool moreImportantAdminArea( const OsmRegion &a, const OsmRegion& b )
0057 {
0058     return a.adminLevel() < b.adminLevel();
0059 }
0060 
0061 OsmParser::OsmParser( QObject *parent ) :
0062     QObject( parent ), m_convexHull( nullptr )
0063 {
0064     m_categoryMap["tourism/camp_site"] = OsmPlacemark::AccomodationCamping;
0065     m_categoryMap["tourism/hostel"] = OsmPlacemark::AccomodationHostel;
0066     m_categoryMap["tourism/hotel"] = OsmPlacemark::AccomodationHotel;
0067     m_categoryMap["tourism/motel"] = OsmPlacemark::AccomodationMotel;
0068     //m_categoryMap["/"] = OsmPlacemark::AccomodationYouthHostel;
0069     m_categoryMap["amenity/library"] = OsmPlacemark::AmenityLibrary;
0070     m_categoryMap["amenity/college"] = OsmPlacemark::EducationCollege;
0071     m_categoryMap["amenity/school"] = OsmPlacemark::EducationSchool;
0072     m_categoryMap["amenity/university"] = OsmPlacemark::EducationUniversity;
0073     m_categoryMap["amenity/bar"] = OsmPlacemark::FoodBar;
0074     m_categoryMap["amenity/biergarten"] = OsmPlacemark::FoodBiergarten;
0075     m_categoryMap["amenity/cafe"] = OsmPlacemark::FoodCafe;
0076     m_categoryMap["amenity/fast_food"] = OsmPlacemark::FoodFastFood;
0077     m_categoryMap["amenity/pub"] = OsmPlacemark::FoodPub;
0078     m_categoryMap["amenity/restaurant"] = OsmPlacemark::FoodRestaurant;
0079     m_categoryMap["amenity/doctor"] = OsmPlacemark::HealthDoctors;
0080     m_categoryMap["amenity/doctors"] = OsmPlacemark::HealthDoctors;
0081     m_categoryMap["amenity/hospital"] = OsmPlacemark::HealthHospital;
0082     m_categoryMap["amenity/pharmacy"] = OsmPlacemark::HealthPharmacy;
0083     m_categoryMap["amenity/atm"] = OsmPlacemark::MoneyAtm;
0084     m_categoryMap["amenity/bank"] = OsmPlacemark::MoneyBank;
0085     m_categoryMap["shop/beverages"] = OsmPlacemark::ShoppingBeverages;
0086     m_categoryMap["shop/hifi"] = OsmPlacemark::ShoppingHifi;
0087     m_categoryMap["shop/supermarket"] = OsmPlacemark::ShoppingSupermarket;
0088     m_categoryMap["tourism/attraction"] = OsmPlacemark::TouristAttraction;
0089     m_categoryMap["tourism/castle"] = OsmPlacemark::TouristCastle;
0090     m_categoryMap["amenity/cinema"] = OsmPlacemark::TouristCinema;
0091     m_categoryMap["tourism/monument"] = OsmPlacemark::TouristMonument;
0092     m_categoryMap["tourism/museum"] = OsmPlacemark::TouristMuseum;
0093     m_categoryMap["historic/ruins"] = OsmPlacemark::TouristRuin;
0094     m_categoryMap["amenity/theatre"] = OsmPlacemark::TouristTheatre;
0095     m_categoryMap["tourism/theme_park"] = OsmPlacemark::TouristThemePark;
0096     m_categoryMap["tourism/viewpoint"] = OsmPlacemark::TouristViewPoint;
0097     m_categoryMap["tourism/zoo"] = OsmPlacemark::TouristZoo;
0098     m_categoryMap["aeroway/aerodrome"] = OsmPlacemark::TransportAirport;
0099     m_categoryMap["aeroway/terminal"] = OsmPlacemark::TransportAirportTerminal;
0100     m_categoryMap["amenity/bus_station"] = OsmPlacemark::TransportBusStation;
0101     m_categoryMap["highway/bus_stop"] = OsmPlacemark::TransportBusStop;
0102     m_categoryMap["highway/speed_camera"] = OsmPlacemark::TransportSpeedCamera;
0103     m_categoryMap["amenity/car_sharing"] = OsmPlacemark::TransportCarShare;
0104     m_categoryMap["amenity/car_rental"] = OsmPlacemark::TransportRentalCar;
0105     m_categoryMap["amenity/bicycle_rental"] = OsmPlacemark::TransportRentalBicycle;
0106     m_categoryMap["amenity/fuel"] = OsmPlacemark::TransportFuel;
0107     m_categoryMap["amenity/parking"] = OsmPlacemark::TransportParking;
0108     m_categoryMap["amenity/taxi"] = OsmPlacemark::TransportTaxiRank;
0109     m_categoryMap["railway/station"] = OsmPlacemark::TransportTrainStation;
0110     m_categoryMap["railway/tram_stop"] = OsmPlacemark::TransportTramStop;
0111 }
0112 
0113 void OsmParser::addWriter( Writer* writer )
0114 {
0115     m_writers.push_back( writer );
0116 }
0117 
0118 Node::operator OsmPlacemark() const
0119 {
0120     OsmPlacemark placemark;
0121     placemark.setCategory( category );
0122     placemark.setName( name.trimmed() );
0123     placemark.setHouseNumber( houseNumber.trimmed() );
0124     placemark.setLongitude( lon );
0125     placemark.setLatitude( lat );
0126     return placemark;
0127 }
0128 
0129 Node::operator Coordinate() const
0130 {
0131     Coordinate coordinate;
0132     coordinate.lon = lon;
0133     coordinate.lat = lat;
0134     return coordinate;
0135 }
0136 
0137 Way::operator OsmPlacemark() const
0138 {
0139     OsmPlacemark placemark;
0140     placemark.setCategory( category );
0141     placemark.setName( name.trimmed() );
0142     placemark.setHouseNumber( houseNumber.trimmed() );
0143     return placemark;
0144 }
0145 
0146 void Way::setPosition( const QHash<int, Coordinate> &database, OsmPlacemark &placemark ) const
0147 {
0148     if ( !nodes.isEmpty() ) {
0149         if ( nodes.first() == nodes.last() && database.contains( nodes.first() ) ) {
0150             GeoDataLinearRing ring;
0151             for( int id: nodes ) {
0152                 if ( database.contains( id ) ) {
0153                     const Coordinate &node = database[id];
0154                     GeoDataCoordinates coordinates( node.lon, node.lat, 0.0, GeoDataCoordinates::Degree );
0155                     ring << coordinates;
0156                 } else {
0157                     qDebug() << "Missing node " << id << " in database";
0158                 }
0159             }
0160 
0161             if ( !ring.isEmpty() ) {
0162                 GeoDataCoordinates center = ring.latLonAltBox().center();
0163                 placemark.setLongitude( center.longitude( GeoDataCoordinates::Degree ) );
0164                 placemark.setLatitude( center.latitude( GeoDataCoordinates::Degree ) );
0165             }
0166         } else {
0167             int id = nodes.at( nodes.size() / 2 );
0168             if ( database.contains( id ) ) {
0169                 const Coordinate &node = database[id];
0170                 placemark.setLongitude( node.lon );
0171                 placemark.setLatitude( node.lat );
0172             }
0173         }
0174     }
0175 }
0176 
0177 void Way::setRegion( const QHash<int, Node> &database,  const OsmRegionTree & tree, QList<OsmOsmRegion> & osmOsmRegions, OsmPlacemark &placemark ) const
0178 {
0179     if ( !city.isEmpty() ) {
0180         for( const OsmOsmRegion & region: osmOsmRegions ) {
0181             if ( region.region.name() == city ) {
0182                 placemark.setRegionId( region.region.identifier() );
0183                 return;
0184             }
0185         }
0186 
0187         for( const Node & node: database ) {
0188             if ( node.category >= OsmPlacemark::PlacesRegion &&
0189                     node.category <= OsmPlacemark::PlacesIsland &&
0190                     node.name == city ) {
0191                 qDebug() << "Creating a new implicit region from " << node.name << " at " << node.lon << "," << node.lat;
0192                 OsmOsmRegion region;
0193                 region.region.setName( city );
0194                 region.region.setLongitude( node.lon );
0195                 region.region.setLatitude( node.lat );
0196                 placemark.setRegionId( region.region.identifier() );
0197                 osmOsmRegions.push_back( region );
0198                 return;
0199             }
0200         }
0201 
0202         qDebug() << "Unable to locate city " << city << ", setting it up without coordinates";
0203         OsmOsmRegion region;
0204         region.region.setName( city );
0205         placemark.setRegionId( region.region.identifier() );
0206         osmOsmRegions.push_back( region );
0207         return;
0208     }
0209 
0210     GeoDataCoordinates position( placemark.longitude(), placemark.latitude(), 0.0, GeoDataCoordinates::Degree );
0211     placemark.setRegionId( tree.smallestRegionId( position ) );
0212 }
0213 
0214 void OsmParser::read( const QFileInfo &content, const QString &areaName )
0215 {
0216     QTime timer;
0217     timer.start();
0218 
0219     m_nodes.clear();
0220     m_ways.clear();
0221     m_relations.clear();
0222 
0223     m_placemarks.clear();
0224     m_osmOsmRegions.clear();
0225 
0226     int pass = 0;
0227     bool needAnotherPass = false;
0228     do {
0229         qWarning() << "Step 1." << pass << ": Parsing input file " << content.fileName();
0230         parse( content, pass++, needAnotherPass );
0231     }
0232     while ( needAnotherPass );
0233 
0234     qWarning() << "Step 2: " << m_coordinates.size() << "coordinates."
0235                << "Now extracting regions from" << m_relations.size() << "relations";
0236 
0237     QHash<int, Relation>::iterator itpoint = m_relations.begin();
0238     QHash<int, Relation>::iterator const endpoint = m_relations.end();
0239     for(; itpoint != endpoint; ++itpoint ) {
0240         if ( itpoint.value().isAdministrativeBoundary /*&& relation.isMultipolygon*/ ) {
0241             importMultipolygon( itpoint.value() );
0242             if ( !itpoint.value().relations.isEmpty() ) {
0243                 qDebug() << "Ignoring relations inside the relation " << itpoint.value().name;
0244             }
0245         }
0246     }
0247 
0248     m_relations.clear();
0249 
0250     for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) {
0251         OsmOsmRegion &osmOsmRegion = m_osmOsmRegions[i];
0252         GeoDataCoordinates center = osmOsmRegion.region.geometry().latLonAltBox().center();
0253         osmOsmRegion.region.setLongitude( center.longitude( GeoDataCoordinates::Degree ) );
0254         osmOsmRegion.region.setLatitude( center.latitude( GeoDataCoordinates::Degree ) );
0255     }
0256 
0257     qWarning() << "Step 3: Creating region hierarchies from" << m_osmOsmRegions.size() << "administrative boundaries";
0258 
0259     QMultiMap<int,int> sortedRegions;
0260     for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) {
0261         sortedRegions.insert( m_osmOsmRegions[i].region.adminLevel(), i );
0262     }
0263 
0264     for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) {
0265         GeoDataLinearRing const & ring = m_osmOsmRegions[i].region.geometry().outerBoundary();
0266         OsmOsmRegion* parent = nullptr;
0267         qDebug() << "Examining admin region " << i << " of " << m_osmOsmRegions.count();
0268         for ( int level=m_osmOsmRegions[i].region.adminLevel()-1; level >= 0 && parent == nullptr; --level ) {
0269             QList<int> candidates = sortedRegions.values( level );
0270             qDebug() << "Examining " << candidates.count() << "admin regions on level" << level;
0271             for( int j: candidates ) {
0272                 GeoDataLinearRing const & outer = m_osmOsmRegions[j].region.geometry().outerBoundary();
0273                 if ( contains<GeoDataLinearRing, GeoDataLinearRing>( outer, ring ) ) {
0274                     if ( parent == nullptr || contains<GeoDataLinearRing, GeoDataLinearRing>( parent->region.geometry().outerBoundary(), outer ) ) {
0275                         qDebug() << "Parent found: " << m_osmOsmRegions[i].region.name() << ", level " << m_osmOsmRegions[i].region.adminLevel()
0276                                    << "is a child of " << m_osmOsmRegions[j].region.name() << ", level " << m_osmOsmRegions[j].region.adminLevel();
0277                         parent = &m_osmOsmRegions[j];
0278                         break;
0279                     }
0280                 }
0281             }
0282         }
0283 
0284         m_osmOsmRegions[i].parent = parent;
0285     }
0286 
0287     for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) {
0288         int const parent = m_osmOsmRegions[i].parent ? m_osmOsmRegions[i].parent->region.identifier() : 0;
0289         m_osmOsmRegions[i].region.setParentIdentifier( parent );
0290     }
0291 
0292     OsmRegion mainArea;
0293     mainArea.setIdentifier( 0 );
0294     mainArea.setName( areaName );
0295     mainArea.setAdminLevel( 1 );
0296     QPair<float, float> minLon( -180.0, 180.0 ), minLat( -90.0, 90.0 );
0297     for( const Coordinate & node: m_coordinates ) {
0298         minLon.first  = qMin( node.lon, minLon.first );
0299         minLon.second = qMax( node.lon, minLon.second );
0300         minLat.first  = qMin( node.lat, minLat.first );
0301         minLat.second = qMax( node.lat, minLat.second );
0302     }
0303     GeoDataLatLonBox center( minLat.second, minLat.first,
0304                              minLon.second, minLon.first );
0305     mainArea.setLongitude( center.center().longitude( GeoDataCoordinates::Degree ) );
0306     mainArea.setLatitude( center.center().latitude( GeoDataCoordinates::Degree ) );
0307 
0308     QList<OsmRegion> regions;
0309     for( const OsmOsmRegion & region: m_osmOsmRegions ) {
0310         regions << region.region;
0311     }
0312 
0313     std::sort( regions.begin(), regions.end(), moreImportantAdminArea );
0314     OsmRegionTree regionTree( mainArea );
0315     regionTree.append( regions );
0316     Q_ASSERT( regions.isEmpty() );
0317     int left = 0;
0318     regionTree.traverse( left );
0319 
0320     qWarning() << "Step 4: Creating placemarks from" << m_nodes.size() << "nodes";
0321 
0322     for( const Node & node: m_nodes ) {
0323         if ( node.save ) {
0324             OsmPlacemark placemark = node;
0325             GeoDataCoordinates position( node.lon, node.lat, 0.0, GeoDataCoordinates::Degree );
0326             placemark.setRegionId( regionTree.smallestRegionId( position ) );
0327 
0328             if ( !node.name.isEmpty() ) {
0329                 placemark.setHouseNumber( QString() );
0330                 m_placemarks.push_back( placemark );
0331             }
0332 
0333             if ( !node.street.isEmpty() && node.name != node.street ) {
0334                 placemark.setCategory( OsmPlacemark::Address );
0335                 placemark.setName( node.street.trimmed() );
0336                 placemark.setHouseNumber( node.houseNumber.trimmed() );
0337                 m_placemarks.push_back( placemark );
0338             }
0339         }
0340     }
0341 
0342     qWarning() << "Step 5: Creating placemarks from" << m_ways.size() << "ways";
0343     QMultiMap<QString, Way> waysByName;
0344     for ( const Way & way: m_ways ) {
0345         if ( way.save ) {
0346             if ( !way.name.isEmpty() && !way.nodes.isEmpty() ) {
0347                 waysByName.insert( way.name, way );
0348             }
0349 
0350             if ( !way.street.isEmpty() && way.name != way.street && !way.nodes.isEmpty() ) {
0351                 waysByName.insert( way.street, way );
0352             }
0353         } else {
0354             ++m_statistic.uselessWays;
0355         }
0356     }
0357 
0358     QSet<QString> keys = QSet<QString>::fromList( waysByName.keys() );
0359     for( const QString & key: keys ) {
0360         QList<QList<Way> > merged = merge( waysByName.values( key ) );
0361         for( const QList<Way> & ways: merged ) {
0362             Q_ASSERT( !ways.isEmpty() );
0363             OsmPlacemark placemark = ways.first();
0364             ways.first().setPosition( m_coordinates, placemark );
0365             ways.first().setRegion( m_nodes, regionTree, m_osmOsmRegions, placemark );
0366 
0367             if ( placemark.category() != OsmPlacemark::Address && !ways.first().name.isEmpty() ) {
0368                 placemark.setHouseNumber( QString() );
0369                 m_placemarks.push_back( placemark );
0370             }
0371 
0372             if ( !ways.first().isBuilding || !ways.first().houseNumber.isEmpty() ) {
0373                 placemark.setCategory( OsmPlacemark::Address );
0374                 QString name = ways.first().street.isEmpty() ? ways.first().name : ways.first().street;
0375                 if ( !name.isEmpty() ) {
0376                     placemark.setName( name.trimmed() );
0377                     placemark.setHouseNumber( ways.first().houseNumber.trimmed() );
0378                     m_placemarks.push_back( placemark );
0379                 }
0380             }
0381         }
0382     }
0383 
0384     m_convexHull = convexHull();
0385     m_coordinates.clear();
0386     m_nodes.clear();
0387     m_ways.clear();
0388 
0389     Q_ASSERT( regions.isEmpty() );
0390     for( const OsmOsmRegion & region: m_osmOsmRegions ) {
0391         regions << region.region;
0392     }
0393 
0394     std::sort( regions.begin(), regions.end(), moreImportantAdminArea );
0395     regionTree = OsmRegionTree( mainArea );
0396     regionTree.append( regions );
0397     Q_ASSERT( regions.isEmpty() );
0398     left = 0;
0399     regionTree.traverse( left );
0400     regions = regionTree;
0401 
0402     qWarning() << "Step 6: " << m_statistic.mergedWays << " ways merged," << m_statistic.uselessWays << "useless ways."
0403                << "Now serializing" << regions.size() << "regions";
0404     for( const OsmRegion & region: regions ) {
0405         for( Writer * writer: m_writers ) {
0406             writer->addOsmRegion( region );
0407         }
0408     }
0409 
0410     qWarning() << "Step 7: Serializing" << m_placemarks.size() << "placemarks";
0411     for( const OsmPlacemark & placemark: m_placemarks ) {
0412         for( Writer * writer: m_writers ) {
0413             Q_ASSERT( !placemark.name().isEmpty() );
0414             writer->addOsmPlacemark( placemark );
0415         }
0416     }
0417 
0418     qWarning() << "Step 8: There is no step 8. Done after " << timer.elapsed() / 1000 << "s.";
0419     //writeOutlineKml( areaName );
0420 }
0421 
0422 QList< QList<Way> > OsmParser::merge( const QList<Way> &ways ) const
0423 {
0424     QList<WayMerger> mergers;
0425 
0426     for( const Way & way: ways ) {
0427         mergers << WayMerger( way );
0428     }
0429 
0430     bool moved = false;
0431     do {
0432         moved = false;
0433         for( int i = 0; i < mergers.size(); ++i ) {
0434             for ( int j = i + 1; j < mergers.size(); ++j ) {
0435                 if ( mergers[i].compatible( mergers[j] ) ) {
0436                     mergers[i].merge( mergers[j] );
0437                     moved = true;
0438                     mergers.removeAt( j );
0439                 }
0440             }
0441         }
0442     } while ( moved );
0443 
0444     QList< QList<Way> > result;
0445     for( const WayMerger & merger: mergers ) {
0446         result << merger.ways;
0447     }
0448     m_statistic.mergedWays += ( ways.size() - result.size() );
0449 
0450     return result;
0451 }
0452 
0453 void OsmParser::importMultipolygon( const Relation &relation )
0454 {
0455     /** @todo: import nodes? What are they used for? */
0456     typedef QPair<int, RelationRole> RelationPair;
0457     QVector<GeoDataLineString> outer;
0458     QVector<GeoDataLineString> inner;
0459     for( const RelationPair & pair: relation.ways ) {
0460         if ( pair.second == Outer ) {
0461             importWay( outer, pair.first );
0462         } else if ( pair.second == Inner ) {
0463             importWay( inner, pair.first );
0464         } else {
0465             qDebug() << "Ignoring way " << pair.first << " with unknown relation role.";
0466         }
0467     }
0468 
0469     for( const GeoDataLineString & string: outer ) {
0470         if ( string.isEmpty() || !( string.first() == string.last() ) ) {
0471             qDebug() << "Ignoring open polygon in relation " << relation.name << ". Check data.";
0472             continue;
0473         }
0474 
0475         GeoDataPolygon polygon;
0476         polygon.setOuterBoundary(GeoDataLinearRing(string));
0477         Q_ASSERT( polygon.outerBoundary().size() > 0 );
0478 
0479         for( const GeoDataLineString & hole: inner ) {
0480             if ( contains<GeoDataLinearRing, GeoDataLineString>( polygon.outerBoundary(), hole ) ) {
0481                 polygon.appendInnerBoundary(GeoDataLinearRing(hole));
0482             }
0483         }
0484 
0485         OsmOsmRegion region;
0486         region.region.setName( relation.name );
0487         region.region.setGeometry( polygon );
0488         region.region.setAdminLevel( relation.adminLevel );
0489         qDebug() << "Adding administrative region " << relation.name;
0490         m_osmOsmRegions.push_back( region );
0491     }
0492 }
0493 
0494 void OsmParser::importWay( QVector<GeoDataLineString> &ways, int id )
0495 {
0496     if ( !m_ways.contains( id ) ) {
0497         qDebug() << "Skipping unknown way " << id << ". Check data.";
0498         return;
0499     }
0500 
0501     GeoDataLineString way;
0502     for( int node: m_ways[id].nodes ) {
0503         if ( !m_coordinates.contains( node ) ) {
0504             qDebug() << "Skipping unknown node " << node << ". Check data.";
0505         } else {
0506             const Coordinate &nd = m_coordinates[node];
0507             GeoDataCoordinates coordinates( nd.lon, nd.lat, 0.0, GeoDataCoordinates::Degree );
0508             way << coordinates;
0509         }
0510     }
0511 
0512     QList<int> remove;
0513     do {
0514         remove.clear();
0515         for ( int i = 0; i < ways.size(); ++i ) {
0516             const GeoDataLineString &existing = ways[i];
0517             if ( existing.first() == way.first() ) {
0518                 way = reverse( way ) << existing;
0519                 remove.push_front( i );
0520             } else if ( existing.last() == way.first() ) {
0521                 GeoDataLineString copy = existing;
0522                 way = copy << way;
0523                 remove.push_front( i );
0524             } else if ( existing.first() == way.last() ) {
0525                 way << existing;
0526                 remove.push_front( i );
0527             } else if ( existing.last() == way.last() ) {
0528                 way << reverse( existing );
0529                 remove.push_front( i );
0530             }
0531         }
0532 
0533         for( int key: remove ) {
0534             ways.remove( key );
0535         }
0536     } while ( !remove.isEmpty() );
0537 
0538     ways.push_back( way );
0539 }
0540 
0541 GeoDataLineString OsmParser::reverse( const GeoDataLineString & string )
0542 {
0543     GeoDataLineString result;
0544     for ( int i = string.size() - 1; i >= 0; --i ) {
0545         result << string[i];
0546     }
0547     return result;
0548 }
0549 
0550 bool OsmParser::shouldSave( ElementType /*type*/, const QString &key, const QString &value )
0551 {
0552     using Dictionary = QList<QString>;
0553     static QHash<QString, Dictionary> interestingTags;
0554     if ( interestingTags.isEmpty() ) {
0555         Dictionary highways;
0556         highways << "primary" << "secondary" << "tertiary";
0557         highways << "residential" << "unclassified" << "road";
0558         highways << "living_street" << "service" << "track";
0559         highways << "bus_stop" << "platform" << "speed_camera";
0560         interestingTags["highway"] = highways;
0561 
0562         Dictionary aeroways;
0563         aeroways << "aerodrome" << "terminal";
0564         interestingTags["aeroway"] = aeroways;
0565 
0566         interestingTags["aerialway"] = Dictionary() << "station";
0567 
0568         Dictionary leisures;
0569         leisures << "sports_centre" << "stadium" << "pitch";
0570         leisures << "park" << "dance";
0571         interestingTags["leisure"] = leisures;
0572 
0573         Dictionary amenities;
0574         amenities << "restaurant" << "food_court" << "fast_food";
0575         amenities << "pub" << "bar" << "cafe";
0576         amenities << "biergarten" << "kindergarten" << "school";
0577         amenities << "college" << "university" << "library";
0578         amenities << "ferry_terminal" << "bus_station" << "car_rental";
0579         amenities << "car_sharing" << "fuel" << "parking";
0580         amenities << "bank" << "pharmacy" << "hospital";
0581         amenities << "cinema" << "nightclub" << "theatre";
0582         amenities << "taxi" << "bicycle_rental" << "atm";
0583         interestingTags["amenity"] = amenities;
0584 
0585         Dictionary shops;
0586         shops << "beverages" << "supermarket" << "hifi";
0587         interestingTags["shop"] = shops;
0588 
0589         Dictionary tourism;
0590         tourism << "attraction" << "camp_site" << "caravan_site";
0591         tourism << "chalet" << "chalet" << "hostel";
0592         tourism << "hotel" << "motel" << "museum";
0593         tourism << "theme_park" << "viewpoint" << "zoo";
0594         interestingTags["tourism"] = tourism;
0595 
0596         Dictionary historic;
0597         historic << "castle" << "fort" << "monument";
0598         historic << "ruins";
0599         interestingTags["historic"] = historic;
0600 
0601         Dictionary railway;
0602         railway << "station" << "tram_stop";
0603         interestingTags["railway"] = railway;
0604 
0605         Dictionary places;
0606         places << "region" << "county" << "city";
0607         places << "town" << "village" << "hamlet";
0608         places << "isolated_dwelling" << "suburb" << "locality";
0609         places << "island";
0610         interestingTags["place"] = places;
0611     }
0612 
0613     return interestingTags.contains( key ) &&
0614            interestingTags[key].contains( value );
0615 }
0616 
0617 void OsmParser::setCategory( Element &element, const QString &key, const QString &value )
0618 {
0619     QString const term = key + QLatin1Char('/') + value;
0620     if ( m_categoryMap.contains( term ) ) {
0621         if ( element.category != OsmPlacemark::UnknownCategory ) {
0622             qDebug() << "Overwriting category " << element.category << " with " << m_categoryMap[term] << " for " << element.name;
0623         }
0624         element.category = m_categoryMap[term];
0625     }
0626 }
0627 
0628 // From https://en.wikipedia.org/wiki/Graham_scan#Pseudocode
0629 GeoDataLinearRing* OsmParser::convexHull() const
0630 {
0631     Q_ASSERT(m_coordinates.size()>2);
0632     QList<Coordinate> coordinates = m_coordinates.values();
0633 
0634     QVector<GrahamScanHelper> points;
0635     points.reserve( coordinates.size()+1 );
0636     Coordinate start = coordinates.first();
0637     int startPos = 0;
0638     for ( int i=0; i<coordinates.size(); ++i ) {
0639         if ( coordinates[i].lon < start.lon ) {
0640             start = coordinates[i];
0641             startPos = i;
0642         }
0643         points << coordinates[i];
0644     }
0645 
0646     int const n = points.size();
0647     Q_ASSERT( n == coordinates.size() );
0648     Q_ASSERT( n>2 );
0649     qSwap( points[1], points[startPos] );
0650 
0651     Q_ASSERT( start.lat != 360.0 );
0652 
0653     for ( int i=0; i<points.size(); ++i ) {
0654         points[i].direction = atan2( start.lon - points[i].coordinate.lon,
0655                                    start.lat - points[i].coordinate.lat );
0656     }
0657 
0658     std::sort( points.begin(), points.end(), GrahamScanHelper::directionLessThan );
0659     points << points.first();
0660 
0661     int m = 2;
0662     for ( int i=3; i<=n; ++i ) {
0663         while ( points[m-1].turnDirection( points[m], points[i] ) <= 0 ) {
0664             if ( m == 2 ) {
0665                 qSwap( points[m], points[i] );
0666                 ++i;
0667             } else {
0668                 --m;
0669             }
0670 
0671             Q_ASSERT( n+1 == points.size() );
0672             Q_ASSERT( m > 0 );
0673             Q_ASSERT( m <= n );
0674             Q_ASSERT( i >= 0 );
0675             Q_ASSERT( i <= n );
0676         }
0677 
0678         ++m;
0679         qSwap( points[m], points[i] );
0680     }
0681 
0682     GeoDataLinearRing* ring = new GeoDataLinearRing;
0683     for ( int i=1; i<=m; ++i ) {
0684         ring->append(GeoDataCoordinates(points[i].coordinate.lon, points[i].coordinate.lat, 0.0, GeoDataCoordinates::Degree));
0685     }
0686 
0687     return ring;
0688 }
0689 
0690 QColor OsmParser::randomColor() const
0691 {
0692     QVector<QColor> colors = QVector<QColor>() << aluminumGray4 << brickRed4;
0693     colors << woodBrown4 << forestGreen4 << hotOrange4;
0694     colors << seaBlue2 << skyBlue4 << sunYellow6;
0695     return colors.at( qrand() % colors.size() );
0696 }
0697 
0698 void OsmParser::writeKml( const QString &area, const QString &version, const QString &date, const QString &transport, const QString &payload, const QString &filename ) const
0699 {
0700     GeoDataDocument* document = new GeoDataDocument;
0701 
0702     //for( const OsmOsmRegion & region: m_osmOsmRegions ) {
0703     GeoDataPlacemark* placemark = new GeoDataPlacemark;
0704     placemark->setName( area );
0705     if ( !version.isEmpty() ) {
0706         placemark->extendedData().addValue( GeoDataData( "version", version ) );
0707     }
0708     if ( !date.isEmpty() ) {
0709         placemark->extendedData().addValue( GeoDataData( "date", date ) );
0710     }
0711     if ( !transport.isEmpty() ) {
0712         placemark->extendedData().addValue( GeoDataData( "transport", transport ) );
0713     }
0714     if ( !payload.isEmpty() ) {
0715         placemark->extendedData().addValue( GeoDataData( "payload", payload ) );
0716     }
0717 
0718     GeoDataStyle::Ptr style(new GeoDataStyle);
0719     GeoDataLineStyle lineStyle;
0720     QColor color = randomColor();
0721     color.setAlpha( 200 );
0722     lineStyle.setColor( color );
0723     lineStyle.setWidth( 4 );
0724     style->setLineStyle( lineStyle );
0725     style->setId(color.name().replace(QLatin1Char('#'), QLatin1Char('f')));
0726 
0727     GeoDataStyleMap styleMap;
0728     styleMap.setId(color.name().replace(QLatin1Char('#'), QLatin1Char('f')));
0729     styleMap.insert("normal", QLatin1Char('#') + style->id());
0730     document->addStyle( style );
0731 
0732     placemark->setStyleUrl(QLatin1Char('#') + styleMap.id());
0733 
0734     //placemark->setGeometry( new GeoDataLinearRing( region.region.geometry().outerBoundary() ) );
0735     GeoDataMultiGeometry *geometry = new GeoDataMultiGeometry;
0736     geometry->append( m_convexHull );
0737     placemark->setGeometry( geometry );
0738 
0739     document->append( placemark );
0740     document->addStyleMap( styleMap );
0741 //    }
0742 
0743     if (!GeoDataDocumentWriter::write(filename, *document)) {
0744         qCritical() << "Can not write to " << filename;
0745     }
0746 }
0747 
0748 Coordinate::Coordinate(float lon_, float lat_) : lon(lon_), lat(lat_)
0749 {
0750     // nothing to do
0751 }
0752 
0753 }
0754 
0755 #include "moc_OsmParser.cpp"