diff options
Diffstat (limited to 'vendor/github.com/tkrajina/gpxgo/gpx/geo.go')
| -rw-r--r-- | vendor/github.com/tkrajina/gpxgo/gpx/geo.go | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/vendor/github.com/tkrajina/gpxgo/gpx/geo.go b/vendor/github.com/tkrajina/gpxgo/gpx/geo.go new file mode 100644 index 0000000..b18008d --- /dev/null +++ b/vendor/github.com/tkrajina/gpxgo/gpx/geo.go @@ -0,0 +1,357 @@ +// 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 +} |
