diff --git a/PROJJSONBuilder2015.js b/PROJJSONBuilder2015.js new file mode 100644 index 0000000..b383f80 --- /dev/null +++ b/PROJJSONBuilder2015.js @@ -0,0 +1,19 @@ +import PROJJSONBuilderBase from './PROJJSONBuilderBase.js'; + +class PROJJSONBuilder2015 extends PROJJSONBuilderBase { + static convert(node, result = {}) { + super.convert(node, result); + + // Skip `CS` and `USAGE` nodes for WKT2-2015 + if (result.coordinate_system?.subtype === "Cartesian") { + delete result.coordinate_system; + } + if (result.usage) { + delete result.usage; + } + + return result; + } +} + +export default PROJJSONBuilder2015; \ No newline at end of file diff --git a/PROJJSONBuilder2019.js b/PROJJSONBuilder2019.js new file mode 100644 index 0000000..2148408 --- /dev/null +++ b/PROJJSONBuilder2019.js @@ -0,0 +1,30 @@ +import PROJJSONBuilderBase from './PROJJSONBuilderBase.js'; + +class PROJJSONBuilder2019 extends PROJJSONBuilderBase { + static convert(node, result = {}) { + super.convert(node, result); + + // Handle `CS` node for WKT2-2019 + const csNode = node.find((child) => Array.isArray(child) && child[0] === "CS"); + if (csNode) { + result.coordinate_system = { + subtype: csNode[1], + axis: this.extractAxes(node), + }; + } + + // Handle `USAGE` node for WKT2-2019 + const usageNode = node.find((child) => Array.isArray(child) && child[0] === "USAGE"); + if (usageNode) { + result.usage = { + scope: usageNode.find((child) => Array.isArray(child) && child[0] === "SCOPE")?.[1], + area: usageNode.find((child) => Array.isArray(child) && child[0] === "AREA")?.[1], + bbox: usageNode.find((child) => Array.isArray(child) && child[0] === "BBOX")?.slice(1), + }; + } + + return result; + } +} + +export default PROJJSONBuilder2019; \ No newline at end of file diff --git a/PROJJSONBuilderBase.js b/PROJJSONBuilderBase.js new file mode 100644 index 0000000..cdd57e3 --- /dev/null +++ b/PROJJSONBuilderBase.js @@ -0,0 +1,317 @@ +class PROJJSONBuilderBase { + static getId(node) { + const idNode = node.find((child) => Array.isArray(child) && child[0] === "ID"); + if (idNode && idNode.length >= 3) { + return { + authority: idNode[1], + code: parseInt(idNode[2], 10), + }; + } + return null; + } + + static convertUnit(node, type = "unit") { + if (!node || node.length < 3) { + return { type, name: "unknown", conversion_factor: null }; + } + + const name = node[1]; + const conversionFactor = parseFloat(node[2]) || null; + + const idNode = node.find((child) => Array.isArray(child) && child[0] === "ID"); + const id = idNode + ? { + authority: idNode[1], + code: parseInt(idNode[2], 10), + } + : null; + + return { + type, + name, + conversion_factor: conversionFactor, + id, + }; + } + + static convertAxis(node) { + const name = node[1] || "Unknown"; + + // Determine the direction + let direction; + const abbreviationMatch = name.match(/^\((.)\)$/); // Match abbreviations like "(E)" or "(N)" + if (abbreviationMatch) { + // Use the abbreviation to determine the direction + const abbreviation = abbreviationMatch[1].toUpperCase(); + if (abbreviation === 'E') direction = 'east'; + else if (abbreviation === 'N') direction = 'north'; + else if (abbreviation === 'U') direction = 'up'; + else throw new Error(`Unknown axis abbreviation: ${abbreviation}`); + } else { + // Use the explicit direction provided in the AXIS node + direction = node[2]?.toLowerCase() || "unknown"; + } + + const orderNode = node.find((child) => Array.isArray(child) && child[0] === "ORDER"); + const order = orderNode ? parseInt(orderNode[1], 10) : null; + + const unitNode = node.find( + (child) => + Array.isArray(child) && + (child[0] === "LENGTHUNIT" || child[0] === "ANGLEUNIT" || child[0] === "SCALEUNIT") + ); + const unit = this.convertUnit(unitNode); + + return { + name, + direction, // Use the valid PROJJSON direction value + unit, + order, + }; + } + + static extractAxes(node) { + return node + .filter((child) => Array.isArray(child) && child[0] === "AXIS") + .map((axis) => this.convertAxis(axis)) + .sort((a, b) => (a.order || 0) - (b.order || 0)); // Sort by the "order" property + } + + static convert(node, result = {}) { + + switch (node[0]) { + case "PROJCRS": + result.type = "ProjectedCRS"; + result.name = node[1]; + result.base_crs = node.find((child) => Array.isArray(child) && child[0] === "BASEGEOGCRS") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "BASEGEOGCRS")) + : null; + result.conversion = node.find((child) => Array.isArray(child) && child[0] === "CONVERSION") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "CONVERSION")) + : null; + + const csNode = node.find((child) => Array.isArray(child) && child[0] === "CS"); + if (csNode) { + result.coordinate_system = { + type: csNode[1], + axis: this.extractAxes(node), + }; + } + + const lengthUnitNode = node.find((child) => Array.isArray(child) && child[0] === "LENGTHUNIT"); + if (lengthUnitNode) { + const unit = this.convertUnit(lengthUnitNode); + result.coordinate_system.unit = unit; // Add unit to coordinate_system + } + + result.id = this.getId(node); + break; + + case "BASEGEOGCRS": + case "GEOGCRS": + result.type = "GeographicCRS"; + result.name = node[1]; + + // Handle DATUM or ENSEMBLE + const datumOrEnsembleNode = node.find( + (child) => Array.isArray(child) && (child[0] === "DATUM" || child[0] === "ENSEMBLE") + ); + if (datumOrEnsembleNode) { + const datumOrEnsemble = this.convert(datumOrEnsembleNode); + if (datumOrEnsembleNode[0] === "ENSEMBLE") { + result.datum_ensemble = datumOrEnsemble; + } else { + result.datum = datumOrEnsemble; + } + const primem = node.find((child) => Array.isArray(child) && child[0] === "PRIMEM"); + if (primem && primem[1] !== 'Greenwich') { + datumOrEnsemble.prime_meridian = { + name: primem[1], + longitude: parseFloat(primem[2]), + } + } + } + + result.coordinate_system = { + type: "ellipsoidal", + axis: this.extractAxes(node), + }; + + result.id = this.getId(node); + break; + + case "DATUM": + result.type = "GeodeticReferenceFrame"; + result.name = node[1]; + result.ellipsoid = node.find((child) => Array.isArray(child) && child[0] === "ELLIPSOID") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "ELLIPSOID")) + : null; + break; + + case "ENSEMBLE": + result.type = "DatumEnsemble"; + result.name = node[1]; + + // Extract ensemble members + result.members = node + .filter((child) => Array.isArray(child) && child[0] === "MEMBER") + .map((member) => ({ + type: "DatumEnsembleMember", + name: member[1], + id: this.getId(member), // Extract ID as { authority, code } + })); + + // Extract accuracy + const accuracyNode = node.find((child) => Array.isArray(child) && child[0] === "ENSEMBLEACCURACY"); + if (accuracyNode) { + result.accuracy = parseFloat(accuracyNode[1]); + } + + // Extract ellipsoid + const ellipsoidNode = node.find((child) => Array.isArray(child) && child[0] === "ELLIPSOID"); + if (ellipsoidNode) { + result.ellipsoid = this.convert(ellipsoidNode); // Convert the ellipsoid node + } + + // Extract identifier for the ensemble + result.id = this.getId(node); + break; + + case "ELLIPSOID": + result.type = "Ellipsoid"; + result.name = node[1]; + result.semi_major_axis = parseFloat(node[2]); + result.inverse_flattening = parseFloat(node[3]); + const units = node.find((child) => Array.isArray(child) && child[0] === "LENGTHUNIT") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "LENGTHUNIT"), result) + : null; + break; + + case "CONVERSION": + result.type = "Conversion"; + result.name = node[1]; + result.method = node.find((child) => Array.isArray(child) && child[0] === "METHOD") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "METHOD")) + : null; + result.parameters = node + .filter((child) => Array.isArray(child) && child[0] === "PARAMETER") + .map((param) => this.convert(param)); + break; + + case "METHOD": + result.type = "Method"; + result.name = node[1]; + result.id = this.getId(node); + break; + + case "PARAMETER": + result.type = "Parameter"; + result.name = node[1]; + result.value = parseFloat(node[2]); + result.unit = this.convertUnit( + node.find( + (child) => + Array.isArray(child) && + (child[0] === "LENGTHUNIT" || child[0] === "ANGLEUNIT" || child[0] === "SCALEUNIT") + ) + ); + result.id = this.getId(node); + break; + + case "BOUNDCRS": + result.type = "BoundCRS"; + + // Process SOURCECRS + const sourceCrsNode = node.find((child) => Array.isArray(child) && child[0] === "SOURCECRS"); + if (sourceCrsNode) { + const sourceCrsContent = sourceCrsNode.find((child) => Array.isArray(child)); + result.source_crs = sourceCrsContent ? this.convert(sourceCrsContent) : null; + } + + // Process TARGETCRS + const targetCrsNode = node.find((child) => Array.isArray(child) && child[0] === "TARGETCRS"); + if (targetCrsNode) { + const targetCrsContent = targetCrsNode.find((child) => Array.isArray(child)); + result.target_crs = targetCrsContent ? this.convert(targetCrsContent) : null; + } + + // Process ABRIDGEDTRANSFORMATION + const transformationNode = node.find((child) => Array.isArray(child) && child[0] === "ABRIDGEDTRANSFORMATION"); + if (transformationNode) { + result.transformation = this.convert(transformationNode); + } else { + result.transformation = null; + } + break; + + case "ABRIDGEDTRANSFORMATION": + result.type = "Transformation"; + result.name = node[1]; + result.method = node.find((child) => Array.isArray(child) && child[0] === "METHOD") + ? this.convert(node.find((child) => Array.isArray(child) && child[0] === "METHOD")) + : null; + + result.parameters = node + .filter((child) => Array.isArray(child) && (child[0] === "PARAMETER" || child[0] === "PARAMETERFILE")) + .map((param) => { + if (param[0] === "PARAMETER") { + return this.convert(param); + } else if (param[0] === "PARAMETERFILE") { + return { + name: param[1], + value: param[2], + id: { + "authority": "EPSG", + "code": 8656 + } + }; + } + }); + + // Adjust the Scale difference parameter if present + if (result.parameters.length === 7) { + const scaleDifference = result.parameters[6]; + if (scaleDifference.name === "Scale difference") { + scaleDifference.value = Math.round((scaleDifference.value - 1) * 1e12) / 1e6; + } + } + + result.id = this.getId(node); + break; + + case "AXIS": + if (!result.coordinate_system) { + result.coordinate_system = { type: "unspecified", axis: [] }; + } + result.coordinate_system.axis.push(this.convertAxis(node)); + break; + + case "LENGTHUNIT": + const unit = this.convertUnit(node, 'LinearUnit'); + if (result.coordinate_system && result.coordinate_system.axis) { + result.coordinate_system.axis.forEach((axis) => { + if (!axis.unit) { + axis.unit = unit; + } + }); + } + if (unit.conversion_factor && unit.conversion_factor !== 1) { + if (result.semi_major_axis) { + result.semi_major_axis = { + value: result.semi_major_axis, + unit, + } + } + } + break; + + default: + result.keyword = node[0]; + break; + } + + return result; + } +} + +export default PROJJSONBuilderBase; \ No newline at end of file diff --git a/README.md b/README.md index 331743f..b9983af 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # wkt-parser -The wkt parser pulled out of proj4 so it can be hacked on. +The wkt parser pulled out of proj4 so it can be hacked on. Supports WKT1, WKT2 and PROJJSON. -The parser currently only supports wkt strings of [version 1](https://docs.ogc.org/is/18-010r7/18-010r7.html#196) (earlier than 2015). - -It does not support geocentric currently (`GEOCCS`). +It does not support geocentric currently (`GEOCCS`/`). `COMPOUNDCS` is only supported for WKT1. diff --git a/buildPROJJSON.js b/buildPROJJSON.js new file mode 100644 index 0000000..6e0f6a1 --- /dev/null +++ b/buildPROJJSON.js @@ -0,0 +1,37 @@ +import PROJJSONBuilder2015 from './PROJJSONBuilder2015.js'; +import PROJJSONBuilder2019 from './PROJJSONBuilder2019.js'; + +/** + * Detects the WKT2 version based on the structure of the WKT. + * @param {Array} root The root WKT array node. + * @returns {string} The detected version ("2015" or "2019"). + */ +function detectWKT2Version(root) { + // Check for WKT2-2019-specific nodes + if (root.find((child) => Array.isArray(child) && child[0] === "USAGE")) { + return "2019"; // `USAGE` is specific to WKT2-2019 + } + + // Check for WKT2-2015-specific nodes + if (root.find((child) => Array.isArray(child) && child[0] === "CS")) { + return "2015"; // `CS` is valid in both, but default to 2015 unless `USAGE` is present + } + + if (root[0] === "BOUNDCRS" || root[0] === "PROJCRS" || root[0] === "GEOGCRS") { + return "2015"; // These are valid in both, but default to 2015 + } + + // Default to WKT2-2015 if no specific indicators are found + return "2015"; +} + +/** + * Builds a PROJJSON object from a WKT array structure. + * @param {Array} root The root WKT array node. + * @returns {Object} The PROJJSON object. + */ +export function buildPROJJSON(root) { + const version = detectWKT2Version(root); + const builder = version === "2019" ? PROJJSONBuilder2019 : PROJJSONBuilder2015; + return builder.convert(root); +} diff --git a/detectWKTVersion.js b/detectWKTVersion.js new file mode 100644 index 0000000..5f6b550 --- /dev/null +++ b/detectWKTVersion.js @@ -0,0 +1,36 @@ +/** + * Detects whether the WKT string is WKT1 or WKT2. + * @param {string} wkt The WKT string. + * @returns {string} The detected version ("WKT1" or "WKT2"). + */ +export function detectWKTVersion(wkt) { + // Normalize the WKT string for easier keyword matching + const normalizedWKT = wkt.toUpperCase(); + + // Check for WKT2-specific keywords + if ( + normalizedWKT.includes("PROJCRS") || + normalizedWKT.includes("GEOGCRS") || + normalizedWKT.includes("BOUNDCRS") || + normalizedWKT.includes("VERTCRS") || + normalizedWKT.includes("LENGTHUNIT") || + normalizedWKT.includes("ANGLEUNIT") || + normalizedWKT.includes("SCALEUNIT") + ) { + return "WKT2"; + } + + // Check for WKT1-specific keywords + if ( + normalizedWKT.includes("PROJCS") || + normalizedWKT.includes("GEOGCS") || + normalizedWKT.includes("LOCAL_CS") || + normalizedWKT.includes("VERT_CS") || + normalizedWKT.includes("UNIT") + ) { + return "WKT1"; + } + + // Default to WKT1 if no specific indicators are found + return "WKT1"; +} \ No newline at end of file diff --git a/index.js b/index.js index e5ac7c5..7bd6545 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,9 @@ -var D2R = 0.01745329251994329577; -import parser from './parser'; -import {sExpr} from './process'; +import { buildPROJJSON } from './buildPROJJSON.js'; +import { detectWKTVersion } from './detectWKTVersion.js'; +import parser from './parser.js'; +import {sExpr} from './process.js'; +import { transformPROJJSON } from './transformPROJJSON.js'; +import { applyProjectionDefaults, d2r } from './util.js'; var knownTypes = ['PROJECTEDCRS', 'PROJCRS', 'GEOGCS', 'GEOCCS', 'PROJCS', 'LOCAL_CS', 'GEODCRS', 'GEODETICCRS', 'GEODETICDATUM', 'ENGCRS', 'ENGINEERINGCRS']; @@ -16,10 +19,6 @@ function rename(obj, params) { } } -function d2r(input) { - return input * D2R; -} - function cleanWKT(wkt) { var keys = Object.keys(wkt); for (var i = 0, ii = keys.length; i 0 ? 90 : -90); - wkt.lat_ts = wkt.lat1; - } else if (!wkt.lat_ts && wkt.lat0 && wkt.projName === 'Polar_Stereographic') { - wkt.lat_ts = wkt.lat0; - wkt.lat0 = d2r(wkt.lat0 > 0 ? 90 : -90); - } + applyProjectionDefaults(wkt); } export default function(wkt) { + if (typeof wkt === 'object') { + return transformPROJJSON(wkt); + } + const version = detectWKTVersion(wkt); var lisp = parser(wkt); + if (version === 'WKT2') { + const projjson = buildPROJJSON(lisp); + return transformPROJJSON(projjson); + } var type = lisp[0]; var obj = {}; sExpr(lisp, obj); diff --git a/package.json b/package.json index d294d1e..6a0af4f 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "description": "wkt-parser ===", "main": "dist/wkt.cjs", "module": "index.js", + "type": "module", "scripts": { - "test": "node -r esm test.js", + "test": "node test.js", "build": "rollup -c", "pretest": "npm run build" }, diff --git a/test-fixtures.json b/test-fixtures.json index 339eb1d..8494792 100644 --- a/test-fixtures.json +++ b/test-fixtures.json @@ -103,6 +103,87 @@ "srsCode": "NZGD49 / New Zealand Map Grid" } }, { + "code": "PROJCRS[\"NAD83 / Massachusetts Mainland\",BASEGEOGCRS[\"NAD83\",DATUM[\"North American Datum 1983\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4269]],CONVERSION[\"SPCS83 Massachusetts Mainland zone (meter)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",41,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-71.5,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",42.6833333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",41.7166666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",200000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",750000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - Massachusetts onshore - counties of Barnstable; Berkshire; Bristol; Essex; Franklin; Hampden; Hampshire; Middlesex; Norfolk; Plymouth; Suffolk; Worcester.\"],BBOX[41.46,-73.5,42.89,-69.86]],ID[\"EPSG\",26986]]", + "value": { + "projName": "Lambert Conic Conformal (2SP)", + "name": "NAD83 / Massachusetts Mainland", + "srsCode": "NAD83 / Massachusetts Mainland", + "datumCode": "EPSG_4269", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "axis": "enu", + "title": "EPSG:26986", + "latitude_of_false_origin": 0.7155849933176752, + "longitude_of_false_origin": -1.247910415175946, + "latitude_of_1st_standard_parallel": 0.7449647023929126, + "latitude_of_2nd_standard_parallel": 0.7280931862903018, + "easting_at_false_origin": 200000, + "northing_at_false_origin": 750000, + "units": "meter", + "to_meter": 1, + "lat0": 0.7155849933176752, + "long0": -1.247910415175946, + "lat1": 0.7449647023929126, + "lat2": 0.7280931862903018, + "x0": 200000, + "y0": 750000 + } + }, { + "code": "PROJCRS[\"NAD83 / Massachusetts Mainland\",BASEGEOGCRS[\"NAD83\",DATUM[\"North American Datum 1983\",ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4269]],CONVERSION[\"SPCS83 Massachusetts Mainland zone (meter)\",METHOD[\"Lambert Conic Conformal (2SP)\",ID[\"EPSG\",9802]],PARAMETER[\"Latitude of false origin\",41,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8821]],PARAMETER[\"Longitude of false origin\",-71.5,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8822]],PARAMETER[\"Latitude of 1st standard parallel\",42.6833333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8823]],PARAMETER[\"Latitude of 2nd standard parallel\",41.7166666666667,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8824]],PARAMETER[\"Easting at false origin\",200000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8826]],PARAMETER[\"Northing at false origin\",750000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8827]]],CS[Cartesian,2],AXIS[\"easting (X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"United States (USA) - Massachusetts onshore - counties of Barnstable; Berkshire; Bristol; Essex; Franklin; Hampden; Hampshire; Middlesex; Norfolk; Plymouth; Suffolk; Worcester.\"],BBOX[41.46,-73.5,42.89,-69.86]],ID[\"EPSG\",26986]]", + "value": { + "projName": "Lambert Conic Conformal (2SP)", + "name": "NAD83 / Massachusetts Mainland", + "srsCode": "NAD83 / Massachusetts Mainland", + "datumCode": "EPSG_4269", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "axis": "enu", + "title": "EPSG:26986", + "latitude_of_false_origin": 0.7155849933176752, + "longitude_of_false_origin": -1.247910415175946, + "latitude_of_1st_standard_parallel": 0.7449647023929126, + "latitude_of_2nd_standard_parallel": 0.7280931862903018, + "easting_at_false_origin": 200000, + "northing_at_false_origin": 750000, + "units": "meter", + "to_meter": 1, + "lat0": 0.7155849933176752, + "long0": -1.247910415175946, + "lat1": 0.7449647023929126, + "lat2": 0.7280931862903018, + "x0": 200000, + "y0": 750000 + } + }, { + "code": {"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"ProjectedCRS","name":"NAD83 / Massachusetts Mainland","base_crs":{"type":"GeographicCRS","name":"NAD83","datum":{"type":"GeodeticReferenceFrame","name":"North American Datum 1983","ellipsoid":{"name":"GRS 1980","semi_major_axis":6378137,"inverse_flattening":298.257222101}},"coordinate_system":{"subtype":"ellipsoidal","axis":[{"name":"Geodetic latitude","abbreviation":"Lat","direction":"north","unit":"degree"},{"name":"Geodetic longitude","abbreviation":"Lon","direction":"east","unit":"degree"}]},"id":{"authority":"EPSG","code":4269}},"conversion":{"name":"SPCS83 Massachusetts Mainland zone (meter)","method":{"name":"Lambert Conic Conformal (2SP)","id":{"authority":"EPSG","code":9802}},"parameters":[{"name":"Latitude of false origin","value":41,"unit":"degree","id":{"authority":"EPSG","code":8821}},{"name":"Longitude of false origin","value":-71.5,"unit":"degree","id":{"authority":"EPSG","code":8822}},{"name":"Latitude of 1st standard parallel","value":42.6833333333333,"unit":"degree","id":{"authority":"EPSG","code":8823}},{"name":"Latitude of 2nd standard parallel","value":41.7166666666667,"unit":"degree","id":{"authority":"EPSG","code":8824}},{"name":"Easting at false origin","value":200000,"unit":"metre","id":{"authority":"EPSG","code":8826}},{"name":"Northing at false origin","value":750000,"unit":"metre","id":{"authority":"EPSG","code":8827}}]},"coordinate_system":{"subtype":"Cartesian","axis":[{"name":"Easting","abbreviation":"X","direction":"east","unit":"metre"},{"name":"Northing","abbreviation":"Y","direction":"north","unit":"metre"}]},"scope":"Engineering survey, topographic mapping.","area":"United States (USA) - Massachusetts onshore - counties of Barnstable; Berkshire; Bristol; Essex; Franklin; Hampden; Hampshire; Middlesex; Norfolk; Plymouth; Suffolk; Worcester.","bbox":{"south_latitude":41.46,"west_longitude":-73.5,"north_latitude":42.89,"east_longitude":-69.86},"id":{"authority":"EPSG","code":26986}}, + "value": { + "projName": "Lambert Conic Conformal (2SP)", + "name": "NAD83 / Massachusetts Mainland", + "srsCode": "NAD83 / Massachusetts Mainland", + "datumCode": "EPSG_4269", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "axis": "enu", + "units": "meter", + "title": "EPSG:26986", + "latitude_of_false_origin": 0.715584993317675, + "longitude_of_false_origin": -1.2479104151759457, + "latitude_of_1st_standard_parallel": 0.7449647023929125, + "latitude_of_2nd_standard_parallel": 0.7280931862903017, + "easting_at_false_origin": 200000, + "northing_at_false_origin": 750000, + "to_meter": 1, + "lat0": 0.715584993317675, + "long0": -1.2479104151759457, + "lat1": 0.7449647023929125, + "lat2": 0.7280931862903017, + "x0": 200000, + "y0": 750000 + } + }, { "code": "PROJCS[\"NAD83 / Massachusetts Mainland\",GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\",SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6269\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4269\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"standard_parallel_1\",42.68333333333333],PARAMETER[\"standard_parallel_2\",41.71666666666667],PARAMETER[\"latitude_of_origin\",41],PARAMETER[\"central_meridian\",-71.5],PARAMETER[\"false_easting\",200000],PARAMETER[\"false_northing\",750000],AUTHORITY[\"EPSG\",\"26986\"],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]]", "value": { "type": "PROJCS", @@ -189,6 +270,63 @@ "axis": "enu", "srsCode": "NAD83 / Massachusetts Mainland" } +}, { + "code": "PROJCRS[\"ETRS89-extended / LAEA Europe\",BASEGEOGCRS[\"ETRS89\",ENSEMBLE[\"European Terrestrial Reference System 1989 ensemble\",MEMBER[\"European Terrestrial Reference Frame 1989\"],MEMBER[\"European Terrestrial Reference Frame 1990\"],MEMBER[\"European Terrestrial Reference Frame 1991\"],MEMBER[\"European Terrestrial Reference Frame 1992\"],MEMBER[\"European Terrestrial Reference Frame 1993\"],MEMBER[\"European Terrestrial Reference Frame 1994\"],MEMBER[\"European Terrestrial Reference Frame 1996\"],MEMBER[\"European Terrestrial Reference Frame 1997\"],MEMBER[\"European Terrestrial Reference Frame 2000\"],MEMBER[\"European Terrestrial Reference Frame 2005\"],MEMBER[\"European Terrestrial Reference Frame 2014\"],MEMBER[\"European Terrestrial Reference Frame 2020\"],ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[0.1]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4258]],CONVERSION[\"Europe Equal Area 2001\",METHOD[\"Lambert Azimuthal Equal Area\",ID[\"EPSG\",9820]],PARAMETER[\"Latitude of natural origin\",52,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",10,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8802]],PARAMETER[\"False easting\",4321000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",3210000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8807]]],CS[Cartesian,2],AXIS[\"northing (Y)\",north,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"easting (X)\",east,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Statistical analysis.\"],AREA[\"Europe - European Union (EU) countries and candidates. Europe - onshore and offshore: Albania; Andorra; Austria; Belgium; Bosnia and Herzegovina; Bulgaria; Croatia; Cyprus; Czechia; Denmark; Estonia; Faroe Islands; Finland; France; Germany; Gibraltar; Greece; Hungary; Iceland; Ireland; Italy; Kosovo; Latvia; Liechtenstein; Lithuania; Luxembourg; Malta; Monaco; Montenegro; Netherlands; North Macedonia; Norway including Svalbard and Jan Mayen; Poland; Portugal including Madeira and Azores; Romania; San Marino; Serbia; Slovakia; Slovenia; Spain including Canary Islands; Sweden; Switzerland; Türkiye (Turkey); United Kingdom (UK) including Channel Islands and Isle of Man; Vatican City State.\"],BBOX[24.6,-35.58,84.73,44.83]],ID[\"EPSG\",3035]]", + "value": { + "projName": "Lambert Azimuthal Equal Area", + "axis": "neu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4258", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "x0": 4321000, + "y0": 3210000, + "long0": 0.17453292519943298, + "lat0": 0.9075712110370515, + "srsCode": "ETRS89-extended / LAEA Europe", + "name": "ETRS89-extended / LAEA Europe", + "title": "EPSG:3035" + } +}, { + "code": "PROJCRS[\"ETRS89-extended / LAEA Europe\",BASEGEOGCRS[\"ETRS89\",ENSEMBLE[\"European Terrestrial Reference System 1989 ensemble\", MEMBER[\"European Terrestrial Reference Frame 1989\", ID[\"EPSG\",1178]], MEMBER[\"European Terrestrial Reference Frame 1990\", ID[\"EPSG\",1179]], MEMBER[\"European Terrestrial Reference Frame 1991\", ID[\"EPSG\",1180]], MEMBER[\"European Terrestrial Reference Frame 1992\", ID[\"EPSG\",1181]], MEMBER[\"European Terrestrial Reference Frame 1993\", ID[\"EPSG\",1182]], MEMBER[\"European Terrestrial Reference Frame 1994\", ID[\"EPSG\",1183]], MEMBER[\"European Terrestrial Reference Frame 1996\", ID[\"EPSG\",1184]], MEMBER[\"European Terrestrial Reference Frame 1997\", ID[\"EPSG\",1185]], MEMBER[\"European Terrestrial Reference Frame 2000\", ID[\"EPSG\",1186]], MEMBER[\"European Terrestrial Reference Frame 2005\", ID[\"EPSG\",1204]], MEMBER[\"European Terrestrial Reference Frame 2014\", ID[\"EPSG\",1206]], MEMBER[\"European Terrestrial Reference Frame 2020\", ID[\"EPSG\",1382]], ELLIPSOID[\"GRS 1980\",6378137,298.257222101,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",7019]], ENSEMBLEACCURACY[0.1],ID[\"EPSG\",6258]],ID[\"EPSG\",4258]],CONVERSION[\"Europe Equal Area 2001\",METHOD[\"Lambert Azimuthal Equal Area\",ID[\"EPSG\",9820]],PARAMETER[\"Latitude of natural origin\",52,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",10,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8802]],PARAMETER[\"False easting\",4321000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",3210000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8807]],ID[\"EPSG\",19986]],CS[Cartesian,2,ID[\"EPSG\",4532]],AXIS[\"Northing (Y)\",north],AXIS[\"Easting (X)\",east],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",3035]]", + "value": { + "projName": "Lambert Azimuthal Equal Area", + "axis": "neu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4258", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "x0": 4321000, + "y0": 3210000, + "long0": 0.17453292519943298, + "lat0": 0.9075712110370515, + "srsCode": "ETRS89-extended / LAEA Europe", + "name": "ETRS89-extended / LAEA Europe", + "title": "EPSG:3035" + } +}, { + "code": {"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"ProjectedCRS","name":"ETRS89-extended / LAEA Europe","base_crs":{"type":"GeographicCRS","name":"ETRS89","datum_ensemble":{"name":"European Terrestrial Reference System 1989 ensemble","members":[{"name":"European Terrestrial Reference Frame 1989","id":{"authority":"EPSG","code":1178}},{"name":"European Terrestrial Reference Frame 1990","id":{"authority":"EPSG","code":1179}},{"name":"European Terrestrial Reference Frame 1991","id":{"authority":"EPSG","code":1180}},{"name":"European Terrestrial Reference Frame 1992","id":{"authority":"EPSG","code":1181}},{"name":"European Terrestrial Reference Frame 1993","id":{"authority":"EPSG","code":1182}},{"name":"European Terrestrial Reference Frame 1994","id":{"authority":"EPSG","code":1183}},{"name":"European Terrestrial Reference Frame 1996","id":{"authority":"EPSG","code":1184}},{"name":"European Terrestrial Reference Frame 1997","id":{"authority":"EPSG","code":1185}},{"name":"European Terrestrial Reference Frame 2000","id":{"authority":"EPSG","code":1186}},{"name":"European Terrestrial Reference Frame 2005","id":{"authority":"EPSG","code":1204}},{"name":"European Terrestrial Reference Frame 2014","id":{"authority":"EPSG","code":1206}},{"name":"European Terrestrial Reference Frame 2020","id":{"authority":"EPSG","code":1382}}],"ellipsoid":{"name":"GRS 1980","semi_major_axis":6378137,"inverse_flattening":298.257222101},"accuracy":"0.1","id":{"authority":"EPSG","code":6258}},"coordinate_system":{"subtype":"ellipsoidal","axis":[{"name":"Geodetic latitude","abbreviation":"Lat","direction":"north","unit":"degree"},{"name":"Geodetic longitude","abbreviation":"Lon","direction":"east","unit":"degree"}]},"id":{"authority":"EPSG","code":4258}},"conversion":{"name":"Europe Equal Area 2001","method":{"name":"Lambert Azimuthal Equal Area","id":{"authority":"EPSG","code":9820}},"parameters":[{"name":"Latitude of natural origin","value":52,"unit":"degree","id":{"authority":"EPSG","code":8801}},{"name":"Longitude of natural origin","value":10,"unit":"degree","id":{"authority":"EPSG","code":8802}},{"name":"False easting","value":4321000,"unit":"metre","id":{"authority":"EPSG","code":8806}},{"name":"False northing","value":3210000,"unit":"metre","id":{"authority":"EPSG","code":8807}}]},"coordinate_system":{"subtype":"Cartesian","axis":[{"name":"Northing","abbreviation":"Y","direction":"north","unit":"metre"},{"name":"Easting","abbreviation":"X","direction":"east","unit":"metre"}]},"scope":"Statistical analysis.","area":"Europe - European Union (EU) countries and candidates. Europe - onshore and offshore: Albania; Andorra; Austria; Belgium; Bosnia and Herzegovina; Bulgaria; Croatia; Cyprus; Czechia; Denmark; Estonia; Faroe Islands; Finland; France; Germany; Gibraltar; Greece; Hungary; Iceland; Ireland; Italy; Kosovo; Latvia; Liechtenstein; Lithuania; Luxembourg; Malta; Monaco; Montenegro; Netherlands; North Macedonia; Norway including Svalbard and Jan Mayen; Poland; Portugal including Madeira and Azores; Romania; San Marino; Serbia; Slovakia; Slovenia; Spain including Canary Islands; Sweden; Switzerland; Türkiye (Turkey); United Kingdom (UK) including Channel Islands and Isle of Man; Vatican City State.","bbox":{"south_latitude":24.6,"west_longitude":-35.58,"north_latitude":84.73,"east_longitude":44.83},"id":{"authority":"EPSG","code":3035}}, + "value": { + "projName": "Lambert Azimuthal Equal Area", + "axis": "neu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4258", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "x0": 4321000, + "y0": 3210000, + "long0": 0.17453292519943295, + "lat0": 0.9075712110370514, + "srsCode": "ETRS89-extended / LAEA Europe", + "name": "ETRS89-extended / LAEA Europe", + "title": "EPSG:3035" + } }, { "code": "PROJCS[\"ETRS89 / ETRS-LAEA\",GEOGCS[\"ETRS89\",DATUM[\"European_Terrestrial_Reference_System_1989\",SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6258\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4258\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Lambert_Azimuthal_Equal_Area\"],PARAMETER[\"latitude_of_center\",52],PARAMETER[\"longitude_of_center\",10],PARAMETER[\"false_easting\",4321000],PARAMETER[\"false_northing\",3210000],AUTHORITY[\"EPSG\",\"3035\"],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH]]", "value": { @@ -337,8 +475,8 @@ "axis": "neu", "srsCode": "ETRS89" } -}, -{ +}, { + "strict": true, "code": "COMPD_CS[\"unknown\",PROJCS[\"NAD83 / Texas North Central\",GEOGCS[\"NAD83\",DATUM[\"North_American_Datum_1983\",SPHEROID[\"GRS 1980\",6378137,298.257222101004,AUTHORITY[\"EPSG\",\"7019\"]],AUTHORITY[\"EPSG\",\"6269\"]],PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4269\"]],PROJECTION[\"Lambert_Conformal_Conic_2SP\"],PARAMETER[\"latitude_of_origin\",31.6666666666667],PARAMETER[\"central_meridian\",-98.5],PARAMETER[\"standard_parallel_1\",32.1333333333333],PARAMETER[\"standard_parallel_2\",33.9666666666667],PARAMETER[\"false_easting\",1968500],PARAMETER[\"false_northing\",6561666.66666667],UNIT[\"US survey foot\",0.304800609601219,AUTHORITY[\"EPSG\",\"9003\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH],AUTHORITY[\"EPSG\",\"32138\"]],VERT_CS[\"NAVD88 height (ftUS)\",VERT_DATUM[\"North American Vertical Datum 1988\",2005,AUTHORITY[\"EPSG\",\"5103\"]],UNIT[\"US survey foot\",0.304800609601219,AUTHORITY[\"EPSG\",\"9003\"]],AXIS[\"Up\",UP],AUTHORITY[\"EPSG\",\"6360\"]]]", "value": { "type": "COMPD_CS", @@ -458,8 +596,27 @@ } } } -}, -{ +}, { + "code": "PROJCRS[\"WGS 84 / Antarctic Polar Stereographic\",BASEGEOGCRS[\"WGS 84\",ENSEMBLE[\"World Geodetic System 1984 ensemble\",MEMBER[\"World Geodetic System 1984 (Transit)\"],MEMBER[\"World Geodetic System 1984 (G730)\"],MEMBER[\"World Geodetic System 1984 (G873)\"],MEMBER[\"World Geodetic System 1984 (G1150)\"],MEMBER[\"World Geodetic System 1984 (G1674)\"],MEMBER[\"World Geodetic System 1984 (G1762)\"],MEMBER[\"World Geodetic System 1984 (G2139)\"],MEMBER[\"World Geodetic System 1984 (G2296)\"],ELLIPSOID[\"WGS 84\",6378137,298.257223563,LENGTHUNIT[\"metre\",1]],ENSEMBLEACCURACY[2.0]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4326]],CONVERSION[\"Antarctic Polar Stereographic\",METHOD[\"Polar Stereographic (variant B)\",ID[\"EPSG\",9829]],PARAMETER[\"Latitude of standard parallel\",-71,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8832]],PARAMETER[\"Longitude of origin\",0,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8833]],PARAMETER[\"False easting\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8807]]],CS[Cartesian,2],AXIS[\"(E)\",north,MERIDIAN[90,ANGLEUNIT[\"degree\",0.0174532925199433]],ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"(N)\",north,MERIDIAN[0,ANGLEUNIT[\"degree\",0.0174532925199433]],ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Antarctic Digital Database and small scale topographic mapping.\"],AREA[\"Antarctica.\"],BBOX[-90,-180,-60,180]],ID[\"EPSG\",3031]]", + "value": { + "projName": "Polar Stereographic (variant B)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4326", + "ellps": "WGS 84", + "a": 6378137, + "rf": 298.257223563, + "x0": 0, + "y0": 0, + "long0": 0, + "lat0": -1.5707963267948966, + "lat_ts": -1.2391837689159741, + "srsCode": "WGS 84 / Antarctic Polar Stereographic", + "name": "WGS 84 / Antarctic Polar Stereographic", + "title": "EPSG:3031" + } +}, { "code": "PROJCS[\"WGS 84 / Antarctic Polar Stereographic\",GEOGCS[\"WGS 84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],PROJECTION[\"Polar_Stereographic\"],PARAMETER[\"latitude_of_origin\",-71],PARAMETER[\"central_meridian\",0],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],AUTHORITY[\"EPSG\",\"3031\"],AXIS[\"Easting\",UNKNOWN],AXIS[\"Northing\",UNKNOWN]]", "value": { "type": "PROJCS", @@ -551,5 +708,911 @@ "srsCode": "WGS 84 / Antarctic Polar Stereographic", "lat_ts": -1.239183768915974 } +}, { + "code": "PROJCRS[\"CH1903 / LV03\",BASEGEOGCRS[\"CH1903\",DATUM[\"CH1903\",ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4149]],CONVERSION[\"Swiss Oblique Mercator 1903M\",METHOD[\"Hotine Oblique Mercator (variant B)\",ID[\"EPSG\",9815]],PARAMETER[\"Latitude of projection centre\",46.9524055555556,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8811]],PARAMETER[\"Longitude of projection centre\",7.43958333333333,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8812]],PARAMETER[\"Azimuth at projection centre\",90,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8813]],PARAMETER[\"Angle from Rectified to Skew Grid\",90,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8814]],PARAMETER[\"Scale factor at projection centre\",1,SCALEUNIT[\"unity\",1],ID[\"EPSG\",8815]],PARAMETER[\"Easting at projection centre\",600000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8816]],PARAMETER[\"Northing at projection centre\",200000,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8817]]],CS[Cartesian,2],AXIS[\"easting (Y)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"northing (X)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Cadastre, engineering survey, topographic mapping (large and medium scale).\"],AREA[\"Liechtenstein; Switzerland.\"],BBOX[45.82,5.96,47.81,10.49]],ID[\"EPSG\",21781]]", + "value": { + "projName": "Hotine Oblique Mercator (variant B)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4149", + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 600000, + "y0": 200000, + "longc": 0.12984522414316144, + "lat0": 0.8194740686761227, + "alpha": 1.570796326794897, + "rectified_grid_angle": 1.570796326794897, + "srsCode": "CH1903 / LV03", + "name": "CH1903 / LV03", + "title": "EPSG:21781" + } +}, { + "code": "PROJCRS[\"CH1903 / LV03\",BASEGEOGCRS[\"CH1903\",DATUM[\"CH1903\",ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",7004]],ID[\"EPSG\",6149]],ID[\"EPSG\",4149]],CONVERSION[\"Swiss Oblique Mercator 1903M\",METHOD[\"Hotine Oblique Mercator (variant B)\",ID[\"EPSG\",9815]],PARAMETER[\"Latitude of projection centre\",46.9524055555558,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8811]],PARAMETER[\"Longitude of projection centre\",7.43958333333361,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8812]],PARAMETER[\"Azimuth at projection centre\",90,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8813]],PARAMETER[\"Angle from Rectified to Skew Grid\",90,ANGLEUNIT[\"degree\",0.0174532925199433,ID[\"EPSG\",9102]],ID[\"EPSG\",8814]],PARAMETER[\"Scale factor at projection centre\",1,SCALEUNIT[\"unity\",1,ID[\"EPSG\",9201]],ID[\"EPSG\",8815]],PARAMETER[\"Easting at projection centre\",600000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8816]],PARAMETER[\"Northing at projection centre\",200000,LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",8817]],ID[\"EPSG\",19922]],CS[Cartesian,2,ID[\"EPSG\",4498]],AXIS[\"Easting (Y)\",east],AXIS[\"Northing (X)\",north],LENGTHUNIT[\"metre\",1,ID[\"EPSG\",9001]],ID[\"EPSG\",21781]]", + "value": { + "projName": "Hotine Oblique Mercator (variant B)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4149", + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 600000, + "y0": 200000, + "longc": 0.12984522414316635, + "lat0": 0.8194740686761263, + "alpha": 1.570796326794897, + "rectified_grid_angle": 1.570796326794897, + "srsCode": "CH1903 / LV03", + "name": "CH1903 / LV03", + "title": "EPSG:21781" + } +}, { + "code": {"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"ProjectedCRS","name":"CH1903 / LV03","base_crs":{"type":"GeographicCRS","name":"CH1903","datum":{"type":"GeodeticReferenceFrame","name":"CH1903","ellipsoid":{"name":"Bessel 1841","semi_major_axis":6377397.155,"inverse_flattening":299.1528128}},"coordinate_system":{"subtype":"ellipsoidal","axis":[{"name":"Geodetic latitude","abbreviation":"Lat","direction":"north","unit":"degree"},{"name":"Geodetic longitude","abbreviation":"Lon","direction":"east","unit":"degree"}]},"id":{"authority":"EPSG","code":4149}},"conversion":{"name":"Swiss Oblique Mercator 1903M","method":{"name":"Hotine Oblique Mercator (variant B)","id":{"authority":"EPSG","code":9815}},"parameters":[{"name":"Latitude of projection centre","value":46.9524055555556,"unit":"degree","id":{"authority":"EPSG","code":8811}},{"name":"Longitude of projection centre","value":7.43958333333333,"unit":"degree","id":{"authority":"EPSG","code":8812}},{"name":"Azimuth at projection centre","value":90,"unit":"degree","id":{"authority":"EPSG","code":8813}},{"name":"Angle from Rectified to Skew Grid","value":90,"unit":"degree","id":{"authority":"EPSG","code":8814}},{"name":"Scale factor at projection centre","value":1,"unit":"unity","id":{"authority":"EPSG","code":8815}},{"name":"Easting at projection centre","value":600000,"unit":"metre","id":{"authority":"EPSG","code":8816}},{"name":"Northing at projection centre","value":200000,"unit":"metre","id":{"authority":"EPSG","code":8817}}]},"coordinate_system":{"subtype":"Cartesian","axis":[{"name":"Easting","abbreviation":"Y","direction":"east","unit":"metre"},{"name":"Northing","abbreviation":"X","direction":"north","unit":"metre"}]},"scope":"Cadastre, engineering survey, topographic mapping (large and medium scale).","area":"Liechtenstein; Switzerland.","bbox":{"south_latitude":45.82,"west_longitude":5.96,"north_latitude":47.81,"east_longitude":10.49},"id":{"authority":"EPSG","code":21781}}, + "value": { + "projName": "Hotine Oblique Mercator (variant B)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4149", + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 600000, + "y0": 200000, + "longc": 0.1298452241431614, + "lat0": 0.8194740686761226, + "alpha": 1.5707963267948966, + "rectified_grid_angle": 1.5707963267948966, + "srsCode": "CH1903 / LV03", + "name": "CH1903 / LV03", + "title": "EPSG:21781" + } +}, { + "code": "PROJCS[\"CH1903 / LV03\",GEOGCS[\"CH1903\",DATUM[\"CH1903\",SPHEROID[\"Bessel 1841\",6377397.155,299.1528128,AUTHORITY[\"EPSG\",\"7004\"]],TOWGS84[674.4,15.1,405.3,0,0,0,0],AUTHORITY[\"EPSG\",\"6149\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4149\"]],PROJECTION[\"Hotine_Oblique_Mercator_Azimuth_Center\"],PARAMETER[\"latitude_of_center\",46.95240555555556],PARAMETER[\"longitude_of_center\",7.439583333333333],PARAMETER[\"azimuth\",90],PARAMETER[\"rectified_grid_angle\",90],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",600000],PARAMETER[\"false_northing\",200000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Y\",EAST],AXIS[\"X\",NORTH],AUTHORITY[\"EPSG\",\"21781\"]]", + "value": { + "projName": "Hotine_Oblique_Mercator_Azimuth_Center", + "units": "meter", + "to_meter": 1, + "datumCode": "ch1903", + "datum_params": [ + 674.4, 15.1, 405.3, + 0, 0, 0, + 0 + ], + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 600000, + "y0": 200000, + "longc": 0.12984522414316146, + "lat0": 0.8194740686761218, + "alpha": 1.5707963267948966, + "rectified_grid_angle": 1.5707963267948966, + "srsCode": "CH1903 / LV03", + "name": "CH1903 / LV03", + "title": "EPSG:21781" + } +}, { + "code": "PROJCRS[\"Mount Dillon / Tobago Grid\",BASEGEOGCRS[\"Mount Dillon\",DATUM[\"Mount Dillon\",ELLIPSOID[\"Clarke 1858\",20926348,294.260676369261,LENGTHUNIT[\"Clarke's foot\",0.3047972654]]],PRIMEM[\"Greenwich\",0,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4157]],CONVERSION[\"Tobago Grid\",METHOD[\"Cassini-Soldner\",ID[\"EPSG\",9806]],PARAMETER[\"Latitude of natural origin\",11.2521786111111,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8801]],PARAMETER[\"Longitude of natural origin\",-60.6860088888889,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8802]],PARAMETER[\"False easting\",187500,LENGTHUNIT[\"Clarke's link\",0.201166195164],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",180000,LENGTHUNIT[\"Clarke's link\",0.201166195164],ID[\"EPSG\",8807]]],CS[Cartesian,2],AXIS[\"(E)\",east,ORDER[1],LENGTHUNIT[\"Clarke's link\",0.201166195164]],AXIS[\"(N)\",north,ORDER[2],LENGTHUNIT[\"Clarke's link\",0.201166195164]],USAGE[SCOPE[\"Engineering survey, topographic mapping.\"],AREA[\"Trinidad and Tobago - Tobago - onshore.\"],BBOX[11.08,-60.9,11.41,-60.44]],ID[\"EPSG\",2066]]", + "value": { + "projName": "Cassini-Soldner", + "axis": "enu", + "units": "clarke's link", + "to_meter": 0.201166195164, + "datumCode": "EPSG_4157", + "ellps": "Clarke 1858", + "a": 6378293.645208759, + "rf": 294.260676369261, + "x0": 37718.66159325, + "y0": 36209.91512952, + "long0": -1.0591706650056572, + "lat0": 0.19638756478637134, + "srsCode": "Mount Dillon / Tobago Grid", + "name": "Mount Dillon / Tobago Grid", + "title": "EPSG:2066" + } +}, { + "code": {"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"ProjectedCRS","name":"Mount Dillon / Tobago Grid","base_crs":{"type":"GeographicCRS","name":"Mount Dillon","datum":{"type":"GeodeticReferenceFrame","name":"Mount Dillon","ellipsoid":{"name":"Clarke 1858","semi_major_axis":{"value":20926348,"unit":{"type":"LinearUnit","name":"Clarke's foot","conversion_factor":0.3047972654}},"semi_minor_axis":{"value":20855233,"unit":{"type":"LinearUnit","name":"Clarke's foot","conversion_factor":0.3047972654}}}},"coordinate_system":{"subtype":"ellipsoidal","axis":[{"name":"Geodetic latitude","abbreviation":"Lat","direction":"north","unit":"degree"},{"name":"Geodetic longitude","abbreviation":"Lon","direction":"east","unit":"degree"}]},"id":{"authority":"EPSG","code":4157}},"conversion":{"name":"Tobago Grid","method":{"name":"Cassini-Soldner","id":{"authority":"EPSG","code":9806}},"parameters":[{"name":"Latitude of natural origin","value":11.2521786111111,"unit":"degree","id":{"authority":"EPSG","code":8801}},{"name":"Longitude of natural origin","value":-60.6860088888889,"unit":"degree","id":{"authority":"EPSG","code":8802}},{"name":"False easting","value":187500,"unit":{"type":"LinearUnit","name":"Clarke's link","conversion_factor":0.201166195164},"id":{"authority":"EPSG","code":8806}},{"name":"False northing","value":180000,"unit":{"type":"LinearUnit","name":"Clarke's link","conversion_factor":0.201166195164},"id":{"authority":"EPSG","code":8807}}]},"coordinate_system":{"subtype":"Cartesian","axis":[{"name":"Easting","abbreviation":"E","direction":"east","unit":{"type":"LinearUnit","name":"Clarke's link","conversion_factor":0.201166195164}},{"name":"Northing","abbreviation":"N","direction":"north","unit":{"type":"LinearUnit","name":"Clarke's link","conversion_factor":0.201166195164}}]},"scope":"Engineering survey, topographic mapping.","area":"Trinidad and Tobago - Tobago - onshore.","bbox":{"south_latitude":11.08,"west_longitude":-60.9,"north_latitude":11.41,"east_longitude":-60.44},"id":{"authority":"EPSG","code":2066}}, + "value": { + "projName": "Cassini-Soldner", + "axis": "enu", + "units": "clarke's link", + "to_meter": 0.201166195164, + "datumCode": "EPSG_4157", + "ellps": "Clarke 1858", + "a": 6378293.645208759, + "rf": 294.26067636926064, + "x0": 37718.66159325, + "y0": 36209.91512952, + "long0": -1.059170665005657, + "lat0": 0.19638756478637132, + "srsCode": "Mount Dillon / Tobago Grid", + "name": "Mount Dillon / Tobago Grid", + "title": "EPSG:2066" + } +}, { + "code": "PROJCS[\"Mount Dillon / Tobago Grid\",GEOGCS[\"Mount Dillon\",DATUM[\"Mount_Dillon\",SPHEROID[\"Clarke 1858\",6378293.645208759,294.2606763692654,AUTHORITY[\"EPSG\",\"7007\"]],AUTHORITY[\"EPSG\",\"6157\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.01745329251994328,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4157\"]],UNIT[\"Clarke's link\",0.201166195164,AUTHORITY[\"EPSG\",\"9039\"]],PROJECTION[\"Cassini_Soldner\"],PARAMETER[\"latitude_of_origin\",11.25217861111111],PARAMETER[\"central_meridian\",-60.68600888888889],PARAMETER[\"false_easting\",187500],PARAMETER[\"false_northing\",180000],AUTHORITY[\"EPSG\",\"2066\"],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "value": { + "projName": "Cassini_Soldner", + "axis": "enu", + "units": "clarke's link", + "to_meter": 0.201166195164, + "datumCode": "mount_dillon", + "ellps": "Clarke 1858", + "a": 6378293.645208759, + "rf": 294.2606763692654, + "x0": 37718.66159325, + "y0": 36209.91512952, + "long0": -1.059170665005657, + "lat0": 0.19638756478637148, + "srsCode": "Mount Dillon / Tobago Grid", + "name": "Mount Dillon / Tobago Grid", + "title": "EPSG:2066" + } +}, { + "code": {"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json","type":"ProjectedCRS","name":"World Equidistant Cylindrical (Sphere)","base_crs":{"type":"GeographicCRS","name":"Unspecified datum based upon the GRS 1980 Authalic Sphere","datum":{"type":"GeodeticReferenceFrame","name":"Not specified (based on GRS 1980 Authalic Sphere)","ellipsoid":{"name":"GRS 1980 Authalic Sphere","radius":6371007}},"coordinate_system":{"subtype":"ellipsoidal","axis":[{"name":"Geodetic latitude","abbreviation":"Lat","direction":"north","unit":"degree"},{"name":"Geodetic longitude","abbreviation":"Lon","direction":"east","unit":"degree"}]},"id":{"authority":"EPSG","code":4047}},"conversion":{"name":"World Equidistant Cylindrical (Sphere)","method":{"name":"Equidistant Cylindrical (Spherical)","id":{"authority":"EPSG","code":1029}},"parameters":[{"name":"Latitude of 1st standard parallel","value":0,"unit":"degree","id":{"authority":"EPSG","code":8823}},{"name":"Longitude of natural origin","value":0,"unit":"degree","id":{"authority":"EPSG","code":8802}},{"name":"False easting","value":0,"unit":"metre","id":{"authority":"EPSG","code":8806}},{"name":"False northing","value":0,"unit":"metre","id":{"authority":"EPSG","code":8807}}]},"coordinate_system":{"subtype":"Cartesian","axis":[{"name":"Easting","abbreviation":"X","direction":"east","unit":"metre"},{"name":"Northing","abbreviation":"Y","direction":"north","unit":"metre"}]},"scope":"Web mapping and visualisation.","area":"World.","bbox":{"south_latitude":-90,"west_longitude":-180,"north_latitude":90,"east_longitude":180},"id":{"authority":"EPSG","code":4088}}, + "value": { + "projName": "Equidistant Cylindrical (Spherical)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4047", + "ellps": "GRS 1980 Authalic Sphere", + "a": 6371007, + "rf": 0, + "x0": 0, + "y0": 0, + "lat1": 0, + "long0": 0, + "srsCode": "World Equidistant Cylindrical (Sphere)", + "name": "World Equidistant Cylindrical (Sphere)", + "title": "EPSG:4088" + } +}, +{ + "code": "PROJCS[\"World Equidistant Cylindrical (Sphere)\",GEOGCS[\"Unspecified datum based upon the GRS 1980 Authalic Sphere\",DATUM[\"D_\",SPHEROID[\"GRS_1980_Authalic_Sphere\",6371007,0]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]],PROJECTION[\"Equidistant_Cylindrical\"],PARAMETER[\"central_meridian\",0],PARAMETER[\"false_easting\",0],PARAMETER[\"false_northing\",0],UNIT[\"Meter\",1]]", + "value": { + "projName": "Equidistant_Cylindrical", + "units": "meter", + "to_meter": 1, + "datumCode": "", + "ellps": "GRS80_Authalic_Sphere", + "a": 6371007, + "rf": 0, + "x0": 0, + "y0": 0, + "long0": 0, + "srsCode": "World Equidistant Cylindrical (Sphere)", + "name": "World Equidistant Cylindrical (Sphere)" + } +}, { + "code": "PROJCRS[\"S-JTSK (Ferro) / Krovak\",BASEGEOGCRS[\"S-JTSK (Ferro)\",DATUM[\"System of the Unified Trigonometrical Cadastral Network (Ferro)\",ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128,LENGTHUNIT[\"metre\",1]]],PRIMEM[\"Ferro\",-17.6666666666667,ANGLEUNIT[\"degree\",0.0174532925199433]],ID[\"EPSG\",4818]],CONVERSION[\"Krovak\",METHOD[\"Krovak\",ID[\"EPSG\",9819]],PARAMETER[\"Latitude of projection centre\",49.5,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8811]],PARAMETER[\"Longitude of origin\",42.5,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8833]],PARAMETER[\"Co-latitude of cone axis\",30.2881397527778,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",1036]],PARAMETER[\"Latitude of pseudo standard parallel\",78.5,ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8818]],PARAMETER[\"Scale factor on pseudo standard parallel\",0.9999,SCALEUNIT[\"unity\",1],ID[\"EPSG\",8819]],PARAMETER[\"False easting\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8806]],PARAMETER[\"False northing\",0,LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8807]]],CS[Cartesian,2],AXIS[\"southing (X)\",south,ORDER[1],LENGTHUNIT[\"metre\",1]],AXIS[\"westing (Y)\",west,ORDER[2],LENGTHUNIT[\"metre\",1]],USAGE[SCOPE[\"Cadastre in Czechia.\"],AREA[\"Czechia; Slovakia.\"],BBOX[47.73,12.09,51.06,22.56]],ID[\"EPSG\",2065]]", + "value": { + "projName": "Krovak", + "axis": "swu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4818", + "ellps": "Bessel 1841", + "from_greenwich": -0.30834150118566545, + "a": 6377397.155, + "rf": 299.1528128, + "k0": 0.9999, + "x0": 0, + "y0": 0, + "long0": 0.7417649320975902, + "lat0": 0.8639379797371933, + "srsCode": "S-JTSK (Ferro) / Krovak", + "name": "S-JTSK (Ferro) / Krovak", + "title": "EPSG:2065" + } +}, { + "code": "PROJCS[\"GDM2000 / Peninsula RSO\", GEOGCS[\"GDM2000\", DATUM[\"Geodetic_Datum_of_Malaysia_2000\", SPHEROID[\"GRS 1980\",6378137,298.257222101, AUTHORITY[\"EPSG\",\"7019\"]], AUTHORITY[\"EPSG\",\"6742\"]], PRIMEM[\"Greenwich\",0, AUTHORITY[\"EPSG\",\"8901\"]], UNIT[\"degree\",0.0174532925199433, AUTHORITY[\"EPSG\",\"9122\"]], AUTHORITY[\"EPSG\",\"4742\"]], PROJECTION[\"Hotine_Oblique_Mercator\"], PARAMETER[\"latitude_of_center\",4], PARAMETER[\"longitude_of_center\",102.25], PARAMETER[\"azimuth\",323.0257964666666], PARAMETER[\"rectified_grid_angle\",323.1301023611111], PARAMETER[\"scale_factor\",0.99984], PARAMETER[\"false_easting\",804671], PARAMETER[\"false_northing\",0], UNIT[\"metre\",1, AUTHORITY[\"EPSG\",\"9001\"]], AXIS[\"Easting\",EAST], AXIS[\"Northing\",NORTH], AUTHORITY[\"EPSG\",\"3375\"]]", + "value": { + "projName": "Hotine_Oblique_Mercator", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "geodetic_datum_of_malaysia_2000", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "k0": 0.99984, + "x0": 804671, + "y0": 0, + "longc": 1.784599160164202, + "lat0": 0.06981317007977318, + "alpha": 5.637863717220397, + "rectified_grid_angle": 5.639684198507691, + "srsCode": "GDM2000 / Peninsula RSO", + "name": "GDM2000 / Peninsula RSO", + "title": "EPSG:3375" + } +}, { + "code": { + "$schema": "https://proj.org/schemas/v0.7/projjson.schema.json", + "type": "ProjectedCRS", + "name": "GDM2000 / Peninsula RSO", + "base_crs": { + "type": "GeographicCRS", + "name": "GDM2000", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "Geodetic Datum of Malaysia 2000", + "ellipsoid": { + "name": "GRS 1980", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257222101 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Geodetic latitude", + "abbreviation": "Lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Geodetic longitude", + "abbreviation": "Lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4742 + } + }, + "conversion": { + "name": "Peninsular RSO", + "method": { + "name": "Hotine Oblique Mercator (variant A)", + "id": { + "authority": "EPSG", + "code": 9812 + } + }, + "parameters": [ + { + "name": "Latitude of projection centre", + "value": 4, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8811 + } + }, + { + "name": "Longitude of projection centre", + "value": 102.25, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8812 + } + }, + { + "name": "Azimuth at projection centre", + "value": 323.025796466667, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8813 + } + }, + { + "name": "Angle from Rectified to Skew Grid", + "value": 323.130102361111, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8814 + } + }, + { + "name": "Scale factor at projection centre", + "value": 0.99984, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8815 + } + }, + { + "name": "False easting", + "value": 804671, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": 0, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + }, + "scope": "Engineering survey, topographic mapping.", + "area": "Malaysia - West Malaysia onshore and offshore.", + "bbox": { + "south_latitude": 1.13, + "west_longitude": 98.02, + "north_latitude": 7.81, + "east_longitude": 105.82 + }, + "id": { + "authority": "EPSG", + "code": 3375 + } + }, + "value": { + "projName": "Hotine Oblique Mercator (variant A)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "EPSG_4742", + "ellps": "GRS 1980", + "a": 6378137, + "rf": 298.257222101, + "k0": 0.99984, + "x0": 804671, + "y0": 0, + "longc": 1.784599160164202, + "lat0": 0.06981317007977318, + "alpha": 5.637863717220403, + "rectified_grid_angle": 5.63968419850769, + "srsCode": "GDM2000 / Peninsula RSO", + "name": "GDM2000 / Peninsula RSO", + "title": "EPSG:3375" + } +}, { + "code": "PROJCS[\"unknown\",GEOGCS[\"unknown\",DATUM[\"Unknown based on Bessel 1841 ellipsoid\",SPHEROID[\"Bessel 1841\",6377397.155,299.1528128],TOWGS84[601.705,84.263,485.227,-4.7354,-1.3145,-5.393,-2.3887]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",0],PARAMETER[\"central_meridian\",16.3333333333333],PARAMETER[\"scale_factor\",1],PARAMETER[\"false_easting\",750000],PARAMETER[\"false_northing\",-5000000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AXIS[\"Easting\",EAST],AXIS[\"Northing\",NORTH]]", + "value": { + "projName": "Transverse_Mercator", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "unknown based on bessel 1841 ellipsoid", + "datum_params": [ + 601.705, + 84.263, + 485.227, + -4.7354, + -1.3145, + -5.393, + -2.3887 + ], + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 750000, + "y0": -5000000, + "long0": 0.2850704444924066, + "lat0": 0, + "srsCode": "unknown", + "name": "unknown" + } +}, { + "code": "BOUNDCRS[ SOURCECRS[ PROJCRS[\"unknown\", BASEGEOGCRS[\"unknown\", DATUM[\"Unknown based on Bessel 1841 ellipsoid\", ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128, LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]]], PRIMEM[\"Greenwich\",0, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8901]]], CONVERSION[\"unknown\", METHOD[\"Transverse Mercator\", ID[\"EPSG\",9807]], PARAMETER[\"Latitude of natural origin\",0, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8801]], PARAMETER[\"Longitude of natural origin\",16.3333333333333, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8802]], PARAMETER[\"Scale factor at natural origin\",1, SCALEUNIT[\"unity\",1], ID[\"EPSG\",8805]], PARAMETER[\"False easting\",750000, LENGTHUNIT[\"metre\",1], ID[\"EPSG\",8806]], PARAMETER[\"False northing\",-5000000, LENGTHUNIT[\"metre\",1], ID[\"EPSG\",8807]]], CS[Cartesian,2], AXIS[\"(E)\",east, ORDER[1], LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]], AXIS[\"(N)\",north, ORDER[2], LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]]]], TARGETCRS[ GEOGCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", ELLIPSOID[\"WGS 84\",6378137,298.257223563, LENGTHUNIT[\"metre\",1]]], PRIMEM[\"Greenwich\",0, ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal,2], AXIS[\"latitude\",north, ORDER[1], ANGLEUNIT[\"degree\",0.0174532925199433]], AXIS[\"longitude\",east, ORDER[2], ANGLEUNIT[\"degree\",0.0174532925199433]], ID[\"EPSG\",4326]]], ABRIDGEDTRANSFORMATION[\"Transformation from unknown to WGS84\", METHOD[\"Position Vector transformation (geog2D domain)\", ID[\"EPSG\",9606]], PARAMETER[\"X-axis translation\",601.705, ID[\"EPSG\",8605]], PARAMETER[\"Y-axis translation\",84.263, ID[\"EPSG\",8606]], PARAMETER[\"Z-axis translation\",485.227, ID[\"EPSG\",8607]], PARAMETER[\"X-axis rotation\",-4.7354, ID[\"EPSG\",8608]], PARAMETER[\"Y-axis rotation\",-1.3145, ID[\"EPSG\",8609]], PARAMETER[\"Z-axis rotation\",-5.393, ID[\"EPSG\",8610]], PARAMETER[\"Scale difference\",0.9999976113, ID[\"EPSG\",8611]]]]", + "value": { + "projName": "Transverse Mercator", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "unknown", + "datum_params": [601.705, 84.263, 485.227, -4.7354, -1.3145, -5.393, -2.3887], + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 750000, + "y0": -5000000, + "long0": 0.28507044449240665, + "lat0": 0, + "srsCode": "unknown", + "name": "unknown" + } +}, { + "code": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "BoundCRS", + "source_crs": { + "type": "ProjectedCRS", + "name": "unknown", + "base_crs": { + "name": "unknown", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "Unknown based on Bessel 1841 ellipsoid", + "ellipsoid": { + "name": "Bessel 1841", + "semi_major_axis": 6377397.155, + "inverse_flattening": 299.1528128 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Longitude", + "abbreviation": "lon", + "direction": "east", + "unit": "degree" + }, + { + "name": "Latitude", + "abbreviation": "lat", + "direction": "north", + "unit": "degree" + } + ] + } + }, + "conversion": { + "name": "unknown", + "method": { + "name": "Transverse Mercator", + "id": { + "authority": "EPSG", + "code": 9807 + } + }, + "parameters": [ + { + "name": "Latitude of natural origin", + "value": 0, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8801 + } + }, + { + "name": "Longitude of natural origin", + "value": 16.3333333333333, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8802 + } + }, + { + "name": "Scale factor at natural origin", + "value": 1, + "unit": "unity", + "id": { + "authority": "EPSG", + "code": 8805 + } + }, + { + "name": "False easting", + "value": 750000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8806 + } + }, + { + "name": "False northing", + "value": -5000000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8807 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + } + }, + "target_crs": { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Latitude", + "abbreviation": "lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Longitude", + "abbreviation": "lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "transformation": { + "name": "Transformation from unknown to WGS84", + "method": { + "name": "Position Vector transformation (geog2D domain)", + "id": { + "authority": "EPSG", + "code": 9606 + } + }, + "parameters": [ + { + "name": "X-axis translation", + "value": 601.705, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8605 + } + }, + { + "name": "Y-axis translation", + "value": 84.263, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8606 + } + }, + { + "name": "Z-axis translation", + "value": 485.227, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8607 + } + }, + { + "name": "X-axis rotation", + "value": -4.7354, + "unit": { + "type": "AngularUnit", + "name": "arc-second", + "conversion_factor": 4.84813681109536e-06 + }, + "id": { + "authority": "EPSG", + "code": 8608 + } + }, + { + "name": "Y-axis rotation", + "value": -1.3145, + "unit": { + "type": "AngularUnit", + "name": "arc-second", + "conversion_factor": 4.84813681109536e-06 + }, + "id": { + "authority": "EPSG", + "code": 8609 + } + }, + { + "name": "Z-axis rotation", + "value": -5.393, + "unit": { + "type": "AngularUnit", + "name": "arc-second", + "conversion_factor": 4.84813681109536e-06 + }, + "id": { + "authority": "EPSG", + "code": 8610 + } + }, + { + "name": "Scale difference", + "value": -2.3887, + "unit": { + "type": "ScaleUnit", + "name": "parts per million", + "conversion_factor": 1e-06 + }, + "id": { + "authority": "EPSG", + "code": 8611 + } + } + ] + } + }, + "value": { + "projName": "Transverse Mercator", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "unknown", + "datum_params": [601.705, 84.263, 485.227, -4.7354, -1.3145, -5.393, -2.3887], + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "k0": 1, + "x0": 750000, + "y0": -5000000, + "long0": 0.2850704444924066, + "lat0": 0, + "srsCode": "unknown", + "name": "unknown" + } +}, { + "code": "BOUNDCRS[ SOURCECRS[ PROJCRS[\"unknown\", BASEGEOGCRS[\"unknown\", DATUM[\"Unknown based on Bessel 1841 ellipsoid\", ELLIPSOID[\"Bessel 1841\",6377397.155,299.1528128, LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]]], PRIMEM[\"Greenwich\",0, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8901]]], CONVERSION[\"unknown\", METHOD[\"Lambert Conic Conformal (2SP)\", ID[\"EPSG\",9802]], PARAMETER[\"Latitude of false origin\",47.5, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8821]], PARAMETER[\"Longitude of false origin\",13.3333333333333, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8822]], PARAMETER[\"Latitude of 1st standard parallel\",49, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8823]], PARAMETER[\"Latitude of 2nd standard parallel\",46, ANGLEUNIT[\"degree\",0.0174532925199433], ID[\"EPSG\",8824]], PARAMETER[\"Easting at false origin\",400000, LENGTHUNIT[\"metre\",1], ID[\"EPSG\",8826]], PARAMETER[\"Northing at false origin\",400000, LENGTHUNIT[\"metre\",1], ID[\"EPSG\",8827]]], CS[Cartesian,2], AXIS[\"(E)\",east, ORDER[1], LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]], AXIS[\"(N)\",north, ORDER[2], LENGTHUNIT[\"metre\",1, ID[\"EPSG\",9001]]]]], TARGETCRS[ GEOGCRS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", ELLIPSOID[\"WGS 84\",6378137,298.257223563, LENGTHUNIT[\"metre\",1]]], PRIMEM[\"Greenwich\",0, ANGLEUNIT[\"degree\",0.0174532925199433]], CS[ellipsoidal,2], AXIS[\"latitude\",north, ORDER[1], ANGLEUNIT[\"degree\",0.0174532925199433]], AXIS[\"longitude\",east, ORDER[2], ANGLEUNIT[\"degree\",0.0174532925199433]], ID[\"EPSG\",4326]]], ABRIDGEDTRANSFORMATION[\"unknown to WGS84\", METHOD[\"NTv2\", ID[\"EPSG\",9615]], PARAMETERFILE[\"Latitude and longitude difference file\",\"AT_GIS_GRID.gsb\", ID[\"EPSG\",8656]]]]", + "value": { + "projName": "Lambert Conic Conformal (2SP)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "unknown", + "nadgrids": "AT_GIS_GRID.gsb", + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "x0": 400000, + "y0": 400000, + "long0": 0.23271056693257675, + "lat0": 0.8290313946973067, + "lat1": 0.8552113334772217, + "lat2": 0.8028514559173917, + "srsCode": "unknown", + "name": "unknown" + } +}, { + "code": { + "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", + "type": "BoundCRS", + "source_crs": { + "type": "ProjectedCRS", + "name": "unknown", + "base_crs": { + "name": "unknown", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "Unknown based on Bessel 1841 ellipsoid", + "ellipsoid": { + "name": "Bessel 1841", + "semi_major_axis": 6377397.155, + "inverse_flattening": 299.1528128 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Longitude", + "abbreviation": "lon", + "direction": "east", + "unit": "degree" + }, + { + "name": "Latitude", + "abbreviation": "lat", + "direction": "north", + "unit": "degree" + } + ] + } + }, + "conversion": { + "name": "unknown", + "method": { + "name": "Lambert Conic Conformal (2SP)", + "id": { + "authority": "EPSG", + "code": 9802 + } + }, + "parameters": [ + { + "name": "Latitude of false origin", + "value": 47.5, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8821 + } + }, + { + "name": "Longitude of false origin", + "value": 13.3333333333333, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8822 + } + }, + { + "name": "Latitude of 1st standard parallel", + "value": 49, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8823 + } + }, + { + "name": "Latitude of 2nd standard parallel", + "value": 46, + "unit": "degree", + "id": { + "authority": "EPSG", + "code": 8824 + } + }, + { + "name": "Easting at false origin", + "value": 400000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8826 + } + }, + { + "name": "Northing at false origin", + "value": 400000, + "unit": "metre", + "id": { + "authority": "EPSG", + "code": 8827 + } + } + ] + }, + "coordinate_system": { + "subtype": "Cartesian", + "axis": [ + { + "name": "Easting", + "abbreviation": "E", + "direction": "east", + "unit": "metre" + }, + { + "name": "Northing", + "abbreviation": "N", + "direction": "north", + "unit": "metre" + } + ] + } + }, + "target_crs": { + "type": "GeographicCRS", + "name": "WGS 84", + "datum": { + "type": "GeodeticReferenceFrame", + "name": "World Geodetic System 1984", + "ellipsoid": { + "name": "WGS 84", + "semi_major_axis": 6378137, + "inverse_flattening": 298.257223563 + } + }, + "coordinate_system": { + "subtype": "ellipsoidal", + "axis": [ + { + "name": "Latitude", + "abbreviation": "lat", + "direction": "north", + "unit": "degree" + }, + { + "name": "Longitude", + "abbreviation": "lon", + "direction": "east", + "unit": "degree" + } + ] + }, + "id": { + "authority": "EPSG", + "code": 4326 + } + }, + "transformation": { + "name": "unknown to WGS84", + "method": { + "name": "NTv2", + "id": { + "authority": "EPSG", + "code": 9615 + } + }, + "parameters": [ + { + "name": "Latitude and longitude difference file", + "value": "AT_GIS_GRID.gsb" + } + ] + } + }, + "value": { + "projName": "Lambert Conic Conformal (2SP)", + "axis": "enu", + "units": "meter", + "to_meter": 1, + "datumCode": "unknown", + "nadgrids": "AT_GIS_GRID.gsb", + "ellps": "Bessel 1841", + "a": 6377397.155, + "rf": 299.1528128, + "x0": 400000, + "y0": 400000, + "long0": 0.2327105669325767, + "lat0": 0.8290313946973066, + "lat1": 0.8552113334772214, + "lat2": 0.8028514559173915, + "srsCode": "unknown", + "name": "unknown" + } } ] diff --git a/test.js b/test.js index 807c137..a4fd634 100644 --- a/test.js +++ b/test.js @@ -1,14 +1,81 @@ -var test = require('tape'); +import test from 'tape'; import compare from 'js-struct-compare'; -var wktParser = require('./dist/wkt.cjs'); -var fixtures = require('./test-fixtures.json'); +import wktParser from './dist/wkt.cjs'; +import fixtures from './test-fixtures.json' with {type: 'json'}; -fixtures.forEach((item, i)=>{ - test(`fixture ${i + 1}`, t=>{ - var out = wktParser(item.code); - //console.log(JSON.stringify(out, null, 2)); - const diff = JSON.stringify(compare(item.value, JSON.parse(JSON.stringify(out))), null, 2); - t.equal(diff, '[]'); +function derivedPropertiesOnly(input) { + const derivedProperties = (({ + projName, + axis, + units, + to_meter, + datumCode, + datum_params, + nadgrids, + ellps, + from_greenwich, + a, + rf, + k0, + x0, + y0, + long0, + longc, + lat0, + lat1, + lat2, + lat_ts, + alpha, + rectified_grid_angle, + srsCode, + name, + title, + sphere + }) => ({ + projName, + axis, + units, + to_meter, + datumCode, + datum_params, + nadgrids, + ellps, + from_greenwich, + a, + rf, + k0, + x0, + y0, + long0, + longc, + lat0, + lat1, + lat2, + lat_ts, + alpha, + rectified_grid_angle, + srsCode, + name, + title, + sphere + }))(input); + for (const key in derivedProperties) { + if (derivedProperties[key] === undefined) { + delete derivedProperties[key]; + } + } + return derivedProperties; +} + +fixtures.forEach((item, i) => { + test(`fixture ${i + 1}`, t => { + const got = item.strict ? wktParser(item.code) : derivedPropertiesOnly(wktParser(item.code)); + const expected = item.strict ? item.value : derivedPropertiesOnly(item.value); + const diff = compare(got, expected); + if (diff.length > 0) { + console.log('got', got, 'expected', expected); + } + t.deepEqual(diff, []); t.end(); }); -}) +}); \ No newline at end of file diff --git a/transformPROJJSON.js b/transformPROJJSON.js new file mode 100644 index 0000000..3d03b47 --- /dev/null +++ b/transformPROJJSON.js @@ -0,0 +1,264 @@ +import { applyProjectionDefaults } from "./util"; + +// Helper function to process units and to_meter +function processUnit(unit) { + let result = { units: null, to_meter: undefined }; + + if (typeof unit === 'string') { + result.units = unit.toLowerCase(); + if (result.units === 'metre') { + result.units = 'meter'; // Normalize 'metre' to 'meter' + } + if (result.units === 'meter') { + result.to_meter = 1; // Only set to_meter if units are 'meter' + } + } else if (unit?.name) { + result.units = unit.name.toLowerCase(); + if (result.units === 'metre') { + result.units = 'meter'; // Normalize 'metre' to 'meter' + } + result.to_meter = unit.conversion_factor; + } + + return result; +} + +function toValue(valueOrObject) { + if (typeof valueOrObject === 'object') { + return valueOrObject.value * valueOrObject.unit.conversion_factor; + } + return valueOrObject; +} + +function calculateEllipsoid(value, result) { + if (value.ellipsoid.radius) { + result.a = value.ellipsoid.radius; + result.rf = 0; + } else { + result.a = toValue(value.ellipsoid.semi_major_axis); + if (value.ellipsoid.inverse_flattening !== undefined) { + result.rf = value.ellipsoid.inverse_flattening; + } else if (value.ellipsoid.semi_major_axis !== undefined && value.ellipsoid.semi_minor_axis !== undefined) { + result.rf = result.a / (result.a - toValue(value.ellipsoid.semi_minor_axis)); + } + } +} + +export function transformPROJJSON(projjson, result = {}) { + if (!projjson || typeof projjson !== 'object') { + return projjson; // Return primitive values as-is + } + + if (projjson.type === 'BoundCRS') { + transformPROJJSON(projjson.source_crs, result); + + if (projjson.transformation) { + if (projjson.transformation.method?.name === 'NTv2') { + // Set nadgrids to the filename from the parameterfile + result.nadgrids = projjson.transformation.parameters[0].value; + } else { + // Populate datum_params if no parameterfile is found + result.datum_params = projjson.transformation.parameters.map((param) => param.value); + } + } + return result; // Return early for BoundCRS + } + + // Handle specific keys in PROJJSON + Object.keys(projjson).forEach((key) => { + const value = projjson[key]; + if (value === null) { + return; + } + + switch (key) { + case 'name': + if (result.srsCode) { + break; + } + result.name = value; + result.srsCode = value; // Map `name` to `srsCode` + break; + + case 'type': + if (value === 'GeographicCRS') { + result.projName = 'longlat'; + } else if (value === 'ProjectedCRS') { + result.projName = projjson.conversion?.method?.name; // Retain original capitalization + } + break; + + case 'datum': + case 'datum_ensemble': // Handle both datum and ensemble + if (value.ellipsoid) { + // Extract ellipsoid properties + result.ellps = value.ellipsoid.name; + calculateEllipsoid(value, result); + } + if (value.prime_meridian) { + result.from_greenwich = value.prime_meridian.longitude * Math.PI / 180; // Convert to radians + } + break; + + case 'ellipsoid': + result.ellps = value.name; + calculateEllipsoid(value, result); + break; + + case 'prime_meridian': + result.long0 = (value.longitude || 0) * Math.PI / 180; // Convert to radians + break; + + case 'coordinate_system': + if (value.axis) { + result.axis = value.axis + .map((axis) => { + const direction = axis.direction; + if (direction === 'east') return 'e'; + if (direction === 'north') return 'n'; + if (direction === 'west') return 'w'; + if (direction === 'south') return 's'; + throw new Error(`Unknown axis direction: ${direction}`); + }) + .join('') + 'u'; // Combine into a single string (e.g., "enu") + + if (value.unit) { + const { units, to_meter } = processUnit(value.unit); + result.units = units; + result.to_meter = to_meter; + } else if (value.axis[0]?.unit) { + const { units, to_meter } = processUnit(value.axis[0].unit); + result.units = units; + result.to_meter = to_meter; + } + } + break; + + case 'id': + if (value.authority && value.code) { + result.title = value.authority + ':' + value.code; + } + break; + + case 'conversion': + if (value.method && value.method.name) { + result.projName = value.method.name; // Retain original capitalization + } + if (value.parameters) { + value.parameters.forEach((param) => { + const paramName = param.name.toLowerCase().replace(/\s+/g, '_'); + const paramValue = param.value; + if (param.unit && param.unit.conversion_factor) { + result[paramName] = paramValue * param.unit.conversion_factor; // Convert to radians or meters + } else if (param.unit === 'degree') { + result[paramName] = paramValue * Math.PI / 180; // Convert to radians + } else { + result[paramName] = paramValue; + } + }); + } + break; + + case 'unit': + if (value.name) { + result.units = value.name.toLowerCase(); + if (result.units === 'metre') { + result.units = 'meter'; + } + } + if (value.conversion_factor) { + result.to_meter = value.conversion_factor; + } + break; + + case 'base_crs': + transformPROJJSON(value, result); // Pass `result` directly + result.datumCode = value.id ? value.id.authority + '_' + value.id.code : value.name; // Set datumCode + break; + + default: + // Ignore irrelevant or unneeded properties + break; + } + }); + + // Additional calculated properties + if (result.latitude_of_false_origin !== undefined) { + result.lat0 = result.latitude_of_false_origin; // Already in radians + } + if (result.longitude_of_false_origin !== undefined) { + result.long0 = result.longitude_of_false_origin; + } + if (result.latitude_of_standard_parallel !== undefined) { + result.lat0 = result.latitude_of_standard_parallel; + result.lat1 = result.latitude_of_standard_parallel; + } + if (result.latitude_of_1st_standard_parallel !== undefined) { + result.lat1 = result.latitude_of_1st_standard_parallel; + } + if (result.latitude_of_2nd_standard_parallel !== undefined) { + result.lat2 = result.latitude_of_2nd_standard_parallel; + } + if (result.latitude_of_projection_centre !== undefined) { + result.lat0 = result.latitude_of_projection_centre; + } + if (result.longitude_of_projection_centre !== undefined) { + result.longc = result.longitude_of_projection_centre; + } + if (result.easting_at_false_origin !== undefined) { + result.x0 = result.easting_at_false_origin; + } + if (result.northing_at_false_origin !== undefined) { + result.y0 = result.northing_at_false_origin; + } + if (result.latitude_of_natural_origin !== undefined) { + result.lat0 = result.latitude_of_natural_origin; + } + if (result.longitude_of_natural_origin !== undefined) { + result.long0 = result.longitude_of_natural_origin; + } + if (result.longitude_of_origin !== undefined) { + result.long0 = result.longitude_of_origin; + } + if (result.false_easting !== undefined) { + result.x0 = result.false_easting; + } + if (result.easting_at_projection_centre) { + result.x0 = result.easting_at_projection_centre; + } + if (result.false_northing !== undefined) { + result.y0 = result.false_northing; + } + if (result.northing_at_projection_centre) { + result.y0 = result.northing_at_projection_centre; + } + if (result.standard_parallel_1 !== undefined) { + result.lat1 = result.standard_parallel_1; + } + if (result.standard_parallel_2 !== undefined) { + result.lat2 = result.standard_parallel_2; + } + if (result.scale_factor_at_natural_origin !== undefined) { + result.k0 = result.scale_factor_at_natural_origin; + } + if (result.scale_factor_at_projection_centre !== undefined) { + result.k0 = result.scale_factor_at_projection_centre; + } + if (result.scale_factor_on_pseudo_standard_parallel !== undefined) { + result.k0 = result.scale_factor_on_pseudo_standard_parallel; + } + if (result.azimuth !== undefined) { + result.alpha = result.azimuth; + } + if (result.azimuth_at_projection_centre !== undefined) { + result.alpha = result.azimuth_at_projection_centre; + } + if (result.angle_from_rectified_to_skew_grid) { + result.rectified_grid_angle = result.angle_from_rectified_to_skew_grid; + } + + // Apply projection defaults + applyProjectionDefaults(result); + + return result; +} \ No newline at end of file diff --git a/util.js b/util.js new file mode 100644 index 0000000..e6f2d1d --- /dev/null +++ b/util.js @@ -0,0 +1,23 @@ +var D2R = 0.01745329251994329577; + +export function d2r(input) { + return input * D2R; +} + +export function applyProjectionDefaults(wkt) { + // Normalize projName for WKT2 compatibility + const normalizedProjName = (wkt.projName || '').toLowerCase().replace(/_/g, ' '); + + if (!wkt.long0 && wkt.longc && (normalizedProjName === 'albers conic equal area' || normalizedProjName === 'lambert azimuthal equal area')) { + wkt.long0 = wkt.longc; + } + if (!wkt.lat_ts && wkt.lat1 && (normalizedProjName === 'stereographic south pole' || normalizedProjName === 'polar stereographic (variant b)')) { + wkt.lat0 = d2r(wkt.lat1 > 0 ? 90 : -90); + wkt.lat_ts = wkt.lat1; + delete wkt.lat1; + } else if (!wkt.lat_ts && wkt.lat0 && (normalizedProjName === 'polar stereographic' || normalizedProjName === 'polar stereographic (variant a)')) { + wkt.lat_ts = wkt.lat0; + wkt.lat0 = d2r(wkt.lat0 > 0 ? 90 : -90); + delete wkt.lat1; + } +} \ No newline at end of file diff --git a/wkt2_parser.js b/wkt2_parser.js new file mode 100644 index 0000000..57ef662 --- /dev/null +++ b/wkt2_parser.js @@ -0,0 +1,147 @@ +import PROJJSONBuilder from './PROJJSONBuilderBase.js'; +import Tokenizer from './wkt2_tokenizer.js' +import WKTNode from './wkt_node.js'; + +/** + * @typedef {Object} Token + * @property {string} type + * @property {string} value + * @property {number} position + */ + +/** + * Custom error type for WKT parsing errors. + */ +class WKTParsingError extends Error { + /** + * @param {string} message + * @param {number} position + */ + constructor(message, position) { + super(`WKT Parsing Error at position ${position}: ${message}`); + this.name = "WKTParsingError"; + this.position = position; + } +} + +/** + * Main parser for WKT2 strings. + */ +class WKTParser { + /** + * @param {string} input + */ + constructor(input) { + this.tokenizer = new Tokenizer(input); + this.currentToken = null; + this.advance(); + } + + advance() { + this.currentToken = this.tokenizer.nextToken(); + } + + /** + * @param {string} type + * @param {string} [value] + */ + expect(type, value) { + if ( + !this.currentToken || + this.currentToken.type !== type || + (value && this.currentToken.value !== value) + ) { + throw new WKTParsingError( + `Expected token type '${type}'` + (value ? ` with value '${value}'` : ""), + this.tokenizer.pos + ); + } + this.advance(); + } + + /** @returns {WKTNode} */ + parse() { + const token = this.currentToken; + + if (!token) { + throw new WKTParsingError('Unexpected end of input', this.tokenizer.pos); + } + + switch (token.value) { + case 'GEODCRS': + case 'PROJCRS': + return this.parseNode(); // Parse CRS definitions as nodes + case 'CS': // CoordinateSystem (example) + return this.parseNode(); + // Add more cases as needed for other WKT2 root types + default: + throw new WKTParsingError(`Unexpected root keyword: ${token.value}`, this.tokenizer.pos); + } + } + + peek() { + return this.tokenizer.peekToken(); + } + + /** @returns {WKTNode} */ + parseNode() { + const token = this.currentToken; + + if (!token || token.type !== 'keyword') { + throw new WKTParsingError('Expected keyword at start of node', this.tokenizer.pos); + } + + const keyword = token.value; + this.advance(); + + this.expect('punctuation', '['); + + const node = new WKTNode(keyword); + + let expectingValue = true; + + while (this.currentToken && !(this.currentToken.type === 'punctuation' && this.currentToken.value === ']')) { + const t = this.currentToken; + + if (t.type === 'string' || t.type === 'number') { + if (expectingValue && node.value === null) { + node.value = t.value; + } else { + node.addChild(new WKTNode(t.value)); + } + this.advance(); + } else if (t.type === 'keyword') { + const next = this.peek(); + + if (expectingValue && !node.value && (!next || next.type !== 'punctuation' || next.value !== '[')) { + node.value = t.value; + this.advance(); + } else if (next && next.type === 'punctuation' && next.value === '[') { + const childNode = this.parseNode(); + console.log(`Parsed child node: ${JSON.stringify(childNode, null, 2)}`); // Debug log + node.addChild(childNode); + } else { + node.addChild(new WKTNode(t.value)); + this.advance(); + } + } else if (t.type === 'punctuation' && t.value === ',') { + this.advance(); + } else { + throw new WKTParsingError(`Unexpected token ${t.type}`, t.position); + } + + expectingValue = false; + } + + this.expect('punctuation', ']'); + + return node; + } + +} + +// Example usage +const parser = new WKTParser(`PROJCRS["MGI / Austria GK M28",BASEGEOGCRS["MGI",DATUM["Militar-Geographische Institut",ELLIPSOID["Bessel 1841",6377397.155,299.1528128,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",7004]],ID["EPSG",6312]],ID["EPSG",4312]],CONVERSION["Austria Gauss-Kruger M28",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",10.3333333333336,ANGLEUNIT["degree",0.0174532925199433,ID["EPSG",9102]],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",1,SCALEUNIT["unity",1,ID["EPSG",9201]],ID["EPSG",8805]],PARAMETER["False easting",150000,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8806]],PARAMETER["False northing",-5000000,LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",8807]],ID["EPSG",18007]],CS[Cartesian,2,ID["EPSG",4530]],AXIS["Northing (X)",north],AXIS["Easting (Y)",east],LENGTHUNIT["metre",1,ID["EPSG",9001]],ID["EPSG",31257]]`); +const root = parser.parse(); +const projjson = PROJJSONBuilder.build(root); +console.log(JSON.stringify(projjson, null, 2)); \ No newline at end of file diff --git a/wkt2_tokenizer.js b/wkt2_tokenizer.js new file mode 100644 index 0000000..2c69361 --- /dev/null +++ b/wkt2_tokenizer.js @@ -0,0 +1,105 @@ +/** + * Tokenizer for WKT2 strings. + */ +class Tokenizer { + /** + * @param {string} input + */ + constructor(input) { + this.input = input; + this.length = input.length; + this.pos = 0; + } + + /** @returns {Token|null} */ + nextToken() { + this._skipWhitespace(); + + if (this.pos >= this.length) return null; + + const char = this.input[this.pos]; + + // Punctuation + if (char === '[' || char === ']' || char === ',') { + this.pos++; + return { + type: 'punctuation', + value: char, + position: this.pos - 1, + }; + } + + // Quoted string + if (char === '"') { + const start = this.pos++; + let str = ''; + while (this.pos < this.length && this.input[this.pos] !== '"') { + str += this.input[this.pos++]; + } + if (this.input[this.pos] !== '"') { + throw new WKTParsingError('Unterminated string', start); + } + this.pos++; // skip closing quote + return { + type: 'string', + value: str, + position: start, + }; + } + + // Number (int or float) + if (char.match(/[0-9\.\-]/)) { + const start = this.pos; + let num = ''; + while (this.pos < this.length && this.input[this.pos].match(/[0-9eE\.\+\-]/)) { + num += this.input[this.pos++]; + } + return { + type: 'number', + value: num, + position: start, + }; + } + + // Keyword (identifiers) + if (char.match(/[A-Z_]/i)) { + const start = this.pos; + let ident = ''; + while (this.pos < this.length && this.input[this.pos].match(/[A-Z0-9_]/i)) { + ident += this.input[this.pos++]; + } + return { + type: 'keyword', + value: ident, + position: start, + }; + } + + throw new WKTParsingError(`Unexpected character '${char}'`, this.pos); + } + + /** @returns {Token|null} */ + peekToken() { + const savedPos = this.pos; + try { + return this.nextToken(); + } finally { + this.pos = savedPos; + } + } + + _skipWhitespace() { + while (this.pos < this.length && this.input[this.pos].match(/\s/)) { + this.pos++; + } + } +} + +export default Tokenizer; + +// example usage +// const tokenizer = new Tokenizer(`GEODCRS["WGS 84", DATUM["World Geodetic System 1984"]]`); +// let token; +// while ((token = tokenizer.nextToken())) { +// console.log(token); +// } \ No newline at end of file diff --git a/wkt_node.js b/wkt_node.js new file mode 100644 index 0000000..3118427 --- /dev/null +++ b/wkt_node.js @@ -0,0 +1,55 @@ +/** + * Represents a WKT node in the parsed tree. + */ +class WKTNode { + /** + * @param {string} keyword + * @param {string} [value] + */ + constructor(keyword, value) { + this.keyword = keyword; + this.value = value; + this.children = []; + } + + /** + * @param {WKTNode} node + */ + addChild(node) { + this.children.push(node); + } + + /** + * Finds the first child with the specified keyword. + * @param {string} keyword + * @returns {WKTNode | undefined} + */ + findChild(keyword) { + return this.children.find( + (c) => c.keyword?.toUpperCase() === keyword.toUpperCase() + ); + } + + /** + * Finds all children with the specified keyword. + * @param {string} keyword + * @returns {WKTNode[]} + */ + findChildren(keyword) { + return this.children.filter( + (c) => c.keyword?.toUpperCase() === keyword.toUpperCase() + ); + } + + /** + * Gets the value or literal content of a child node matching the keyword. + * @param {string} keyword + * @returns {string | undefined} + */ + getValue(keyword) { + const child = this.findChild(keyword); + return child?.value ?? child?.literal; + } +} + +export default WKTNode; \ No newline at end of file