Files
dbstorage/dbapp/mapsapp/templates/mapsapp/map2d.html

566 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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, "&amp;")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
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 %}