itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
779 lines (666 loc) • 26 kB
JavaScript
"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;