Подправил маркеры на карте

This commit is contained in:
2025-12-03 11:47:41 +03:00
parent d7d85ac834
commit 51eb5f3732
7 changed files with 358 additions and 243 deletions

View File

@@ -120,6 +120,32 @@
.moving-marker {
transition: transform 0.1s linear;
}
.marker-size-control {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 999;
background: white;
padding: 10px 12px;
border-radius: 4px;
box-shadow: 0 1px 5px rgba(0,0,0,0.3);
font-size: 11px;
}
.marker-size-control label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.marker-size-control input[type="range"] {
width: 120px;
cursor: pointer;
}
.marker-size-control .size-value {
display: inline-block;
margin-left: 5px;
font-weight: bold;
color: #007bff;
}
</style>
{% endblock %}
@@ -133,6 +159,12 @@
<div id="map"></div>
<div class="marker-size-control">
<label>Размер маркеров:</label>
<input type="range" id="markerSizeSlider" min="0.5" max="2" step="0.1" value="1">
<span class="size-value" id="sizeValue">1.0x</span>
</div>
<div class="playback-control" id="playbackControl" style="display: none;">
<button id="playBtn" title="Воспроизвести"></button>
<button id="pauseBtn" title="Пауза" disabled></button>
@@ -195,7 +227,7 @@ let currentMarkers = {};
let trailPolylines = {};
let staticMarkers = {};
// Color mapping
// Color mapping - расширенная палитра
const colorMap = {
'red': '#dc3545',
'blue': '#007bff',
@@ -204,41 +236,85 @@ const colorMap = {
'orange': '#fd7e14',
'cyan': '#17a2b8',
'magenta': '#e83e8c',
'yellow': '#ffc107',
'lime': '#32cd32',
'pink': '#ff69b4'
'pink': '#ff69b4',
'teal': '#20c997',
'indigo': '#6610f2',
'brown': '#8b4513',
'navy': '#000080',
'maroon': '#800000',
'olive': '#808000',
'coral': '#ff7f50',
'turquoise': '#40e0d0'
};
// Create marker icon using leaflet-markers library for static markers
function createStaticMarkerIcon(type) {
let markerColor = 'grey';
// Shape mapping - разные формы маркеров
const shapeMap = {
'circle': (color, size) => {
const adjustedSize = Math.round(size * 0.85); // Уменьшаем на 15%
return `<div style="background: ${color}; width: ${adjustedSize}px; height: ${adjustedSize}px; border-radius: 50%; border: 1px solid black; box-sizing: border-box;"></div>`;
},
'square': (color, size) => {
const adjustedSize = Math.round(size * 0.85); // Уменьшаем на 15%
return `<div style="background: ${color}; width: ${adjustedSize}px; height: ${adjustedSize}px; border: 1px solid black; box-sizing: border-box;"></div>`;
},
'triangle': (color, size) => {
const adjustedSize = Math.round(size * 0.8); // Уменьшаем на 20%
return `<svg width="${adjustedSize}" height="${adjustedSize}" viewBox="0 0 100 100"><polygon points="50,10 90,90 10,90" fill="${color}" stroke="black" stroke-width="2"/></svg>`;
},
'star': (color, size) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="${color}" stroke="black" stroke-width="0.8"/></svg>`,
'pentagon': (color, size) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24"><path d="M12 2l7.5 5.5-2.9 9H7.4l-2.9-9z" fill="${color}" stroke="black" stroke-width="0.8"/></svg>`,
'hexagon': (color, size) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24"><path d="M12 2l6 4v8l-6 4-6-4V6z" fill="${color}" stroke="black" stroke-width="0.8"/></svg>`,
'diamond': (color, size) => {
const adjustedSize = Math.round(size * 0.85); // Уменьшаем на 15%
return `<div style="background: ${color}; width: ${adjustedSize}px; height: ${adjustedSize}px; transform: rotate(45deg); border: 1px solid black; box-sizing: border-box;"></div>`;
},
'cross': (color, size) => `<svg width="${size}" height="${size}" viewBox="0 0 24 24"><path d="M9 2h6v7h7v6h-7v7H9v-7H2V9h7V2z" fill="${color}" stroke="black" stroke-width="0.8"/></svg>`
};
// Available shapes for assignment
const availableShapes = ['circle', 'square', 'triangle', 'star', 'pentagon', 'hexagon', 'diamond', 'cross'];
// Global marker size multiplier
let markerSizeMultiplier = 1.0;
// Create static marker icon (start/intermediate/end points)
function createStaticMarkerIcon(type, color, shape) {
const hexColor = colorMap[color] || color;
let baseSize = 12; // Уменьшенный базовый размер
if (type === 'start') {
markerColor = 'green';
baseSize = 14; // Начальная точка чуть больше
} else if (type === 'end') {
markerColor = 'red';
baseSize = 14; // Конечная точка чуть больше
} else if (type === 'intermediate') {
markerColor = 'grey';
baseSize = 10; // Промежуточные точки меньше
}
return L.icon({
iconUrl: '{% static "leaflet-markers/img/marker-icon-" %}' + markerColor + '.png',
shadowUrl: '{% static "leaflet-markers/img/marker-shadow.png" %}',
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
const size = Math.round(baseSize * markerSizeMultiplier);
const shapeFunc = shapeMap[shape] || shapeMap['circle'];
return L.divIcon({
className: 'static-marker',
iconSize: [size, size],
iconAnchor: [size/2, size/2],
popupAnchor: [0, -size/2],
html: shapeFunc(hexColor, size)
});
}
// Create moving marker icon (colored circle)
function createMovingMarkerIcon(color) {
// Create moving marker icon (current position - larger and more prominent)
function createMovingMarkerIcon(color, shape) {
const hexColor = colorMap[color] || color;
const baseSize = 16; // Движущийся маркер немного больше
const size = Math.round(baseSize * markerSizeMultiplier);
const shapeFunc = shapeMap[shape] || shapeMap['circle'];
return L.divIcon({
className: 'current-marker',
iconSize: [18, 18],
iconAnchor: [9, 9],
html: `<div style="background: ${hexColor}; width: 100%; height: 100%; border-radius: 50%; border: 3px solid white; box-shadow: 0 0 6px rgba(0,0,0,0.5);"></div>`
className: 'current-marker moving-marker',
iconSize: [size, size],
iconAnchor: [size/2, size/2],
popupAnchor: [0, -size/2],
html: shapeFunc(hexColor, size)
});
}
@@ -335,22 +411,35 @@ function updateDisplay(progress) {
sourcesData.forEach(source => {
const pos = getPositionAtProgress(source, progress);
const color = source.color;
const shape = source.shape;
if (pos) {
// Show/update current marker (moving object - colored circle)
// Check if we've reached the end
const isAtEnd = (progress >= 1 || pos.pointIndex === source.points.length - 1) && pos.segmentProgress >= 0.99;
// Show/update current marker (moving object) - hide when at end
if (isAtEnd) {
// Remove moving marker when at end point
if (currentMarkers[source.source_id]) {
sourceLayerGroups[source.source_id].removeLayer(currentMarkers[source.source_id]);
delete currentMarkers[source.source_id];
}
} else {
// Show/update moving marker
if (!currentMarkers[source.source_id]) {
// Get first point name for popup
const firstPointName = source.points && source.points.length > 0 ? source.points[0].name : '';
const popupContent = `<b>ID ${source.source_id}:</b> ${firstPointName}<br><i style="color: #666;">Текущая позиция</i>`;
currentMarkers[source.source_id] = L.marker([pos.lat, pos.lng], {
icon: createMovingMarkerIcon(color),
icon: createMovingMarkerIcon(color, shape),
zIndexOffset: 1000
}).bindPopup(popupContent);
sourceLayerGroups[source.source_id].addLayer(currentMarkers[source.source_id]);
} else {
currentMarkers[source.source_id].setLatLng([pos.lat, pos.lng]);
}
}
// Update trail (dashed line showing path)
const trailCoords = [];
@@ -386,7 +475,7 @@ function updateDisplay(progress) {
}
const marker = L.marker([point.lat, point.lng], {
icon: createStaticMarkerIcon(markerType),
icon: createStaticMarkerIcon(markerType, color, shape),
zIndexOffset: markerType === 'start' ? 500 : (markerType === 'end' ? 600 : 100)
}).bindPopup(`<b>${source.source_name}</b><br>${popupPrefix}${point.name}<br>${point.frequency}<br>${formatDate(point.timestamp)}`);
@@ -401,7 +490,7 @@ function updateDisplay(progress) {
if (!staticMarkers[endMarkerKey] && source.points.length > 1) {
const lastPoint = source.points[source.points.length - 1];
const endMarker = L.marker([lastPoint.lat, lastPoint.lng], {
icon: createStaticMarkerIcon('end'),
icon: createStaticMarkerIcon('end', color, shape),
zIndexOffset: 600
}).bindPopup(`<b>${source.source_name}</b><br><span style="color: red;">■ Конец</span><br>${lastPoint.name}<br>${lastPoint.frequency}<br>${formatDate(lastPoint.timestamp)}`);
sourceLayerGroups[source.source_id].addLayer(endMarker);
@@ -422,19 +511,21 @@ function resetPlayback() {
// Recreate trails
sourcesData.forEach(source => {
const color = colorMap[source.color] || source.color;
const shape = source.shape;
trailPolylines[source.source_id] = L.polyline([], {
color: color,
weight: 3,
weight: 2, // Уменьшенная толщина линии
opacity: 0.7,
dashArray: '5, 10'
});
sourceLayerGroups[source.source_id].addLayer(trailPolylines[source.source_id]);
// Add start marker immediately (green)
// Add start marker immediately
if (source.points && source.points.length > 0) {
const firstPoint = source.points[0];
const startMarker = L.marker([firstPoint.lat, firstPoint.lng], {
icon: createStaticMarkerIcon('start'),
icon: createStaticMarkerIcon('start', color, shape),
zIndexOffset: 500
}).bindPopup(`<b>${source.source_name}</b><br><span style="color: green;">▶ Начало</span><br>${firstPoint.name}<br>${firstPoint.frequency}<br>${formatDate(firstPoint.timestamp)}`);
sourceLayerGroups[source.source_id].addLayer(startMarker);
@@ -446,6 +537,16 @@ function resetPlayback() {
updateDisplay(currentProgress);
}
// Update all marker sizes
function updateMarkerSizes() {
// Clear and recreate all markers with new size
pause();
const currentProg = currentProgress;
resetPlayback();
currentProgress = currentProg;
updateDisplay(currentProgress);
}
// Play animation
function play() {
if (playbackInterval) return;
@@ -493,6 +594,11 @@ async function loadData() {
// Filter out sources with no points
sourcesData = sourcesData.filter(s => s.points && s.points.length > 0);
// Assign shapes to sources (cycle through available shapes)
sourcesData.forEach((source, idx) => {
source.shape = availableShapes[idx % availableShapes.length];
});
if (!sourcesData || sourcesData.length === 0) {
document.getElementById('loadingOverlay').innerHTML = `
<div class="alert alert-warning">
@@ -567,6 +673,21 @@ async function loadData() {
speedMultiplier = parseFloat(this.value);
});
// Marker size control
const markerSizeSlider = document.getElementById('markerSizeSlider');
const sizeValue = document.getElementById('sizeValue');
markerSizeSlider.addEventListener('input', function() {
markerSizeMultiplier = parseFloat(this.value);
sizeValue.textContent = markerSizeMultiplier.toFixed(1) + 'x';
updateMarkerSizes();
});
// Disable map scroll/zoom when mouse is over marker size control
const markerSizeControl = document.querySelector('.marker-size-control');
L.DomEvent.disableScrollPropagation(markerSizeControl);
L.DomEvent.disableClickPropagation(markerSizeControl);
} catch (error) {
console.error('Error loading data:', error);
document.getElementById('loadingOverlay').innerHTML = `
@@ -593,22 +714,12 @@ function addLegend() {
sourcesData.forEach(source => {
const color = colorMap[source.color] || source.color;
const shape = source.shape;
const points = source.points;
// Get marker color for this source
const markerColorMap = {
'red': 'red',
'blue': 'blue',
'green': 'green',
'purple': 'violet',
'orange': 'orange',
'cyan': 'blue',
'magenta': 'red',
'yellow': 'yellow',
'lime': 'green',
'pink': 'red'
};
const markerColor = markerColorMap[source.color] || 'blue';
// Create shape preview
const shapeFunc = shapeMap[shape] || shapeMap['circle'];
const shapePreview = shapeFunc(color, 16);
// Get first point name and time info
let firstPointName = '';
@@ -627,11 +738,12 @@ function addLegend() {
html += `
<div class="legend-item" style="flex-direction: column; align-items: flex-start; margin-bottom: 8px;">
<div style="display: flex; align-items: center;">
<img src="{% static "leaflet-markers/img/marker-icon-" %}${markerColor}.png"
style="width: 18px; height: 30px; margin-right: 6px;">
<div style="width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; margin-right: 6px;">
${shapePreview}
</div>
<span><strong>ID ${source.source_id}:</strong> ${firstPointName}</span>
</div>
<div style="margin-left: 24px;">
<div style="margin-left: 26px;">
<small style="color: #666;">${source.points_count} точек</small>
${timeInfo}
</div>
@@ -639,31 +751,6 @@ function addLegend() {
`;
});
html += '<div class="legend-section"><strong>Маркеры:</strong></div>';
html += `
<div class="legend-item">
<img src="{% static "leaflet-markers/img/marker-icon-green.png" %}"
style="width: 18px; height: 30px; margin-right: 6px;">
<span>Начальная точка</span>
</div>
<div class="legend-item">
<img src="{% static "leaflet-markers/img/marker-icon-red.png" %}"
style="width: 18px; height: 30px; margin-right: 6px;">
<span>Конечная точка</span>
</div>
<div class="legend-item">
<img src="{% static "leaflet-markers/img/marker-icon-grey.png" %}"
style="width: 18px; height: 30px; margin-right: 6px;">
<span>Промежуточная точка</span>
</div>
<div class="legend-item">
<div style="width: 18px; height: 18px; border-radius: 50%; background: #007bff; border: 3px solid white; box-shadow: 0 0 4px rgba(0,0,0,0.4); margin-right: 6px;"></div>
<span>Движущийся объект (цвет по объекту)</span>
</div>
`;
html += '<div class="legend-section"><small style="color: #666;">Все объекты движутся<br>с одинаковой скоростью</small></div>';
div.innerHTML = html;
return div;
};

View File

@@ -124,7 +124,7 @@
</div>
<div class="averaging-container">
<h2>Усреднение точек по источникам</h2>
<h2>Усреднение точек по объектам</h2>
<div class="form-section">
<div class="row">
@@ -156,7 +156,7 @@
<div class="table-section">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5>Источники <span id="source-count" class="badge bg-primary">0</span></h5>
<h5>Объекты <span id="source-count" class="badge bg-primary">0</span></h5>
</div>
<div class="btn-group-custom">
<button id="export-xlsx" class="btn btn-success" disabled>
@@ -180,7 +180,7 @@
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали источника</h5>
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали объекта</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="modal-body-content">
@@ -279,7 +279,7 @@ document.addEventListener('DOMContentLoaded', function() {
{column: "frequency", dir: "asc"}
],
columns: [
{title: "Источник", field: "source_name", minWidth: 180, widthGrow: 2},
{title: "Объект", field: "source_name", minWidth: 180, widthGrow: 2},
{title: "Групп", field: "groups_count", minWidth: 70, hozAlign: "center"},
{title: "Точек", field: "total_points", minWidth: 70, hozAlign: "center"},
{title: "Частота", field: "frequency", minWidth: 100, sorter: "number"},
@@ -327,7 +327,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Delete source
function deleteSource(sourceIdx) {
//if (!confirm('Удалить этот источник со всеми группами?')) return;
//if (!confirm('Удалить этот объект со всеми группами?')) return;
allSourcesData.splice(sourceIdx, 1);
updateSourcesTable();
}
@@ -338,7 +338,7 @@ document.addEventListener('DOMContentLoaded', function() {
const source = allSourcesData[sourceIdx];
if (!source) return;
document.getElementById('sourceDetailsModalLabel').textContent = `Источник: ${source.source_name}`;
document.getElementById('sourceDetailsModalLabel').textContent = `Объект: ${source.source_name}`;
renderModalContent();
const modal = new bootstrap.Modal(document.getElementById('sourceDetailsModal'));
@@ -619,7 +619,7 @@ document.addEventListener('DOMContentLoaded', function() {
allSourcesData.forEach(source => {
source.groups.forEach(group => {
summaryData.push({
'Источник': source.source_name,
'Объект': source.source_name,
'Частота, МГц': group.frequency,
'Полоса, МГц': group.freq_range,
'Символьная скорость, БОД': group.bod_velocity,
@@ -646,7 +646,7 @@ document.addEventListener('DOMContentLoaded', function() {
source.groups.forEach(group => {
group.points.forEach(point => {
allPointsData.push({
'Источник': source.source_name,
'Объект': source.source_name,
'ID точки': point.id,
'Имя точки': point.name,
'Частота, МГц': point.frequency,

View File

@@ -216,7 +216,7 @@
filterPolygon.addTo(map);
// Добавляем popup с информацией
filterPolygon.bindPopup('<strong>Область фильтра</strong><br>Отображаются только источники с точками в этой области');
filterPolygon.bindPopup('<strong>Область фильтра</strong><br>Отображаются только объекты с точками в этой области');
// Если нет других точек, центрируем карту на полигоне
{% if not groups %}

View File

@@ -154,7 +154,7 @@ class ShowSourcesMapView(LoginRequiredMixin, View):
points.append(
{
"point": (coords.x, coords.y), # (lon, lat)
"source_id": f"Источник #{source.id}",
"source_id": f"Объект #{source.id}",
}
)

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.7 on 2025-12-03 07:51
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0017_add_satellite_alternative_name'),
('mapsapp', '0002_alter_transponders_snr'),
]
operations = [
migrations.AlterField(
model_name='transponders',
name='sat_id',
field=models.ForeignKey(help_text='Спутник, которому принадлежит транспондер', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tran_satellite', to='mainapp.satellite', verbose_name='Спутник'),
),
]

View File

@@ -103,7 +103,8 @@ class Transponders(models.Model):
)
sat_id = models.ForeignKey(
Satellite,
on_delete=models.PROTECT,
on_delete=models.SET_NULL,
null=True,
related_name="tran_satellite",
verbose_name="Спутник",
db_index=True,

View File

@@ -64,5 +64,12 @@ def test_celery_connection():
print("=" * 60)
return True
if __name__ == "__main__":
test_celery_connection()
# if __name__ == "__main__":
# test_celery_connection()
import requests
url = f"https://www.lyngsat.com/europe.html"
payload = {"cmd": "request.get", "url": url, "maxTimeout": 60000}
response = requests.post("http://localhost:8191/v1", json=payload)
print(response.content)