#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>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
using IntervalsKey = std::pair<int, int>;
using IntervalsValue = std::tuple<std::string, std::string, std::string, std::string, unsigned int>;
using Key4 = std::tuple<std::string, std::string, std::string, std::string>;
using IntervalSec = std::tuple<double, double, int>;
typedef bg::model::point<double, 2, bg::cs::cartesian> BoostPoint;
typedef bg::model::box<BoostPoint> BoostBox;
typedef std::pair<BoostBox, size_t> Value;
typedef bg::model::linestring<BoostPoint> BoostLineString;
typedef bg::model::polygon<BoostPoint> BoostPolygon;
typedef bg::model::multi_linestring<BoostLineString> BoostMultiLineString;
typedef std::pair<BoostLineString, size_t> IndexedSubSegment;
// Структура для хранения направляющего вектора линии в 3D-пространстве (для работы с географическими координатами)
struct DirectionVector {
double x, y, z; // Компоненты вектора в 3D пространстве
double azimuth; // Приблизительный азимут для быстрой фильтрации
BoostPoint midpoint; // Центральная точка линии
DirectionVector(const BoostLineString& line) {
if (line.size() < 2) {
x = y = z = azimuth = 0;
midpoint = BoostPoint(0, 0);
return;
}
const BoostPoint& A = line.front(); // В географических координатах: (широта, долгота)
const BoostPoint& B = line.back();
// Вычисляем азимут между точками (для быстрой фильтрации)
double lat1 = bg::get<0>(A) * M_PI / 180.0;
double lon1 = bg::get<1>(A) * M_PI / 180.0;
double lat2 = bg::get<0>(B) * M_PI / 180.0;
double lon2 = bg::get<1>(B) * M_PI / 180.0;
double dLon = lon2 - lon1;
double y_comp = sin(dLon) * cos(lat2);
double x_comp = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
azimuth = atan2(y_comp, x_comp);
if (azimuth < 0) azimuth += 2 * M_PI;
// Преобразуем географические координаты в 3D векторы на сфере
double Ax, Ay, Az, Bx, By, Bz;
geo_to_xyz(lat1, lon1, Ax, Ay, Az);
geo_to_xyz(lat2, lon2, Bx, By, Bz);
// Вычисляем 3D вектор направления
x = Bx - Ax;
y = By - Ay;
z = Bz - Az;
// Нормализуем вектор
double len = sqrt(x*x + y*y + z*z);
if (len > 0) {
x /= len;
y /= len;
z /= len;
}
// Вычисляем центральную точку линии в географических координатах
// Простое усреднение координат - приблизительное решение для небольших расстояний
midpoint = BoostPoint(
(bg::get<0>(A) + bg::get<0>(B)) / 2.0,
(bg::get<1>(A) + bg::get<1>(B)) / 2.0
);
}
// Преобразование географических координат в 3D декартовы на единичной сфере
static void geo_to_xyz(double lat, double lon, double& x, double& y, double& z) {
x = cos(lat) * cos(lon);
y = cos(lat) * sin(lon);
z = sin(lat);
}
// Быстрая проверка коллинеарности по азимуту
bool is_roughly_collinear(const DirectionVector& other, double threshold = 0.26) const { // 15 градусов в радианах
double delta_azimuth = 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);
}
};
// Вычисление точного угла между 3D векторами в градусах
double calculate_exact_angle(const DirectionVector& v1, const DirectionVector& v2) {
double dot_product = v1.x*v2.x + v1.y*v2.y + v1.z*v2.z;
dot_product = std::max(-1.0, std::min(1.0, dot_product));
double angle_rad = acos(dot_product);
double angle_deg = angle_rad * 180.0 / M_PI;
return angle_deg > 90.0 ? 180.0 - angle_deg : angle_deg;
}
// Вычисление расстояния между двумя точками на сфере (формула гаверсинуса)
double haversine_distance(const BoostPoint& p1, const BoostPoint& p2) {
const double R = 6371000.0; // Радиус Земли в метрах
double lat1 = bg::get<0>(p1) * M_PI / 180.0;
double lon1 = bg::get<1>(p1) * M_PI / 180.0;
double lat2 = bg::get<0>(p2) * M_PI / 180.0;
double lon2 = bg::get<1>(p2) * M_PI / 180.0;
double dLat = lat2 - lat1;
double dLon = lon2 - lon1;
double a = sin(dLat/2) * sin(dLat/2) +
cos(lat1) * cos(lat2) *
sin(dLon/2) * sin(dLon/2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
return R * c;
}
// Вычисление расстояния между двумя линиями на сфере
double calculate_line_distance(const BoostLineString& line1, const BoostLineString& line2) {
if (line1.size() < 2 || line2.size() < 2) {
return std::numeric_limits<double>::max();
}
// Для коллинеарных линий используем среднее расстояние между соответствующими точками
double d1 = haversine_distance(line1.front(), line2.front());
double d2 = haversine_distance(line1.back(), line2.back());
// Проверяем, не перевернута ли вторая линия относительно первой
double d3 = haversine_distance(line1.front(), line2.back());
double d4 = haversine_distance(line1.back(), line2.front());
// Выбираем минимальное из двух возможных соответствий точек
if (d1 + d2 > d3 + d4) {
return (d3 + d4) / 2.0;
} else {
return (d1 + d2) / 2.0;
}
}
// Класс для эффективного поиска коллинеарных и близких линий
class CollinearityFinder {
private:
// Пространственный индекс
bgi::rtree<Value, bgi::quadratic<16>> spatial_index;
// Индекс по направлению (азимуту)
std::unordered_map<int, std::vector<size_t>> direction_index;
// Кэш направляющих векторов
std::vector<DirectionVector> directions;
// Исходные линии
std::vector<BoostLineString> lines;
// Параметры поиска
double spatial_filter_expansion = 0.01; // ~1 км в градусах
double collinearity_threshold = 10.0; // в градусах
double distance_weight = 0.5; // Вес расстояния в комбинированной оценке
public:
CollinearityFinder(const std::vector<BoostLineString>& input_lines) : lines(input_lines) {
preprocess();
}
// Установка весов для баланса между коллинеарностью и расстоянием
void set_distance_weight(double weight) {
distance_weight = std::max(0.0, std::min(1.0, weight));
}
// Установка порога коллинеарности
void set_collinearity_threshold(double threshold) {
collinearity_threshold = threshold;
}
// Установка расширения пространственного фильтра
void set_spatial_filter_expansion(double expansion) {
spatial_filter_expansion = expansion;
}
private:
void preprocess() {
auto start_time = std::chrono::high_resolution_clock::now();
// Предварительное вычисление направляющих векторов
directions.reserve(lines.size());
for (const auto& line : lines) {
directions.emplace_back(line);
}
// Строим пространственный индекс
for (size_t i = 0; i < lines.size(); ++i) {
BoostBox box;
bg::envelope(lines[i], box);
spatial_index.insert(std::make_pair(box, i));
// Индексируем по азимуту (с шагом 10 градусов)
int azimuth_bin = static_cast<int>(directions[i].azimuth * 18.0 / M_PI); // 36 секторов по 10°
azimuth_bin %= 36; // Гарантируем, что бин в диапазоне [0, 35]
direction_index[azimuth_bin].push_back(i);
// Добавляем также в противоположный сектор (для антипараллельных линий)
int opposite_bin = (azimuth_bin + 18) % 36; // +180°
direction_index[opposite_bin].push_back(i);
}
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<size_t, std::pair<double, double>> find_most_collinear_and_close(const BoostLineString& reference_line) {
auto start_time = std::chrono::high_resolution_clock::now();
DirectionVector reference_vector(reference_line);
// Этап 1: Пространственная фильтрация
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));
// Этап 2: Фильтрация по направлению
int azimuth_bin = static_cast<int>(reference_vector.azimuth * 18.0 / M_PI) % 36;
// Собираем соседние бины азимута
std::vector<size_t> azimuth_candidates;
for (int offset = -1; offset <= 1; ++offset) {
int current_bin = (azimuth_bin + offset) % 36;
if (current_bin < 0) current_bin += 36;
auto it = direction_index.find(current_bin);
if (it != direction_index.end()) {
azimuth_candidates.insert(azimuth_candidates.end(), it->second.begin(), it->second.end());
}
}
std::unordered_set<size_t> azimuth_set(azimuth_candidates.begin(), azimuth_candidates.end());
// Этап 3: Комбинированная фильтрация
std::vector<size_t> candidates;
for (const auto& spatial_candidate : spatial_candidates) {
size_t idx = spatial_candidate.second;
if (azimuth_set.find(idx) != azimuth_set.end() &&
reference_vector.is_roughly_collinear(directions[idx])) {
candidates.push_back(idx);
}
}
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.size() << " to " << candidates.size()
<< " in " << filter_duration << " ms" << std::endl;
// Этап 4: Вычисление комбинированной оценки (угол и расстояние)
double best_score = std::numeric_limits<double>::max();
size_t best_index = 0;
double best_angle = 0.0;
double best_distance = 0.0;
if (candidates.empty()) {
std::cout << "No candidates found after filtering" << std::endl;
return {0, {std::numeric_limits<double>::max(), std::numeric_limits<double>::max()}};
}
// Предварительное вычисление некоторых метрик для эталонной линии
double ref_line_length = 0.0;
if (reference_line.size() >= 2) {
ref_line_length = haversine_distance(reference_line.front(), reference_line.back());
}
// Нормализуем расстояние относительно длины эталонной линии
double distance_normalize_factor = (ref_line_length > 0) ? (1.0 / ref_line_length) : 1.0;
// Вычисляем комбинированную оценку для каждого кандидата
for (size_t idx : candidates) {
// Точный угол
double angle = calculate_exact_angle(reference_vector, directions[idx]);
// Угол должен быть меньше порога для дальнейшего рассмотрения
if (angle > collinearity_threshold) {
continue;
}
// Вычисляем расстояние между линиями
double distance = calculate_line_distance(reference_line, lines[idx]);
// Нормализуем расстояние относительно длины эталонной линии
double normalized_distance = distance * distance_normalize_factor;
// Нормализуем угол к диапазону [0, 1], где 0 - идеальная коллинеарность
double normalized_angle = angle / 90.0;
// Комбинированная оценка: взвешенная сумма нормализованного угла и расстояния
double score = (1.0 - distance_weight) * normalized_angle + distance_weight * normalized_distance;
if (score < best_score) {
best_score = score;
best_index = idx;
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_index, {best_angle, best_distance}};
}
};
int main() {
// Эталонная линия (широта, долгота)
BoostLineString reference_line;
bg::append(reference_line, BoostPoint(45.094734, 37.565922));
bg::append(reference_line, BoostPoint(45.094696, 37.566153));
// Создаем тестовый набор линий
std::vector<BoostLineString> lines;
// Генерируем случайные линии в окрестности эталонной
std::cout << "Generating test data..." << std::endl;
const int NUM_LINES = 10000; // Для тестирования используем 10,000 линий
for (int i = 0; i < NUM_LINES; ++i) {
double base_lat = 45.0 + (std::rand() % 2000 - 1000) * 0.0001;
double base_lon = 37.5 + (std::rand() % 2000 - 1000) * 0.0001;
BoostLineString line;
bg::append(line, BoostPoint(base_lat, base_lon));
double angle = (std::rand() % 360) * M_PI / 180.0;
double length = 0.0002 + (std::rand() % 100) * 0.00001;
// Здесь мы добавляем точку с учетом приблизительного масштабирования для географических координат
bg::append(line, BoostPoint(
base_lat + length * cos(angle),
base_lon + length * sin(angle) / cos(base_lat * M_PI / 180.0)
));
lines.push_back(line);
}
// Добавляем линию, коллинеарную и близкую к эталонной
BoostLineString collinear_close_line;
bg::append(collinear_close_line, BoostPoint(45.094730, 37.565920));
bg::append(collinear_close_line, BoostPoint(45.094692, 37.566151));
lines.push_back(collinear_close_line);
// Добавляем линию, коллинеарную, но далекую
BoostLineString collinear_far_line;
bg::append(collinear_far_line, BoostPoint(45.084730, 37.555920));
bg::append(collinear_far_line, BoostPoint(45.084692, 37.556151));
lines.push_back(collinear_far_line);
// Добавляем линию, близкую, но не коллинеарную
BoostLineString close_noncollinear_line;
bg::append(close_noncollinear_line, BoostPoint(45.094734, 37.565922));
bg::append(close_noncollinear_line, BoostPoint(45.094834, 37.566022));
lines.push_back(close_noncollinear_line);
std::cout << "Created " << lines.size() << " test lines" << std::endl;
// Инициализируем поисковик
CollinearityFinder finder(lines);
// Устанавливаем вес расстояния в 0.5 (50% важности на коллинеарность, 50% на расстояние)
finder.set_distance_weight(0.5);
// Ищем наиболее коллинеарную и близкую линию
auto result = finder.find_most_collinear_and_close(reference_line);
std::cout << "Best match: index=" << result.first
<< ", angle=" << result.second.first << " degrees"
<< ", distance=" << result.second.second << " meters" << std::endl;
return 0;
}