UNPKG

uriproj

Version:

Map projection functions from standard coordinate reference system URIs.

174 lines (160 loc) 6.64 kB
import 'whatwg-fetch' import proj4 from 'proj4' const ROOT_PREFIX = 'http://www.opengis.net/def/crs/' const OGC_PREFIX = ROOT_PREFIX + 'OGC/' const EPSG_PREFIX = ROOT_PREFIX + 'EPSG/0/' /** * @typedef {Object} Projection * @property {function(lonlat: Array<number>): Array<number>} forward * Transforms a geographic WGS84 [longitude, latitude] coordinate to an [x, y] projection coordinate. * @property {function(xy: Array<number>): Array<number>} inverse * Transforms an [x, y] projection coordinate to a geographic WGS84 [longitude, latitude] coordinate. */ // a cache of URI string -> Projection object mappings let projCache = {} // work-arounds for incorrect epsg.io / proj4 behaviour let needsAxesReordering = { [EPSG_PREFIX + 4326]: true // proj4 is [lon,lat], we need [lat,lon] } // store some built-in projections which are not available on epsg.io let LONLAT = proj4('+proj=longlat +datum=WGS84 +no_defs') set(OGC_PREFIX + '1.3/CRS84', LONLAT) set(EPSG_PREFIX + 4979, reverseAxes(LONLAT)) /** * Returns a stored {@link Projection} for a given URI, or {@link undefined} if no {@link Projection} is stored for that URI. * * @param {string} crsUri The CRS URI for which to return a {@link Projection}. * @return {Projection|undefined} A {@link Projection} object, or {@link undefined} if not stored by {@link load} or {@link set}. * * @example * // has to be stored previously via load() or set() * var proj = uriproj.get('http://www.opengis.net/def/crs/EPSG/0/27700') * var [longitude, latitude] = [-1.54, 55.5] * var [easting,northing] = proj.forward([longitude, latitude]) */ export function get (crsUri) { return projCache[crsUri] } /** * Returns a {@link Promise} that succeeds with an already stored {@link Projection} or, if not stored, * that remotely loads the {@link Projection} (currently using https://epsg.io), stores it, and then succeeds with it. * * @param {string} crsUri The CRS URI for which to return a projection. * @return {Promise<Projection,Error>} A {@link Promise} object succeeding with a {@link Projection} object, * and failing with an {@link Error} object in case of network or PROJ.4 parsing problems. * * @example <caption>Loading a single projection</caption> * uriproj.load('http://www.opengis.net/def/crs/EPSG/0/27700').then(proj => { * var [longitude, latitude] = [-1.54, 55.5] * var [easting,northing] = proj.forward([longitude, latitude]) * }) * * @example <caption>Loading multiple projections</caption> * var uris = [ * 'http://www.opengis.net/def/crs/EPSG/0/27700', * 'http://www.opengis.net/def/crs/EPSG/0/7376', * 'http://www.opengis.net/def/crs/EPSG/0/7375'] * Promise.all(uris.map(uriproj.load)).then(projs => { * // all projections are loaded and stored now * * // get the first projection * var proj1 = projs[0] * // or: * var proj1 = uriproj.get(uris[0]) * }) * */ export function load (crsUri) { if (crsUri in projCache) { return Promise.resolve(projCache[crsUri]) } let epsg = crsUriToEPSG(crsUri) let url = `https://epsg.io/${epsg}.proj4` return fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP response code: ${response.status}`) } return response.text() }).then(proj4string => set(crsUri, proj4string, {reverseAxes: crsUri in needsAxesReordering})) } /** * Stores a given projection for a given URI that can then be accessed via {@link get} and {@link load}. * If the projection is given as proj4 string and does not require axis reversal, then it is stored * as a named projection in proj4 itself under the given URI. * * @param {string} crsUri The CRS URI for which to store the projection. * @param {string|Projection} proj A proj4 string or a {@link Projection} object. * @param {Object} [options] Options object. * @param {boolean} [options.reverseAxes=false] If proj is a proj4 string, whether to reverse the projection axes. * @return {Projection} The newly stored projection. * @throws {Error} If crsUri or proj is missing, or if a PROJ.4 string cannot be parsed by proj4js. * * @example <caption>Storing a projection using a PROJ.4 string</caption> * var uri = 'http://www.opengis.net/def/crs/EPSG/0/27700' * var proj4 = '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 ' + * '+ellps=airy +towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 +units=m +no_defs' * uriproj.set(uri, proj4) * * @example <caption>Storing a projection using a Projection object</caption> * var uri = 'http://www.opengis.net/def/crs/EPSG/0/27700' * var proj = { * forward: ([lon,lat]) => [..., ...], * inverse: ([x,y]) => [..., ...] * } * uriproj.set(uri, proj) */ export function set (crsUri, proj, options={}) { if (!crsUri || !proj) { throw new Error('crsUri and proj cannot be empty') } let projobj if (typeof proj === 'string') { projobj = proj4(proj) if (!projobj) { throw new Error(`Unsupported proj4 string: ${proj}`) } proj4.defs(crsUri, proj) if (options.reverseAxes) { projobj = reverseAxes(projobj) } } else { projobj = proj } projCache[crsUri] = projobj return projobj } /** * Return the EPSG code of an OGC CRS URI of the form * http://www.opengis.net/def/crs/EPSG/0/1234 (would return 1234). * * @param {string} crsUri The CRS URI for which to return the EPSG code. * @return {string} The EPSG code. */ function crsUriToEPSG (uri) { let epsg if (uri.indexOf(EPSG_PREFIX) === 0) { epsg = uri.substr(EPSG_PREFIX.length) } else { throw new Error(`Unsupported CRS URI: ${uri}`) } return epsg } /** * Reverses projection axis order. * * For example, a projection with lon, lat axis order is turned into one with lat, lon order. * This is necessary since geographic projections in proj4 can only be defined with * lon,lat order, however some CRSs have lat,lon order (like EPSG4326). * Incorrectly, epsg.io returns a proj4 string (with lon,lat order) even if the CRS * has lat,lon order. This function manually flips the axis order of a given projection. * See also `needsAxesReordering` above. * * @param {Projection} proj The projection whose axis order to revert. * @return {Projection} The projection with reversed axis order. */ function reverseAxes (proj) { return { forward: pos => proj.forward(pos).reverse(), inverse: pos => proj.inverse([pos[1], pos[0]]) } }