Типизированные обертки над MapLibre GL JS позволяют превратить динамическое, слабо типизированное API в строго проверяемую систему, удобную для масштабных приложений. В основе подхода лежит идея изоляции «сырых» объектов библиотеки и предоставления поверх них слоя TypeScript-типов, контрактов и доменных интерфейсов, которые фиксируют структуру данных, событий и конфигурации карты.
JavaScript-API картографических движков традиционно построены вокруг гибких объектов: параметры слоёв, источников данных, событий и стилей принимаются в виде свободных структур. Это повышает скорость прототипирования, но снижает устойчивость системы при росте кода.
Типизированные обёртки решают несколько ключевых задач:
Основной объект библиотеки — экземпляр карты. В типизированной архитектуре он никогда не используется напрямую. Вместо этого создаётся обёртка, описывающая допустимые операции.
import maplibregl, { Map } from "maplibre-gl";
export interface TypedMapOptions {
container: string | HTMLElement;
center: [number, number];
zoom: number;
style: string;
}
export class TypedMap {
private map: Map;
constructor(options: TypedMapOptions) {
this.map = new maplibregl.Map(options);
}
getInstance(): Map {
return this.map;
}
}
Ключевой принцип — внешнему коду не предоставляется доступ к
Map напрямую без необходимости. Это создаёт контролируемую
точку интеграции.
Стиль карты в MapLibre представляет собой сложную JSON-структуру. Без типизации она становится источником ошибок: опечатки в идентификаторах слоёв или источников не выявляются заранее.
Подход с обёртками включает:
export type SourceId = string;
export type LayerId = string;
export interface TypedGeoJSONSource {
type: "geojson";
dat a: GeoJSON.FeatureCollection;
}
export interface TypedFillLayer {
id: LayerId;
type: "fill";
source: SourceId;
paint?: {
"fill-color"?: string;
"fill-opacity"?: number;
};
}
Такой слой позволяет проверять корректность структуры ещё до отправки данных в карту.
Источники данных в картографических приложениях обладают разной структурой: vector tiles, raster tiles, geojson. Унификация через generics позволяет формализовать зависимости.
interface BaseSource<TType extends string> {
type: TType;
}
interface GeoJSONSource extends BaseSource<"geojson"> {
data: GeoJSON.FeatureCollection;
}
interface RasterSource extends BaseSource<"raster"> {
tiles: string[];
tileSize?: number;
}
type AnySource = GeoJSONSource | RasterSource;
Дальнейшее расширение API карты строится на этих типах, что исключает передачу несовместимых данных.
Добавление слоёв через обёртку позволяет контролировать соответствие слоя источнику данных.
type Layer =
| TypedFillLayer
| TypedLineLayer
| TypedCircleLayer;
export class LayerManager {
constructor(private map: Map) {}
addLayer(layer: Layer) {
this.map.addLayer(layer as any);
}
removeLayer(id: string) {
this.map.removeLayer(id);
}
}
На уровне TypeScript возможно расширение проверки: сопоставление
layer.source с зарегистрированными источниками карты через
дополнительный слой состояния.
События в MapLibre представляют собой слабоструктурированные объекты. Типизированная обёртка вводит строгую сигнатуру обработчиков.
export interface MapEvents {
click: maplibregl.MapMouseEvent;
move: maplibregl.MapEvent;
load: maplibregl.MapEvent;
}
export class TypedEventEmitter {
constructor(private map: Map) {}
on<K extends keyof MapEvents>(
event: K,
handler: (e: MapEvents[K]) => void
) {
this.map.on(event, handler as any);
}
}
Такой подход исключает передачу обработчика с неверным типом аргумента.
При расширении функциональности карты часто требуется добавление пользовательских слоёв, источников или событий. TypeScript позволяет расширять типы библиотеки без модификации исходного кода.
declare module "maplibre-gl" {
interface Map {
_customRegistry?: Record<string, unknown>;
}
}
Это создаёт механизм безопасного хранения метаданных внутри экземпляра карты.
Фабричный подход позволяет централизовать создание экземпляров с единым контрактом конфигурации.
export interface MapFactoryOptions {
container: HTMLElement;
center: [number, number];
zoom: number;
style: string;
}
export function createTypedMap(options: MapFactoryOptions) {
return new TypedMap(options);
}
Расширение фабрики может включать:
Геоданные требуют строгого соответствия спецификации. TypeScript предоставляет типы GeoJSON, которые используются как основа доменной модели.
import { FeatureCollection, Geometry } from "geojson";
export interface TypedFeature<G extends Geometry> {
type: "Feature";
geometry: G;
properties: Record<string, unknown>;
}
Такой уровень абстракции позволяет строить геопространственные операции без потери типовой безопасности.
Типизированная обёртка перестаёт быть просто оболочкой над API и превращается в доменный слой:
Пример структуры:
export class MapContext {
constructor(
public map: TypedMap,
public layers: LayerManager,
public events: TypedEventEmitter
) {}
}
Такой подход позволяет отделить бизнес-логику от низкоуровневого взаимодействия с картой.
Ключевой архитектурный принцип — запрет прямого доступа к нативному API вне обёрток. Это достигается:
class SafeMap {
private readonly map: Map;
constructor(map: Map) {
this.map = map;
}
setCenter(center: [number, number]) {
this.map.setCenter(center);
}
}
Расширение функциональности карты через плагины требует строгого описания интерфейсов расширения.
export interface MapPlugin {
name: string;
install(context: MapContext): void;
}
export class PluginManager {
constructor(private context: MapContext) {}
use(plugin: MapPlugin) {
plugin.install(this.context);
}
}
Такой подход позволяет вводить модульную архитектуру без нарушения целостности типов.
При росте системы типы начинают комбинироваться:
type MapState = {
zoom: number;
center: [number, number];
activeLayerId?: string;
};
Типизированная обёртка превращает состояние карты в формально описанную структуру, пригодную для интеграции с внешними системами управления состоянием.
Основной эффект введения типизированных обёрток проявляется в переносе класса ошибок из runtime в compile-time:
Это снижает стоимость поддержки и повышает предсказуемость поведения системы при сложных сценариях визуализации геоданных.