Работа с XML

Работа с XML в веб-картографии чаще всего связана с обменом геоданными в форматах KML, GPX и GeoRSS, а также с обработкой произвольных XML-структур, содержащих координаты объектов. Несмотря на доминирование GeoJSON в экосистеме современных картографических библиотек, XML остаётся распространённым форматом в GIS-системах и внешних API.

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


Базовое получение XML-данных

Получение XML-документа в JavaScript обычно выполняется через fetch или XMLHttpRequest. Современный подход опирается на fetch с последующим преобразованием текста в XML-структуру.

fetch('data/points.xml')
  .then(response => response.text())
  .then(str => {
    const parser = new DOMParser();
    const xml = parser.parseFromString(str, 'application/xml');
    console.log(xml);
  });

В результате получается DOM-дерево, с которым можно работать через стандартные методы:

  • getElementsByTagName
  • querySelector
  • getAttribute

Извлечение координат из XML

Типичный XML-файл с геоданными содержит элементы, описывающие точки, линии или полигоны. Пример структуры:

<markers>
  <marker>
    <name>Точка A</name>
    <lat>51.1694</lat>
    <lng>71.4491</lng>
  </marker>
  <marker>
    <name>Точка B</name>
    <lat>51.1280</lat>
    <lng>71.4300</lng>
  </marker>
</markers>

Обработка таких данных:

const markers = xml.getElementsByTagName('marker');

for (let i = 0; i < markers.length; i++) {
  const el = markers[i];

  const name = el.getElementsByTagName('name')[0].textContent;
  const lat = parseFloat(el.getElementsByTagName('lat')[0].textContent);
  const lng = parseFloat(el.getElementsByTagName('lng')[0].textContent);

  L.marker([lat, lng])
    .addTo(map)
    .bindPopup(name);
}

Ключевым моментом является преобразование строковых значений координат в числовой формат.


Преобразование XML в GeoJSON

Наиболее устойчивый подход к интеграции XML с Leaflet заключается в преобразовании данных в GeoJSON и использовании L.geoJSON.

function xmlToGeoJSON(xml) {
  const features = [];
  const markers = xml.getElementsByTagName('marker');

  for (let i = 0; i < markers.length; i++) {
    const el = markers[i];

    const name = el.getElementsByTagName('name')[0].textContent;
    const lat = parseFloat(el.getElementsByTagName('lat')[0].textContent);
    const lng = parseFloat(el.getElementsByTagName('lng')[0].textContent);

    features.push({
      type: 'Feature',
      properties: {
        name: name
      },
      geometry: {
        type: 'Point',
        coordinates: [lng, lat]
      }
    });
  }

  return {
    type: 'FeatureCollection',
    features: features
  };
}

Использование результата:

fetch('data/points.xml')
  .then(r => r.text())
  .then(str => {
    const xml = new DOMParser().parseFromString(str, 'application/xml');
    const geojson = xmlToGeoJSON(xml);

    L.geoJSON(geojson, {
      onEachFeature: function (feature, layer) {
        if (feature.properties && feature.properties.name) {
          layer.bindPopup(feature.properties.name);
        }
      }
    }).addTo(map);
  });

Работа с KML через XML

KML представляет собой XML-диалект, широко используемый в GIS. Его структура сложнее, так как включает геометрию, стили и вложенные элементы.

Пример извлечения точек из KML:

function parseKML(xml) {
  const placemarks = xml.getElementsByTagName('Placemark');
  const features = [];

  for (let i = 0; i < placemarks.length; i++) {
    const pm = placemarks[i];

    const nameEl = pm.getElementsByTagName('name')[0];
    const coordEl = pm.getElementsByTagName('coordinates')[0];

    if (!coordEl) continue;

    const coordsText = coordEl.textContent.trim();
    const [lng, lat] = coordsText.split(',').map(Number);

    features.push({
      type: 'Feature',
      properties: {
        name: nameEl ? nameEl.textContent : ''
      },
      geometry: {
        type: 'Point',
        coordinates: [lng, lat]
      }
    });
  }

  return {
    type: 'FeatureCollection',
    features
  };
}

KML может содержать линии и полигоны, где координаты представлены списками значений:

longitude,latitude,altitude longitude,latitude,altitude

Для таких случаев требуется разбор строк по пробелам и запятым.


Использование GeoRSS (XML-ленты)

GeoRSS представляет собой RSS/Atom с географическими расширениями. В XML присутствуют теги вида georss:point.

<item>
  <title>Объект</title>
  <georss:point>51.1694 71.4491</georss:point>
</item>

Парсинг:

const items = xml.getElementsByTagName('item');

for (let i = 0; i < items.length; i++) {
  const point = items[i].getElementsByTagName('georss:point')[0];

  if (!point) continue;

  const coords = point.textContent.split(' ');
  const lat = parseFloat(coords[0]);
  const lng = parseFloat(coords[1]);

  L.marker([lat, lng]).addTo(map);
}

Особенностью GeoRSS является использование XML namespace, что иногда требует работы через getElementsByTagNameNS.


Обработка XML с пространствами имён

Некоторые XML-документы используют namespace, например:

<georss:point xmlns:georss="http://www.georss.org/georss">
  51.1694 71.4491
</georss:point>

Корректный доступ:

const ns = 'http://www.georss.org/georss';
const points = xml.getElementsByTagNameNS(ns, 'point');

Игнорирование namespace часто приводит к пустым результатам выборки.


Использование XPath для сложных XML-структур

XPath позволяет упростить выборку элементов в глубоко вложенных XML-документах.

function select(xpath, xml) {
  const result = xml.evaluate(
    xpath,
    xml,
    null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
    null
  );

  const nodes = [];
  for (let i = 0; i < result.snapshotLength; i++) {
    nodes.push(result.snapshotItem(i));
  }

  return nodes;
}

Пример:

const markers = select('//marker', xml);

XPath особенно полезен при работе с KML и сложными RSS-структурами.


Интеграция с Leaflet слоями

После преобразования XML в структуру координат данные передаются в стандартные слои Leaflet.

Для точек:

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

Для динамически создаваемых слоёв:

const group = L.layerGroup();

group.addLayer(L.marker([lat, lng]));
group.addTo(map);

При больших XML-файлах предпочтительно использовать L.geoJSON, так как он оптимизирован для массового рендеринга.


Асинхронная загрузка и обработка ошибок

Работа с XML требует контроля ошибок парсинга и сетевых запросов.

fetch('data.xml')
  .then(res => {
    if (!res.ok) {
      throw new Error('Ошибка загрузки');
    }
    return res.text();
  })
  .then(str => {
    const xml = new DOMParser().parseFromString(str, 'application/xml');

    const errorNode = xml.querySelector('parsererror');
    if (errorNode) {
      throw new Error('Некорректный XML');
    }

    return xml;
  })
  .catch(err => {
    console.error(err);
  });

Проблемы совместимости и ограничения

При работе с XML в веб-картографии возникают типовые ограничения:

  • необходимость строгого соответствия формату XML;
  • чувствительность к namespace;
  • различия в KML-реализациях разных систем;
  • большие размеры файлов, влияющие на производительность;
  • необходимость предварительного преобразования в GeoJSON для оптимальной работы Leaflet.

Оптимизация обработки больших XML-файлов

При обработке больших наборов данных важны следующие подходы:

  • построчный разбор через TextDecoder и потоковую обработку;
  • предварительная фильтрация элементов;
  • кэширование результатов парсинга;
  • использование Web Worker для парсинга вне основного потока.

Пример выноса парсинга:

const worker = new Worker('parser.js');

worker.postMessage(xmlString);

worker.onmess age = function(e) {
  const geojson = e.data;
  L.geoJSON(geojson).addTo(map);
};

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

Для упрощения работы с XML и форматами на его основе применяются специализированные инструменты:

  • преобразование KML → GeoJSON;
  • поддержка GPX;
  • универсальные парсеры геоданных.

Типовой подход — конвертация перед передачей в Leaflet, что снижает сложность кода и повышает предсказуемость отображения слоёв.