Compare commits

...

1 Commits

Author SHA1 Message Date
zyphlar
5aaf5aa378 VIBECODED: idea for marking which roads have Panoramax imagery
Signed-off-by: zyphlar <zyphlar@gmail.com>
2025-12-14 10:55:33 -08:00
9 changed files with 288 additions and 0 deletions

View File

@@ -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

View File

@@ -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<FeatureBuilder> features;
ForEachFeatureRawFormat<serialization_policy::MaxAccuracy>(
path, [&](FeatureBuilder && fb, uint64_t) {
collector.EnrichRoad(fb);
features.emplace_back(std::move(fb));
});
// Rewrite the file with enriched features
FeatureBuilderWriter<serialization_policy::MaxAccuracy> 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.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 <cstdint>
#include <fstream>
#include <sstream>
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<char*>(&version), sizeof(version));
file.read(reinterpret_cast<char*>(&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<ImageryPoint> points;
points.reserve(count);
for (uint64_t i = 0; i < count; ++i)
{
double lat, lon;
file.read(reinterpret_cast<char*>(&lat), sizeof(lat));
file.read(reinterpret_cast<char*>(&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<uint32_t>(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<m2::PointD> 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

View File

@@ -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 <string>
#include <vector>
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<ImageryPoint> m_imageryTree;
/// Statistics
Stats m_stats;
/// Decode an MVT tile and extract imagery coordinates
std::vector<m2::PointD> DecodeMVTTile(std::string const & tilePath,
int tileX, int tileY, int zoom);
};
} // namespace generator

View File

@@ -182,6 +182,7 @@ RawGenerator::FinalProcessorPtr RawGenerator::CreateCountryFinalProcessor(Affili
auto finalProcessor = std::make_shared<CountryFinalProcessor>(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)