UNPKG

covutils

Version:

Utilities for creating, transforming, and handling Coverage Data objects.

505 lines (435 loc) 17 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _LongitudeAxisIndex; exports.getReferenceObject = getReferenceObject; exports.getHorizontalCRSReferenceObject = getHorizontalCRSReferenceObject; exports.isEllipsoidalCRS = isEllipsoidalCRS; exports.getProjection = getProjection; exports.loadProjection = loadProjection; exports.getHorizontalCRSComponents = getHorizontalCRSComponents; exports.getHorizontalCRSCoordinateIDs = getHorizontalCRSCoordinateIDs; exports.reprojectCoords = reprojectCoords; exports.getLongitudeWrapper = getLongitudeWrapper; exports.isLongitudeAxis = isLongitudeAxis; exports.isISODateAxis = isISODateAxis; exports.asTime = asTime; var _uriproj = require('uriproj'); var uriproj = _interopRequireWildcard(_uriproj); var _constants = require('../constants.js'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var OPENGIS_CRS_PREFIX = 'http://www.opengis.net/def/crs/'; /** 3D WGS84 in lat-lon-height order */ var EPSG4979 = OPENGIS_CRS_PREFIX + 'EPSG/0/4979'; /** 2D WGS84 in lat-lon order */ var EPSG4326 = OPENGIS_CRS_PREFIX + 'EPSG/0/4326'; /** 2D WGS84 in lon-lat order */ var CRS84 = OPENGIS_CRS_PREFIX + 'OGC/1.3/CRS84'; /** CRSs in which position is specified by geodetic latitude and longitude */ var GeographicCRSs = [EPSG4979, EPSG4326, CRS84]; /** Position of longitude axis */ var LongitudeAxisIndex = (_LongitudeAxisIndex = {}, _defineProperty(_LongitudeAxisIndex, EPSG4979, 1), _defineProperty(_LongitudeAxisIndex, EPSG4326, 1), _defineProperty(_LongitudeAxisIndex, CRS84, 0), _LongitudeAxisIndex); /** * Return the reference system connection object for the given domain coordinate ID, * or undefined if none exists. */ function getReferenceObject(domain, coordinateId) { var ref = domain.referencing.find(function (ref) { return ref.coordinates.indexOf(coordinateId) !== -1; }); return ref; } /** * Return the reference system connection object of the horizontal CRS of the domain, * or ``undefined`` if none found. * A horizontal CRS is either geodetic (typically ellipsoidal, meaning lat/lon) * or projected, and may be 2D or 3D (including height). */ function getHorizontalCRSReferenceObject(domain) { var isHorizontal = function isHorizontal(ref) { return ['GeodeticCRS', 'GeographicCRS', 'GeocentricCRS', 'ProjectedCRS'].indexOf(ref.system.type) !== -1; }; var ref = domain.referencing.find(isHorizontal); return ref; } /** * Return whether the reference system is a CRS in which * horizontal position is specified by geodetic latitude and longitude. */ function isEllipsoidalCRS(rs) { return rs.type === 'GeographicCRS' || GeographicCRSs.indexOf(rs.id) !== -1; } /** * Return a projection object based on the CRS found in the coverage domain. * If no CRS is found or it is unsupported, then ``undefined`` is returned. * For non-built-in projections, this function returns already-cached projections * that were loaded via {@link loadProjection}. * * A projection converts between geodetic lat/lon and projected x/y values. * * For lat/lon CRSs the projection is defined such that an input lat/lon * position gets projected/wrapped to the longitude range used in the domain, for example * [0,360]. The purpose of this is to make intercomparison between different coverages easier. * * The following limitations currently apply: * - only primitive axes and Tuple/Polygon composite axes are supported for lat/lon CRSs * * @param {Domain} domain A coverage domain object. * @return {IProjection} A stripped-down Leaflet IProjection object. */ function getProjection(domain) { var isEllipsoidal = domain.referencing.some(function (ref) { return isEllipsoidalCRS(ref.system); }); if (isEllipsoidal) { return getLonLatProjection(domain); } // try to get projection via uriproj library var ref = getHorizontalCRSReferenceObject(domain); if (!ref) { throw new Error('No horizontal CRS found in coverage domain'); } var uri = ref.system.id; var proj = uriproj.get(uri); if (!proj) { throw new Error('Projection ' + uri + ' not cached in uriproj, use loadProjection() instead'); } return wrapProj4(proj); } /** * Like {@link getProjection} but will also try to remotely load a projection definition via the uriproj library. * On success, the loaded projection is automatically cached for later use and can be directly requested * with {@link getProjection}. * * @param {Domain} domain A coverage domain object. * @return {Promise<IProjection>} A Promise succeeding with a stripped-down Leaflet IProjection object. */ function loadProjection(domain) { try { // we try the local one first so that we get our special lon/lat projection (which doesn't exist in uriproj) return getProjection(domain); } catch (e) {} // try to load projection remotely via uriproj library var ref = getHorizontalCRSReferenceObject(domain); if (!ref) { throw new Error('No horizontal CRS found in coverage domain'); } var uri = ref.system.id; return uriproj.load(uri).then(function (proj) { return wrapProj4(proj); }); } /** * Return the coordinate IDs of the horizontal CRS of the domain. * * @deprecated use getHorizontalCRSCoordinateIDs * @example * var [xComp,yComp] = getHorizontalCRSComponents(domain) */ function getHorizontalCRSComponents(domain) { return getHorizontalCRSCoordinateIDs(domain); } /** * Return the coordinate IDs of the horizontal CRS of the domain. * * @example * var [xComp,yComp] = getHorizontalCRSCoordinateIDs(domain) */ function getHorizontalCRSCoordinateIDs(domain) { var ref = getHorizontalCRSReferenceObject(domain); return ref.coordinates; } /** * Wraps a proj4 Projection object into an IProjection object. */ function wrapProj4(proj) { return { project: function project(_ref) { var lon = _ref.lon; var lat = _ref.lat; var _proj$forward = proj.forward([lon, lat]); var _proj$forward2 = _slicedToArray(_proj$forward, 2); var x = _proj$forward2[0]; var y = _proj$forward2[1]; return { x: x, y: y }; }, unproject: function unproject(_ref2) { var x = _ref2.x; var y = _ref2.y; var _proj$inverse = proj.inverse([x, y]); var _proj$inverse2 = _slicedToArray(_proj$inverse, 2); var lon = _proj$inverse2[0]; var lat = _proj$inverse2[1]; return { lon: lon, lat: lat }; } }; } function getLonLatProjection(domain) { var ref = domain.referencing.find(function (ref) { return isEllipsoidalCRS(ref.system); }); var lonIdx = LongitudeAxisIndex[ref.system.id]; if (lonIdx > 1) { // this should never happen as longitude is always the first or second axis throw new Error(); } var lonComponent = ref.coordinates[lonIdx]; // we find the min and max longitude occuring in the domain by inspecting the axis values // Note: this is inefficient for big composite axes. // In that case, something like a domain extent might help which has the min/max values for each component. // TODO handle bounds var lonMin = void 0, lonMax = void 0; if (domain.axes.has(lonComponent)) { // longitude is a grid axis var lonAxisName = lonComponent; var lonAxisVals = domain.axes.get(lonAxisName).values; lonMin = lonAxisVals[0]; lonMax = lonAxisVals[lonAxisVals.length - 1]; if (lonMin > lonMax) { var _ref3 = [lonMax, lonMin]; lonMin = _ref3[0]; lonMax = _ref3[1]; } } else { // TODO there should be no dependency to CovJSON // longitude is not a primitive grid axis but a component of a composite axis // find the composite axis containing the longitude component var axes = [].concat(_toConsumableArray(domain.axes.values())); var axis = axes.find(function (axis) { return axis.coordinates.indexOf(lonComponent) !== -1; }); var lonCompIdx = axis.coordinates.indexOf(lonComponent); // scan the composite axis for min/max longitude values lonMin = Infinity; lonMax = -Infinity; if (axis.dataType === _constants.COVJSON_DATATYPE_TUPLE) { var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = axis.values[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var tuple = _step.value; var lon = tuple[lonCompIdx]; lonMin = Math.min(lon, lonMin); lonMax = Math.max(lon, lonMax); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } else if (axis.dataType === _constants.COVJSON_DATATYPE_POLYGON) { var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = axis.values[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var poly = _step2.value; var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = poly[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var ring = _step3.value; var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = ring[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var point = _step4.value; var _lon = point[lonCompIdx]; lonMin = Math.min(_lon, lonMin); lonMax = Math.max(_lon, lonMax); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } } else { throw new Error('Unsupported data type: ' + axis.dataType); } } var lonMid = (lonMax + lonMin) / 2; var lonMinExtended = lonMid - 180; var lonMaxExtended = lonMid + 180; return { project: function project(_ref4) { var lon = _ref4.lon; var lat = _ref4.lat; var lonProjected = void 0; if (lonMinExtended <= lon && lon <= lonMaxExtended) { // use unchanged to avoid introducing rounding errors lonProjected = lon; } else { lonProjected = ((lon - lonMinExtended) % 360 + 360) % 360 + lonMinExtended; } var _ref5 = lonIdx === 0 ? [lonProjected, lat] : [lat, lonProjected]; var _ref6 = _slicedToArray(_ref5, 2); var x = _ref6[0]; var y = _ref6[1]; return { x: x, y: y }; }, unproject: function unproject(_ref7) { var x = _ref7.x; var y = _ref7.y; var _ref8 = lonIdx === 0 ? [x, y] : [y, x]; var _ref9 = _slicedToArray(_ref8, 2); var lon = _ref9[0]; var lat = _ref9[1]; return { lon: lon, lat: lat }; } }; } /** * Reprojects coordinates from one projection to another. */ function reprojectCoords(pos, fromProjection, toProjection) { return toProjection.project(fromProjection.unproject(pos)); } /** * Returns a function which converts an arbitrary longitude to the * longitude extent used in the coverage domain. * This only supports primitive axes since this is what subsetByValue supports. * The longitude extent is extended to 360 degrees if the actual extent is smaller. * The extension is done equally on both sides of the extent. * * For example, the domain may have longitudes within [0,360]. * An input longitude of -70 is converted to 290. * All longitudes within [0,360] are returned unchanged. * * If the domain has longitudes within [10,50] then the * extended longitude range is [-150,210] (-+180 from the middle point). * An input longitude of -170 is converted to 190. * All longitudes within [-150,210] are returned unchanged. * * @ignore */ function getLongitudeWrapper(domain, axisName) { // TODO deprecate this in favour of getProjection, check leaflet-coverage // for primitive axes, the axis identifier = component identifier if (!isLongitudeAxis(domain, axisName)) { throw new Error('\'' + axisName + '\' is not a longitude axis'); } var vals = domain.axes.get(axisName).values; var lon_min = vals[0]; var lon_max = vals[vals.length - 1]; if (lon_min > lon_max) { var _ref10 = [lon_max, lon_min]; lon_min = _ref10[0]; lon_max = _ref10[1]; } var x_mid = (lon_max + lon_min) / 2; var x_min = x_mid - 180; var x_max = x_mid + 180; return function (lon) { if (x_min <= lon && lon <= x_max) { // directly return to avoid introducing rounding errors return lon; } else { return ((lon - x_min) % 360 + 360) % 360 + x_min; } }; } /** * Return whether the given domain axis represents longitudes. * * @ignore */ function isLongitudeAxis(domain, axisName) { var ref = getReferenceObject(domain, axisName); if (!ref) { return false; } var crsId = ref.system.id; // TODO should support unknown CRSs with embedded axis information if (GeographicCRSs.indexOf(crsId) === -1) { // this also covers the case when there is no ID property return false; } var compIdx = ref.coordinates.indexOf(axisName); var isLongitude = LongitudeAxisIndex[crsId] === compIdx; return isLongitude; } /** * Returns true if the given axis has ISO8601 date strings * as axis values. */ function isISODateAxis(domain, axisName) { var val = domain.axes.get(axisName).values[0]; if (typeof val !== 'string') { return false; } return !isNaN(new Date(val).getTime()); } function asTime(inp) { var res = void 0; var err = false; if (typeof inp === 'string') { res = new Date(inp).getTime(); } else if (inp instanceof Date) { res = inp.getTime(); } else { err = true; } if (isNaN(res)) { err = true; } if (err) { throw new Error('Invalid date: ' + inp); } return res; }