Этот проект демонстрирует базовые функции работы с API Яндекс Карт (MapKit) для создания интерактивных карт на веб-страницах.
- ✅ Инициализация карты с настройками
- ✅ Добавление маркеров (Placemark)
- ✅ Создание полигонов (Polygon)
- ✅ Рисование линий (Polyline)
- ✅ Управление зумом и центром карты
- ✅ Обработка событий карты
- ✅ Геокодирование адресов
- ✅ Расчет расстояний между точками
- ✅ Клики по карте с добавлением маркеров
- 🎨 Случайные цвета для объектов
- 📱 Адаптивный дизайн
- 🔄 Автоматическое обновление информации
- 📊 Счетчик объектов на карте
- 🎯 Информационная панель
- Веб-браузер с поддержкой JavaScript ES6+
- API ключ Яндекс Карт
- Локальный веб-сервер (для корректной работы)
- Перейдите на Яндекс.Разработчики
- Создайте новое приложение
- Получите API ключ для JavaScript API
- Замените
YOUR_API_KEY
в файлеindex.html
на ваш ключ
<script src="https://api-maps.yandex.ru/2.1/?apikey=ВАШ_API_КЛЮЧ&lang=ru_RU"></script>
# Python 3
python -m http.server 8000
# Node.js
npx http-server
# PHP
php -S localhost:8000
Просто откройте index.html
в браузере (некоторые функции могут не работать)
Перейдите по адресу: http://localhost:8000
// Через кнопку
document.getElementById('addMarker').click();
// Программно
window.mapKit.addRandomMarker();
// Через кнопку
document.getElementById('addPolygon').click();
// Программно
window.mapKit.addRandomPolygon();
// Через кнопку
document.getElementById('addPolyline').click();
// Программно
window.mapKit.addRandomPolyline();
// Через кнопку
document.getElementById('clearAll').click();
// Программно
window.mapKit.clearAll();
// Поиск и центрирование карты
const coords = await window.mapKit.searchByAddress('Москва, Красная площадь');
const point1 = [55.7558, 37.6176]; // Москва
const point2 = [59.9311, 30.3609]; // Санкт-Петербург
const distance = window.mapKit.calculateDistance(point1, point2);
console.log(`Расстояние: ${distance} метров`);
const center = window.mapKit.getCenter();
console.log('Центр карты:', center);
// Случайный зум
window.mapKit.setRandomZoom();
// Конкретный зум
window.mapKit.map.setZoom(15);
class YandexMapKit {
constructor() {
this.map = null; // Экземпляр карты
this.objects = []; // Все объекты на карте
this.markers = []; // Маркеры
this.polygons = []; // Полигоны
this.polylines = []; // Линии
this.isInitialized = false; // Статус инициализации
}
}
workshop/
├── index.html # Основная HTML страница
├── styles.css # Стили и CSS
├── mapkit.js # Основной JavaScript API
└── README.md # Документация
const marker = new ymaps.Placemark(coords, {
balloonContent: 'Текст в балуне'
}, {
preset: 'islands#blueDotIcon', // Иконка
iconColor: '#ff0000' // Цвет иконки
});
const polygon = new ymaps.Polygon([points], {
balloonContent: 'Описание полигона'
}, {
fillColor: '#ff0000', // Цвет заливки
strokeColor: '#000000', // Цвет границы
strokeWidth: 2, // Толщина границы
fillOpacity: 0.6 // Прозрачность заливки
});
const polyline = new ymaps.Polyline([points], {
balloonContent: 'Описание линии'
}, {
strokeColor: '#ff0000', // Цвет линии
strokeWidth: 3 // Толщина линии
});
this.map = new ymaps.Map('map', {
center: [55.7558, 37.6176], // Центр карты
zoom: 10, // Начальный зум
controls: [ // Элементы управления
'zoomControl', // Кнопки зума
'fullscreenControl', // Полноэкранный режим
'geolocationControl' // Определение местоположения
]
});
Откройте Developer Tools (F12) и перейдите на вкладку Console для просмотра логов:
Инициализация Яндекс Карт...
Карта успешно инициализирована
Добавлен маркер: [55.7558, 37.6176]
Убедитесь, что API Яндекс Карт загружен:
console.log(typeof ymaps); // Должно вывести "function"
- Проверьте API ключ
- Убедитесь, что используется локальный сервер
- Проверьте консоль на ошибки
- Дождитесь полной загрузки карты
- Проверьте статус
isInitialized
- Убедитесь, что все элементы DOM загружены
- Используйте локальный веб-сервер
- Не открывайте файл напрямую в браузере
Если у вас есть идеи по улучшению или вы нашли ошибки:
- Создайте Issue с описанием проблемы
- Предложите Pull Request с исправлениями
- Добавьте новые функции в API
Этот проект создан для образовательных целей. Используйте свободно для изучения API Яндекс Карт.
Создано с ❤️ для изучения Яндекс Карт
Современное картографическое приложение с ИИ-функциями на базе Yandex MapKit и YaGPT
Yandex Maps Workshop — это инновационное картографическое приложение, демонстрирующее возможности современных технологий разработки. Проект объединяет мощь Yandex MapKit, элегантность Compose Multiplatform и интеллект YaGPT для создания уникального пользовательского опыта.
- 🗺️ Интерактивные карты с плавными анимациями
- 🤖 ИИ-генерация описаний мест с помощью YaGPT
- 📱 Кроссплатформенность на базе Kotlin Multiplatform
- 🎨 Современный UI с Material Design 3
- 🔍 Умный поиск и геолокация
- ✨ Пользовательские метки с возможностью создания
- Android Studio (последняя версия)
- JDK 17+
- Android SDK (API 26+)
- Git
- Создайте файл
local.properties
в корне проекта:
sdk.dir=C\:\\Users\\YourUsername\\AppData\\Local\\Android\\Sdk
- Обновите
gradle.properties
с вашими ключами:
folderId=your_folder_id_here
gptToken=your_yagpt_token_here
mapkitToken=your_mapkit_token_here
# Клонирование репозитория
git clone https://github.com/kramlex/MapsWorkShop.git
cd MapsWorkShop
# Сборка для Android
./gradlew :composeApp:assembleDebug
# Или для Windows
.\gradlew.bat :composeApp:assembleDebug
- Подключите Android устройство или запустите эмулятор
- Выполните:
./gradlew :composeApp:installDebug
- Запустите приложение на устройстве
- Эффект: Многоэтапная анимация "полета" над картой
- Длительность: 3 секунды плавного перехода
- Масштаб: От мирового обзора до детального вида (zoom 3.0 → 16.0)
- К Исаакию: Мгновенный переход с плавной анимацией
- 🏛️ СПб: Переход к центру Санкт-Петербурга
- 🏰 Москва: Переход к центру Москвы
- Одиночное нажатие: Создание обычной метки
- Долгое нажатие: Создание "длинной" метки
- Автоматическое именование: "Метка 1", "Метка 2" и т.д.
- Просмотр: Список последних 3 меток
- Навигация: Переход к метке по нажатию
- Очистка: Удаление всех пользовательских меток
- Расположение: Верхняя центральная часть экрана
- Функции: Поиск мест, отображение результатов
- Результаты: До 5 найденных мест с описаниями
- Автогенерация: Описания для найденных мест
- Контекст: Учет названия и адреса места
- Стиль: Туристические описания с историческими фактами
MapsWorkShop/
├── 📱 composeApp/ # Основное приложение
├── 🔧 common/ # Общая логика
├── 🗺️ mapkit-bindings/ # Yandex MapKit KMP
├── 🔗 mapkit-interop/ # Межплатформенные адаптеры
└── 📚 docs/ # Документация
- Compose Multiplatform - современный декларативный UI
- Material Design 3 - последние принципы дизайна
- Responsive Layout - адаптация под разные экраны
- Yandex MapKit KMP - нативные карты для всех платформ
- Геолокация - определение координат и адресов
- Анимации - плавные переходы камеры и масштабирование
- YaGPT API - генерация описаний мест
- Контекстный анализ - учет названия и местоположения
- Туристический контент - специализированные описания
- Ktor Client - HTTP клиент для API запросов
- Kotlinx Serialization - работа с JSON
- Coroutines - асинхронные операции
@Composable
fun App() {
// 1. Инициализация MapKit
rememberAndInitializeMapKit(apiKey = BuildKonfig.mapkitToken)
// 2. Состояние приложения
val mapScreenMutableState = remember { MapScreenMutableState() }
// 3. Основные панели
MapWithPlacemarks(...)
AnimationControlPanel(...)
UserPlacemarksPanel(...)
SearchPanel(...)
}
class MapScreenMutableState {
val mapState: MapState // Состояние карты
private val _userPlacemarks: MutableState // Пользовательские метки
private val _searchResults: MutableState // Результаты поиска
private val _isSearching: MutableState // Статус поиска
}
@Composable
fun Map(state: MapState) {
// Настройка начальной позиции
LaunchedEffect(Unit) {
// Мировой обзор → Исаакиевский собор
state.map?.let { mapWindow ->
// 1. Установка мирового обзора
val worldPosition = CameraPositionFactory.create(
target = PointFactory.create(55.0, 37.0),
zoom = 3.0f
)
mapWindow.map.move(worldPosition)
// 2. Анимированный переход к собору
Handler(Looper.getMainLooper()).postDelayed({
val animation = AnimationFactory.create(
type = AnimationType.SMOOTH,
duration = 3.0f
)
val targetPosition = CameraPositionFactory.create(
target = PointFactory.create(59.9343, 30.3061),
zoom = 16.0f
)
mapWindow.map.move(targetPosition, animation, null)
}, 1000)
}
}
}
fun moveToPoint(point: Point, zoom: Float = 16.0f, duration: Float = 2.0f) {
map?.let { mapWindow ->
val animation = AnimationFactory.create(
type = AnimationType.SMOOTH,
duration = duration
)
val position = CameraPositionFactory.create(
target = point,
zoom = zoom
)
mapWindow.map.move(position, animation, null)
}
}
@Serializable
data class YaGPTRequest(
val modelUri: String, // "gpt://{folderId}/yandexgpt-lite"
val completionOptions: CompletionOptions,
val messages: List<Message> // system + user prompts
)
suspend fun generatePlaceDescription(placeName: String, address: String?): String {
val systemPrompt = """
Ты - эксперт по туризму и истории. Создай интересное и информативное
описание места для туристического приложения.
Описание должно быть:
- Кратким (до 3-4 предложений)
- Интересным и познавательным
- Содержать исторические факты или интересные особенности
- Подходящим для туристов
""".trimIndent()
val userPrompt = "Создай описание для места: $placeName (адрес: $address)"
// Отправка запроса к YaGPT API
val response = httpClient.post("https://llm.api.cloud.yandex.net/foundationModels/v1/completion") {
headers {
append("Authorization", "Api-Key $apiKey")
append("x-folder-id", folderId)
}
contentType(ContentType.Application.Json)
setBody(json.encodeToString(YaGPTRequest.serializer(), request))
}
return json.decodeFromString<YaGPTResponse>(response.body())
.result.alternatives.firstOrNull()?.message?.text
?: "Не удалось получить описание"
}
class SearchController(
private val onSearchResults: (List<SearchResult>) -> Unit,
private val onSearchError: (String) -> Unit
) {
fun search(query: String, center: Point) {
// Демонстрационные результаты
val mockResults = listOf(
SearchResult(
id = "mock_1",
name = "Результат поиска: $query",
point = center,
address = "Адрес не найден",
category = "demo"
)
)
// Имитация задержки поиска
CoroutineScope(Dispatchers.Main).launch {
delay(1000)
onSearchResults(mockResults)
}
}
}
@Composable
fun AnimationControlPanel(
mapState: MapState,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.padding(16.dp),
horizontalAlignment = Alignment.End
) {
Text("🎞 Анимации", fontSize = 14.sp, fontWeight = FontWeight.Bold)
Button(onClick = { mapState.flyToIsaacCathedral() }) {
Text("✈️ Полет к Исаакию")
}
Button(onClick = { mapState.moveToIsaacCathedral() }) {
Text("📍 К Исаакию")
}
Button(onClick = { mapState.moveToStPetersburg() }) {
Text("🏛️ СПб")
}
Button(onClick = { mapState.moveToMoscow() }) {
Text("🏰 Москва")
}
}
}
@Composable
fun UserPlacemarksPanel(
mapScreenMutableState: MapScreenMutableState,
modifier: Modifier = Modifier
) {
val userPlacemarks = mapScreenMutableState.getUserPlacemarks()
if (userPlacemarks.isNotEmpty()) {
Column(modifier = modifier.padding(16.dp)) {
Text("👆 Мои метки (${userPlacemarks.size})")
Button(onClick = { mapScreenMutableState.clearUserPlacemarks() }) {
Text("🗑️ Очистить")
}
Button(onClick = {
userPlacemarks.forEach { placemark ->
mapScreenMutableState.generateDescriptionForUserPlacemark(placemark)
}
}) {
Text("🤖 Описания")
}
// Последние 3 метки
userPlacemarks.takeLast(3).forEach { userPlacemark ->
Button(onClick = {
mapScreenMutableState.mapState.moveToPoint(userPlacemark.point, 18.0f, 2.0f)
}) {
Text("📍 ${userPlacemark.title}")
}
}
}
} else {
Text("💡 Нажмите на карту\nчтобы добавить метку!")
}
}
@Composable
fun SearchPanel(
mapScreenMutableState: MapScreenMutableState,
modifier: Modifier = Modifier
) {
var searchText by remember { mutableStateOf("") }
val searchResults = mapScreenMutableState.searchResults
val isSearching = mapScreenMutableState.isSearching
Card(
modifier = modifier.padding(16.dp).fillMaxWidth(0.8f),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("🔎 Поиск мест", fontSize = 16.sp, fontWeight = FontWeight.Bold)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
value = searchText,
onValueChange = { searchText = it },
placeholder = { Text("Введите название места...") },
modifier = Modifier.weight(1f),
leadingIcon = { Icon(Icons.Default.Search, "Поиск") }
)
Button(
onClick = {
if (searchText.isNotBlank()) {
mapScreenMutableState.performSearch(searchText)
}
},
enabled = searchText.isNotBlank() && !isSearching
) {
if (isSearching) {
CircularProgressIndicator(modifier = Modifier.size(16.dp))
} else {
Text("Найти")
}
}
}
// Результаты поиска с описаниями от YaGPT
if (searchResults.isNotEmpty()) {
LazyColumn(modifier = Modifier.heightIn(max = 200.dp)) {
items(searchResults.take(5)) { result ->
SearchResultItem(
result = result,
onClick = {
mapScreenMutableState.mapState.moveToPoint(result.point, 16.0f, 2.0f)
}
)
}
}
}
}
}
}
MapsWorkShop/
├── 📱 composeApp/ # Основное приложение
│ ├── src/
│ │ ├── androidMain/ # Android-специфичный код
│ │ │ └── kotlin/.../Map.android.kt # Android реализация карты
│ │ ├── commonMain/ # Общий код для всех платформ
│ │ │ └── kotlin/.../App.kt # Основная логика приложения
│ │ └── iosMain/ # iOS-специфичный код
│ ├── build.gradle.kts # Конфигурация сборки
│ └── srcSet/ # Настройки исходных наборов
├── 🔧 common/ # Общие модули
├── 🗺️ mapkit-bindings/ # Yandex MapKit KMP
├── 🔗 mapkit-interop/ # Межплатформенные адаптеры
├── 📚 docs/ # Документация
├── 🏗️ build.gradle.kts # Корневая конфигурация
├── 📋 gradle.properties # Свойства Gradle
├── 🔑 local.properties # Локальные настройки
└── 📖 README.md # Этот файл
sourceSets.commonMain.dependencies {
// Compose Multiplatform
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
// Ktor для HTTP запросов
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.json)
implementation(libs.ktor.client.logging)
// Сериализация
implementation(libs.kotlinx.serialization)
}
sourceSets.androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
// Ktor Android клиент
implementation(libs.ktor.client.okhttp)
}
# Yandex Cloud
folderId=b1g334ut7mfp3stm8gnk
# YaGPT API
gptToken=t1.9euelZrJk5zNnM_GnsvMncmTzsqMl-3rnpWajZybjc-YicaJzczPlouXj5Hl8_d8J1o6-e9qJCw8_N3z9zxWVzr572okLDz8zef1656VmpuYkZGTlZzKmYvLjpLHy8mM7_zF656VmpuYkZGTlZzKmYvLjpLHy8mM.X4DV0UomYuFRk2M1DAsFT-vCnf8yipedm1UoRkLVxDoQVUevaZnSFIaWH67Vn3aIRrnWOQMgoM2sdTeoE1ikAg
# Yandex MapKit
mapkitToken=e049cdec-2adc-4569-a841-3311fe521913
# Android SDK путь
sdk.dir=C\:\\Users\\YourUsername\\AppData\\Local\\Android\\Sdk
./gradlew :composeApp:assembleDebug
./gradlew :composeApp:assembleRelease
./gradlew :composeApp:installDebug
./gradlew :composeApp:iosSimulatorArm64Test
./gradlew :composeApp:jsBrowserDevelopmentRun
-
Карта и анимации:
- Запуск приложения → автоматический переход к Исаакиевскому собору
- Тестирование кнопок анимаций
- Проверка плавности переходов
-
Интерактивность:
- Создание меток нажатием на карту
- Управление пользовательскими метками
- Навигация между метками
-
Поиск и YaGPT:
- Ввод поисковых запросов
- Проверка генерации описаний
- Отображение результатов поиска
adb logcat | grep "MapsWorkShop"
# Тест YaGPT API
curl -X POST "https://llm.api.cloud.yandex.net/foundationModels/v1/completion" \
-H "Authorization: Api-Key YOUR_TOKEN" \
-H "x-folder-id: YOUR_FOLDER_ID" \
-H "Content-Type: application/json" \
-d '{"modelUri":"gpt://YOUR_FOLDER_ID/yandexgpt-lite",...}'
- Fork репозитория
- Создайте feature branch (
git checkout -b feature/AmazingFeature
) - Commit изменения (
git commit -m 'Add some AmazingFeature'
) - Push в branch (
git push origin feature/AmazingFeature
) - Откройте Pull Request
Используйте GitHub Issues для:
- Сообщений об ошибках
- Предложений новых функций
- Вопросов по использованию
## 🐛 Описание ошибки
Краткое описание проблемы
## 🔍 Шаги для воспроизведения
1. Откройте приложение
2. Выполните действие X
3. Ошибка происходит в Y
## 📱 Окружение
- Устройство: [например, Samsung Galaxy S21]
- Android версия: [например, 13]
- Версия приложения: [например, 1.0.0]
## 📸 Скриншоты
Добавьте скриншоты если применимо
## 💻 Логи
adb logcat | grep "MapsWorkShop"
- Yandex MapKit: Документация
- YaGPT: API документация
- Compose Multiplatform: Руководство
- Kotlin Multiplatform: Документация
- 📚 Полный индекс: DOCUMENTATION_INDEX.md - Центральный индекс всей документации
- 🏗️ Архитектура: ARCHITECTURE.md - Описание архитектуры проекта
- 📚 API Справочник: API_REFERENCE.md - Подробная справка по API
- 🤝 Руководство разработчика: CONTRIBUTING.md - Как внести вклад в проект
- 🔧 Решение проблем: TROUBLESHOOTING.md - FAQ и решение проблем
- 📱 Мобильная разработка: MOBILE_DEVELOPMENT.md - Руководство по мобильным приложениям
- 📋 История изменений: CHANGELOG.md - История версий и изменений
- "Kotlin Multiplatform by Example" - практические примеры
- "Compose Multiplatform: From Android to iOS" - руководство по UI
- "Yandex MapKit Developer Guide" - работа с картами
Этот проект распространяется под лицензией MIT. См. файл LICENSE для подробностей.
- Основной разработчик: kramlex
- UI/UX дизайн: MapsWorkShop Team
- Документация: AI Assistant
- Yandex за предоставление MapKit и YaGPT API
- JetBrains за Kotlin и Compose Multiplatform
- Сообщество за вклад в развитие проекта
⭐ Если проект вам понравился, поставьте звездочку! ⭐
🚀 Следите за обновлениями и новыми функциями! 🚀
Последнее обновление: Декабрь 2024