Типизация в экосистеме MapLibre GL JS опирается на сочетание декларативных интерфейсов, структурных типов и расширяемых контрактов, которые отражают динамическую природу WebGL-картографического рендеринга и одновременно позволяют сохранить строгую проверку на этапе компиляции TypeScript. Основная сложность заключается в том, что модель данных карты включает сразу несколько уровней абстракции: геометрии GeoJSON, стили слоя, источники данных, события взаимодействия и расширяемые плагины.
В основе типизации MapLibre GL JS лежит структурный подход, при котором совместимость определяется не именем типа, а его формой. Это особенно важно для объектов конфигурации карты, где большинство сущностей описывается через набор опциональных и обязательных полей.
import maplibregl from "maplibre-gl";
const map = new maplibregl.Map({
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [0, 0],
zoom: 2
});
Тип Map инкапсулирует десятки методов и событий, но при
этом инициализация требует лишь частичного описания конфигурации. Это
достигается через использование частичных типов и перегрузок
конструкторов.
Конфигурационные объекты MapLibre активно используют
Partial<T>, что позволяет задавать только необходимые
поля:
type MapOptions = Partial<{
container: string | HTMLElement;
style: string | object;
center: [number, number];
zoom: number;
}>;
Такой подход снижает когнитивную нагрузку и позволяет постепенно наращивать конфигурацию без нарушения типовой целостности.
Одним из ключевых элементов типизации выступает стандарт GeoJSON, который строго формализован через набор интерфейсов:
import type { FeatureCollection, Geometry } from "geojson";
const data: FeatureCollection<Geometry> = {
type: "FeatureCollection",
features: []
};
Здесь используется параметризация по типу геометрии, что позволяет ограничивать допустимые формы данных. Например, можно зафиксировать только точки:
import type { FeatureCollection, Point } from "geojson";
type PointCollection = FeatureCollection<Point>;
Такой подход критически важен при работе с источниками
geojson в MapLibre, где несоответствие геометрии приводит к
runtime-ошибкам рендеринга.
MapLibre разделяет данные на источники, каждый из которых имеет
собственную структуру. Основные типы включают vector,
raster, geojson, image,
video.
type Source =
| GeoJSONSource
| VectorSource
| RasterSource
| ImageSource
| VideoSource;
Использование дискриминированных объединений позволяет безопасно работать с источниками:
function isGeoJSONSource(source: Source): source is GeoJSONSource {
return source.type === "geojson";
}
Это обеспечивает корректную работу с API, где методы зависят от конкретного типа источника.
Стиль карты представляет собой иерархию слоёв, каждый из которых строго типизирован:
type Layer =
| FillLayer
| LineLayer
| SymbolLayer
| CircleLayer
| HeatmapLayer
| FillExtrusionLayer;
Каждый слой имеет обязательное поле type, выступающее
дискриминатором:
interface FillLayer {
id: string;
type: "fill";
source: string;
paint?: FillPaint;
layout?: FillLayout;
}
Типизация paint и layout реализуется через
вложенные интерфейсы, отражающие свойства визуализации. Это позволяет на
этапе компиляции исключать некорректные комбинации свойств.
Ключевым паттерном является использование discriminated unions для слоёв:
type AnyLayer = {
id: string;
type: string;
} & (FillLayer | LineLayer | SymbolLayer);
Такой подход позволяет TypeScript автоматически сужать тип при
проверке поля type:
function handleLayer(layer: AnyLayer) {
if (layer.type === "line") {
layer.paint.lineColor;
}
}
MapLibre активно использует событийную модель, где события параметризуются типами данных:
map.on("click", (e) => {
const lngLat = e.lngLat;
});
В TypeScript это выражается через перегрузки:
type MapMouseEvent = {
type: string;
lngLat: maplibregl.LngLat;
point: maplibregl.Point;
};
Для повышения строгости используется сопоставление событий с конкретными типами:
type MapEventType =
| "click"
| "mousemove"
| "load"
| "idle";
Одним из наиболее мощных механизмов является расширение типов через декларативное дополнение модулей:
declare module "maplibre-gl" {
interface Map {
customMethod(): void;
}
}
Этот паттерн позволяет интегрировать сторонние плагины без модификации исходных типов библиотеки.
При создании кастомных слоёв используется строгий контракт:
interface CustomLayer {
id: string;
type: "custom";
renderingMode: "2d" | "3d";
onAdd(map: maplibregl.Map): void;
render(gl: WebGLRenderingContext): void;
}
Такой интерфейс обеспечивает согласованность с жизненным циклом WebGL-рендеринга.
Генерики применяются для параметризации данных:
interface TypedSource<T> {
data: T;
}
Это особенно полезно при работе с GeoJSON:
type TypedGeoJSONSource<T> = TypedSource<FeatureCollection<T>>;
Внутренние конфигурации часто используют utility types:
Partial<T>Required<T>Pick<T, K>Omit<T, K>Пример:
type MinimalLayer = Pick<FillLayer, "id" | "type" | "source">;
Такой подход позволяет строить облегчённые версии конфигураций для динамического создания слоёв.
MapLibre поддерживает фильтры в стиле выражений, которые также типизируются:
type Filter =
| ["==", string, any]
| ["!=", string, any]
| [">", string, number];
Это позволяет ограничить синтаксис выражений на уровне компиляции:
const filter: Filter = ["==", "type", "park"];
Географические координаты строго типизированы:
interface LngLat {
lng: number;
lat: number;
}
А экранные координаты отделены в отдельный тип:
interface Point {
x: number;
y: number;
}
Разделение этих пространств предотвращает логические ошибки при трансформации координат между слоями представления.
Интерфейсы анимации используют функции высшего порядка:
type EasingFunction = (t: number) => number;
Это позволяет типизировать кастомные интерполяции:
const easeIn: EasingFunction = (t) => t * t;
Типизация MapLibre часто строится на принципе изоляции внутренних и внешних API. Внутренние структуры не экспортируются напрямую, а внешние интерфейсы предоставляются через стабильные типы.
Это достигается комбинацией:
export typeinterface segregationreadonly полейНекоторые части API проектируются как неизменяемые:
type ReadonlyStyle = Readonly<StyleSpecification>;
Это снижает риск случайного изменения состояния карты после загрузки стиля.
Финальная модель типизации строится как композиция:
type MapEntity =
| Source
| Layer
| StyleSpecification;
Такая композиция позволяет унифицировать обработку всех сущностей карты в общих утилитах сериализации, валидации и миграции конфигураций.