728 lines
30 KiB
HTML
728 lines
30 KiB
HTML
{% extends "mainapp/base.html" %}
|
||
{% load static %}
|
||
{% block title %}Карта объекта #{{ source_id }} с точками{% endblock title %}
|
||
|
||
{% block extra_css %}
|
||
<!-- Leaflet CSS -->
|
||
<link href="{% static 'leaflet/leaflet.css' %}" rel="stylesheet">
|
||
<link href="{% static 'leaflet-measure/leaflet-measure.css' %}" rel="stylesheet">
|
||
<link href="{% static 'leaflet-tree/L.Control.Layers.Tree.css' %}" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-playback@1.0.2/dist/LeafletPlayback.css" />
|
||
<style>
|
||
body {
|
||
overflow: hidden;
|
||
}
|
||
#map {
|
||
position: fixed;
|
||
top: 56px; /* Высота navbar */
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 1;
|
||
}
|
||
.legend {
|
||
background: white;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
||
font-size: 11px;
|
||
}
|
||
.legend h6 {
|
||
font-size: 12px;
|
||
margin: 0 0 6px 0;
|
||
}
|
||
.legend-item {
|
||
margin: 3px 0;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.legend-marker {
|
||
width: 18px;
|
||
height: 30px;
|
||
margin-right: 6px;
|
||
background-size: contain;
|
||
background-repeat: no-repeat;
|
||
}
|
||
.legend-section {
|
||
margin-top: 8px;
|
||
padding-top: 8px;
|
||
border-top: 1px solid #ddd;
|
||
}
|
||
.legend-section:first-child {
|
||
margin-top: 0;
|
||
padding-top: 0;
|
||
border-top: none;
|
||
}
|
||
.legend-section-title {
|
||
font-weight: bold;
|
||
font-size: 11px;
|
||
margin-bottom: 4px;
|
||
}
|
||
.playback-control {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 1000;
|
||
background: white;
|
||
padding: 15px 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
.playback-control button {
|
||
padding: 6px 12px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
background: #007bff;
|
||
color: white;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
min-width: auto;
|
||
}
|
||
.playback-control button:hover {
|
||
background: #0056b3;
|
||
}
|
||
.playback-control button:disabled {
|
||
background: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
.playback-control .time-display {
|
||
font-size: 13px;
|
||
font-weight: bold;
|
||
min-width: 160px;
|
||
text-align: center;
|
||
}
|
||
.playback-control input[type="range"] {
|
||
width: 250px;
|
||
}
|
||
.playback-control .speed-control {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.playback-control .speed-control label {
|
||
font-size: 11px;
|
||
margin: 0;
|
||
}
|
||
.playback-control .speed-control select {
|
||
padding: 3px 6px;
|
||
border-radius: 4px;
|
||
border: 1px solid #ccc;
|
||
font-size: 12px;
|
||
}
|
||
.playback-control .visibility-controls {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
border-left: 1px solid #ddd;
|
||
padding-left: 15px;
|
||
margin-left: 10px;
|
||
}
|
||
.playback-control .visibility-controls label {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font-size: 11px;
|
||
margin: 0;
|
||
cursor: pointer;
|
||
}
|
||
.playback-control .visibility-controls input[type="checkbox"] {
|
||
cursor: pointer;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div id="map"></div>
|
||
<div class="playback-control" id="playbackControl" style="display: none;">
|
||
<button id="playBtn">▶</button>
|
||
<button id="pauseBtn" disabled>⏸</button>
|
||
<button id="resetBtn">⏮</button>
|
||
<input type="range" id="timeSlider" min="0" max="100" value="0" step="1">
|
||
<div class="time-display" id="timeDisplay">--</div>
|
||
<div class="speed-control">
|
||
<label for="speedSelect">Скорость:</label>
|
||
<select id="speedSelect">
|
||
<option value="0.5">0.5x</option>
|
||
<option value="1" selected>1x</option>
|
||
<option value="2">2x</option>
|
||
<option value="5">5x</option>
|
||
<option value="10">10x</option>
|
||
</select>
|
||
</div>
|
||
<div class="visibility-controls">
|
||
<label>
|
||
<input type="checkbox" id="showPointsCheckbox" checked>
|
||
Точки
|
||
</label>
|
||
<label>
|
||
<input type="checkbox" id="showTrackCheckbox" checked>
|
||
Трек
|
||
</label>
|
||
</div>
|
||
</div>
|
||
{% endblock content %}
|
||
|
||
{% block extra_js %}
|
||
<!-- Leaflet JavaScript -->
|
||
<script src="{% static 'leaflet/leaflet.js' %}"></script>
|
||
<script src="{% static 'leaflet-measure/leaflet-measure.ru.js' %}"></script>
|
||
<script src="{% static 'leaflet-tree/L.Control.Layers.Tree.js' %}"></script>
|
||
<script src="{% static 'leaflet-polylineDecorator/leaflet.polylineDecorator.js' %}"></script>
|
||
<script src="{% static 'leaflet-playback/leaflet-playback.js' %}"></script>
|
||
|
||
<script>
|
||
// Инициализация карты
|
||
let map = L.map('map').setView([55.75, 37.62], 5);
|
||
L.control.scale({
|
||
imperial: false,
|
||
metric: true
|
||
}).addTo(map);
|
||
map.attributionControl.setPrefix(false);
|
||
|
||
const street = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
maxZoom: 19,
|
||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||
});
|
||
street.addTo(map);
|
||
|
||
const satellite = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||
attribution: 'Tiles © Esri'
|
||
});
|
||
|
||
const street_local = L.tileLayer('http://127.0.0.1:8090/styles/basic-preview/512/{z}/{x}/{y}.png', {
|
||
maxZoom: 19,
|
||
attribution: 'Local Tiles'
|
||
});
|
||
|
||
const baseLayers = {
|
||
"Улицы": street,
|
||
"Спутник": satellite,
|
||
"Локально": street_local
|
||
};
|
||
|
||
L.control.layers(baseLayers).addTo(map);
|
||
map.setMaxZoom(18);
|
||
map.setMinZoom(0);
|
||
L.control.measure({ primaryLengthUnit: 'kilometers' }).addTo(map);
|
||
|
||
// Функция для создания иконки
|
||
var getColorIcon = function(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]
|
||
});
|
||
};
|
||
|
||
var sourceOverlays = [];
|
||
var glPointLayers = [];
|
||
var glPointCoordinates = [];
|
||
var glPointsData = [];
|
||
var trackLayer = L.layerGroup(); // Layer group for track and related elements
|
||
var glPointsGroupLayer = L.layerGroup(); // Layer group specifically for GL points
|
||
|
||
// Сначала собираем все данные о точках ГЛ с временными метками
|
||
{% for group in groups %}
|
||
{% for point_data in group.points %}
|
||
{% if not point_data.source_id %}
|
||
glPointsData.push({
|
||
name: "{{ point_data.name|escapejs }}",
|
||
frequency: "{{ point_data.frequency|escapejs }}",
|
||
coords: [{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}],
|
||
timestamp: {% if point_data.timestamp %}"{{ point_data.timestamp|escapejs }}"{% else %}null{% endif %}
|
||
});
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endfor %}
|
||
|
||
// Фильтруем точки с временными метками и сортируем
|
||
var glPointsWithTime = glPointsData.filter(p => p.timestamp !== null);
|
||
glPointsWithTime.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
||
|
||
// Создаём слои для координат объекта и точек ГЛ
|
||
{% for group in groups %}
|
||
var groupName = '{{ group.name|escapejs }}';
|
||
var colorName = '{{ group.color }}';
|
||
var groupIcon = getColorIcon(colorName);
|
||
var groupLayer = L.layerGroup();
|
||
|
||
{% for point_data in group.points %}
|
||
{% if point_data.source_id %}
|
||
// Это координата объекта
|
||
var pointName = "{{ point_data.source_id|escapejs }}";
|
||
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
|
||
icon: groupIcon
|
||
}).bindPopup(pointName);
|
||
groupLayer.addLayer(marker);
|
||
{% else %}
|
||
// Это точка ГЛ
|
||
var pointName = "{{ point_data.name|escapejs }}";
|
||
var pointCoords = [{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}];
|
||
var pointNumber = {{ forloop.counter }};
|
||
|
||
// Определяем цвет маркера: первый - зеленый, последний - оранжевый, остальные - обычный
|
||
var markerIcon;
|
||
if (pointNumber === 1) {
|
||
markerIcon = getColorIcon('green');
|
||
} else if (pointNumber === glPointsData.length) {
|
||
markerIcon = getColorIcon('orange');
|
||
} else {
|
||
markerIcon = groupIcon;
|
||
}
|
||
|
||
var marker = L.marker(pointCoords, {
|
||
icon: markerIcon
|
||
}).bindPopup(pointNumber + '. ' + pointName + '<br>' + "{{ point_data.frequency|escapejs }}");
|
||
groupLayer.addLayer(marker);
|
||
glPointsGroupLayer.addLayer(marker); // Also add to GL points group layer
|
||
|
||
// Сохраняем координаты для построения трека
|
||
glPointCoordinates.push(pointCoords);
|
||
|
||
// Добавляем каждую точку ГЛ отдельно в список
|
||
glPointLayers.push({
|
||
label: pointNumber + " - {{ point_data.name|escapejs }} ({{ point_data.frequency|escapejs }})",
|
||
layer: marker
|
||
});
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
// Для координат объекта добавляем как отдельный слой без вложенности
|
||
{% if group.color in 'blue,orange,green,violet' %}
|
||
sourceOverlays.push({
|
||
label: groupName,
|
||
layer: groupLayer
|
||
});
|
||
{% endif %}
|
||
|
||
// НЕ добавляем слой группы на карту при загрузке - будет управляться через layer control
|
||
{% if group.color in 'blue,orange,green,violet' %}
|
||
// groupLayer.addTo(map); // Закомментировано - слои скрыты по умолчанию
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
// Создаём трек между точками ГЛ с номерами на сегментах
|
||
if (glPointCoordinates.length > 1) {
|
||
// Создаём отдельные сегменты линий между точками
|
||
for (var i = 0; i < glPointCoordinates.length - 1; i++) {
|
||
var segmentNumber = i + 1;
|
||
|
||
// Создаем линию с стрелкой
|
||
var segment = L.polyline([glPointCoordinates[i], glPointCoordinates[i + 1]], {
|
||
color: 'blue',
|
||
weight: 3,
|
||
opacity: 0.7
|
||
});
|
||
trackLayer.addLayer(segment);
|
||
|
||
// Добавляем стрелку через декоратор
|
||
setTimeout(function(seg, segLayer, num) {
|
||
return function() {
|
||
var decorator = L.polylineDecorator(seg, {
|
||
patterns: [
|
||
{
|
||
offset: '65%',
|
||
repeat: 0,
|
||
symbol: L.Symbol.arrowHead({
|
||
pixelSize: 15,
|
||
polygon: false,
|
||
pathOptions: {
|
||
stroke: true,
|
||
color: 'blue',
|
||
weight: 3,
|
||
opacity: 0.7
|
||
}
|
||
})
|
||
}
|
||
]
|
||
});
|
||
segLayer.addLayer(decorator);
|
||
};
|
||
}(segment, trackLayer, segmentNumber), 100);
|
||
|
||
// Вычисляем центр сегмента для размещения номера
|
||
var midLat = (glPointCoordinates[i][0] + glPointCoordinates[i + 1][0]) / 2;
|
||
var midLng = (glPointCoordinates[i][1] + glPointCoordinates[i + 1][1]) / 2;
|
||
|
||
// Добавляем номер сегмента
|
||
var segmentIcon = L.divIcon({
|
||
className: 'segment-label',
|
||
html: '<div style="background: rgba(0, 0, 255, 0.9); color: white; border: 2px solid white; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 14px; box-shadow: 0 2px 4px rgba(0,0,0,0.4);">' + segmentNumber + '</div>',
|
||
iconSize: [28, 28],
|
||
iconAnchor: [14, 14]
|
||
});
|
||
var segmentMarker = L.marker([midLat, midLng], { icon: segmentIcon });
|
||
trackLayer.addLayer(segmentMarker);
|
||
}
|
||
|
||
// НЕ добавляем слой трека на карту при загрузке - будет управляться через playback
|
||
// trackLayer.addTo(map);
|
||
}
|
||
|
||
// НЕ добавляем GL точки на карту при загрузке - будет управляться через playback
|
||
// if (glPointLayers.length > 0) {
|
||
// glPointsGroupLayer.addTo(map);
|
||
// }
|
||
|
||
// Создаём иерархию
|
||
var treeOverlays = [];
|
||
|
||
if (sourceOverlays.length > 0) {
|
||
treeOverlays.push({
|
||
label: "Координаты объекта #{{ source_id }}",
|
||
selectAllCheckbox: true,
|
||
children: sourceOverlays,
|
||
layer: L.layerGroup()
|
||
});
|
||
}
|
||
|
||
if (glPointLayers.length > 0) {
|
||
treeOverlays.push({
|
||
label: "Точки ГЛ",
|
||
selectAllCheckbox: true,
|
||
children: glPointLayers,
|
||
layer: glPointsGroupLayer
|
||
});
|
||
}
|
||
|
||
// Добавляем слой трека в контрол
|
||
if (glPointCoordinates.length > 1) {
|
||
treeOverlays.push({
|
||
label: "Трек между точками ГЛ",
|
||
layer: trackLayer
|
||
});
|
||
}
|
||
|
||
// Создаём tree control
|
||
const layerControl = L.control.layers.tree(baseLayers, treeOverlays, {
|
||
collapsed: false,
|
||
autoZIndex: true
|
||
});
|
||
layerControl.addTo(map);
|
||
|
||
// Подгоняем карту под все маркеры
|
||
{% if groups %}
|
||
var groupBounds = L.featureGroup([]);
|
||
{% for group in groups %}
|
||
{% for point_data in group.points %}
|
||
groupBounds.addLayer(L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}]));
|
||
{% endfor %}
|
||
{% endfor %}
|
||
map.fitBounds(groupBounds.getBounds().pad(0.1));
|
||
{% endif %}
|
||
|
||
// Добавляем легенду в левый нижний угол
|
||
var legend = L.control({ position: 'bottomleft' });
|
||
legend.onAdd = function(map) {
|
||
var div = L.DomUtil.create('div', 'legend');
|
||
div.innerHTML = '<h6><strong>Легенда</strong></h6>';
|
||
|
||
// Координаты объекта
|
||
var hasSourceCoords = false;
|
||
{% for group in groups %}
|
||
{% if group.color in 'blue,orange,green,violet' %}
|
||
{% if not hasSourceCoords %}
|
||
if (!hasSourceCoords) {
|
||
div.innerHTML += '<div class="legend-section-title">Координаты объекта:</div>';
|
||
hasSourceCoords = true;
|
||
}
|
||
{% endif %}
|
||
div.innerHTML += `
|
||
<div class="legend-item">
|
||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-" %}{{ group.color }}.png');"></div>
|
||
<span>{{ group.name|escapejs }}</span>
|
||
</div>
|
||
`;
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
// Точки ГЛ
|
||
{% for group in groups %}
|
||
{% if group.color not in 'blue,orange,green,violet' %}
|
||
div.innerHTML += '<div class="legend-section"><div class="legend-section-title">Точки ГЛ:</div></div>';
|
||
div.innerHTML += `
|
||
<div class="legend-item">
|
||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-" %}{{ group.color }}.png');"></div>
|
||
<span>{{ group.name|escapejs }}</span>
|
||
</div>
|
||
`;
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
// Добавляем информацию о треке
|
||
if (glPointCoordinates.length > 1) {
|
||
div.innerHTML += '<div class="legend-section"><div class="legend-section-title">Трек между точками:</div></div>';
|
||
div.innerHTML += `
|
||
<div class="legend-item">
|
||
<div style="width: 18px; height: 3px; background: blue; margin-right: 6px;"></div>
|
||
<span>Соединительная линия</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Добавляем информацию о специальных точках
|
||
if (glPointCoordinates.length > 1) {
|
||
div.innerHTML += '<div class="legend-section"><div class="legend-section-title">Специальные точки:</div></div>';
|
||
div.innerHTML += `
|
||
<div class="legend-item">
|
||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-green.png" %}');"></div>
|
||
<span>Первая точка</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-orange.png" %}');"></div>
|
||
<span>Последняя точка</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// Добавляем информацию о playback (если доступен)
|
||
if (glPointsWithTime && glPointsWithTime.length > 1) {
|
||
div.innerHTML += '<div class="legend-section"><div class="legend-section-title">Режим воспроизведения:</div></div>';
|
||
div.innerHTML += `
|
||
<div class="legend-item">
|
||
<div class="legend-marker" style="background-image: url('{% static "leaflet-markers/img/marker-icon-grey.png" %}');"></div>
|
||
<span>Пройденные точки</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div style="width: 24px; height: 24px; margin-right: 6px; background: rgba(0, 0, 255, 0.9); color: white; border: 2px solid white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 11px;">1</div>
|
||
<span>Номер сегмента трека</span>
|
||
</div>
|
||
<div class="legend-item">
|
||
<div style="width: 18px; height: 18px; margin-right: 6px; background: #007bff; border-radius: 3px; display: flex; align-items: center; justify-content: center; color: white; font-size: 10px;">▶</div>
|
||
<span>Используйте панель внизу</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
return div;
|
||
};
|
||
legend.addTo(map);
|
||
|
||
// ============ PLAYBACK FUNCTIONALITY ============
|
||
var playbackData = null;
|
||
var currentPlaybackIndex = 0;
|
||
var playbackInterval = null;
|
||
var playbackMarkers = [];
|
||
var playbackPolyline = null;
|
||
var playbackSpeed = 1000; // milliseconds per step
|
||
|
||
// Показываем контроллер только если есть точки с временными метками
|
||
if (glPointsWithTime.length > 1) {
|
||
document.getElementById('playbackControl').style.display = 'flex';
|
||
|
||
// Инициализация слайдера
|
||
var timeSlider = document.getElementById('timeSlider');
|
||
timeSlider.max = glPointsWithTime.length - 1;
|
||
|
||
// Функция для форматирования даты
|
||
function formatDate(isoString) {
|
||
var date = new Date(isoString);
|
||
return date.toLocaleString('ru-RU', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
}
|
||
|
||
// Функция для обновления отображения времени
|
||
function updateTimeDisplay(index) {
|
||
var timeDisplay = document.getElementById('timeDisplay');
|
||
if (index >= 0 && index < glPointsWithTime.length) {
|
||
timeDisplay.textContent = formatDate(glPointsWithTime[index].timestamp);
|
||
}
|
||
}
|
||
|
||
// Функция для отрисовки точек до указанного индекса
|
||
function renderPlaybackState(index) {
|
||
// Очищаем предыдущие маркеры
|
||
playbackMarkers.forEach(m => map.removeLayer(m));
|
||
playbackMarkers = [];
|
||
|
||
if (playbackPolyline) {
|
||
map.removeLayer(playbackPolyline);
|
||
}
|
||
|
||
// Проверяем состояние чекбоксов
|
||
var showPoints = document.getElementById('showPointsCheckbox').checked;
|
||
var showTrack = document.getElementById('showTrackCheckbox').checked;
|
||
|
||
// Рисуем все точки до текущего индекса
|
||
var coords = [];
|
||
for (var i = 0; i <= index; i++) {
|
||
var point = glPointsWithTime[i];
|
||
coords.push(point.coords);
|
||
|
||
if (showPoints) {
|
||
var markerIcon;
|
||
|
||
// Первая точка - зеленая, текущая - оранжевая, остальные - серые
|
||
if (i === 0) {
|
||
markerIcon = getColorIcon('green');
|
||
} else if (i === index) {
|
||
markerIcon = getColorIcon('orange');
|
||
} else {
|
||
markerIcon = getColorIcon('grey');
|
||
}
|
||
|
||
var marker = L.marker(point.coords, { icon: markerIcon })
|
||
.bindPopup((i + 1) + '. ' + point.name + '<br>' + point.frequency + '<br>' + formatDate(point.timestamp));
|
||
marker.addTo(map);
|
||
playbackMarkers.push(marker);
|
||
}
|
||
}
|
||
|
||
// Рисуем линию трека с номерами сегментов
|
||
if (showTrack && coords.length > 1) {
|
||
// Рисуем отдельные сегменты с номерами
|
||
for (var i = 0; i < coords.length - 1; i++) {
|
||
var segmentNumber = i + 1;
|
||
|
||
// Создаем линию сегмента
|
||
var segment = L.polyline([coords[i], coords[i + 1]], {
|
||
color: 'blue',
|
||
weight: 3,
|
||
opacity: 0.7
|
||
}).addTo(map);
|
||
playbackMarkers.push(segment);
|
||
|
||
// Добавляем стрелку на последний сегмент
|
||
if (i === coords.length - 2) {
|
||
var decorator = L.polylineDecorator(segment, {
|
||
patterns: [{
|
||
offset: '65%',
|
||
repeat: 0,
|
||
symbol: L.Symbol.arrowHead({
|
||
pixelSize: 15,
|
||
polygon: false,
|
||
pathOptions: {
|
||
stroke: true,
|
||
color: 'blue',
|
||
weight: 3,
|
||
opacity: 0.7
|
||
}
|
||
})
|
||
}]
|
||
}).addTo(map);
|
||
playbackMarkers.push(decorator);
|
||
}
|
||
|
||
// Вычисляем центр сегмента для размещения номера
|
||
var midLat = (coords[i][0] + coords[i + 1][0]) / 2;
|
||
var midLng = (coords[i][1] + coords[i + 1][1]) / 2;
|
||
|
||
// Добавляем номер сегмента
|
||
var segmentIcon = L.divIcon({
|
||
className: 'segment-label',
|
||
html: '<div style="background: rgba(0, 0, 255, 0.9); color: white; border: 2px solid white; border-radius: 50%; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.4);">' + segmentNumber + '</div>',
|
||
iconSize: [24, 24],
|
||
iconAnchor: [12, 12]
|
||
});
|
||
var segmentMarker = L.marker([midLat, midLng], { icon: segmentIcon });
|
||
segmentMarker.addTo(map);
|
||
playbackMarkers.push(segmentMarker);
|
||
}
|
||
}
|
||
|
||
updateTimeDisplay(index);
|
||
timeSlider.value = index;
|
||
}
|
||
|
||
// Кнопка воспроизведения
|
||
document.getElementById('playBtn').addEventListener('click', function() {
|
||
if (playbackInterval) return;
|
||
|
||
this.disabled = true;
|
||
document.getElementById('pauseBtn').disabled = false;
|
||
|
||
playbackInterval = setInterval(function() {
|
||
currentPlaybackIndex++;
|
||
|
||
if (currentPlaybackIndex >= glPointsWithTime.length) {
|
||
// Достигли конца
|
||
clearInterval(playbackInterval);
|
||
playbackInterval = null;
|
||
document.getElementById('playBtn').disabled = false;
|
||
document.getElementById('pauseBtn').disabled = true;
|
||
currentPlaybackIndex = glPointsWithTime.length - 1;
|
||
} else {
|
||
renderPlaybackState(currentPlaybackIndex);
|
||
}
|
||
}, playbackSpeed);
|
||
});
|
||
|
||
// Кнопка паузы
|
||
document.getElementById('pauseBtn').addEventListener('click', function() {
|
||
if (playbackInterval) {
|
||
clearInterval(playbackInterval);
|
||
playbackInterval = null;
|
||
document.getElementById('playBtn').disabled = false;
|
||
this.disabled = true;
|
||
}
|
||
});
|
||
|
||
// Кнопка сброса
|
||
document.getElementById('resetBtn').addEventListener('click', function() {
|
||
if (playbackInterval) {
|
||
clearInterval(playbackInterval);
|
||
playbackInterval = null;
|
||
}
|
||
|
||
currentPlaybackIndex = 0;
|
||
renderPlaybackState(0);
|
||
|
||
document.getElementById('playBtn').disabled = false;
|
||
document.getElementById('pauseBtn').disabled = true;
|
||
});
|
||
|
||
// Слайдер времени
|
||
timeSlider.addEventListener('input', function() {
|
||
if (playbackInterval) {
|
||
clearInterval(playbackInterval);
|
||
playbackInterval = null;
|
||
document.getElementById('playBtn').disabled = false;
|
||
document.getElementById('pauseBtn').disabled = true;
|
||
}
|
||
|
||
currentPlaybackIndex = parseInt(this.value);
|
||
renderPlaybackState(currentPlaybackIndex);
|
||
});
|
||
|
||
// Выбор скорости
|
||
document.getElementById('speedSelect').addEventListener('change', function() {
|
||
var speedMultiplier = parseFloat(this.value);
|
||
playbackSpeed = 1000 / speedMultiplier;
|
||
|
||
// Если воспроизведение активно, перезапускаем с новой скоростью
|
||
if (playbackInterval) {
|
||
clearInterval(playbackInterval);
|
||
document.getElementById('pauseBtn').click();
|
||
setTimeout(function() {
|
||
document.getElementById('playBtn').click();
|
||
}, 100);
|
||
}
|
||
});
|
||
|
||
// Обработчики для чекбоксов видимости
|
||
document.getElementById('showPointsCheckbox').addEventListener('change', function() {
|
||
renderPlaybackState(currentPlaybackIndex);
|
||
});
|
||
|
||
document.getElementById('showTrackCheckbox').addEventListener('change', function() {
|
||
renderPlaybackState(currentPlaybackIndex);
|
||
});
|
||
|
||
// Инициализация - показываем первую точку
|
||
renderPlaybackState(0);
|
||
}
|
||
</script>
|
||
{% endblock extra_js %}
|