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

477
create_static_map.py Normal file
View File

@@ -0,0 +1,477 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Создание статической HTML карты с использованием Leaflet и OpenStreetMap
Отображает все найденные учебные заведения Челябинской области
"""
import json
import os
from pathlib import Path
def create_static_map(json_file: str = "chelyabinsk_educational_institutions.json"):
"""Создает статическую HTML карту с данными из JSON"""
# Загружаем данные
try:
with open(json_file, 'r', encoding='utf-8') as f:
data = json.load(f)
except FileNotFoundError:
print(f"❌ Файл {json_file} не найден!")
return None
institutions = data.get('institutions', [])
total_count = len(institutions)
print(f"Создание карты для {total_count} учебных заведений...")
# Определяем центр карты (центр Челябинской области)
center_lat = 55.1644
center_lng = 61.4368
# Создаем HTML с картой
html_content = f"""<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Карта учебных заведений Челябинской области</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<!-- Leaflet JavaScript -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<style>
body {{
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
}}
.header {{
background: linear-gradient(135deg, #4CAF50 0%, #2E7D32 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 #4CAF50;
background: white;
color: #4CAF50;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}}
.filter-btn:hover {{
background: #4CAF50;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(76, 175, 80, 0.3);
}}
.filter-btn.active {{
background: #4CAF50;
color: white;
}}
.stats {{
background: #e8f5e8;
padding: 10px 15px;
border-radius: 20px;
color: #2E7D32;
font-weight: bold;
font-size: 14px;
}}
#map {{
width: 100%;
height: calc(100vh - 200px);
border: 2px solid #ddd;
}}
.legend {{
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(255, 255, 255, 0.95);
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 1000;
max-width: 250px;
}}
.legend h4 {{
margin: 0 0 10px 0;
color: #2E7D32;
}}
.legend-item {{
display: flex;
align-items: center;
margin: 5px 0;
font-size: 13px;
}}
.legend-color {{
width: 16px;
height: 16px;
border-radius: 50%;
margin-right: 8px;
border: 2px solid white;
box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}}
.university {{ background-color: #2196F3; }}
.institute {{ background-color: #FF9800; }}
.college {{ background-color: #4CAF50; }}
.technical {{ background-color: #9C27B0; }}
.other {{ background-color: #607D8B; }}
@media (max-width: 768px) {{
.header h1 {{
font-size: 1.8em;
}}
.controls {{
padding: 10px;
}}
.filter-btn {{
font-size: 12px;
padding: 6px 12px;
}}
.legend {{
bottom: 10px;
right: 10px;
max-width: 200px;
}}
}}
</style>
</head>
<body>
<div class="header">
<h1>🎓 Карта учебных заведений Челябинской области</h1>
<p>Университеты, институты, колледжи, техникумы и училища • {total_count} учреждений</p>
</div>
<div class="controls">
<button class="filter-btn active" onclick="filterInstitutions('all')">🏛️ Все ({total_count})</button>
<button class="filter-btn" onclick="filterInstitutions('университет')">🎓 Университеты</button>
<button class="filter-btn" onclick="filterInstitutions('институт')">🏛️ Институты</button>
<button class="filter-btn" onclick="filterInstitutions('колледж')">📚 Колледжи</button>
<button class="filter-btn" onclick="filterInstitutions('техникум')">🔧 Техникумы</button>
<button class="filter-btn" onclick="filterInstitutions('училище')">⚙️ Училища</button>
<div class="stats">
<span id="showing-count">{total_count}</span> из {total_count} учреждений
</div>
</div>
<div id="map"></div>
<div class="legend">
<h4>Легенда</h4>
<div class="legend-item">
<div class="legend-color university"></div>
<span>Университеты</span>
</div>
<div class="legend-item">
<div class="legend-color institute"></div>
<span>Институты</span>
</div>
<div class="legend-item">
<div class="legend-color college"></div>
<span>Колледжи</span>
</div>
<div class="legend-item">
<div class="legend-color technical"></div>
<span>Техникумы/Училища</span>
</div>
<div class="legend-item">
<div class="legend-color other"></div>
<span>Другие</span>
</div>
</div>
<script>
// Данные учебных заведений
const institutionsData = {json.dumps(institutions, ensure_ascii=False, indent=2)};
// Инициализация карты
const map = L.map('map').setView([{center_lat}, {center_lng}], 8);
// Добавляем слой OpenStreetMap
L.tileLayer('https://{{s}}.tile.openstreetmap.org/{{z}}/{{x}}/{{y}}.png', {{
maxZoom: 19,
attribution: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}}).addTo(map);
// Массив всех маркеров
let allMarkers = [];
// Функция определения цвета маркера по типу учреждения
function getMarkerColor(institution) {{
const name = institution.name.toLowerCase();
if (name.includes('университет')) return '#2196F3';
if (name.includes('институт')) return '#FF9800';
if (name.includes('колледж')) return '#4CAF50';
if (name.includes('техникум') || name.includes('училище')) return '#9C27B0';
return '#607D8B';
}}
// Функция создания пользовательского маркера
function createCustomMarker(color) {{
return L.divIcon({{
className: 'custom-marker',
html: `<div style="
width: 20px;
height: 20px;
background-color: ${{color}};
border: 3px solid white;
border-radius: 50%;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
"></div>`,
iconSize: [20, 20],
iconAnchor: [10, 10]
}});
}}
// Добавление маркеров на карту
function addMarkersToMap() {{
institutionsData.forEach((institution, index) => {{
const lat = institution.latitude;
const lng = institution.longitude;
if (lat && lng && lat !== 0 && lng !== 0) {{
const color = getMarkerColor(institution);
const customIcon = createCustomMarker(color);
const marker = L.marker([lat, lng], {{ icon: customIcon }});
// Создаем содержимое всплывающего окна
let popupContent = `
<div style="max-width: 300px; font-family: Arial, sans-serif;">
<h3 style="margin: 0 0 10px 0; color: #2E7D32; font-size: 16px;">
${{institution.name}}
</h3>
`;
if (institution.official_name && institution.official_name !== institution.name) {{
popupContent += `
<p style="margin: 5px 0; font-size: 13px; color: #666; font-style: italic;">
${{institution.official_name}}
</p>
`;
}}
if (institution.address) {{
popupContent += `
<p style="margin: 5px 0; font-size: 14px;">
<strong>📍 Адрес:</strong> ${{institution.address}}
</p>
`;
}}
if (institution.city) {{
popupContent += `
<p style="margin: 5px 0; font-size: 14px;">
<strong>🏙️ Город:</strong> ${{institution.city}}
</p>
`;
}}
if (institution.phone) {{
popupContent += `
<p style="margin: 5px 0; font-size: 14px;">
<strong>📞 Телефон:</strong>
<a href="tel:${{institution.phone}}" style="color: #4CAF50;">
${{institution.phone}}
</a>
</p>
`;
}}
if (institution.website) {{
popupContent += `
<p style="margin: 5px 0; font-size: 14px;">
<strong>🌐 Сайт:</strong>
<a href="${{institution.website}}" target="_blank" style="color: #4CAF50;">
Перейти на сайт
</a>
</p>
`;
}}
if (institution.amenity) {{
popupContent += `
<p style="margin: 5px 0; font-size: 13px; color: #666;">
<strong>Тип:</strong> ${{institution.amenity}}
</p>
`;
}}
popupContent += `
<p style="margin: 10px 0 0 0; font-size: 11px; color: #999;">
Источник: ${{institution.source}}
</p>
</div>
`;
marker.bindPopup(popupContent);
marker.institutionData = institution;
marker.addTo(map);
allMarkers.push(marker);
}}
}});
console.log(`Добавлено ${{allMarkers.length}} маркеров на карту`);
}}
// Функция фильтрации учреждений
function filterInstitutions(filterType) {{
// Обновляем активную кнопку
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
let visibleCount = 0;
allMarkers.forEach(marker => {{
const institution = marker.institutionData;
let shouldShow = false;
if (filterType === 'all') {{
shouldShow = true;
}} else {{
const name = institution.name.toLowerCase();
shouldShow = name.includes(filterType);
}}
if (shouldShow) {{
map.addLayer(marker);
visibleCount++;
}} else {{
map.removeLayer(marker);
}}
}});
// Обновляем счетчик
document.getElementById('showing-count').textContent = visibleCount;
// Подгоняем карту под видимые маркеры
if (visibleCount > 0) {{
const visibleMarkers = allMarkers.filter(marker => map.hasLayer(marker));
if (visibleMarkers.length > 1) {{
const group = new L.featureGroup(visibleMarkers);
map.fitBounds(group.getBounds(), {{padding: [20, 20]}});
}}
}}
}}
// Инициализация после загрузки страницы
document.addEventListener('DOMContentLoaded', function() {{
console.log('Загрузка карты с данными об учебных заведениях...');
addMarkersToMap();
// Подгоняем карту под все маркеры
if (allMarkers.length > 0) {{
setTimeout(() => {{
const group = new L.featureGroup(allMarkers);
map.fitBounds(group.getBounds(), {{padding: [50, 50]}});
}}, 1000);
}}
}});
</script>
</body>
</html>"""
# Сохраняем HTML файл
output_file = "chelyabinsk_educational_map_static.html"
try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✅ Статическая карта создана: {output_file}")
return output_file
except Exception as e:
print(f"❌ Ошибка создания карты: {e}")
return None
def main():
"""Основная функция"""
print("=" * 80)
print("СОЗДАНИЕ СТАТИЧЕСКОЙ КАРТЫ УЧЕБНЫХ ЗАВЕДЕНИЙ")
print("=" * 80)
# Проверяем наличие файла с данными
json_file = "chelyabinsk_educational_institutions.json"
if not os.path.exists(json_file):
print(f"❌ Файл с данными {json_file} не найден!")
print("Сначала запустите collect_educational_data.py для сбора данных")
return
# Создаем карту
output_file = create_static_map(json_file)
if output_file:
print(f"\n🎯 Карта готова!")
print(f"📁 Файл: {os.path.abspath(output_file)}")
print(f"🌐 Откройте файл в браузере для просмотра")
# Предлагаем открыть в браузере
open_browser = input("\nОткрыть карту в браузере? (y/N): ").strip().lower()
if open_browser in ['y', 'yes', 'да', 'д']:
import webbrowser
try:
webbrowser.open(f'file://{os.path.abspath(output_file)}')
print("🚀 Карта открыта в браузере!")
except Exception as e:
print(f"Не удалось открыть браузер: {e}")
else:
print("Не удалось создать карту")
if __name__ == "__main__":
main()