Skip to content

Commit

Permalink
Added a file with coordinates in Sweden to be used for regression tes…
Browse files Browse the repository at this point in the history
…ting, and implemented a test method

which verifies the transformations for this current implementation.
The current implementation use the three classes RT90Position, SWEREF99Position, WGS84Position,
from within a new class Transformer with implementation that selects which of these classes to use based
on the EPSG code for the source and target coordinates.
  • Loading branch information
TomasJohansson committed Jan 8, 2021
1 parent 6ace16e commit 135e543
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 0 deletions.
90 changes: 90 additions & 0 deletions MightyLittleGeodesy/Transformer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using MightyLittleGeodesy.Positions;
using System;
using static MightyLittleGeodesy.Positions.RT90Position;
using static MightyLittleGeodesy.Positions.SWEREF99Position;

namespace MightyLittleGeodesy
{

public class Transformer {
public const int epsgForWgs84 = 4326;
private const int epsgForSweref99tm = 3006;

//private const int epsgLowerValueForSwerefLocal = 3007; // the NATIONAL sweref99TM has value 3006 as in the above constant
//private const int epsgUpperValueForSwerefLocal = 3018;
private const int epsgLowerValueForSweref = epsgForSweref99tm;
private const int epsgUpperValueForSweref = 3018;

private const int epsgLowerValueForRT90 = 3019;
private const int epsgUpperValueForRT90 = 3024;

public static Coordinate Transform(Coordinate sourceCoordinate, int targetEpsg) {
if(sourceCoordinate.epsgNumber == targetEpsg) throw new ArgumentException("Trying to transform from/to the same CRS");
if(isWgs84(sourceCoordinate.epsgNumber)) {
var wgs84position = new WGS84Position(sourceCoordinate.yLatitude, sourceCoordinate.xLongitude);
if(isSweref(targetEpsg)) {
SWEREFProjection swerefProjection = ProjectionFactory.GetSwerefProjectionByEpsgNumber(targetEpsg);
var position = new SWEREF99Position(wgs84position, swerefProjection);
return new Coordinate(targetEpsg, position.Longitude, position.Latitude);
}
else if(isRT90(targetEpsg)) {
RT90Projection rt90Projection = ProjectionFactory.GetRT90ProjectionProjectionByEpsgNumber(targetEpsg);
var position = new RT90Position(wgs84position, rt90Projection);
return new Coordinate(targetEpsg, position.Longitude, position.Latitude);
}
}
else if(isSweref(sourceCoordinate.epsgNumber)) {
if(isWgs84(targetEpsg)) {
SWEREFProjection swerefProjection = ProjectionFactory.GetSwerefProjectionByEpsgNumber(sourceCoordinate.epsgNumber);
var sweref99Position = new SWEREF99Position(sourceCoordinate.yLatitude, sourceCoordinate.xLongitude, swerefProjection);
var wgs84result = sweref99Position.ToWGS84();
return new Coordinate(targetEpsg, wgs84result.Longitude, wgs84result.Latitude);
}
else {
// the only direct transform supported is to/from WGS84, so therefore first transform to wgs84
var wgs84coordinate = Transform(sourceCoordinate, epsgForWgs84);
return Transform(wgs84coordinate, targetEpsg);
}
}
else if(isRT90(sourceCoordinate.epsgNumber)) {
if(isWgs84(targetEpsg)) {
RT90Projection rt90Projection = ProjectionFactory.GetRT90ProjectionProjectionByEpsgNumber(sourceCoordinate.epsgNumber);
var rt90Position = new RT90Position(sourceCoordinate.yLatitude, sourceCoordinate.xLongitude, rt90Projection);
var wgs84result = rt90Position.ToWGS84();
return new Coordinate(targetEpsg, wgs84result.Longitude, wgs84result.Latitude);
}
else {
// the only direct transform supported is to/from WGS84, so therefore first transform to wgs84
var wgs84coordinate = Transform(sourceCoordinate, epsgForWgs84);
return Transform(wgs84coordinate, targetEpsg);
}
}
throw new ArgumentException(string.Format("Unhandled source/target EPSG {0} ==> {1}", sourceCoordinate.epsgNumber, targetEpsg));
}

private static bool isWgs84(int epsgNumber) {
return epsgNumber == epsgForWgs84;
}
private static bool isSweref(int epsgNumber) {
return epsgLowerValueForSweref <= epsgNumber && epsgNumber <= epsgUpperValueForSweref;
}
private static bool isRT90(int epsgNumber) {
return epsgLowerValueForRT90 <= epsgNumber && epsgNumber <= epsgUpperValueForRT90;
}
}

public class Coordinate {
public readonly int epsgNumber;
public readonly double xLongitude;
public readonly double yLatitude;
public Coordinate(
int epsgNumber,
double xLongitude,
double yLatitude
) {
this.epsgNumber = epsgNumber;
this.xLongitude = xLongitude;
this.yLatitude = yLatitude;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using MightyLittleGeodesy;

namespace SwedenCrsTransformationsTests.CoordinateFiles {

[TestClass]
public class TransformingCoordinatesFromFileTest {

internal const string columnSeparator = "|";

// the below file "swedish_crs_transformations.csv" was copied from: https://github.com/TomasJohansson/crsTransformations/blob/a1da6c74daf040a521beb32f9f395124ffe76aa6/crs-transformation-adapter-test/src/test/resources/generated/swedish_crs_coordinates.csv
// and it was generated with a method "createFileWithTransformationResultsForCoordinatesInSweden()" at https://github.com/TomasJohansson/crsTransformations/blob/a1da6c74daf040a521beb32f9f395124ffe76aa6/crs-transformation-adapter-test/src/test/java/com/programmerare/com/programmerare/testData/CoordinateTestDataGeneratedFromEpsgDatabaseTest.java
private const string relativePathForFileWith_swedish_crs_transformations = "CoordinateFiles/data/swedish_crs_coordinates.csv";
// the project file should use "CopyToOutputDirectory" for the above file


[TestMethod]
public void AssertThatTransformationsDoNotDifferTooMuchFromExpectedResultInFile() {
string directory = GetPathToOutputDirectoryWhereTheDataFileShouldBeCopiedToAutomatically();
string absolutePathToFile = Path.Combine(directory, relativePathForFileWith_swedish_crs_transformations).Replace('/', Path.DirectorySeparatorChar);
FileInfo file = new FileInfo(absolutePathToFile);
Assert.IsTrue(file.Exists, "Try the build action 'CopyToOutputDirectory' ! . The file could not be found: " + file.FullName);

IList<string> problemTransformationResults = new List<string>();
IList<string> lines = File.ReadAllLines(file.FullName);
// The first two lines of the input file (the header row, and a data row):
// EPSG 4326 (WGS84)Longitude for WGS84 (EPSG 4326)|Latitude for WGS84 (EPSG 4326)|EPSG 3006|X for EPSG 3006|Y for EPSG 3006|EPSG 3007-3024|X for EPSG 3007-3024|Y for EPSG 3007-3024|Implementation count for EPSG 3006 transformation|Implementation count for EPSG 3007-3024 transformation
// 4326|12.146151472138385|58.46573396912418|3006|333538.2957000149|6484098.2550872|3007|158529.85136620898|6483166.205771873|6|6
// The last two columns can be ignored here, but the first nine columns are in three pairs with three columns each:
// an epsg number, and then the longitude(x) and latitude(y) for that coordinate.
// All three coordinates in one row represents the same location but in different coordinate reference systems.
// The first two, of the three, coordinates are for the same coordinate reference systems, WGS84 and SWEREF99TM,
// but the third is different for all rows (18 data rows for the local swedish CRS systems, RT90 and SWEREF99, with EPSG codes 3007-3024).

// The below loop iterates all lines and makes transformations between (to and from) the three coordinate reference systems
// and verifies the expected result according to the file, and asserts with an error if the difference is too big.
// Note that the expected coordinates have been calculated in another project, by using a median value for 6 different implementations.
// (and the number 6 is actually what the last columns means i.e. how many implementations were used to create the data file)
IList<Coordinates> listOfCoordinates = lines.Select(line => new Coordinates(line)).Skip(1).ToList();
Assert.AreEqual(18, listOfCoordinates.Count);
int numberOfTransformations = 0;
foreach(var listOfCoordinatesWhichRepresentTheSameLocation in listOfCoordinates) {
IList<Coordinate> coordinates = listOfCoordinatesWhichRepresentTheSameLocation.coordinateList;
for(int i=0; i<coordinates.Count-1; i++) {
for(int j=i+1; j<coordinates.Count; j++) {
Transform(coordinates[i], coordinates[j], problemTransformationResults);
Transform(coordinates[j], coordinates[i], problemTransformationResults);
numberOfTransformations += 2;
}
}

}
if (problemTransformationResults.Count > 0) {
foreach (string s in problemTransformationResults) {
Console.WriteLine(s);
}
}
Assert.AreEqual(0, problemTransformationResults.Count, "For further details see the Console output");

const int expectedNumberOfTransformations = 108; // for an explanation, see the lines below:
// Each line in the input file "swedish_crs_coordinates.csv" has three coordinates (and let's below call then A B C)
// and then for each line we should have done six number of transformations:
// A ==> B
// A ==> C
// B ==> C
// (and three more in the opposite directions)
// And there are 18 local CRS for sweden (i.e number of data rows in the file)
// Thus the total number of transformations should be 18 * 6 = 108
Assert.AreEqual(expectedNumberOfTransformations, numberOfTransformations);
}



private void Transform(
Coordinate sourceCoordinate,
Coordinate targetCoordinateExpected,
IList<string> problemTransformationResults
) {
int targetEpsg = targetCoordinateExpected.epsgNumber;
Coordinate targetCoordinate = Transformer.Transform(sourceCoordinate, targetEpsg);
bool isTargetEpsgWgs84 = targetEpsg == Transformer.epsgForWgs84;
// double maxDifference = isTargetEpsgWgs84 ? 0.000002 : 0.2; // fails, Epsg 3022 ==> 4326 , diffLongitude 2.39811809521484E-06
// double maxDifference = isTargetEpsgWgs84 ? 000003 : 0.1; // fails, Epsg 4326 ==> 3022 , diffLongitude 0.117090131156147
double maxDifference = isTargetEpsgWgs84 ? 000003 : 0.2; // the other (i.e. non-WGS84) are using meter as unit, so 0.2 is just two decimeters difference
double diffLongitude = Math.Abs((targetCoordinate.xLongitude - targetCoordinateExpected.xLongitude));
double diffLatitude = Math.Abs((targetCoordinate.yLatitude - targetCoordinateExpected.yLatitude));

if (diffLongitude > maxDifference || diffLatitude > maxDifference) {
string problem = string.Format(
"Epsg {0} ==> {1} , diffLongitude {2} , diffLatitude {3}"
+ "sourceCoordinate xLongitude/yLatitude: {4}/{5}"
+ "targetCoordinate xLongitude/yLatitude: {6}/{7}"
+ "targetCoordinateExpected xLongitude/yLatitude: {8}/{9}",
sourceCoordinate.epsgNumber, targetCoordinateExpected.epsgNumber,
diffLongitude, diffLatitude,
sourceCoordinate.xLongitude, sourceCoordinate.yLatitude,
targetCoordinate.xLongitude, targetCoordinate.yLatitude,
targetCoordinateExpected.xLongitude, targetCoordinateExpected.yLatitude
);
problemTransformationResults.Add(problem);
}
}

private string GetPathToOutputDirectoryWhereTheDataFileShouldBeCopiedToAutomatically() {
string pathToOutputDirectory= Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
// e.g. a path ending with something like this: "... SwedenCrsTransformationsTests\bin\Debug\netcoreapp2.1"
return pathToOutputDirectory;
}
}

internal class Coordinates {
internal readonly List<Coordinate> coordinateList;
internal Coordinates(
string lineFromFile
) {
var array = lineFromFile.Split(TransformingCoordinatesFromFileTest.columnSeparator);
coordinateList = new List<Coordinate> {
new Coordinate(int.Parse(array[0]), double.Parse(array[1]), double.Parse(array[2])),
new Coordinate(int.Parse(array[3]), double.Parse(array[4]), double.Parse(array[5])),
new Coordinate(int.Parse(array[6]), double.Parse(array[7]), double.Parse(array[8]))
};
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
EPSG 4326 (WGS84)Longitude for WGS84 (EPSG 4326)|Latitude for WGS84 (EPSG 4326)|EPSG 3006|X for EPSG 3006|Y for EPSG 3006|EPSG 3007-3024|X for EPSG 3007-3024|Y for EPSG 3007-3024|Implementation count for EPSG 3006 transformation|Implementation count for EPSG 3007-3024 transformation
4326|12.146151472138385|58.46573396912418|3006|333538.2957000149|6484098.2550872|3007|158529.85136620898|6483166.205771873|6|6
4326|13.470524334397624|58.58400993288184|3006|411075.37423013494|6494745.783843142|3008|148285.48862749408|6496331.697852695|6|6
4326|14.977576047737374|58.78282472189397|3006|498703.5890065983|6515870.052578431|3009|148703.07023469123|6518477.443555852|6|6
4326|16.511041403075566|59.30321531687106|3006|586043.5750084487|6574791.715626789|3010|150629.0196083946|6576446.640618691|6|6
4326|18.19079263233461|59.678160851467794|3006|679655.2692001299|6619889.429962698|3011|160749.4537030397|6618232.315005776|6|6
4326|13.721704597205232|62.96891741227423|3006|435185.3935222791|6982770.168583414|3012|123201.54397365177|6985030.111975779|6|6
4326|15.5453884066594|62.99805848320907|3006|527626.9057630751|6985490.052601999|3013|139631.07711081076|6988184.660387672|6|6
4326|16.829108886010665|64.99460630590642|3006|586262.6436480937|7209101.47066392|3014|130140.34937436534|7210803.867463439|6|6
4326|18.76546663731605|63.52666974241647|3006|687205.0085568134|7049780.682800813|3015|150769.58533200162|7047090.338527749|6|6
4326|19.95189549865619|66.86485452747677|3006|716925.6906953243|7424941.873508802|3016|136924.61238266266|7419313.586609598|6|6
4326|21.388317251528505|65.72336003627437|3006|792678.6631042717|7303969.768206225|3017|133400.26969797508|7292040.086681188|6|6
4326|23.01759451041915|66.743305441312|3006|852474.0312307824|7425471.195805718|3018|139755.73621974868|7405746.358696348|6|6
4326|12.000334935356666|58.49748841971714|3006|325194.2619877269|6488002.126540723|3019|1540497.4440695136|6486274.58960603|6|6
4326|13.535611821690898|60.061656774989856|3006|418476.30827460607|6659180.662580081|3020|1498894.371510969|6660313.85211511|6|6
4326|15.816797928880426|61.291598649254105|3006|543770.853060645|6795541.286404101|3021|1500627.951348714|6797357.300170688|6|6
4326|18.073441702621203|64.15010420890953|3006|649477.2997972438|7117350.010634856|3022|1500917.9130901312|7115957.205396191|6|6
4326|20.543455199211188|66.20776695378555|3006|749287.0042619368|7354114.654538377|3023|1510782.4273971773|7345394.689191865|6|6
4326|22.795306156014576|66.83989741757875|3006|841397.9017335944|7434928.316155667|3024|1510614.3821736928|7415882.894158065|6|6
5 changes: 5 additions & 0 deletions MightyLittleGeodesyTests/MightyLittleGeodesyTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@
<ItemGroup>
<ProjectReference Include="..\MightyLittleGeodesy\MightyLittleGeodesy.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="CoordinateFiles\data\swedish_crs_coordinates.csv">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

0 comments on commit 135e543

Please sign in to comment.