mirror of
https://github.com/Luzifer/staticmap.git
synced 2024-12-30 09:41:20 +00:00
358 lines
9.4 KiB
Go
358 lines
9.4 KiB
Go
|
// Copyright 2013, 2014 Peter Vasil, Tomo Krajina. All
|
||
|
// rights reserved. Use of this source code is governed
|
||
|
// by a BSD-style license that can be found in the
|
||
|
// LICENSE file.
|
||
|
|
||
|
package gpx
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
"sort"
|
||
|
)
|
||
|
|
||
|
const oneDegree = 1000.0 * 10000.8 / 90.0
|
||
|
const earthRadius = 6371 * 1000
|
||
|
|
||
|
func ToRad(x float64) float64 {
|
||
|
return x / 180. * math.Pi
|
||
|
}
|
||
|
|
||
|
type Location interface {
|
||
|
GetLatitude() float64
|
||
|
GetLongitude() float64
|
||
|
GetElevation() NullableFloat64
|
||
|
}
|
||
|
|
||
|
type MovingData struct {
|
||
|
MovingTime float64
|
||
|
StoppedTime float64
|
||
|
MovingDistance float64
|
||
|
StoppedDistance float64
|
||
|
MaxSpeed float64
|
||
|
}
|
||
|
|
||
|
func (md MovingData) Equals(md2 MovingData) bool {
|
||
|
return md.MovingTime == md2.MovingTime &&
|
||
|
md.MovingDistance == md2.MovingDistance &&
|
||
|
md.StoppedTime == md2.StoppedTime &&
|
||
|
md.StoppedDistance == md2.StoppedDistance &&
|
||
|
md.MaxSpeed == md.MaxSpeed
|
||
|
}
|
||
|
|
||
|
type SpeedsAndDistances struct {
|
||
|
Speed float64
|
||
|
Distance float64
|
||
|
}
|
||
|
|
||
|
// HaversineDistance returns the haversine distance between two points.
|
||
|
//
|
||
|
// Implemented from http://www.movable-type.co.uk/scripts/latlong.html
|
||
|
func HaversineDistance(lat1, lon1, lat2, lon2 float64) float64 {
|
||
|
dLat := ToRad(lat1 - lat2)
|
||
|
dLon := ToRad(lon1 - lon2)
|
||
|
thisLat1 := ToRad(lat1)
|
||
|
thisLat2 := ToRad(lat2)
|
||
|
|
||
|
a := math.Sin(dLat/2)*math.Sin(dLat/2) + math.Sin(dLon/2)*math.Sin(dLon/2)*math.Cos(thisLat1)*math.Cos(thisLat2)
|
||
|
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
|
||
|
d := earthRadius * c
|
||
|
|
||
|
return d
|
||
|
}
|
||
|
|
||
|
func length(locs []Point, threeD bool) float64 {
|
||
|
var previousLoc Point
|
||
|
var res float64
|
||
|
for k, v := range locs {
|
||
|
if k > 0 {
|
||
|
previousLoc = locs[k-1]
|
||
|
var d float64
|
||
|
if threeD {
|
||
|
d = v.Distance3D(&previousLoc)
|
||
|
} else {
|
||
|
d = v.Distance2D(&previousLoc)
|
||
|
}
|
||
|
res += d
|
||
|
}
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func Length2D(locs []Point) float64 {
|
||
|
return length(locs, false)
|
||
|
}
|
||
|
|
||
|
func Length3D(locs []Point) float64 {
|
||
|
return length(locs, true)
|
||
|
}
|
||
|
|
||
|
func CalcMaxSpeed(speedsDistances []SpeedsAndDistances) float64 {
|
||
|
lenArrs := len(speedsDistances)
|
||
|
|
||
|
if len(speedsDistances) < 20 {
|
||
|
//log.Println("Segment too small to compute speed, size: ", lenArrs)
|
||
|
return 0.0
|
||
|
}
|
||
|
|
||
|
var sum_dists float64
|
||
|
for _, d := range speedsDistances {
|
||
|
sum_dists += d.Distance
|
||
|
}
|
||
|
average_dist := sum_dists / float64(lenArrs)
|
||
|
|
||
|
var variance float64
|
||
|
for i := 0; i < len(speedsDistances); i++ {
|
||
|
variance += math.Pow(speedsDistances[i].Distance-average_dist, 2)
|
||
|
}
|
||
|
stdDeviation := math.Sqrt(variance)
|
||
|
|
||
|
// ignore items with distance too long
|
||
|
filteredSD := make([]SpeedsAndDistances, 0)
|
||
|
for i := 0; i < len(speedsDistances); i++ {
|
||
|
dist := math.Abs(speedsDistances[i].Distance - average_dist)
|
||
|
if dist <= stdDeviation*1.5 {
|
||
|
filteredSD = append(filteredSD, speedsDistances[i])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
speeds := make([]float64, len(filteredSD))
|
||
|
for i, sd := range filteredSD {
|
||
|
speeds[i] = sd.Speed
|
||
|
}
|
||
|
|
||
|
speedsSorted := sort.Float64Slice(speeds)
|
||
|
|
||
|
if len(speedsSorted) == 0 {
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
maxIdx := int(float64(len(speedsSorted)) * 0.95)
|
||
|
if maxIdx >= len(speedsSorted) {
|
||
|
maxIdx = len(speedsSorted) - 1
|
||
|
}
|
||
|
if maxIdx < 0 {
|
||
|
maxIdx = 0
|
||
|
}
|
||
|
return speedsSorted[maxIdx]
|
||
|
}
|
||
|
|
||
|
func CalcUphillDownhill(elevations []NullableFloat64) (float64, float64) {
|
||
|
elevsLen := len(elevations)
|
||
|
if elevsLen == 0 {
|
||
|
return 0.0, 0.0
|
||
|
}
|
||
|
|
||
|
smoothElevations := make([]NullableFloat64, elevsLen)
|
||
|
|
||
|
for i, elev := range elevations {
|
||
|
currEle := elev
|
||
|
if 0 < i && i < elevsLen-1 {
|
||
|
prevEle := elevations[i-1]
|
||
|
nextEle := elevations[i+1]
|
||
|
if prevEle.NotNull() && nextEle.NotNull() && elev.NotNull() {
|
||
|
currEle = *NewNullableFloat64(prevEle.Value()*0.3 + elev.Value()*0.4 + nextEle.Value()*0.3)
|
||
|
}
|
||
|
}
|
||
|
smoothElevations[i] = currEle
|
||
|
}
|
||
|
|
||
|
var uphill float64
|
||
|
var downhill float64
|
||
|
|
||
|
for i := 1; i < len(smoothElevations); i++ {
|
||
|
if smoothElevations[i].NotNull() && smoothElevations[i-1].NotNull() {
|
||
|
d := smoothElevations[i].Value() - smoothElevations[i-1].Value()
|
||
|
if d > 0.0 {
|
||
|
uphill += d
|
||
|
} else {
|
||
|
downhill -= d
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return uphill, downhill
|
||
|
}
|
||
|
|
||
|
func distance(lat1, lon1 float64, ele1 NullableFloat64, lat2, lon2 float64, ele2 NullableFloat64, threeD, haversine bool) float64 {
|
||
|
absLat := math.Abs(lat1 - lat2)
|
||
|
absLon := math.Abs(lon1 - lon2)
|
||
|
if haversine || absLat > 0.2 || absLon > 0.2 {
|
||
|
return HaversineDistance(lat1, lon1, lat2, lon2)
|
||
|
}
|
||
|
|
||
|
coef := math.Cos(ToRad(lat1))
|
||
|
x := lat1 - lat2
|
||
|
y := (lon1 - lon2) * coef
|
||
|
|
||
|
distance2d := math.Sqrt(x*x+y*y) * oneDegree
|
||
|
|
||
|
if !threeD || ele1 == ele2 {
|
||
|
return distance2d
|
||
|
}
|
||
|
|
||
|
eleDiff := 0.0
|
||
|
if ele1.NotNull() && ele2.NotNull() {
|
||
|
eleDiff = ele1.Value() - ele2.Value()
|
||
|
}
|
||
|
|
||
|
return math.Sqrt(math.Pow(distance2d, 2) + math.Pow(eleDiff, 2))
|
||
|
}
|
||
|
|
||
|
func distanceBetweenLocations(loc1, loc2 Location, threeD, haversine bool) float64 {
|
||
|
lat1 := loc1.GetLatitude()
|
||
|
lon1 := loc1.GetLongitude()
|
||
|
ele1 := loc1.GetElevation()
|
||
|
|
||
|
lat2 := loc2.GetLatitude()
|
||
|
lon2 := loc2.GetLongitude()
|
||
|
ele2 := loc2.GetElevation()
|
||
|
|
||
|
return distance(lat1, lon1, ele1, lat2, lon2, ele2, threeD, haversine)
|
||
|
}
|
||
|
|
||
|
func Distance2D(lat1, lon1, lat2, lon2 float64, haversine bool) float64 {
|
||
|
return distance(lat1, lon1, *new(NullableFloat64), lat2, lon2, *new(NullableFloat64), false, haversine)
|
||
|
}
|
||
|
|
||
|
func Distance3D(lat1, lon1 float64, ele1 NullableFloat64, lat2, lon2 float64, ele2 NullableFloat64, haversine bool) float64 {
|
||
|
return distance(lat1, lon1, ele1, lat2, lon2, ele2, true, haversine)
|
||
|
}
|
||
|
|
||
|
func ElevationAngle(loc1, loc2 Point, radians bool) float64 {
|
||
|
if loc1.Elevation.Null() || loc2.Elevation.Null() {
|
||
|
return 0.0
|
||
|
}
|
||
|
|
||
|
b := loc2.Elevation.Value() - loc1.Elevation.Value()
|
||
|
a := loc2.Distance2D(&loc1)
|
||
|
|
||
|
if a == 0.0 {
|
||
|
return 0.0
|
||
|
}
|
||
|
|
||
|
angle := math.Atan(b / a)
|
||
|
|
||
|
if radians {
|
||
|
return angle
|
||
|
}
|
||
|
|
||
|
return 180 * angle / math.Pi
|
||
|
}
|
||
|
|
||
|
// Distance of point from a line given with two points.
|
||
|
func distanceFromLine(point Point, linePoint1, linePoint2 GPXPoint) float64 {
|
||
|
a := linePoint1.Distance2D(&linePoint2)
|
||
|
|
||
|
if a == 0 {
|
||
|
return linePoint1.Distance2D(&point)
|
||
|
}
|
||
|
|
||
|
b := linePoint1.Distance2D(&point)
|
||
|
c := linePoint2.Distance2D(&point)
|
||
|
|
||
|
s := (a + b + c) / 2.
|
||
|
|
||
|
return 2.0 * math.Sqrt(math.Abs((s * (s - a) * (s - b) * (s - c)))) / a
|
||
|
}
|
||
|
|
||
|
func getLineEquationCoefficients(location1, location2 Point) (float64, float64, float64) {
|
||
|
if location1.Longitude == location2.Longitude {
|
||
|
// Vertical line:
|
||
|
return 0.0, 1.0, -location1.Longitude
|
||
|
} else {
|
||
|
a := (location1.Latitude - location2.Latitude) / (location1.Longitude - location2.Longitude)
|
||
|
b := location1.Latitude - location1.Longitude*a
|
||
|
return 1.0, -a, -b
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func simplifyPoints(points []GPXPoint, maxDistance float64) []GPXPoint {
|
||
|
if len(points) < 3 {
|
||
|
return points
|
||
|
}
|
||
|
|
||
|
begin, end := points[0], points[len(points)-1]
|
||
|
|
||
|
/*
|
||
|
Use a "normal" line just to detect the most distant point (not its real distance)
|
||
|
this is because this is faster to compute than calling distance_from_line() for
|
||
|
every point.
|
||
|
|
||
|
This is an approximation and may have some errors near the poles and if
|
||
|
the points are too distant, but it should be good enough for most use
|
||
|
cases...
|
||
|
*/
|
||
|
a, b, c := getLineEquationCoefficients(begin.Point, end.Point)
|
||
|
|
||
|
tmpMaxDistance := -1000000000.0
|
||
|
tmpMaxDistancePosition := 0
|
||
|
for pointNo, point := range points {
|
||
|
d := math.Abs(a*point.Latitude + b*point.Longitude + c)
|
||
|
if d > tmpMaxDistance {
|
||
|
tmpMaxDistance = d
|
||
|
tmpMaxDistancePosition = pointNo
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//fmt.Println()
|
||
|
|
||
|
//fmt.Println("tmpMaxDistancePosition=", tmpMaxDistancePosition, " len(points)=", len(points))
|
||
|
|
||
|
realMaxDistance := distanceFromLine(points[tmpMaxDistancePosition].Point, begin, end)
|
||
|
//fmt.Println("realMaxDistance=", realMaxDistance, " len(points)=", len(points))
|
||
|
|
||
|
if realMaxDistance < maxDistance {
|
||
|
return []GPXPoint{begin, end}
|
||
|
}
|
||
|
|
||
|
points1 := points[:tmpMaxDistancePosition]
|
||
|
point := points[tmpMaxDistancePosition]
|
||
|
points2 := points[tmpMaxDistancePosition+1:]
|
||
|
|
||
|
//fmt.Println("before simplify: len_points=", len(points), " l_points1=", len(points1), " l_points2=", len(points2))
|
||
|
|
||
|
points1 = simplifyPoints(points1, maxDistance)
|
||
|
points2 = simplifyPoints(points2, maxDistance)
|
||
|
|
||
|
//fmt.Println("after simplify: len_points=", len(points), " l_points1=", len(points1), " l_points2=", len(points2))
|
||
|
|
||
|
result := append(points1, point)
|
||
|
return append(result, points2...)
|
||
|
}
|
||
|
|
||
|
func smoothHorizontal(originalPoints []GPXPoint) []GPXPoint {
|
||
|
result := make([]GPXPoint, len(originalPoints))
|
||
|
|
||
|
for pointNo, point := range originalPoints {
|
||
|
result[pointNo] = point
|
||
|
if 1 <= pointNo && pointNo <= len(originalPoints)-2 {
|
||
|
previousPoint := originalPoints[pointNo-1]
|
||
|
nextPoint := originalPoints[pointNo+1]
|
||
|
result[pointNo] = point
|
||
|
result[pointNo].Latitude = previousPoint.Latitude*0.4 + point.Latitude*0.2 + nextPoint.Latitude*0.4
|
||
|
result[pointNo].Longitude = previousPoint.Longitude*0.4 + point.Longitude*0.2 + nextPoint.Longitude*0.4
|
||
|
//log.Println("->(%f, %f)", seg.Points[pointNo].Latitude, seg.Points[pointNo].Longitude)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
func smoothVertical(originalPoints []GPXPoint) []GPXPoint {
|
||
|
result := make([]GPXPoint, len(originalPoints))
|
||
|
|
||
|
for pointNo, point := range originalPoints {
|
||
|
result[pointNo] = point
|
||
|
if 1 <= pointNo && pointNo <= len(originalPoints)-2 {
|
||
|
previousPointElevation := originalPoints[pointNo-1].Elevation
|
||
|
nextPointElevation := originalPoints[pointNo+1].Elevation
|
||
|
if previousPointElevation.NotNull() && point.Elevation.NotNull() && nextPointElevation.NotNull() {
|
||
|
result[pointNo].Elevation = *NewNullableFloat64(previousPointElevation.Value()*0.4 + point.Elevation.Value()*0.2 + nextPointElevation.Value()*0.4)
|
||
|
//log.Println("->%f", seg.Points[pointNo].Elevation.Value())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result
|
||
|
}
|