MapLibre GL JS построена вокруг динамической модели конфигурации карты, где значительная часть поведения задаётся через JSON-объекты: стили, источники данных, слои, выражения. В таких условиях типизация выполняет роль слоя формализации поверх гибкой, но слабо структурированной модели данных.
Основная сложность типизации возникает из-за сочетания трёх факторов: большой и глубокой JSON-схемы стиля, динамических источников данных (GeoJSON, vector tiles, raster tiles) и событийной модели API. TypeScript здесь используется не как формальный ограничитель, а как инструмент описания контрактов между частями системы.
В экосистеме MapLibre основой типизации служат декларации,
описывающие публичный API: Map, MapOptions,
StyleSpecification, Source,
Layer.
Ключевой тип — объект карты:
import type { Map, MapOptions } from "maplibre-gl";
const options: MapOptions = {
container: "map",
style: "https://demotiles.maplibre.org/style.json",
center: [30.3, 59.9],
zoom: 10
};
const map: Map = new Map(options);
Тип MapOptions описывает допустимые параметры
конфигурации. При этом часть полей опциональна, а часть перегружена
union-типами: строка URL, объект стиля или функция.
MapLibre активно использует стандарт GeoJSON, и именно он формирует основу типизации пространственных данных.
import type { Feature, FeatureCollection, Geometry } from "geojson";
const point: Feature = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [30.3, 59.9]
},
properties: {
name: "sample"
}
};
Типы GeoJSON обеспечивают строгую структуру геометрии:
PointLineStringPolygonMultiPointMultiLineStringMultiPolygonКлючевая особенность: TypeScript не просто проверяет структуру, но и
сужает типы координат в зависимости от геометрии. Однако на практике в
MapLibre часто используются обобщённые Geometry или
GeometryCollection, что снижает строгость ради
гибкости.
Источники данных являются одной из наиболее сложных зон типизации, поскольку каждый тип источника имеет собственную структуру.
import type { VectorSourceSpecification } from "maplibre-gl";
const vectorSource: VectorSourceSpecification = {
type: "vector",
url: "mapbox://maplibre.traffic"
};
import type { GeoJSONSourceSpecification } from "maplibre-gl";
const geojsonSource: GeoJSONSourceSpecification = {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
};
import type { RasterSourceSpecification } from "maplibre-gl";
const raster: RasterSourceSpecification = {
type: "raster",
tiles: ["https://tiles.example.com/{z}/{x}/{y}.png"],
tileSize: 256
};
Типизация источников основана на discriminated union через поле
type, что позволяет TypeScript точно определять допустимые
поля конфигурации.
Центральная часть типизации MapLibre —
StyleSpecification. Это формализованная структура
JSON-стиля.
import type { StyleSpecification } from "maplibre-gl";
const style: StyleSpecification = {
version: 8,
sources: {
osm: {
type: "vector",
url: "https://example.com/tiles.json"
}
},
layers: [
{
id: "background",
type: "background",
paint: {
"background-color": "#000"
}
}
]
};
Тип StyleSpecification включает:
sources — словарь источниковlayers — массив слоёвsprite, glyphs — строки или URLtransition — объект анимацииСложность заключается в том, что layers представляет
собой union-тип всех возможных типов слоёв, каждый из которых имеет
собственный набор полей.
Слои — наиболее вариативная часть API. Каждый тип слоя имеет строго определённую структуру.
import type { FillLayerSpecification } from "maplibre-gl";
const layer: FillLayerSpecification = {
id: "water",
type: "fill",
source: "osm",
"source-layer": "water",
paint: {
"fill-color": "#00f"
}
};
import type { LineLayerSpecification } from "maplibre-gl";
const line: LineLayerSpecification = {
id: "roads",
type: "line",
source: "osm",
"source-layer": "roads",
paint: {
"line-color": "#ff0000",
"line-width": 2
}
};
import type { SymbolLayerSpecification } from "maplibre-gl";
const symbol: SymbolLayerSpecification = {
id: "labels",
type: "symbol",
source: "osm",
"source-layer": "labels",
layout: {
"text-field": "{name}"
}
};
Типизация слоёв основана на дискриминаторе type, что
позволяет безопасно разделять конфигурации.
MapLibre активно использует expression language для динамической стилизации. Однако TypeScript не всегда способен строго типизировать выражения из-за их рекурсивной структуры.
Пример:
const layer = {
id: "population",
type: "circle",
source: "cities",
paint: {
"circle-radius": [
"interpolate",
["linear"],
["get", "population"],
0, 2,
1000000, 10
]
}
};
В типах это часто представлено как:
type Expression = any[] | string | number;
Причина слабой типизации — невозможность статически вывести все допустимые комбинации операторов и аргументов.
Событийная модель MapLibre также имеет строгие типы.
map.on("click", (e) => {
console.log(e.lngLat);
});
Типизация событий включает:
MapMouseEventMapTouchEventMapLayerMouseEventПример:
import type { MapMouseEvent } from "maplibre-gl";
function handler(e: MapMouseEvent) {
console.log(e.point);
console.log(e.lngLat);
}
Типизация событий обеспечивает доступ к координатам, пиксельным координатам и объектам слоя.
В реальных проектах часто требуется расширять типы MapLibre, например добавлять кастомные свойства в properties GeoJSON или расширять события.
interface MyFeatureProps {
id: string;
category: string;
}
import type { Feature } from "geojson";
type TypedFeature = Feature<GeoJSON.Point, MyFeatureProps>;
declare module "maplibre-gl" {
interface MapOptions {
customFlag?: boolean;
}
}
Такой подход позволяет интегрировать MapLibre в крупные TypeScript-архитектуры без потери типовой согласованности.
Несмотря на наличие деклараций, типизация MapLibre не является полностью строгой по нескольким причинам:
any для совместимостиЭто приводит к тому, что TypeScript используется как “структурный фильтр”, а не как строгая формальная система.
В реальных приложениях типизация дополняется runtime-валидацией. Это особенно важно для данных, приходящих извне.
Пример с проверкой GeoJSON:
import { FeatureCollection } from "geojson";
function isFeatureCollection(data: any): data is FeatureCollection {
return data?.type === "FeatureCollection";
}
Также часто используются схемы:
Пример с Zod:
import { z } from "zod";
const FeatureSchema = z.object({
type: z.literal("Feature"),
geometry: z.object({
type: z.string(),
coordinates: z.any()
}),
properties: z.record(z.any())
});
MapLibre позволяет подключать кастомные источники, например WebGL-based или tile sources. В таких случаях типизация часто уходит в расширенные интерфейсы:
interface CustomSource {
type: "custom";
render: (gl: WebGLRenderingContext) => void;
}
Такие типы не входят в стандартные декларации и требуют ручного описания.
Использование TypeScript в MapLibre-проектах влияет на архитектуру следующим образом:
При этом чрезмерная строгость иногда замедляет разработку из-за сложности expression-типов.
Типовая система MapLibre строится вокруг нескольких ключевых осей:
Эти элементы связаны через дискриминированные объединения и частичную типизацию, формируя баланс между строгой структурой и динамической гибкостью.