diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index b1af498d3..94efa888c 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -147,6 +147,8 @@ set(SRC osm_o5m_source.hpp osm_source.cpp osm_xml_source.hpp + panoramax_collector.cpp + panoramax_collector.hpp place_processor.cpp place_processor.hpp platform_helpers.cpp diff --git a/generator/final_processor_country.cpp b/generator/final_processor_country.cpp index e35960c12..dbc078e30 100644 --- a/generator/final_processor_country.cpp +++ b/generator/final_processor_country.cpp @@ -10,6 +10,7 @@ #include "generator/mini_roundabout_transformer.hpp" #include "generator/node_mixer.hpp" #include "generator/osm2type.hpp" +#include "generator/panoramax_collector.hpp" #include "generator/region_meta.hpp" #include "routing/speed_camera_prohibition.hpp" @@ -68,6 +69,9 @@ void CountryFinalProcessor::Process() if (!m_isolinesPath.empty()) AddIsolines(); + LOG(LINFO, ("Enriching with Panoramax data...")); + EnrichPanoramax(); + // DropProhibitedSpeedCameras(); LOG(LINFO, ("Processing building parts...")); ProcessBuildingParts(); @@ -293,6 +297,50 @@ void CountryFinalProcessor::AddAddresses() LOG(LINFO, ("Total addresses:", totalStats)); } +void CountryFinalProcessor::EnrichPanoramax() +{ + if (m_panoramaxFilename.empty() || !Platform::IsFileExistsByFullPath(m_panoramaxFilename)) + { + LOG(LINFO, ("Panoramax data not available, skipping enrichment")); + return; + } + + LOG(LINFO, ("Enriching roads with Panoramax imagery data from:", m_panoramaxFilename)); + + // Load Panoramax imagery data + PanoramaxCollector collector; + if (!collector.LoadImageryData(m_panoramaxFilename, "")) + { + LOG(LWARNING, ("Failed to load Panoramax data")); + return; + } + + // Enrich roads in each MWM + ForEachMwmTmp(m_temporaryMwmPath, [&](auto const & name, auto const & path) + { + if (!IsCountry(name)) + return; + + LOG(LINFO, ("Enriching Panoramax for:", name)); + + std::vector features; + ForEachFeatureRawFormat( + path, [&](FeatureBuilder && fb, uint64_t) { + collector.EnrichRoad(fb); + features.emplace_back(std::move(fb)); + }); + + // Rewrite the file with enriched features + FeatureBuilderWriter writer(path, FileWriter::Op::OP_WRITE_TRUNCATE); + for (auto & fb : features) + writer.Write(fb); + + LOG(LINFO, (name, "done")); + }); + + LOG(LINFO, ("Panoramax enrichment complete:", DebugPrint(collector.GetStats()))); +} + void CountryFinalProcessor::ProcessCoastline() { /// @todo We can remove MinSize at all. diff --git a/generator/final_processor_country.hpp b/generator/final_processor_country.hpp index 7f6b270ea..f0e239f38 100644 --- a/generator/final_processor_country.hpp +++ b/generator/final_processor_country.hpp @@ -26,6 +26,7 @@ public: void SetAddressesDir(std::string const & dir) { m_addressPath = dir; } void SetCityBoundariesFiles(std::string const & collectorFile) { m_boundariesCollectorFile = collectorFile; } + void SetPanoramaxFile(std::string const & filename) { m_panoramaxFilename = filename; } // FinalProcessorIntermediateMwmInterface overrides: void Process() override; @@ -39,6 +40,7 @@ private: void AddFakeNodes(); void AddIsolines(); void AddAddresses(); + void EnrichPanoramax(); void DropProhibitedSpeedCameras(); // void Finish(); @@ -54,6 +56,7 @@ private: std::string m_fakeNodesFilename; std::string m_miniRoundaboutsFilename; std::string m_addrInterpolFilename; + std::string m_panoramaxFilename; std::string m_hierarchySrcFilename; diff --git a/generator/generate_info.hpp b/generator/generate_info.hpp index 8400352d8..830b8d631 100644 --- a/generator/generate_info.hpp +++ b/generator/generate_info.hpp @@ -42,6 +42,9 @@ struct GenerateInfo // External folders with additional preprocessed data (isolines, addresses). std::string m_isolinesDir, m_addressesDir; + // Panoramax imagery data file + std::string m_panoramaxFilename; + // Current generated file name if --output option is defined. std::string m_fileName; diff --git a/generator/generator_tool/generator_tool.cpp b/generator/generator_tool/generator_tool.cpp index c0dcc8cef..e0827ec69 100644 --- a/generator/generator_tool/generator_tool.cpp +++ b/generator/generator_tool/generator_tool.cpp @@ -107,6 +107,7 @@ DEFINE_string(nodes_list_path, "", DEFINE_bool(generate_isolines_info, false, "Generate the isolines info section"); DEFINE_string(isolines_path, "", "Path to isolines directory. If set, adds isolines linear features."); DEFINE_string(addresses_path, "", "Path to addresses directory. If set, adds addr:interpolation features."); +DEFINE_string(panoramax_file, "", "Path to Panoramax imagery coords file. If set, enriches roads with street-level imagery availability."); // Routing. DEFINE_bool(make_routing_index, false, "Make sections with the routing information."); @@ -243,6 +244,7 @@ MAIN_WITH_ERROR_HANDLING([](int argc, char ** argv) genInfo.m_complexHierarchyFilename = FLAGS_complex_hierarchy_data; genInfo.m_isolinesDir = FLAGS_isolines_path; genInfo.m_addressesDir = FLAGS_addresses_path; + genInfo.m_panoramaxFilename = FLAGS_panoramax_file; // Use merged style. GetStyleReader().SetCurrentStyle(MapStyleMerged); diff --git a/generator/panoramax_collector.cpp b/generator/panoramax_collector.cpp new file mode 100644 index 000000000..4a5464a85 --- /dev/null +++ b/generator/panoramax_collector.cpp @@ -0,0 +1,157 @@ +#include "generator/panoramax_collector.hpp" + +#include "indexer/feature_meta.hpp" +#include "indexer/ftypes_matcher.hpp" + +#include "geometry/mercator.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include +#include +#include + +namespace generator +{ + +PanoramaxCollector::PanoramaxCollector() = default; + +bool PanoramaxCollector::LoadImageryData(std::string const & coordsFilePath, std::string const & /* tilesDir */) +{ + LOG(LINFO, ("Loading Panoramax imagery from:", coordsFilePath)); + + std::ifstream file(coordsFilePath, std::ios::binary); + if (!file.is_open()) + { + LOG(LWARNING, ("Failed to open Panoramax coords file:", coordsFilePath)); + return false; + } + + // Read header + uint32_t version; + uint64_t count; + + file.read(reinterpret_cast(&version), sizeof(version)); + file.read(reinterpret_cast(&count), sizeof(count)); + + if (version != 1) + { + LOG(LWARNING, ("Unsupported Panoramax coords file version:", version)); + return false; + } + + LOG(LINFO, ("Loading", count, "Panoramax imagery points...")); + + // Read coordinates and build spatial index + std::vector points; + points.reserve(count); + + for (uint64_t i = 0; i < count; ++i) + { + double lat, lon; + file.read(reinterpret_cast(&lat), sizeof(lat)); + file.read(reinterpret_cast(&lon), sizeof(lon)); + + // Convert to Mercator coordinates + m2::PointD mercPt = mercator::FromLatLon(lat, lon); + points.emplace_back(mercPt); + + if ((i + 1) % 500000 == 0) + LOG(LINFO, ("Loaded", i + 1, "/", count, "points")); + } + + if (!file) + { + LOG(LWARNING, ("Error reading Panoramax coords file")); + return false; + } + + file.close(); + + LOG(LINFO, ("Building spatial index for", points.size(), "points...")); + + // Build spatial index + for (auto const & pt : points) + m_imageryTree.Add(pt); + + m_stats.m_totalImageryPoints = static_cast(points.size()); + + LOG(LINFO, ("Panoramax data loaded:", m_stats.m_totalImageryPoints, "imagery points")); + return true; +} + +bool PanoramaxCollector::EnrichRoad(feature::FeatureBuilder & fb) +{ + // Only process roads + static auto const & isRoad = ftypes::IsWayChecker::Instance(); + if (!isRoad(fb.GetTypes())) + return false; + + m_stats.m_totalRoads++; + + // Check if road already has Panoramax flag + if (fb.GetMetadata().Has(feature::Metadata::FMD_PANORAMAX)) + return false; + + // Get road geometry + std::vector roadPoints; + fb.ForEachPoint([&roadPoints](m2::PointD const & pt) { + roadPoints.push_back(pt); + }); + + if (roadPoints.empty()) + return false; + + // Calculate road bounding box + m2::RectD roadBBox; + for (auto const & pt : roadPoints) + roadBBox.Add(pt); + + // Expand bounding box by threshold distance in Mercator units + auto const center = roadBBox.Center(); + auto const inflateRect = mercator::RectByCenterXYAndSizeInMeters(center, kDistanceThresholdM); + roadBBox.Inflate(inflateRect.SizeX() / 2.0, inflateRect.SizeY() / 2.0); + + // Query imagery tree for nearby points + bool hasImagery = false; + + m_imageryTree.ForEachInRect(roadBBox, [&](ImageryPoint const & imgPt) { + if (hasImagery) + return; // Already found, early exit + + // Check distance to each road point + for (auto const & roadPt : roadPoints) + { + double const distM = mercator::DistanceOnEarth(roadPt, imgPt.m_point); + if (distM <= kDistanceThresholdM) + { + hasImagery = true; + return; + } + } + }); + + if (hasImagery) + { + fb.GetMetadata().Set(feature::Metadata::FMD_PANORAMAX, "yes"); + m_stats.m_roadsWithImagery++; + return true; + } + + return false; +} + +std::string DebugPrint(PanoramaxCollector::Stats const & s) +{ + std::ostringstream out; + out << "PanoramaxCollector::Stats {" + << " totalRoads: " << s.m_totalRoads + << ", roadsWithImagery: " << s.m_roadsWithImagery + << " (" << (s.m_totalRoads > 0 ? (s.m_roadsWithImagery * 100.0 / s.m_totalRoads) : 0) << "%)" + << ", totalImageryPoints: " << s.m_totalImageryPoints + << " }"; + return out.str(); +} + +} // namespace generator diff --git a/generator/panoramax_collector.hpp b/generator/panoramax_collector.hpp new file mode 100644 index 000000000..fff349389 --- /dev/null +++ b/generator/panoramax_collector.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "generator/feature_builder.hpp" + +#include "geometry/point2d.hpp" +#include "geometry/tree4d.hpp" + +#include "base/geo_object_id.hpp" + +#include +#include + +namespace generator +{ +/// Enriches road features with Panoramax street-level imagery availability flag. +/// Loads Panoramax imagery coordinates from tile index and marks roads within threshold distance. +class PanoramaxCollector +{ +public: + // Distance threshold for matching roads to imagery (meters) + static double constexpr kDistanceThresholdM = 20.0; + + /// ImageryPoint represents a single point where street-level imagery exists + struct ImageryPoint + { + m2::PointD m_point; // Mercator coordinates + + ImageryPoint() = default; + explicit ImageryPoint(m2::PointD const & pt) : m_point(pt) {} + + m2::RectD GetLimitRect() const { return m2::RectD(m_point, m_point); } + }; + + /// Statistics for debugging/logging + struct Stats + { + uint32_t m_totalRoads = 0; + uint32_t m_roadsWithImagery = 0; + uint32_t m_totalImageryPoints = 0; + uint32_t m_tilesProcessed = 0; + + friend std::string DebugPrint(Stats const & s); + }; + + PanoramaxCollector(); + + /// Load Panoramax imagery data from tile directory + /// @param tileIndexPath Path to tile_index.json + /// @param tilesDir Directory containing .mvt tiles + /// @return true if data loaded successfully + bool LoadImageryData(std::string const & tileIndexPath, std::string const & tilesDir); + + /// Check if a road has nearby imagery and enrich it + /// @param fb FeatureBuilder for a road segment + /// @return true if imagery was found within threshold + bool EnrichRoad(feature::FeatureBuilder & fb); + + Stats const & GetStats() const { return m_stats; } + +private: + /// Spatial index of imagery points + m4::Tree m_imageryTree; + + /// Statistics + Stats m_stats; + + /// Decode an MVT tile and extract imagery coordinates + std::vector DecodeMVTTile(std::string const & tilePath, + int tileX, int tileY, int zoom); +}; + +} // namespace generator diff --git a/generator/raw_generator.cpp b/generator/raw_generator.cpp index cf90df8dd..548694636 100644 --- a/generator/raw_generator.cpp +++ b/generator/raw_generator.cpp @@ -182,6 +182,7 @@ RawGenerator::FinalProcessorPtr RawGenerator::CreateCountryFinalProcessor(Affili auto finalProcessor = std::make_shared(affiliations, m_genInfo.m_tmpDir, m_threadsCount); finalProcessor->SetIsolinesDir(m_genInfo.m_isolinesDir); finalProcessor->SetAddressesDir(m_genInfo.m_addressesDir); + finalProcessor->SetPanoramaxFile(m_genInfo.m_panoramaxFilename); finalProcessor->SetMiniRoundabouts(m_genInfo.GetIntermediateFileName(MINI_ROUNDABOUTS_FILENAME)); finalProcessor->SetAddrInterpolation(m_genInfo.GetIntermediateFileName(ADDR_INTERPOL_FILENAME)); if (addAds) diff --git a/scripts/panoramax_tiles_switzerland/panoramax_coords.bin b/scripts/panoramax_tiles_switzerland/panoramax_coords.bin new file mode 100644 index 000000000..1eebbf641 Binary files /dev/null and b/scripts/panoramax_tiles_switzerland/panoramax_coords.bin differ