UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

393 lines 14 kB
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