File indexing completed on 2023-09-24 07:56:32
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"