Типизация MapLibre

MapLibre GL JS построена вокруг динамической модели конфигурации карты, где значительная часть поведения задаётся через JSON-объекты: стили, источники данных, слои, выражения. В таких условиях типизация выполняет роль слоя формализации поверх гибкой, но слабо структурированной модели данных.

Основная сложность типизации возникает из-за сочетания трёх факторов: большой и глубокой JSON-схемы стиля, динамических источников данных (GeoJSON, vector tiles, raster tiles) и событийной модели API. TypeScript здесь используется не как формальный ограничитель, а как инструмент описания контрактов между частями системы.


Базовые 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, объект стиля или функция.


Типизация GeoJSON как фундамент данных

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 обеспечивают строгую структуру геометрии:

  • Point
  • LineString
  • Polygon
  • MultiPoint
  • MultiLineString
  • MultiPolygon

Ключевая особенность: TypeScript не просто проверяет структуру, но и сужает типы координат в зависимости от геометрии. Однако на практике в MapLibre часто используются обобщённые Geometry или GeometryCollection, что снижает строгость ради гибкости.


Типизация источников данных (Sources)

Источники данных являются одной из наиболее сложных зон типизации, поскольку каждый тип источника имеет собственную структуру.

Vector Source

import type { VectorSourceSpecification } from "maplibre-gl";

const vectorSource: VectorSourceSpecification = {
  type: "vector",
  url: "mapbox://maplibre.traffic"
};

GeoJSON Source

import type { GeoJSONSourceSpecification } from "maplibre-gl";

const geojsonSource: GeoJSONSourceSpecification = {
  type: "geojson",
  data: {
    type: "FeatureCollection",
    features: []
  }
};

Raster Source

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 точно определять допустимые поля конфигурации.


Типизация стиля и Style Specification

Центральная часть типизации 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 — строки или URL
  • transition — объект анимации

Сложность заключается в том, что layers представляет собой union-тип всех возможных типов слоёв, каждый из которых имеет собственный набор полей.


Типизация слоёв (Layers)

Слои — наиболее вариативная часть API. Каждый тип слоя имеет строго определённую структуру.

Fill Layer

import type { FillLayerSpecification } from "maplibre-gl";

const layer: FillLayerSpecification = {
  id: "water",
  type: "fill",
  source: "osm",
  "source-layer": "water",
  paint: {
    "fill-color": "#00f"
  }
};

Line Layer

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
  }
};

Symbol Layer

import type { SymbolLayerSpecification } from "maplibre-gl";

const symbol: SymbolLayerSpecification = {
  id: "labels",
  type: "symbol",
  source: "osm",
  "source-layer": "labels",
  layout: {
    "text-field": "{name}"
  }
};

Типизация слоёв основана на дискриминаторе type, что позволяет безопасно разделять конфигурации.


Типизация выражений (Expressions)

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);
});

Типизация событий включает:

  • MapMouseEvent
  • MapTouchEvent
  • MapLayerMouseEvent

Пример:

import type { MapMouseEvent } from "maplibre-gl";

function handler(e: MapMouseEvent) {
  console.log(e.point);
  console.log(e.lngLat);
}

Типизация событий обеспечивает доступ к координатам, пиксельным координатам и объектам слоя.


Расширение типов и module augmentation

В реальных проектах часто требуется расширять типы MapLibre, например добавлять кастомные свойства в properties GeoJSON или расширять события.

Расширение properties

interface MyFeatureProps {
  id: string;
  category: string;
}

import type { Feature } from "geojson";

type TypedFeature = Feature<GeoJSON.Point, MyFeatureProps>;

Module augmentation

declare module "maplibre-gl" {
  interface MapOptions {
    customFlag?: boolean;
  }
}

Такой подход позволяет интегрировать MapLibre в крупные TypeScript-архитектуры без потери типовой согласованности.


Проблемы глубокой типизации и компромиссы

Несмотря на наличие деклараций, типизация MapLibre не является полностью строгой по нескольким причинам:

  1. JSON-стиль имеет слишком большую вариативность
  2. Expression language динамичен
  3. Некоторые API принимают any для совместимости
  4. Слои и источники часто расширяются плагинами

Это приводит к тому, что TypeScript используется как “структурный фильтр”, а не как строгая формальная система.


Runtime-валидация и типизация

В реальных приложениях типизация дополняется runtime-валидацией. Это особенно важно для данных, приходящих извне.

Пример с проверкой GeoJSON:

import { FeatureCollection } from "geojson";

function isFeatureCollection(data: any): data is FeatureCollection {
  return data?.type === "FeatureCollection";
}

Также часто используются схемы:

  • Zod
  • io-ts
  • Yup

Пример с 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-проектах влияет на архитектуру следующим образом:

  • уменьшает количество runtime-ошибок в стилях
  • улучшает автодополнение в IDE
  • упрощает рефакторинг слоёв и источников
  • снижает риск несовместимости между версиями стилей

При этом чрезмерная строгость иногда замедляет разработку из-за сложности expression-типов.


Итоговая структура типовой модели MapLibre

Типовая система MapLibre строится вокруг нескольких ключевых осей:

  • геометрии (GeoJSON)
  • источников (Sources)
  • визуализации (Layers)
  • стилей (StyleSpecification)
  • событий (Events)
  • выражений (Expressions)

Эти элементы связаны через дискриминированные объединения и частичную типизацию, формируя баланс между строгой структурой и динамической гибкостью.