s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
393 lines • 14 kB
JavaScript
import { degToRad } from '../../geometry';
import { parseWKTObject } from '.';
export const KEYWORDS = [
'PROJCS', // projected coordinate system
'GEOGCS', // geographic coordinate system
'COMPD_CS', // compound coordinate system
'VERT_CS', // vertical coordinate system
'PROJECTEDCRS', // projected coordinate system
'PROJCRS', // projected coordinate system
'GEOCCS', // geographic coordinate system
'LOCAL_CS', // local coordinate system
'GEODCRS', // geographic coordinate system
'GEODETICCRS', // geographic coordinate system
'GEODETICDATUM', // geographic coordinate system
'EDATUM', // geographic coordinate system
'ENGINEERINGDATUM', // engineering datum
'VERTCRS', // vertical coordinate system
'VERTICALCRS', // vertical coordinate system
'COMPOUNDCRS', // compound coordinate system
'ENGINEERINGCRS', // engineering coordinate system
'ENGCRS', // engineering coordinate system
'FITTED_CS', // fitted coordinate system
'LOCAL_DATUM', // local datum
'DATUM', // datum
];
/**
* Checks the string to see if it is a WKT projection
* @param srsCode - WKT string
* @returns - true if it is a WKT projection string
*/
export function isWKTProjection(srsCode) {
// has words with KEYWORDS in it
return KEYWORDS.some((key) => srsCode.includes(key));
}
/**
* Parses a WKT projection
* @param srsCode - WKT string input
* @returns - WKT object
*/
export function parseWKTProjection(srsCode) {
const obj = parseWKTObject(srsCode);
const res = {};
parseProj(obj, res);
updateProj(res);
return res;
}
/**
* Parses a WKT projection object
* @param obj - WKT object
* @param res - the resolved WKT object with cleaned data
*/
function parseProj(obj, res) {
// grab type
const type = obj.shift();
if (typeof type !== 'string')
return;
res.type = type;
obj = obj[0];
// grab name
const name = obj.shift();
if (typeof name !== 'string')
return;
res.name = res.srsCode = name;
// parse keywords
buildKeywords(obj, res);
}
/**
* Builds keywords
* @param obj - WKT object to read from
* @param res - the resolved WKT object with cleaned data
*/
function buildKeywords(obj, res) {
while (obj.length > 0) {
const key = obj.shift();
if (typeof key !== 'string')
return;
if (KEYWORDS.includes(key)) {
const value = obj.shift();
if (Array.isArray(value))
res[key] = buildSubKeywords(value);
}
else if (key === 'SPHEROID' || key === 'ELLIPSOID') {
const value = obj.shift();
if (Array.isArray(value))
res[key] = buildSpheroid(value);
}
else if (key === 'UNIT' || key === 'PRIMEM' || key === 'VERT_DATUM') {
const value = obj.shift();
if (Array.isArray(value))
res[key] = buildUnit(value);
}
else if (key === 'AUTHORITY') {
const value = obj.shift();
if (Array.isArray(value))
res.AUTHORITY = buildAuthority(value);
}
else if (key === 'TOWGS84') {
const value = obj.shift();
if (Array.isArray(value))
res.TOWGS84 = buildTOWGS84(value);
}
else if (key === 'PROJECTION') {
const value = obj.shift();
if (Array.isArray(value) && typeof value[0] === 'string')
res.PROJECTION = res.projName = value[0];
}
else if (key === 'PARAMETER') {
const param = obj.shift();
if (!Array.isArray(param))
continue;
const [resKey, resValue] = param;
if (typeof resKey === 'string' && typeof resValue === 'string') {
res[resKey] = parseFloat(resValue);
}
}
else if (key === 'AXIS') {
const axis = obj.shift();
if (Array.isArray(axis)) {
if (res.AXIS === undefined)
res.AXIS = [];
res.AXIS.push(axis);
}
}
}
}
/**
* Builds sub keywords for compound coordinate systems
* @param obj - WKT object
* @returns - GeoGCS with sub keywords parsed
*/
function buildSubKeywords(obj) {
const [name, ...keywords] = obj;
const res = {
name: name,
};
buildKeywords(keywords, res);
return res;
}
/**
* Builds a Unit object
* @param input - WKT object
* @returns - Unit object with appropriate values
*/
function buildUnit(input) {
const [name, convert, _authStr, authority] = input;
return {
name: name,
convert: parseFloat(convert),
AUTHORITY: buildAuthority(authority),
};
}
/**
* Builds a Spheroid object
* @param input - WKT object
* @returns - Spheroid object with appropriate values
*/
function buildSpheroid(input) {
const [name, a, rf, _authStr, authority] = input;
return {
name: name,
a: parseFloat(a),
rf: parseFloat(rf),
AUTHORITY: buildAuthority(authority),
};
}
/**
* Builds an Authority object
* @param input - WKT object
* @returns - Authority object
*/
function buildAuthority(input) {
return { EPSG: (input !== undefined ? input[1] : '') };
}
/**
* Builds a TOWGS84 object (Datum Parameters)
* @param input - WKT object
* @returns - TOWGS84 object
*/
function buildTOWGS84(input) {
return input.map((key) => parseFloat(key));
}
/**
* Update the projection to match the WKTCRS standard
* @param wkt - the projection object to update
*/
function updateProj(wkt) {
// adjust projName if necessary
if (wkt.type === 'GEOGCS') {
wkt.projName = 'longlat';
}
else if (wkt.type === 'LOCAL_CS') {
wkt.projName = 'identity';
wkt.local = true;
}
// improve axis definitions
if (wkt.AXIS !== undefined) {
let axisOrder = '';
for (let i = 0, ii = wkt.AXIS.length; i < ii; ++i) {
const axis = [wkt.AXIS[i][0].toLowerCase(), wkt.AXIS[i][1].toLowerCase()];
if (axis[0].indexOf('north') !== -1 ||
((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'north')) {
axisOrder += 'n';
}
else if (axis[0].indexOf('south') !== -1 ||
((axis[0] === 'y' || axis[0] === 'lat') && axis[1] === 'south')) {
axisOrder += 's';
}
else if (axis[0].indexOf('east') !== -1 ||
((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'east')) {
axisOrder += 'e';
}
else if (axis[0].indexOf('west') !== -1 ||
((axis[0] === 'x' || axis[0] === 'lon') && axis[1] === 'west')) {
axisOrder += 'w';
}
}
if (axisOrder.length === 2) {
axisOrder += 'u';
}
if (axisOrder.length === 3) {
wkt.axis = axisOrder;
}
}
// unit adjustments
if (wkt.UNIT !== undefined) {
wkt.units = wkt.UNIT.name?.toLowerCase();
if (wkt.units === 'metre')
wkt.units = 'meter';
if (wkt.UNIT.convert !== undefined) {
if (wkt.type === 'GEOGCS') {
if (wkt.DATUM !== undefined && wkt.DATUM.SPHEROID !== undefined) {
wkt.to_meter = wkt.UNIT.convert * wkt.DATUM.SPHEROID.a;
}
}
else {
wkt.to_meter = wkt.UNIT.convert;
}
}
}
let geogcs = wkt.GEOGCS;
if (wkt.type === 'GEOGCS') {
geogcs = wkt;
}
if (geogcs !== undefined) {
//if(wkt.GEOGCS.PRIMEM&&wkt.GEOGCS.PRIMEM.convert){
// wkt.from_greenwich=wkt.GEOGCS.PRIMEM.convert*D2R;
//}
if (geogcs.DATUM !== undefined) {
wkt.datumCode = geogcs.DATUM.name?.toLowerCase();
}
else {
wkt.datumCode = geogcs.name?.toLowerCase();
}
if (wkt.datumCode?.slice(0, 2) === 'd_') {
wkt.datumCode = wkt.datumCode.slice(2);
}
if (wkt.datumCode === 'new_zealand_geodetic_datum_1949' ||
wkt.datumCode === 'new_zealand_1949') {
wkt.datumCode = 'nzgd49';
}
if (wkt.datumCode === 'wgs_1984' || wkt.datumCode === 'world_geodetic_system_1984') {
if (wkt.PROJECTION === 'Mercator_Auxiliary_Sphere') {
wkt.sphere = true;
}
wkt.datumCode = 'wgs84';
}
if (wkt.datumCode?.slice(-6) === '_ferro') {
wkt.datumCode = wkt.datumCode.slice(0, -6);
}
if (wkt.datumCode?.slice(-8) === '_jakarta') {
wkt.datumCode = wkt.datumCode.slice(0, -8);
}
if (~(wkt.datumCode?.indexOf('belge') ?? -1) !== 0) {
wkt.datumCode = 'rnb72';
}
if (geogcs.DATUM?.SPHEROID !== undefined) {
wkt.ellps = geogcs.DATUM.SPHEROID.name.replace('_19', '').replace(/[Cc]larke_18/, 'clrk');
if (wkt.ellps.toLowerCase().slice(0, 13) === 'international') {
wkt.ellps = 'intl';
}
wkt.a = geogcs.DATUM.SPHEROID.a;
wkt.rf = geogcs.DATUM.SPHEROID.rf;
}
if (geogcs.DATUM?.TOWGS84 !== undefined) {
wkt.datum_params = geogcs.DATUM.TOWGS84;
}
if (~(wkt.datumCode?.indexOf('osgb_1936') ?? -1) !== 0) {
wkt.datumCode = 'osgb36';
}
if (~(wkt.datumCode?.indexOf('osni_1952') ?? -1) !== 0) {
wkt.datumCode = 'osni52';
}
if (~(wkt.datumCode?.indexOf('tm65') ?? -1) !== 0 ||
~(wkt.datumCode?.indexOf('geodetic_datum_of_1965') ?? -1) !== 0) {
wkt.datumCode = 'ire65';
}
if (wkt.datumCode === 'ch1903+') {
wkt.datumCode = 'ch1903';
}
if (~(wkt.datumCode?.indexOf('israel') ?? -1) !== 0) {
wkt.datumCode = 'isr93';
}
}
if (wkt.b !== undefined && !isFinite(wkt.b)) {
wkt.b = wkt.a;
}
/**
* Converts input data to meters
* @param input - meters pre-conversion
* @returns - meters
*/
const toMeter = (input) => {
const ratio = wkt.to_meter ?? 1;
return input * ratio;
};
// remaps
remap(wkt, 'standard_parallel_1', 'Standard_Parallel_1');
remap(wkt, 'standard_parallel_1', 'Latitude of 1st standard parallel');
remap(wkt, 'standard_parallel_2', 'Standard_Parallel_2');
remap(wkt, 'standard_parallel_2', 'Latitude of 2nd standard parallel');
remap(wkt, 'rectified_grid_angle', 'Rectified_Grid_Angle');
remap(wkt, 'rectifiedGridAngle', 'rectified_grid_angle');
remap(wkt, 'false_easting', 'False_Easting');
remap(wkt, 'false_easting', 'easting');
remap(wkt, 'false_easting', 'Easting at false origin');
remap(wkt, 'false_northing', 'False_Northing');
remap(wkt, 'false_northing', 'False northing');
remap(wkt, 'false_northing', 'Northing at false origin');
remap(wkt, 'central_meridian', 'Central_Meridian');
remap(wkt, 'central_meridian', 'Longitude of natural origin');
remap(wkt, 'central_meridian', 'Longitude of false origin');
remap(wkt, 'latitude_of_origin', 'Latitude_Of_Origin');
remap(wkt, 'latitude_of_origin', 'Central_Parallel');
remap(wkt, 'latitude_of_origin', 'Latitude of natural origin');
remap(wkt, 'latitude_of_origin', 'Latitude of false origin');
remap(wkt, 'scale_factor', 'Scale_Factor');
remap(wkt, 'latitude_of_center', 'Latitude_Of_Center');
remap(wkt, 'latitude_of_center', 'Latitude_of_center');
remap(wkt, 'lat0', 'latitude_of_center', degToRad);
remap(wkt, 'longitude_of_center', 'Longitude_Of_Center');
remap(wkt, 'longitude_of_center', 'Longitude_of_center');
remap(wkt, 'longc', 'longitude_of_center', degToRad);
remap(wkt, 'x0', 'false_easting', toMeter);
remap(wkt, 'y0', 'false_northing', toMeter);
remap(wkt, 'long0', 'central_meridian', degToRad);
remap(wkt, 'lat0', 'latitude_of_origin', degToRad);
remap(wkt, 'lat0', 'standard_parallel_1', degToRad);
remap(wkt, 'lat1', 'standard_parallel_1', degToRad);
remap(wkt, 'lat2', 'standard_parallel_2', degToRad);
remap(wkt, 'azimuth', 'Azimuth');
remap(wkt, 'alpha', 'azimuth', degToRad);
// uppercase all
remap(wkt, 'toMeter', 'to_meter');
remap(wkt, 'fromGreenwich', 'from_greenwich');
// latTS, datumParams, and scaleFactor
remap(wkt, 'latTs', 'lat_ts', degToRad);
remap(wkt, 'datumParams', 'datum_params');
remap(wkt, 'scaleFactor', 'scale_factor');
remap(wkt, 'k0', 'scaleFactor');
// update long0 if applicable
if (wkt.long0 === undefined &&
wkt.longc !== undefined &&
(wkt.projName === 'Albers_Conic_Equal_Area' || wkt.projName === 'Lambert_Azimuthal_Equal_Area')) {
wkt.long0 = wkt.longc;
}
// update lat_ts and lat0 for polar stereographic
if (wkt.lat_ts === undefined &&
wkt.lat1 !== undefined &&
['Stereographic_South_Pole', 'Polar Stereographic (variant B)'].includes(wkt.projName ?? '')) {
wkt.lat0 = degToRad(wkt.lat1 > 0 ? 90 : -90);
wkt.lat_ts = wkt.latTs = wkt.lat1;
}
else if (wkt.lat_ts === undefined &&
wkt.lat0 !== undefined &&
wkt.projName === 'Polar_Stereographic') {
wkt.lat_ts = wkt.latTs = wkt.lat0;
wkt.lat0 = degToRad(wkt.lat0 > 0 ? 90 : -90);
}
}
/**
* Only update the to key if it did not exist
* @param input - input object
* @param to - keys to update
* @param from - keys to remap
* @param updateFun - function guide on how to update the value
*/
function remap(input, to, from, updateFun) {
if (input[to] === undefined && input[from] !== undefined) {
// @ts-expect-error - its ok to remap the key
input[to] = updateFun !== undefined ? updateFun(input[from]) : input[from];
}
}
//# sourceMappingURL=projection.js.map