let ol = require('openlayers');
let axios = require('axios');
let AbstractDataSource = require('./abstractDataSource');
let TransactionItem = require('./../editing/transactionItem');
let TransactionStack = require('./../editing/transactionStack');
let geometryTypes = require('./../constants/geometryType');
let scalarTypes = require('./../constants/scalarType');
/**
* Класс GeoJsonVectorSource предназначен для создания векторного источника данных для WFS-T сервисов
* @extends AbstractDataSource
*/
class GeoJsonVectorSource extends AbstractDataSource {
/**
* Создает экземпляр класса GeoJsonVectorSource
* @param params
*/
constructor(params) {
super(params);
this.source = this.create(params);
this.type = "GeoJsonVectorSource";
this.events = {};
this.params = params;
this.loadSourceProperties()
.then((params) => {
this.dataSourceParams = params;
});
}
/**
* Метод для первичного создания/пересоздания источника данных
* @param params
* @return {ol.source.Vector} - экземлпяр источника данных
*/
create(params) {
let self = this;
self.geometryType = params.geometryType;
let formatJson = new ol.format.GeoJSON();
let tiles = params.tiles || 4;
let url = params.url;
let nameSpace = params.className.split(":")[0];
let source = new ol.source.Vector({
loader: function (extent) {
for (let i = 0; i < tiles; i++) {
for (let j = 0; j < tiles; j++) {
let dx = Math.abs(extent[2] - extent[0]) / 2;
let dy = Math.abs(extent[3] - extent[1]) / 2;
let minx = extent[0] + dx * i - dx / 8;
let miny = extent[1] + dy * j - dy / 8;
let maxx = extent[0] + dx * (i + 1) + dx / 8;
let maxy = extent[1] + dy * (j + 1) + dy / 8;
axios.get(url + nameSpace + "/ows", {
params: {
service: 'WFS',
version: '1.1.0',
request: 'GetFeature',
typeName: params.className,
srsname: 'EPSG:3857',
bbox: [minx, miny, maxx, maxy].join(',') + ',EPSG:3857',
outputFormat: "json"
}
}).then((response) => {
source.addFeatures(formatJson.readFeatures(response.data));
});
}
}
},
//создает стратегию для загрузки карты
strategy: ol.loadingstrategy.bbox || params.strategy,
projection: 'EPSG:3857' || params.projection
});
return source;
}
/**
* Метод выполняет загрузку основных свойств слоя через веб-запрос к сервису
* @return {Promise.<void>}
*/
async loadSourceProperties() {
let self = this;
let serverUrl = self.params.url;
let namespace = self.params.className.split(":")[0];
let featureType = self.params.className.split(":")[1];
// todo переделать на параметры, вынести в отельный класс http вызовы
let url = serverUrl + "/" + namespace + "/ows?service=WFS&version=1.1.0&request=DescribeFeatureType&outputFormat=application/json&typeName=" + featureType;
let data = (await axios.get(url)).data;
return data;
}
/**
* Метод, возвращающий тип геометрии источника векторных данных
* @return {string} - geometryType - Point, LineString, Polygon
*/
getGeometryType() {
let self = this;
let dataSourceParams = self.dataSourceParams;
let geometryType = self.geometryType || undefined;
try {
let featureTypes = dataSourceParams.featureTypes;
if (featureTypes.length > 0) {
let featureType = featureTypes[0];
let properties = featureType.properties;
let geomTypes = Object.keys(geometryTypes);
let geomField = properties.find((prop) => {
let propType = prop.localType;
let isExist = geomTypes.some((item) => {
return item === propType;
});
return isExist;
});
if (geomField) {
geometryType = geomField.localType;
self.geometryType = geometryType;
}
}
}
catch (e) {
}
return geometryType;
}
/**
* Метод возвращает информацию по полям источника данных - для получения информации в виде массива или объекта используйте getArray() или getObject(), соответственно
* @return {{getArray: (function()), getObject: (function())}}
*/
getFields() {
let self = this;
let dataSourceParams = self.dataSourceParams;
let fieldLikeArray = [];
let featureTypes = dataSourceParams.featureTypes;
if (featureTypes.length > 0) {
fieldLikeArray = featureTypes[0].properties;
}
fieldLikeArray = fieldLikeArray.map((item) => {
let result = {
localType: item.localType,
name: item.name,
alias: this.getFieldAlias(item),
type: scalarTypes.convertToJsType("geoserver", item.localType),
isNullable: item.nillable,
isRequired: item.isRequired || false,
};
return result;
});
let fieldsLikeObject = {};
for (let i = 0; i < fieldLikeArray.length; i++) {
fieldsLikeObject[fieldLikeArray[i].name] = fieldLikeArray[i];
}
let fieldsObject = {
getArray: () => {
return fieldLikeArray;
},
getObject: () => {
return fieldsLikeObject;
}
};
return fieldsObject;
}
/**
* Метод возвращает ссылку на источника данных
* @return {ol.source.Vector}
*/
getSource() {
return this.source;
}
/**
* Получение feature из сервиса
* @param featureId {string} - featureId
*/
async loadFeatureFromService(featureId) {
let self = this;
let url = self.params.url;
let source = self.getSource();
let nameSpace = self.params.className.split(":")[0];
let className = self.params.className;
let formatJson = new ol.format.GeoJSON();
let response = await axios.get(url + nameSpace + "/ows", {
params: {
service: 'WFS',
version: '1.1.0',
request: 'GetFeature',
typeName: className,
outputFormat: "json",
featureId: featureId
}
});
source.addFeatures(formatJson.readFeatures(response.data));
return response;
}
/**
* Обновление features
* @param {object} params - опции обновления
* @param {string} featureId - id обновляемого объекта
* @param {ol.Feature} feature - замещаемый объект
*/
updateFeatures(params) {
let self = this;
let featureId = params.featureId;
let feature = params.feature;
self.removeFeature(feature);
self.loadFeatureFromService(featureId);
}
/**
* Получить список объектов источника
* @param params
* @return {*|Array.<ol.Feature|ol.render.Feature>|ol.Collection.<ol.Feature>|Array.<ol.Feature>}
*/
getFeatures(params) {
let self = this;
let source = self.source;
return source.getFeatures();
}
/**
* Добавить объекты в источник
* @param data
*/
addFeatures(data) {
let self = this;
let source = self.source;
source.addFeatures(data);
}
/**
* Удаление feature из источника
* @param feature
*/
removeFeature(feature) {
let self = this;
let source = self.source;
source.removeFeature(feature);
}
/**
* Удаление feature из источника через указания свойства и значения
* @param {object} params - параметры поиска
* @param params.key {string} - имя свойства
* @param params.value {string} - искомое значение
*/
removeFeatureByProperty(params) {
let self = this;
let key = params.key;
let value = params.value;
if (key && value) {
let features = self.getFeatures();
let feature = features.find((item) => {
let testValue = feature.get(key);
if (testValue === value) return true;
});
if (feature) {
self.removeFeature(feature);
}
}
}
/**
* Метод применяет источник данных
* @param {ol.source.Vector} - source (источник)
*/
setSource(source) {
this.source = source;
}
/**
* Метод возвращает ссылку на всеь объект
* @return {GeoJsonVectorSource} - объект типа GeoJsonVectorSource
*/
getDescriptor() {
return this;
}
/**
* Создание объекта транзаксии
* @param params
* @return {TransactionItem} - транзакция
*/
createTransaction(params) {
let self = this;
let serverUrl = self.params.url;
let featureType = self.params.className;
let featureNs = serverUrl + featureType.split(":")[0];
let node;
let feature = params.feature;
//TODO определять через конструктор источника данных
feature.set("geom", feature.getGeometry());
feature.setGeometryName("geom");
//TODO need do research this unspecified behavior for seek a reason
try {
if (params.transactionType === 'update') {
feature.unset("bbox");
feature.unset("geometry");
}
}
catch (e) {
}
let transactionType = params.transactionType;
let xs = new XMLSerializer();
let formatWFS = new ol.format.WFS();
let formatGML = new ol.format.GML({
featureNS: self.params.className.split(":")[0],
featureType: self.params.className.split(":")[1]
});
switch (transactionType) {
case 'insert':
node = formatWFS.writeTransaction([feature], null, null, formatGML);
break;
case 'update':
node = formatWFS.writeTransaction(null, [feature], null, formatGML);
break;
case 'delete':
node = formatWFS.writeTransaction(null, null, [feature], formatGML);
break;
}
let payload = xs.serializeToString(node);
let url = featureNs + "/ows";
let action = {
url: url,
method: 'POST',
headers: {
"Content-Type": "text/xml",
Accept: "application/xml, text/xml, */*; q=0.01"
},
dataType: 'xml',
version: '1.1.0',
processData: false,
contentType: 'text/xml',
data: payload
};
return new TransactionItem({
action: action,
className: self.params.className,
layerId: self.params.id,
sourceObject: self.getDescriptor(),
feature: feature,
transactionType: transactionType
})
}
/**
* Непосредственное выполнение транзакции
* @param params
* @return {Promise.<*>}
*/
async transact(params) {
let self = this;
let transaction = self.createTransaction(params);
let executor = transaction.executor;
let action = transaction.action;
return executor(action);
}
/**
* Перемещение транзакции в стек операций редактирования
* @param params
* @return {{transaction: TransactionItem}}
*/
toStack(params) {
let self = this;
let transaction = self.createTransaction(params);
TransactionStack.push(transaction);
return {
transaction: transaction
}
}
fromStack(params) {
}
/**
* Подписка на события
* @param eventName
* @param listener
*/
on(eventName, listener) {
let self = this;
let eventStore = self.events;
eventStore[eventName] = listener;
}
/**
* Выключение подписки на событие
* @param eventName
*/
off(eventName) {
let self = this;
let eventStore = self.events;
if (eventStore[eventName]) delete eventStore[eventName];
}
/**
* Инициация события с контекстом
* @param eventName
* @param context
*/
emit(eventName, context) {
let self = this;
let eventStore = self.events;
let listener = eventStore[eventName];
if (listener && typeof listener === "function") {
listener(context);
}
}
}
module.exports = GeoJsonVectorSource;