566 lines
26 KiB
HTML
566 lines
26 KiB
HTML
{% extends "mapsapp/map2d_base.html" %}
|
||
{% load static %}
|
||
{% block content %}
|
||
|
||
<div class="db-objects-panel"
|
||
style="position: absolute; top: 100px; z-index: 1000; background: white; padding: 10px; border: 1px solid #ccc; border-radius: 5px;">
|
||
<div class="panel-title">Объекты из базы</div>
|
||
<select id="objectSelector" class="object-select">
|
||
<option value="">— Выберите объект —</option>
|
||
{% for sat in sats %}
|
||
<option value="{{ sat.id }}">{{ sat.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
<button id="loadObjectBtn" class="load-btn" style="display: block; width: 100%; margin-top: 10px;">Все
|
||
точки</button>
|
||
<button id="loadObjectTransBtn" class="load-btn" style="display: block; width: 100%; margin-top: 10px;">Точки
|
||
транспондеров</button>
|
||
<button id="clearMarkersBtn" type="button" onclick="clearAllMarkers()"
|
||
style="display: block; width: 100%; margin-top: 10px;">Очистить маркеры</button>
|
||
|
||
|
||
</div>
|
||
|
||
<div class="footprint-control"
|
||
style="position: absolute; top: 270px; z-index: 1000; background: white; padding: 10px; border: 1px solid #ccc; border-radius: 5px;">
|
||
<div class="panel-title">Области покрытия</div>
|
||
<div class="footprint-actions">
|
||
<button id="showAllFootprints">Показать все</button>
|
||
<button id="hideAllFootprints">Скрыть все</button>
|
||
</div>
|
||
<div id="footprintToggles"></div>
|
||
</div>
|
||
{% endblock content %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
function clearAllMarkers() {
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
if (window.geoJsonOverlaysControl) {
|
||
delete window.geoJsonOverlaysControl;
|
||
}
|
||
}
|
||
map.eachLayer(function (layer) {
|
||
if (!(layer instanceof L.TileLayer)) {
|
||
map.removeLayer(layer);
|
||
}
|
||
});
|
||
}
|
||
let markerColors = ['red', 'green', 'orange', 'violet', 'grey', 'black', 'blue'];
|
||
|
||
function getColorIcon(color) {
|
||
return L.icon({
|
||
iconUrl: '{% static "leaflet-markers/img/marker-icon-" %}' + color + '.png',
|
||
shadowUrl: '{% static "leaflet-markers/img/marker-shadow.png" %}',
|
||
iconSize: [25, 41],
|
||
iconAnchor: [12, 41],
|
||
popupAnchor: [1, -34],
|
||
shadowSize: [41, 41]
|
||
});
|
||
}
|
||
|
||
// --- Новая функция загрузки и отображения GeoJSON ---
|
||
function loadGeoJsonForSatellite(satId) {
|
||
if (!satId) {
|
||
alert('Пожалуйста, выберите объект.');
|
||
return;
|
||
}
|
||
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
}
|
||
|
||
const url = `/api/locations/${encodeURIComponent(satId)}/geojson`;
|
||
|
||
fetch(url)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Сервер вернул ошибку: ' + response.status);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data => {
|
||
if (!data.features || data.features.length === 0) {
|
||
alert('Объекты с таким именем не найдены.');
|
||
return;
|
||
}
|
||
|
||
// --- Группировка данных по частоте ---
|
||
const groupedByFreq = {};
|
||
data.features.forEach(feature => {
|
||
const freq = feature.properties.freq / 1000000;
|
||
if (!groupedByFreq[freq]) {
|
||
groupedByFreq[freq] = [];
|
||
}
|
||
groupedByFreq[freq].push(feature);
|
||
});
|
||
|
||
// --- Создание overlay слоев для L.control.layers.tree ---
|
||
const overlays = [];
|
||
let freqIndex = 0;
|
||
|
||
for (const [freq, features] of Object.entries(groupedByFreq)) {
|
||
const colorName = markerColors[freqIndex % markerColors.length];
|
||
const freqIcon = getColorIcon(colorName);
|
||
const freqGroupLayer = L.layerGroup();
|
||
const subgroup = [];
|
||
|
||
features.forEach((feature, idx) => {
|
||
const [lon, lat] = feature.geometry.coordinates;
|
||
const pointName = feature.properties.name || `Точка ${idx}`;
|
||
const marker = L.marker([lat, lon], { icon: freqIcon })
|
||
.bindPopup(`${pointName}<br>Частота: ${freq}`);
|
||
freqGroupLayer.addLayer(marker);
|
||
|
||
subgroup.push({
|
||
label: freq == -1 ? `${idx + 1} - Неизвестно` : `${idx + 1} - ${freq} МГц`,
|
||
layer: marker
|
||
});
|
||
});
|
||
|
||
// Группа для частоты
|
||
overlays.push({
|
||
label: `${features[0].properties.name} (${freq} МГц)`,
|
||
selectAllCheckbox: true,
|
||
children: subgroup,
|
||
layer: freqGroupLayer
|
||
});
|
||
|
||
freqIndex++;
|
||
}
|
||
const rootGroup = {
|
||
label: "Все точки",
|
||
selectAllCheckbox: true,
|
||
children: overlays,
|
||
layer: L.layerGroup()
|
||
};
|
||
|
||
const geoJsonControl = L.control.layers.tree(baseLayers, [rootGroup], {
|
||
collapsed: false,
|
||
autoZIndex: true
|
||
});
|
||
|
||
window.geoJsonOverlaysControl = geoJsonControl;
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
}
|
||
window.mainTreeControl = geoJsonControl.addTo(map);
|
||
})
|
||
.catch(err => {
|
||
console.error('Ошибка загрузки GeoJSON:', err);
|
||
alert('Не удалось загрузить объекты: ' + err.message);
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
}
|
||
});
|
||
}
|
||
|
||
function loadTranspondersPointsForSatellite(satId) {
|
||
if (!satId) {
|
||
alert('Пожалуйста, выберите объект.');
|
||
return;
|
||
}
|
||
|
||
// --- Явная очистка перед началом ---
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
console.log('Удаляем старый контрол точек');
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
// window.geoJsonOverlaysControl также можно удалить, если он больше не нужен
|
||
if (window.geoJsonOverlaysControl) {
|
||
delete window.geoJsonOverlaysControl;
|
||
}
|
||
}
|
||
// --- Конец очистки ---
|
||
|
||
const url_points = `/api/locations/${encodeURIComponent(satId)}/geojson`;
|
||
const url_trans = `/api/transponders/${encodeURIComponent(satId)}`;
|
||
|
||
fetch(url_trans)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Сервер вернул ошибку при загрузке транспондеров: ' + response.status);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data_trans => {
|
||
console.log('Загруженные транспондеры:', data_trans);
|
||
|
||
return fetch(url_points)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Сервер вернул ошибку при загрузке точек: ' + response.status);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(data_points => {
|
||
console.log('Загруженные точки:', data_points);
|
||
processAndDisplayTransponderPointsByZone(data_points, data_trans);
|
||
});
|
||
})
|
||
.catch(err => {
|
||
console.error('Ошибка загрузки транспондеров или точек:', err);
|
||
alert('Не удалось загрузить данные: ' + err.message);
|
||
// Повторная проверка на случай ошибки
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
if (window.geoJsonOverlaysControl) {
|
||
delete window.geoJsonOverlaysControl;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function processAndDisplayTransponderPointsByZone(data_points, transpondersData) {
|
||
if (!data_points.features || data_points.features.length === 0) {
|
||
alert('Точки с таким идентификатором спутника не найдены.');
|
||
return;
|
||
}
|
||
|
||
if (!transpondersData || !Array.isArray(transpondersData)) {
|
||
console.error('Данные транспондеров недоступны или некорректны.');
|
||
alert('Ошибка: данные транспондеров отсутствуют.');
|
||
return;
|
||
}
|
||
|
||
// --- Функция для определения транспондера по частоте и поляризации ---
|
||
function findTransponderForPoint(pointFreqHz, pointPolarization) {
|
||
const pointFreqMhz = pointFreqHz / 1000000; // Переводим в МГц
|
||
for (const trans of transpondersData) {
|
||
if (typeof trans.frequency !== 'number' || typeof trans.frequency_range !== 'number' || typeof trans.polarization !== 'string') {
|
||
continue;
|
||
}
|
||
|
||
const centerFreq = trans.frequency;
|
||
const bandwidth = trans.frequency_range;
|
||
const halfBandwidth = bandwidth / 2;
|
||
const lowerBound = centerFreq - halfBandwidth;
|
||
const upperBound = centerFreq + halfBandwidth;
|
||
|
||
if (
|
||
pointFreqMhz >= lowerBound &&
|
||
pointFreqMhz <= upperBound &&
|
||
pointPolarization === trans.polarization
|
||
) {
|
||
return trans;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// --- Группировка точек по транспондерам ---
|
||
const groupedByTransponder = {};
|
||
data_points.features.forEach(feature => {
|
||
const pointFreqHz = feature.properties.freq;
|
||
let pointPolarization = feature.properties.polarization;
|
||
if (pointPolarization === undefined || pointPolarization === null) {
|
||
pointPolarization = feature.properties.pol;
|
||
}
|
||
if (pointPolarization === undefined || pointPolarization === null) {
|
||
pointPolarization = feature.properties.polar;
|
||
}
|
||
if (pointPolarization === undefined || pointPolarization === null) {
|
||
pointPolarization = feature.properties.polarisation;
|
||
}
|
||
if (pointPolarization === undefined || pointPolarization === null) {
|
||
console.warn('Точка без поляризации, игнорируется:', feature);
|
||
return;
|
||
}
|
||
|
||
const transponder = findTransponderForPoint(pointFreqHz, pointPolarization);
|
||
|
||
if (transponder) {
|
||
// --- ИСПРАВЛЕНО: используем name как уникальный идентификатор ---
|
||
const transId = transponder.name;
|
||
if (!groupedByTransponder[transId]) {
|
||
groupedByTransponder[transId] = {
|
||
transponder: transponder,
|
||
features: []
|
||
};
|
||
}
|
||
groupedByTransponder[transId].features.push(feature);
|
||
} else {
|
||
console.log(`Точка ${pointFreqHz / 1000000} МГц, ${pointPolarization} -> Не найден транспондер`);
|
||
}
|
||
});
|
||
|
||
console.log('Сгруппированные данные:', groupedByTransponder);
|
||
|
||
// --- Создание иерархии: зоны -> транспондеры -> точки (внутри слоя) ---
|
||
const zonesMap = {};
|
||
|
||
for (const [transId, groupData] of Object.entries(groupedByTransponder)) {
|
||
const trans = groupData.transponder;
|
||
const zoneName = trans.zone_name || 'Без зоны';
|
||
|
||
if (!zonesMap[zoneName]) {
|
||
zonesMap[zoneName] = [];
|
||
}
|
||
zonesMap[zoneName].push({
|
||
transponder: trans,
|
||
features: groupData.features
|
||
});
|
||
}
|
||
|
||
console.log('Сгруппировано по зонам:', zonesMap);
|
||
|
||
// --- Создание overlay слоев для L.control.layers.tree ---
|
||
const overlays = [];
|
||
let zoneIndex = 0;
|
||
|
||
for (const [zoneName, transponderGroups] of Object.entries(zonesMap)) {
|
||
const zoneGroupLayer = L.layerGroup();
|
||
const zoneChildren = [];
|
||
|
||
let transIndex = 0;
|
||
for (const transGroup of transponderGroups) {
|
||
const trans = transGroup.transponder;
|
||
const features = transGroup.features;
|
||
|
||
// Слой для одного транспондера
|
||
const transGroupLayer = L.layerGroup();
|
||
|
||
// Проходим по точкам транспондера
|
||
features.forEach(feature => {
|
||
const [lon, lat] = feature.geometry.coordinates;
|
||
const pointName = feature.properties.name || `Точка`;
|
||
const pointFreqHz = feature.properties.freq;
|
||
const pointFreqMhz = (pointFreqHz / 1000000).toFixed(2);
|
||
const pointPolarization = feature.properties.polarization || feature.properties.pol || feature.properties.polar || feature.properties.polarisation || '?';
|
||
|
||
// --- НОВОЕ: определяем цвет по частоте точки ---
|
||
// Округляем частоту до ближайшего целого Гц или, например, до 1000 Гц (1 кГц) для группировки
|
||
// const freqKey = Math.round(pointFreqHz / 1000); // Группировка по 1 кГц
|
||
const freqKey = Math.round(pointFreqHz); // Группировка по 1 Гц (можно изменить)
|
||
const colorIndex = freqKey % markerColors.length; // Индекс цвета зависит от частоты
|
||
const colorName = markerColors[colorIndex];
|
||
const pointIcon = getColorIcon(colorName); // Создаём иконку с цветом для этой точки
|
||
|
||
const marker = L.marker([lat, lon], { icon: pointIcon }) // Используем иконку точки
|
||
.bindPopup(`${pointName}<br>Частота: ${pointFreqMhz} МГц<br>Поляр.: ${pointPolarization}<br>Транспондер: ${trans.name}<br>Зона: ${trans.zone_name}`);
|
||
transGroupLayer.addLayer(marker);
|
||
});
|
||
|
||
// Добавляем транспондер в дочерние элементы зоны
|
||
// Транспондер не будет иметь фиксированного цвета, только его точки
|
||
const lowerBound = (trans.frequency - trans.frequency_range / 2).toFixed(2);
|
||
const upperBound = (trans.frequency + trans.frequency_range / 2).toFixed(2);
|
||
zoneChildren.push({
|
||
label: `${trans.name} (${lowerBound} - ${upperBound})`,
|
||
selectAllCheckbox: true,
|
||
layer: transGroupLayer // Этот слой содержит точки с разными цветами
|
||
});
|
||
|
||
zoneGroupLayer.addLayer(transGroupLayer);
|
||
transIndex++;
|
||
}
|
||
|
||
overlays.push({
|
||
label: zoneName,
|
||
selectAllCheckbox: true,
|
||
children: zoneChildren,
|
||
layer: zoneGroupLayer
|
||
});
|
||
|
||
zoneIndex++;
|
||
}
|
||
|
||
// --- Корневая группа ---
|
||
const rootGroup = {
|
||
label: "Все точки",
|
||
selectAllCheckbox: true,
|
||
children: overlays,
|
||
layer: L.layerGroup()
|
||
};
|
||
|
||
// --- Создаем контрол и добавляем на карту ---
|
||
const geoJsonControl = L.control.layers.tree(baseLayers, [rootGroup], {
|
||
collapsed: false,
|
||
autoZIndex: true
|
||
});
|
||
|
||
window.geoJsonOverlaysControl = geoJsonControl;
|
||
if (window.mainTreeControl && window.mainTreeControl._map) {
|
||
map.removeControl(window.mainTreeControl);
|
||
delete window.mainTreeControl;
|
||
}
|
||
window.mainTreeControl = geoJsonControl.addTo(map);
|
||
}
|
||
|
||
// --- Обработчики событий ---
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const select = document.getElementById('objectSelector');
|
||
select.selectedIndex = 0;
|
||
const loadBtn = document.getElementById('loadObjectBtn');
|
||
const transBtn = document.getElementById('loadObjectTransBtn')
|
||
|
||
// Загружаем footprint'ы при смене выбора
|
||
select.addEventListener('change', function () {
|
||
const satId = this.value;
|
||
console.log(satId);
|
||
loadFootprintsForSatellite(satId);
|
||
});
|
||
|
||
// Загружаем GeoJSON при нажатии кнопки
|
||
loadBtn.addEventListener('click', function () {
|
||
const satId = select.value;
|
||
loadGeoJsonForSatellite(satId);
|
||
});
|
||
|
||
transBtn.addEventListener('click', function () {
|
||
const satId = select.value;
|
||
loadTranspondersPointsForSatellite(satId);
|
||
});
|
||
});
|
||
let currentFootprintLayers = {};
|
||
let currentSatelliteId = null;
|
||
|
||
const togglesContainer = document.getElementById('footprintToggles');
|
||
const showAllBtn = document.getElementById('showAllFootprints');
|
||
const hideAllBtn = document.getElementById('hideAllFootprints');
|
||
|
||
// --- Функции ---
|
||
|
||
function escapeHtml(unsafe) {
|
||
// Простая функция для экранирования HTML-символов в именах
|
||
return unsafe
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
function clearFootprintUIAndLayers() {
|
||
// Удаляем все текущие слои footprint'ов с карты и очищаем объект
|
||
Object.entries(currentFootprintLayers).forEach(([name, layer]) => {
|
||
if (map.hasLayer(layer)) {
|
||
map.removeLayer(layer);
|
||
}
|
||
});
|
||
currentFootprintLayers = {};
|
||
|
||
// Очищаем контейнер с чекбоксами
|
||
togglesContainer.innerHTML = '';
|
||
}
|
||
|
||
function loadFootprintsForSatellite(satId) {
|
||
// Проверка, если satId пустой - очищаем
|
||
if (!satId) {
|
||
clearFootprintUIAndLayers();
|
||
currentSatelliteId = null;
|
||
return;
|
||
}
|
||
|
||
// Сохраняем текущий ID спутника
|
||
currentSatelliteId = satId;
|
||
|
||
const url = `/api/footprint-names/${encodeURIComponent(satId)}`;
|
||
|
||
fetch(url)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error('Ошибка загрузки footprint\'ов: ' + response.statusText);
|
||
}
|
||
return response.json();
|
||
})
|
||
.then(footprints => {
|
||
if (!Array.isArray(footprints)) {
|
||
throw new Error('Ожидался массив footprint\'ов');
|
||
}
|
||
|
||
// Очищаем старое состояние
|
||
clearFootprintUIAndLayers();
|
||
|
||
// Создаём новые слои и чекбоксы
|
||
footprints.forEach(fp => {
|
||
// 1. Создаём тайловый слой Leaflet
|
||
// Убедитесь, что URL соответствует вашей структуре тайлов
|
||
const layer = L.tileLayer(`/tiles/${fp.name}/{z}/{x}/{y}.png`, {
|
||
minZoom: 0,
|
||
maxZoom: 21, // Установите соответствующий maxZoom
|
||
opacity: 0.7, // Установите нужную прозрачность
|
||
// attribution: 'SatBeams Rendered' // Можно добавить атрибуцию
|
||
});
|
||
|
||
// Слои изначально ДОБАВЛЕНЫ на карту (и видимы), если хотите изначально скрытыми - закомментируйте следующую строку
|
||
layer.addTo(map);
|
||
|
||
// Сохраняем слой в объекте
|
||
currentFootprintLayers[fp.name] = layer;
|
||
|
||
const safeNameAttr = encodeURIComponent(fp.name); // для data-атрибута
|
||
const safeFullName = escapeHtml(fp.fullname); // для отображения
|
||
|
||
// 2. Создаём чекбокс и метку
|
||
const label = document.createElement('label');
|
||
label.style.display = 'block';
|
||
label.style.margin = '4px 0';
|
||
// Чекбокс изначально отмечен, если слой добавлен на карту
|
||
label.innerHTML = `
|
||
<input type="checkbox"
|
||
data-footprint="${safeNameAttr}"
|
||
checked> <!-- Отмечен, так как слой добавлен -->
|
||
${safeFullName}
|
||
`;
|
||
togglesContainer.appendChild(label);
|
||
|
||
// 3. Связываем чекбокс со слоем
|
||
const checkbox = label.querySelector('input');
|
||
checkbox.addEventListener('change', function () {
|
||
const footprintName = decodeURIComponent(this.dataset.footprint);
|
||
const layer = currentFootprintLayers[footprintName];
|
||
if (layer) {
|
||
if (this.checked && !map.hasLayer(layer)) {
|
||
// Если чекбокс отмечен и слой не на карте - добавляем
|
||
map.addLayer(layer);
|
||
} else if (!this.checked && map.hasLayer(layer)) {
|
||
// Если чекбокс снят и слой на карте - удаляем
|
||
map.removeLayer(layer);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
})
|
||
.catch(err => {
|
||
console.error('Ошибка загрузки footprint\'ов:', err);
|
||
alert('Не удалось загрузить области покрытия: ' + err.message);
|
||
clearFootprintUIAndLayers(); // При ошибке очищаем UI
|
||
});
|
||
}
|
||
|
||
|
||
function showAllFootprints() {
|
||
Object.entries(currentFootprintLayers).forEach(([name, layer]) => {
|
||
if (!map.hasLayer(layer)) {
|
||
map.addLayer(layer);
|
||
}
|
||
});
|
||
// Синхронизируем чекбоксы
|
||
document.querySelectorAll('#footprintToggles input[type="checkbox"]').forEach(cb => {
|
||
cb.checked = true;
|
||
});
|
||
}
|
||
|
||
function hideAllFootprints() {
|
||
Object.entries(currentFootprintLayers).forEach(([name, layer]) => {
|
||
if (map.hasLayer(layer)) {
|
||
map.removeLayer(layer);
|
||
}
|
||
});
|
||
document.querySelectorAll('#footprintToggles input[type="checkbox"]').forEach(cb => {
|
||
cb.checked = false;
|
||
});
|
||
}
|
||
|
||
// --- Обработчики событий для кнопок ---
|
||
showAllBtn.addEventListener('click', showAllFootprints);
|
||
hideAllBtn.addEventListener('click', hideAllFootprints);
|
||
</script>
|
||
{% endblock extra_js %} |