Last active
April 25, 2024 07:21
-
-
Save thisdougb/e8da04a51947e5e5146c3fe5968a55f0 to your computer and use it in GitHub Desktop.
Create GPS Test Data In Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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