API для геоданных

Работа с геоданными является центральной частью Leaflet. Библиотека предоставляет развитый набор инструментов для загрузки, отображения, стилизации и обработки пространственных данных различных форматов. Наиболее важное место занимает поддержка стандарта GeoJSON, который фактически стал универсальным способом обмена географической информацией между веб-картами и серверными приложениями.

Leaflet позволяет работать как с простыми объектами — отдельными точками или линиями, так и со сложными наборами пространственных данных, содержащими тысячи объектов различных типов.


Формат GeoJSON

GeoJSON представляет собой текстовый формат хранения географических данных на основе JSON.

Каждый объект содержит описание геометрии и дополнительных атрибутов.

Пример точки:

{
  "type": "Feature",
  "properties": {
    "name": "Москва"
  },
  "geometry": {
    "type": "Point",
    "coordinates": [37.6176, 55.7558]
  }
}

Структура GeoJSON включает несколько основных сущностей:

Тип Назначение
Point Точка
MultiPoint Набор точек
LineString Линия
MultiLineString Набор линий
Polygon Полигон
MultiPolygon Набор полигонов
Feature Объект с атрибутами
FeatureCollection Коллекция объектов

Leaflet умеет автоматически преобразовывать большинство типов геометрий в соответствующие картографические объекты.


Создание слоя GeoJSON

Для отображения GeoJSON используется класс L.geoJSON().

Простейший пример:

const geojsonLayer = L.geoJSON(data);

geojsonLayer.addTo(map);

Где data содержит объект GeoJSON.

Полный пример:

const city = {
  type: "Feature",
  properties: {
    name: "Москва"
  },
  geometry: {
    type: "Point",
    coordinates: [37.6176, 55.7558]
  }
};

L.geoJSON(city).addTo(map);

После добавления на карту точка автоматически отображается в виде стандартного маркера.


Работа с FeatureCollection

На практике чаще используются коллекции объектов.

Пример:

const cities = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: {
        name: "Москва"
      },
      geometry: {
        type: "Point",
        coordinates: [37.6176, 55.7558]
      }
    },
    {
      type: "Feature",
      properties: {
        name: "Санкт-Петербург"
      },
      geometry: {
        type: "Point",
        coordinates: [30.3351, 59.9343]
      }
    }
  ]
};

L.geoJSON(cities).addTo(map);

Leaflet автоматически создаст отдельный слой для каждого объекта коллекции.


Добавление данных после создания слоя

GeoJSON можно загружать поэтапно.

Создание пустого слоя:

const geojsonLayer = L.geoJSON().addTo(map);

Добавление объектов:

geojsonLayer.addData(data);

Добавление новой коллекции:

geojsonLayer.addData(newFeatures);

Такой подход часто применяется при динамической подгрузке информации с сервера.


Стилизация геометрии

Для линий и полигонов используется параметр style.

L.geoJSON(data, {
  style: {
    color: "#ff0000",
    weight: 3,
    opacity: 0.8
  }
}).addTo(map);

Поддерживаются свойства:

Свойство Описание
color Цвет границы
weight Толщина линии
opacity Прозрачность
fillColor Цвет заливки
fillOpacity Прозрачность заливки
dashArray Пунктир

Пример:

L.geoJSON(data, {
  style: {
    color: "blue",
    fillColor: "lightblue",
    fillOpacity: 0.5
  }
}).addTo(map);

Динамическая стилизация

Стиль может определяться функцией.

L.geoJSON(data, {
  style(feature) {
    return {
      color: feature.properties.color
    };
  }
}).addTo(map);

GeoJSON:

{
  "properties": {
    "color": "green"
  }
}

В результате каждый объект получает собственное оформление.


Преобразование точек в маркеры

По умолчанию точки отображаются как стандартные маркеры.

Для полного контроля используется параметр pointToLayer.

L.geoJSON(data, {
  pointToLayer(feature, latlng) {
    return L.circleMarker(latlng);
  }
}).addTo(map);

Создание кругового маркера:

L.geoJSON(data, {
  pointToLayer(feature, latlng) {
    return L.circleMarker(latlng, {
      radius: 8,
      color: "red",
      fillColor: "orange",
      fillOpacity: 0.7
    });
  }
}).addTo(map);

Использование данных объекта

Внутри pointToLayer доступны свойства GeoJSON.

L.geoJSON(data, {
  pointToLayer(feature, latlng) {
    return L.circleMarker(latlng, {
      radius: feature.properties.population / 1000000
    });
  }
}).addTo(map);

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


Фильтрация объектов

Параметр filter позволяет исключать объекты до их отображения.

L.geoJSON(data, {
  filter(feature) {
    return feature.properties.active;
  }
}).addTo(map);

Отобразятся только объекты, у которых:

active === true

Более сложный пример:

L.geoJSON(data, {
  filter(feature) {
    return feature.properties.population > 1000000;
  }
}).addTo(map);

Обработка каждого объекта

Параметр onEachFeature вызывается для каждого элемента коллекции.

L.geoJSON(data, {
  onEachFeature(feature, layer) {
    console.log(feature.properties.name);
  }
}).addTo(map);

Аргументы:

Аргумент Описание
feature Объект GeoJSON
layer Соответствующий слой Leaflet

Через этот обработчик обычно подключаются всплывающие окна и события.


Всплывающие окна для GeoJSON

Пример создания popup:

L.geoJSON(data, {
  onEachFeature(feature, layer) {
    layer.bindPopup(feature.properties.name);
  }
}).addTo(map);

Более подробный вариант:

L.geoJSON(data, {
  onEachFeature(feature, layer) {
    const html = `
      <h3>${feature.properties.name}</h3>
      <p>Население: ${feature.properties.population}</p>
    `;

    layer.bindPopup(html);
  }
}).addTo(map);

Подключение событий

Каждый объект GeoJSON является полноценным слоем Leaflet.

Поэтому доступны все стандартные события.

L.geoJSON(data, {
  onEachFeature(feature, layer) {
    layer.on("click", () => {
      console.log(feature.properties.name);
    });
  }
}).addTo(map);

Обработка наведения курсора:

layer.on("mouseover", () => {
  console.log("Наведение");
});

Изменение стиля при наведении

Частый сценарий работы с полигонами.

function highlight(e) {
  e.target.setStyle({
    weight: 5,
    color: "#ffff00"
  });
}

Подключение:

L.geoJSON(data, {
  onEachFeature(feature, layer) {
    layer.on({
      mouseover: highlight
    });
  }
}).addTo(map);

Восстановление исходного стиля:

function reset(e) {
  geojsonLayer.resetStyle(e.target);
}

Получение границ слоя

Каждый слой GeoJSON умеет вычислять свои границы.

const bounds = geojsonLayer.getBounds();

Автоматическое масштабирование карты:

map.fitBounds(
  geojsonLayer.getBounds()
);

Метод особенно полезен при загрузке данных неизвестного размера.


Загрузка GeoJSON с сервера

Получение данных через Fetch API:

fetch("/data/cities.geojson")
  .then(response => response.json())
  .then(data => {
    L.geoJSON(data).addTo(map);
  });

Асинхронная версия:

async function loadData() {
  const response = await fetch(
    "/data/cities.geojson"
  );

  const data = await response.json();

  L.geoJSON(data).addTo(map);
}

loadData();

Работа с удалёнными API

Сервер может возвращать GeoJSON напрямую.

fetch("/api/regions")
  .then(response => response.json())
  .then(data => {
    geojsonLayer.addData(data);
  });

Такой подход широко используется в:

  • GIS-системах;
  • кадастровых сервисах;
  • транспортных приложениях;
  • системах мониторинга;
  • картографических порталах.

Преобразование Leaflet-объектов в GeoJSON

Большинство объектов Leaflet поддерживает экспорт.

Маркер:

const marker = L.marker([
  55.7558,
  37.6176
]);

const geojson = marker.toGeoJSON();

Линия:

const line = L.polyline([
  [55.7, 37.5],
  [55.8, 37.8]
]);

const geojson = line.toGeoJSON();

Полигон:

const polygon = L.polygon([
  [55.7, 37.5],
  [55.8, 37.8],
  [55.6, 38.0]
]);

const geojson = polygon.toGeoJSON();

Экспорт FeatureCollection

Группа объектов также может быть преобразована в GeoJSON.

const group = L.featureGroup([
  marker1,
  marker2,
  polygon
]);

const geojson = group.toGeoJSON();

Результатом станет объект типа:

{
  "type": "FeatureCollection"
}

Это позволяет сохранять данные пользователей на сервере или экспортировать их в файлы.


Работа с координатами

Важная особенность GeoJSON заключается в порядке координат.

GeoJSON использует:

[долгота, широта]

Например:

[37.6176, 55.7558]

Leaflet использует:

[широта, долгота]

Например:

[55.7558, 37.6176]

При создании GeoJSON Leaflet автоматически выполняет преобразование, однако при ручной обработке данных эту разницу необходимо учитывать.


Производительность при больших объёмах данных

При работе с тысячами объектов рекомендуется:

  • использовать фильтрацию на сервере;
  • загружать данные частями;
  • применять кластеризацию маркеров;
  • избегать тяжёлых обработчиков событий;
  • использовать Canvas-рендеринг;
  • упрощать геометрию полигонов.

Включение Canvas:

const map = L.map("map", {
  preferCanvas: true
});

Для больших наборов точек это может существенно повысить скорость отображения.


FeatureGroup и GeoJSON

GeoJSON-слой наследует возможности FeatureGroup.

Поэтому доступны методы:

geojsonLayer.eachLayer(...)
geojsonLayer.clearLayers()
geojsonLayer.getLayers()
geojsonLayer.removeLayer(...)
geojsonLayer.addLayer(...)

Очистка слоя:

geojsonLayer.clearLayers();

Получение всех объектов:

const layers =
  geojsonLayer.getLayers();

Перебор элементов:

geojsonLayer.eachLayer(layer => {
  console.log(layer);
});

Комбинирование нескольких источников данных

На одной карте могут одновременно отображаться разные наборы данных.

const roadsLayer =
  L.geoJSON(roads);

const riversLayer =
  L.geoJSON(rivers);

const citiesLayer =
  L.geoJSON(cities);

Добавление на карту:

roadsLayer.addTo(map);
riversLayer.addTo(map);
citiesLayer.addTo(map);

Управление отображением через переключатель слоёв:

L.control.layers(null, {
  "Дороги": roadsLayer,
  "Реки": riversLayer,
  "Города": citiesLayer
}).addTo(map);

Такой подход лежит в основе большинства профессиональных веб-ГИС, где карта формируется из множества независимых наборов геоданных, объединённых в единую интерактивную систему визуализации и анализа пространственной информации.