text:
import Ember from 'ember';
import distance from 'npm:@turf/distance';
import helpers from 'npm:@turf/helpers';
import booleanContains from 'npm:@turf/boolean-contains';
import area from 'npm:@turf/area';
import intersect from 'npm:@turf/intersect';
import { getLeafletCrs } from '../utils/leaflet-crs';
import html2canvasClone from '../utils/html2canvas-clone';
import state from '../utils/state';
import SnapDraw from './snap-draw';
import ClipperLib from 'npm:clipper-lib';
import jsts from 'npm:jsts';
import { geometryToJsts } from '../utils/layer-to-jsts';
import { downloadFile, downloadBlob } from '../utils/download-file';
import { getCrsByName } from '../utils/get-crs-by-name';
import actionsHandler from './flexberry-maplayer-actions-handler';

export default Ember.Mixin.create(SnapDraw, actionsHandler, {
  /**
    Service for managing map API.
    @property mapApi
    @type MapApiService
  */
  mapApi: Ember.inject.service(),

  /**
    Opens a form for editing or creating objects of the selected layer.

    @method featureEdit
    @param {String} layerPath path to a layer.
    @param {String} loadingPath path to property containing flag indicating whether 'flexberry-layers-attributes-panel' is folded or not.
    @param {String} mapAction the name of the action to be called.
    @param {Object} dataValues field values for filling out the form.
  */
  showFeatureEditForm(layerPath, loadingPath, mapAction, dataValues = null) {
    const controller = Ember.getOwner(this).lookup('controller:map');
    this.featureEdit(controller, layerPath, loadingPath, mapAction, dataValues);
  },

  /**
    Shows layers specified by IDs.

    @method showLayers.
    @param {Array} layerIds Array of layer IDs.
    @return {Promise}
  */
  showLayers(layerIds) {
    return this._setVisibility(layerIds, true);
  },

  /**
    Hides layers specified by IDs.

    @method hideLayers.
    @param {Array} layerIds Array of layer IDs.
    @return nothing
  */
  hideLayers(layerIds) {
    return this._setVisibility(layerIds, false);
  },

  /**
    Shows objects for layer.

    @method showLayerObjects
    @param {string} layerId Layer id.
    @param {string[]} objectIds Array of objects IDs.
    @return nothing
  */
  showLayerObjects(layerId, objectIds) {
    return this._setVisibilityObjects(layerId, objectIds, true);
  },

  /**
    Hides objects for layer.

    @method hideLayerObjects
    @param {string} layerId Layer id.
    @param {Array} objectIds Array of objects IDs.
    @return nothing
  */
  hideLayerObjects(layerId, objectIds) {
    return this._setVisibilityObjects(layerId, objectIds, false);
  },

  /**
    Show all layer objects.

    @method showAllLayerObjects
    @param {string} layerId Layer id.
    @return {Promise}
  */
  showAllLayerObjects(layerId) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      const layer = this.get('mapLayer').findBy('id', layerId);
      if (Ember.isNone(layer)) {
        reject(`Layer '${layerId}' not found.`);
      }

      const leafletObject = Ember.get(layer, '_leafletObject');
      if (!Ember.isNone(leafletObject) && typeof leafletObject.showAllLayerObjects === 'function') {
        leafletObject.showAllLayerObjects().then((result) => {
          resolve(result);
        });
      } else {
        resolve('Is not a vector layer');
      }
    });
  },

  /**
    Hide all layer objects.

    @method hideAllLayerObjects
    @param {string} layerId Layer id.
    @return nothing
  */
  hideAllLayerObjects(layerId) {
    const layer = this.get('mapLayer').findBy('id', layerId);
    if (Ember.isNone(layer)) {
      throw `Layer '${layerId}' not found.`;
    }

    const leafletObject = Ember.get(layer, '_leafletObject');
    if (!Ember.isNone(leafletObject) && typeof leafletObject.hideAllLayerObjects === 'function') {
      leafletObject.hideAllLayerObjects();
    } else {
      throw 'Is not a vector layer';
    }
  },

  /**
    Creates new layer with specified options.
    @method createNewLayer.
    @param {Object} options
    @return Layer ID.
  */
  createNewLayer(options) {
    options = options || {};
    const store = this.get('store');
    let layer = store.createRecord('new-platform-flexberry-g-i-s-map-layer', options);
    layer.set('map', this);
    return layer.save().then(() => {
      const layers = this.get('hierarchy');
      layers.addObject(layer);
      return layer.get('id');
    });
  },

  /**
    Remove object from layer.
    @method deleteLayerObject.
    @param {String} layerId Layer ID.
    @param {String} featureId Object ID.
    @return Promise.
  */
  deleteLayerObject(layerId, featureId) {
    this.deleteLayerObjects(layerId, [featureId]);
  },

  /**
    Remove shapes from layer.
    @method deleteLayerObjects.
    @param {string} layerId Layer ID.
    @param {Object[]} featureIds Array of objects IDs.
    @return Promise.
  */
  deleteLayerObjects(layerId, featureIds) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let ids = [];
      this._getModelLayerFeature(layerId, featureIds, true).then(([layer, leafletObject]) => {
        leafletObject.eachLayer(function (shape) {
          const id = this._getLayerFeatureId(layer, shape);

          if (!Ember.isNone(id) && featureIds.indexOf(id) !== -1) {
            ids.push(id);
            leafletObject.removeLayer(shape);
          }
        }.bind(this));

        const deleteLayerFromAttrPanelFunc = this.get('mapApi').getFromApi('_deleteLayerFromAttrPanel');
        ids.forEach((id) => {
          if (typeof deleteLayerFromAttrPanelFunc === 'function') {
            deleteLayerFromAttrPanelFunc(id, layer);
          }
        });
        resolve();
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Gets intersected features.
    @method getIntersectionObjects
    @param {Object} feature GeoJson Feature.
    @param {string} crsName Name of coordinate reference system, in which to give coordinates.
    @param {Array} layerIds Array of layers IDs.
    @return {Promise} Array of layers and objects which intersected selected object.
  */
  getIntersectionObjects(feature, crsName, layerIds) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      if (!Ember.isNone(feature) && feature.hasOwnProperty('geometry')) {
        const leafletMap = this.get('mapApi').getFromApi('leafletMap');
        let layersIntersect = [];
        layerIds.forEach(id => {
          const layer = this.get('mapLayer').findBy('id', id);
          if (!Ember.isNone(layer)) {
            if (layer.get('settingsAsObject.identifySettings.canBeIdentified')) {
              layersIntersect.push(layer);
            }
          }
        });

        let crs = crsName || 'EPSG:4326';
        let featureCrs = crs === 'EPSG:4326' ? feature : this._convertObjectCoordinates(crs, feature);
        let featureLayer = L.GeoJSON.geometryToLayer(featureCrs);
        let latlng = featureLayer instanceof L.Marker || featureLayer instanceof L.CircleMarker ?
          featureLayer.getLatLng() : featureLayer.getBounds().getCenter();
        let e = {
          latlng: latlng,
          polygonLayer: featureLayer,
          bufferedMainPolygonLayer: featureLayer,
          excludedLayers: [],
          layers: layersIntersect,
          results: Ember.A()
        };

        if (e.layers.length > 0) {
          leafletMap.fire('flexberry-map:identify', e);
        }

        e.results = Ember.isArray(e.results) ? e.results : Ember.A();
        let promises = Ember.A();

        // Handle each result.
        // Detach promises from already received features.
        e.results.forEach((result) => {
          if (Ember.isNone(result)) {
            return;
          }

          promises.pushObject(Ember.get(result, 'features'));
        });

        // Wait for all promises to be settled & call '_finishIdentification' hook.
        Ember.RSVP.allSettled(promises).then(() => {
          resolve(e.results);
        });
      }
    });
  },

  /**
    Get the nearest object
    @method getNearObject
    @param {string} layerId Layer ID of the selected object.
    @param {string} layerObjectId Object ID of the selected object.
    @param {Array} layerIds Array of layers IDs in which to search.
    @return {Promise} Object constains distance, layer and layer object.
  */
  getNearObject(layerId, layerObjectId, layerIds) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this._getModelLayerFeature(layerId, [layerObjectId]).then(([, leafletObject, layerObject]) => {
        const leafletMap = this.get('mapApi').getFromApi('leafletMap');
        let layersGetNeatObject = [];
        layerIds.forEach(id => {
          const layer = this.get('mapLayer').findBy('id', id);
          layersGetNeatObject.push(layer);
        });

        let e = {
          featureLayer: layerObject[0],
          featureId: layerObjectId,
          layerObjectId: layerId,
          layers: layersGetNeatObject,
          results: Ember.A()
        };

        if (e.layers.length > 0) {
          leafletMap.fire('flexberry-map:getNearObject', e);
        }

        e.results = Ember.isArray(e.results) ? e.results : Ember.A();
        let promises = Ember.A();

        // Handle each result.
        // Detach promises from already received features.
        e.results.forEach((result) => {
          if (Ember.isNone(result)) {
            return;
          }

          promises.pushObject(Ember.get(result, 'features'));
        });

        Ember.RSVP.allSettled(promises).then((results) => {
          const rejected = results.filter((item) => { return item.state === 'rejected'; }).length > 0;
          if (rejected) {
            return reject('Failed to get nearest object');
          }

          let res = null;
          results.forEach((item) => {
            if (Ember.isNone(res) || item.value.distance < res.distance) {
              res = item.value;
            }
          });
          resolve(res);
        });
      });
    });
  },

  getObjectCenter(object) {
    const type = Ember.get(object, 'feature.geometry.type');
    if (type === 'Point') {
      return object._latlng;
    } else {
      return object.getBounds().getCenter();
    }
  },

  /**
    Get distance between objects
    @method _getDistanceBetweenObjects
    @param {Object} firstLayerObject First layer object.
    @param {Object} secondLayerObject Second layer object.
    @return {number} Distance between objects in meters.
  */
  _getDistanceBetweenObjects(firstLayerObject, secondLayerObject) {
    const firstPoint = this.getObjectCenter(firstLayerObject);
    const firstObject = helpers.point([firstPoint.lat, firstPoint.lng]);

    const secondPoint = this.getObjectCenter(secondLayerObject);
    const secondObject = helpers.point([secondPoint.lat, secondPoint.lng]);

    // Get distance in meters.
    return distance.default(firstObject, secondObject, { units: 'kilometers' }) * 1000;
  },

  /**
    Get distance between objects
    @method getDistanceBetweenObjects
    @param {string} firstLayerId First layer id.
    @param {string} firstLayerObjectId First layer object id.
    @param {string} secondLayerId Second layer id.
    @param {string} secondLayerObjectId Second layer object id.
    @return {Promise} Distance between objects in meters.
  */
  getDistanceBetweenObjects(firstLayerId, firstLayerObjectId, secondLayerId, secondLayerObjectId) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      Ember.RSVP.all([
        this._getModelLayerFeature(firstLayerId, [firstLayerObjectId]),
        this._getModelLayerFeature(secondLayerId, [secondLayerObjectId])
      ]).then((result) => {
        let objA = result[0][2][0];
        let objB = result[1][2][0];
        resolve(this._getDistanceBetweenObjects(objA, objB));
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Get layer object attributes and coordinates.
    @method getLayerObjectOptions
    @param {String} layerId Layer ID.
    @param {String} featureId Object ID.
    @param {String} crsName Name of coordinate reference system, in which to give coordinates.
    @return {Promise} Attributes and coordinates
  */
  getLayerObjectOptions(layerId, featureId, crsName) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let result;
      this._getModelLayerFeature(layerId, [featureId]).then(([, leafletLayer, features]) => {
        let featureLayer = features[0];
        if (leafletLayer && featureLayer) {
          result = Object.assign({}, featureLayer.feature.properties);
          let geoJSON = featureLayer.feature.geometry;
          geoJSON.crs = { type: 'name', properties: { name: '' } };
          if (crsName) {
            let NewObjCrs = this._convertObjectCoordinates(leafletLayer.options.crs.code, featureLayer.feature, crsName);
            result.geometry = NewObjCrs.geometry.coordinates;
            geoJSON.geometry = NewObjCrs.geometry.coordinates;
            geoJSON.crs.properties.name = crsName;
          } else {
            result.geometry = featureLayer.feature.geometry.coordinates;
            geoJSON.crs.properties.name = leafletLayer.options.crs.code;
          }

          result.shape = geoJSON;
          let jstsGeoJSONReader = new jsts.io.GeoJSONReader();
          let featureLayerGeoJSON = featureLayer.toProjectedGeoJSON(leafletLayer.options.crs);
          let jstsGeoJSON = jstsGeoJSONReader.read(featureLayerGeoJSON);
          result.area = jstsGeoJSON.geometry.getArea();
          resolve(result);
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Check if object A contains object B.
    @method isContainsObject
    @param {String} layerAId First layer ID.
    @param {String} objectAId First object ID.
    @param {String} layerBId Second layer ID.
    @param {String} objectBId Second object ID.
    @return {Promise} true or false.
  */
  isContainsObject(layerAId, objectAId, layerBId, objectBId) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      Ember.RSVP.all([
        this._getModelLayerFeature(layerAId, [objectAId]),
        this._getModelLayerFeature(layerBId, [objectBId])
      ]).then((result) => {
        let objA = result[0][2][0].feature;
        let objB = result[1][2][0].feature;
        let leafletLayerA = result[0][1];
        let leafletLayerB = result[1][1];
        if (objA && objB && leafletLayerA && leafletLayerB) {
          let feature1 = leafletLayerA.options.crs.code === 'EPSG:4326' ? objA : this._convertObjectCoordinates(leafletLayerA.options.crs.code, objA);
          let feature2 = leafletLayerB.options.crs.code === 'EPSG:4326' ? objB : this._convertObjectCoordinates(leafletLayerB.options.crs.code, objB);

          if (feature1.geometry.type === 'MultiPolygon') {
            feature1 = L.polygon(feature1.geometry.coordinates[0]).toGeoJSON();
          }

          if (feature2.geometry.type === 'MultiPolygon') {
            feature2 = L.polygon(feature2.geometry.coordinates[0]).toGeoJSON();
          }

          resolve(booleanContains(feature1, feature2));
        }

      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Calculate the area of object B that extends beyond the boundaries of object A.
    @method getAreaExtends
    @param {String} layerAId First layer ID.
    @param {String} objectAId First object ID.
    @param {String} layerBId Second layer ID.
    @param {String} objectBId Second object ID.
    @return {Promise} Area
  */
  getAreaExtends(layerAId, objectAId, layerBId, objectBId) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      Ember.RSVP.all([
        this._getModelLayerFeature(layerAId, [objectAId]),
        this._getModelLayerFeature(layerBId, [objectBId])
      ]).then((result) => {
        let objA = result[0][2][0].feature;
        let objB = result[1][2][0].feature;
        let layerObjectA = result[0][1];
        let layerObjectB = result[1][1];
        let feature1 = layerObjectA.options.crs.code === 'EPSG:4326' ? objA : this._convertObjectCoordinates(layerObjectA.options.crs.code, objA);
        let feature2 = layerObjectB.options.crs.code === 'EPSG:4326' ? objB : this._convertObjectCoordinates(layerObjectB.options.crs.code, objB);
        let intersectionRes = intersect.default(feature2, feature1);
        if (intersectionRes) {
          let resultArea = area(feature2) - area(intersectionRes);
          resolve(resultArea);
        } else {
          resolve(area(feature2));
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Get layer type.
    @method _getTypeLayer
    @param {Object} layerModel layer model.
    @return {Object} layer type
  */
  _getTypeLayer(layerModel) {
    let className = Ember.get(layerModel, 'type');
    let layerType = Ember.getOwner(this).knownForType('layer', className);
    return layerType;
  },

  _setVisibility(layerIds, visibility = false) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      if (Ember.isArray(layerIds)) {
        const leafletMap = this.get('mapApi').getFromApi('leafletMap');
        let currentLayerIds = [];
        layerIds.forEach(id => {
          const layer = this.get('mapLayer').findBy('id', id);
          if (layer) {
            layer.set('visibility', visibility);
            currentLayerIds.push(id);
          } else {
            Ember.run.later(this, () => { reject(`Layer '${id}' not found.`); }, 1);
          }
        });
        if (visibility) {
          if (currentLayerIds.length > 0) {
            let e = {
              layers: currentLayerIds,
              results: Ember.A()
            };
            leafletMap.fire('flexberry-map:moveend', e);
            e.results = Ember.isArray(e.results) ? e.results : Ember.A();
            let promises = Ember.A();
            e.results.forEach((result) => {
              if (Ember.isNone(result)) {
                return;
              }

              promises.pushObject(Ember.get(result, 'promise'));
            });

            Ember.RSVP.allSettled(promises).then(() => {
              Ember.run.later(this, () => { resolve('success'); }, 1);
            });
          } else {
            Ember.run.later(this, () => { reject('all layerIds is not found'); }, 1);
          }
        } else {
          Ember.run.later(this, () => { resolve('success'); }, 1);
        }
      } else {
        reject('Parametr is not a Array');
      }
    });
  },

  /**
    Get object id by object and layer.

    @method _getLayerFeatureId
    @param {Object} layer Layer.
    @param {Object} layerObject Object.
    @return {number} Object ID.
  */
  _getLayerFeatureId(layer, layerObject) {
    let field = this._getPkField(layer);
    if (layerObject.state !== state.insert) {
      if (layerObject.feature.properties.hasOwnProperty(field)) {
        return Ember.get(layerObject, 'feature.properties.' + field);
      }

      return Ember.get(layerObject, 'feature.id');
    } else {
      return null;
    }
  },

  /**
    Determine the visibility of the specified objects by id for the layer.

    @method _setVisibilityObjects
    @param {string} layerId Layer ID.
    @param {string[]} objectIds Array of objects IDs.
    @param {boolean} [visibility=false] visibility Object Visibility.
    @return {Ember.RSVP.Promise}
  */
  _setVisibilityObjects(layerId, objectIds, visibility = false) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      if (Ember.isArray(objectIds)) {
        let [layer, leafletObject] = this._getModelLeafletObject(layerId);
        if (Ember.isNone(layer)) {
          return reject(`Layer '${layerId}' not found.`);
        }

        if (!Ember.isNone(leafletObject) && typeof leafletObject._setVisibilityObjects === 'function') {
          leafletObject._setVisibilityObjects(objectIds, visibility).then((result) => {
            resolve(result);
          });
        } else {
          return reject('Layer type not supported');
        }
      }
    });
  },

  /**
    To copy Object from Source layer to Destination.
    @method copyObject
    @param {Object} source Object with source settings
    {
      layerId, //{string} Layer ID
      objectId, //{string} Object ID
      shouldRemove //{Bool} Should remove object from source layer
    }
    @param {Object} destination Object with destination settings
    {
      layerId, //{string} Layer ID
      properties //{Object} Properties of new object.
    }
    @return {Promise} Object in Destination layer
  */
  copyObject(source, destination) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this._getModelLayerFeature(source.layerId, [source.objectId], source.shouldRemove).then(([, sourceLeafletLayer, obj]) => {
        let sourceFeature = obj[0];
        let [destLayerModel, destLeafletLayer] = this._getModelLeafletObject(destination.layerId);
        let destFeature;
        switch (destLayerModel.get('settingsAsObject.typeGeometry').toLowerCase()) {
          case 'polygon':
            destFeature = L.polygon(sourceFeature.getLatLngs());
            break;
          case 'polyline':
            destFeature = L.polyline(sourceFeature.getLatLngs());
            break;
          case 'marker':
            destFeature = L.marker(sourceFeature.getLatLng());
            break;
          default:
            reject(`Unknown layer type: '${destLayerModel.get('settingsAsObject.typeGeometry')}`);
        }

        if (!Ember.isNone(destFeature)) {
          destFeature.feature = {
            properties: Object.assign({}, sourceFeature.feature.properties, destination.properties || {})
          };

          if (sourceLeafletLayer.geometryField) {
            delete destFeature.feature.properties[sourceLeafletLayer.geometryField];
          }

          if (destLeafletLayer.geometryField) {
            delete destFeature.feature.properties[destLeafletLayer.geometryField];
          }

          let e = { layers: [destFeature], results: Ember.A() };
          destLeafletLayer.fire('load', e);

          Ember.RSVP.allSettled(e.results).then(() => {
            if (source.shouldRemove) {
              sourceLeafletLayer.removeLayer(sourceFeature);
            }

            resolve(destFeature);
          });
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    To copy Objects from Source layer to Destination.
    @method copyObjectsBatch
    @param {Object} source Object with source settings
    {
      layerId, //{string} Layer ID
      objectIds, //{array} Objects ID
      shouldRemove //{Bool} Should remove object from source layer
    }
    @param {Object} destination Object with destination settings
    {
      layerId, //{string} Layer ID
      withProperties //{Bool} To copy objects with it properties.
    }
    @return {Promise} Object in Destination layer
  */
  copyObjectsBatch(source, destination) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      if (Ember.isNone(source.layerId) || Ember.isNone(source.objectIds) || Ember.isNone(destination.layerId)) {
        reject('Check the parameters you are passing');
      } else {
        let loadPromise = new Ember.RSVP.all(this.loadingFeaturesByPackages(source.layerId, source.objectIds, source.shouldRemove));

        loadPromise.then((res) => {
          let destFeatures = [];
          let sourceFeatures = [];
          let [destLayerModel, destLeafletLayer] = this._getModelLeafletObject(destination.layerId);
          let [sourceModel, sourceLeafletLayer] = this._getModelLeafletObject(source.layerId);
          let objects = [];
          if (source.shouldRemove) {
            sourceLeafletLayer.eachLayer(shape => {
              if (source.objectIds.indexOf(this._getLayerFeatureId(sourceModel, shape)) !== -1) {
                objects.push(shape);
              }
            });
          } else {
            res.forEach(result => {
              objects = objects.concat(result[2]);
            });
          }

          objects.forEach(sourceFeature => {
            let destFeature;
            switch (destLayerModel.get('settingsAsObject.typeGeometry').toLowerCase()) {
              case 'polygon':
                destFeature = L.polygon(sourceFeature.getLatLngs());
                break;
              case 'polyline':
                destFeature = L.polyline(sourceFeature.getLatLngs());
                break;
              case 'marker':
                destFeature = L.marker(sourceFeature.getLatLng());
                break;
              default:
                reject(`Unknown layer type: '${destLayerModel.get('settingsAsObject.typeGeometry')}`);
            }

            if (!Ember.isNone(destFeature)) {
              destFeature.feature = { properties: {} };
              if (destination.withProperties) {
                destFeature.feature.properties = Object.assign({}, sourceFeature.feature.properties);

                if (sourceLeafletLayer.geometryField) {
                  delete destFeature.feature.properties[sourceLeafletLayer.geometryField];
                }

                if (destLeafletLayer.geometryField) {
                  delete destFeature.feature.properties[destLeafletLayer.geometryField];
                }
              }

              destFeatures.push(destFeature);
              if (source.shouldRemove) {
                sourceFeatures.push(sourceFeature);
              }
            }

          });

          let e = { layers: destFeatures, results: Ember.A() };
          destLeafletLayer.fire('load', e);

          Ember.RSVP.allSettled(e.results).then(() => {
            if (source.shouldRemove) {
              sourceFeatures.forEach(sourceFeature => {
                sourceLeafletLayer.removeLayer(sourceFeature);
              });
            }

            resolve(destFeatures);
          });
        }).catch(e => reject(e));
      }
    });
  },

  /**
    Calculate the area of intersection between object A and objects in array B.
    @method getIntersectionArea
    @param {String} layerAId First layer ID.
    @param {String} objectAId First object ID.
    @param {String} layerBId Second layer ID.
    @param {Array} objectBIds Array of object IDs in second layer.
    @param {Bool} showOnMap flag indicates if intersection area will be displayed on map.
    @return {Promise} If showOnMap = true, return objects, which show on map in serviceLayer, and area, else only area.
  */
  getIntersectionArea(layerAId, objectAId, layerBId, objectBIds, showOnMap) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let result = Ember.A();
      Ember.RSVP.all([
        this._getModelLayerFeature(layerAId, [objectAId]),
        this._getModelLayerFeature(layerBId, objectBIds)
      ]).then((res) => {
        let layerObjectA = res[0][1];
        let layerObjectB = res[1][1];
        let objA = res[0][2][0].feature;
        let feature1 = layerObjectA.options.crs.code === 'EPSG:4326' ? objA : this._convertObjectCoordinates(layerObjectA.options.crs.code, objA);
        let featuresB = res[1][2];
        featuresB.forEach((feat) => {
          let objB = feat.feature;
          let feature2 = layerObjectB.options.crs.code === 'EPSG:4326' ? objB : this._convertObjectCoordinates(layerObjectB.options.crs.code, objB);
          let intersectionRes = intersect.default(feature1, feature2);
          if (intersectionRes) {
            let object = {
              id: objB.properties.primarykey,
              area: area(intersectionRes)
            };
            if (showOnMap) {
              let obj = L.geoJSON(intersectionRes, {
                style: { color: 'green' }
              });
              let serviceLayer = this.get('mapApi').getFromApi('serviceLayer');
              obj.addTo(serviceLayer);
              object.objectIntesect = obj;
            }

            result.pushObject(object);
          } else {
            result.pushObject({
              id: objB.properties.primarykey,
              area: 'Intersection not found'
            });
          }
        });
        if (!Ember.isNone(result)) {
          resolve(result);
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Cleans the service layer.
    @method clearServiceLayer
    @return nothing
  */
  clearServiceLayer() {
    let serviceLayer = this.get('mapApi').getFromApi('serviceLayer');
    serviceLayer.clearLayers();
  },

  /**
    Create image for layer object.
    @method  getSnapShot
    @param {Object} source Object with settings
    {
      layerId, //{string} Layer ID.
      objectId, //{string} Object ID.
      layerArrIds, //{Array} Array of layers IDs.
      options {
        width, //{number} width image
        height //{number} height image
      }
    }
    @return {Promise} Image url.
  */
  getSnapShot({ layerId, objectId, layerArrIds, options }) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this._getModelLayerFeature(layerId, [objectId]).then(([layerModel, leafletObject, featureLayer]) => {
        let allLayers = this.get('mapLayer.canonicalState');
        let allLayersIds = allLayers.map((l) => l.id);
        if (layerArrIds) {
          let showLayersIds = layerArrIds;
          showLayersIds.push(layerId);

          this.showLayers(showLayersIds);
          let hideLayersIds = allLayersIds.filter((i) => { return showLayersIds.indexOf(i) < 0; });
          this.hideLayers(hideLayersIds);
        }

        const leafletMap = this.get('mapApi').getFromApi('leafletMap');

        let $mapPicture = Ember.$(leafletMap._container);
        let heightMap = $mapPicture.height();
        let widthMap = $mapPicture.width();
        let heightNew = heightMap;
        let widthNew = widthMap;
        if (!Ember.isNone(options)) {
          heightNew = Ember.isNone(options.height) ? heightMap : options.height;
          widthNew = Ember.isNone(options.width) ? widthMap : options.width;
        }

        $mapPicture.height(heightNew);
        $mapPicture.width(widthNew);

        let load = [];
        let ids = Ember.isEmpty(layerArrIds) ? allLayersIds : layerArrIds;
        if (ids) {
          ids.forEach((lid) => {
            if (lid !== layerId) {
              let [, layerObject] = this._getModelLeafletObject(lid);
              layerObject.statusLoadLayer = true;
              load.push(layerObject);
            }
          });
        }

        leafletObject.statusLoadLayer = true;
        load.push(leafletObject);

        leafletMap.once('moveend', () => {
          Ember.run.later(() => {
            document.getElementsByClassName('leaflet-control-zoom leaflet-bar leaflet-control')[0].style.display = 'none';
            document.getElementsByClassName('history-control leaflet-bar leaflet-control horizontal')[0].style.display = 'none';
            Ember.$(document).find('.leaflet-top.leaflet-left').css('display', 'none');
            Ember.$(document).find('.leaflet-top.leaflet-right').css('display', 'none');
            Ember.$(document).find('.leaflet-bottom.leaflet-right').css('display', 'none');

            let promises = load.map((object) => {
              return !Ember.isNone(leafletObject.promiseLoadLayer) && (leafletObject.promiseLoadLayer instanceof Ember.RSVP.Promise);
            });

            Ember.RSVP.allSettled(promises).then((e) => {
              load.forEach((obj) => {
                obj.statusLoadLayer = false;
                obj.promiseLoadLayer = null;
              });

              let html2canvasOptions = Object.assign({
                useCORS: true,
                onclone: function (clonedDoc) {
                  html2canvasClone(clonedDoc);
                }
              });
              window.html2canvas($mapPicture[0], html2canvasOptions)
                .then((canvas) => {
                  let type = 'image/png';
                  var image64 = canvas.toDataURL(type);
                  resolve(image64);
                })
                .catch((e) => reject(e))
                .finally(() => {
                  document.getElementsByClassName('leaflet-control-zoom leaflet-bar leaflet-control')[0].style.display = 'block';
                  document.getElementsByClassName('history-control leaflet-bar leaflet-control horizontal')[0].style.display = 'block';
                  Ember.$(document).find('.leaflet-top.leaflet-left').css('display', 'block');
                  Ember.$(document).find('.leaflet-top.leaflet-right').css('display', 'block');
                  Ember.$(document).find('.leaflet-bottom.leaflet-right').css('display', 'block');
                  $mapPicture.height(heightMap);
                  $mapPicture.width(widthMap);
                });
            });
          });
        });

        let bounds = featureLayer[0].getBounds();
        if (!Ember.isNone(bounds)) {
          leafletMap.fitBounds(bounds.pad(0.5));
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Download image for layer object.
    @method  downloadSnapShot
    @param {Object} source Object with settings
    {
      layerId, //{string} Layer ID.
      objectId, //{string} Object ID.
      layerArrIds, //{Array} Array of layers IDs.
      options {
        width, //{number} width image
        height //{number} height image
      },
      fileName //{string} File name.
    }
    @return {File} Image file.
  */
  downloadSnapShot({ layerId, objectId, layerArrIds, options, fileName }) {
    this.getSnapShot({ layerId, objectId, layerArrIds, options }).then((uri) => {
      var link = document.createElement('a');
      if (typeof link.download === 'string') {
        link.href = uri;
        link.download = fileName;

        //Firefox requires the link to be in the body
        document.body.appendChild(link);

        //simulate click
        link.click();

        //remove the link when done
        document.body.removeChild(link);
      } else {
        window.open(uri);
      }
    });
  },

  /**
    Get a rhumb object for [LineString, MultiLineString, Polygon, MultiPolygon]. Parameters is object in GeoJSON
    format and name of coordinate reference system. Calculates rhumb between points. Use jsts libraries to calculate distance between points.
    Distance calculation in units of coordinate reference system. Names of direction is [NE, SE, NW, SW]. Angle calculation in degree.
    Returns array of object:

    ```javascript
    [{
      type - type of object is [LineString, Polygon],
      crs - name of coordinate reference system of start point,
      startPoint - coordinates of start point,
      skip - how many rhumb skip from beginning (always 0),
      points - array objects of rhumbs,
      hole - if this part is hole then true else false. Only Polygon and MultiPolygon have it.
    }]
    ```

    Objects of rhumbs consist from angle, distance and direction of rhumb. Example:

    ```javascript
    {
      rhumb: 'NE',
      angle: 45,
      distance: 1000
    }
    ```

    Example of method call:

    ```javascript
    var feature = {
      type: "Feature",
      geometry:
      {
        "type": "Polygon",
        "coordinates": [
          [[56.09419, 58.08895], [56.093588, 58.088632], [56.094269, 58.088632], [56.094269, 58.088902], [56.09419, 58.08895]]
        ]
      }
    };

    var result = mapApi.mapModel.getRhumb(feature, 'EPSG:4326');
    ```
    @method  getRhumb
    @param {Object} feature GeoJson Feature.
    @param {string} crsName Name of coordinate reference system, in which to give coordinates.
    @return {Array} Array object rhumb.
  */
  getRhumb(feature, crsName) {
    let coords = feature.geometry.coordinates;
    let result = [];

    var calcRhumb = function (point1, point2) {
      // Get distance
      let geojson1 = {
        type: 'Point',
        coordinates: point1
      };
      let geojson2 = {
        type: 'Point',
        coordinates: point2
      };

      let jsts1 = geometryToJsts(geojson1);
      let jsts2 = geometryToJsts(geojson2);
      const distance = jsts1.distance(jsts2);

      // Get the angle.
      var getAngle = function (p1, p2) {
        return Math.atan2(p1[1] - p2[1], p1[0] - p2[0]) / Math.PI * 180;
      };

      const bearing = getAngle(point2, point1);

      let rhumb;
      let angle;

      // Calculates rhumb.
      if (bearing <= 90 && bearing >= 0) {
        // NE
        rhumb = 'NE';
        angle = 90 - bearing;
      } else if (bearing <= 180 && bearing >= 90) {
        // NW
        rhumb = 'NW';
        angle = (bearing - 90);
      } else if (bearing >= -180 && bearing <= -90) {
        // SW
        rhumb = 'SW';
        angle = (-1 * bearing - 90);
      } if (bearing <= 0 && bearing >= -90) {
        // SE
        rhumb = 'SE';
        angle = (90 + bearing);
      }

      return {
        rhumb: rhumb,
        angle: angle,
        distance: distance
      };
    };

    let coordToRhumbs = function(type, coords) {
      let startPoint = null;
      let n;
      let point1;
      let point2;
      let rhumbs = [];
      for (let i = 0; i < coords.length - 1; i++) {
        startPoint = i === 0 ? coords[i] : startPoint;
        point1 = coords[i];
        n = !Ember.isNone(coords[i + 1]) ? i + 1 : 0;
        point2 = coords[n];
        rhumbs.push(calcRhumb(point1, point2));
      }

      return {
        type: type,
        crs: crsName,
        startPoint: startPoint,
        skip: 0,
        points: rhumbs
      };
    };

    switch (feature.geometry.type) {
      case 'LineString':
        result.push(coordToRhumbs('LineString', coords));
        break;
      case 'MultiLineString':
        for (let i = 0; i < coords.length; i++) {
          result.push(coordToRhumbs('LineString', coords[i]));
        }

        break;
      case 'Polygon':
        for (let i = 0; i < coords.length; i++) {
          result.push(coordToRhumbs('Polygon', coords[i]));
          result[i].hole = i > 0 ? true : false;
        }

        break;
      case 'MultiPolygon':
        for (let i = 0; i < coords.length; i++) {
          for (let j = 0; j < coords[i].length; j++) {
            result.push(coordToRhumbs('Polygon', coords[i][j]));
            result[result.length - 1].hole = j > 0 ? true : false;
          }
        }

        break;
    }

    return result;
  },

  /**
    Add a layer to the group.

    @method layerToGroup
    @parm {string} layerId Layer ID.
    @parm {string} layerGroupId Group layer ID.
    @return nothing
  */
  moveLayerToGroup(layerId, layerGroupId) {
    const layer = this.get('mapLayer').findBy('id', layerGroupId);
    if (Ember.isNone(layer)) {
      throw (`Group layer '${layerGroupId}' not found`);
    }

    let layerModel = this.getLayerModel(layerId);
    if (Ember.isNone(layerModel)) {
      throw (`Layer '${layerId}' not found`);
    }

    layerModel.set('parent', layer);
  },

  /**
    Edit object layer.
    @method editLayerObject
    @param {String} layerId Layer ID.
    @param {String} objectId Object ID.
    @param {String} polygon Сoordinates of the new object in geoJSON format.
    @param {String} crsName Name of coordinate reference system.
    @return {Promise} Layer object.
  */
  editLayerObject(layerId, objectId, polygon, crsName) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      if (polygon) {
        this._getModelLayerFeature(layerId, [objectId], true).then(([, leafletLayer, featureLayer]) => {
          if (leafletLayer && featureLayer) {
            let crs = leafletLayer.options.crs;
            if (!Ember.isNone(crsName)) {
              crs = getLeafletCrs('{ "code": "' + crsName.toUpperCase() + '", "definition": "" }', this);
            }

            let coordsToLatLng = function (coords) {
              return crs.unproject(L.point(coords));
            };

            let geoJSON = null;
            if (!Ember.isNone(crs) && crs.code !== 'EPSG:4326') {
              geoJSON = L.geoJSON(polygon, { coordsToLatLng: coordsToLatLng.bind(this) }).getLayers()[0];
            } else {
              geoJSON = L.geoJSON(polygon).getLayers()[0];
            }

            if (!Ember.isNone(Ember.get(geoJSON, 'feature.geometry'))) {
              if (Ember.get(geoJSON, 'feature.geometry.type').toLowerCase() !== 'point') {
                featureLayer[0].setLatLngs(geoJSON._latlngs);
              } else {
                featureLayer[0].setLatLng(geoJSON._latlng);
              }

              if (typeof leafletLayer.editLayer === 'function') {
                leafletLayer.editLayer(featureLayer[0]);
                resolve(featureLayer[0]);
              }
            } else {
              reject(`Unable to convert coordinates for this CRS '${crsName}'`);
            }
          } else if (leafletLayer) {
            reject(`Layer '${layerId}' not found`);
          } else if (featureLayer[0]) {
            reject(`Object '${objectId}' not found`);
          }
        }).catch((e) => {
          reject(e);
        });
      } else {
        reject('new object settings not passed');
      }
    });
  },

  /**
    Upload file.
    @method uploadFile
    @param {File} file.
    @return {Promise} Object in geoJSON format
  */
  uploadFile(file) {
    let config = Ember.getOwner(this).resolveRegistration('config:environment');
    let data = new FormData();
    data.append(file.name, file);

    return new Ember.RSVP.Promise((resolve, reject) => {
      Ember.$.ajax({
        url: `${config.APP.backendUrl}/controls/FileUploaderHandler.ashx?FileName=${file.name}`,
        type: 'POST',
        data: data,
        cache: false,
        contentType: false,
        processData: false,
        success: function (data) {
          resolve(data);
        },
        error: function (e) {
          reject(e);
        }
      });
    });
  },

  _isObject(item) {
    return (item && typeof item === 'object' && !Array.isArray(item));
  },

  /**
    Convert coordinates of object to wgs84, or other crsName.
    @method convertObjectCoordinates
    @param {featureLayer} object.
    @return {featureLayer} Returns provided object with converted coordinates
    @private
  */
  _convertObjectCoordinates(projection, object, crsName = null) {

    // copy from https://stackoverflow.com/a/48218209/2014079 for replace $.extend
    // such as it is not properly work with Proxy properties
    var mergeDeep = function (...objects) {
      const isObject = obj => obj && typeof obj === 'object';

      return objects.reduce((prev, obj) => {
        Object.keys(obj).forEach(key => {
          const pVal = prev[key];
          const oVal = obj[key];

          if (Array.isArray(pVal) && Array.isArray(oVal)) {
            prev[key] = pVal.concat(...oVal);
          } else if (isObject(pVal) && isObject(oVal)) {
            prev[key] = mergeDeep(pVal, oVal);
          } else {
            prev[key] = oVal;
          }
        });

        return prev;
      }, {});
    };

    let knownCrs = Ember.getOwner(this).knownForType('coordinate-reference-system');
    let knownCrsArray = Ember.A(Object.values(knownCrs));
    let firstProjection = projection ? projection : 'EPSG:4326';
    let secondProjection = crsName ? crsName : 'EPSG:4326';
    let firstCrs = knownCrsArray.findBy('code', firstProjection);
    let secondCrs = knownCrsArray.findBy('code', secondProjection);
    if (firstCrs && secondCrs) {
      let firstDefinition = Ember.get(firstCrs, 'definition');
      let secondDefinition = Ember.get(secondCrs, 'definition');
      if (firstDefinition && secondDefinition) {
        if (firstDefinition !== secondDefinition) {
          let result = mergeDeep({}, object);
          let coordinatesArray = [];
          if (result.geometry.type !== 'Point') {
            result.geometry.coordinates.forEach(arr => {
              var arr1 = [];
              if (result.geometry.type.indexOf('Multi') !== -1) {
                arr.forEach(pair => {
                  if (result.geometry.type === 'MultiPolygon') {
                    let arr2 = [];
                    pair.forEach(cords => {
                      let transdormedCords = proj4(firstDefinition, secondDefinition, cords);
                      arr2.push(transdormedCords);
                    });
                    arr1.push(arr2);
                  } else {
                    let cords = proj4(firstDefinition, secondDefinition, pair);
                    arr1.push(cords);
                  }

                });
                coordinatesArray.push(arr1);
              } else {
                if (result.geometry.type === 'Polygon') {
                  arr.forEach(cords => {
                    let transdormedCords = proj4(firstDefinition, secondDefinition, cords);
                    arr1.push(transdormedCords);
                  });
                  coordinatesArray.push(arr1);
                } else {
                  let cords = proj4(firstDefinition, secondDefinition, arr);
                  coordinatesArray.push(cords);
                }
              }
            });
          } else {
            coordinatesArray = proj4(firstDefinition, secondDefinition, result.geometry.coordinates);
          }

          result.geometry.coordinates = coordinatesArray;
          return result;
        } else {
          return object;
        }
      }
    } else {
      throw 'unknown coordinate reference system';
    }
  },

  /*
    Get the field to search for objects
    @method getPkField
    @param {Object} layer.
    @return {String} Field name.
  */
  _getPkField(layer) {
    if (!Ember.isNone(layer) && !Ember.isNone(layer._leafletObject) && typeof layer._leafletObject.getPkField === 'function') {
      return layer._leafletObject.getPkField(layer);
    } else {
      throw 'Layer is not VectorLayer';
    }
  },

  /**
    Get coordinates point.
    @method getCoordPoint
    @param {String} crsName Name of coordinate reference system, in which to give coordinates.
    @param {Boolean} snap Snap or not
    @param {Array} snapLayers Layers for snap
    @param {Integer} snapDistance in pixels
    @param {Boolean} snapOnlyVertex or segments too
    @return {Promise} Coordinate.
  */
  getCoordPoint(crsName, snap, snapLayers, snapDistance, snapOnlyVertex) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      const leafletMap = this.get('mapApi').getFromApi('leafletMap');
      Ember.$(leafletMap._container).css('cursor', 'crosshair');

      var getCoord = (e) => {
        if (snap) {
          this._drawClick(e);
        }

        leafletMap.off('mousemove', this._handleSnapping, this);
        let layers = this.get('_snapLayersGroups');
        if (layers) {
          layers.forEach((l, i) => {
            l.off('load', this._setSnappingFeatures, this);
          });
        }

        this._cleanupSnapping();

        Ember.$(leafletMap._container).css('cursor', '');
        let crs = Ember.get(leafletMap, 'options.crs');
        if (!Ember.isNone(crsName)) {
          crs = getLeafletCrs('{ "code": "' + crsName.toUpperCase() + '", "definition": "" }', this);
        }

        resolve(crs.project(e.latlng));
      };

      if (snap) {
        let layers = snapLayers.map((id) => {
          let [, leafletObject] = this._getModelLeafletObject(id);
          return leafletObject;
        }).filter(l => !!l);

        layers.forEach((l, i) => {
          l.on('load', this._setSnappingFeatures, this);
        });

        this.set('_snapLayersGroups', layers);
        this._setSnappingFeatures();

        if (snapDistance) {
          this.set('_snapDistance', snapDistance);
        }

        if (!Ember.isNone(snapOnlyVertex)) {
          this.set('_snapOnlyVertex', snapOnlyVertex);
        }

        let editTools = this._getEditTools();
        leafletMap.on('mousemove', this._handleSnapping, this);
        this.set('_snapMarker', L.marker(leafletMap.getCenter(), {
          icon: editTools.createVertexIcon({ className: 'leaflet-div-icon leaflet-drawing-icon' }),
          opacity: 1,
          zIndexOffset: 1000
        }));
      }

      leafletMap.once('click', getCoord);
    });
  },

  /**
    Loading features by packages
    @method loadingFeaturesByPackages
    @param {String} layerId Layer ID.
    @param {Array} objectIds Object IDs.
    @return {Promise}
  */
  loadingFeaturesByPackages(layerId, objectIds, shouldRemove = false) {
    let packageSize = 100;

    let layerPromises = [];

    let startPackage = 0;
    while (startPackage < objectIds.length) {
      let endPackage = (startPackage + packageSize) <= objectIds.length ? startPackage + packageSize : objectIds.length;
      let objectsPackage = [];
      for (var i = startPackage; i < endPackage; i++) {
        objectsPackage.push(objectIds[i]);
      }

      layerPromises.push(this._getModelLayerFeature(layerId, objectsPackage, shouldRemove));
      startPackage = endPackage;
    }

    return layerPromises;
  },

  /**
    Get merged geometry. Loads objects from a layers by packages of 100 units each.
    Waits when all objects successfully load. Transform objects into JSTS objects.
    First it merges geometry of objects on first layer using _getMulti method, then on second layer.
    Result of combining objects in each layer is merged into a common geometry using createMulti method.

    @method getMergedGeometry
    @param {String} layerAId First layer ID.
    @param {Array} objectAIds First layer object IDs.
    @param {String} layerBId Second layer ID.
    @param {Array} objectBIds Second layer object IDs.
    @param {Boolean} failIfInvalid Fail when has invalid geometry.
    @param {Boolean} forceMulti Flag: indicates whether to make geometries as multi..
    @return {Promise} GeoJson Feature.
  */
  getMergedGeometry(layerAId, objectAIds, layerBId, objectBIds, isUnion = false, failIfInvalid = false, forceMulti = true) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let layerAPromises = this.loadingFeaturesByPackages(layerAId, objectAIds);
      let layerBPromises = this.loadingFeaturesByPackages(layerBId, objectBIds);

      Ember.RSVP.allSettled(
        layerAPromises.concat(layerBPromises)
      ).then((layerFeatures) => {
        const rejected = layerFeatures.filter((item) => { return item.state === 'rejected'; }).length > 0;

        if (rejected) {
          reject('Error loading objects');
        }

        let count = 0;
        let scale = this.get('mapApi').getFromApi('precisionScale');
        let resultObjs = Ember.A();

        layerFeatures.forEach((r, i) => {
          let geometries = Ember.A();
          r.value[2].forEach((obj, ind) => {
            if (Ember.get(obj, 'feature.geometry') && Ember.get(obj, 'options.crs.code')) {
              let feature = obj.toJsts(obj.options.crs, scale);
              geometries.pushObject(feature);
            }
          });

          count += 1;

          // если вся геометрия невалидна, то будет null
          let merged = this._getMulti(geometries, isUnion, failIfInvalid);
          if (merged) {
            resultObjs.pushObject(merged);
          }
        });

        let resultObj = resultObjs.length > 0 ? this.createMulti(resultObjs, isUnion, failIfInvalid, true, forceMulti) : null;
        resolve(resultObj ? resultObj : null);
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Add to array points and feature.
    @method _addToArrayPointsAndFeature
    @param {String} layerId Layer ID.
    @param {String} crsName Name of coordinate reference system, in which to conver coordinates.
    @return {Promise} array of points and feature.
  */
  _addToArrayPointsAndFeature(layerId, crsName) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      this._getModelLayerFeature(layerId, null).then(([, layerObject, layerFeatures]) => {
        if (!Ember.isEmpty(layerFeatures)) {
          let arrPoints = Ember.A();
          let features = Ember.A();
          layerFeatures.forEach((layer) => {
            let obj = layer.feature;
            if (!Ember.isNone(crsName)) {
              obj = this._convertObjectCoordinates(layerObject.options.crs.code, obj, crsName);
            }

            let featureLayer = L.GeoJSON.geometryToLayer(obj);
            arrPoints.push(this._coordsToPoints(featureLayer.getLatLngs()));
            features.push(layer);
          });

          resolve({ arrPoints, features });
        } else {
          reject('Error to load objects');
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Difference layers.
    @method differenceLayers
    @param {String} layerAId First layer ID.
    @param {String} layerBId Second layer ID.
    @return {Promise} array of Object { diffFeatures, layerAFeatures, layerBFeatures }.
  */
  differenceLayers(layerAId, layerBId) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let crsA = this._getModelLeafletObject(layerAId)[1].options.crs.code;
      let crsB = this._getModelLeafletObject(layerBId)[1].options.crs.code;
      let arrayPointsAndFeaturePromises = [this._addToArrayPointsAndFeature(layerAId), this._addToArrayPointsAndFeature(layerBId)];
      if (crsA !== crsB) {
        arrayPointsAndFeaturePromises = [this._addToArrayPointsAndFeature(layerAId), this._addToArrayPointsAndFeature(layerBId, crsA)];
      }

      Ember.RSVP.all(arrayPointsAndFeaturePromises).then((res) => {
        let subj = res[0].arrPoints; // layer A
        let clip = res[1].arrPoints; // layer B
        let solution = ClipperLib.Paths();
        let cpr = new ClipperLib.Clipper(); // The Clipper constructor creates an instance of the Clipper class

        // Add 'Subject' paths - layer A
        for (let s = 0, slen = subj.length; s < slen; s++) {
          if (Ember.isArray(subj[s])) { // multipolygon
            for (let k = 0, klen = subj[s].length; k < klen; k++) {
              cpr.AddPaths(subj[s][k], ClipperLib.PolyType.ptSubject, true);
            }
          } else { // polygon
            cpr.AddPaths(subj[s], ClipperLib.PolyType.ptSubject, true);
          }
        }

        // Add 'Clipping' paths - layer B
        for (let c = 0, clen = clip.length; c < clen; c++) {
          if (Ember.isArray(clip[c])) { // multipolygon
            for (let k = 0, klen = clip[c].length; k < klen; k++) {
              cpr.AddPaths(clip[c][k], ClipperLib.PolyType.ptClip, true);
            }
          } else { // polygon
            cpr.AddPaths(clip[c], ClipperLib.PolyType.ptClip, true);
          }
        }

        // Performing the clipping operation - Difference, result operation be return in solution
        cpr.Execute(ClipperLib.ClipType.ctDifference, solution);

        // filtering 'solution' by area !== 0, transformating of geometry in jsts for comparison and calculate area, filtering after transformation by area > 0
        if (!Ember.isEmpty(solution)) {
          let jstsGeoJSONReader = new jsts.io.GeoJSONReader();
          let diffNotNullArea = solution.filter((geom) => {
            return ClipperLib.Clipper.Area(geom) !== 0;
          }).map((geom) => {
            let feature = {
              type: 'Feature',
              geometry: {
                type: 'Polygon',
                coordinates: [this._pointsToCoords(geom)],
              }
            };
            let jstsFeature = jstsGeoJSONReader.read(feature);
            if (jstsFeature.geometry.isValid()) {
              let area = jstsFeature.geometry.getArea();
              return { feature, jstsGeometry: jstsFeature.geometry, area };
            } else {
              return { feature, jstsGeometry: jstsFeature.geometry, area: 0 };
            }
          }).filter((diff) => {
            return diff.area > 0;
          });

          resolve({ diffFeatures: diffNotNullArea, layerA: res[0].features, layerB: res[1].features });
        } else {
          resolve('The difference is not found');
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Compare layers.
    @method compareLayers
    @param {String} layerAId First layer ID.
    @param {String} layerBId Second layer ID.
    @param {String} condition Comparison conditions ["contains", "intersects", "notIntersects"].
    @param {Boolean} showOnMap flag indicates if difference area will be displayed on map.
    @return {Promise} array of objects with {areaDifference, objectDifference, id of layerB, that matches the condition}.
  */
  compareLayers(layerAId, layerBId, condition, showOnMap) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      let result = Ember.A();
      let cond = ['contains', 'intersects', 'notIntersects'];
      let diffLayerPromise = this.differenceLayers(layerAId, layerBId);
      if (!cond.includes(condition)) {
        reject('The comparison condition is set incorrectly. It must be ["contains", "intersects", "notIntersects"].');
      } else if (condition === cond[2]) {
        diffLayerPromise = this.differenceLayers(layerBId, layerAId);
      }

      diffLayerPromise.then((res) => {
        if (res.hasOwnProperty('diffFeatures')) {
          let jstsGeoJSONReader = new jsts.io.GeoJSONReader();
          res.diffFeatures.forEach((diff) => {
            if (!cond.includes(condition)) {
              reject('The comparison condition is set incorrectly. It must be ["contains", "intersects", "notIntersects"].');
            }

            let layerFeatures = res.layerB;
            if (condition === cond[2]) {
              layerFeatures = res.layerA;
            }

            let crs = res.layerA[0].options.crs;
            let coordsToLatLng = function(coords) {
              return crs.unproject(L.point(coords));
            };

            let featureLayer = L.geoJSON(diff.feature, { coordsToLatLng: coordsToLatLng.bind(this) }).getLayers()[0];
            let object = {
              areaDifference: diff.area,
              objectDifference: featureLayer
            };

            layerFeatures.every((feat) => {
              let jstsFeat = jstsGeoJSONReader.read(feat.feature);
              if (jstsFeat.geometry.isValid()) {
                switch (condition) {
                  case cond[0]:
                    if (jstsFeat.geometry.contains(diff.jstsGeometry)) {
                      object.id = jstsFeat.properties.primarykey;
                      return false;
                    } else {
                      return true;
                    }

                    break;
                  case cond[1]:
                    if (jstsFeat.geometry.intersects(diff.jstsGeometry) && !jstsFeat.geometry.contains(diff.jstsGeometry)) {
                      object.id = jstsFeat.properties.primarykey;
                      return false;
                    } else {
                      return true;
                    }

                    break;
                  case cond[2]:
                    if (jstsFeat.geometry.intersects(diff.jstsGeometry)) {
                      object.id = jstsFeat.properties.primarykey;
                      return false;
                    } else {
                      return true;
                    }

                    break;
                  default:
                    return true;
                }
              } else {
                return true;
              }
            });

            if (showOnMap) {
              let serviceLayer = this.get('mapApi').getFromApi('serviceLayer');
              featureLayer.addTo(serviceLayer);
            }

            result.pushObject(object);
          });
          resolve(result);
        } else {
          reject(res);
        }
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Exponentiation.
    @method _pointAmplifier
    @return {Number} 100000000.
  */
  _pointAmplifier() {
    return Math.pow(10, 8);
  },

  /**
    Transform coordinates in points.
    @method _coordsToPoints
    @param {Array} polygons Array of coordinates.
    @return {Array} Array of points.
  */
  _coordsToPoints(polygons) {
    let amp = this._pointAmplifier();
    if (Array.isArray(polygons[0]) || (!(polygons instanceof L.LatLng) && (polygons[0] instanceof L.LatLng))) {
      let coords = [];
      for (let i = 0; i < polygons.length; i++) {
        coords.push(this._coordsToPoints(polygons[i]));
      }

      return coords;
    }

    return { X: Math.round(polygons.lng * amp), Y: Math.round(polygons.lat * amp) };
  },

  /**
    Transform points in coordinates.
    @method _pointsToCoords
    @param {Array} points Array of points.
    @return {Array} Array of coordinates.
  */
  _pointsToCoords(points) {
    let amp = this._pointAmplifier();
    if (Array.isArray(points[0]) || (!(points instanceof ClipperLib.IntPoint) && (points[0] instanceof ClipperLib.IntPoint))) {
      let coord = [];
      for (let i = 0; i < points.length; i++) {
        coord.push(this._pointsToCoords(points[i]));
      }

      // closing polygon
      if (!Array.isArray(coord[0][0])) {
        let first = coord[0];
        coord.push(first);
      }

      return coord;
    }

    return [points.X / amp, points.Y / amp];
  },

  _requestDownloadFile(layerModel, objectIds, outputFormat, crsOuput, crsLayer, url) {
    return new Ember.RSVP.Promise((resolve, reject) => {
      downloadFile(layerModel, objectIds, outputFormat, crsOuput, crsLayer, url).then((res) => {
        resolve(res);
      }).catch((e) => {
        reject(e);
      });
    });
  },

  /**
    Download file.
    @method downloadFile
    @param {String} layerId.
    @param {Array} objectIds.
    @param {String} outputFormat.
    @param {String} crsName.
    @param {boolean} isFile flag indicates if return file or blob. By default 'true'.
    @return {Promise} Object consist of fileName and blob. If isFile = true then returns file too.
  */
  downloadFile(layerId, objectIds, outputFormat, crsName, isFile = true) {
    let config = Ember.getOwner(this).resolveRegistration('config:environment');
    let url = config.APP.backendUrls.featureExportApi;

    return new Ember.RSVP.Promise((resolve, reject) => {
      const layerModel = this.get('mapLayer').findBy('id', layerId);
      if (Ember.isNone(layerModel)) {
        reject(`Layer '${layerId}' not found.`);
      }

      let crsOuput = getCrsByName(crsName, this);
      let crsLayer = getCrsByName(layerModel.get('crs').code, this);
      this._requestDownloadFile(layerModel, objectIds, outputFormat, crsOuput, crsLayer, url).then((res) => {
        if (isFile) {
          downloadBlob(res.fileName, res.blob);
        }

        resolve(res);
      }).catch((e) => {
        reject(e);
      });
    });
  },

  setLayerFilter(layerId, filter) {
    let layerModel = this.getLayerModel(layerId);

    if (Ember.isNone(layerModel)) {
      return;
    }

    layerModel.set('filter', filter);
  }
});

redpajama_set_name: RedPajamaGithub


A genuine pair of reclaimed Staddle stones - mushroom stones. Recently removed from a local farm, these are nice and tall and have the original square tops on. A slight bit of stone loss to one side of the right hand side stone , this is from years ago very well weathered and look superb tops are approx 43 cm square and the bases are approx 33 sq.


Bead 2 Bead PROTEK : Bead to bead reinforcement with an overall protection of the tire casing.
X-Miles Compound: New mix of exclusive gums improving tire wear.
Gravel Design: Sculpture with small studs providing a low rolling resistance and an excellent grip on mixed terrains.
Tubeless Ready for a better impermeability, rolling efficiency and puncture resistance.
I bought this tyre in 40 mm size which is my preferred size for the sort of gravel I ride on. I live on a gravel and regularly ride from here on a variet of surfaces. The Michelin has a nicely rounded profile and a very supple ride. It's tread is made up of numerous small blocks which offer sufficient grip to cope with a range of gravel from small through to small rounds. On sealed roads it rolls well and offers a good compromise for different surfaces. The tyre is tubeless ready and a tight fit on the rim. It copes well with a range of pressures and I have run it between 35 and 60 psi. Overall I think this is a good tyre at a good price.
Nice width & rounded profile, very nice combo of gravel & snow grip vs low rolling resistance . Only wonder about quality of casing, can't get it to centre on the rim despite attempts at letting out all air & re-inflating, still has hops & wobbles (radial & axial run out). Not noteciable on gravel & snow, but a concern on smooth pavement at higher speed.
Put a 700x35 on my gravel bike and these are sure nice tires. Easy to install tubeless. I was able to roll these onto my rims with my hands and then using my floor pump got them to seat. Riding on the road feels really good so far. No soft drag from these. Excellent traction on the trails and fire roads. I would say that the 700x35 runs a bit smaller. I have a 700x33 and plan to put that on the front. Not sure yet how these will hold up but with such a good price at the Probikekit, it would make sense to change them often.
I bought this tire hopefull that I could use them tubeless on a tubed rim. I had struggled for years getting tires to seal on these wheels with tires that were tubeless ready. I could tell right away these were different and fit tight on the wheels. Tape was used on the wheels and boy do they hold pressure. They ride great. I have put 200 gravel miles on them in two weeks with zero issues.
Surprisingly fast tires that are equally good on pavement or gravel. I am hoping that they are durable to withstand commuting on pavement without wearing down the knobbies at an accelerated rate. The comfort is a big upgrade from previous 32c tires. Very difficult to install on my CaadX stock wheels. Very tight fit.


You need fats! But more importantly you need the good fats that are essential to your health and longevity. How much you need is dependant on your lifestyle and health, but the 'low-fat' craze has potentially caused more people to become obese and unwell than any other diet craze I am aware of!
Fats are used as one of your body's primary sources of fuel, in particular for muscular work. The body has the capacity to store more fat as fuel than carbohydrates. This is part of our genetic make-up that helped us survive in times of feast or famine – when there wasn't a plentiful supply of food, the body used its stored fat for energy.
If you are limiting your healthy fat intake, your metabolism gets confused and will do all sorts of things to make up for this 'lack' and will result in potentially unhealthy side effects.
Fats are also needed to cushion your body's organs and joints, to provide protection against extreme heat or cold, to transport fat-soluble nutrients, and are the building blocks of the human cells.
You've most likely heard of trans-fats or trans fatty acids (TFAs). TFA's are produced when oils are heated to very high temperatures (such as pan frying or deep frying) or produced by hydrogenation, which turns liquid oil into a harder form like shortening and margarine. Quite simply, the natural, healthy oil in its liquid state is changed on a molecular level by heat or hydrogenation, thus becoming a TFA and harmful to our bodies. TFAs can negatively impact normal heart function, by breaking down cell membranes (allowing harmful molecules to penetrate and damage cells), and by interfering with certain enzyme systems.
You've read above that hardened fats are a source of the 'toxic' TFA's. That's exactly what margarine is. My recommendation is to throw away any margarine you have and never to buy it again. If you choose to use butter, it is a healthier choice than margarine but it still has some drawbacks. The good news: the drawbacks of butter are not toxic. Butter is churned to become solid and not hydrogenated. However, butter is a saturated fat and if you are trying to lose body fat, a better choice would be to replace butter with olive oil, canola oil or flax seed oil.
Essential Fatty Acids (EFAs) are called Essential because our body can't produce them so we have to get EFAs from food sources and/or supplements.
There are two essential fatty acids important to cardiovascular health: alpha-linolenic acid (omega-3 fatty acid) and linoleic acid (omega-6 fatty acid).
Most of us do not get enough omega 3 fatty acids in our diet. The best sources for omega-3s are from flax, hemp and fish oils (cold water fish like salmon and herring). The omega-6 fatty acids come from a number of sources including hemp, sunflower, pumpkin and extra-virgin olive oil.
The omega-3s are necessary for optimal health and wellness. They help support cardiovascular health, they nourish your brain, lubricate your joints, and metabolize fat stores for energy (Yes! Omega-3s help your body burn stored fat), assist in tissue repair, and they're an excellent source of 'clean calories' if you are working out to gain lean muscle weight.
There are a number of recommendations for how much of the omega-3 fatty acids to consume in a day. Udo Erasmus in "Fats that Heal, Fats that Kill", recommends a minimum of 1 teaspoon to a tablespoon per 50 pounds of body weight per day. The higher dose will work for some people depending on their nutritional and health needs. Consult with your doctor or nutritionist to best determine your needs.
You can add flax to your shake, smoothie or pour onto your veggies, brown rice, etc., after they have been cooked. Never cook flax oil as it has a low burn temperature and if you burn it, it becomes toxic.
Olive oil is an example of an omega-6 fatty acid. You've probably heard of the Mediterranean diet. Numerous studies have shown that the Mediterranean region has one of the lowest rates of coronary heart disease. There are a number of reasons for the health and longevity of the people in this region, however the defining component of this diet is the abundant use of olives and olive oil. Olives and olive oil have been associated with a variety of health benefits, including lower incidence of heart disease and certain cancers.
For your everyday cooking, olive oil is a healthy choice. Another factor in olive oil's favour is that it has a high burn temperature, meaning if you fry food you can cook with olive oil at a much higher temperature before the oil burns.


text:
» Why Defendants Should Never Negotiate Their Own Plea Deals
Why Defendants Should Never Negotiate Their Own Plea Deals
On Behalf of BSS Law, LLC | Mar 29, 2022 | Criminal Defense
When a defendant is charged with a crime, they are given the opportunity to have their case heard by a jury. If possible, defendants should insist on going to trial because plea bargains are typically considered sub-optimal strategies when compared to the normally obtainable outcome in a criminal case. Read on for further details and advice on why you should never negotiate plea deals on your own.
What Is a Plea?
The first thing to know is what a plea bargain is. A plea bargain is an agreement between the defendant and the prosecutor, wherein the defendant pleads guilty in exchange for a lesser charge or less time in prison. In other words, the defendant agrees to plead guilty to something that isn't necessarily true or at least isn't entirely true in exchange for something that he or she wants.
Plea bargaining has many advantages for both defendants and prosecutors. Obviously, it saves time and money for everyone involved because there's no trail. And because most criminal cases never go to trial, it's also very effective at getting people into jail without having to hear their side of the story.
Why You Should Never Get Into a Plea on Your Own
Negotiating Experience
Negotiating with prosecutors is difficult, especially if you're not sure what to say or how to say it. Prosecutors are highly trained and know how to get defendants to accept unfavorable plea bargains, but an experienced criminal defense attorney knows all the tricks and can help you avoid falling into common traps.
Informed Decision Making
Defendants who do not have legal experience often misunderstand what they're being charged with, which means they can't always make informed decisions about whether going to trial or negotiating a plea bargain would be in their best interests. While your criminal defense attorney will explain everything in detail, he or she will also provide you with advice regarding whether accepting a plea deal would be in your best interests or whether going to trial is more likely to result in a favorable outcome.
You Might Get Sentenced to More Time than You'd Get at Trial
Many defendants overestimate how much time they'd spend in prison if a jury found them guilty at trial. In reality, juries are reluctant to send people away for long prison sentences unless the evidence against them is overwhelming. This is particularly true if their crime was nonviolent and didn't involve drugs or alcohol.
You May Not Know All the Evidence Against You
Prosecutors will only disclose so much evidence before a defendant pleads guilty. Depending on the state, this may be "Brady material" only — evidence that could clear the defendant or reduce their sentence — or it may include other evidence such as police reports. If there is evidence that could be used to clear your name or reduce your sentence, but it isn't disclosed, you may wind up pleading guilty and accepting a harsher penalty than necessary.
In short, the answer to this question is yes: defendants are better off hiring attorneys to negotiate their plea deals for them. They will be able to protect their interests throughout the process and get a much better outcome in the end.
The problems with eyewitness testimony
Five Common Mistakes People Make When Getting Arrested for DUI
Can a DUI Affect Your Ability to Get a Commercial Driver's License
If You've Been Convicted of Drug Possession Here's How You Get Access to Treatment Rather Than Jail
Six Things an Attorney Can Do for You After You've Been Charged with A Crime

redpajama_set_name: RedPajamaCommonCrawl


text:
HomeBlogNewsMarvel: What Captain America&#...
Marvel: What Captain America's Full Pokémon Team Would Be | CBR
It's time to combine super-powered people with super-powered creatures. This clash of cultures combines the First Avenger with the wonderful world of Pokémon to see exactly which Pokémon would fit the Star-Spangled Hero. Like his fighting style, a team of powered critters serving Captain America would be as diverse as possible.
RELATED: DC: What Flash's Full Pokémon Team Would Be
With the ability to assess the situation and figure out the best recourse, someone like Steve Rogers would almost demand a team that could handle almost any situation. Though that doesn't mean he cannot add his own personal touches while choosing who best to serve him to bring him all the way to the top of the Pokémon League or wherever else his power will take him. Let's see what sort of Pokémon Captain America would bring with him if he were to find himself in the fantastical world of Pokémon.
6 Hitmonchan
If there is one person on the Avengers that would appreciate a good boxer, it is good ol' Steve Rogers since boxing is one of the prime components of his unique combat style. If anything, Hitmonchan would at least be one hell of a training partner for the best hand-to-hand fighter on the Avengers.
Otherwise, he is just a great Pokemon to have. Hitmonchan is one of the few Pokémon that can learn all three of the elemental punches giving it a high chance to strike a Pokemon's weakest point. Even better, it can come with the Iron Fist ability giving it another boost to its already potent punching power!
5 Arcanine
Other than being a good boy, there is a number of reasons why Steve Rogers would enjoy having an Arcanine on his team. Not only is this creature a classic since its one of the original 151 from the first game, but its bravery and loyalty are only matched by few other Pokemon.
Also, legend has it that an Arcanine once fought alongside a general and conquered a whole country. It is hard to think of a better military leader in fiction for Arcanine to try and do that all over again with. Though this time, they won't be conquering a country, but the Pokémon League itself.
4 Bastiodon
It would be hard for people to tell why Captain America would like this sort of Pokémon if the answer wasn't staring them right in the face. It is a pretty good guess that Cap himself would find a good way to use a Pokémon that has a shield for a face.
RELATED: 10 Marvel Heroes (& Who Their Pokémon Partner Would Be)
Said to be able to deflect virtually any attack, all it needs is a red, white, and blue shiny version, and it could downright replace his shield. Docile in nature, but more than tough on the outside, the rock and steel type would be an excellent choice to his already varied team.
3 Aegislash
Like Bastoidon, there is an excellent reason why Captain America would be attracted to it, and it again would be staring right at him. Unlike Bastoidon, Captain America might not find this Pokémon. Instead, Aegislash would probably find him.
Aegislash has the innate ability to detect qualities of leadership in people, and what better beacon of leadership than the person in charge of Earth's Mightiest Heroes. Said to have served leaders for generations, it wouldn't be far fetched to believe that Aegislash would just show up in front of Steve Rogers one day instead of him having to capture it. Aegislash would think Cap would be as worthy of him as he is of Mjölnir, and would then follow him along on his journey.
2 Mamoswine
It might not seem like the type of Pokémon for Captain America at first, but in reality, they have a lot in common. Both of these old soldiers come from a distant past and are now flung into a present that no longer supports them.
RELATED: DC: What Superman's Full Pokemon Team Would Be
Furthermore, they both seem to have a history with ice. Mamoswine thrived during the ice age and is an ice type pokemon, and Steve Rogers was frozen in ice for decades. Given their similar histories, there is a good chance that they would be able to bond as Master and Pokémon.
1 Braviary
Braviary would be Steve Roger's starter Pokémon if it were at all possible. It is almost like Game Freaks took everything that made Captain America great and decided to compact it into bird form and then gave it a red, white, and blue feathered mane on its head to further drive the point home.
This fierce avian has many similarities to the First Avenger. First off, it is a natural-born fighter. Though while aggressive, it only fights for the benefit of its friends. The fierce bird is often a protector to all those around it, earning itself the nickname "hero of the skies." It also shares Captain America's strength; the bird is able to lift a car clear off the ground if it needs too.
NEXT: DC: What Wonder Woman's Full Pokemon Team Would Be
Marvel's superheroes and Pokémon obviously don't take place in the same universe, but if they did, this would be Captain America's Pokémon team.


"We applaud the FTC for ensuring that victims of Herbalife's fraudulent activities will receive some restitution for their losses. This action is yet another sign that, despite statements to the contrary, it is no longer business as usual for the direct selling industry. We strongly urge the FTC and state regulators to hold Herbalife's and the entire direct selling industry's feet to the fire to ensure that consumers are protected from pyramid scheme behavior."


text:
Switzerland: Gambling Laws and Regulations 2021
ICLG - Gambling Laws and Regulations - Switzerland covers common issues in gambling laws and regulations – including relevant authorities and legislation, application for a licence, licence restrictions, digital media, enforcement and liability – in 37 jurisdictions.
ICLG.com > Practice Areas > Gambling > Switzerland
Chapter Content Free Access
1. Relevant Authorities and Legislation
2. Application for a Licence and Licence Restrictions
3. Online/Mobile/Digital/Electronic Media
4. Enforcement and Liability
5. Anticipated Reforms
1.1 Which entities regulate what type of gambling and social/skill gaming activity in your jurisdiction?
Who regulates it in digital form?
Who regulates it in land-based form?
Casino gaming (including slots and casino table games such as roulette & blackjack)
The Swiss Federal Gaming Board ("SFGB"), based on the Money Gaming Act ("MGA").
SFGB.
Small Poker tournaments (neither automated nor intercantonal): the cantonal authorities.
Comlot.
Automated and/or intercantonal: Comlot.
Small games (neither automated nor intercantonal): cantonal authorities.
Small games (neither automated nor intercantonal): the cantonal authorities.
Sports/horse race betting (if regulated separately to other forms of betting)
Fantasy betting (payment to back a 'league' or 'portfolio' selection over a period of time, for example in relation to sport or shares)
Switzerland does not have a special regulatory regime for fantasy betting, e-gaming and social gaming. It must be decided on a case-by-case basis if a bet/game qualifies as a casino, a large-scale or a small-scale game under the gaming regulations. Dependent on that decision, SFGB, Comlot or the cantonal authority is the competent regulatory body.
Small games: cantonal authorities.
Social/Skill arrangements
"Social" gaming with no prize in money or money's worth
Social games with no prize in money/money's worth are not considered gaming in the sense of the Swiss gaming regulations.
Skill games and competitions with no element of chance
Skill games that are not automated, intercantonal or online are not subject to the MGA.
1.2 Specify: (i) the law and regulation that applies to the Relevant Products in your jurisdiction; and (ii) – in broad terms – whether it permits or prohibits the offer of Relevant Products to persons located in your jurisdiction.
Since 1 January 2019, casino games, lotteries, sports betting and skill games are regulated in the MGA and the Ordinance on Money Games ("MGO"). These laws generally regulate money games in which there is the chance to win a prize or other monetary advantage in return for a stake/monetary transaction to enter the game. The MGA distinguishes between casino games, lotteries, sports betting and skill games. Lotteries, sports betting and skill games can be categorised into large-scale (executed automatically, intercantonal or online) and small-scale games (neither automated, intercantonal nor online) (Art. 2 MGA).
Licensed land-based casinos can apply for an extension of their licence to offer online gaming. Licences for (online) betting and lotteries will only be given to Swisslos and Loterie Romande. Foreign providers cannot apply for a licence and their websites are blocked (DNS-blocking) by the Swiss authorities if the provider does not block the access to the games from Switzerland itself.
2.1 What regulatory licences, permits, authorisations or other official approvals (collectively, "Licences") are required for the lawful offer of the Relevant Products to persons located in your jurisdiction?
Swiss law distinguishes between terrestrial casino licences and the extension of the terrestrial licences for online gaming, lottery and betting licences and licences for small games (not executed automatically, online or intercantonal).
2.2 Where Licences are available, please outline the structure of the relevant licensing regime.
Casino licence
A-type casino: no limits in stakes and the number of offered games and slot machines, connected jackpots and maximum winnings possible. Only casinos with a licence "A" qualify as Grand Casino (Art. 6 MGA).
B-type casino: usually for spa or resort casinos, with a limited number of table games (three) and slot machines, limited stakes, and jackpots may not be connected. The limit per stake for automated games in B-type casinos is CHF 25/game.
The number of licences is determined by the Swiss Federal Council ("SFC"), whereby the locations are distributed evenly among interested regions (Art. 7 MGA). A licence is usually granted for a period of 20 years and can be renewed (Art. 12 MGA). The licence can be extended for the offering of online games (Art. 9 MGA).
The concession is not transferable (Art. 14 MGA).
Large-scale games (lotteries, sports betting and skill games which are executed automatically, intercantonal or online)
In order to be able to offer large-scale matches, an organiser's licence and a gaming licence are required. These are issued by Comlot, with the cantons deciding on the maximum number of lottery and sports betting operators (Art. 23 MGA). Only Swisslos and Loterie Romande receive licences for large-scale games. The licence is not transferable to third parties (Art. 30 MGA).
Small game licence (lotteries, sports betting and skill games which are executed neither automatically, online nor intercantonally)
Small-scale games operators can apply for a cantonal licence with the competent cantonal authority. There are separate regulations concerning small lotteries and sports betting in the cantons.
2.3 What is the process of applying for a Licence for a Relevant Product?
The SFC decides on the maximum number of terrestrial casino licences that may be granted, and also defines the geographical locations of such casinos.
The written application for the casino licence must be submitted to SFGB. SFGB reviews the application and submits a proposal to the SFC. The SFC decides whether or not to grant a licence. Casinos holding a Swiss casino licence can apply for an extension of their licence to offer online games. The process of the application is the same as for a terrestrial casino licence.
Applicants of large-scale games must submit their application to Comlot (only Swisslos and Loterie Romande can receive a licence) and operators of small games must submit theirs to the cantonal authority.
2.4 Are any restrictions placed upon licensees in your jurisdiction?
Licensees may only provide casino games and large-scale/small games services within the scope of (i) the licence obtained, and (ii) the applicable regulations. Licences contain a wide range of restrictions with regard to the games that may be offered, how these games must be organised, what form and in which amounts payments may be accepted and how the marketing, social concept and security as well as AML procedures are organised. If the licensee fails to comply with the regulations, SFGB, Comlot or the cantonal authority may cancel or impose restrictions on the licensee.
2.5 Please give a summary of the following features of any Licences: (i) duration; (ii) vulnerability to review, suspension or revocation.
A casino licence is usually granted for a period of 20 years (Art. 12 MGA). After the 20-year period has elapsed, the licence can be extended or renewed. In certain circumstances, the licence may be revoked, restricted or suspended. A revocation of the licence is possible if any of the following apply (Art. 15 MGA):
The requirements for issuing the licence are no longer fulfilled.
The licensee has obtained the licence based on incomplete or false information.
The licensee has not started operations within the set time limit by SFGB.
The licensee leaves the business inoperative.
The licence is used for any unlawful or improper purposes.
The licence for large-scale or small games is not subject to a fixed term but can be limited in time and be renewed. In addition, the licences can be subject to conditions and obligations (Art. 29 MGA). If the legal requirements for the licence are no longer given, Comlot (or in cases of small games, the cantonal authority) can withdraw the licence. The licence may also be suspended, restricted or subject to additional conditions and obligations (Art. 31 MGA).
2.6 By Relevant Product, what are the key limits on providing services to customers? Please include in this answer any material promotion and advertising restrictions.
Casino games: the limits on the types of games, stakes and the maximum number of slot machines depend on the type of the licence (A licence or B licence, see question 2.2 above).
Large-scale lotteries: may only be offered by Swisslos or Loterie Romande.
Small lotteries (cantonal licence): the maximum stake for a single bet is CHF 10, with a maximum of CHF 100,000 for total stakes (Art. 37 MGO, total stakes of CHF 500,000 if the goal of the lottery is to finance an event of supra-regional importance). The value of the prizes must be at least 50% of the maximum total stakes.
Small local sports bets (cantonal licence): maximum stake of CHF 200 per bet, with a maximum of CHF 200,000 for total stakes on one competition day (Art. 38 MGO).
Small poker tournament (cantonal licence): maximum entry fee of CHF 200 and CHF 20,000 total entry fees. There are additional restrictions regarding the number of tournaments, number of participants, duration of tournaments, etc. (Art. 39 MGO).
Tombola: maximum total stake of CHF 50,000 (Art. 40 MGO).
Any advertisement for the commercial offering of games of chance is prohibited if made in an obtrusive way (Art. 74 MGA). Advertising may also not be addressed to minors or persons subject to a ban. Advertising for money games not licensed in Switzerland is prohibited.
Any prohibited promotion can be sanctioned with a fine up to CHF 500,000 (Art. 131 MGA).
2.7 What are the tax and other compulsory levies?
In accordance with the Swiss constitution, a casino's gross revenues are taxed. The collected tax funds flow into Switzerland's pension system.
Terrestrial casinos: the basic tax rate is 40% (up to CHF 10 Mio. GGR). The federal government can change the current level of taxation rates up to 80% (Art. 120 MGA).
Online gaming: the basic tax rate is 20% (up to CHF 3 Mio. GGR) and can be increased up to 80% (Art. 120 MGA).
Winnings from terrestrial casinos are tax-exempt. This also applies to winnings from online casino games and lotteries, sports betting or large games of skill up to CHF 1 Mio., if the games are licensed in Switzerland.
Winnings from small games (not executed automatically, intercantonal or online) are tax-free if they are licensed by the competent cantonal authorities. Winnings from lotteries or games of skill for sales promotions are tax-free up to CHF 1,000. Winnings from foreign casinos, resp. non-licensed games, are fully taxed.
2.8 What are the broad social responsibility requirements?
One of the main objections to the deregulation of the casino market is the assumption that deregulation would result in an increase in gambling addiction. Therefore, applicants must present a social concept, including measures to prevent gambling addiction and strict security policies (Art. 76 MGA).
The social concept of casinos and providers of online games of luck must include the following measures:
information for players about the risks of games, possibilities for self-control, bans, etc.;
early identification of at-risk players;
implementation of bans;
education of personnel; and
data collection on the effectiveness of the measures.
2.9 How do any AML, financial services regulations or payment restrictions restrict or impact on entities supplying gambling? Does your jurisdiction permit virtual currencies to be used for gambling and are they separately regulated?
Casinos are subject to the Federal Act on Combating Money Laundering and Terrorist Financing in the Financial Sectors ("AMLA") and are considered as financial intermediaries. SFGB has issued an ordonnance to clarify the application of the AMLA for casinos. In addition, the majority of the licensed casinos are members of the Self-Regulating Organisation, which sets the AML standards for its members.
Financial intermediaries under the AMLA must comply with different duties of due diligence. For example, casinos must verify the identity of the customer on the basis of an identification document in one of the following situations:
When they enter a casino.
When they reach a certain threshold.
When they establish a certain business relationship (accounts or depots).
In addition, casinos must report any suspicion of money laundering immediately and respect the criminal provisions of the AMLA. A violation of provisions of the AMLA may lead to a revocation of the casino licence. Lottery companies are not yet considered as financial intermediaries. The Federal Justice and Police Department has issued an ordonnance to specify AML requirements for providers of large games (intercantonal, automated or online lotteries, sports bets and games of skill).
Virtual currencies: neither the MGA nor the MGO have imposed any restrictions on virtual currencies.
3.1 How does local law/regulation affect the provision of the Relevant Products in online/mobile/digital/electronic form, both from: (i) operators located inside your jurisdiction; and (ii) operators located outside your jurisdiction?
There is no clear definition of online gambling. However, the chain of distribution to the customer is significant in determining whether a game is classified as remote gaming or online gaming. Since 1 January 2019, existing casinos with a Swiss licence can apply for an online licence.
Offering non-licensed online games within Switzerland is prohibited. Foreign, non-licensed operators who offer online cash games of luck to Swiss players may be foreclosed from the Swiss market through the introduction of DNS blocking measures to be implemented by the Internet access providers (Art. 86 MGA).
However, foreign providers will be able to cooperate with Swiss casinos in order to offer their online services legally in Switzerland. The cooperation will, amongst other requirements, only be approved if the cooperation partner has a "good reputation".
3.2 What other restrictions have an impact on Relevant Products supplied via online/mobile/digital/electronic means?
The access to online games requires a gaming account with the operator. To be able to open an account, a player must be over 18 years old, Swiss-resident and not be banned from gaming. The provider of online games must identify the players. Winnings from licensed gambling can only be transferred to accounts in the name of the player (Art. 45 ss. MGO).
3.3 What terminal/machine-based gaming is permitted and where?
Gaming machines that involve a predominant element of skill can be operated outside casinos, if permitted by cantonal law (Art. 106 para. 4 Federal Constitution). Cantonal permission can only be granted if the gaming machine has been approved by SFGB as skill-based.
There are two main categories of slot machines:
Gaming machines, which have entertainment as their sole purpose (such as table football, pinball and any kind of sports simulators).
Slot machines, which give the player an opportunity to win money or other prizes of monetary value (such as points, chips or goods).
The rules for the control and construction of the slot machines are set forth in the Casino Ordonnance. The MGO contains restrictions as to the stakes for slot machines according to the type of the casino licence (A or B).
4.1 Who is liable under local law/regulation?
The following parties are liable for breaches of the relevant legislation:
the casino licensee;
the customer itself;
the large-scale/small game licensee; and
the gaming service operator and supporting third parties.
4.2 What form does enforcement action take in your jurisdiction?
The licence may be withdrawn from domestic casinos or large-scale operators. In addition, prison sentences and high fines up to CHF 500,000 can be imposed (Art. 131 MGA).
Websites of foreign gaming providers can be blocked and the provider will be listed on a public blacklist (Art. 86 MGA).
4.3 Do other non-national laws impact upon liability and enforcement?
No, there are no other non-national laws that have an impact upon liability and enforcement. Switzerland is not a member of the European Union; therefore, EU law is not applicable.
4.4 Are gambling debts enforceable in your jurisdiction?
In general, under Swiss law, gambling and betting debts do not give rise to a claim (non-actionable claim; Art. 513 of the Swiss Code of Obligations [SR 220]).
A claim may arise if the claim arose during a licensed lottery game or during a game in a casino licensed by the competent authority (Art. 515 of the Swiss Code of Obligations).
4.5 What appetite for and track record of enforcement does your local regulatory authority have? Have fines, licence revocations or other sanctions been enforced in your jurisdiction?
SFGB imposed fines in the amount of CHF 1.5 Mio. and demanded CHF 500,000 compensation for illegally earned profits (based on the gambling legislation in force until 31 December 2018). SFGB did not enforce other (administrative) sanctions.
In 2018, Comlot opened 78 cases for suspected violations of the lottery legislation (based on the lottery legislation in force until 31 December 2018).
The MGA came into force on 1 January 2019 and the first six online licences were granted in June and November 2019.
During the second half of the year SFGB and Comlot have each published three blacklists of blocked websites of foreign gaming providers. In addition, SFGB instituted 108 criminal proceedings for illegal gaming.
In April 2020, two more licences for the offering of online games were granted. SFGB and Comlot have published further blacklists since the beginning of the year.
It remains to be seen whether the blocking measures, the new player protection measures and the new provisions to ensure secure and transparent gaming operations will prove their worth in practice.
5.1 What (if any) intended changes to the gambling law/regulations are being discussed currently?
Since a legal reform has just taken place and the new MGA and the MGO came into force on 1 January 2019, no further reform is pending for the time being.
Read this chapter FREE
To download this chapter free
Register or log in here
Buy the Book in PDF format£295
MME Legal | Tax | Compliance
Other Switzerland Chapters
Class and Group Actions
The write up is extremely useful as it offers a bird's eye view of different jurisdictions. The comparative tool is remarkable. Thank you for an amazing initiative.
Ms V.Sewlal - Master of the North Gauteng High Court


Residents from Carshalton and Wallington are being urged by their local MP, Tom Brake, to make time for their health by taking part in Blood Pressure UK's 'Know Your Number!' Week from the 14th to the 20th September 2015.
A surveyfrom the charity Blood Pressure UK has revealed that one in three people haven't had their blood pressure checked in the last year as they don't think high blood pressure affects them. Yet just a couple of minutes spent thinking about health could save their life.
The independent survey of over 1000 Britons also showed that out of those people who hadn't had their blood pressure taken in the last year, 25% don't even consider it as a health issue.
The Know Your Numbers! campaign is the UK's biggest blood pressure testing event. It is in its fourteenth year, and this year aims to highlight that in the time it takes to boil a kettle, you could have taken a blood pressure reading which might save your life. A shocking 16 million people in the UK suffer from high blood pressure, which is the biggest risk factor for stroke and heart attacks, yet 63% did not know their blood pressure numbers.
To find your nearest check visit:http://www.bloodpressureuk.org/microsites/kyn/Home/Freebpchecks/Findfreecheck. If your local pharmacy is not on the list, check with your pharmacist whether they can carry out the test.