Files
map_yandex/create_static_map.py
2025-09-23 20:13:51 +05:00

477 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()