#include <boost/geometry.hpp>
#include <boost/geometry/index/rtree.hpp>
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <chrono>
#include <random>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
typedef bg::model::point<float, 2, bg::cs::cartesian> BoostPoint;
typedef bg::model::box<BoostPoint> BoostBox;
typedef std::pair<BoostBox, int> Value;
typedef bg::model::linestring<BoostPoint> BoostLineString;
typedef bg::model::polygon<BoostPoint> BoostPolygon;
typedef bg::model::multi_linestring<BoostLineString> BoostMultiLineString;
// Структура для хранения направляющего вектора линии в 2D-пространстве
struct DirectionVector {
float x, y; // Компоненты вектора в 2D пространстве
float azimuth; // Азимут для быстрой фильтрации
BoostPoint midpoint; // Центральная точка линии
DirectionVector() : x(0), y(0), azimuth(0), midpoint(BoostPoint(0, 0)) {}
DirectionVector(const BoostLineString& line) {
if (line.size() < 2) {
x = y = azimuth = 0;
midpoint = BoostPoint(0, 0);
return;
}
const BoostPoint& A = line.front();
const BoostPoint& B = line.back();
// Вычисляем азимут между точками
float lat1 = bg::get<0>(A) * M_PI / 180.0f;
float lon1 = bg::get<1>(A) * M_PI / 180.0f;
float lat2 = bg::get<0>(B) * M_PI / 180.0f;
float lon2 = bg::get<1>(B) * M_PI / 180.0f;
float dLon = lon2 - lon1;
float y_comp = std::sin(dLon) * std::cos(lat2);
float x_comp = std::cos(lat1) * std::sin(lat2) - std::sin(lat1) * std::cos(lat2) * std::cos(dLon);
azimuth = std::atan2(y_comp, x_comp);
if (azimuth < 0) azimuth += 2 * M_PI;
// Вычисляем 2D вектор направления (нормализованное направление)
// Используем проекцию Меркатора для аппроксимации направления в 2D
x = std::cos(azimuth);
y = std::sin(azimuth);
// Вычисляем центральную точку линии
midpoint = BoostPoint(
(bg::get<0>(A) + bg::get<0>(B)) / 2.0f,
(bg::get<1>(A) + bg::get<1>(B)) / 2.0f
);
}
// Проверка коллинеарности по азимуту
bool is_roughly_collinear(const DirectionVector& other, float threshold = 0.26f) const {
float delta_azimuth = std::fabs(azimuth - other.azimuth);
if (delta_azimuth > M_PI) {
delta_azimuth = 2 * M_PI - delta_azimuth;
}
return delta_azimuth < threshold || delta_azimuth > (M_PI - threshold);
}
};
// Вычисление точного угла между 2D векторами в градусах
float calculate_exact_angle(const DirectionVector& v1, const DirectionVector& v2) {
float dot_product = v1.x*v2.x + v1.y*v2.y;
float v1_length = std::sqrt(v1.x*v1.x + v1.y*v1.y);
float v2_length = std::sqrt(v2.x*v2.x + v2.y*v2.y);
float cos_angle = dot_product / (v1_length * v2_length);
cos_angle = std::max(-1.0f, std::min(1.0f, cos_angle));
float angle_rad = std::acos(cos_angle);
float angle_deg = angle_rad * 180.0f / M_PI;
return angle_deg > 90.0f ? 180.0f - angle_deg : angle_deg;
}
// Вычисление расстояния между двумя точками на сфере (формула гаверсинуса)
float haversine_distance(const BoostPoint& p1, const BoostPoint& p2) {
const float R = 6371000.0f;
float lat1 = bg::get<0>(p1) * M_PI / 180.0f;
float lon1 = bg::get<1>(p1) * M_PI / 180.0f;
float lat2 = bg::get<0>(p2) * M_PI / 180.0f;
float lon2 = bg::get<1>(p2) * M_PI / 180.0f;
float dLat = lat2 - lat1;
float dLon = lon2 - lon1;
float a = std::sin(dLat/2) * std::sin(dLat/2) +
std::cos(lat1) * std::cos(lat2) *
std::sin(dLon/2) * std::sin(dLon/2);
float c = 2 * std::atan2(std::sqrt(a), std::sqrt(1-a));
return R * c;
}
// Вычисление расстояния между двумя линиями
float calculate_line_distance(const BoostLineString& line1, const BoostLineString& line2) {
if (line1.size() < 2 || line2.size() < 2) {
return std::numeric_limits<float>::max();
}
float d1 = haversine_distance(line1.front(), line2.front());
float d2 = haversine_distance(line1.back(), line2.back());
float d3 = haversine_distance(line1.front(), line2.back());
float d4 = haversine_distance(line1.back(), line2.front());
if (d1 + d2 > d3 + d4) {
return (d3 + d4) / 2.0f;
} else {
return (d1 + d2) / 2.0f;
}
}
// Класс для поиска коллинеарных и близких линий
class CollinearityFinder {
private:
bgi::rtree<Value, bgi::quadratic<8>> spatial_index;
std::unordered_map<uint8_t, std::vector<int>> direction_index;
// Храним направляющие векторы, индексируемые по ID линии
std::unordered_map<int, DirectionVector> directions;
// Храним ссылку на карту линий
const std::unordered_map<int, BoostLineString>& lines_ref;
// Параметры поиска
float spatial_filter_expansion = 0.01f;
float collinearity_threshold = 10.0f;
float distance_weight = 0.5f;
public:
// Конструктор принимает ссылку на карту линий
CollinearityFinder(const std::unordered_map<int, BoostLineString>& input_lines)
: lines_ref(input_lines) {
preprocess();
}
void set_distance_weight(float weight) {
distance_weight = std::max(0.0f, std::min(1.0f, weight));
}
void set_collinearity_threshold(float threshold) {
collinearity_threshold = threshold;
}
void set_spatial_filter_expansion(float expansion) {
spatial_filter_expansion = expansion;
}
private:
void preprocess() {
auto start_time = std::chrono::high_resolution_clock::now();
// Предварительное вычисление направляющих векторов
for (const auto& pair : lines_ref) {
int id = pair.first;
const BoostLineString& line = pair.second;
directions.emplace(id, DirectionVector(line));
// Строим пространственный индекс
BoostBox box;
bg::envelope(line, box);
spatial_index.insert(std::make_pair(box, id));
// Индексируем по азимуту
uint8_t azimuth_bin = static_cast<uint8_t>(directions[id].azimuth * 9.0f / M_PI) % 18;
direction_index[azimuth_bin].push_back(id);
// Добавляем в противоположный сектор
uint8_t opposite_bin = (azimuth_bin + 9) % 18;
direction_index[opposite_bin].push_back(id);
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
std::cout << "Preprocessing completed in " << duration << " ms" << std::endl;
}
public:
// Поиск наиболее коллинеарной и близкой линии
std::pair<int, std::pair<float, float>> find_most_collinear_and_close(const BoostLineString& reference_line) {
auto start_time = std::chrono::high_resolution_clock::now();
DirectionVector reference_vector(reference_line);
// Пространственная фильтрация
BoostBox query_box;
bg::envelope(reference_line, query_box);
// Расширяем область поиска
BoostPoint min_corner = query_box.min_corner();
BoostPoint max_corner = query_box.max_corner();
bg::set<0>(min_corner, bg::get<0>(min_corner) - spatial_filter_expansion);
bg::set<1>(min_corner, bg::get<1>(min_corner) - spatial_filter_expansion);
bg::set<0>(max_corner, bg::get<0>(max_corner) + spatial_filter_expansion);
bg::set<1>(max_corner, bg::get<1>(max_corner) + spatial_filter_expansion);
query_box = BoostBox(min_corner, max_corner);
std::vector<Value> spatial_candidates;
spatial_index.query(bgi::intersects(query_box), std::back_inserter(spatial_candidates));
// Фильтрация по направлению
uint8_t azimuth_bin = static_cast<uint8_t>(reference_vector.azimuth * 9.0f / M_PI) % 18;
// Собираем кандидатов по азимуту
std::unordered_set<int> azimuth_set;
for (int offset = -1; offset <= 1; ++offset) {
uint8_t current_bin = (azimuth_bin + offset + 18) % 18; // Защита от отрицательных значений
auto it = direction_index.find(current_bin);
if (it != direction_index.end()) {
azimuth_set.insert(it->second.begin(), it->second.end());
}
}
// Комбинированная фильтрация
std::vector<int> candidates;
candidates.reserve(std::min(spatial_candidates.size(), size_t(100)));
for (const auto& spatial_candidate : spatial_candidates) {
int id = spatial_candidate.second;
if (azimuth_set.find(id) != azimuth_set.end() &&
reference_vector.is_roughly_collinear(directions[id])) {
candidates.push_back(id);
}
}
auto filter_time = std::chrono::high_resolution_clock::now();
auto filter_duration = std::chrono::duration_cast<std::chrono::milliseconds>(filter_time - start_time).count();
std::cout << "Filtering reduced candidates from " << lines_ref.size() << " to " << candidates.size()
<< " in " << filter_duration << " ms" << std::endl;
// Вычисление комбинированной оценки
float best_score = std::numeric_limits<float>::max();
int best_id = -1;
float best_angle = 0.0f;
float best_distance = 0.0f;
if (candidates.empty()) {
std::cout << "No candidates found after filtering" << std::endl;
return {-1, {std::numeric_limits<float>::max(), std::numeric_limits<float>::max()}};
}
// Вычисляем длину эталонной линии
float ref_line_length = 0.0f;
if (reference_line.size() >= 2) {
ref_line_length = haversine_distance(reference_line.front(), reference_line.back());
}
float distance_normalize_factor = (ref_line_length > 0) ? (1.0f / ref_line_length) : 1.0f;
// Обработка кандидатов
for (int id : candidates) {
float angle = calculate_exact_angle(reference_vector, directions[id]);
if (angle > collinearity_threshold) {
continue;
}
const auto& candidate_line = lines_ref.at(id);
float distance = calculate_line_distance(reference_line, candidate_line);
float normalized_distance = distance * distance_normalize_factor;
float normalized_angle = angle / 90.0f;
float score = (1.0f - distance_weight) * normalized_angle + distance_weight * normalized_distance;
if (score < best_score) {
best_score = score;
best_id = id;
best_angle = angle;
best_distance = distance;
}
}
auto end_time = std::chrono::high_resolution_clock::now();
auto total_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
std::cout << "Total search completed in " << total_duration << " ms" << std::endl;
return {best_id, {best_angle, best_distance}};
}
};
int main() {
// Эталонная линия
BoostLineString reference_line;
bg::append(reference_line, BoostPoint(45.094734f, 37.565922f));
bg::append(reference_line, BoostPoint(45.094696f, 37.566153f));
// Создаем тестовый набор линий
std::unordered_map<int, BoostLineString> lines;
std::cout << "Generating test data..." << std::endl;
const int NUM_LINES = 10000;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> lat_dist(45.0f - 0.1f, 45.0f + 0.1f);
std::uniform_real_distribution<float> lon_dist(37.5f - 0.1f, 37.5f + 0.1f);
std::uniform_real_distribution<float> angle_dist(0, 2 * M_PI);
std::uniform_real_distribution<float> length_dist(0.0002f, 0.0012f);
// Генерируем случайные линии
for (int i = 0; i < NUM_LINES; ++i) {
float base_lat = lat_dist(gen);
float base_lon = lon_dist(gen);
BoostLineString line;
line.reserve(2);
bg::append(line, BoostPoint(base_lat, base_lon));
float angle = angle_dist(gen);
float length = length_dist(gen);
bg::append(line, BoostPoint(
base_lat + length * std::cos(angle),
base_lon + length * std::sin(angle) / std::cos(base_lat * M_PI / 180.0f)
));
lines.emplace(i, std::move(line));
}
// Добавляем тестовые линии
BoostLineString collinear_close_line;
collinear_close_line.reserve(2);
bg::append(collinear_close_line, BoostPoint(45.094730f, 37.565920f));
bg::append(collinear_close_line, BoostPoint(45.094692f, 37.566151f));
lines.emplace(NUM_LINES, std::move(collinear_close_line));
BoostLineString collinear_far_line;
collinear_far_line.reserve(2);
bg::append(collinear_far_line, BoostPoint(45.084730f, 37.555920f));
bg::append(collinear_far_line, BoostPoint(45.084692f, 37.556151f));
lines.emplace(NUM_LINES + 1, std::move(collinear_far_line));
BoostLineString close_noncollinear_line;
close_noncollinear_line.reserve(2);
bg::append(close_noncollinear_line, BoostPoint(45.094734f, 37.565922f));
bg::append(close_noncollinear_line, BoostPoint(45.094834f, 37.566022f));
lines.emplace(NUM_LINES + 2, std::move(close_noncollinear_line));
std::cout << "Created " << lines.size() << " test lines" << std::endl;
// Инициализируем поисковик
CollinearityFinder finder(lines);
finder.set_distance_weight(0.5f);
// Ищем наиболее коллинеарную и близкую линию
auto result = finder.find_most_collinear_and_close(reference_line);
std::cout << "Best match: ID=" << result.first
<< ", angle=" << result.second.first << " degrees"
<< ", distance=" << result.second.second << " meters" << std::endl;
return 0;
}