Привет! Меня зовут Виктор, и у меня есть некоторый опыт в области телефонии и разработки. Хотел бы поделиться своим проектом — тепловой картой телефонных номеров. Хотя подобные карты уже существуют, аналогичной визуализации звонков на Хабре пока не встречал. Вот пример того, как она выглядит:
В настоящее время я работаю на одном из телеканалов. Передо мной поставили задачу создания тепловой карты входящих звонков клиентов на номера телеканала по всей территории Российской Федерации. В моём распоряжении находятся АТС Asterisk и соответствующие записи CDR (Call Detail Records), а также система мониторинга Grafana, тестовый сервер и две MySQL-базы данных.
Задача заключается в том, чтобы извлечь номера телефонов из CDR, определить их географическое местоположение и затем отобразить эти данные на карте, используя соответствующие координаты. Хочу сразу отметить, что DEF-номера не имеют прямой географической привязки, но в реестре российской системы нумерации такая информация всё же присутствует. Именно оттуда я загрузил CSV-файлы для импорта в базу данных.
Для получения координат (широта и долгота) мне пришлось воспользоваться API. Признаюсь, я обращался к платному ресурсу дадата, хотя они утверждают, что используют бесплатный источник информации с openstreetmap.org. Создаю запросы через API, извлекая уникальные строки из базы данных и добавляя координаты после получения ответа от сервиса:
<?php
// Настройки подключения к базе данных
$host = 'Адрес_базы';
$db = 'Выбор_базы';
$user = 'Пользователь';
$pass = 'Пароль';
$charset = 'utf8mb4';
// Подключение к базе данных
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
try {
$pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
// Функция для логирования
function log_message($message)
{
// Сохраняем сообщение в лог-файл в директории /scripts
$logFilePath = '/scripts/log.txt';
// Выводим сообщение в консоль
echo "$message\n";
// Пишем сообщение в файл
file_put_contents($logFilePath, "$message\n", FILE_APPEND | LOCK_EX);
}
// Чтение данных из таблицы
$sql = "SELECT DISTINCT region FROM asterisk.region";
$stmt = $pdo->query($sql);
$regions = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Массив для хранения уникальных регионов
$uniqueRegions = [];
// Обрабатываем каждый уникальный регион
foreach ($regions as $region) {
// Добавляем в массив уникальных регионов
$uniqueRegions[] = ['region' => $region];
// Логируем обработку региона
log_message("Обрабатывается регион: $region");
}
// Ключ API
$apiToken = 'TOKEN';
$apiSecret = 'KEY';
// URL для обращения к API
$apiUrl = 'https://dadata.ru/api/address';
// Отправка запросов к API и получение координат
foreach ($uniqueRegions as $item) {
sleep(1); // Пауза перед каждым запросом на 1 секунду
// Данные для отправки
$postData = json_encode([$item['region']]);
// Инициализируем cURL
$ch = curl_init();
// Устанавливаем параметры cURL
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Accept: application/json',
'Authorization: Token ' . $apiToken,
'X-Secret: ' . $apiSecret
));
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
// Выполняем запрос
$response = curl_exec($ch);
// Проверяем, был ли получен ответ
if ($response === false) {
log_message("Не удалось получить координаты для региона {$item['region']} (ошибка HTTP).");
continue;
}
// Закрываем соединение
curl_close($ch);
// Парсим JSON-ответ
$data = json_decode($response, true);
// Пример проверки наличия координат в новом формате ответа
if (isset($data[0]['geo_lat'], $data[0]['geo_lon'])) {
$latitude = $data[0]['geo_lat']; // Здесь предполагаемый путь к широте
$longitude = $data[0]['geo_lon']; // Здесь предполагаемый путь к долготе
// Формируем строку для записи в БД
$geo = "$latitude,$longitude";
// Запись координат в базу данных
$updateSql = "UPDATE asterisk.region SET geo = :geo WHERE region = :region";
$stmt = $pdo->prepare($updateSql);
$stmt->execute([':geo' => $geo, ':region' => $item['region']]);
// Логируем успешную запись
log_message("Записаны координаты для региона {$item['region']}: $geo");
} else {
// Логируем отсутствие координат
log_message("Не удалось получить координаты для региона {$item['region']}");
}
}
// Логируем завершение обработки
log_message("Обновление базы данных завершено.");
?>
В результате получилась вот такая картина, но некоторые координаты остались пустыми - около 106 000 строк не заполнились. Чтобы решить эту проблему, я вручную добавил координаты для крупных регионов, используя Яндекс.Карты и выполнив массовые обновления в базе данных.
Основная часть работы сделана - координаты получены. Теперь нужно взять номера из CDR, сопоставить их с префиксами и найти соответствующие координаты. Скрипт занял много времени, поэтому я решил упростить процесс и создал триггер прямо в базе данных. Возможно, это не самый правильный подход, но мне было важно ускорить работу. Этот триггер срабатывает каждый раз, когда появляется новая запись в CDR: номер парсится, и результат записывается в таблицу geo.
DELIMITER $$
CREATE TRIGGER trg_insert_geo_code
AFTER INSERT ON asterisk_cdr.cdr
FOR EACH ROW
BEGIN
DECLARE v_region VARCHAR(255);
DECLARE v_latitude DECIMAL(10,6);
DECLARE v_longitude DECIMAL(10,6);
-- Получаем данные для вставки в geo_codes
SELECT r.region,
SUBSTRING_INDEX(r.geo, ',', 1),
SUBSTRING_INDEX(SUBSTRING_INDEX(r.geo, ',', -1), ',', 1)
INTO v_region, v_latitude, v_longitude
FROM asterisk_cdr.region r
WHERE LEFT(NEW.src, 3) = r.prefix
AND CAST(SUBSTR(NEW.src, 4) AS UNSIGNED) BETWEEN r.`from` AND r.`to`;
-- Вставляем данные в geo_codes
INSERT INTO asterisk_cdr.geo_codes (Name, Latitude, Longitude, record_id)
VALUES (v_region, v_latitude, v_longitude, NEW.id);
END$$
DELIMITER ;
В grafana все стандартное и идет из коробки, просто добавил сервера для обращения и все.
Вот и всё - теперь есть красивая карта, которая автоматически обновляется и радует глаз.
Не судите строго, это первая публикация.
Комментарии (16)
mlnw
02.02.2025 00:07В эпоху, когда у людей обычно один номер на всю жизнь, но меняются и операторы, и города, и регионы обитания, думать, что по DEF-коду что-то можно определить - наивно. А когда так думает человек, подписывающийся как "инженер телефонист" - то это по меньшей мере странно.
temadiary
02.02.2025 00:07тут надо быть толерантным и инклюзивным
накидают в минус кармы\рейтинга
художника обидеть можно каждый, особенно если художник не изучает матчасть
vadimk91
02.02.2025 00:07Как человек, работающий в связи уже 30+ лет, скажу - геопривязка однозначно работала и работает для номеров стационарных телефонов, для мобильных не так однозначно. У нас (райцентр, СЗФО) примерно 80% клиентов обращаются с мобильных номеров, которые изначально были розданы операторами регионам, но оставшаяся - тут полное разнообразие, иногда такой код встречаешь вообще впервые.
mlnw
02.02.2025 00:07геопривязка однозначно работала и работает для номеров стационарных телефонов
В эпоху voip и коллцентров, работающих из одного места на всю страну, подставляющих callerid каждого звонка def-кодом целевого региона, уже нет.
ErshoFF
02.02.2025 00:07Практически на данный момент DEF код является географическим (оператору дается код для использования в конкретном регионе) - так что авто при нанесении на карту согласно DEF коду близок к истине.
Перенос номер между региона находится на стадии проработки, дата включения возможности переноса не установлена.mlnw
02.02.2025 00:07Если вы про MNP, то ввиду повсеместной отмены внутресетевого роуминга перенос потерял былую актуальность.
А после появления VoWiFi люди продолжают пользоваться российскими номерами, работая напостоянку из заграницы.
Andreyika
02.02.2025 00:07Внутрисетевой роуминг последние лет 15 особо и не был проблемой - цена вопроса 1$ (30руб) в месяц и вы "ну прям как в домашнем регионе". Проблема начинается, когда вам позвонить попытаются за 4рубля/минута вместо изпакета. Ну и стоимость тарифа в челябинске и мск может на 30-40% отличаться запросто (не в пользу мск)
Lpndn
02.02.2025 00:07def'ы ещё не могут переезжать между регионами, так что нормально
MarksMan09
02.02.2025 00:07А людям с этими номерами то что переезжать мешает - крепостное право? :)
fujikiriku
02.02.2025 00:07Ну что вы такие старые вещи вспоминаете, нет, крепостное право уже отменили, хотя многие считают что зря.. нет, сейчас обычный визовый режим
tortor
02.02.2025 00:07На практике, по крайней мере еще в прошлом году, не удавалось при смене региона забрать свой номер "с собой" как внутри одного оператора (в моем случае - МТС), так и при переходе к другому по MNP. Мол, хотите переехать с своим номером - извольте, да только номер должен быть "нашего" региона. Так что номер на практике имеет все-таки ограниченную мобильность даже внутри страны. Ну а то, что сам человек, этот номер использующий, в моменте может путешествовать хоть по всему миру - это понятно. Роуминг работал еще и в 90-е.
orland
02.02.2025 00:07Занятно, что активность абонентов из Ханты-Мансийска больше, чем из Новосибирска, при более чем в десятикратном перевесе по населению в пользу последнего. Есть мысли почему это так? Там больше активных телезрителей?
VictorSkvorez Автор
02.02.2025 00:07Ребята, здорово, что я получил столько откликов на свою работу! Вы абсолютно правы насчёт геопозиции DEF-номера – да, это условная информация. Я, конечно, не претендую на «Оскар» от Хабра. Хотя в телекоме я работаю уже довольно давно, но это не столь важно. Главное, что даже если я попал в цель хотя бы наполовину, для меня и компании этого вполне достаточно. Эта реализация – скорее красивая иллюстрация, чем строгий статистический анализ. Конечно, я осознаю, что точную геолокацию DEF-номера может определить только оператор через триангуляцию, но доступа к таким данным у меня нет. Кстати, если говорить об ABC-нумерации, ничто не мешает мне, например, разместить сервер в Амстердаме, арендовать питерский номер и позвонить из Уфы – в эпоху VoIP это несложно организовать. Спасибо вам за обратную связь и карму, очень приятно!
Max_2D
Приветствую,как вариант можно в режиме реального времени получать номер с помощью правок в dialplan как вариант.