UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

779 lines (666 loc) 26 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); 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")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * 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 }; var southWest = new THREE.Vector3(); var northEast = new THREE.Vector3(); 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.isExtent = true; this.crs = crs; // Scale/zoom this.zoom = 0; if (_Crs["default"].isTms(this.crs)) { 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 (_Crs["default"].isTms(this.crs)) { 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); } } /** * 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 == _Crs["default"].tms_3857) { 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(crs, 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.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 (_Crs["default"].isTms(this.crs)) { 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["default"].isEpsg(crs)) { 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.zoom = this.zoom; 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 (_Crs["default"].isTms(this.crs)) { 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; if (this.crs == coord.crs) { _c.copy(coord); } else { 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 (_Crs["default"].isTms(this.crs)) { 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 (_Crs["default"].isTms(this.crs)) { _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|Extent} v0 west value, * zoom value, Array of values [west, east, south and north], Extent of same * type (tiled or not), 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 (v0 == undefined) { throw new Error('No values to set in the extent'); } if (v0.isExtent) { if (_Crs["default"].isTms(v0.crs)) { v1 = v0.row; v2 = v0.col; v0 = v0.zoom; } else { v1 = v0.east; v2 = v0.south; v3 = v0.north; v0 = v0.west; } } if (_Crs["default"].isTms(this.crs)) { this.zoom = v0; this.row = v1; this.col = v2; } 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.west !== undefined) { this.west = v0.west; this.east = v0.east; this.south = v0.south; this.north = v0.north; } else if (v0.length == 4) { this.west = v0[0]; this.east = v0[1]; this.south = v0[2]; this.north = v0[3]; } else if (v3 !== undefined) { this.west = v0; this.east = v1; this.south = v2; this.north = v3; } 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", value: /** * Return values of extent in string, separated by the separator input. * @param {string} separator * @return {string} */ function toString() { var separator = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; if (_Crs["default"].isTms(this.crs)) { 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; } /** * Multiplies all extent `coordinates` (with an implicit 1 in the 4th dimension) and `matrix`. * * @param {THREE.Matrix4} matrix The matrix * @return {Extent} return this extent instance. */ }, { key: "applyMatrix4", value: function applyMatrix4(matrix) { if (!_Crs["default"].isTms(this.crs)) { southWest.set(this.west, this.south).applyMatrix4(matrix); northEast.set(this.east, this.north).applyMatrix4(matrix); this.west = southWest.x; this.east = northEast.x; this.south = southWest.y; this.north = northEast.y; if (this.west > this.east) { var temp = this.west; this.west = this.east; this.east = temp; } if (this.south > this.north) { var _temp = this.south; this.south = this.north; this.north = _temp; } return this; } } /** * 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 TMS in 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(_Crs["default"].tms_3857, schemeTiles.get('default')); schemeTiles.set(_Crs["default"].tms_4326, new THREE.Vector2(2, 1)); var _default = Extent; exports["default"] = _default;