Сделал 1 карту на LibreMap

This commit is contained in:
2025-11-18 17:15:03 +03:00
parent c8bcd1adf0
commit 4d7cc9f667

View File

@@ -3,46 +3,169 @@
{% block title %}Карта объектов{% 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">
<!-- MapLibre GL CSS -->
<link href="{% static 'maplibre/maplibre-gl.css' %}" rel="stylesheet">
<style>
body {
overflow: hidden;
}
#map {
position: fixed;
top: 56px; /* Высота navbar */
top: 56px;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
z-index: 0;
}
.legend {
/* Легенда */
.maplibregl-ctrl-legend {
background: white;
padding: 8px;
padding: 10px;
border-radius: 4px;
box-shadow: 0 0 10px rgba(0,0,0,0.2);
box-shadow: 0 0 0 2px rgba(0,0,0,.1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 11px;
max-height: 400px;
overflow-y: auto;
}
.legend h6 {
.maplibregl-ctrl-legend h6 {
font-size: 12px;
margin: 0 0 6px 0;
margin: 0 0 8px 0;
font-weight: bold;
}
.legend-item {
margin: 3px 0;
margin: 4px 0;
display: flex;
align-items: center;
padding: 2px;
}
.legend-marker {
width: 18px;
height: 30px;
margin-right: 6px;
background-size: contain;
width: 12px;
height: 12px;
margin-right: 8px;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 3px rgba(0,0,0,0.3);
}
.legend-section {
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px solid #ddd;
}
.legend-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
/* Слои контрол */
.maplibregl-ctrl-layers {
background: white;
padding: 10px;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0,0,0,.1);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 250px;
max-height: 400px;
overflow-y: auto;
}
.maplibregl-ctrl-layers h6 {
margin: 0 0 10px 0;
font-size: 13px;
font-weight: bold;
}
.layer-item {
margin: 5px 0;
font-size: 12px;
}
.layer-item label {
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
user-select: none;
}
.layer-item input[type="checkbox"] {
cursor: pointer;
}
/* Кастомные кнопки контролов */
.maplibregl-ctrl-projection .maplibregl-ctrl-icon,
.maplibregl-ctrl-3d .maplibregl-ctrl-icon,
.maplibregl-ctrl-style .maplibregl-ctrl-icon {
background-size: 20px 20px;
background-position: center;
background-repeat: no-repeat;
}
.maplibregl-ctrl-projection .maplibregl-ctrl-icon {
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><circle cx="10" cy="10" r="6" fill="none" stroke="%23333" stroke-width="1.5"/><ellipse cx="10" cy="10" rx="3" ry="6" fill="none" stroke="%23333" stroke-width="1.5"/><line x1="4" y1="10" x2="16" y2="10" stroke="%23333" stroke-width="1.5"/></svg>');
}
.maplibregl-ctrl-3d .maplibregl-ctrl-icon {
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><path d="M 5 13 L 5 8 L 10 5 L 15 8 L 15 13 L 10 16 Z M 5 8 L 10 10.5 M 10 10.5 L 15 8 M 10 10.5 L 10 16" fill="none" stroke="%23333" stroke-width="1.5" stroke-linejoin="round"/></svg>');
}
.maplibregl-ctrl-style .maplibregl-ctrl-icon {
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><rect x="3" y="3" width="6" height="6" fill="none" stroke="%23333" stroke-width="1.5"/><rect x="11" y="3" width="6" height="6" fill="none" stroke="%23333" stroke-width="1.5"/><rect x="3" y="11" width="6" height="6" fill="none" stroke="%23333" stroke-width="1.5"/><rect x="11" y="11" width="6" height="6" fill="none" stroke="%23333" stroke-width="1.5"/></svg>');
}
/* Popup стили */
.maplibregl-popup-content {
padding: 10px 15px;
min-width: 150px;
}
.popup-title {
font-weight: bold;
margin-bottom: 5px;
font-size: 13px;
}
.popup-info {
font-size: 12px;
color: #666;
}
/* Стили меню */
.style-menu {
background: white;
border-radius: 4px;
box-shadow: 0 0 0 2px rgba(0,0,0,.1);
padding: 5px;
min-width: 120px;
}
.style-menu button {
display: block;
width: 100%;
padding: 6px 10px;
border: none;
background: none;
text-align: left;
cursor: pointer;
font-size: 12px;
border-radius: 2px;
}
.style-menu button:hover {
background-color: #f0f0f0;
}
.style-menu button.active {
background-color: #e3f2fd;
color: #1976d2;
font-weight: 500;
}
</style>
{% endblock %}
@@ -51,181 +174,567 @@
{% 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>
<!-- MapLibre GL JavaScript -->
<script src="{% static 'maplibre/maplibre-gl.js' %}"></script>
<script>
// Инициализация карты
let map = L.map('map').setView([55.75, 37.62], 10);
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: '&copy; <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 &copy; Esri'
});
const street_local = L.tileLayer('http://127.0.0.1:8080/styles/basic-preview/{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 markerColors = {
'blue': 'blue',
'orange': 'orange',
'green': 'green',
'violet': 'violet'
const markerColors = {
'blue': '#3388ff',
'orange': '#ff8c00',
'green': '#28a745',
'violet': '#9c27b0'
};
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 overlays = [];
// Создаём слои для каждого типа координат
{% for group in groups %}
var groupName = '{{ group.name|escapejs }}';
var colorName = '{{ group.color }}';
var groupIcon = getColorIcon(colorName);
var groupLayer = L.layerGroup();
var subgroup = [];
{% for point_data in group.points %}
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);
subgroup.push({
label: "{{ forloop.counter }} - {{ point_data.source_id|escapejs }}",
layer: marker
});
{% endfor %}
overlays.push({
label: groupName,
selectAllCheckbox: true,
children: subgroup,
layer: groupLayer
});
{% endfor %}
// Корневая группа
const rootGroup = {
label: "Все точки",
selectAllCheckbox: true,
children: overlays,
layer: L.layerGroup()
};
// Создаём tree control
const layerControl = L.control.layers.tree(baseLayers, [rootGroup], {
collapsed: false,
autoZIndex: true
});
layerControl.addTo(map);
// Подгоняем карту под все маркеры
{% if groups %}
var groupBounds = L.featureGroup([]);
// Данные групп из Django
const groups = [
{% 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 %}
{
name: '{{ group.name|escapejs }}',
color: '{{ group.color }}',
points: [
{% for point_data in group.points %}
{
id: '{{ point_data.source_id|escapejs }}',
coordinates: [{{ point_data.point.0|safe }}, {{ point_data.point.1|safe }}]
}{% if not forloop.last %},{% endif %}
{% endfor %}
]
}{% if not forloop.last %},{% endif %}
{% 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>';
// Полигон фильтра
const polygonCoords = {% if polygon_coords %}{{ polygon_coords|safe }}{% else %}null{% endif %};
{% for group in groups %}
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>
`;
{% 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 %}
// Стили карт
const mapStyles = {
streets: {
version: 8,
sources: {
'osm': {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '&copy; OpenStreetMap'
}
},
layers: [{
id: 'osm',
type: 'raster',
source: 'osm'
}]
},
satellite: {
version: 8,
sources: {
'satellite': {
type: 'raster',
tiles: ['https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'],
tileSize: 256,
attribution: '&copy; Esri'
}
},
layers: [{
id: 'satellite',
type: 'raster',
source: 'satellite'
}]
},
local: {
version: 8,
sources: {
'local': {
type: 'raster',
tiles: ['http://127.0.0.1:8080/styles/basic-preview/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: 'Local'
}
},
layers: [{
id: 'local',
type: 'raster',
source: 'local'
}]
}
};
// Инициализация карты
const map = new maplibregl.Map({
container: 'map',
style: mapStyles.streets,
center: [37.62, 55.75],
zoom: 10,
maxZoom: 18,
minZoom: 0
});
let currentStyle = 'streets';
let is3DEnabled = false;
let isGlobeProjection = false;
const allMarkers = [];
// Добавляем стандартные контролы
map.addControl(new maplibregl.NavigationControl(), 'top-right');
map.addControl(new maplibregl.ScaleControl({ unit: 'metric' }), 'bottom-right');
map.addControl(new maplibregl.FullscreenControl(), 'top-right');
map.addControl(new maplibregl.GeolocateControl({
positionOptions: { enableHighAccuracy: true },
trackUserLocation: true
}), 'top-right');
// Кастомный контрол для переключения проекции
class ProjectionControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-projection';
this._container.innerHTML = '<button type="button" title="Переключить проекцию"><span class="maplibregl-ctrl-icon"></span></button>';
this._container.onclick = () => {
if (isGlobeProjection) {
map.setProjection({ type: 'mercator' });
isGlobeProjection = false;
} else {
map.setProjection({ type: 'globe' });
isGlobeProjection = true;
}
};
return this._container;
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
} catch (e) {
console.error('Ошибка при отображении полигона фильтра:', e);
}
{% endif %}
// Кастомный контрол для 3D зданий
class Buildings3DControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-3d';
this._container.innerHTML = '<button type="button" title="3D здания"><span class="maplibregl-ctrl-icon"></span></button>';
this._container.onclick = () => {
if (is3DEnabled) {
if (map.getLayer('3d-buildings')) {
map.removeLayer('3d-buildings');
}
is3DEnabled = false;
this._container.querySelector('button').style.backgroundColor = '';
} else {
this.add3DBuildings();
is3DEnabled = true;
this._container.querySelector('button').style.backgroundColor = '#e3f2fd';
}
};
return this._container;
}
add3DBuildings() {
if (this._map.getLayer('3d-buildings')) return;
const layers = this._map.getStyle().layers;
let labelLayerId;
for (let i = 0; i < layers.length; i++) {
if (layers[i].type === 'symbol' && layers[i].layout && layers[i].layout['text-field']) {
labelLayerId = layers[i].id;
break;
}
}
this._map.addLayer({
'id': '3d-buildings',
'source': 'composite',
'source-layer': 'building',
'filter': ['==', 'extrude', 'true'],
'type': 'fill-extrusion',
'minzoom': 15,
'paint': {
'fill-extrusion-color': '#aaa',
'fill-extrusion-height': ['get', 'height'],
'fill-extrusion-base': ['get', 'min_height'],
'fill-extrusion-opacity': 0.6
}
}, labelLayerId);
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
// Кастомный контрол для переключения стилей
class StyleControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-style';
const button = document.createElement('button');
button.type = 'button';
button.title = 'Стиль карты';
button.innerHTML = '<span class="maplibregl-ctrl-icon"></span>';
const menu = document.createElement('div');
menu.className = 'style-menu';
menu.style.display = 'none';
menu.style.position = 'absolute';
menu.style.top = '100%';
menu.style.right = '0';
menu.style.marginTop = '5px';
const styles = [
{ id: 'streets', name: 'Улицы' },
{ id: 'satellite', name: 'Спутник' },
{ id: 'local', name: 'Локально' }
];
styles.forEach(style => {
const btn = document.createElement('button');
btn.textContent = style.name;
btn.className = style.id === currentStyle ? 'active' : '';
btn.onclick = () => {
this.switchStyle(style.id);
menu.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
menu.style.display = 'none';
};
menu.appendChild(btn);
});
button.onclick = (e) => {
e.stopPropagation();
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
};
document.addEventListener('click', () => {
menu.style.display = 'none';
});
this._container.appendChild(button);
this._container.appendChild(menu);
this._container.style.position = 'relative';
return this._container;
}
switchStyle(styleName) {
const center = this._map.getCenter();
const zoom = this._map.getZoom();
const bearing = this._map.getBearing();
const pitch = this._map.getPitch();
this._map.setStyle(mapStyles[styleName]);
currentStyle = styleName;
this._map.once('styledata', () => {
this._map.setCenter(center);
this._map.setZoom(zoom);
this._map.setBearing(bearing);
this._map.setPitch(pitch);
addMarkersToMap();
addFilterPolygon();
});
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
// Кастомный контрол для слоев
class LayersControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-layers';
const title = document.createElement('h6');
title.textContent = 'Слои точек';
this._container.appendChild(title);
groups.forEach((group, groupIndex) => {
const layerId = `points-layer-${groupIndex}`;
const layerItem = document.createElement('div');
layerItem.className = 'layer-item';
const label = document.createElement('label');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = true;
checkbox.addEventListener('change', (e) => {
const visibility = e.target.checked ? 'visible' : 'none';
if (map.getLayer(layerId)) {
map.setLayoutProperty(layerId, 'visibility', visibility);
}
// Также скрываем/показываем внутренний круг
const innerLayerId = `${layerId}-inner`;
if (map.getLayer(innerLayerId)) {
map.setLayoutProperty(innerLayerId, 'visibility', visibility);
}
});
const colorSpan = document.createElement('span');
colorSpan.className = 'legend-marker';
colorSpan.style.backgroundColor = markerColors[group.color];
const nameSpan = document.createElement('span');
nameSpan.textContent = `${group.name} (${group.points.length})`;
label.appendChild(checkbox);
label.appendChild(colorSpan);
label.appendChild(nameSpan);
layerItem.appendChild(label);
this._container.appendChild(layerItem);
});
return this._container;
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
// Кастомный контрол для легенды
class LegendControl {
onAdd(map) {
this._map = map;
this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-legend';
const title = document.createElement('h6');
title.textContent = 'Легенда';
this._container.appendChild(title);
groups.forEach(group => {
const section = document.createElement('div');
section.className = 'legend-section';
const item = document.createElement('div');
item.className = 'legend-item';
const marker = document.createElement('div');
marker.className = 'legend-marker';
marker.style.backgroundColor = markerColors[group.color];
const text = document.createElement('span');
text.textContent = `${group.name} (${group.points.length})`;
item.appendChild(marker);
item.appendChild(text);
section.appendChild(item);
this._container.appendChild(section);
});
if (polygonCoords && polygonCoords.length > 0) {
const section = document.createElement('div');
section.className = 'legend-section';
const item = document.createElement('div');
item.className = 'legend-item';
const marker = document.createElement('div');
marker.style.width = '18px';
marker.style.height = '18px';
marker.style.marginRight = '8px';
marker.style.backgroundColor = 'rgba(51, 136, 255, 0.2)';
marker.style.border = '2px solid #3388ff';
marker.style.borderRadius = '2px';
const text = document.createElement('span');
text.textContent = 'Область фильтра';
item.appendChild(marker);
item.appendChild(text);
section.appendChild(item);
this._container.appendChild(section);
}
return this._container;
}
onRemove() {
this._container.parentNode.removeChild(this._container);
this._map = undefined;
}
}
// Добавляем кастомные контролы
map.addControl(new ProjectionControl(), 'top-right');
map.addControl(new Buildings3DControl(), 'top-right');
map.addControl(new StyleControl(), 'top-right');
map.addControl(new LayersControl(), 'top-left');
map.addControl(new LegendControl(), 'bottom-left');
// Добавление маркеров на карту
function addMarkersToMap() {
groups.forEach((group, groupIndex) => {
const sourceId = `points-${groupIndex}`;
const layerId = `points-layer-${groupIndex}`;
// Создаем GeoJSON для группы
const geojson = {
type: 'FeatureCollection',
features: group.points.map(point => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: point.coordinates
},
properties: {
id: point.id,
groupName: group.name,
color: markerColors[group.color]
}
}))
};
// Добавляем источник данных
if (!map.getSource(sourceId)) {
map.addSource(sourceId, {
type: 'geojson',
data: geojson
});
}
// Добавляем слой с кругами (основной маркер)
if (!map.getLayer(layerId)) {
map.addLayer({
id: layerId,
type: 'circle',
source: sourceId,
paint: {
'circle-radius': 10,
'circle-color': ['get', 'color'],
'circle-stroke-width': 3,
'circle-stroke-color': '#ffffff',
'circle-opacity': 1
}
});
// Добавляем внутренний круг
map.addLayer({
id: `${layerId}-inner`,
type: 'circle',
source: sourceId,
paint: {
'circle-radius': 4,
'circle-color': '#ffffff',
'circle-opacity': 1
}
});
// Добавляем popup при клике
map.on('click', layerId, (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const { id, groupName } = e.features[0].properties;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`<div class="popup-title">${id}</div><div class="popup-info">Группа: ${groupName}</div>`)
.addTo(map);
});
// Меняем курсор при наведении
map.on('mouseenter', layerId, () => {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', layerId, () => {
map.getCanvas().style.cursor = '';
});
}
});
}
// Добавление полигона фильтра
function addFilterPolygon() {
if (!polygonCoords || polygonCoords.length === 0) return;
try {
const polygonGeoJSON = {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [polygonCoords]
}
};
if (!map.getSource('filter-polygon')) {
map.addSource('filter-polygon', {
type: 'geojson',
data: polygonGeoJSON
});
}
if (!map.getLayer('filter-polygon-fill')) {
map.addLayer({
id: 'filter-polygon-fill',
type: 'fill',
source: 'filter-polygon',
paint: {
'fill-color': '#3388ff',
'fill-opacity': 0.2
}
});
}
if (!map.getLayer('filter-polygon-outline')) {
map.addLayer({
id: 'filter-polygon-outline',
type: 'line',
source: 'filter-polygon',
paint: {
'line-color': '#3388ff',
'line-width': 2,
'line-dasharray': [2, 2]
}
});
}
map.on('click', 'filter-polygon-fill', (e) => {
new maplibregl.Popup()
.setLngLat(e.lngLat)
.setHTML('<div class="popup-title">Область фильтра</div><div class="popup-info">Отображаются только источники с точками в этой области</div>')
.addTo(map);
});
} catch (e) {
console.error('Ошибка при отображении полигона фильтра:', e);
}
}
// Подгонка карты под все маркеры
function fitMapToBounds() {
const allCoordinates = [];
groups.forEach(group => {
group.points.forEach(point => {
allCoordinates.push(point.coordinates);
});
});
if (polygonCoords && polygonCoords.length > 0) {
polygonCoords.forEach(coord => {
allCoordinates.push(coord);
});
}
if (allCoordinates.length > 0) {
const bounds = allCoordinates.reduce((bounds, coord) => {
return bounds.extend(coord);
}, new maplibregl.LngLatBounds(allCoordinates[0], allCoordinates[0]));
map.fitBounds(bounds, { padding: 50 });
}
}
// Инициализация после загрузки карты
map.on('load', () => {
addMarkersToMap();
addFilterPolygon();
fitMapToBounds();
});
</script>
{% endblock extra_js %}