226 lines
6.7 KiB
JavaScript
226 lines
6.7 KiB
JavaScript
/**
|
|
* @module ol/proj/proj4
|
|
*/
|
|
import {
|
|
addCoordinateTransforms,
|
|
addEquivalentProjections,
|
|
addProjection,
|
|
createSafeCoordinateTransform,
|
|
} from '../proj.js';
|
|
import Projection from './Projection.js';
|
|
import {get as getCachedProjection} from './projections.js';
|
|
import {get as getTransform} from './transforms.js';
|
|
|
|
/**
|
|
* @type {import("proj4")|null}
|
|
*/
|
|
let registered = null;
|
|
|
|
/**
|
|
* @return {boolean} Proj4 has been registered.
|
|
*/
|
|
export function isRegistered() {
|
|
return !!registered;
|
|
}
|
|
|
|
/**
|
|
* Unsets the shared proj4 previously set with register.
|
|
*/
|
|
export function unregister() {
|
|
registered = null;
|
|
}
|
|
|
|
/**
|
|
* Make projections defined in proj4 (with `proj4.defs()`) available in
|
|
* OpenLayers. Requires proj4 >= 2.8.0.
|
|
*
|
|
* This function should be called whenever changes are made to the proj4
|
|
* registry, e.g. after calling `proj4.defs()`. Existing transforms will not be
|
|
* modified by this function.
|
|
*
|
|
* @param {import("proj4")} proj4 Proj4.
|
|
* @api
|
|
*/
|
|
export function register(proj4) {
|
|
registered = proj4;
|
|
|
|
const projCodes = Object.keys(proj4.defs);
|
|
const len = projCodes.length;
|
|
let i, j;
|
|
for (i = 0; i < len; ++i) {
|
|
const code = projCodes[i];
|
|
if (!getCachedProjection(code)) {
|
|
const def = proj4.defs(code);
|
|
let units = /** @type {import("./Units.js").Units} */ (def.units);
|
|
if (!units && def.projName === 'longlat') {
|
|
units = 'degrees';
|
|
}
|
|
addProjection(
|
|
new Projection({
|
|
code: code,
|
|
axisOrientation: def.axis,
|
|
metersPerUnit: def.to_meter,
|
|
units,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
for (i = 0; i < len; ++i) {
|
|
const code1 = projCodes[i];
|
|
const proj1 = getCachedProjection(code1);
|
|
for (j = 0; j < len; ++j) {
|
|
const code2 = projCodes[j];
|
|
const proj2 = getCachedProjection(code2);
|
|
if (!getTransform(code1, code2)) {
|
|
if (proj4.defs[code1] === proj4.defs[code2]) {
|
|
addEquivalentProjections([proj1, proj2]);
|
|
} else {
|
|
const transform = proj4(code1, code2);
|
|
addCoordinateTransforms(
|
|
proj1,
|
|
proj2,
|
|
createSafeCoordinateTransform(proj1, proj2, transform.forward),
|
|
createSafeCoordinateTransform(proj2, proj1, transform.inverse),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} code The EPSG code.
|
|
* @return {Promise<string>} The proj4 definition.
|
|
*/
|
|
let epsgLookup = async function (code) {
|
|
const response = await fetch(`https://epsg.io/${code}.proj4`);
|
|
if (!response.ok) {
|
|
throw new Error(`Unexpected response from epsg.io: ${response.status}`);
|
|
}
|
|
return response.text();
|
|
};
|
|
|
|
/**
|
|
* Set the lookup function for getting proj4 definitions given an EPSG code.
|
|
* By default, the {@link module:ol/proj/proj4.fromEPSGCode} function uses the
|
|
* epsg.io website for proj4 definitions. This can be changed by providing a
|
|
* different lookup function.
|
|
*
|
|
* @param {function(number):Promise<string>} func The lookup function.
|
|
* @api
|
|
*/
|
|
export function setEPSGLookup(func) {
|
|
epsgLookup = func;
|
|
}
|
|
|
|
/**
|
|
* Get the current EPSG lookup function.
|
|
*
|
|
* @return {function(number):Promise<string>} The EPSG lookup function.
|
|
*/
|
|
export function getEPSGLookup() {
|
|
return epsgLookup;
|
|
}
|
|
|
|
/**
|
|
* Get a projection from an EPSG code. This function fetches the projection
|
|
* definition from the epsg.io website, registers this definition for use with
|
|
* proj4, and returns a configured projection. You must call import proj4 and
|
|
* call {@link module:ol/proj/proj4.register} before using this function.
|
|
*
|
|
* If the projection definition is already registered with proj4, it will not
|
|
* be fetched again (so it is ok to call this function multiple times with the
|
|
* same code).
|
|
*
|
|
* @param {number|string} code The EPSG code (e.g. 4326 or 'EPSG:4326').
|
|
* @return {Promise<Projection>} The projection.
|
|
* @api
|
|
*/
|
|
export async function fromEPSGCode(code) {
|
|
if (typeof code === 'string') {
|
|
code = parseInt(code.split(':').pop(), 10);
|
|
}
|
|
|
|
const proj4 = registered;
|
|
if (!proj4) {
|
|
throw new Error('Proj4 must be registered first with register(proj4)');
|
|
}
|
|
|
|
const epsgCode = 'EPSG:' + code;
|
|
if (proj4.defs(epsgCode)) {
|
|
return getCachedProjection(epsgCode);
|
|
}
|
|
|
|
proj4.defs(epsgCode, await epsgLookup(code));
|
|
register(proj4);
|
|
|
|
return getCachedProjection(epsgCode);
|
|
}
|
|
|
|
/**
|
|
* Generate an EPSG lookup function which uses the MapTiler Coordinates API to find projection
|
|
* definitions which do not require proj4 to be configured to handle `+nadgrids` parameters.
|
|
* Call {@link module:ol/proj/proj4.setEPSGLookup} use the function for lookups
|
|
* `setEPSGLookup(epsgLookupMapTiler('{YOUR_MAPTILER_API_KEY_HERE}'))`.
|
|
*
|
|
* @param {string} key MapTiler API key. Get your own API key at https://www.maptiler.com/cloud/.
|
|
* @return {function(number):Promise<string>} The EPSG lookup function.
|
|
* @api
|
|
*/
|
|
export function epsgLookupMapTiler(key) {
|
|
return async function (code) {
|
|
const response = await fetch(
|
|
`https://api.maptiler.com/coordinates/search/code:${code}.json?transformations=true&exports=true&key=${key}`,
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Unexpected response from maptiler.com: ${response.status}`,
|
|
);
|
|
}
|
|
return response.json().then((json) => {
|
|
const results = json['results'];
|
|
if (results?.length > 0) {
|
|
const result = results.filter(
|
|
(r) =>
|
|
r['id']?.['authority'] === 'EPSG' && r['id']?.['code'] === code,
|
|
)[0];
|
|
if (result) {
|
|
const transforms = result['transformations'];
|
|
if (transforms?.length > 0) {
|
|
// use default transform if it does not require grids
|
|
const defaultTransform = result['default_transformation'];
|
|
if (
|
|
transforms.filter(
|
|
(t) =>
|
|
t['id']?.['authority'] === defaultTransform?.['authority'] &&
|
|
t['id']?.['code'] === defaultTransform?.['code'] &&
|
|
t['grids']?.length === 0,
|
|
).length > 0
|
|
) {
|
|
return result['exports']?.['proj4'];
|
|
}
|
|
// otherwise use most accurate alternative without grids
|
|
const transform = transforms
|
|
.filter(
|
|
(t) =>
|
|
t['grids']?.length === 0 &&
|
|
t['target_crs']?.['authority'] === 'EPSG' &&
|
|
t['target_crs']?.['code'] === 4326 &&
|
|
t['deprecated'] === false &&
|
|
t['usable'] === true,
|
|
)
|
|
.sort((t1, t2) => t1['accuracy'] - t2['accuracy'])[0]?.[
|
|
'exports'
|
|
]?.['proj4'];
|
|
if (transform) {
|
|
return transform;
|
|
}
|
|
}
|
|
// fallback to default
|
|
return result['exports']?.['proj4'];
|
|
}
|
|
}
|
|
});
|
|
};
|
|
}
|