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

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.12

418
README.md Normal file
View File

@@ -0,0 +1,418 @@
# 🎓 Карта образовательных учреждений Челябинска# 🎓 Карта колледжей и техникумов Челябинска# Карта колледжей Челябинска
Интерактивная карта университетов, институтов, колледжей, техникумов, лицеев и училищ города Челябинска с использованием Yandex Maps API.
## 🎯 Что это?Интерактивная карта учебных заведений среднего профессионального образования в Челябинске с использованием Yandex Maps API.Интерактивная карта колледжей Челябинска с использованием Yandex Maps API.
Этот проект создает интерактивную карту, на которой отмечены все основные образовательные учреждения Челябинска. Карта включает в себя информацию о:
- Университетах и институтах ## 📋 Описание## Описание
- Колледжах и техникумах
- Лицеях и училищах
- Специализациях учреждений
- Контактной информацииПриложение создает интерактивную карту с 27 колледжами и техникумами Челябинска, найденными через 2GIS Places API. Карта включает подробную информацию о каждом учебном заведении, их специальностях и контактах.Этот проект создает интерактивную веб-карту с метками всех основных колледжей Челябинска. Каждая метка содержит подробную информацию об учебном заведении, включая:
- Адресах и координатах
## 🗺️ Функциональность
## 🚀 Установка и запуск- Название колледжа
- **Интерактивная карта** - удобное перемещение и масштабирование
- **Фильтрация по специальностям** - быстрый поиск нужного направления- Адрес
- **Подробная информация** - всплывающие окна с данными об учреждениях
- **Адаптивный дизайн** - работает на всех устройствах1. Убедитесь, что установлен Python 3.8+ и uv:- Описание специальностей
- **Статистика** - подсчет найденных учреждений
```bash- Контактную информацию (телефон, веб-сайт)
## 🏛️ База данных
# Проверить установку uv- Координаты на карте
В проекте содержится информация о **72 образовательных учреждениях** Челябинска:
uv --version
### По типам:
- **Университеты**: 4 учреждения```## Возможности
- **Институты**: 5 учреждений
- **Академии**: 1 учреждение
- **Колледжи**: 50 учреждений
- **Техникумы**: 7 учреждений2. Установите зависимости:- 📍 10 колледжей с точными координатами
- **Лицеи**: 3 учреждения
- **Училища**: 2 учреждения```bash- 🗺️ Интерактивная карта на базе Yandex Maps API
### По специализациям:uv sync- 💬 Всплывающие окна с подробной информацией
- Техника и технологии
- Экономика и управление ```- 📱 Адаптивный дизайн для мобильных устройств
- Медицина и здравоохранение
- Педагогика и образование- 🔍 Поиск и масштабирование карты
- Строительство и архитектура
- Искусство и культура3. Запустите приложение:- 📋 Список всех колледжей под картой
- Транспорт и логистика
- Информационные технологии```bash
- Право и юриспруденция
- Пищевая промышленностьpython main.py## Установка и запуск
- Энергетика
- Сельское хозяйство```
- Металлургия
- Химия### Предварительные требования
- Спорт и физическая культура
4. Откроется браузер с интерактивной картой колледжей.
## 🚀 Как использовать
- Python 3.12 или выше
### 1. Установка зависимостей
## ✨ Функционал- Пакетный менеджер uv (или pip)
Убедитесь, что у вас установлен Python 3.8+ и uv:
```bash
# Проверить установку uv- **Интерактивная карта** с 27 метками колледжей и техникумов### Установка зависимостей
uv --version
- **Фильтрация по специальностям**: строительство, машиностроение, энергетика, экономика, медицина, педагогика, питание, транспорт, IT, право
# Установить зависимости
uv sync- **Подробная информация** о каждом учебном заведении в всплывающих окнах```bash
```
- **Контактная информация**: телефоны и сайты (где доступно)# Используя uv (рекомендуется)
### 2. Запуск приложения
- **Адаптивный дизайн** для мобильных устройствuv sync
```bash
uv run python main.py- **Автоматическое масштабирование** карты под выбранные фильтры
```
# Или используя pip
Приложение:
1. Загрузит данные об образовательных учреждениях## 📁 Структура проектаpip install -r requirements.txt
2. Создаст HTML-файл с интерактивной картой
3. Покажет статистику по всем учреждениям```
4. Предложит открыть карту в браузере
```
### 3. Настройка API ключа (опционально)
map_yandex/### Получение API ключа Yandex Maps
Для полного функционала получите бесплатный API ключ Yandex Maps:
1. Перейдите на https://developer.tech.yandex.ru/├── main.py # Основной скрипт приложения
2. Зарегистрируйтесь и получите ключ
3. Введите ключ при запуске приложения├── map_template.html # HTML шаблон карты1. Перейдите на [Yandex Developer Console](https://developer.tech.yandex.ru/)
## 📁 Структура проекта├── colleges_data.json # База данных колледжей (27 учреждений)2. Создайте новый проект или выберите существующий
```├── colleges_data_backup.json # Резервная копия данных3. Подключите Maps JavaScript API 2.1
map_yandex/
├── main.py # Основное приложение├── pyproject.toml # Конфигурация проекта и зависимости4. Скопируйте полученный API ключ
├── map_template.html # HTML шаблон карты
├── colleges_data.json # База данных учреждений├── uv.lock # Заблокированные версии зависимостей
├── chelyabinsk_colleges_map.html # Готовая карта (создается автоматически)
├── pyproject.toml # Конфигурация проекта└── README.md # Документация### Запуск
└── README.md # Документация
``````
## 🎯 Основные учреждения```bash
### Университеты:## 🏫 База данныхpython main.py
- Южно-Уральский государственный университет (ЮУрГУ)
- Челябинский государственный университет (ЧелГУ)```
- Челябинский государственный педагогический университет (ЧГПУ)
- Уральский государственный университет физической культурыБаза содержит **27 колледжей и техникумов**, включая:
### Институты:Программа:
- Челябинский филиал РАНХиГС
- Челябинский институт путей сообщения**Колледжи (22):**1. Загрузит данные о колледжах
- Южно-Уральский институт управления и экономики
- Челябинский филиал РГСУ- Челябинский юридический колледж2. Выведет список всех колледжей в консоль
- Челябинский филиал МПСУ
- Южно-Уральский государственный колледж 3. Запросит API ключ Yandex Maps (можно пропустить)
### Ведущие колледжи:
- Челябинский государственный колледж индустрии питания и торговли- Челябинский педагогический колледж №1 и №24. Создаст HTML файл с картой
- Челябинский медицинский колледж
- Челябинский базовый медицинский колледж- Челябинский медицинский колледж5. Предложит открыть карту в браузере
- Челябинский педагогический колледж №1
- Челябинский юридический колледж- Челябинский энергетический колледж им. С. М. Кирова
- Челябинский машиностроительный колледж
- Челябинский металлургический колледж- Южно-Уральский государственный технический колледж## Структура проекта
- Челябинский энергетический колледж
- Челябинский строительный колледж- Челябинский государственный колледж индустрии питания и торговли
- И многие другие...
- И другие...```
## 🔧 Технические детали
map_yandex/
- **Python 3.8+** - основной язык разработки
- **Jinja2** - шаблонизация HTML**Техникумы (5):**├── main.py # Основной скрипт
- **Yandex Maps API 2.1** - интерактивная карта
- **HTML5/CSS3/JavaScript** - фронтенд- Челябинский автотранспортный техникум├── map_template.html # HTML шаблон карты
- **JSON** - хранение данных
- Челябинский механико-технологический техникум├── colleges_data.json # Данные о колледжах
## 📱 Совместимость
- Челябинский радиотехнический техникум├── pyproject.toml # Конфигурация проекта
Карта работает во всех современных браузерах:
- Chrome/Chromium- Челябинский государственный промышленно-гуманитарный техникум им. А.В. Яковлева├── README.md # Документация
- Firefox
- Safari- Техникум-интернат инвалидов им. И.И. Шуба└── chelyabinsk_colleges_map.html # Сгенерированная карта
- Edge
- Мобильные браузеры```
## 🌟 Особенности## 🛠 Технологии
- **Полная база данных** - 72 учреждения всех типов## Колледжи на карте
- **Точные координаты** - все адреса геолокализованы
- **Детальная информация** - специальности, контакты, описания- **Python 3.8+** - основной язык разработки
- **Удобная фильтрация** - поиск по направлениям подготовки
- **Адаптивный дизайн** - работает на всех устройствах- **Yandex Maps API 2.1** - картографический сервис1. **Челябинский государственный колледж индустрии питания и торговли**
- **Оффлайн-совместимость** - сохраненная карта работает без интернета
- **Jinja2** - шаблонизатор HTML - Специализация: общественное питание, торговля, гостиничный сервис
## 📊 Статистика
- **HTML5/CSS3/JavaScript** - фронтенд
- **72** образовательных учреждения
- **6** типов учреждений (университеты, институты, академии, колледжи, техникумы, лицеи, училища)- **2GIS Places API** - источник данных о колледжах2. **Челябинский государственный промышленно-гуманитарный техникум им. А.В. Яковлева**
- **15+** направлений специализации
- **100%** покрытие координатами- **uv** - современный менеджер пакетов Python - Специализация: машиностроение, металлургия, IT
- **Полная** контактная информация
## 🤝 Вклад в проект
## 📊 Данные3. **Челябинский техникум текстильной и легкой промышленности**
Если вы нашли ошибку или хотите добавить новое учреждение:
1. Создайте Issue - Специализация: текстиль, швейное производство, дизайн
2. Внесите изменения в `colleges_data.json`
3. Создайте Pull RequestДанные о колледжах получены через 2GIS Places API и включают:
## 📄 Лицензия- Названия учебных заведений4. **Челябинский медицинский колледж**
Проект распространяется под лицензией MIT. Данные об учебных заведениях собраны из открытых источников.- Координаты расположения - Специализация: медицина, здравоохранение
## 🆘 Поддержка- Специальности обучения (автоматически определяются по типу учреждения)
При возникновении вопросов:- Контактную информацию (где доступно)5. **Челябинский государственный колледж физической культуры**
1. Проверьте, что все зависимости установлены
2. Убедитесь, что Python версии 3.8+- Описания - Специализация: физкультура, спорт
3. Проверьте доступность интернета для загрузки карт Yandex
---
## 🔧 Разработка6. **Челябинский техникум промышленности и городского хозяйства им. Я.П. Осадчего**
**Автор**: Создано с использованием данных 2GIS и Yandex Maps API
**Версия**: 2.0 (расширенная база данных) - Специализация: ЖКХ, строительство, энергетика
**Последнее обновление**: Сентябрь 2025
Для разработки используется современный стек Python:
- `uv` для управления зависимостями7. **Челябинский автотранспортный техникум**
- `pyproject.toml` для конфигурации проекта - Специализация: автотранспорт, механика
- Автоматическая генерация HTML из шаблонов
8. **Челябинский педагогический колледж № 1**
## 📝 Лицензия - Специализация: педагогика, дошкольное образование
Проект создан в образовательных целях.9. **Челябинский радиотехнический техникум**
- Специализация: радиоэлектроника, телекоммуникации
10. **Челябинский колледж комтехно**
- Специализация: IT, экономика, коммерция
## Использование без API ключа
Карта будет работать и без API ключа Yandex Maps, но с ограничениями:
- Возможны водяные знаки на карте
- Ограничения по количеству запросов
- Некоторые функции могут быть недоступны
Для полноценной работы рекомендуется получить бесплатный API ключ.
## Технические детали
- **Backend**: Python 3.12, Jinja2
- **Frontend**: HTML5, CSS3, JavaScript, Yandex Maps API 2.1
- **Данные**: JSON файл с координатами и описаниями колледжей
- **Иконки**: Стандартные иконки Yandex Maps (образование)
## Настройка
Вы можете легко добавить новые колледжи, отредактировав файл `colleges_data.json`:
```json
{
"name": "Название колледжа",
"address": "Адрес",
"lat": 55.1234,
"lon": 61.5678,
"description": "Описание специальностей",
"website": "http://example.com",
"phone": "+7 (351) 123-45-67"
}
```
## Лицензия
Этот проект создан в образовательных целях. Данные о колледжах собраны из открытых источников.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

381
collect_educational_data.py Normal file
View File

@@ -0,0 +1,381 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Сбор данных об учебных заведениях Челябинской области через бесплатные API
Использует: OpenStreetMap Nominatim API, Overpass API для сбора данных
"""
import requests
import json
import time
from typing import List, Dict, Optional
from urllib.parse import quote
class EducationalInstitutionsCollector:
"""Сборщик данных об учебных заведениях через бесплатные API"""
def __init__(self):
self.nominatim_url = "https://nominatim.openstreetmap.org"
self.overpass_url = "https://overpass-api.de/api/interpreter"
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Educational Institutions Collector 1.0 (educational purposes)'
})
self.results = []
def search_nominatim(self, query: str, region: str = "Челябинская область") -> List[Dict]:
"""Поиск через Nominatim API"""
print(f"Поиск через Nominatim: {query}")
params = {
'q': f"{query}, {region}",
'format': 'json',
'limit': 50,
'addressdetails': 1,
'extratags': 1,
'countrycodes': 'ru'
}
try:
response = self.session.get(f"{self.nominatim_url}/search", params=params)
response.raise_for_status()
time.sleep(1) # Уважаем лимиты API
data = response.json()
print(f"Найдено {len(data)} результатов")
return data
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса к Nominatim: {e}")
return []
def search_overpass(self, amenity_type: str) -> List[Dict]:
"""Поиск через Overpass API (OpenStreetMap)"""
print(f"Поиск через Overpass API: {amenity_type}")
# Bounding box для Челябинской области (приблизительно)
bbox = "53.5,58.0,56.5,63.5" # south,west,north,east
query = f"""
[out:json][timeout:60];
(
node["amenity"="{amenity_type}"]({bbox});
way["amenity"="{amenity_type}"]({bbox});
relation["amenity"="{amenity_type}"]({bbox});
);
out center meta;
"""
try:
response = self.session.post(self.overpass_url, data=query)
response.raise_for_status()
time.sleep(2) # Уважаем лимиты API
data = response.json()
elements = data.get('elements', [])
print(f"Найдено {len(elements)} объектов")
return elements
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса к Overpass API: {e}")
return []
def process_nominatim_result(self, item: Dict) -> Dict:
"""Обработка результата от Nominatim"""
address = item.get('address', {})
return {
'id': f"nominatim_{item.get('place_id')}",
'name': item.get('display_name', '').split(',')[0].strip(),
'full_name': item.get('display_name', ''),
'address': self._format_address(address),
'latitude': float(item.get('lat', 0)),
'longitude': float(item.get('lon', 0)),
'type': item.get('type', ''),
'category': item.get('category', ''),
'city': address.get('city') or address.get('town') or address.get('village', ''),
'region': address.get('state', ''),
'source': 'OpenStreetMap Nominatim',
'osm_id': item.get('osm_id'),
'osm_type': item.get('osm_type'),
'raw_data': item
}
def process_overpass_result(self, item: Dict) -> Dict:
"""Обработка результата от Overpass API"""
tags = item.get('tags', {})
# Определяем координаты
if 'center' in item:
lat = item['center']['lat']
lon = item['center']['lon']
else:
lat = item.get('lat', 0)
lon = item.get('lon', 0)
# Форматируем адрес
address_parts = []
if tags.get('addr:street'):
if tags.get('addr:housenumber'):
address_parts.append(f"{tags['addr:street']}, {tags['addr:housenumber']}")
else:
address_parts.append(tags['addr:street'])
if tags.get('addr:city'):
address_parts.append(tags['addr:city'])
address = ', '.join(address_parts) if address_parts else ''
return {
'id': f"osm_{item.get('id')}",
'name': tags.get('name', ''),
'official_name': tags.get('official_name', ''),
'address': address,
'latitude': float(lat),
'longitude': float(lon),
'amenity': tags.get('amenity', ''),
'operator': tags.get('operator', ''),
'website': tags.get('website', ''),
'phone': tags.get('phone', ''),
'email': tags.get('email', ''),
'city': tags.get('addr:city', ''),
'region': tags.get('addr:state', ''),
'opening_hours': tags.get('opening_hours', ''),
'source': 'OpenStreetMap Overpass',
'osm_id': item.get('id'),
'osm_type': item.get('type'),
'raw_data': item
}
def _format_address(self, address: Dict) -> str:
"""Форматирование адреса"""
parts = []
# Улица и номер дома
if address.get('road'):
if address.get('house_number'):
parts.append(f"{address['road']}, {address['house_number']}")
else:
parts.append(address['road'])
# Город
city = address.get('city') or address.get('town') or address.get('village')
if city:
parts.append(city)
return ', '.join(parts)
def collect_all_data(self) -> List[Dict]:
"""Собираем все данные об учебных заведениях"""
print("=" * 80)
print("СБОР ДАННЫХ ОБ УЧЕБНЫХ ЗАВЕДЕНИЯХ ЧЕЛЯБИНСКОЙ ОБЛАСТИ")
print("=" * 80)
all_institutions = []
seen_names = set()
# 1. Поиск через Nominatim
print("\n1. Поиск через Nominatim API...")
nominatim_queries = [
"университет",
"институт",
"колледж",
"техникум",
"училище",
"академия",
"ВУЗ",
"образовательное учреждение"
]
for query in nominatim_queries:
results = self.search_nominatim(query)
for item in results:
processed = self.process_nominatim_result(item)
if processed['name'] and processed['name'] not in seen_names:
all_institutions.append(processed)
seen_names.add(processed['name'])
# 2. Поиск через Overpass API
print("\n2. Поиск через Overpass API...")
overpass_amenities = [
"university",
"college",
"school" # может включать техникумы и училища
]
for amenity in overpass_amenities:
results = self.search_overpass(amenity)
for item in results:
processed = self.process_overpass_result(item)
if processed['name'] and processed['name'] not in seen_names:
# Фильтруем школы, оставляем только ВУЗы и колледжи
name = processed['name'].lower()
if any(word in name for word in ['университет', 'институт', 'колледж', 'техникум', 'училище', 'академия']):
all_institutions.append(processed)
seen_names.add(processed['name'])
# 3. Добавляем известные учебные заведения
print("\n3. Добавляем известные учебные заведения...")
known_institutions = self._get_known_institutions()
for institution in known_institutions:
if institution['name'] not in seen_names:
all_institutions.append(institution)
seen_names.add(institution['name'])
print(f"\nВсего найдено уникальных учебных заведений: {len(all_institutions)}")
return all_institutions
def _get_known_institutions(self) -> List[Dict]:
"""Список известных учебных заведений для дополнения"""
return [
{
'id': 'known_1',
'name': 'Южно-Уральский государственный университет',
'official_name': 'ФГАОУ ВО "Южно-Уральский государственный университет (национальный исследовательский университет)"',
'address': 'проспект Ленина, 76, Челябинск',
'latitude': 55.1549,
'longitude': 61.4289,
'phone': '+7 (351) 267-90-90',
'website': 'https://www.susu.ru',
'type': 'университет',
'city': 'Челябинск',
'region': 'Челябинская область',
'source': 'Дополнительные данные'
},
{
'id': 'known_2',
'name': 'Челябинский государственный университет',
'official_name': 'ФГБОУ ВО "Челябинский государственный университет"',
'address': 'ул. Братьев Кашириных, 129, Челябинск',
'latitude': 55.1605,
'longitude': 61.4025,
'phone': '+7 (351) 799-70-02',
'website': 'https://www.csu.ru',
'type': 'университет',
'city': 'Челябинск',
'region': 'Челябинская область',
'source': 'Дополнительные данные'
},
{
'id': 'known_3',
'name': 'Магнитогорский государственный технический университет',
'official_name': 'ФГБОУ ВО "Магнитогорский государственный технический университет им. Г.И. Носова"',
'address': 'проспект Ленина, 38, Магнитогорск',
'latitude': 53.4213,
'longitude': 59.0457,
'phone': '+7 (3519) 29-84-42',
'website': 'https://www.magtu.ru',
'type': 'университет',
'city': 'Магнитогорск',
'region': 'Челябинская область',
'source': 'Дополнительные данные'
},
{
'id': 'known_4',
'name': 'Южно-Уральский государственный медицинский университет',
'official_name': 'ФГБОУ ВО "Южно-Уральский государственный медицинский университет"',
'address': 'ул. Воровского, 64, Челябинск',
'latitude': 55.1634,
'longitude': 61.4012,
'phone': '+7 (351) 232-73-47',
'website': 'https://www.chelsma.ru',
'type': 'университет',
'city': 'Челябинск',
'region': 'Челябинская область',
'source': 'Дополнительные данные'
}
]
def save_to_json(self, data: List[Dict], filename: str = "chelyabinsk_educational_institutions.json"):
"""Сохранение данных в JSON"""
try:
output = {
'region': 'Челябинская область',
'collection_date': time.strftime('%Y-%m-%d %H:%M:%S'),
'total_count': len(data),
'sources': ['OpenStreetMap Nominatim', 'OpenStreetMap Overpass', 'Дополнительные данные'],
'data_types': {
'universities': len([x for x in data if 'университет' in x.get('name', '').lower() or x.get('type') == 'университет']),
'institutes': len([x for x in data if 'институт' in x.get('name', '').lower()]),
'colleges': len([x for x in data if any(word in x.get('name', '').lower() for word in ['колледж', 'техникум', 'училище'])]),
},
'institutions': data
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"\n✅ Данные сохранены в файл: {filename}")
return filename
except Exception as e:
print(f"❌ Ошибка сохранения: {e}")
return None
def main():
"""Основная функция"""
collector = EducationalInstitutionsCollector()
# Собираем данные
institutions = collector.collect_all_data()
if institutions:
# Сохраняем в JSON
filename = collector.save_to_json(institutions)
# Выводим статистику
print("\n" + "=" * 80)
print("СТАТИСТИКА СОБРАННЫХ ДАННЫХ")
print("=" * 80)
cities = {}
types = {}
for inst in institutions:
city = inst.get('city', 'Не указан')
cities[city] = cities.get(city, 0) + 1
name = inst.get('name', '').lower()
if 'университет' in name:
inst_type = 'Университеты'
elif 'институт' in name:
inst_type = 'Институты'
elif 'колледж' in name:
inst_type = 'Колледжи'
elif 'техникум' in name:
inst_type = 'Техникумы'
elif 'училище' in name:
inst_type = 'Училища'
else:
inst_type = 'Другие'
types[inst_type] = types.get(inst_type, 0) + 1
print(f"Всего учебных заведений: {len(institutions)}")
print(f"\nПо типам:")
for inst_type, count in sorted(types.items(), key=lambda x: x[1], reverse=True):
print(f" {inst_type}: {count}")
print(f"\nПо городам:")
for city, count in sorted(cities.items(), key=lambda x: x[1], reverse=True)[:10]:
print(f" {city}: {count}")
print(f"\nПримеры найденных учреждений:")
for i, inst in enumerate(institutions[:5], 1):
print(f"{i}. {inst['name']}")
if inst.get('address'):
print(f" 📍 {inst['address']}")
if inst.get('phone'):
print(f" 📞 {inst['phone']}")
if inst.get('website'):
print(f" 🌐 {inst['website']}")
print()
else:
print("Не удалось собрать данные об учебных заведениях")
if __name__ == "__main__":
main()

1236
colleges_data.json Normal file

File diff suppressed because it is too large Load Diff

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()

View File

@@ -0,0 +1,400 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Демо-данные заведений среднего специального и высшего образования в Челябинске
Для случая, когда API 2GIS недоступен без ключа
"""
import json
def get_demo_institutions():
"""Возвращает список заведений среднего специального и высшего образования в Челябинске"""
institutions = [
# Университеты
{
"id": "demo_1",
"name": "Южно-Уральский государственный университет",
"full_name": "ФГАОУ ВО «Южно-Уральский государственный университет (национальный исследовательский университет)»",
"address": "проспект Ленина, 76, Челябинск",
"latitude": 55.1549,
"longitude": 61.4289,
"phones": ["+7 (351) 267-90-90"],
"categories": ["Университет", "Высшее образование"],
"source": "Демо-данные"
},
{
"id": "demo_2",
"name": "Челябинский государственный университет",
"full_name": "ФГБОУ ВО «Челябинский государственный университет»",
"address": "ул. Братьев Кашириных, 129, Челябинск",
"latitude": 55.1605,
"longitude": 61.4025,
"phones": ["+7 (351) 799-70-02"],
"categories": ["Университет", "Высшее образование"],
"source": "Демо-данные"
},
{
"id": "demo_3",
"name": "Челябинский государственный педагогический университет",
"full_name": "ФГБОУ ВО «Южно-Уральский государственный гуманитарно-педагогический университет»",
"address": "проспект Ленина, 69, Челябинск",
"latitude": 55.1545,
"longitude": 61.4267,
"phones": ["+7 (351) 216-56-01"],
"categories": ["Университет", "Педагогическое образование"],
"source": "Демо-данные"
},
{
"id": "demo_4",
"name": "Уральский государственный университет физической культуры",
"full_name": "ФГБОУ ВО «Уральский государственный университет физической культуры»",
"address": "ул. Орджоникидзе, 1, Челябинск",
"latitude": 55.1674,
"longitude": 61.3988,
"phones": ["+7 (351) 237-04-54"],
"categories": ["Университет", "Физическая культура"],
"source": "Демо-данные"
},
# Институты
{
"id": "demo_5",
"name": "Челябинский институт экономики и права",
"full_name": "АНО ВО «Челябинский институт экономики и права им. М.В. Ладошина»",
"address": "ул. Свободы, 155, Челябинск",
"latitude": 55.1890,
"longitude": 61.3176,
"phones": ["+7 (351) 217-18-18"],
"categories": ["Институт", "Экономика и право"],
"source": "Демо-данные"
},
{
"id": "demo_6",
"name": "Челябинский филиал РАНХиГС",
"full_name": "Челябинский филиал Российской академии народного хозяйства и государственной службы",
"address": "ул. Комарова, 26, Челябинск",
"latitude": 55.1823,
"longitude": 61.3895,
"phones": ["+7 (351) 220-11-91"],
"categories": ["Филиал института", "Государственная служба"],
"source": "Демо-данные"
},
# Колледжи и техникумы
{
"id": "demo_7",
"name": "Челябинский техникум промышленности и городского хозяйства",
"full_name": "ГБПОУ «Челябинский техникум промышленности и городского хозяйства имени Я.П. Осадчего»",
"address": "ул. Энтузиастов, 15, Челябинск",
"latitude": 55.1456,
"longitude": 61.3789,
"phones": ["+7 (351) 256-56-82"],
"categories": ["Техникум", "Промышленность"],
"source": "Демо-данные"
},
{
"id": "demo_8",
"name": "Челябинский медицинский колледж",
"full_name": "ГБПОУ «Челябинский базовый медицинский колледж»",
"address": "ул. Свободы, 159а, Челябинск",
"latitude": 55.1901,
"longitude": 61.3164,
"phones": ["+7 (351) 232-72-01"],
"categories": ["Медицинский колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_9",
"name": "Челябинский государственный колледж индустрии питания и торговли",
"full_name": "ГБПОУ «Челябинский государственный колледж индустрии питания и торговли»",
"address": "ул. Энтузиастов, 21, Челябинск",
"latitude": 55.1444,
"longitude": 61.3812,
"phones": ["+7 (351) 256-71-90"],
"categories": ["Колледж", "Торговля и питание"],
"source": "Демо-данные"
},
{
"id": "demo_10",
"name": "Челябинский педагогический колледж №1",
"full_name": "ГБПОУ «Челябинский педагогический колледж № 1»",
"address": "ул. Красная, 305, Челябинск",
"latitude": 55.1733,
"longitude": 61.3456,
"phones": ["+7 (351) 263-89-50"],
"categories": ["Педагогический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_11",
"name": "Челябинский технологический колледж",
"full_name": "ГБПОУ «Челябинский технологический колледж»",
"address": "ул. Машиностроителей, 23, Челябинск",
"latitude": 55.1267,
"longitude": 61.4567,
"phones": ["+7 (351) 729-99-60"],
"categories": ["Технологический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_12",
"name": "Челябинский юридический колледж",
"full_name": "ЧОУ СПО «Челябинский юридический колледж»",
"address": "ул. Цвиллинга, 60, Челябинск",
"latitude": 55.1645,
"longitude": 61.4156,
"phones": ["+7 (351) 266-55-20"],
"categories": ["Юридический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_13",
"name": "Южно-Уральский государственный технический колледж",
"full_name": "ГБПОУ «Южно-Уральский государственный технический колледж»",
"address": "ул. Воровского, 16, Челябинск",
"latitude": 55.1598,
"longitude": 61.4034,
"phones": ["+7 (351) 263-44-36"],
"categories": ["Технический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_14",
"name": "Челябинский экономический колледж",
"full_name": "ЧОУ СПО «Челябинский экономический колледж»",
"address": "ул. Кирова, 159, Челябинск",
"latitude": 55.1534,
"longitude": 61.3789,
"phones": ["+7 (351) 263-78-05"],
"categories": ["Экономический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_15",
"name": "Челябинский автотранспортный техникум",
"full_name": "ГБПОУ «Челябинский автотранспортный техникум»",
"address": "ул. Монакова, 33, Челябинск",
"latitude": 55.1123,
"longitude": 61.4234,
"phones": ["+7 (351) 721-81-67"],
"categories": ["Автотранспортный техникум", "Среднее специальное образование"],
"source": "Демо-данные"
},
# Дополнительные университеты и институты
{
"id": "demo_16",
"name": "Челябинский государственный институт культуры",
"full_name": "ФГБОУ ВО «Челябинский государственный институт культуры»",
"address": "ул. Орджоникидзе, 36а, Челябинск",
"latitude": 55.1699,
"longitude": 61.4018,
"phones": ["+7 (351) 263-83-18"],
"categories": ["Институт культуры", "Высшее образование"],
"source": "Демо-данные"
},
{
"id": "demo_17",
"name": "Магнитогорский государственный технический университет (филиал)",
"full_name": "Челябинский филиал ФГБОУ ВО «МГТУ им. Г.И. Носова»",
"address": "ул. 250-летия Челябинска, 17, Челябинск",
"latitude": 55.1589,
"longitude": 61.3756,
"phones": ["+7 (351) 799-74-45"],
"categories": ["Филиал университета", "Техническое образование"],
"source": "Демо-данные"
},
{
"id": "demo_18",
"name": "Российский государственный профессионально-педагогический университет (филиал)",
"full_name": "Челябинский филиал ФГАОУ ВО «РГППУ»",
"address": "ул. Машиностроителей, 11, Челябинск",
"latitude": 55.1298,
"longitude": 61.4523,
"phones": ["+7 (351) 729-99-10"],
"categories": ["Филиал университета", "Педагогическое образование"],
"source": "Демо-данные"
},
# Дополнительные колледжи и техникумы
{
"id": "demo_19",
"name": "Челябинский государственный промышленно-гуманитарный техникум",
"full_name": "ГБПОУ «Челябинский государственный промышленно-гуманитарный техникум им. А.В. Яковлева»",
"address": "ул. Красноармейская, 88, Челябинск",
"latitude": 55.1678,
"longitude": 61.4125,
"phones": ["+7 (351) 263-22-89"],
"categories": ["Промышленно-гуманитарный техникум", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_20",
"name": "Челябинский радиотехнический техникум",
"full_name": "ГБПОУ «Челябинский радиотехнический техникум»",
"address": "ул. Цвиллинга, 38, Челябинск",
"latitude": 55.1634,
"longitude": 61.4089,
"phones": ["+7 (351) 232-08-71"],
"categories": ["Радиотехнический техникум", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_21",
"name": "Челябинский механико-технологический техникум",
"full_name": "ГБПОУ «Челябинский механико-технологический техникум»",
"address": "ул. Агалакова, 102, Челябинск",
"latitude": 55.1445,
"longitude": 61.3756,
"phones": ["+7 (351) 256-78-34"],
"categories": ["Механико-технологический техникум", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_22",
"name": "Челябинский строительный колледж",
"full_name": "ГБПОУ «Челябинский строительный колледж»",
"address": "ул. Электростальская, 12, Челябинск",
"latitude": 55.1234,
"longitude": 61.4567,
"phones": ["+7 (351) 729-88-90"],
"categories": ["Строительный колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_23",
"name": "Челябинский энергетический колледж",
"full_name": "ГБПОУ «Челябинский энергетический колледж им. С.М. Кирова»",
"address": "ул. Энергетиков, 44, Челябинск",
"latitude": 55.1345,
"longitude": 61.4234,
"phones": ["+7 (351) 721-17-17"],
"categories": ["Энергетический колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_24",
"name": "Южно-Уральский многопрофильный колледж",
"full_name": "ГБПОУ «Южно-Уральский многопрофильный колледж»",
"address": "ул. Доватора, 16а, Челябинск",
"latitude": 55.1456,
"longitude": 61.3987,
"phones": ["+7 (351) 263-44-78"],
"categories": ["Многопрофильный колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_25",
"name": "Челябинский колледж физической культуры",
"full_name": "ГБПОУ «Челябинский колледж физической культуры»",
"address": "ул. Российская, 258, Челябинск",
"latitude": 55.1567,
"longitude": 61.3456,
"phones": ["+7 (351) 237-25-25"],
"categories": ["Колледж физической культуры", "Среднее специальное образование"],
"source": "Демо-данные"
},
{
"id": "demo_26",
"name": "Челябинский социально-профессиональный колледж",
"full_name": "ГБПОУ «Челябинский социально-профессиональный колледж «Сфера»",
"address": "ул. Чичерина, 38, Челябинск",
"latitude": 55.1678,
"longitude": 61.3234,
"phones": ["+7 (351) 264-65-65"],
"categories": ["Социально-профессиональный колледж", "Среднее специальное образование"],
"source": "Демо-данные"
},
# Частные образовательные учреждения
{
"id": "demo_27",
"name": "Международный институт дизайна и сервиса",
"full_name": "ЧОУ ВО «Международный институт дизайна и сервиса»",
"address": "ул. Воровского, 36, Челябинск",
"latitude": 55.1589,
"longitude": 61.4012,
"phones": ["+7 (351) 265-35-35"],
"categories": ["Частный институт", "Дизайн и сервис"],
"source": "Демо-данные"
},
{
"id": "demo_28",
"name": "Челябинский институт путей сообщения",
"full_name": "Челябинский институт путей сообщения (филиал УрГУПС)",
"address": "ул. Жукова, 99, Челябинск",
"latitude": 55.1234,
"longitude": 61.3789,
"phones": ["+7 (351) 263-88-88"],
"categories": ["Институт путей сообщения", "Транспорт"],
"source": "Демо-данные"
},
{
"id": "demo_29",
"name": "Уральский социально-экономический institut",
"full_name": "ЧОУ ВО «Уральский социально-экономический институт»",
"address": "ул. Ленина, 77, Челябинск",
"latitude": 55.1545,
"longitude": 61.4278,
"phones": ["+7 (351) 200-33-33"],
"categories": ["Частный институт", "Социально-экономическое образование"],
"source": "Демо-данные"
},
{
"id": "demo_30",
"name": "Челябинский колледж индустрии красоты",
"full_name": "ЧОУ СПО «Челябинский колледж индустрии красоты»",
"address": "ул. Труда, 179, Челябинск",
"latitude": 55.1723,
"longitude": 61.3567,
"phones": ["+7 (351) 700-44-44"],
"categories": ["Колледж красоты", "Среднее специальное образование"],
"source": "Демо-данные"
}
]
return institutions
def save_demo_data():
"""Сохраняет демо-данные в JSON файл"""
institutions = get_demo_institutions()
data = {
'city': 'Челябинск',
'source': 'Демо-данные (среднее специальное и высшее образование)',
'count': len(institutions),
'institutions': institutions
}
filename = "educational_institutions_demo.json"
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"Демо-данные сохранены в файл: {filename}")
print(f"Всего заведений: {len(institutions)}")
# Статистика по типам
categories_count = {}
for inst in institutions:
for category in inst.get('categories', []):
categories_count[category] = categories_count.get(category, 0) + 1
print("\nРаспределение по категориям:")
for category, count in sorted(categories_count.items(), key=lambda x: x[1], reverse=True):
print(f" {category}: {count}")
return filename
except Exception as e:
print(f"Ошибка при сохранении файла: {e}")
return None
if __name__ == "__main__":
print("Создание демо-данных заведений среднего специального и высшего образования")
print("="*75)
save_demo_data()

View File

@@ -0,0 +1,487 @@
{
"city": "Челябинск",
"source": "Демо-данные (среднее специальное и высшее образование)",
"count": 30,
"institutions": [
{
"id": "demo_1",
"name": "Южно-Уральский государственный университет",
"full_name": "ФГАОУ ВО «Южно-Уральский государственный университет (национальный исследовательский университет)»",
"address": "проспект Ленина, 76, Челябинск",
"latitude": 55.1549,
"longitude": 61.4289,
"phones": [
"+7 (351) 267-90-90"
],
"categories": [
"Университет",
"Высшее образование"
],
"source": "Демо-данные"
},
{
"id": "demo_2",
"name": "Челябинский государственный университет",
"full_name": "ФГБОУ ВО «Челябинский государственный университет»",
"address": "ул. Братьев Кашириных, 129, Челябинск",
"latitude": 55.1605,
"longitude": 61.4025,
"phones": [
"+7 (351) 799-70-02"
],
"categories": [
"Университет",
"Высшее образование"
],
"source": "Демо-данные"
},
{
"id": "demo_3",
"name": "Челябинский государственный педагогический университет",
"full_name": "ФГБОУ ВО «Южно-Уральский государственный гуманитарно-педагогический университет»",
"address": "проспект Ленина, 69, Челябинск",
"latitude": 55.1545,
"longitude": 61.4267,
"phones": [
"+7 (351) 216-56-01"
],
"categories": [
"Университет",
"Педагогическое образование"
],
"source": "Демо-данные"
},
{
"id": "demo_4",
"name": "Уральский государственный университет физической культуры",
"full_name": "ФГБОУ ВО «Уральский государственный университет физической культуры»",
"address": "ул. Орджоникидзе, 1, Челябинск",
"latitude": 55.1674,
"longitude": 61.3988,
"phones": [
"+7 (351) 237-04-54"
],
"categories": [
"Университет",
"Физическая культура"
],
"source": "Демо-данные"
},
{
"id": "demo_5",
"name": "Челябинский институт экономики и права",
"full_name": "АНО ВО «Челябинский институт экономики и права им. М.В. Ладошина»",
"address": "ул. Свободы, 155, Челябинск",
"latitude": 55.189,
"longitude": 61.3176,
"phones": [
"+7 (351) 217-18-18"
],
"categories": [
"Институт",
"Экономика и право"
],
"source": "Демо-данные"
},
{
"id": "demo_6",
"name": "Челябинский филиал РАНХиГС",
"full_name": "Челябинский филиал Российской академии народного хозяйства и государственной службы",
"address": "ул. Комарова, 26, Челябинск",
"latitude": 55.1823,
"longitude": 61.3895,
"phones": [
"+7 (351) 220-11-91"
],
"categories": [
"Филиал института",
"Государственная служба"
],
"source": "Демо-данные"
},
{
"id": "demo_7",
"name": "Челябинский техникум промышленности и городского хозяйства",
"full_name": "ГБПОУ «Челябинский техникум промышленности и городского хозяйства имени Я.П. Осадчего»",
"address": "ул. Энтузиастов, 15, Челябинск",
"latitude": 55.1456,
"longitude": 61.3789,
"phones": [
"+7 (351) 256-56-82"
],
"categories": [
"Техникум",
"Промышленность"
],
"source": "Демо-данные"
},
{
"id": "demo_8",
"name": "Челябинский медицинский колледж",
"full_name": "ГБПОУ «Челябинский базовый медицинский колледж»",
"address": "ул. Свободы, 159а, Челябинск",
"latitude": 55.1901,
"longitude": 61.3164,
"phones": [
"+7 (351) 232-72-01"
],
"categories": [
"Медицинский колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_9",
"name": "Челябинский государственный колледж индустрии питания и торговли",
"full_name": "ГБПОУ «Челябинский государственный колледж индустрии питания и торговли»",
"address": "ул. Энтузиастов, 21, Челябинск",
"latitude": 55.1444,
"longitude": 61.3812,
"phones": [
"+7 (351) 256-71-90"
],
"categories": [
"Колледж",
"Торговля и питание"
],
"source": "Демо-данные"
},
{
"id": "demo_10",
"name": "Челябинский педагогический колледж №1",
"full_name": "ГБПОУ «Челябинский педагогический колледж № 1»",
"address": "ул. Красная, 305, Челябинск",
"latitude": 55.1733,
"longitude": 61.3456,
"phones": [
"+7 (351) 263-89-50"
],
"categories": [
"Педагогический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_11",
"name": "Челябинский технологический колледж",
"full_name": "ГБПОУ «Челябинский технологический колледж»",
"address": "ул. Машиностроителей, 23, Челябинск",
"latitude": 55.1267,
"longitude": 61.4567,
"phones": [
"+7 (351) 729-99-60"
],
"categories": [
"Технологический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_12",
"name": "Челябинский юридический колледж",
"full_name": "ЧОУ СПО «Челябинский юридический колледж»",
"address": "ул. Цвиллинга, 60, Челябинск",
"latitude": 55.1645,
"longitude": 61.4156,
"phones": [
"+7 (351) 266-55-20"
],
"categories": [
"Юридический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_13",
"name": "Южно-Уральский государственный технический колледж",
"full_name": "ГБПОУ «Южно-Уральский государственный технический колледж»",
"address": "ул. Воровского, 16, Челябинск",
"latitude": 55.1598,
"longitude": 61.4034,
"phones": [
"+7 (351) 263-44-36"
],
"categories": [
"Технический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_14",
"name": "Челябинский экономический колледж",
"full_name": "ЧОУ СПО «Челябинский экономический колледж»",
"address": "ул. Кирова, 159, Челябинск",
"latitude": 55.1534,
"longitude": 61.3789,
"phones": [
"+7 (351) 263-78-05"
],
"categories": [
"Экономический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_15",
"name": "Челябинский автотранспортный техникум",
"full_name": "ГБПОУ «Челябинский автотранспортный техникум»",
"address": "ул. Монакова, 33, Челябинск",
"latitude": 55.1123,
"longitude": 61.4234,
"phones": [
"+7 (351) 721-81-67"
],
"categories": [
"Автотранспортный техникум",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_16",
"name": "Челябинский государственный институт культуры",
"full_name": "ФГБОУ ВО «Челябинский государственный институт культуры»",
"address": "ул. Орджоникидзе, 36а, Челябинск",
"latitude": 55.1699,
"longitude": 61.4018,
"phones": [
"+7 (351) 263-83-18"
],
"categories": [
"Институт культуры",
"Высшее образование"
],
"source": "Демо-данные"
},
{
"id": "demo_17",
"name": "Магнитогорский государственный технический университет (филиал)",
"full_name": "Челябинский филиал ФГБОУ ВО «МГТУ им. Г.И. Носова»",
"address": "ул. 250-летия Челябинска, 17, Челябинск",
"latitude": 55.1589,
"longitude": 61.3756,
"phones": [
"+7 (351) 799-74-45"
],
"categories": [
"Филиал университета",
"Техническое образование"
],
"source": "Демо-данные"
},
{
"id": "demo_18",
"name": "Российский государственный профессионально-педагогический университет (филиал)",
"full_name": "Челябинский филиал ФГАОУ ВО «РГППУ»",
"address": "ул. Машиностроителей, 11, Челябинск",
"latitude": 55.1298,
"longitude": 61.4523,
"phones": [
"+7 (351) 729-99-10"
],
"categories": [
"Филиал университета",
"Педагогическое образование"
],
"source": "Демо-данные"
},
{
"id": "demo_19",
"name": "Челябинский государственный промышленно-гуманитарный техникум",
"full_name": "ГБПОУ «Челябинский государственный промышленно-гуманитарный техникум им. А.В. Яковлева»",
"address": "ул. Красноармейская, 88, Челябинск",
"latitude": 55.1678,
"longitude": 61.4125,
"phones": [
"+7 (351) 263-22-89"
],
"categories": [
"Промышленно-гуманитарный техникум",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_20",
"name": "Челябинский радиотехнический техникум",
"full_name": "ГБПОУ «Челябинский радиотехнический техникум»",
"address": "ул. Цвиллинга, 38, Челябинск",
"latitude": 55.1634,
"longitude": 61.4089,
"phones": [
"+7 (351) 232-08-71"
],
"categories": [
"Радиотехнический техникум",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_21",
"name": "Челябинский механико-технологический техникум",
"full_name": "ГБПОУ «Челябинский механико-технологический техникум»",
"address": "ул. Агалакова, 102, Челябинск",
"latitude": 55.1445,
"longitude": 61.3756,
"phones": [
"+7 (351) 256-78-34"
],
"categories": [
"Механико-технологический техникум",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_22",
"name": "Челябинский строительный колледж",
"full_name": "ГБПОУ «Челябинский строительный колледж»",
"address": "ул. Электростальская, 12, Челябинск",
"latitude": 55.1234,
"longitude": 61.4567,
"phones": [
"+7 (351) 729-88-90"
],
"categories": [
"Строительный колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_23",
"name": "Челябинский энергетический колледж",
"full_name": "ГБПОУ «Челябинский энергетический колледж им. С.М. Кирова»",
"address": "ул. Энергетиков, 44, Челябинск",
"latitude": 55.1345,
"longitude": 61.4234,
"phones": [
"+7 (351) 721-17-17"
],
"categories": [
"Энергетический колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_24",
"name": "Южно-Уральский многопрофильный колледж",
"full_name": "ГБПОУ «Южно-Уральский многопрофильный колледж»",
"address": "ул. Доватора, 16а, Челябинск",
"latitude": 55.1456,
"longitude": 61.3987,
"phones": [
"+7 (351) 263-44-78"
],
"categories": [
"Многопрофильный колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_25",
"name": "Челябинский колледж физической культуры",
"full_name": "ГБПОУ «Челябинский колледж физической культуры»",
"address": "ул. Российская, 258, Челябинск",
"latitude": 55.1567,
"longitude": 61.3456,
"phones": [
"+7 (351) 237-25-25"
],
"categories": [
"Колледж физической культуры",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_26",
"name": "Челябинский социально-профессиональный колледж",
"full_name": "ГБПОУ «Челябинский социально-профессиональный колледж «Сфера»",
"address": "ул. Чичерина, 38, Челябинск",
"latitude": 55.1678,
"longitude": 61.3234,
"phones": [
"+7 (351) 264-65-65"
],
"categories": [
"Социально-профессиональный колледж",
"Среднее специальное образование"
],
"source": "Демо-данные"
},
{
"id": "demo_27",
"name": "Международный институт дизайна и сервиса",
"full_name": "ЧОУ ВО «Международный институт дизайна и сервиса»",
"address": "ул. Воровского, 36, Челябинск",
"latitude": 55.1589,
"longitude": 61.4012,
"phones": [
"+7 (351) 265-35-35"
],
"categories": [
"Частный институт",
"Дизайн и сервис"
],
"source": "Демо-данные"
},
{
"id": "demo_28",
"name": "Челябинский институт путей сообщения",
"full_name": "Челябинский институт путей сообщения (филиал УрГУПС)",
"address": "ул. Жукова, 99, Челябинск",
"latitude": 55.1234,
"longitude": 61.3789,
"phones": [
"+7 (351) 263-88-88"
],
"categories": [
"Институт путей сообщения",
"Транспорт"
],
"source": "Демо-данные"
},
{
"id": "demo_29",
"name": "Уральский социально-экономический institut",
"full_name": "ЧОУ ВО «Уральский социально-экономический институт»",
"address": "ул. Ленина, 77, Челябинск",
"latitude": 55.1545,
"longitude": 61.4278,
"phones": [
"+7 (351) 200-33-33"
],
"categories": [
"Частный институт",
"Социально-экономическое образование"
],
"source": "Демо-данные"
},
{
"id": "demo_30",
"name": "Челябинский колледж индустрии красоты",
"full_name": "ЧОУ СПО «Челябинский колледж индустрии красоты»",
"address": "ул. Труда, 179, Челябинск",
"latitude": 55.1723,
"longitude": 61.3567,
"phones": [
"+7 (351) 700-44-44"
],
"categories": [
"Колледж красоты",
"Среднее специальное образование"
],
"source": "Демо-данные"
}
]
}

158
main.py Normal file
View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Интерактивная карта образовательных учреждений Челябинска
Отображает 72+ учебных заведения: университеты, институты, колледжи, техникумы, лицеи и училища
Использует Yandex Maps API для создания интерактивной карты с фильтрацией
"""
import json
import os
import webbrowser
from pathlib import Path
from jinja2 import Template
def load_colleges_data():
"""Загружает данные об образовательных учреждениях из JSON файла"""
current_dir = Path(__file__).parent
colleges_file = current_dir / "colleges_data.json"
try:
with open(colleges_file, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
print(f"Файл с данными об образовательных учреждениях не найден: {colleges_file}")
return []
except json.JSONDecodeError as e:
print(f"Ошибка при чтении JSON файла: {e}")
return []
def generate_map_html(colleges_data, api_key=""):
"""Генерирует HTML файл с интерактивной картой образовательных учреждений"""
current_dir = Path(__file__).parent
template_file = current_dir / "map_template.html"
try:
with open(template_file, 'r', encoding='utf-8') as f:
template_content = f.read()
except FileNotFoundError:
print(f"Шаблон HTML не найден: {template_file}")
return None
# Заменяем placeholder API ключа
template_content = template_content.replace("YOUR_API_KEY", api_key)
# Создаем шаблон Jinja2
template = Template(template_content)
# Рендерим HTML с данными о колледжах
html_content = template.render(
colleges=colleges_data,
colleges_json=json.dumps(colleges_data, ensure_ascii=False)
)
# Сохраняем HTML файл
output_file = current_dir / "chelyabinsk_colleges_map.html"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
return output_file
def print_colleges_info(colleges_data):
"""Выводит информацию об образовательных учреждениях в консоль"""
print("=" * 80)
print("ОБРАЗОВАТЕЛЬНЫЕ УЧРЕЖДЕНИЯ ЧЕЛЯБИНСКА")
print("=" * 80)
# Группировка по типам
types_stats = {}
for institution in colleges_data:
inst_type = institution.get('type', 'Неопределенный')
types_stats[inst_type] = types_stats.get(inst_type, 0) + 1
print(f"\n📊 Статистика по типам учреждений:")
for inst_type, count in sorted(types_stats.items()):
print(f" {inst_type}: {count}")
print(f"\n🎯 Всего учреждений: {len(colleges_data)}")
for i, institution in enumerate(colleges_data, 1):
print(f"\n{i}. {institution['name']}")
print(f" Тип: {institution.get('type', 'Не указан')}")
print(f" Адрес: {institution['address']}")
coords = institution.get('coordinates', {})
if coords:
print(f" Координаты: {coords.get('lat', 'Н')}, {coords.get('lon', 'Н')}")
specialties = institution.get('specialties', [])
if specialties:
print(f" Специальности: {', '.join(specialties)}")
contacts = institution.get('contacts', {})
if contacts.get('website'):
print(f" Веб-сайт: {contacts['website']}")
if contacts.get('phone'):
print(f" Телефон: {contacts['phone']}")
print(f" Описание: {institution.get('description', 'Не указано')}")
print("-" * 80)
def main():
"""Основная функция программы"""
print("Генерация карты колледжей Челябинска...")
# Загружаем данные о колледжах
colleges_data = load_colleges_data()
if not colleges_data:
print("Не удалось загрузить данные о колледжах!")
return
print(f"Загружено {len(colleges_data)} колледжей")
# Выводим информацию о колледжах
print_colleges_info(colleges_data)
# Запрашиваем API ключ у пользователя
print("\n" + "=" * 80)
print("НАСТРОЙКА API КЛЮЧА YANDEX MAPS")
print("=" * 80)
print("Для работы карты необходим API ключ Yandex Maps.")
print("Получить ключ можно на: https://developer.tech.yandex.ru/")
print("Если у вас нет ключа, оставьте поле пустым (карта будет работать с ограничениями)")
api_key = input("\nВведите ваш API ключ Yandex Maps (или нажмите Enter для пропуска): ").strip()
# Генерируем HTML файл с картой
output_file = generate_map_html(colleges_data, api_key)
if output_file:
print(f"\n✅ Карта успешно создана: {output_file}")
# Предлагаем открыть карту в браузере
open_browser = input("\nОткрыть карту в браузере? (y/N): ").strip().lower()
if open_browser in ['y', 'yes', 'да', 'д']:
try:
webbrowser.open(f'file://{output_file.absolute()}')
print("Карта открыта в браузере!")
except Exception as e:
print(f"Не удалось открыть браузер: {e}")
print(f"Откройте файл вручную: {output_file.absolute()}")
else:
print(f"Откройте файл в браузере: {output_file.absolute()}")
print("\n📍 На карте отмечены следующие колледжи:")
for college in colleges_data:
print(f"{college['name']}")
else:
print("❌ Ошибка при создании карты!")
if __name__ == "__main__":
main()

265
main_2gis.py Normal file
View File

@@ -0,0 +1,265 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Интерактивная карта учебных заведений Челябинска
Отображает заведения среднего специального и высшего образования
Использует 2GIS Maps API для создания интерактивной карты с фильтрацией
"""
import json
import os
import webbrowser
from pathlib import Path
from jinja2 import Template
def load_institutions_data():
"""Загружает данные об учебных заведениях из JSON файла (демо-данные или 2GIS API)"""
current_dir = Path(__file__).parent
# Сначала пробуем демо-данные
demo_file = current_dir / "educational_institutions_demo.json"
if demo_file.exists():
try:
with open(demo_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('institutions', [])
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Ошибка при чтении демо-данных: {e}")
# Пробуем данные от 2GIS API
api_file = current_dir / "educational_institutions_2gis.json"
if api_file.exists():
try:
with open(api_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('institutions', [])
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Ошибка при чтении данных 2GIS API: {e}")
# Пробуем старые данные (на случай отката)
legacy_file = current_dir / "colleges_data.json"
if legacy_file.exists():
try:
with open(legacy_file, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"Ошибка при чтении устаревших данных: {e}")
print("Файлы с данными об учебных заведениях не найдены!")
return []
def generate_map_html(institutions_data):
"""Генерирует HTML файл с интерактивной картой учебных заведений (2GIS)"""
current_dir = Path(__file__).parent
template_file = current_dir / "map_template_2gis.html"
try:
with open(template_file, 'r', encoding='utf-8') as f:
template_content = f.read()
except FileNotFoundError:
print(f"Шаблон HTML не найден: {template_file}")
return None
# Создаем шаблон Jinja2
template = Template(template_content)
# Рендерим HTML с данными об учебных заведениях
html_content = template.render(
institutions=institutions_data,
institutions_json=json.dumps(institutions_data, ensure_ascii=False)
)
# Сохраняем HTML файл
output_file = current_dir / "chelyabinsk_education_map_2gis.html"
with open(output_file, 'w', encoding='utf-8') as f:
f.write(html_content)
return output_file
def print_institutions_info(institutions_data):
"""Выводит информацию об учебных заведениях в консоль"""
print("=" * 90)
print("УЧЕБНЫЕ ЗАВЕДЕНИЯ ЧЕЛЯБИНСКА - СРЕДНЕЕ СПЕЦИАЛЬНОЕ И ВЫСШЕЕ ОБРАЗОВАНИЕ")
print("=" * 90)
# Группировка по категориям
categories_stats = {}
type_stats = {}
for institution in institutions_data:
categories = institution.get('categories', [])
for category in categories:
categories_stats[category] = categories_stats.get(category, 0) + 1
# Определяем тип по названию
name = institution.get('name', '').lower()
if 'университет' in name:
inst_type = 'Университет'
elif 'институт' in name:
inst_type = 'Институт'
elif 'колледж' in name:
inst_type = 'Колледж'
elif 'техникум' in name:
inst_type = 'Техникум'
elif 'академия' in name:
inst_type = 'Академия'
else:
inst_type = 'Другое'
type_stats[inst_type] = type_stats.get(inst_type, 0) + 1
print(f"\n📊 Статистика по типам учреждений:")
for inst_type, count in sorted(type_stats.items(), key=lambda x: x[1], reverse=True):
print(f" {inst_type}: {count}")
print(f"\n📋 Статистика по категориям:")
for category, count in sorted(categories_stats.items(), key=lambda x: x[1], reverse=True)[:10]:
print(f" {category}: {count}")
print(f"\n🎯 Всего учреждений: {len(institutions_data)}")
print(f"\n📍 Список учебных заведений:")
for i, institution in enumerate(institutions_data, 1):
print(f"\n{i}. {institution['name']}")
if institution.get('full_name') and institution['full_name'] != institution['name']:
print(f" Полное название: {institution['full_name']}")
print(f" Адрес: {institution.get('address', 'Не указан')}")
lat = institution.get('latitude', 0)
lon = institution.get('longitude', 0)
if lat and lon:
print(f" Координаты: {lat}, {lon}")
categories = institution.get('categories', [])
if categories:
print(f" Категории: {', '.join(categories)}")
phones = institution.get('phones', [])
if phones:
print(f" Телефоны: {', '.join(phones)}")
source = institution.get('source', 'Не указан')
print(f" Источник данных: {source}")
print("-" * 90)
def run_search_if_needed():
"""Запускает поиск через 2GIS API или создает демо-данные, если данных нет"""
current_dir = Path(__file__).parent
demo_file = current_dir / "educational_institutions_demo.json"
api_file = current_dir / "educational_institutions_2gis.json"
if not demo_file.exists() and not api_file.exists():
print("Данные об учебных заведениях не найдены.")
print("Создание демо-данных...")
# Импортируем и запускаем создание демо-данных
try:
from demo_institutions_chelyabinsk import save_demo_data
save_demo_data()
except ImportError:
print("Не удалось импортировать модуль создания демо-данных")
return False
return True
return True
def main():
"""Основная функция программы"""
print("Генерация карты учебных заведений Челябинска с использованием 2GIS Maps API...")
print("Отображаются только заведения среднего специального и высшего образования")
print("=" * 90)
# Проверяем наличие данных, создаем если нужно
if not run_search_if_needed():
print("Не удалось получить данные об учебных заведениях!")
return
# Загружаем данные об учебных заведениях
institutions_data = load_institutions_data()
if not institutions_data:
print("Не удалось загрузить данные об учебных заведениях!")
return
print(f"Загружено {len(institutions_data)} учебных заведений")
# Выводим информацию об учебных заведениях
print_institutions_info(institutions_data)
# Информация о 2GIS API
print("\n" + "=" * 90)
print("ИНФОРМАЦИЯ О 2GIS MAPS API")
print("=" * 90)
print("Карта использует 2GIS Maps API для отображения.")
print("2GIS API предоставляется бесплатно для некоммерческого использования.")
print("Для получения API ключа для коммерческого использования:")
print("https://platform.2gis.com/")
# Генерируем HTML файл с картой
output_file = generate_map_html(institutions_data)
if output_file:
print(f"\n✅ Карта успешно создана: {output_file}")
# Предлагаем открыть карту в браузере
open_browser = input("\nОткрыть карту в браузере? (y/N): ").strip().lower()
if open_browser in ['y', 'yes', 'да', 'д']:
try:
webbrowser.open(f'file://{output_file.absolute()}')
print("Карта открыта в браузере!")
except Exception as e:
print(f"Не удалось открыть браузер: {e}")
print(f"Откройте файл вручную: {output_file.absolute()}")
else:
print(f"Откройте файл в браузере: {output_file.absolute()}")
print("\n🎓 Особенности карты:")
print(" • Использует 2GIS Maps API вместо Yandex Maps")
print(" • Показывает только среднее специальное и высшее образование")
print(" • Фильтрация по типам учреждений")
print(" • Цветные маркеры для разных типов")
print(" • Подробная информация в всплывающих окнах")
print(f"\n📍 На карте отмечены следующие учебные заведения:")
# Группируем по типам для удобного отображения
by_type = {}
for institution in institutions_data:
name = institution.get('name', '').lower()
if 'университет' in name:
inst_type = 'Университеты'
elif 'институт' in name:
inst_type = 'Институты'
elif 'колледж' in name:
inst_type = 'Колледжи'
elif 'техникум' in name:
inst_type = 'Техникумы'
elif 'академия' in name:
inst_type = 'Академии'
else:
inst_type = 'Другие'
if inst_type not in by_type:
by_type[inst_type] = []
by_type[inst_type].append(institution['name'])
for inst_type, names in by_type.items():
print(f"\n {inst_type} ({len(names)}):")
for name in names:
print(f"{name}")
else:
print("❌ Ошибка при создании карты!")
if __name__ == "__main__":
main()

323
map_template.html Normal file
View File

@@ -0,0 +1,323 @@
<!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://api-maps.yandex.ru/2.1/?apikey=YOUR_API_KEY&lang=ru_RU" type="text/javascript"></script>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
}
.header {
background: linear-gradient(135deg, #667eea 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 #667eea;
background: white;
color: #667eea;
border-radius: 25px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
font-weight: 500;
}
.filter-btn:hover {
background: #667eea;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.3);
}
.filter-btn.active {
background: #667eea;
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-specialties {
margin-bottom: 8px;
}
.specialty-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: #667eea;
text-decoration: none;
}
.balloon-contacts a:hover {
text-decoration: underline;
}
@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="filterBySpecialty('all')">🏛️ Все</button>
<button class="filter-btn" onclick="filterBySpecialty('Техника и технологии')">⚙️ Техника</button>
<button class="filter-btn" onclick="filterBySpecialty('Экономика и управление')">💼 Экономика</button>
<button class="filter-btn" onclick="filterBySpecialty('Медицина и здравоохранение')">🏥 Медицина</button>
<button class="filter-btn" onclick="filterBySpecialty('Педагогика и образование')">👨<EFBFBD> Педагогика</button>
<button class="filter-btn" onclick="filterBySpecialty('Строительство и архитектура')">🏗️ Строительство</button>
<button class="filter-btn" onclick="filterBySpecialty('Искусство и культура')"><EFBFBD> Искусство</button>
<button class="filter-btn" onclick="filterBySpecialty('Транспорт и логистика')"><EFBFBD> Транспорт</button>
<button class="filter-btn" onclick="filterBySpecialty('Информационные технологии')">💻 IT</button>
<button class="filter-btn" onclick="filterBySpecialty('Право и юриспруденция')">⚖️ Право</button>
<button class="filter-btn" onclick="filterBySpecialty('Пищевая промышленность')">🍳 Питание</button>
<button class="filter-btn" onclick="filterBySpecialty('Энергетика')">🏭 Энергетика</button>
<div class="stats">
<span id="showing-count">{{ colleges|length }}</span> из {{ colleges|length }} учреждений
</div>
</div>
<div id="map"></div>
<script>
let myMap;
let placemarks = [];
let colleges = {{ colleges_json|safe }};
ymaps.ready(init);
function init() {
myMap = new ymaps.Map("map", {
center: [55.1644, 61.4291], // Центр Челябинска
zoom: 11,
controls: ['zoomControl', 'searchControl', 'trafficControl', 'fullscreenControl']
});
// Добавляем метки для всех колледжей
colleges.forEach(function(college, index) {
addPlacemark(college, index);
});
// Подгоняем границы карты под все метки
if (placemarks.length > 0) {
myMap.setBounds(myMap.geoObjects.getBounds(), {
checkZoomRange: true,
zoomMargin: 50
});
}
}
function addPlacemark(college, index) {
let placemark = new ymaps.Placemark(
[college.lat, college.lon],
{
balloonContentHeader: '<div class="balloon-title">' + college.name + '</div>',
balloonContentBody: createBalloonContent(college),
balloonContentFooter: '',
hintContent: college.name,
clusterCaption: college.name
},
{
preset: 'islands#redEducationIcon',
hideIconOnBalloonOpen: false,
balloonOffset: [0, -40]
}
);
// Сохраняем данные колледжа в метке для фильтрации
placemark.college = college;
placemark.collegeIndex = index;
myMap.geoObjects.add(placemark);
placemarks.push(placemark);
}
function createBalloonContent(college) {
let content = '<div class="balloon-content">';
if (college.address) {
content += '<div class="balloon-address">📍 ' + college.address + '</div>';
}
if (college.specialties && college.specialties.length > 0) {
content += '<div class="balloon-specialties">';
content += '<strong>🎓 Специальности:</strong><br>';
college.specialties.slice(0, 5).forEach(function(specialty) {
content += '<span class="specialty-tag">' + specialty + '</span>';
});
if (college.specialties.length > 5) {
content += '<span class="specialty-tag">+' + (college.specialties.length - 5) + ' еще</span>';
}
content += '</div>';
}
if (college.description) {
content += '<div style="margin-top: 10px; font-size: 13px; color: #666;">' + college.description + '</div>';
}
let contacts = [];
if (college.phone) {
contacts.push('📞 <a href="tel:' + college.phone + '">' + college.phone + '</a>');
}
if (college.website) {
contacts.push('🌐 <a href="' + college.website + '" target="_blank">Сайт</a>');
}
if (contacts.length > 0) {
content += '<div class="balloon-contacts">' + contacts.join('<br>') + '</div>';
}
content += '</div>';
return content;
}
function filterBySpecialty(specialty) {
// Обновляем активную кнопку
document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
let visibleCount = 0;
placemarks.forEach(function(placemark) {
let college = placemark.college;
let shouldShow = false;
if (specialty === 'all') {
shouldShow = true;
} else {
// Проверяем специальности и название колледжа
let searchText = (college.name + ' ' + college.specialties.join(' ')).toLowerCase();
shouldShow = searchText.includes(specialty.toLowerCase());
}
if (shouldShow) {
placemark.options.set('visible', true);
visibleCount++;
} else {
placemark.options.set('visible', false);
}
});
// Обновляем счетчик
document.getElementById('showing-count').textContent = visibleCount;
// Подгоняем карту под видимые метки
if (visibleCount > 0) {
let visiblePlacemarks = placemarks.filter(p => p.options.get('visible'));
if (visiblePlacemarks.length > 0) {
let bounds = [];
visiblePlacemarks.forEach(p => {
bounds.push(p.geometry.getCoordinates());
});
myMap.setBounds(ymaps.util.bounds.fromPoints(bounds), {
checkZoomRange: true,
zoomMargin: 50
});
}
}
}
// Добавляем обработчик изменения размера окна
window.addEventListener('resize', function() {
if (myMap) {
myMap.container.fitToViewport();
}
});
</script>
</body>
</html>

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>

10
pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[project]
name = "map-yandex"
version = "0.1.0"
description = "Карта колледжей Челябинска с использованием Yandex Maps API"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"requests>=2.31.0",
"jinja2>=3.1.2",
]

249
search_2gis.py Normal file
View File

@@ -0,0 +1,249 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Скрипт для поиска учебных заведений в Челябинске через 2GIS Places API
"""
import requests
import json
import time
from typing import List, Dict, Optional
class TwoGISSearcher:
"""Клиент для работы с 2GIS Places API"""
def __init__(self, api_key: Optional[str] = None):
self.base_url = "https://catalog.api.2gis.com/3.0/items"
self.api_key = api_key or "f61cfc4b-55b7-4937-8625-c00932bee844"
self.session = requests.Session()
# Челябинск (region_id получен из документации 2GIS)
self.chelyabinsk_location = "55.1644,61.4368" # координаты центра Челябинска
self.chelyabinsk_region_id = 1 # region_id для России
# Поисковые запросы для среднего специального и высшего образования
self.search_queries = [
"техникум",
"колледж",
"университет",
"институт",
"академия",
"профессиональное училище",
"высшее учебное заведение",
"вуз",
"факультет",
"филиал университета",
"медицинский колледж",
"педагогический колледж",
"технический колледж",
"экономический колледж"
]
def search_educational_institutions(self, query: str, limit: int = 50) -> List[Dict]:
"""Поиск учебных заведений по запросу"""
params = {
'q': query,
'location': self.chelyabinsk_location,
'radius': 50000, # радиус поиска 50км от центра города
'region_id': self.chelyabinsk_region_id,
'fields': 'items.name,items.full_name,items.address,items.point,items.contact_groups,items.rubrics,items.context',
'page_size': limit
}
if self.api_key:
params['key'] = self.api_key
try:
print(f"Поиск: {query}")
response = self.session.get(self.base_url, params=params)
response.raise_for_status()
data = response.json()
if 'result' in data and 'items' in data['result']:
items = data['result']['items']
print(f"Найдено {len(items)} заведений для запроса '{query}'")
return items
else:
print(f"Нет результатов для запроса '{query}'")
return []
except requests.exceptions.RequestException as e:
print(f"Ошибка при запросе '{query}': {e}")
return []
def filter_educational_institutions(self, items: List[Dict]) -> List[Dict]:
"""Фильтрация результатов - оставляем только учебные заведения"""
educational_rubrics = [
'university',
'college',
'technical_school',
'higher_education',
'vocational_school'
]
filtered_items = []
for item in items:
# Проверяем рубрики
if 'rubrics' in item:
for rubric in item['rubrics']:
if any(edu_key in rubric.get('alias', '').lower() for edu_key in educational_rubrics):
filtered_items.append(item)
break
# Дополнительная проверка по названию (только среднее специальное и высшее)
name = item.get('name', '').lower()
educational_keywords = [
'техникум', 'колледж', 'университет', 'институт', 'академия',
'вуз', 'факультет', 'филиал', 'профессиональное училище',
'медицинский', 'педагогический', 'технический', 'экономический'
]
# Исключаем школы, лицеи, гимназии, детские сады
excluded_keywords = [
'школа', 'лицей', 'гимназия', 'детский сад', 'дошкольн'
]
# Проверяем наличие ключевых слов и отсутствие исключаемых
has_educational_keyword = any(keyword in name for keyword in educational_keywords)
has_excluded_keyword = any(keyword in name for keyword in excluded_keywords)
if has_educational_keyword and not has_excluded_keyword:
if item not in filtered_items:
filtered_items.append(item)
return filtered_items
def extract_institution_data(self, item: Dict) -> Dict:
"""Извлечение данных об учебном заведении"""
# Получаем координаты
point = item.get('point', {})
lat = point.get('lat', 0)
lon = point.get('lon', 0)
# Получаем адрес
address_info = item.get('address', {})
address = address_info.get('name', '') if address_info else ''
# Получаем контактную информацию
contacts = []
if 'contact_groups' in item:
for group in item['contact_groups']:
if 'contacts' in group:
for contact in group['contacts']:
if contact.get('type') == 'phone':
contacts.append(contact.get('value', ''))
# Получаем рубрики (категории)
rubrics = []
if 'rubrics' in item:
rubrics = [rubric.get('name', '') for rubric in item['rubrics']]
return {
'id': item.get('id', ''),
'name': item.get('name', ''),
'full_name': item.get('full_name', ''),
'address': address,
'latitude': lat,
'longitude': lon,
'phones': contacts,
'categories': rubrics,
'source': '2GIS Places API'
}
def search_all_educational_institutions(self) -> List[Dict]:
"""Поиск всех учебных заведений в Челябинске"""
all_institutions = []
seen_ids = set()
for query in self.search_queries:
# Задержка между запросами чтобы не превысить лимиты API
time.sleep(0.5)
items = self.search_educational_institutions(query)
filtered_items = self.filter_educational_institutions(items)
for item in filtered_items:
institution_data = self.extract_institution_data(item)
# Избегаем дубликатов по ID
if institution_data['id'] and institution_data['id'] not in seen_ids:
seen_ids.add(institution_data['id'])
all_institutions.append(institution_data)
# Если нет ID, проверяем по названию и адресу
elif not institution_data['id']:
unique_key = f"{institution_data['name']}_{institution_data['address']}"
if unique_key not in seen_ids:
seen_ids.add(unique_key)
all_institutions.append(institution_data)
print(f"\nВсего найдено уникальных учебных заведений: {len(all_institutions)}")
return all_institutions
def save_to_json(self, institutions: List[Dict], filename: str = "educational_institutions_2gis.json"):
"""Сохранение данных в JSON файл"""
try:
with open(filename, 'w', encoding='utf-8') as f:
json.dump({
'city': 'Челябинск',
'source': '2GIS Places API',
'count': len(institutions),
'institutions': institutions
}, f, ensure_ascii=False, indent=2)
print(f"Данные сохранены в файл: {filename}")
except Exception as e:
print(f"Ошибка при сохранении файла: {e}")
def main():
"""Основная функция"""
print("Поиск заведений среднего специального и высшего образования в Челябинске")
print("Используется 2GIS Places API")
print("="*70)
# Создаем экземпляр поисковика
# API ключ можно получить на https://platform.2gis.com/
# Для тестирования можно попробовать без ключа (есть лимиты)
searcher = TwoGISSearcher()
# Выполняем поиск
institutions = searcher.search_all_educational_institutions()
if institutions:
# Сохраняем результаты
searcher.save_to_json(institutions)
# Выводим краткую статистику
print("\n" + "="*60)
print("СТАТИСТИКА:")
print(f"Всего найдено заведений среднего специального и высшего образования: {len(institutions)}")
# Группируем по типам
categories_count = {}
for inst in institutions:
for category in inst.get('categories', []):
categories_count[category] = categories_count.get(category, 0) + 1
print("\nРаспределение по категориям:")
for category, count in sorted(categories_count.items(), key=lambda x: x[1], reverse=True):
print(f" {category}: {count}")
# Показываем первые 5 найденных заведений
print("\nПримеры найденных заведений:")
for i, inst in enumerate(institutions[:5], 1):
print(f"{i}. {inst['name']}")
print(f" Адрес: {inst['address']}")
print(f" Координаты: {inst['latitude']}, {inst['longitude']}")
if inst['phones']:
print(f" Телефон: {', '.join(inst['phones'])}")
print()
else:
print("Учебные заведения не найдены. Возможно, нужен API ключ для полноценного поиска.")
print("Получите API ключ на https://platform.2gis.com/")
if __name__ == "__main__":
main()

114
simple_test_map.html Normal file
View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Простая карта учебных заведений - 2GIS</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; font-family: Arial, sans-serif; }
#map { width: 100%; height: 100vh; }
.info {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255,255,255,0.9);
padding: 15px;
border-radius: 8px;
z-index: 1000;
max-width: 300px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
.status {
margin-bottom: 10px;
padding: 5px 10px;
border-radius: 5px;
font-weight: bold;
}
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.loading { background: #cce7ff; color: #004085; }
</style>
</head>
<body>
<div class="info">
<h3>🎓 Карта ВУЗов и колледжей</h3>
<div id="status" class="status loading">Загрузка...</div>
<div id="counter">Учреждений на карте: 0</div>
</div>
<div id="map"></div>
<script>
const statusDiv = document.getElementById('status');
const counterDiv = document.getElementById('counter');
function updateStatus(message, type = 'loading') {
statusDiv.innerHTML = message;
statusDiv.className = `status ${type}`;
console.log(message);
}
function updateCounter(count) {
counterDiv.innerHTML = `Учреждений на карте: ${count}`;
}
// Упрощенные данные для тестирования
const testInstitutions = [
{ name: "ЮУрГУ", lat: 55.1549, lng: 61.4289, address: "пр. Ленина, 76" },
{ name: "ЧелГУ", lat: 55.1605, lng: 61.4025, address: "ул. Братьев Кашириных, 129" },
{ name: "ЧГПУ", lat: 55.1545, lng: 61.4267, address: "пр. Ленина, 69" },
{ name: "УралГУФК", lat: 55.1674, lng: 61.3988, address: "ул. Орджоникидзе, 1" },
{ name: "Медицинский колледж", lat: 55.1901, lng: 61.3164, address: "ул. Свободы, 159а" }
];
updateStatus('Инициализация 2GIS API...');
DG.then(function() {
updateStatus('✅ 2GIS API загружен', 'success');
try {
// Создаем карту
const map = DG.map('map', {
center: [55.1644, 61.4291],
zoom: 11
});
updateStatus('✅ Карта создана', 'success');
let markerCount = 0;
// Добавляем маркеры
testInstitutions.forEach(function(institution) {
try {
const marker = DG.marker([institution.lat, institution.lng])
.addTo(map)
.bindPopup(`
<div style="min-width: 200px;">
<h4 style="margin: 0 0 8px 0;">${institution.name}</h4>
<p style="margin: 0; color: #666;">📍 ${institution.address}</p>
</div>
`);
markerCount++;
console.log(`Добавлен маркер: ${institution.name}`);
} catch (error) {
console.error(`Ошибка добавления маркера ${institution.name}:`, error);
}
});
updateCounter(markerCount);
updateStatus(`✅ Готово! Добавлено ${markerCount} маркеров`, 'success');
} catch (error) {
updateStatus('❌ Ошибка создания карты: ' + error.message, 'error');
console.error('Подробная ошибка:', error);
}
}).catch(function(error) {
updateStatus('❌ Ошибка загрузки API: ' + error.message, 'error');
console.error('Ошибка DG.then:', error);
});
</script>
</body>
</html>

63
test_2gis_api.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест 2GIS API</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; font-family: Arial, sans-serif; }
#map { width: 100%; height: 100vh; }
.info { position: absolute; top: 10px; left: 10px; background: white; padding: 10px; border-radius: 5px; z-index: 1000; }
</style>
</head>
<body>
<div class="info">
<h3>Тест карты 2GIS</h3>
<div id="status">Загрузка...</div>
</div>
<div id="map"></div>
<script>
const statusDiv = document.getElementById('status');
function updateStatus(message) {
statusDiv.innerHTML = message;
console.log(message);
}
updateStatus('Проверяем доступность 2GIS API...');
if (typeof DG === 'undefined') {
updateStatus('❌ 2GIS API не загружен');
} else {
updateStatus('✅ 2GIS API доступен, инициализируем карту...');
DG.then(function() {
updateStatus('✅ 2GIS API загружен успешно');
try {
const map = DG.map('map', {
center: [55.1644, 61.4291], // Центр Челябинска
zoom: 11
});
updateStatus('✅ Карта создана успешно');
// Добавляем тестовый маркер
const marker = DG.marker([55.1644, 61.4291]).addTo(map);
marker.bindPopup('Тестовый маркер в центре Челябинска');
updateStatus('✅ Карта полностью работает!');
} catch (error) {
updateStatus('❌ Ошибка создания карты: ' + error.message);
}
}).catch(function(error) {
updateStatus('❌ Ошибка загрузки 2GIS API: ' + error.message);
});
}
</script>
</body>
</html>

152
uv.lock generated Normal file
View File

@@ -0,0 +1,152 @@
version = 1
revision = 2
requires-python = ">=3.12"
[[package]]
name = "certifi"
version = "2025.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" },
{ url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" },
{ url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" },
{ url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" },
{ url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" },
{ url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" },
{ url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" },
{ url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" },
{ url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" },
{ url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" },
{ url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" },
{ url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" },
{ url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" },
{ url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" },
{ url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" },
{ url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" },
{ url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" },
{ url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" },
{ url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" },
{ url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" },
{ url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" },
{ url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" },
{ url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" },
{ url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" },
{ url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" },
{ url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" },
{ url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" },
{ url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" },
{ url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" },
{ url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" },
{ url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" },
{ url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "map-yandex"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "jinja2" },
{ name = "requests" },
]
[package.metadata]
requires-dist = [
{ name = "jinja2", specifier = ">=3.1.2" },
{ name = "requests", specifier = ">=2.31.0" },
]
[[package]]
name = "markupsafe"
version = "3.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" },
]

376
working_map_2gis.html Normal file
View File

@@ -0,0 +1,376 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Карта учебных заведений Челябинска - 2GIS</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;
background: #f0f0f0;
padding: 5px 10px;
border-radius: 15px;
}
#map {
width: 100%;
height: calc(100vh - 180px);
border: 2px solid #ddd;
}
.error-message {
background: #ffe6e6;
border: 1px solid #ff9999;
color: #cc0000;
padding: 15px;
margin: 20px;
border-radius: 5px;
text-align: center;
}
.loading {
background: #e6f3ff;
border: 1px solid #99ccff;
color: #0066cc;
padding: 15px;
margin: 20px;
border-radius: 5px;
text-align: center;
}
@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>Среднее специальное и высшее образование • Powered by 2GIS</p>
</div>
<div class="controls">
<button class="filter-btn active" onclick="filterInstitutions('all')">🏛️ Все</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">30</span> из 30 учреждений
</div>
</div>
<div id="map-container">
<div class="loading" id="loading">
⏳ Загрузка карты 2GIS...
</div>
<div id="map" style="display: none;"></div>
</div>
<script>
// Данные об учебных заведениях
const institutions = [
{
"name": "Южно-Уральский государственный университет",
"address": "проспект Ленина, 76, Челябинск",
"latitude": 55.1549,
"longitude": 61.4289,
"phones": ["+7 (351) 267-90-90"],
"categories": ["Университет", "Высшее образование"],
"type": "университет"
},
{
"name": "Челябинский государственный университет",
"address": "ул. Братьев Кашириных, 129, Челябинск",
"latitude": 55.1605,
"longitude": 61.4025,
"phones": ["+7 (351) 799-70-02"],
"categories": ["Университет", "Высшее образование"],
"type": "университет"
},
{
"name": "Челябинский государственный педагогический университет",
"address": "проспект Ленина, 69, Челябинск",
"latitude": 55.1545,
"longitude": 61.4267,
"phones": ["+7 (351) 216-56-01"],
"categories": ["Университет", "Педагогическое образование"],
"type": "университет"
},
{
"name": "Уральский государственный университет физической культуры",
"address": "ул. Орджоникидзе, 1, Челябинск",
"latitude": 55.1674,
"longitude": 61.3988,
"phones": ["+7 (351) 237-04-54"],
"categories": ["Университет", "Физическая культура"],
"type": "университет"
},
{
"name": "Челябинский институт экономики и права",
"address": "ул. Свободы, 155, Челябинск",
"latitude": 55.1890,
"longitude": 61.3176,
"phones": ["+7 (351) 217-18-18"],
"categories": ["Институт", "Экономика и право"],
"type": "институт"
},
{
"name": "Челябинский государственный институт культуры",
"address": "ул. Орджоникидзе, 36а, Челябинск",
"latitude": 55.1699,
"longitude": 61.4018,
"phones": ["+7 (351) 263-83-18"],
"categories": ["Институт культуры", "Высшее образование"],
"type": "институт"
},
{
"name": "Челябинский медицинский колледж",
"address": "ул. Свободы, 159а, Челябинск",
"latitude": 55.1901,
"longitude": 61.3164,
"phones": ["+7 (351) 232-72-01"],
"categories": ["Медицинский колледж", "Среднее специальное образование"],
"type": "колледж"
},
{
"name": "Челябинский технологический колледж",
"address": "ул. Машиностроителей, 23, Челябинск",
"latitude": 55.1267,
"longitude": 61.4567,
"phones": ["+7 (351) 729-99-60"],
"categories": ["Технологический колледж", "Среднее специальное образование"],
"type": "колледж"
},
{
"name": "Челябинский юридический колледж",
"address": "ул. Цвиллинга, 60, Челябинск",
"latitude": 55.1645,
"longitude": 61.4156,
"phones": ["+7 (351) 266-55-20"],
"categories": ["Юридический колледж", "Среднее специальное образование"],
"type": "колледж"
},
{
"name": "Челябинский автотранспортный техникум",
"address": "ул. Монакова, 33, Челябинск",
"latitude": 55.1123,
"longitude": 61.4234,
"phones": ["+7 (351) 721-81-67"],
"categories": ["Автотранспортный техникум", "Среднее специальное образование"],
"type": "техникум"
}
];
let myMap;
let allMarkers = [];
function showError(message) {
document.getElementById('loading').innerHTML = `
<div class="error-message">
${message}
<br><small>Проверьте подключение к интернету или попробуйте позже</small>
</div>
`;
}
function initializeMap() {
console.log('Инициализация карты 2GIS...');
try {
// Создаем карту
myMap = DG.map('map', {
center: [55.1644, 61.4291], // Центр Челябинска
zoom: 11
});
console.log('Карта создана успешно');
document.getElementById('loading').style.display = 'none';
document.getElementById('map').style.display = 'block';
// Добавляем маркеры
addMarkersToMap();
} catch (error) {
console.error('Ошибка создания карты:', error);
showError('Не удалось создать карту: ' + error.message);
}
}
function addMarkersToMap() {
console.log('Добавляем маркеры на карту...');
institutions.forEach((institution, index) => {
try {
const marker = DG.marker([institution.latitude, institution.longitude])
.addTo(myMap);
// Создаем содержимое всплывающего окна
const popupContent = `
<div style="max-width: 300px;">
<h3 style="margin: 0 0 10px 0; color: #2c5aa0;">${institution.name}</h3>
<p style="margin: 5px 0;"><strong>📍 Адрес:</strong> ${institution.address}</p>
<p style="margin: 5px 0;"><strong>📋 Категории:</strong> ${institution.categories.join(', ')}</p>
${institution.phones.length ? `<p style="margin: 5px 0;"><strong>📞 Телефон:</strong> ${institution.phones.join(', ')}</p>` : ''}
</div>
`;
marker.bindPopup(popupContent);
marker.institutionData = institution;
allMarkers.push(marker);
} catch (error) {
console.error('Ошибка создания маркера для', institution.name, ':', error);
}
});
console.log(`Добавлено ${allMarkers.length} маркеров`);
// Подгоняем карту под все маркеры
if (allMarkers.length > 0) {
setTimeout(() => {
try {
const group = DG.featureGroup(allMarkers);
myMap.fitBounds(group.getBounds(), {padding: [20, 20]});
} catch (error) {
console.error('Ошибка подгонки границ:', error);
}
}, 1000);
}
}
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 {
shouldShow = institution.type === filterType ||
institution.name.toLowerCase().includes(filterType) ||
institution.categories.some(cat => cat.toLowerCase().includes(filterType));
}
if (shouldShow) {
myMap.addLayer(marker);
visibleCount++;
} else {
myMap.removeLayer(marker);
}
});
// Обновляем счетчик
document.getElementById('showing-count').textContent = visibleCount;
}
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
console.log('Страница загружена, проверяем 2GIS API...');
if (typeof DG === 'undefined') {
console.error('2GIS API не загружен');
showError('2GIS API недоступен');
return;
}
DG.then(function() {
console.log('2GIS API готов к использованию');
initializeMap();
}).catch(function(error) {
console.error('Ошибка инициализации 2GIS API:', error);
showError('Не удалось инициализировать 2GIS API: ' + error.message);
});
});
</script>
</body>
</html>