Добавил геофильтры. Теперь нужен рефакторинг.

This commit is contained in:
2025-11-17 17:44:24 +03:00
parent b889fb29a3
commit c0f2f16303
7 changed files with 385 additions and 2 deletions

View File

@@ -3,6 +3,8 @@
{% block title %}Список объектов{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css" />
<style>
.table-responsive tr.selected {
background-color: #d4edff;
@@ -22,6 +24,9 @@
.btn-group .btn {
position: relative;
}
#polygonFilterMap {
z-index: 1;
}
</style>
{% endblock %}
@@ -94,6 +99,23 @@
</button>
</div>
<!-- Polygon Filter Button -->
<div>
<button class="btn btn-outline-success btn-sm" type="button"
onclick="openPolygonFilterMap()">
<i class="bi bi-pentagon"></i> Фильтр по полигону
{% if polygon_coords %}
<span class="badge bg-success"></span>
{% endif %}
</button>
{% if polygon_coords %}
<button class="btn btn-outline-danger btn-sm" type="button"
onclick="clearPolygonFilter()" title="Очистить фильтр по полигону">
<i class="bi bi-x-circle"></i>
</button>
{% endif %}
</div>
<!-- Pagination -->
<div class="ms-auto">
{% include 'mainapp/components/_pagination.html' with page_obj=page_obj show_info=True %}
@@ -112,6 +134,11 @@
</div>
<div class="offcanvas-body">
<form method="get" id="filter-form">
<!-- Hidden field to preserve polygon filter -->
{% if polygon_coords %}
<input type="hidden" name="polygon" value="{{ polygon_coords }}">
{% endif %}
<!-- Satellite Selection - Multi-select -->
<div class="mb-2">
<label class="form-label">Спутник:</label>
@@ -703,6 +730,167 @@
{% endblock %}
{% block extra_js %}
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js"></script>
<script>
// Polygon filter map variables
let polygonFilterMapInstance = null;
let drawnItems = null;
let drawControl = null;
let currentPolygon = null;
// Initialize polygon filter map
function initPolygonFilterMap() {
if (polygonFilterMapInstance) {
return; // Already initialized
}
// Create map centered on Russia
polygonFilterMapInstance = L.map('polygonFilterMap').setView([55.7558, 37.6173], 4);
// Add OpenStreetMap tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(polygonFilterMapInstance);
// Initialize FeatureGroup to store drawn items
drawnItems = new L.FeatureGroup();
polygonFilterMapInstance.addLayer(drawnItems);
// Initialize draw control
drawControl = new L.Control.Draw({
position: 'topright',
draw: {
polygon: {
allowIntersection: false,
showArea: true,
drawError: {
color: '#e1e100',
message: '<strong>Ошибка:</strong> полигон не должен пересекать сам себя!'
},
shapeOptions: {
color: '#3388ff',
fillOpacity: 0.2
}
},
polyline: false,
rectangle: {
shapeOptions: {
color: '#3388ff',
fillOpacity: 0.2
}
},
circle: false,
circlemarker: false,
marker: false
},
edit: {
featureGroup: drawnItems,
remove: true
}
});
polygonFilterMapInstance.addControl(drawControl);
// Handle polygon creation
polygonFilterMapInstance.on(L.Draw.Event.CREATED, function (event) {
const layer = event.layer;
// Remove existing polygon
drawnItems.clearLayers();
// Add new polygon
drawnItems.addLayer(layer);
currentPolygon = layer;
});
// Handle polygon edit
polygonFilterMapInstance.on(L.Draw.Event.EDITED, function (event) {
const layers = event.layers;
layers.eachLayer(function (layer) {
currentPolygon = layer;
});
});
// Handle polygon deletion
polygonFilterMapInstance.on(L.Draw.Event.DELETED, function () {
currentPolygon = null;
});
// Load existing polygon if present
{% if polygon_coords %}
try {
const coords = {{ polygon_coords|safe }};
if (coords && coords.length > 0) {
const latLngs = coords.map(coord => [coord[1], coord[0]]); // [lng, lat] -> [lat, lng]
const polygon = L.polygon(latLngs, {
color: '#3388ff',
fillOpacity: 0.2
});
drawnItems.addLayer(polygon);
currentPolygon = polygon;
// Fit map to polygon bounds
polygonFilterMapInstance.fitBounds(polygon.getBounds());
}
} catch (e) {
console.error('Error loading existing polygon:', e);
}
{% endif %}
}
// Open polygon filter map modal
function openPolygonFilterMap() {
const modal = new bootstrap.Modal(document.getElementById('polygonFilterModal'));
modal.show();
// Initialize map after modal is shown (to ensure proper rendering)
setTimeout(() => {
initPolygonFilterMap();
if (polygonFilterMapInstance) {
polygonFilterMapInstance.invalidateSize();
}
}, 300);
}
// Clear polygon on map
function clearPolygonOnMap() {
if (drawnItems) {
drawnItems.clearLayers();
currentPolygon = null;
}
}
// Apply polygon filter
function applyPolygonFilter() {
if (!currentPolygon) {
alert('Пожалуйста, нарисуйте полигон на карте');
return;
}
// Get polygon coordinates
const latLngs = currentPolygon.getLatLngs()[0]; // Get first ring for polygon
const coords = latLngs.map(latLng => [latLng.lng, latLng.lat]); // [lat, lng] -> [lng, lat]
// Close the polygon by adding first point at the end
coords.push(coords[0]);
// Add polygon coordinates to URL and reload
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('polygon', JSON.stringify(coords));
urlParams.delete('page'); // Reset to first page
window.location.search = urlParams.toString();
}
// Clear polygon filter
function clearPolygonFilter() {
const urlParams = new URLSearchParams(window.location.search);
urlParams.delete('polygon');
urlParams.delete('page');
window.location.search = urlParams.toString();
}
</script>
<script>
let lastCheckedIndex = null;
@@ -748,8 +936,15 @@ function showSelectedOnMap() {
selectedIds.push(checkbox.value);
});
// Redirect to the map view with selected IDs as query parameter
const url = '{% url "mainapp:show_sources_map" %}' + '?ids=' + selectedIds.join(',');
// Build URL with IDs and preserve polygon filter if present
const urlParams = new URLSearchParams(window.location.search);
const polygonParam = urlParams.get('polygon');
let url = '{% url "mainapp:show_sources_map" %}' + '?ids=' + selectedIds.join(',');
if (polygonParam) {
url += '&polygon=' + encodeURIComponent(polygonParam);
}
window.open(url, '_blank'); // Open in a new tab
}
@@ -786,6 +981,8 @@ function performSearch() {
}
urlParams.delete('page');
// Preserve polygon filter
// (already in urlParams from window.location.search)
window.location.search = urlParams.toString();
}
@@ -794,6 +991,8 @@ function clearSearch() {
const urlParams = new URLSearchParams(window.location.search);
urlParams.delete('search');
urlParams.delete('page');
// Preserve polygon filter
// (already in urlParams from window.location.search)
window.location.search = urlParams.toString();
}
@@ -810,6 +1009,8 @@ function updateItemsPerPage() {
const urlParams = new URLSearchParams(window.location.search);
urlParams.set('items_per_page', itemsPerPage);
urlParams.delete('page');
// Preserve polygon filter
// (already in urlParams from window.location.search)
window.location.search = urlParams.toString();
}
@@ -829,6 +1030,8 @@ function updateSort(field) {
urlParams.set('sort', newSort);
urlParams.delete('page');
// Preserve polygon filter
// (already in urlParams from window.location.search)
window.location.search = urlParams.toString();
}
@@ -884,6 +1087,12 @@ function updateFilterCounter() {
}
}
// Check if polygon filter is active
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('polygon')) {
filterCount++;
}
// Display the filter counter
const counterElement = document.getElementById('filterCounter');
if (counterElement) {
@@ -1386,4 +1595,42 @@ function showTransponderModal(transponderId) {
<!-- Include the satellite modal component -->
{% include 'mainapp/components/_satellite_modal.html' %}
<!-- Polygon Filter Map Modal -->
<div class="modal fade" id="polygonFilterModal" tabindex="-1" aria-labelledby="polygonFilterModalLabel" aria-hidden="true">
<div class="modal-dialog modal-fullscreen">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="polygonFilterModalLabel">
<i class="bi bi-pentagon"></i> Нарисуйте полигон для фильтрации
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body p-0" style="position: relative;">
<div id="polygonHelpAlert" class="alert alert-info m-2" style="position: absolute; top: 10px; left: 10px; z-index: 1000; max-width: 400px; opacity: 0.95;">
<button type="button" class="btn-close btn-sm float-end" onclick="document.getElementById('polygonHelpAlert').style.display='none'"></button>
<small>
<strong>Инструкция:</strong>
<ul class="mb-0 ps-3">
<li>Используйте инструменты справа для рисования полигона или прямоугольника</li>
<li>Кликайте по карте для создания вершин полигона</li>
<li>Замкните полигон, кликнув на первую точку</li>
<li>Нажмите "Применить фильтр" для фильтрации источников</li>
</ul>
</small>
</div>
<div id="polygonFilterMap" style="height: calc(100vh - 120px); width: 100%;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-danger" onclick="clearPolygonOnMap()">
<i class="bi bi-trash"></i> Очистить полигон
</button>
<button type="button" class="btn btn-primary" onclick="applyPolygonFilter()">
<i class="bi bi-check-circle"></i> Применить фильтр
</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -182,8 +182,50 @@
`;
{% endfor %}
{% if polygon_coords %}
div.innerHTML += `
<div class="legend-item" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #ddd;">
<div style="width: 18px; height: 18px; margin-right: 6px; background-color: rgba(51, 136, 255, 0.2); border: 2px solid #3388ff;"></div>
<span>Область фильтра</span>
</div>
`;
{% endif %}
return div;
};
legend.addTo(map);
// Добавляем полигон фильтра на карту, если он есть
{% if polygon_coords %}
try {
const polygonCoords = {{ polygon_coords|safe }};
if (polygonCoords && polygonCoords.length > 0) {
// Преобразуем координаты из [lng, lat] в [lat, lng] для Leaflet
const latLngs = polygonCoords.map(coord => [coord[1], coord[0]]);
// Создаем полигон
const filterPolygon = L.polygon(latLngs, {
color: '#3388ff',
fillColor: '#3388ff',
fillOpacity: 0.2,
weight: 2,
dashArray: '5, 5'
});
// Добавляем полигон на карту
filterPolygon.addTo(map);
// Добавляем popup с информацией
filterPolygon.bindPopup('<strong>Область фильтра</strong><br>Отображаются только источники с точками в этой области');
// Если нет других точек, центрируем карту на полигоне
{% if not groups %}
map.fitBounds(filterPolygon.getBounds());
{% endif %}
}
} catch (e) {
console.error('Ошибка при отображении полигона фильтра:', e);
}
{% endif %}
</script>
{% endblock extra_js %}