Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/.ipynb_checkpoints/**/*
4 changes: 3 additions & 1 deletion coordinates/Geocentric.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"github.com/wroge/wgs84"
)

// Geocentric is a type for geocentric coordinates.
// Geocentric is a type for geocentric coordinates. The {X, Y, Z} order should be followed.
type Geocentric mgl64.Vec3

// GeocentricReferenceSystem is the reference system for Geocentric.
var GeocentricReferenceSystem = wgs84.GeocentricReferenceSystem{}

// GeocentricFromGeodetic converts geodetic coordinates to geocentric coordinates.
// The order for the point to convert should be {Longitude, Latitude, Altitude} and
// {X, Y, Z} will be returned.
func GeocentricFromGeodetic(geodetic Geodetic) (geocentric Geocentric) {
geocentric[0], geocentric[1], geocentric[2] = GeodeticReferenceSystem.To(GeocentricReferenceSystem)(geodetic[0], geodetic[1], geodetic[2])
return
Expand Down
173 changes: 173 additions & 0 deletions coordinates/Geocentric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package coordinates

import (
"fmt"
"math"
"testing"

"github.com/go-gl/mathgl/mgl64"
)

// Original point data source: https://www.ngs.noaa.gov/NCAT/
// (Note that we are not affiliated with NOAA and we do not claim copyright for their information we use.
// Please see the disclaimer https://www.ngs.noaa.gov/disclaimer.html for more detials.)

// var errorCriterion an error criterion of 1 meter
const errorCriterion float64 = 1

// var movingDistance is the distance in meters each point will be moved for tests
const movingDistance float64 = 1

func TestGeodeticFromGeocentric90(t *testing.T) {

// point at Lon:0 Lat: 90 Alt: 0
point90 := Geocentric{0, 6378137.000, 0}
testGeodeticFromGeocentric(t, point90)
}

func TestGeodeticFromGeocentric45(t *testing.T) {

// point at Lon:0 Lat: 45 Alt: 0
point45 := Geocentric{4517590.879, 0.0, 4487348.409}
testGeodeticFromGeocentric(t, point45)

}

func TestGeodeticFromGeocentric0(t *testing.T) {

// point at Lon:0 Lat: 0 Alt: 0
point0 := Geocentric{6378137.000, 0.0, 0.0}
testGeodeticFromGeocentric(t, point0)

}

func TestGeocentricFromGeodetic90(t *testing.T) {

// point at Lon:0 Lat: 90 Alt: 0
point90 := Geocentric{0, 6378137.000, 0}
testGeocentricFromGeodetic(t, point90)

}

func TestGeocentricFromGeodetic45(t *testing.T) {

// point at Lon:0 Lat: 90 Alt: 0
point45 := Geocentric{4517590.879, 0.0, 4487348.409}
testGeocentricFromGeodetic(t, point45)

}

func TestGeocentricFromGeodetic0(t *testing.T) {

// point at Lon:0 Lat: 0 Alt: 0
point0 := Geocentric{6378137.000, 0.0, 0.0}
testGeocentricFromGeodetic(t, point0)

}

func testGeocentricFromGeodetic(t *testing.T, point Geocentric) {

// general pattern: geocentric (1) -> geodetic -> geocentric (2) then measure
// the distance between geocentric 1 and 2.

pointOrigin1 := point

// convert to geodetic
pointGeodetic := GeodeticFromGeocentric(pointOrigin1)

// convert back to geodetic
pointOrigin2 := GeocentricFromGeodetic(pointGeodetic)

// measure distance between pointOrigin1 and pointOrigin2
absoluteDifference := mgl64.Vec3(pointOrigin1).Sub(mgl64.Vec3(pointOrigin2))

// the error value is the length between pointOrigin1 and pointOrigin2.
errorVal := absoluteDifference.Len()

if errorVal > errorCriterion {
t.Fatalf("The error value (%2.16f) for on point %v is too large\n", errorVal, point)
}

fmt.Printf(
`Origin1: (cartesian) %2.16f
Origin1: (geodetic) %2.16f
Origin2: (cartesian) %2.16f
Error: %2.16f
All error values are less than or equal to 1 (PASS)
`,
pointOrigin1,
pointGeodetic,
pointOrigin2,
errorVal,
)

}

func testGeodeticFromGeocentric(t *testing.T, point Geocentric) {

// make 3 sets of two cartesian (geocentric) points, the second of each moved by 1m in each
// of x, y, and z directions
pointOrigin := point
pointOriginX := Geocentric{*pointOrigin.X() + movingDistance, *pointOrigin.Y(), *pointOrigin.Z()}

pointOriginY := Geocentric{*pointOrigin.X(), *pointOrigin.Y() - movingDistance, *pointOrigin.Z()}

pointOriginZ := Geocentric{*pointOrigin.X(), *pointOrigin.Y(), *pointOrigin.Z() + movingDistance}

movedPoints := []Geocentric{pointOriginX, pointOriginY, pointOriginZ}

pointOriginGeodetic := GeodeticFromGeocentric(pointOrigin)
pointOriginSpherical := Spherical(pointOriginGeodetic)

pointsGeodetic := []Geodetic{}
distances := []float64{}
errorVals := []float64{}

// convert each pair to lat/lon (geodetic)
for _, point := range movedPoints {

// convert to geodetic
pointGeodetic := GeodeticFromGeocentric(point)

pointsGeodetic = append(pointsGeodetic, pointGeodetic)

// save in sphereical struct
pointSpherical := Spherical(pointGeodetic)

// use .GetLengthTo() to measure distance and append to distances
distance := pointOriginSpherical.GetLengthTo(pointSpherical)

distances = append(distances, distance)

// check that the error value is less than errorCriterion meters. We expect the difference
// in distance to be movingDistance meter, so subtract movingDistance from distance
errorVal := math.Abs(distance - movingDistance)
if errorVal > errorCriterion {
t.Fatalf("The error value for on point %v is too large\n", point)
}

errorVals = append(errorVals, errorVal)

}

fmt.Printf(
` Origin: (cartesian) %2.16f (geodetic) %2.16f,
ShiftX+1: (cartesian) %2.16f (geodetic) %2.16f,
ShiftY-1: (cartesian) %2.16f (geodetic) %2.16f,
ShiftZ+1: (cartesian) %2.16f (geodetic) %2.16f,
Distance 1 (delta x): %2.16f
Distance 2 (delta y): %2.16f
Distance 3 (delta z): %2.16f
Error X: %2.16f
Error Y: %2.16f
Error Z: %2.16f
All error values are less than or equal to 1 (PASS)
`,
pointOrigin, pointOriginGeodetic,
pointOriginX, pointsGeodetic[0],
pointOriginY, pointsGeodetic[1],
pointOriginZ, pointsGeodetic[2],
distances[0], distances[1], distances[2],
errorVals[0], errorVals[1], errorVals[2],
)
}
3 changes: 3 additions & 0 deletions coordinates/Geodetic.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import (
)

// Geodetic is a type for geodetic coordinates.
// The order for geodetic points should be {Longitude, Latitude, Altitude}
type Geodetic mgl64.Vec3

// GeodeticReferenceSystem is the reference system for Geodetic.
// The default is WGS84.
var GeodeticReferenceSystem = wgs84.LonLat()

// GeodeticFromGeocentric converts geocentric coordinates to geodetic coordinates.
// A Geodetic object in the order of {X, Y, Z} should be entered and {Longitude, Latitude, Altitude}
// will be returned
func GeodeticFromGeocentric(geocentric Geocentric) (geodetic Geodetic) {
geodetic[0], geodetic[1], geodetic[2] = GeocentricReferenceSystem.To(GeodeticReferenceSystem)(geocentric[0], geocentric[1], geocentric[2])
return
Expand Down
136 changes: 136 additions & 0 deletions coordinates/Geodetic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package coordinates

import (
"fmt"
"math"
"testing"

"github.com/go-gl/mathgl/mgl64"
)

// Original point data source: https://www.ngs.noaa.gov/NCAT/
// (Note that we are not affiliated with NOAA and we do not claim copyright for their information we use.
// Please see the disclaimer https://www.ngs.noaa.gov/disclaimer.html for more detials.)

func TestGenerateLocalFromGeocentric90(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := Geodetic{0, 90, 0}
testGenerateLocalFromGeocentric(t, point90)
}

func TestGenerateLocalFromGeocentric45(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := Geodetic{0, 45, 0}
testGenerateLocalFromGeocentric(t, point90)
}

func TestGenerateLocalFromGeocentric0(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := Geodetic{0, 0, 0}
testGenerateLocalFromGeocentric(t, point90)
}

func TestGenerateGeocentricFromLocal90(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := mgl64.Vec3{0, 6378137.000, 0}
testGenerateGeocentricFromLocal(t, point90)
}

func TestGenerateGeocentricFromLocal45(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := mgl64.Vec3{4517590.879, 0.0, 4487348.409}
testGenerateGeocentricFromLocal(t, point90)
}

func TestGenerateGeocentricFromLocal0(t *testing.T) {
// point at Lon:0 Lat: 90 Alt: 0
point90 := mgl64.Vec3{6378137.000, 0.0, 0.0}
testGenerateGeocentricFromLocal(t, point90)
}

func testGenerateLocalFromGeocentric(t *testing.T, point Geodetic) {

// general pattern: geocentric1 -> local1 ; geocentric2 -> local2 ; compare local1 and local2
// ToLocalFunction takes the "world" conversion parameters from point (geodetic)
// to local and then saves these conversion parameters in a function to apply
// to another geodetic point.

// generate a moved point in geodetic
pointCartesian := GeocentricFromGeodetic(point)
movedPointCartesian := Geocentric{*pointCartesian.X(), *pointCartesian.Y() - movingDistance, *pointCartesian.Z()}
movedPoint := GeodeticFromGeocentric(movedPointCartesian)

// generate a conversion function
ToLocalFunction := point.GenerateLocalFromGeocentric()

// convert point to local
local := ToLocalFunction(pointCartesian)

// convert movedPoint to local using same function
localMovedPoint := ToLocalFunction(movedPointCartesian)

// measure distance between point and movedPoint. Subtract movingDistance to get errorVal
absoluteDifference := mgl64.Vec3(local).Sub(mgl64.Vec3(localMovedPoint))
errorVal := math.Abs(absoluteDifference.Len() - movingDistance)

if errorVal > errorCriterion {
t.Fatalf("The error value (%2.16f) for on point %v is too large\n", errorVal, point)
}

fmt.Printf(
`Original Point: (cartesian) %2.16f
Moved Point: (cartesian) %2.16f
Original Point to Local: %2.16f
Moved Point to Local: %2.16f
Error: %2.16f
All error values are less than or equal to 1 (PASS)
`,
point,
movedPoint,
local,
localMovedPoint,
errorVal,
)
}

func testGenerateGeocentricFromLocal(t *testing.T, point mgl64.Vec3) {

// general pattern: local -> geocentric1 ; local2 -> geocentric2 ; compare geocentric1 and geocentric2

// generate a moved local point
movedPoint := mgl64.Vec3{point.X(), point.Y() - movingDistance, point.Z()}

// convert both local points to geocentric
pointGeodetic := GeodeticFromGeocentric(Geocentric{point.X(), point.Y(), point.Z()})
//movedPointGeocentric := GeodeticFromGeocentric(movedPoint)

// generate a conversion function (local -> geocentric)
ToGeocentricFunction := pointGeodetic.GenerateGeocentricFromLocal()

// convert local points to geocentric using same function
geocentric := ToGeocentricFunction(point)
movedGeocentric := ToGeocentricFunction(movedPoint)

// measure distance between point and movedPoint. Subtract movingDistance to get errorVal
absoluteDifference := mgl64.Vec3(geocentric).Sub(mgl64.Vec3(movedGeocentric))
errorVal := math.Abs(absoluteDifference.Len() - movingDistance)

if errorVal > errorCriterion {
t.Fatalf("The error value (%2.16f) for on point %v is too large\n", errorVal, point)
}

fmt.Printf(
`Original Point: (cartesian) %2.16f
Moved Point: (cartesian) %2.16f
Original Point to Local: %2.16f
Moved Point to Local: %2.16f
Error: %2.16f
All error values are less than or equal to 1 (PASS)
`,
point,
movedPoint,
geocentric,
movedGeocentric,
errorVal,
)
}
6 changes: 6 additions & 0 deletions coordinates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# geodesy_go/Coordinates
The Coordinates module provides functions and structs for conversion between geodetic, geocentric, and local coordinate systems.

## Examples

See [guides/Coordinates_Guide.ipynb](guides/Coordinates_Guide.ipynb) for an overview of use-cases and examples.
Loading