"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = exports.schemeTiles = exports.globalExtentTMS = void 0;

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var THREE = _interopRequireWildcard(require("three"));

var _Coordinates = _interopRequireDefault(require("./Coordinates"));

var _Crs = _interopRequireDefault(require("./Crs"));

/**
 * Extent is a SIG-area (so 2D)
 * It can use explicit coordinates (e.g: lon/lat) or implicit (WMTS coordinates)
 */
var _dim = new THREE.Vector2();

var _dim2 = new THREE.Vector2();

var _countTiles = new THREE.Vector2();

var tmsCoord = new THREE.Vector2();
var dimensionTile = new THREE.Vector2();
var defaultScheme = new THREE.Vector2(2, 2);
var r = {
  row: 0,
  col: 0,
  invDiff: 0
};

function _rowColfromParent(extent, zoom) {
  var diffLevel = extent.zoom - zoom;
  var diff = Math.pow(2, diffLevel);
  r.invDiff = 1 / diff;
  r.row = (extent.row - extent.row % diff) * r.invDiff;
  r.col = (extent.col - extent.col % diff) * r.invDiff;
  return r;
}

var _extent;

var _extent2;

var cardinals = new Array(8);

for (var i = cardinals.length - 1; i >= 0; i--) {
  cardinals[i] = new _Coordinates["default"]('EPSG:4326', 0, 0, 0, 0);
}

var _c = new _Coordinates["default"]('EPSG:4326', 0, 0);

var globalExtentTMS = new Map();
exports.globalExtentTMS = globalExtentTMS;
var schemeTiles = new Map();
exports.schemeTiles = schemeTiles;

function getInfoTms(crs) {
  var epsg = _Crs["default"].formatToEPSG(crs);

  var globalExtent = globalExtentTMS.get(epsg);
  var globalDimension = globalExtent.dimensions(_dim2);

  var tms = _Crs["default"].formatToTms(crs);

  var sTs = schemeTiles.get(tms) || schemeTiles.get('default'); // The isInverted parameter is to be set to the correct value, true or false
  // (default being false) if the computation of the coordinates needs to be
  // inverted to match the same scheme as OSM, Google Maps or other system.
  // See link below for more information
  // https://alastaira.wordpress.com/2011/07/06/converting-tms-tile-coordinates-to-googlebingosm-tile-coordinates/
  // in crs includes ':NI' => tms isn't inverted (NOT INVERTED)

  var isInverted = !tms.includes(':NI');
  return {
    epsg: epsg,
    globalExtent: globalExtent,
    globalDimension: globalDimension,
    sTs: sTs,
    isInverted: isInverted
  };
}

function getCountTiles(crs, zoom) {
  var sTs = schemeTiles.get(_Crs["default"].formatToTms(crs)) || schemeTiles.get('default');
  var count = Math.pow(2, zoom);

  _countTiles.set(count, count).multiply(sTs);

  return _countTiles;
}

var Extent =
/*#__PURE__*/
function () {
  /**
   * Extent is geographical bounding rectangle defined by 4 limits: west, east, south and north.
   * If crs is tiled projection (WMTS or TMS), the extent is defined by zoom, row and column.
   *
   * @param {String} crs projection of limit values.
   * @param {number|Array.<number>|Coordinates|Object} v0 west value, zoom
   * value, Array of values [west, east, south and north], Coordinates of
   * west-south corner or object {west, east, south and north}
   * @param {number|Coordinates} [v1] east value, row value or Coordinates of
   * east-north corner
   * @param {number} [v2] south value or column value
   * @param {number} [v3] north value
   */
  function Extent(crs, v0, v1, v2, v3) {
    (0, _classCallCheck2["default"])(this, Extent);
    this.crs = crs; // Scale/zoom

    this.zoom = 0;

    if (this.isTiledCrs()) {
      this.row = 0;
      this.col = 0;
    } else {
      this.west = 0;
      this.east = 0;
      this.south = 0;
      this.north = 0;
    }

    this.set(v0, v1, v2, v3);
  }
  /**
   * Clone this extent
   * @return {Extent} cloned extent
   */


  (0, _createClass2["default"])(Extent, [{
    key: "clone",
    value: function clone() {
      if (this.isTiledCrs()) {
        return new Extent(this.crs, this.zoom, this.row, this.col);
      } else {
        return new Extent(this.crs, this.west, this.east, this.south, this.north);
      }
    }
    /**
     * Return true is tiled Extent (WGS84, PM)
     * @return {boolean}
     */

  }, {
    key: "isTiledCrs",
    value: function isTiledCrs() {
      return this.crs.indexOf('WMTS:') == 0;
    }
    /**
     * get tiled extents convering this extent
     *
     * @param      {string}  crs WMTS, TMS crs
     * @return     {Array<Extent>}   array of extents covering
     */

  }, {
    key: "tiledCovering",
    value: function tiledCovering(crs) {
      if (this.crs == 'EPSG:4326' && crs == 'WMTS:PM') {
        var extents_WMTS_PM = [];

        var extent = _extent.copy(this).as(_Crs["default"].formatToEPSG(crs), _extent2);

        var _getInfoTms = getInfoTms(_Crs["default"].formatToEPSG(crs)),
            globalExtent = _getInfoTms.globalExtent,
            globalDimension = _getInfoTms.globalDimension,
            sTs = _getInfoTms.sTs;

        extent.clampByExtent(globalExtent);
        extent.dimensions(dimensionTile);
        var zoom = this.zoom + 1 || Math.floor(Math.log2(Math.round(globalDimension.x / (dimensionTile.x * sTs.x))));
        var countTiles = getCountTiles(crs, zoom);
        var center = extent.center(_c);
        tmsCoord.x = center.x - globalExtent.west;
        tmsCoord.y = globalExtent.north - extent.north;
        tmsCoord.divide(globalDimension).multiply(countTiles).floor(); // ]N; N+1] => N

        var maxRow = Math.ceil((globalExtent.north - extent.south) / globalDimension.x * countTiles.y) - 1;

        for (var _r = maxRow; _r >= tmsCoord.y; _r--) {
          extents_WMTS_PM.push(new Extent(crs, zoom, _r, tmsCoord.x));
        }

        return extents_WMTS_PM;
      } else {
        var target = new Extent('WMTS:PM', 0, 0, 0);

        var _getInfoTms2 = getInfoTms(this.crs),
            _globalExtent = _getInfoTms2.globalExtent,
            _globalDimension = _getInfoTms2.globalDimension,
            _sTs = _getInfoTms2.sTs,
            isInverted = _getInfoTms2.isInverted;

        var _center = this.center(_c);

        this.dimensions(dimensionTile); // Each level has 2^n * 2^n tiles...
        // ... so we count how many tiles of the same width as tile we can fit in the layer
        // ... 2^zoom = tilecount => zoom = log2(tilecount)

        var _zoom = Math.floor(Math.log2(Math.round(_globalDimension.x / (dimensionTile.x * _sTs.x))));

        var _countTiles2 = getCountTiles(crs, _zoom); // Now that we have computed zoom, we can deduce x and y (or row / column)


        tmsCoord.x = _center.x - _globalExtent.west;
        tmsCoord.y = isInverted ? _globalExtent.north - _center.y : _center.y - _globalExtent.south;
        tmsCoord.divide(_globalDimension).multiply(_countTiles2).floor();
        target.crs = crs;
        target.set(_zoom, tmsCoord.y, tmsCoord.x);
        return [target];
      }
    }
    /**
     * Convert Extent to the specified projection.
     * @param {string} crs the projection of destination.
     * @param {Extent} target copy the destination to target.
     * @return {Extent}
     */

  }, {
    key: "as",
    value: function as(crs, target) {
      _Crs["default"].isValid(crs);

      target = target || new Extent('EPSG:4326', [0, 0, 0, 0]);

      if (this.isTiledCrs()) {
        var _getInfoTms3 = getInfoTms(this.crs),
            epsg = _getInfoTms3.epsg,
            globalExtent = _getInfoTms3.globalExtent,
            globalDimension = _getInfoTms3.globalDimension;

        var countTiles = getCountTiles(this.crs, this.zoom);
        dimensionTile.set(1, 1).divide(countTiles).multiply(globalDimension);
        target.west = globalExtent.west + (globalDimension.x - dimensionTile.x * (countTiles.x - this.col));
        target.east = target.west + dimensionTile.x;
        target.south = globalExtent.south + dimensionTile.y * (countTiles.y - this.row - 1);
        target.north = target.south + dimensionTile.y;
        target.crs = epsg;
        target.zoom = this.zoom;
        return crs == epsg ? target : target.as(crs, target);
      } else if (!crs.includes('WMTS:')) {
        if (this.crs != crs) {
          // Compute min/max in x/y by projecting 8 cardinal points,
          // and then taking the min/max of each coordinates.
          var center = this.center(_c);
          cardinals[0].setFromValues(this.west, this.north);
          cardinals[1].setFromValues(center.x, this.north);
          cardinals[2].setFromValues(this.east, this.north);
          cardinals[3].setFromValues(this.east, center.y);
          cardinals[4].setFromValues(this.east, this.south);
          cardinals[5].setFromValues(center.x, this.south);
          cardinals[6].setFromValues(this.west, this.south);
          cardinals[7].setFromValues(this.west, center.y);
          target.set(Infinity, -Infinity, Infinity, -Infinity); // loop over the coordinates

          for (var _i = 0; _i < cardinals.length; _i++) {
            // convert the coordinate.
            cardinals[_i].crs = this.crs;

            cardinals[_i].as(crs, _c);

            target.north = Math.max(target.north, _c.y);
            target.south = Math.min(target.south, _c.y);
            target.east = Math.max(target.east, _c.x);
            target.west = Math.min(target.west, _c.x);
          }

          target.zoom = this.zoom;
          target.crs = crs;
          return target;
        }

        target.crs = crs;
        target.set(this.west, this.east, this.south, this.north);
        return target;
      }
    }
    /**
     * Return the center of Extent
     * @param {Coordinates} target copy the center to the target.
     * @return {Coordinates}
     */

  }, {
    key: "center",
    value: function center() {
      var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new _Coordinates["default"](this.crs);

      if (this.isTiledCrs()) {
        throw new Error('Invalid operation for WMTS bbox');
      }

      this.dimensions(_dim);
      target.crs = this.crs;
      target.setFromValues(this.west + _dim.x * 0.5, this.south + _dim.y * 0.5);
      return target;
    }
    /**
    * Returns the dimension of the extent, in a `THREE.Vector2`.
    *
    * @param {THREE.Vector2} [target] - The target to assign the result in.
    *
    * @return {THREE.Vector2}
    */

  }, {
    key: "dimensions",
    value: function dimensions() {
      var target = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new THREE.Vector2();
      target.x = Math.abs(this.east - this.west);
      target.y = Math.abs(this.north - this.south);
      return target;
    }
    /**
     * Return true if `coord` is inside the bounding box.
     *
     * @param {Coordinates} coord
     * @param {number} [epsilon=0] - to take into account when comparing to the
     * point.
     *
     * @return {boolean}
     */

  }, {
    key: "isPointInside",
    value: function isPointInside(coord) {
      var epsilon = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
      var c = this.crs == coord.crs ? coord : coord.as(this.crs, _c); // TODO this ignores altitude

      return c.x <= this.east + epsilon && c.x >= this.west - epsilon && c.y <= this.north + epsilon && c.y >= this.south - epsilon;
    }
    /**
     * Return true if `extent` is inside this extent.
     *
     * @param {Extent} extent the extent to check
     * @param {number} epsilon to take into account when comparing to the
     * point.
     *
     * @return {boolean}
     */

  }, {
    key: "isInside",
    value: function isInside(extent, epsilon) {
      if (this.isTiledCrs()) {
        if (this.zoom == extent.zoom) {
          return this.row == extent.row && this.col == extent.col;
        } else if (this.zoom < extent.zoom) {
          return false;
        } else {
          _rowColfromParent(this, extent.zoom);

          return r.row == extent.row && r.col == extent.col;
        }
      } else {
        extent.as(this.crs, _extent);
        epsilon = epsilon == undefined ? _Crs["default"].reasonnableEpsilon(this.crs) : epsilon;
        return this.east - _extent.east <= epsilon && _extent.west - this.west <= epsilon && this.north - _extent.north <= epsilon && _extent.south - this.south <= epsilon;
      }
    }
    /**
     * Return the translation and scale to transform this extent to input extent.
     *
     * @param {Extent} extent input extent
     * @param {THREE.Vector4} target copy the result to target.
     * @return {THREE.Vector4} {x: translation on west-east, y: translation on south-north, z: scale on west-east, w: scale on south-north}
     */

  }, {
    key: "offsetToParent",
    value: function offsetToParent(extent) {
      var target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector4();

      if (this.crs != extent.crs) {
        throw new Error('unsupported mix');
      }

      if (this.isTiledCrs()) {
        _rowColfromParent(this, extent.zoom);

        return target.set(this.col * r.invDiff - r.col, this.row * r.invDiff - r.row, r.invDiff, r.invDiff);
      }

      extent.dimensions(_dim);
      this.dimensions(_dim2);
      var originX = (this.west - extent.west) / _dim.x;
      var originY = (extent.north - this.north) / _dim.y;
      var scaleX = _dim2.x / _dim.x;
      var scaleY = _dim2.y / _dim.y;
      return target.set(originX, originY, scaleX, scaleY);
    }
    /**
     * Return parent tiled extent with input level
     *
     * @param {number} levelParent level of parent.
     * @return {Extent}
     */

  }, {
    key: "tiledExtentParent",
    value: function tiledExtentParent(levelParent) {
      if (levelParent && levelParent < this.zoom) {
        _rowColfromParent(this, levelParent);

        return new Extent(this.crs, levelParent, r.row, r.col);
      } else {
        return this;
      }
    }
    /**
     * Return true if this bounding box intersect with the bouding box parameter
     * @param {Extent} extent
     * @returns {Boolean}
     */

  }, {
    key: "intersectsExtent",
    value: function intersectsExtent(extent) {
      // TODO don't work when is on limit
      var other = extent.crs == this.crs ? extent : extent.as(this.crs, _extent);
      return !(this.west >= other.east || this.east <= other.west || this.south >= other.north || this.north <= other.south);
    }
    /**
     * Return the intersection of this extent with another one
     * @param {Extent} extent
     * @returns {Boolean}
     */

  }, {
    key: "intersect",
    value: function intersect(extent) {
      if (!this.intersectsExtent(extent)) {
        return new Extent(this.crs, 0, 0, 0, 0);
      }

      if (extent.crs != this.crs) {
        extent = extent.as(this.crs, _extent);
      }

      return new Extent(this.crs, Math.max(this.west, extent.west), Math.min(this.east, extent.east), Math.max(this.south, extent.south), Math.min(this.north, extent.north));
    }
    /**
     * Set west, east, south and north values.
     * Or if tiled extent, set zoom, row and column values
     *
     * @param {number|Array.<number>|Coordinates|Object} v0 west value, zoom
     * value, Array of values [west, east, south and north], Coordinates of
     * west-south corner or object {west, east, south and north}
     * @param {number|Coordinates} [v1] east value, row value or Coordinates of
     * east-north corner
     * @param {number} [v2] south value or column value
     * @param {number} [v3] north value
     *
     * @return {Extent}
     */

  }, {
    key: "set",
    value: function set(v0, v1, v2, v3) {
      if (this.isTiledCrs()) {
        if (v0 !== undefined) {
          if (this.zoom < 0) {
            throw new Error('Invalid zoom value for tiled extent');
          }

          this.zoom = v0;
          this.row = v1;
          this.col = v2;
        } else {
          throw new Error('Invalid values to set');
        }
      } else if (v0.isCoordinates) {
        // seem never used
        this.west = v0.x;
        this.east = v1.x;
        this.south = v0.y;
        this.north = v1.y;
      } else if (v0 && v0.west !== undefined) {
        this.west = v0.west;
        this.east = v0.east;
        this.south = v0.south;
        this.north = v0.north;
      } else if (v0 && v0.length == 4) {
        this.west = v0[0];
        this.east = v0[1];
        this.south = v0[2];
        this.north = v0[3];
      } else if (v0 !== undefined) {
        this.west = v0;
        this.east = v1;
        this.south = v2;
        this.north = v3;
      } else {
        throw new Error('No values to set in the extent');
      }

      return this;
    }
    /**
     * Copy to this extent to input extent.
     * @param {Extent} extent
     * @return {Extent} copied extent
     */

  }, {
    key: "copy",
    value: function copy(extent) {
      this.crs = extent.crs;
      return this.set(extent);
    }
    /**
     * Union this extent with the input extent.
     * @param {Extent} extent the extent to union.
     */

  }, {
    key: "union",
    value: function union(extent) {
      if (extent.crs != this.crs) {
        throw new Error('unsupported union between 2 diff crs');
      }

      if (this.west === Infinity) {
        this.copy(extent);
      } else {
        var west = extent.west;

        if (west < this.west) {
          this.west = west;
        }

        var east = extent.east;

        if (east > this.east) {
          this.east = east;
        }

        var south = extent.south;

        if (south < this.south) {
          this.south = south;
        }

        var north = extent.north;

        if (north > this.north) {
          this.north = north;
        }
      }
    }
    /**
     * expandByCoordinates perfoms the minimal extension
     * for the coordinates to belong to this Extent object
     * @param {Coordinates} coordinates  The coordinates to belong
     */

  }, {
    key: "expandByCoordinates",
    value: function expandByCoordinates(coordinates) {
      var coords = coordinates.crs == this.crs ? coordinates : coordinates.as(this.crs, _c);
      this.expandByValuesCoordinates(coords.x, coords.y);
    }
    /**
    * expandByValuesCoordinates perfoms the minimal extension
    * for the coordinates values to belong to this Extent object
    * @param {number} we  The coordinate on west-east
    * @param {number} sn  The coordinate on south-north
    *
    */

  }, {
    key: "expandByValuesCoordinates",
    value: function expandByValuesCoordinates(we, sn) {
      if (we < this.west) {
        this.west = we;
      }

      if (we > this.east) {
        this.east = we;
      }

      if (sn < this.south) {
        this.south = sn;
      }

      if (sn > this.north) {
        this.north = sn;
      }
    }
    /**
     * Instance Extent with THREE.Box2
     * @param {string} crs Projection of extent to instancied.
     * @param {THREE.Box2} box
     * @return {Extent}
     */

  }, {
    key: "toString",

    /**
     * Return values of extent in string, separated by the separator input.
     * @param {string} separator
     * @return {string}
     */
    value: function toString() {
      var separator = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';

      if (this.isTiledCrs()) {
        return "".concat(this.zoom).concat(separator).concat(this.row).concat(separator).concat(this.col);
      } else {
        return "".concat(this.east).concat(separator).concat(this.north).concat(separator).concat(this.west).concat(separator).concat(this.south);
      }
    }
    /**
     * Subdivide equally an extent from its center to return four extents:
     * north-west, north-east, south-west and south-east.
     *
     * @returns {Extent[]} An array containing the four sections of the extent. The
     * order of the sections is [NW, NE, SW, SE].
     */

  }, {
    key: "subdivision",
    value: function subdivision() {
      return this.subdivisionByScheme();
    }
    /**
     * subdivise extent by scheme.x on west-east and scheme.y on south-north.
     *
     * @param      {Vector2}  [scheme=Vector2(2,2)]  The scheme to subdivise.
     * @return     {Array<Extent>}   subdivised extents.
     */

  }, {
    key: "subdivisionByScheme",
    value: function subdivisionByScheme() {
      var scheme = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaultScheme;
      var subdivisedExtents = [];
      var dimSub = this.dimensions(_dim).divide(scheme);

      for (var x = scheme.x - 1; x >= 0; x--) {
        for (var y = scheme.y - 1; y >= 0; y--) {
          var west = this.west + x * dimSub.x;
          var south = this.south + y * dimSub.y;
          subdivisedExtents.push(new Extent(this.crs, west, west + dimSub.x, south, south + dimSub.y));
        }
      }

      return subdivisedExtents;
    }
    /**
     * Apply transform and copy this extent to input.  The `transformedCopy`
     * doesn't handle the issue of overflow of geographic limits.
     * @param {THREE.Vector2} t translation transform
     * @param {THREE.Vector2} s scale transform
     * @param {Extent} extent Extent to copy after transformation.
     */

  }, {
    key: "transformedCopy",
    value: function transformedCopy(t, s, extent) {
      if (!extent.isTiledCrs()) {
        this.crs = extent.crs;
        this.west = (extent.west + t.x) * s.x;
        this.east = (extent.east + t.x) * s.x;

        if (this.west > this.east) {
          var temp = this.west;
          this.west = this.east;
          this.east = temp;
        }

        this.south = (extent.south + t.y) * s.y;
        this.north = (extent.north + t.y) * s.y;

        if (this.south > this.north) {
          var _temp = this.south;
          this.south = this.north;
          this.north = _temp;
        }
      }
    }
    /**
     * clamp south and north values
     *
     * @param      {number}  [south=this.south]  The min south
     * @param      {number}  [north=this.north]  The max north
     * @return     {Extent}  this extent
     */

  }, {
    key: "clampSouthNorth",
    value: function clampSouthNorth() {
      var south = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.south;
      var north = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.north;
      this.south = Math.max(this.south, south);
      this.north = Math.min(this.north, north);
      return this;
    }
    /**
     * clamp west and east values
     *
     * @param      {number}  [west=this.west]  The min west
     * @param      {number}  [east=this.east]  The max east
     * @return     {Extent}  this extent
     */

  }, {
    key: "clampWestEast",
    value: function clampWestEast() {
      var west = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.west;
      var east = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.east;
      this.west = Math.max(this.west, west);
      this.east = Math.min(this.east, east);
      return this;
    }
    /**
     * clamp this extent by passed extent
     *
     * @param      {Extent}  extent  The maximum extent.
     * @return     {Extent}  this extent.
     */

  }, {
    key: "clampByExtent",
    value: function clampByExtent(extent) {
      this.clampSouthNorth(extent.south, extent.north);
      return this.clampWestEast(extent.west, extent.east);
    }
  }], [{
    key: "fromBox3",
    value: function fromBox3(crs, box) {
      return new Extent(crs, {
        west: box.min.x,
        east: box.max.x,
        south: box.min.y,
        north: box.max.y
      });
    }
  }]);
  return Extent;
}();

_extent = new Extent('EPSG:4326', [0, 0, 0, 0]);
_extent2 = new Extent('EPSG:4326', [0, 0, 0, 0]);
globalExtentTMS.set('EPSG:4326', new Extent('EPSG:4326', -180, 180, -90, 90)); // Compute global extent of WMTS:PM EPSG:3857
// It's square whose a side is between -180° to 180°.
// So, west extent, it's 180 convert in EPSG:3857

var extent3857 = globalExtentTMS.get('EPSG:4326').as('EPSG:3857');
extent3857.clampSouthNorth(extent3857.west, extent3857.east);
globalExtentTMS.set('EPSG:3857', extent3857);
schemeTiles.set('default', new THREE.Vector2(1, 1));
schemeTiles.set('WMTS:PM', schemeTiles.get('default'));
schemeTiles.set('WMTS:WGS84', new THREE.Vector2(2, 1));
var _default = Extent;
exports["default"] = _default;