При разработке образовательных игр важно предоставлять точную и содержательную обратную связь для привлечения пользователей. В этой статье я поделюсь, как мы реализовали географическую систему расчетов для Flagle Explorer, игры на угадывание флагов, которая помогает пользователям изучать мировую географию через интерактивную обратную связь.
Техническое испытание
Наши основные требования были:
- Точные расчеты расстояний между любыми двумя точками на Земле
- Точные расчеты направлений для навигационного руководства
- Нормализованная оценка близости
- Работа в реальном времени для моментальной обратной связи
Детали реализации
1. Основная структура данных
Сначала мы определили интерфейс нашей базовой географической точки:
export interface GeoPoint {
lat: number; // Latitude in degrees
lon: number; // Longitude in degrees
}
2. Реализация расчета расстояний
Мы реализовали формулу Хаверсина для расчета расстояний по великому кругу:
export function calculateDistance(point1: GeoPoint, point2: GeoPoint): number {
// Early return for identical points
if (point1.lat === point2.lat && point1.lon === point2.lon) {
return 0;
}
const R = 6371000; // Earth's radius in meters
// Convert to radians
const dLat = (point2.lat - point1.lat) * Math.PI / 180;
const dLon = (point2.lon - point1.lon) * Math.PI / 180;
const lat1 = point1.lat * Math.PI / 180;
const lat2 = point2.lat * Math.PI / 180;
// Haversine formula
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1) * Math.cos(lat2) *
Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return (R * c) / 1000; // Convert to kilometers
}
3. Система расчета подшипников
Мы разработали сложный расчет подшипников, который преобразует сложные угловые математические вычисления в понятные направленные индикаторы:
export function calculateOrientation(point1: GeoPoint, point2: GeoPoint): number {
if (point1.lat === point2.lat && point1.lon === point2.lon) return 0;
// Convert to radians
const lat1 = point1.lat * Math.PI / 180;
const lat2 = point2.lat * Math.PI / 180;
const dLon = (point2.lon - point1.lon) * Math.PI / 180;
// Calculate bearing
const y = Math.sin(dLon) * Math.cos(lat2);
const x = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
let bearing = Math.atan2(y, x) * 180 / Math.PI;
return (bearing + 360) % 360;
}
4. Картографирование направлений для удобства пользователя
Для упрощения расчетов подшипников мы отображаем их с помощью направленных эмодзи:
export function calculateOrientationEmoji(point1: GeoPoint, point2: GeoPoint): string {
const orientation = calculateOrientation(point1, point2);
// Map angles to 8-direction compass
if (orientation >= 337.5 || orientation < 22.5) return '⬆️';
if (orientation >= 22.5 && orientation < 67.5) return '↗️';
if (orientation >= 67.5 && orientation < 112.5) return '➡️';
if (orientation >= 112.5 && orientation < 157.5) return '↘️';
if (orientation >= 157.5 && orientation < 202.5) return '⬇️';
if (orientation >= 202.5 && orientation < 247.5) return '↙️';
if (orientation >= 247.5 && orientation < 292.5) return '⬅️';
return '↖️';
}
Рассмотрение производительности
- Ранние возвраты: Мы используем ранние возвраты для идентичных точек, чтобы избежать ненужных вычислений.
- Оптимизация постоянных величин: Радиус Земли и преобразования градусов в радианы предварительно рассчитываются.
- Контроль точности: Числа округляются до соответствующего количества десятичных знаков для балансировки точности и производительности.
Обработка ошибок и крайних случаев
Наша реализация обрабатывает несколько крайних случаев:
- Идентичные точки
- Антиподальные точки
- Точки на полюсах
- Расчеты через датовую линию
Стратегия тестирования
Мы провели всесторонние тесты, охватывающие:
- Известные расчеты расстояний между крупными городами
- Граничные случаи на полюсах и международной линии перемены дат
- Расчеты направлений для кардинальных и междукардинальных точек
- Показатели производительности для обратной связи в реальном времени
Прикладное применение в реальном мире
Эта система успешно внедрена в Flagle Explorer, обрабатывая тысячи расчетов ежедневно с:
- Средним временем ответа < 5 мс
- Точностью 99,99% по сравнению с эталонными расчетами
- Отсутствием сообщенных ошибок, связанных с расчетами, в производстве
Будущие оптимизации
Мы исследуем несколько улучшений:
- Реализация WebAssembly для сложных расчетов
- Кэширование часто используемых маршрутов
- Пакетная обработка для многоточечных расчетов
- Интеграция с данными об высоте местности
Заключение
Построение географической системы расчетов требует тщательного внимания к математической точности, оптимизации производительности и пользовательскому опыту. Наша реализация на TypeScript успешно учитывает эти факторы, сохраняя читаемость кода и удобство обслуживания.
Хотите увидеть эти расчеты в действии? Вы можете попробовать их в действии в Flagle Explorer и увидеть, как индикаторы расстояния и направления помогут вам ориентироваться по глобальной географии!
Репозиторий кода
Полная реализация доступна на нашем GitHub. Вот краткое руководство по началу работы:
import { calculateDistance, calculateOrientationEmoji } from 'the-library/geo';
const london: GeoPoint = { lat: 51.5074, lon: -0.1278 };
const tokyo: GeoPoint = { lat: 35.6762, lon: 139.6503 };
const distance = calculateDistance(london, tokyo);
const direction = calculateOrientationEmoji(london, tokyo);
console.log(`Tokyo is ${distance}km ${direction} from London`);
Эта реализация доказала свою надежность в производстве, обрабатывая миллионы расчетов и при этом поддерживая высокие стандарты производительности и точности.
Source:
https://dzone.com/articles/geographic-distance-calculator-using-typescript