This commit is contained in:
2025-09-23 20:13:51 +05:00
commit 234525435d
21 changed files with 25832 additions and 0 deletions

443
map_template_2gis.html Normal file
View File

@@ -0,0 +1,443 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Карта учебных заведений Челябинска - Среднее специальное и высшее образование</title>
<script src="https://maps.api.2gis.ru/2.0/loader.js?pkg=full&key=f61cfc4b-55b7-4937-8625-c00932bee844"></script>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #2c5aa0 0%, #764ba2 100%);
color: white;
padding: 20px;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header h1 {
margin: 0;
font-size: 2.2em;
font-weight: 300;
}
.header p {
margin: 10px 0 0 0;
opacity: 0.9;
font-size: 1.1em;
}
.controls {
background: white;
padding: 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
align-items: center;
}
.filter-btn {
padding: 8px 16px;
border: 2px solid #2c5aa0;
background: white;
color: #2c5aa0;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}
.filter-btn:hover {
background: #2c5aa0;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(44, 90, 160, 0.3);
}
.filter-btn.active {
background: #2c5aa0;
color: white;
}
.stats {
text-align: center;
color: #666;
font-size: 14px;
margin-left: 20px;
}
#map {
width: 100%;
height: calc(100vh - 180px);
}
.balloon-content {
max-width: 300px;
}
.balloon-title {
font-weight: bold;
color: #333;
margin-bottom: 8px;
font-size: 16px;
}
.balloon-address {
color: #666;
margin-bottom: 8px;
font-size: 14px;
}
.balloon-categories {
margin-bottom: 8px;
}
.category-tag {
display: inline-block;
background: #e8f2ff;
color: #0066cc;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
margin: 2px;
border: 1px solid #cce7ff;
}
.balloon-contacts {
margin-top: 10px;
font-size: 13px;
}
.balloon-contacts a {
color: #2c5aa0;
text-decoration: none;
}
.balloon-contacts a:hover {
text-decoration: underline;
}
.powered-by {
position: fixed;
bottom: 10px;
right: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 5px 10px;
border-radius: 5px;
font-size: 12px;
color: #666;
z-index: 1000;
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.8em;
}
.controls {
padding: 10px;
}
.filter-btn {
font-size: 12px;
padding: 6px 12px;
}
.stats {
margin-left: 0;
margin-top: 10px;
width: 100%;
}
}
</style>
</head>
<body>
<div class="header">
<h1>🎓 Карта учебных заведений Челябинска</h1>
<p>Среднее специальное и высшее образование: университеты, института, колледжи, техникумы</p>
</div>
<div class="controls">
<button class="filter-btn active" onclick="filterByCategory('all')">🏛️ Все</button>
<button class="filter-btn" onclick="filterByCategory('университет')">🎓 Университеты</button>
<button class="filter-btn" onclick="filterByCategory('институт')">🏛️ Институты</button>
<button class="filter-btn" onclick="filterByCategory('колледж')">📚 Колледжи</button>
<button class="filter-btn" onclick="filterByCategory('техникум')">🔧 Техникумы</button>
<button class="filter-btn" onclick="filterByCategory('медицинский')">🏥 Медицинские</button>
<button class="filter-btn" onclick="filterByCategory('педагогический')">👨‍🏫 Педагогические</button>
<button class="filter-btn" onclick="filterByCategory('технический')">⚙️ Технические</button>
<button class="filter-btn" onclick="filterByCategory('экономический')">💼 Экономические</button>
<div class="stats">
<span id="showing-count">{{ institutions|length }}</span> из {{ institutions|length }} учреждений
</div>
</div>
<div id="map"></div>
<div class="powered-by">Powered by 2GIS Maps API</div>
<script>
let myMap;
let markers = [];
let institutions = {{ institutions_json|safe }};
DG.then(function () {
console.log('2GIS API загружен успешно');
init();
}).catch(function(error) {
console.error('Ошибка загрузки 2GIS API:', error);
document.getElementById('map').innerHTML = '<div style="padding: 50px; text-align: center; color: red; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 5px;">❌ Не удалось загрузить 2GIS Maps API<br><small>Проверьте подключение к интернету</small></div>';
});
// Проверяем, что DG доступен
if (typeof DG === 'undefined') {
console.error('2GIS API не загружен');
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('map').innerHTML = '<div style="padding: 50px; text-align: center; color: red; background: #ffe6e6; border: 1px solid #ff9999; border-radius: 5px;">❌ 2GIS Maps API недоступен<br><small>Проверьте подключение к интернету</small></div>';
});
}
function init() {
console.log('Инициализация карты 2GIS...');
console.log('Всего учебных заведений:', institutions.length);
try {
myMap = DG.map('map', {
center: [55.1644, 61.4291], // Центр Челябинска
zoom: 11
});
console.log('Карта создана успешно');
// Добавляем маркеры для всех учебных заведений
institutions.forEach(function(institution, index) {
console.log('Добавляем маркер для:', institution.name);
addMarker(institution, index);
});
console.log('Добавлено маркеров:', markers.length);
// Подгоняем границы карты под все маркеры
if (markers.length > 0) {
setTimeout(function() {
fitMapToBounds();
}, 1000);
}
} catch (error) {
console.error('Ошибка при инициализации карты:', error);
document.getElementById('map').innerHTML = '<div style="padding: 50px; text-align: center; color: red;">Ошибка загрузки карты 2GIS: ' + error.message + '</div>';
}
}
function addMarker(institution, index) {
// Определяем иконку в зависимости от типа учреждения
let iconColor = getIconColor(institution);
let marker = DG.marker([institution.latitude, institution.longitude], {
icon: DG.icon({
iconUrl: getIconUrl(institution),
iconSize: [32, 32],
iconAnchor: [16, 32]
})
}).addTo(myMap);
// Создаем содержимое всплывающего окна
let popupContent = createPopupContent(institution);
marker.bindPopup(popupContent);
// Сохраняем данные учреждения в маркере для фильтрации
marker.institution = institution;
marker.institutionIndex = index;
markers.push(marker);
}
function getIconColor(institution) {
let name = institution.name.toLowerCase();
let categories = institution.categories.join(' ').toLowerCase();
if (name.includes('университет') || categories.includes('университет')) return 'blue';
if (name.includes('институт') || categories.includes('институт')) return 'green';
if (name.includes('колледж') || categories.includes('колледж')) return 'orange';
if (name.includes('техникум') || categories.includes('техникум')) return 'red';
return 'gray';
}
function getIconUrl(institution) {
// Используем стандартные иконки 2GIS или создаем собственные
let name = institution.name.toLowerCase();
let categories = institution.categories.join(' ').toLowerCase();
// Для демонстрации используем простые цветные маркеры
if (name.includes('университет') || categories.includes('университет')) {
return 'data:image/svg+xml;base64,' + btoa(`
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="12" fill="#2c5aa0" stroke="white" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="16">🎓</text>
</svg>
`);
}
if (name.includes('институт') || categories.includes('институт')) {
return 'data:image/svg+xml;base64,' + btoa(`
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="12" fill="#28a745" stroke="white" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="16">🏛️</text>
</svg>
`);
}
if (name.includes('колледж') || categories.includes('колледж')) {
return 'data:image/svg+xml;base64,' + btoa(`
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="12" fill="#fd7e14" stroke="white" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="16">📚</text>
</svg>
`);
}
if (name.includes('техникум') || categories.includes('техникум')) {
return 'data:image/svg+xml;base64,' + btoa(`
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="12" fill="#dc3545" stroke="white" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="16">🔧</text>
</svg>
`);
}
// По умолчанию
return 'data:image/svg+xml;base64,' + btoa(`
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="12" fill="#6c757d" stroke="white" stroke-width="2"/>
<text x="16" y="20" text-anchor="middle" fill="white" font-size="16">🏫</text>
</svg>
`);
}
function createPopupContent(institution) {
let content = '<div class="balloon-content">';
content += '<div class="balloon-title">' + institution.name + '</div>';
if (institution.full_name && institution.full_name !== institution.name) {
content += '<div style="font-size: 13px; color: #888; margin-bottom: 8px;">' + institution.full_name + '</div>';
}
if (institution.address) {
content += '<div class="balloon-address">📍 ' + institution.address + '</div>';
}
if (institution.categories && institution.categories.length > 0) {
content += '<div class="balloon-categories">';
content += '<strong>📋 Категории:</strong><br>';
institution.categories.forEach(function(category) {
content += '<span class="category-tag">' + category + '</span>';
});
content += '</div>';
}
let contacts = [];
if (institution.phones && institution.phones.length > 0) {
institution.phones.forEach(function(phone) {
contacts.push('📞 <a href="tel:' + phone + '">' + phone + '</a>');
});
}
if (contacts.length > 0) {
content += '<div class="balloon-contacts">' + contacts.join('<br>') + '</div>';
}
// Источник данных
if (institution.source) {
content += '<div style="margin-top: 10px; font-size: 11px; color: #999;">Источник: ' + institution.source + '</div>';
}
content += '</div>';
return content;
}
function filterByCategory(category) {
// Обновляем активную кнопку
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
let visibleCount = 0;
markers.forEach(function(marker) {
let institution = marker.institution;
let shouldShow = false;
if (category === 'all') {
shouldShow = true;
} else {
// Проверяем название и категории учреждения
let searchText = (institution.name + ' ' + institution.categories.join(' ') + ' ' + institution.full_name).toLowerCase();
shouldShow = searchText.includes(category.toLowerCase());
}
if (shouldShow) {
if (!myMap.hasLayer(marker)) {
myMap.addLayer(marker);
}
visibleCount++;
} else {
if (myMap.hasLayer(marker)) {
myMap.removeLayer(marker);
}
}
});
// Обновляем счетчик
document.getElementById('showing-count').textContent = visibleCount;
// Подгоняем карту под видимые маркеры
if (visibleCount > 0) {
fitMapToBounds();
}
}
function fitMapToBounds() {
let visibleMarkers = markers.filter(marker => myMap.hasLayer(marker));
if (visibleMarkers.length === 0) return;
if (visibleMarkers.length === 1) {
// Если только один маркер, центрируем на него
let coords = visibleMarkers[0].getLatLng();
myMap.setView([coords.lat, coords.lng], 14);
} else {
// Если несколько маркеров, подгоняем границы
let bounds = [];
visibleMarkers.forEach(marker => {
let coords = marker.getLatLng();
bounds.push([coords.lat, coords.lng]);
});
// Вычисляем границы
let minLat = Math.min(...bounds.map(b => b[0]));
let maxLat = Math.max(...bounds.map(b => b[0]));
let minLng = Math.min(...bounds.map(b => b[1]));
let maxLng = Math.max(...bounds.map(b => b[1]));
// Добавляем отступы
let padding = 0.01;
myMap.fitBounds([
[minLat - padding, minLng - padding],
[maxLat + padding, maxLng + padding]
]);
}
}
// Добавляем обработчик изменения размера окна
window.addEventListener('resize', function() {
if (myMap) {
myMap.invalidateSize();
}
});
</script>
</body>
</html>