Skip to content

Instantly share code, notes, and snippets.

@thisdougb
Last active April 25, 2024 07:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thisdougb/e8da04a51947e5e5146c3fe5968a55f0 to your computer and use it in GitHub Desktop.
Save thisdougb/e8da04a51947e5e5146c3fe5968a55f0 to your computer and use it in GitHub Desktop.
Create GPS Test Data In Go
package main
import (
"fmt"
"math"
"math/rand"
"github.com/paulmach/orb"
"github.com/paulmach/orb/geo"
"github.com/paulmach/orb/geojson"
)
// the compass rose, naming format for readability
const (
Direction_N = iota
Direction_NNE
Direction_NE
Direction_ENE
Direction_E
Direction_ESE
Direction_SE
Direction_SSE
Direction_S
Direction_SSW
Direction_SW
Direction_WSW
Direction_W
Direction_WNW
Direction_NW
Direction_NNW
)
const (
compassRoseDegrees = 22.5
)
func main() {
rand.Seed(time.Now().UnixNano())
CreateGPSData()
}
func CreateGPSData() {
line := orb.LineString{orb.Point{-3.188267, 55.953251}}
// general direction and distance per point
bearingRange := [2]int{Direction_SSE, Direction_SSW}
distanceRange := [2]int{10 * 1000, 15 * 1000}
// generate a test GPS track
for range 5000 {
newPoint := generateNewLocation(line[len(line)-1],
bearingRange,
distanceRange)
line = append(line, newPoint)
}
// add skewness in the data
bearingRange = [2]int{Direction_W, Direction_WNW}
distanceRange = [2]int{1000, 1500}
for range 100 {
newPoint := generateNewLocation(line[len(line)-1],
bearingRange,
distanceRange)
line = append(line, newPoint)
}
// export geoJSON data
fc := geojson.NewFeatureCollection()
f := geojson.NewFeature(line)
fc.Append(f)
rawJSON, _ := fc.MarshalJSON()
fmt.Println(string(rawJSON))
}
// generateNewLocation returns a new point in the range of direction and
// distance. It is meant to build non-repetitive but predictable GPS tracks, to
// help generate test input cases.
//
// It's also meant to be readable code.
func generateNewLocation(start orb.Point, direction [2]int, distance [2]int) orb.Point {
// Mistakes with lon/lat indexing area easy to make, explicit index names
// helps
const (
Longitude = 0
Latitude = 1
)
var (
latitudeOneDegreeOfDistance = 111000 // metres
newPoint orb.Point // []float64{Long, Lat}
// convert from degrees to radians
deg2rad = func(d float64) float64 { return d * math.Pi / 180 }
)
// Use trigonometry of a right angled triangle to solve the distances on the ground.
// The hypotenuse is our desired distance to travel, and one angle
// is our desired bearing.
//
// now work out the vertical (longitude) and horizontal (latitude) sides in
// distance units.
hyp := (rand.Float64() * float64(distance[1]-distance[0])) + float64(distance[0])
// Get the compass bearing in degrees, with a little randomness between the
// general direction. Non-linear tracks are easier to troubleshoot visually.
bearingMin := float64(direction[0]) * 22.5
bearingMax := float64(direction[1]) * 22.5
angle := (rand.Float64() * (bearingMax - bearingMin)) + bearingMin
// Calulate the other side lengths using SOH CAH TOA. The Go math package
// works in radians
adj := math.Cos(deg2rad(angle)) * hyp // adjacent side of angle
opp := math.Sin(deg2rad(angle)) * hyp // opposite side of angle
// Each degree change in every latitude equates to ~111 km on the ground. So
// now find the degree change required for the length of adj
latitudeDelta := (1.0 / float64(latitudeOneDegreeOfDistance)) * adj
newPoint[Latitude] = start[Latitude] + latitudeDelta
// Distance on the ground for each degree of longitude changes depending on
// latitude because the earth is not perfectly spherical. So we need to
// calculate the distance of one degree longitude for our current latitude.
p1 := orb.Point{1.0, start[Latitude]}
p2 := orb.Point{2.0, start[Latitude]}
longitudeOneDegreeOfDistance := geo.Distance(p1, p2) // returns metres
// Now we can use this value to calculate the longitude degree change
// required to move opp distance (in a horizontal straight line) at this
// latitude.
longitudeDelta := (1.0 / longitudeOneDegreeOfDistance) * opp
// The new point is a vertical and horizontal shift to arrive at hyp
// distance from the start point on the required bearing.
newPoint[Longitude] = start[Longitude] + longitudeDelta
return newPoint
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment