Редагування просторових даних в Leaflet

Незважаючи на розвиток картографічних веб-фреймворків, редагування векторних географічних даних все ще здебільшого відбувається в настільних застосуваннях. На дворі 2015-й рік, пора вже переходити до редагування в браузерах.

Для відображення веб-карт існує кілька відкритих бібліотек, нарпімер, OpenLayers і Leaflet. Досить давно наш вибір припав на Leaflet і ми продовжуємо його активно використовувати при реалізації проектів. Для редагування геоданих хотілося б використовувати його ж і, при цьому, мати можливість інтегруватися з існуючими сховищами просторових даних.

Для досягнення останньої мети, як правило, використовуються ГІС-серверу (geoserver, mapserver), які вміють публікувати велика кількість різноманітних форматів даних стандартам OGC. Так, WMS протокол чудово справляється з функцією візуалізації готової карти, але не передбачає функції редагування, для якої резонно використовувати WFS-протокол з можливістю зміни даних. Запити до WMS повертають вже відмальовані тайли — картинки, а до WFS — сиру інформацію, «вихідний код» за цими тайлами. Leaflet підтримує модулі розширення, відповідно, можна пошукати готовий компонент, або написати свій. Т. к. пошук готових модулів для Leaflet задовольняють результатів не дав, ми приступили до власної реалізації.

За статистикою запитів на leaflet.uservoice.com зрозуміло, що даний модуль цікавий не тільки нам.

Почнемо з опису WFS-T і для чого він використовується
Стандарт OGC Web Feature Service дозволяє запитувати і редагувати (у випадку з припискою “-T" — transaction) просторові дані з допомогою запитів до сервера. Функції CRUD стандартом розділені на запити GetFeature для читання і Transaction для інших.

Для взаємодії клієнта і сервера можуть використовуватись два способи: перший використовує XML і POST запити, другий — пари ключ\значення і GET запити.

Отримати дані можна за допомогою запитів GET виду:
%WFSServerURL%?service=WFS&version=1.0.0&request=GetFeature&typeName=osm_perm_region:perm_water_polygon&maxFeatures=50&outputFormat=application/json,
service=WFS тип сервісу, завжди однаковий
version=1.0.0 версія стандарту WFS. На даний момент існує 3 версії: 1.0.0, 1.1.0, 2.0.0. Ми будемо використовувати версію 1.1.0, т. к. 2.0.0 реалізована не всіма виробниками серверної частини
request=GetFeature тип запиту
typeName=osm_perm_region:perm_water_polygon тип опублікованих WFS-сервером даних
maxFeature=50 кількість об'єктів, яке буде повернуто сервером
outputFormat=application/json формат даних, які будуть повернуті сервером. Стандартом регламентується тільки один формат даних — GML, але деякі реалізації можуть використовувати і відмінні від GML, наприклад, geoserver вміє віддавати дані в geoJson
Для створення\зміни\видалення даних у деяких випадках також можна скористатися парами ключ-значення в GET запиті, але виконання POST запит на %WFSServerURL% з даними у форматі XML дає більше можливостей.
Приклади зміни об'єктів:

Створення
<wfs:Transaction service="WFS" version="1.0.0"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:topp="http://www.openplans.org/topp"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://www.openplans.org/topp http://localhost:8080/geoserver/wfs/DescribeFeatureType?typename=topp:tasmania_roads">
<wfs:Insert>
<topp:tasmania_roads>
<topp:the_geom>
<gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:lineStringMember>
<gml:LineString>
<gml:coordinates decimal="." cs="," ts=" ">
494475.71056415,5433016.8189323 494982.70115662,5435041.95096618
</gml:coordinates>
</gml:LineString>
</gml:lineStringMember>
</gml:MultiLineString>
</topp:the_geom>
<topp:TYPE>alley</topp:TYPE>
</topp:tasmania_roads>
</wfs:Insert>
</wfs:Transaction>


Оновлення
<wfs:Transaction service="WFS" version="1.0.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd">
<wfs:Update typeName="topp:tasmania_roads">
<wfs:Property>
<wfs:Name>the_geom</wfs:Name>
<wfs:Value>
<gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:lineStringMember>
<gml:LineString>
<gml:coordinates>500000,5450000,0 540000,5450000,0</gml:coordinates>
</gml:LineString>
</gml:lineStringMember>
</gml:MultiLineString>
</wfs:Value>
</wfs:Property>
<ogc:Filter>
<ogc:FeatureId fid="tasmania_roads.1"/>
</ogc:Filter>
</wfs:Update>
</wfs:Transaction>


Видалити
<wfs:Transaction service="WFS" version="1.0.0"
xmlns:cdf="http://www.opengis.net/cite/data"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:topp="http://www.openplans.org/topp">
<wfs:Delete typeName="topp:tasmania_roads">
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>topp:TYPE</ogc:PropertyName>
<ogc:Literal>alley</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
</wfs:Delete>
</wfs:Transaction>


Для опису просторових даних використовується GML. У цьому форматі за замовчуванням сервер віддає дані (при незаданном прапор outputFormat) і тільки в ньому приймаються запити при зміні даних. Наприклад, точка [0, 0] GML може бути представлена наступним чином:

<gml:Point srsName="http://www.opengis.net/def/crs/EPSG/0/4326">
<gml:pos srsDimension="2">0.0 0.0</gml:pos>
</gml:Point>

Для обмеження запитів щодо видалення та зміни даних використовуються фільтри — ще один із стандартів OGC. Спочатку буде досить — GmlObjectId, він використовується для оновлення\видалення об'єктів. Надалі будуть потрібні і інші фільтри.

Приклад для ID=1:

<ogc:Filter>
<ogc:GmlFeatureId gml:id=1/>
</ogc:Filter>

Створення плагіна Leaflet
Як було сказано вище, у Leaflet добре продумана модульна інфраструктура, тому треба вміти писати ці самі модулі. Вступна щодо створення плагіна є на сайті Leaflet, і приклад реалізації ILayer. Також є кілька статей на хабре.

Нам потрібно створити свій шар, який при завантаженні буде отримувати дані від сервісу і перетворювати їх. Для читання WFS було знайдено кілька плагінів, всі вони успадковувалися від L. GeoJSON шару і вичитували дані відразу в потрібному форматі. Але стандарт не зобов'язує виробників серверної частини надавати дані в geoJson (я бачив таку можливість тільки для geoserver'a), обов'язковим є формат GML. Підглянувши, як читання зроблено в OpenLayers, ми взяли ідею звідти: для читання використовувати окремий клас, який вміє розуміти потрібний формат. Як і L. GeoJSON, свою реалізацію ми успадкували від L. FeatureGroup. Було реалізовано 2 формату читання GML і GeoJSON

Отримання даних
виконується AJAX запитом і віддається на обробку класу читання, тут ми просто передаємо трансформацію geoJson у маркери\полігони\полілінії в надра самого Leaflet:

var layers = [];
var geoJson = JSON.parse(response.rawData);
for (var i = 0; i < geoJson.features.length; i++) {
var layer = L. GeoJSON.geometryToLayer(geoJson.features[i], options.pointToLayer || null, options.coordsToLatLng, null);
layer.feature = geoJson.features[i];
layers.push(layer);
}
return layers;

Або парсим GML — на виході ті ж маркери\полігони\полілінії:

var layers = [];
var xmlDoc = L. XmlUtil.parseXml(rawData);
var featureCollection = xmlDoc.documentElement;
var featureMemberNodes = featureCollection.getElementsByTagNameNS(L. XmlUtil.namespaces.gml, 'featureMember');
for (var i = 0; i < featureMemberNodes.length; i++) {
var feature = featureMemberNodes[i].firstChild;
layers.push(this.processFeature(feature));
}

var featureMembersNode = featureCollection.getElementsByTagNameNS(L. XmlUtil.namespaces.gml, 'featureMembers');
if (featureMembersNode.length > 0) {
var features = featureMembersNode[0].childNodes;
for (var j = 0; j < features.length; j++) {
var node = features[j];
if (node.nodeType === document.ELEMENT_NODE) {
layers.push(this.processFeature(node));
}
}
}

return layers;

" Редагування об'єктів
Написані фукнції, які, взаємодіючи з плагінами візуального редагування об'єктів Leaflet (leaflet.draw, Leaflet.Editable), запам'ятовують зроблені зміни. Після того як редагування закінчено треба викликати метод save(), який сформує GML опис змін — елемент “wfs:Transaction", і, на кожен з змінених об'єктів, відповідна дія (Action): wfs:Insert, wfs:Update, wfs:Delete. Після цього виконується AJAX запит.

Приклад підписки на події плагіна Leaflet.Editable:

map.on('editable:created', function (e) {
wfst.addLayer(e.layer);
});

map.on('editable:editing', function (e) {
wfst.editLayer(e.layer);
});

map.on('editable:delete', function (e) {
wfst.removeLayer(e.layer);
});

Для кожного примітиву Leaflet (Marker, Polyline, Polygon та ін) була написана функція його перекладу опис геометрії GML, наприклад, для маркера вона виглядає так:

L. Marker.include({
toGml: function (crs) {
var node = L. XmlUtil.createElementNS('gml:Point', {srsName: crs.code});
node.appendChild(L. GMLUtil.posNode(L. Util.project(crs, this.getLatLng ())));
return node;
}
});

Приклади використання
Тільки читання
var map = L. map('map').setView([0, 0], 2);

var boundaries = new L. WFS({
url: 'http://demo.opengeo.org/geoserver/ows',
typeNS: 'topp',
typeName: 'tasmania_state_boundaries',
crs: L. CRS.EPSG4326,
style: {
color: blue',
weight: 2
}
}).addTo(map)
.on('load', function () {
map.fitBounds(boundaries);
})

[ посилання ]

Правка
var wfst = new L. WFS.Transaction({
url: 'http://myserver/geoserver/ows',
typeNS: 'myns',
typeName: 'POIPOINT',
style: {
color: blue',
weight: 2
}
}).addTo(map).once('load', function () {
map.fitBounds(wfst);
wfst.enableEdit();
});

map.on('editable:created', function (e) {
wfst.addLayer(e.layer);
});

map.on('editable:editing', function (e) {
wfst.editLayer(e.layer);
});

L. easyButton('fa-save', function () {
wfst.save();
}, 'Save changes');

посилання

У планах розвитку плагіна
— Міграція на Leaflet 0.8 (змінилися нутрощі Multi-класів, так і полілінії з полігонами обзавелися ring's);
— Можливість використання різних версій WFS;
— Підтримка інших пунктів стандарту OGC Filter Encoding.

Исходники і розробка
Проект живе на GitHub. Для автоматизації використовується Grunt. Для тестування використовується зв'язка karma+mocha+chai+sinon. Бажаючі взяти участь — ласкаво просимо.

Посилання
  1. Стандарти OGC: WMS, WFS, GML, Filter Encoding
  2. Опис деяких стандартів російською — live.osgeo.org
  3. leaflet.wfs-t — нашедшийся wfst плагін для leaflet, з мінусів — занедбаний, alpha версія, складання запитів(xml) конкатенацией, тільки маркери і полігони, тільки geojson.
  4. geoserver.org — один з відкритих гіс-серверів з підтримкою WFST, на ньому опубліковані демонстраційні дані.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.