#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/linestring.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;
typedef bg::model::point<double, 2, bg::cs::geographic<bg::degree>> point_type;
typedef bg::model::linestring<point_type> linestring_type;
typedef bg::model::box<point_type> box_type;
typedef std::pair<box_type, size_t> rtree_value;
// Структура направляющего вектора с дополнительными метриками
struct DirectionVector {
double x, y, z;
double azimuth;
point_type midpoint; // Центральная точка линии для вычисления приблизительного расстояния
DirectionVector(const linestring_type& line) {
if (line.size() < 2) {
x = y = z = azimuth = 0;
midpoint = point_type(0, 0);
return;
}
const point_type& A = line.front();
const point_type& 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) * 180.0 / M_PI;
if (azimuth < 0) azimuth += 360.0;
// Вычисляем 3D вектор для точного расчета
double Ax, Ay, Az, Bx, By, Bz;
geo_to_ecef(bg::get<0>(A), bg::get<1>(A), Ax, Ay, Az);
geo_to_ecef(bg::get<0>(B), bg::get<1>(B), Bx, By, Bz);
x = Bx - Ax;
y = By - Ay;
z = Bz - Az;
// Нормализация
double len = sqrt(x*x + y*y + z*z);
x /= len;
y /= len;
z /= len;
// Вычисляем среднюю точку линии
midpoint = point_type(
(bg::get<0>(A) + bg::get<0>(B)) / 2.0,
(bg::get<1>(A) + bg::get<1>(B)) / 2.0
);
}
static void geo_to_ecef(double lat, double lon, double& x, double& y, double& z) {
const double R = 6378137.0; // Радиус Земли в метрах
const double lat_rad = lat * M_PI / 180.0;
const double lon_rad = lon * M_PI / 180.0;
x = R * cos(lat_rad) * cos(lon_rad);
y = R * cos(lat_rad) * sin(lon_rad);
z = R * sin(lat_rad);
}
// Функция для быстрой грубой оценки схожести направлений
bool is_roughly_collinear(const DirectionVector& other, double threshold = 15.0) const {
double delta_azimuth = fabs(azimuth - other.azimuth);
if (delta_azimuth > 180.0) {
delta_azimuth = 360.0 - delta_azimuth;
}
return delta_azimuth < threshold || delta_azimuth > (180.0 - threshold);
}
};
// Вычисление точного угла между векторами
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 point_type& p1, const point_type& p2) {
const double R = 6378137.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 linestring_type& line1, const linestring_type& 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<rtree_value, bgi::quadratic<16>> spatial_index;
// Индекс по направлению (азимуту)
std::unordered_map<int, std::vector<size_t>> direction_index;
// Кэш направляющих векторов
std::vector<DirectionVector> directions;
// Исходные линии
std::vector<linestring_type> lines;
// Параметры поиска
double spatial_filter_expansion = 0.01; // ~1 км
double collinearity_threshold = 10.0; // в градусах
double distance_weight = 0.5; // Вес расстояния в комбинированной оценке
public:
CollinearityFinder(const std::vector<linestring_type>& input_lines) : lines(input_lines) {
preprocess();
}
// Установка весов для баланса между коллинеарностью и расстоянием
void set_distance_weight(double weight) {
distance_weight = std::max(0.0, std::min(1.0, weight));
}
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) {
box_type box;
bg::envelope(lines[i], box);
spatial_index.insert(std::make_pair(box, i));
// Индексируем по азимуту (с шагом 10 градусов)
int azimuth_bin = static_cast<int>(directions[i].azimuth / 10.0);
direction_index[azimuth_bin].push_back(i);
// Добавляем также в противоположный сектор
int opposite_bin = static_cast<int>((directions[i].azimuth + 180.0) / 10.0) % 36;
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 linestring_type& reference_line) {
auto start_time = std::chrono::high_resolution_clock::now();
DirectionVector reference_vector(reference_line);
// Этап 1: Пространственная фильтрация
box_type query_box;
bg::envelope(reference_line, query_box);
// Расширяем область поиска
point_type min_corner = query_box.min_corner();
point_type 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 = box_type(min_corner, max_corner);
std::vector<rtree_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 / 10.0);
// Собираем соседние бины азимута
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() {
// Эталонная линия
linestring_type reference_line;
bg::append(reference_line, point_type(45.094734, 37.565922));
bg::append(reference_line, point_type(45.094696, 37.566153));
// Создаем тестовый набор линий
std::vector<linestring_type> 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;
linestring_type line;
bg::append(line, point_type(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, point_type(
base_lat + length * cos(angle),
base_lon + length * sin(angle)
));
lines.push_back(line);
}
// Добавляем линию, коллинеарную и близкую к эталонной
linestring_type collinear_close_line;
bg::append(collinear_close_line, point_type(45.094730, 37.565920));
bg::append(collinear_close_line, point_type(45.094692, 37.566151));
lines.push_back(collinear_close_line);
// Добавляем линию, коллинеарную, но далекую
linestring_type collinear_far_line;
bg::append(collinear_far_line, point_type(45.084730, 37.555920));
bg::append(collinear_far_line, point_type(45.084692, 37.556151));
lines.push_back(collinear_far_line);
// Добавляем линию, близкую, но не коллинеарную
linestring_type close_noncollinear_line;
bg::append(close_noncollinear_line, point_type(45.094734, 37.565922));
bg::append(close_noncollinear_line, point_type(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;
}