Source: dataSources/esriJsonVectorSource.js

let ol = require('openlayers');
let axios = require('axios');
let jquery = require('jquery');

let AbstractDataSource = require('./abstractDataSource');

let TransactionItem = require('./../editing/transactionItem');
let TransactionStack = require('./../editing/transactionStack');

let scalarTypes = require('./../constants/scalarType');

/**
 * Класс EsriJsonVectorSource предназначен для создания векторного источника данных для ESRI REST API сервисов
 * @extends AbstractDataSource
 */
class EsriJsonVectorSource extends AbstractDataSource {
  /**
   * Создает экземпляр класса EsriJsonVectorSource
   * @param params
   */
  constructor(params) {
    super(params);
    this.source = this.create(params);
    this.type = "esriJsonVectorSource";
    this.loadSourceProperties().then((data) => {
      this.dataSourceParams = data;
    });
  }

  /**
   * Выполняет загрузку и инициализацию свойств источника
   * @return {Promise.<{}>}
   */
  async loadSourceProperties() {
    let self = this;
    let url = self.params.url + "?f=pjson";
    let sourceProperties = (await axios.get(url)).data;

    const esriGeometryTypes = {
      "esriGeometryPoint": "Point",
      "esriGeometryPolyline": "MultiLineString",
      "esriGeometryPolygon": "MultiPolygon"
    };

    self.geometryType = esriGeometryTypes[sourceProperties.geometryType];
    return sourceProperties
  }

  /**
   * Метод, возвращающий тип геометрии источника векторных данных
   * @return {string} - geometryType - Point, LineString, Polygon
   */
  getGeometryType() {
    return this.geometryType;
  }

  /**
   * Метод возвращает информацию по полям источника данных - для получения информации в виде массива или объекта используйте getArray() или getObject(), соответственно
   * @return {{getArray: (function()), getObject: (function())}}
   */
  getFields() {
    let self = this;
    let dataSourceParams = self.dataSourceParams;
    let fieldLikeArray = [];
    let fields = dataSourceParams.fields;
    if (fields.length > 0) {
      fieldLikeArray = fields;
    }
    fieldLikeArray = fieldLikeArray.map((item) => {
      let result = {
        localType: item.type,
        name: item.name,
        alias: this.getFieldAlias(item),
        type: scalarTypes.convertToJsType("esri", item.type),
        isNullable: item.nullable,
        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 {*}
   */
  getSource() {
    return this.source;
  }

  getDescriptor() {
    return this;
  }

  /**
   * Обновление features
   */
  updateFeatures(params) {
    let self = this;
    let featureId = params.featureId;
    let feature = params.feature;
    self.removeFeature(feature);
    self.loadFeatureFromService(featureId);
  }

  /**
   * Получение 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.0.0',
        request: 'GetFeature',
        typeName: className,
        outputFormat: "json",
        featureId: featureId
      }
    });
    source.addFeatures(formatJson.readFeatures(response.data));
    return response;
  }

  /**
   * Получить список объектов источника
   * @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 source
   * @return {EsriJsonVectorSource} - источник данных
   */
  setSource(source) {
    this.source = source;
    return this;
  }

  /**
   * Инициализирует (и позволяет выполнить реинициализацию) источника данных
   * @param params
   * @return {ol.source.Vector}
   */
  create(params) {
    let formatJson = new ol.format.EsriJSON();
    let tiles = params.tiles || 2;
    let url = params.url;

    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;
            let miny = extent[1] + dy * j;
            let maxx = extent[0] + dx * (i + 1);
            let maxy = extent[1] + dy * (j + 1);

            axios.get(url + "/query", {
              params: {
                where: "1=1",
                geometryType: "esriGeometryEnvelope",
                spatialRel: "esriSpatialRelIntersects",
                outFields: params.outFields || '*',
                srsname: 'EPSG:3857',
                geometry: {xmin: minx, ymin: miny, xmax: maxx, ymax: maxy},
                returnGeometry: params.returnGeometry || true,
                f: "pjson"
              }
            }).then((response) => {
              source.addFeatures(formatJson.readFeatures(response.data));
            });
          }
        }
      },
      //создает стратегию для загрузки карты
      strategy: ol.loadingstrategy.bbox || params.strategy,
      projection: 'EPSG:3857' || params.projection
    });

    return source;
  }

  /**
   * Создание объекта транзаксии
   * @param params
   * @return {TransactionItem} - транзакция
   */
  createTransaction(params) {
    let self = this;
    let serverUrl = self.params.url;
    let feature = params.feature;
    let transactionType = params.transactionType;
    let esriJsonFormat = new ol.format.EsriJSON();
    let url;
    let payload = "[" + esriJsonFormat.writeFeature(feature) + "]";
    payload = JSON.parse(payload);

    switch (transactionType) {
      case 'insert':
        url = serverUrl + '/addFeatures';
        break;
      case 'update':
        url = serverUrl + '/updateFeatures';
        break;
      case 'delete':
        url = serverUrl + '/deleteFeatures';
        break;
    }

    let action = {
      url: url,
      method: 'POST',
      typ: 'POST',
      data: {
        features: JSON.stringify(payload),
        f: "pjson",
        rollbackOnFailure: true
      },
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      }
    };

    return new TransactionItem({
      action: action,
      executor: jquery.post,
      className: self.params.className,
      layerId: self.params.id,
      sourceObject: self.getDescriptor(),
      feature: feature,
      transactionType: params.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) {

  }
}

module.exports = EsriJsonVectorSource;