UNPKG

@programmerare/sweden_crs_transformations

Version:

TypeScript/JavaScript library for transformation of geographic coordinates between WGS84 and the Swedish coordinate reference systems SWEREF99 and RT90

368 lines (344 loc) 17.8 kB
/* * https://github.com/TomasJohansson/sweden_crs_transformations_4typescript * Copyright (c) Tomas Johansson , http://www.programmerare.com * The code in this 'sweden_crs_transformations_4typescript' library is licensed with MIT. * The library is based on the C#.NET library 'sweden_crs_transformations_4net' (https://github.com/TomasJohansson/sweden_crs_transformations_4net) * and the Dart library 'sweden_crs_transformations_4dart' (https://github.com/TomasJohansson/sweden_crs_transformations_4dart) * Both above libraries are based on the C#.NET library 'MightyLittleGeodesy' (https://github.com/bjornsallarp/MightyLittleGeodesy/) * which is also released with MIT. * License information about 'sweden_crs_transformations_4typescript' and 'MightyLittleGeodesy': * https://github.com/TomasJohansson/sweden_crs_transformations_4typescript/blob/typescript_SwedenCrsTransformations/LICENSE */ // Note that some of the comments below may still refer to a C#.NET or Dart class rather than this TypeScript class // (the TypeScript have been 'ported' from corresponding C#/Dart projects, as mentioned above) // This project is based on the library [MightyLittleGeodesy](https://github.com/bjornsallarp/MightyLittleGeodesy/) // It started as a fork, but then most of the original code is gone. // The main part that is still used is this file with the mathematical calculations i.e. the file "GaussKreuger.cs" // Although there has been some modifications of this file too, as mentioned below. // https://github.com/bjornsallarp/MightyLittleGeodesy/blob/83491fc6e7454f5d90d792610b317eca7a332334/MightyLittleGeodesy/Classes/GaussKreuger.cs // The original version of the below class 'GaussKreuger' is located at the above URL. // That original version has been modified below in this file below but not in a significant way (e.g. the mathematical calculations has not been modified). // The modifications: // - changed the class from public to internal i.e. "public class GaussKreuger" ==> "internal class GaussKreuger" // - a new 'LatLon' class is used as return type from two methods instead of returning an array "double[]" // i.e. the two method signatures have changed as below: // "public double[] geodetic_to_grid(double latitude, double longitude)" ==> "public LatLon geodetic_to_grid(double latitude, double longitude)" // "public double[] grid_to_geodetic(double x, double y)" ==> "public LatLon grid_to_geodetic(double yLatitude, double xLongitude)" // - renamed and changed order of the parameters for the method "grid_to_geodetic" (see the above line) // - changed the method "swedish_params" to use an enum as parameter instead of string, i.e. the method signature changed as below: // "public void swedish_params(string projection)" ==> "public void swedish_params(CrsProjection projection)" // - now the if/else statements in the implementation of the above method "swedish_params" compares with the enum values for CrsProjection instead of comparing with string literals // - removed the if/else statements in the above method "swedish_params" which used the projection strings beginning with "bessel_rt90" // - removed the now unused method 'bessel_params()' // // For more details about exactly what has changed in this GaussKreuger class, you can also use a git client with "compare" or "blame" features to see the changes) // Note that *most of* the above changes were copied from the C# project which modified the GaussKreuger class. // But later this file has been ported to the programming languages Dart and then also TypeScript. // Thus there have been some more modifications, but for those details about what has changed when porting from C# to Dart and TypeScript, please see the git repository with the source code. // (also be aware that some further updates may have been made in the TypeScript project without being mentioned above in the above comments that mostly was copied from the C# library) // ------------------------------------------------------------------------------------------ // The below comment block is kept from the original source file (see the above github URL) /* * MightyLittleGeodesy * RT90, SWEREF99 and WGS84 coordinate transformation library * * Read my blog @ http://blog.sallarp.com * * * Copyright (C) 2009 Björn Sållarp * Permission is hereby granted, free of charge, to any person obtaining a copy of this * software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // ------------------------------------------------------------------------------------------ /* * .NET-implementation of "Gauss Conformal Projection * (Transverse Mercator), Krügers Formulas". * - Parameters for SWEREF99 lat-long to/from RT90 and SWEREF99 * coordinates (RT90 and SWEREF99 are used in Swedish maps). * * The calculations are based entirely on the excellent * javscript library by Arnold Andreassons. * Source: http://www.lantmateriet.se/geodesi/ * Source: Arnold Andreasson, 2007. http://mellifica.se/konsult * Author: Björn Sållarp. 2009. http://blog.sallarp.com * * Modifications in this file were made 2021 by Tomas Johansson, first in a C# project. * But later more changes of course when it was ported to the programming language Dart, * and then later to TypeScript as in this ."ts" source file. * For details about changes, you should be able to use the github repository to see the git history where you found this source code file. */ import CrsProjection from '../crs_projection'; import LatLon from './lat_lon'; type double = number; export default class GaussKreuger { private _axis: double = 0.0; // Semi-major axis of the ellipsoid. private _flattening: double = 0.0; // Flattening of the ellipsoid. private _central_meridian: double = 0.0; // Central meridian for the projection. private _scale: double = 0.0; // Scale on central meridian. private _false_northing: double = 0.0; // Offset for origo. private _false_easting: double = 0.0; // Offset for origo. /** * Parameters for RT90 and SWEREF99TM. * Note: Parameters for RT90 are choosen to eliminate the * differences between Bessel and GRS80-ellipsoides. * Bessel-variants should only be used if lat/long are given as * RT90-lat/long based on the Bessel ellipsoide (from old maps). * Parameter: projection (string). Must match if-statement. */ swedish_params(projection: CrsProjection): void { // RT90 parameters, GRS 80 ellipsoid. if (projection == CrsProjection.rt90_7_5_gon_v) { this._grs80_params(); this._central_meridian = 11.0 + 18.375 / 60.0; this._scale = 1.000006000000; this._false_northing = -667.282; this._false_easting = 1500025.141; } else if (projection == CrsProjection.rt90_5_0_gon_v) { this._grs80_params(); this._central_meridian = 13.0 + 33.376 / 60.0; this._scale = 1.000005800000; this._false_northing = -667.130; this._false_easting = 1500044.695; } else if (projection == CrsProjection.rt90_2_5_gon_v) { this._grs80_params(); this._central_meridian = 15.0 + 48.0 / 60.0 + 22.624306 / 3600.0; this._scale = 1.00000561024; this._false_northing = -667.711; this._false_easting = 1500064.274; } else if (projection == CrsProjection.rt90_0_0_gon_v) { this._grs80_params(); this._central_meridian = 18.0 + 3.378 / 60.0; this._scale = 1.000005400000; this._false_northing = -668.844; this._false_easting = 1500083.521; } else if (projection == CrsProjection.rt90_2_5_gon_o) { this._grs80_params(); this._central_meridian = 20.0 + 18.379 / 60.0; this._scale = 1.000005200000; this._false_northing = -670.706; this._false_easting = 1500102.765; } else if (projection == CrsProjection.rt90_5_0_gon_o) { this._grs80_params(); this._central_meridian = 22.0 + 33.380 / 60.0; this._scale = 1.000004900000; this._false_northing = -672.557; this._false_easting = 1500121.846; } // SWEREF99TM and SWEREF99ddmm parameters. else if (projection == CrsProjection.sweref_99_tm) { this._sweref99_params(); this._central_meridian = 15.00; this._scale = 0.9996; this._false_northing = 0.0; this._false_easting = 500000.0; } else if (projection == CrsProjection.sweref_99_12_00) { this._sweref99_params(); this._central_meridian = 12.00; } else if (projection == CrsProjection.sweref_99_13_30) { this._sweref99_params(); this._central_meridian = 13.50; } else if (projection == CrsProjection.sweref_99_15_00) { this._sweref99_params(); this._central_meridian = 15.00; } else if (projection == CrsProjection.sweref_99_16_30) { this._sweref99_params(); this._central_meridian = 16.50; } else if (projection == CrsProjection.sweref_99_18_00) { this._sweref99_params(); this._central_meridian = 18.00; } else if (projection == CrsProjection.sweref_99_14_15) { this._sweref99_params(); this._central_meridian = 14.25; } else if (projection == CrsProjection.sweref_99_15_45) { this._sweref99_params(); this._central_meridian = 15.75; } else if (projection == CrsProjection.sweref_99_17_15) { this._sweref99_params(); this._central_meridian = 17.25; } else if (projection == CrsProjection.sweref_99_18_45) { this._sweref99_params(); this._central_meridian = 18.75; } else if (projection == CrsProjection.sweref_99_20_15) { this._sweref99_params(); this._central_meridian = 20.25; } else if (projection == CrsProjection.sweref_99_21_45) { this._sweref99_params(); this._central_meridian = 21.75; } else if (projection == CrsProjection.sweref_99_23_15) { this._sweref99_params(); this._central_meridian = 23.25; } else { this._central_meridian = Number.MIN_VALUE; } } /** Sets of default parameters. */ _grs80_params(): void { this._axis = 6378137.0; // GRS 80. this._flattening = 1.0 / 298.257222101; // GRS 80. this._central_meridian = Number.MIN_VALUE; } /** Sets default parameters for sweref99. */ _sweref99_params(): void { this._axis = 6378137.0; // GRS 80. this._flattening = 1.0 / 298.257222101; // GRS 80. this._central_meridian = Number.MIN_VALUE; this._scale = 1.0; this._false_northing = 0.0; this._false_easting = 150000.0; } /** Conversion from geodetic coordinates to grid coordinates. */ geodetic_to_grid(latitude: double, longitude: double): LatLon // public double[] geodetic_to_grid(double latitude, double longitude) { const x_y: Array<double> = [0.0, 0.0]; // Prepare ellipsoid-based stuff. const e2 = this._flattening * (2.0 - this._flattening); const n = this._flattening / (2.0 - this._flattening); const a_roof = this._axis / (1.0 + n) * (1.0 + n * n / 4.0 + n * n * n * n / 64.0); const A = e2; const B = (5.0 * e2 * e2 - e2 * e2 * e2) / 6.0; const C = (104.0 * e2 * e2 * e2 - 45.0 * e2 * e2 * e2 * e2) / 120.0; const D = (1237.0 * e2 * e2 * e2 * e2) / 1260.0; const beta1 = n / 2.0 - 2.0 * n * n / 3.0 + 5.0 * n * n * n / 16.0 + 41.0 * n * n * n * n / 180.0; const beta2 = 13.0 * n * n / 48.0 - 3.0 * n * n * n / 5.0 + 557.0 * n * n * n * n / 1440.0; const beta3 = 61.0 * n * n * n / 240.0 - 103.0 * n * n * n * n / 140.0; const beta4 = 49561.0 * n * n * n * n / 161280.0; // Convert. const deg_to_rad = Math.PI / 180.0; const phi = latitude * deg_to_rad; const lambda = longitude * deg_to_rad; const lambda_zero = this._central_meridian * deg_to_rad; const phi_star = phi - Math.sin(phi) * Math.cos(phi) * (A + B * Math.pow(Math.sin(phi), 2) + C * Math.pow(Math.sin(phi), 4) + D * Math.pow(Math.sin(phi), 6)); const delta_lambda = lambda - lambda_zero; const xi_prim = Math.atan(Math.tan(phi_star) / Math.cos(delta_lambda)); const eta_prim = this._math_atanh(Math.cos(phi_star) * Math.sin(delta_lambda)); const x = this._scale * a_roof * (xi_prim + beta1 * Math.sin(2.0 * xi_prim) * this._math_cosh(2.0 * eta_prim) + beta2 * Math.sin(4.0 * xi_prim) * this._math_cosh(4.0 * eta_prim) + beta3 * Math.sin(6.0 * xi_prim) * this._math_cosh(6.0 * eta_prim) + beta4 * Math.sin(8.0 * xi_prim) * this._math_cosh(8.0 * eta_prim)) + this._false_northing; const y = this._scale * a_roof * (eta_prim + beta1 * Math.cos(2.0 * xi_prim) * this._math_sinh(2.0 * eta_prim) + beta2 * Math.cos(4.0 * xi_prim) * this._math_sinh(4.0 * eta_prim) + beta3 * Math.cos(6.0 * xi_prim) * this._math_sinh(6.0 * eta_prim) + beta4 * Math.cos(8.0 * xi_prim) * this._math_sinh(8.0 * eta_prim)) + this._false_easting; x_y[0] = Math.round((x * 1000.0)) / 1000.0; x_y[1] = Math.round((y * 1000.0)) / 1000.0; return new LatLon(x_y[0], x_y[1]); } /** Conversion from grid coordinates to geodetic coordinates. */ grid_to_geodetic(yLatitude: double, xLongitude: double): LatLon // public double[] grid_to_geodetic(double yLatitude, double xLongitude) { if (this._central_meridian == Number.MIN_VALUE) { return new LatLon(0.0, 0.0); } const lat_lon: Array<double> = [0.0, 0.0]; // Prepare ellipsoid-based stuff. const e2 = this._flattening * (2.0 - this._flattening); const n = this._flattening / (2.0 - this._flattening); const a_roof = this._axis / (1.0 + n) * (1.0 + n * n / 4.0 + n * n * n * n / 64.0); const delta1 = n / 2.0 - 2.0 * n * n / 3.0 + 37.0 * n * n * n / 96.0 - n * n * n * n / 360.0; const delta2 = n * n / 48.0 + n * n * n / 15.0 - 437.0 * n * n * n * n / 1440.0; const delta3 = 17.0 * n * n * n / 480.0 - 37 * n * n * n * n / 840.0; const delta4 = 4397.0 * n * n * n * n / 161280.0; const Astar = e2 + e2 * e2 + e2 * e2 * e2 + e2 * e2 * e2 * e2; const Bstar = -(7.0 * e2 * e2 + 17.0 * e2 * e2 * e2 + 30.0 * e2 * e2 * e2 * e2) / 6.0; const Cstar = (224.0 * e2 * e2 * e2 + 889.0 * e2 * e2 * e2 * e2) / 120.0; const Dstar = -(4279.0 * e2 * e2 * e2 * e2) / 1260.0; // Convert. const deg_to_rad = Math.PI / 180; const lambda_zero = this._central_meridian * deg_to_rad; const xi = (yLatitude - this._false_northing) / (this._scale * a_roof); const eta = (xLongitude - this._false_easting) / (this._scale * a_roof); const xi_prim = xi - delta1 * Math.sin(2.0 * xi) * this._math_cosh(2.0 * eta) - delta2 * Math.sin(4.0 * xi) * this._math_cosh(4.0 * eta) - delta3 * Math.sin(6.0 * xi) * this._math_cosh(6.0 * eta) - delta4 * Math.sin(8.0 * xi) * this._math_cosh(8.0 * eta); const eta_prim = eta - delta1 * Math.cos(2.0 * xi) * this._math_sinh(2.0 * eta) - delta2 * Math.cos(4.0 * xi) * this._math_sinh(4.0 * eta) - delta3 * Math.cos(6.0 * xi) * this._math_sinh(6.0 * eta) - delta4 * Math.cos(8.0 * xi) * this._math_sinh(8.0 * eta); const phi_star = Math.asin(Math.sin(xi_prim) / this._math_cosh(eta_prim)); const delta_lambda = Math.atan(this._math_sinh(eta_prim) / Math.cos(xi_prim)); const lon_radian = lambda_zero + delta_lambda; const lat_radian = phi_star + Math.sin(phi_star) * Math.cos(phi_star) * (Astar + Bstar * Math.pow(Math.sin(phi_star), 2) + Cstar * Math.pow(Math.sin(phi_star), 4) + Dstar * Math.pow(Math.sin(phi_star), 6)); lat_lon[0] = lat_radian * 180.0 / Math.PI; lat_lon[1] = lon_radian * 180.0 / Math.PI; return new LatLon(lat_lon[0], lat_lon[1]); } _math_sinh(value: double): double { return 0.5 * (Math.exp(value) - Math.exp(-value)); } _math_cosh(value: double): double { return 0.5 * (Math.exp(value) + Math.exp(-value)); } _math_atanh(value: double): double { return 0.5 * Math.log((1.0 + value) / (1.0 - value)); } }