Skip to content

Improve GPS accuracy when locating videos #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 14, 2023
Merged
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
24 changes: 18 additions & 6 deletions pkg/gopro/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,26 @@ import (

const parent = "gopro"

func gpsLockTypesFromConfig() []int {
key := fmt.Sprintf("%s.gps_fix", parent)
viper.SetDefault(key, []int{2, 3}) // 3d lock, 2d lock
return viper.GetIntSlice(key)
}

func gpsMinAccuracyFromConfig() uint16 {
key := fmt.Sprintf("%s.gps_accuracy", parent)
viper.SetDefault(key, 500)
return uint16(viper.GetUint(key))
}

func gpsMaxAltitudeFromConfig() float64 {
key := fmt.Sprintf("%s.gps_max_altitude", parent)
viper.SetDefault(key, 8000)
return float64(viper.GetUint(key))
}

func gpsCountryCodesFromConfig() []string {
key := fmt.Sprintf("%s.gps_country_codes", parent)
viper.SetDefault(key, []string{}) // 3d lock, 2d lock
return viper.GetStringSlice(key)
}

func gpsMaxCountryCodesFromConfig() int {
key := fmt.Sprintf("%s.gps_max_country_codes", parent)
viper.SetDefault(key, 5)
return viper.GetInt(key)
}
85 changes: 79 additions & 6 deletions pkg/gopro/location.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package gopro
import (
"bytes"
"io"
"math"
"path/filepath"
"strings"

"github.com/codingsince1985/geo-golang/openstreetmap"
"github.com/konradit/gopro-utils/telemetry"
mErrors "github.com/konradit/mmt/pkg/errors"
"github.com/konradit/mmt/pkg/utils"
Expand Down Expand Up @@ -37,10 +39,13 @@ func fromMP4(videoPath string) (*utils.Location, error) {
return nil, err
}

GPSNum := 0
reader := bytes.NewReader(*data)

lastEvent := &telemetry.TELEM{}
coordinates := []utils.Location{}

GetLocation:
for {
event, err := telemetry.Read(reader)
if err != nil && err != io.EOF {
Expand All @@ -62,16 +67,84 @@ func fromMP4(videoPath string) (*utils.Location, error) {

telems := lastEvent.ShitJson()
for _, telem := range telems {
if telem.Speed == 0 || telem.Latitude == 0 || telem.Longitude == 0 || telem.GpsAccuracy > gpsMinAccuracyFromConfig() || !slices.Contains(gpsLockTypesFromConfig(), int(telem.GpsFix)) {
if telem.Altitude > gpsMaxAltitudeFromConfig() || telem.Latitude == 0 || telem.Longitude == 0 || telem.GpsAccuracy > gpsMinAccuracyFromConfig() {
continue
}
return &utils.Location{
Latitude: telem.Latitude,
Longitude: telem.Longitude,
}, nil

CountryCodes := gpsCountryCodesFromConfig()
if len(CountryCodes) != 0 {
service := openstreetmap.Geocoder()

address, err := service.ReverseGeocode(telem.Latitude, telem.Longitude)
if err != nil || !slices.Contains(CountryCodes, address.CountryCode) {
continue
}

GPSNum++
}

coordinates = append(coordinates, utils.Location{Latitude: telem.Latitude, Longitude: telem.Longitude})

if GPSNum > gpsMaxCountryCodesFromConfig() {
break GetLocation
}
}
*lastEvent = *event
}

return nil, mErrors.ErrNoGPS
if len(coordinates) == 0 {
return nil, mErrors.ErrNoGPS
}

closestLocation := getClosestLocation(coordinates)

return &utils.Location{
Latitude: closestLocation.Latitude,
Longitude: closestLocation.Longitude,
}, nil
}

func getClosestLocation(locations []utils.Location) utils.Location {
// Find the nearest and repeat location
counts := make(map[utils.Location]int)
for _, loc := range locations {
counts[loc]++
}

mostFrequentLocation := utils.Location{}
maxCount := 0
for loc, count := range counts {
if count > maxCount {
mostFrequentLocation = loc
maxCount = count
} else if count == maxCount {
// If there are multiple locations with the same frequency, choose the closest one
distanceToLoc := distance(locations[0], loc)
distanceToMostFrequent := distance(locations[0], mostFrequentLocation)
if distanceToLoc < distanceToMostFrequent {
mostFrequentLocation = loc
}
}
}

return mostFrequentLocation
}

func distance(loc1 utils.Location, loc2 utils.Location) float64 {
lat1 := degreesToRadians(loc1.Latitude)
lon1 := degreesToRadians(loc1.Longitude)
lat2 := degreesToRadians(loc2.Latitude)
lon2 := degreesToRadians(loc2.Longitude)

deltaLat := lat2 - lat1
deltaLon := lon2 - lon1

a := math.Pow(math.Sin(deltaLat/2), 2) + math.Cos(lat1)*math.Cos(lat2)*math.Pow(math.Sin(deltaLon/2), 2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))

return 6371 * c // radius of the earth in km
}

func degreesToRadians(degrees float64) float64 {
return degrees * math.Pi / 180
}
5 changes: 3 additions & 2 deletions pkg/gopro/location_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ import (

var videoTests = map[string]bool{
"hero5.mp4": true,
"hero6+ble.mp4": false,
"hero6+ble.mp4": true,
"hero6.mp4": true,
"hero6a.mp4": true,

"hero7.mp4": false,
"hero8.mp4": false,
"hero8.mp4": true,
"karma.mp4": false,
"max-360mode.mp4": true,
"max-heromode.mp4": true,
}
Expand Down
34 changes: 22 additions & 12 deletions pkg/utils/ordering.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package utils
import (
"os"
"path/filepath"
"sync"
)

type locationUtil interface {
Expand All @@ -18,6 +19,8 @@ func GetOrder(sortoptions SortOptions, GetLocation locationUtil, osPathname, out
order := orderFromConfig()
dayFolder := out

var wg sync.WaitGroup

for _, item := range order {
switch item {
case "date":
Expand All @@ -30,24 +33,31 @@ func GetOrder(sortoptions SortOptions, GetLocation locationUtil, osPathname, out
if GetLocation == nil {
continue
}
location := fallbackFromConfig()
locationFromFile, locerr := GetLocation.GetLocation(osPathname)
if locerr == nil {
reverseLocation, reverseerr := ReverseLocation(*locationFromFile)
if reverseerr == nil {
location = reverseLocation
if location == "" || location == " " {
location = fallbackFromConfig()
wg.Add(1)
go func() {
defer wg.Done()
location := fallbackFromConfig()
locationFromFile, locerr := GetLocation.GetLocation(osPathname)
if locerr == nil {
reverseLocation, reverseerr := ReverseLocation(*locationFromFile)
if reverseerr == nil {
location = reverseLocation
if location == "" || location == " " {
location = fallbackFromConfig()
}
}
}
}
if sortoptions.ByLocation {
dayFolder = filepath.Join(dayFolder, location)
}
if sortoptions.ByLocation {
dayFolder = filepath.Join(dayFolder, location)
}
}()

default:
// Not supported
}
}
wg.Wait()

if _, err := os.Stat(dayFolder); os.IsNotExist(err) {
_ = os.MkdirAll(dayFolder, 0o755)
}
Expand Down