Пользовательские проекции

Система координат (CRS — Coordinate Reference System) определяет способ интерпретации географических данных на плоскости карты. В Leaflet базовая архитектура построена вокруг предположения, что все тайлы, координаты и преобразования подчиняются выбранной проекции. По умолчанию используется Web Mercator, однако архитектура библиотеки позволяет полностью заменить систему координат, включая математическую модель преобразования широты/долготы в пиксельные координаты.


CRS как основа системы координат

В Leaflet каждая карта опирается на объект L.CRS, который описывает:

  • способ преобразования географических координат в пиксели;
  • масштабирование карты;
  • поведение тайловой сетки;
  • ограничение диапазонов координат;
  • методы обратного преобразования (pixel → latlng).

Основные встроенные CRS:

  • L.CRS.EPSG3857 — Web Mercator (дефолтный вариант)
  • L.CRS.EPSG4326 — географическая проекция (псевдо-равноугольная)
  • L.CRS.Simple — плоская декартова система координат

Каждый CRS реализует единый интерфейс, что позволяет подменять его без изменения логики карты.


Web Mercator как базовая модель

Web Mercator (EPSG:3857) используется в большинстве веб-картографических систем. Его особенности:

  • широта и долгота проецируются на цилиндрическую поверхность;
  • искажения возрастают ближе к полюсам;
  • удобно работать с тайловыми слоями;
  • координаты ограничены диапазоном примерно ±85° по широте.

Leaflet использует эту проекцию как стандартную, так как она совместима с большинством тайловых серверов (OpenStreetMap, Mapbox и др.).


Зачем нужны пользовательские проекции

Переопределение CRS требуется в случаях:

  • работа с локальными картами (здания, этажи, игровые уровни);
  • использование национальных систем координат (например, Gauss-Krüger);
  • интеграция с CAD/GIS данными;
  • отображение изображений как карты;
  • работа с нестандартными тайловыми сетками;
  • научные и инженерные визуализации.

В таких сценариях Web Mercator становится ограничением, так как предполагает географическую модель Земли.


Пространство L.CRS.Simple

Наиболее простой вариант пользовательской системы координат — L.CRS.Simple.

Она трактует карту как двумерную плоскость:

  • координаты: (x, y) вместо (lat, lng);
  • отсутствует географическая проекция;
  • масштабирование линейное;
  • идеально подходит для изображений и схем.

Пример использования:

const map = L.map('map', {
  crs: L.CRS.Simple,
  minZoom: -2
});

const bounds = [[0, 0], [1000, 1000]];

L.imageOverlay('floorplan.png', bounds).addTo(map);

map.fitBounds(bounds);

Особенность: Leaflet воспринимает координаты как линейное пространство, где «широта» и «долгота» — это просто X и Y.


Архитектура пользовательского CRS

Полноценный пользовательский CRS реализуется через объект с набором обязательных методов:

  • latLngToPoint(latlng, zoom)
  • pointToLatLng(point, zoom)
  • project(latlng)
  • unproject(point)
  • scale(zoom)
  • zoom(scale)
  • wrapLatLng(latlng)

Эти методы образуют математический слой преобразования координат.

Минимальный шаблон:

const CustomCRS = L.extend({}, L.CRS, {
  project(latlng) {
    return new L.Point(latlng.lng * 200, latlng.lat * 200);
  },

  unproject(point) {
    return new L.LatLng(point.y / 200, point.x / 200);
  },

  scale(zoom) {
    return Math.pow(2, zoom);
  }
});

Масштабирование и уровни зума

Функция scale(zoom) определяет, как меняется размер карты при увеличении масштаба.

Стандартная модель:

  • zoom 0 = базовый масштаб
  • каждый следующий уровень удваивает детализацию
  • используется степень двойки
scale(zoom) {
  return 256 * Math.pow(2, zoom);
}

Нестандартные CRS могут использовать:

  • логарифмическое масштабирование;
  • линейное масштабирование;
  • дискретные уровни (например, для этажей зданий).

Пример: локальная координатная система здания

Для карты здания часто используют условные метры:

const BuildingCRS = L.extend({}, L.CRS.Simple, {
  transformation: new L.Transformation(1 / 2, 0, -1 / 2, 0)
});

Инициализация:

const map = L.map('map', {
  crs: BuildingCRS,
  minZoom: 0,
  maxZoom: 4
});

Здесь L.Transformation задаёт линейное преобразование:

  • масштаб по X и Y;
  • смещение;
  • инверсию оси Y (часто требуется для изображений).

Использование L.Transformation

Ключевой элемент пользовательских CRS — линейные преобразования:

new L.Transformation(a, b, c, d)

Формула:

  • x’ = a * x + b
  • y’ = c * y + d

Это позволяет:

  • изменять масштаб;
  • смещать координаты;
  • инвертировать оси.

Пример инверсии Y:

const t = new L.Transformation(1, 0, -1, 0);

Интеграция Proj4 и сложных проекций

Для работы с реальными географическими CRS часто используется библиотека proj4js и расширение Proj4Leaflet.

Она позволяет подключать EPSG-коды:

  • EPSG:3395
  • EPSG:32636
  • национальные проекции

Пример:

const crs = new L.Proj.CRS(
  'EPSG:32636',
  '+proj=utm +zone=36 +datum=WGS84 +units=m +no_defs',
  {
    resolutions: [
      8192, 4096, 2048, 1024, 512,
      256, 128, 64, 32, 16, 8, 4, 2, 1
    ]
  }
);

const map = L.map('map', { crs });

Тайловая система в пользовательских CRS

TileLayer в Leaflet зависит от CRS:

  • система координат определяет сетку тайлов;
  • влияет на индексацию x/y;
  • задаёт количество тайлов на уровне zoom.

При пользовательском CRS необходимо учитывать:

  • соответствие tile server;
  • корректность resolutions;
  • origin (начало координат).

Origin и anchor point

Каждый CRS имеет точку отсчёта:

  • обычно (0, 0) — верхний левый угол;
  • может быть центр карты;
  • может быть произвольная точка.

Пример изменения origin:

const crs = L.extend({}, L.CRS.Simple, {
  transformation: new L.Transformation(1, 500, -1, 500)
});

Это смещает систему координат в центр условного пространства.


Обратное преобразование координат

Leaflet требует симметрии:

  • forward transform: latLng → pixel
  • inverse transform: pixel → latLng

Пример:

pointToLatLng(point, zoom) {
  const scale = this.scale(zoom);
  return L.latLng(point.y / scale, point.x / scale);
}

Нарушение симметрии приводит к:

  • дрейфу маркеров;
  • некорректному pan;
  • смещению тайлов.

Ограничения пользовательских проекций

При создании CRS необходимо учитывать:

  • производительность трансформаций;
  • совместимость с тайлами;
  • округление координат;
  • стабильность при масштабировании.

Сложные нелинейные проекции могут снижать производительность из-за частых вычислений при pan/zoom.


Применение в игровых картах

Игровые миры часто используют собственные CRS:

  • координаты в метрах игрового мира;
  • ось Y направлена вверх;
  • отсутствие географической привязки.

Пример:

const GameCRS = L.extend({}, L.CRS.Simple, {
  transformation: new L.Transformation(1, 0, -1, 0),
  scale: function (zoom) {
    return Math.pow(2, zoom);
  }
});

Используется для:

  • RPG карт;
  • стратегических игр;
  • редакторов уровней.

Согласование слоёв при смене CRS

При смене CRS важно учитывать:

  • все слои пересоздаются;
  • тайлы не кэшируются между CRS;
  • маркеры требуют перерасчёта позиции;
  • GeoJSON пересчитывается через project/unproject.

GeoJSON и пользовательские CRS

GeoJSON данные интерпретируются через CRS:

L.geoJSON(data, {
  coordsToLatLng: function (coords) {
    return L.latLng(coords[1], coords[0]);
  }
}).addTo(map);

Это позволяет адаптировать:

  • нестандартные форматы;
  • локальные системы координат;
  • инженерные чертежи.

Производительность пользовательских CRS

Ключевые факторы:

  • частота вызова project/unproject;
  • сложность математических операций;
  • количество объектов на карте;
  • частота zoom/pan событий.

Оптимизации:

  • кэширование преобразований;
  • предрасчёт resolutions;
  • использование линейных моделей вместо нелинейных.