xaxaxax
This commit is contained in:
477
create_static_map.py
Normal file
477
create_static_map.py
Normal 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()
|
||||
Reference in New Issue
Block a user