home
erste Version am 02.02.2022
letzte Änderung am 07.02.2022

Z3D map to gpx

Kurz vor Weihnachten 2021 habe ich mir eine DashCam gegönnt.
Die DashCam schreibt (bei meinen Einstellungen) alle fünf Minuten eine neue mp4-Datei.
Parallel wird zu jeder Video-Datei eine map-Datei erstellt, die sekündlich Timestamp, GeoKoordinaten und gefahrene Geschwindigkeit als csv erfasst.
Und was liegt da näher, als mir ein Parser-Script zu bauen, das diese Daten in ein von wxOSM verarbeitbares Format umwandelt.



Nach der Übertragung aller Dateien von der SD-Karte auf den Rechner, siehts im Zielverzeichnis erstmal so aus:
-rw-r--r-- 1 dede users  92217549 29. Dez 14:33 2021_1229_133239_001A.MP4
-rw-r--r-- 1 dede users     15975  8. Jan 11:37 2022_0108_103529_002A.map
-rw-r--r-- 1 dede users 693894821  8. Jan 11:37 2022_0108_103529_002A.MP4
-rw-r--r-- 1 dede users     18482  8. Jan 11:45 2022_0108_104026_003A.map
[....]
-rw-r--r-- 1 dede users       185  8. Jan 14:10 2022_0108_131028_033A.map
-rw-r--r-- 1 dede users  32891957  8. Jan 14:10 2022_0108_131028_033A.MP4

Und innerhalb einer map-Datei dann etwa so:
V,080122,113528,0.0000,0,0.0000,0,0.00,0.00,0.00,0.00;
V,080122,113529,0.0000,0,0.0000,0,0.00,0.00,0.00,0.00;
[....]
V,080122,113646,0.0000,0,0.0000,0,24.56,0.00,0.00,0.00;
V,080122,113647,0.0000,0,0.0000,0,40.73,0.00,0.00,0.00;
V,080122,113649,0.0000,0,0.0000,0,47.78,0.00,0.00,0.00;
A,080122,103353,5448.2363,N,931.2752,E,52.39,0.00,-2.75,0.00;
A,080122,103354,5448.2393,N,931.2582,E,56.97,0.00,-2.75,0.00;
A,080122,103355,5448.2417,N,931.2417,E,61.88,0.00,-2.75,0.00;
A,080122,103355,5448.2422,N,931.2374,E,63.12,0.00,-2.75,0.00;
A,080122,103357,5448.2461,N,931.2071,E,67.65,0.00,-2.75,0.00;
A,080122,103358,5448.2476,N,931.1886,E,68.75,0.00,-2.75,0.00;
A,080122,103359,5448.2495,N,931.1706,E,70.28,0.00,-2.75,0.00;
A,080122,103400,5448.2515,N,931.1523,E,70.73,0.00,-2.75,0.00;
A,080122,103401,5448.2529,N,931.1338,E,71.64,0.00,-2.75,0.00;
A,080122,103401,5448.2539,N,931.1294,E,70.34,0.00,-2.75,0.00;
A,080122,103403,5448.2593,N,931.1062,E,78.60,0.00,0.00,0.00;
A,080122,103404,5448.2612,N,931.0825,E,72.77,0.00,0.00,0.00;
A,080122,103405,5448.2627,N,931.0620,E,74.19,0.00,0.00,0.00;
A,080122,103406,5448.2651,N,931.0458,E,77.10,0.00,0.00,0.00;
A,080122,103407,5448.2632,N,931.0232,E,74.56,0.00,0.00,0.00;
[...]
Das erste Element kann offenbar die Werte V und A annehmen. Nur bei A sind die GPS-Daten brauchbar.
Als zweites kommt das Datum, als drittes die Uhrzeit.
Die folgenden vier Elemente ergeben die GeoKoordinate. Und zwar offenbar im Format "Grad, Dezimalminuten".
Danach die Geschwindigkeit in km/h ...und drei weitere Elemente mit noch unbekannter Bedeutung.
Das Format "Grad, Dezimalminuten" kannte ich vorher nicht und es hat einige Zeit gedauert, bis ich dann endlich diese Seite gefunden hatte, wo ein halbsweg ähnlich aussehendes Koordinaten-Format angenommen wurde und auch gleich umgerechnet werden konnte.
Hier war ich dann auch noch kurz - das erschien mir dann aber doch als völliger Overkill für meinen Zweck.
Am ersten Abend ist schließlich das hier entstanden (locker 5h für 6 Zeilen):
Python 3.6.15 (default, Sep 23 2021, 15:41:43) [GCC] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def ddm2dd(ddmf):
...     ddm = ddmf/100.0
...     d = int(ddm)
...     m = int((ddm-d)*100)
...     s = round((((ddm-d)*100)-m)*60, 4)
...     return round(d+m/60.0+s/3600.0, 6)
...
>>> ddm2dd(5448.2363)
54.803938
>>> ddm2dd(931.2752)
9.521253
>>>

Damit konnte ich nun also die Koordinaten des ersten brauchbaren A-Satzes aus obiger map-Datei in Koordinaten im Format "Dezimalgrad" umrechnen.
Gestern kam das Parsen aller map-Dateien in einem Verzeichnis hinzu, heute dann noch variable Commandline-Parameter bzw. die Nutzung von argparse.

Der Aufruf sieht folgendermaßen aus, nachdem ich Hin- und Rückfahrt der aufgezeichneten Strecke bereits in einzelne Verzeichnisse verteilt hatte:
~> ./z3d_map_to_gpx.py -f /10TB2/DashCam/Reppenstedt_Januar_2022/rückfahrt -d Rückfahrt
files parsed  : 30
points written: 8478

~> ./z3d_map_to_gpx.py -f /10TB2/DashCam/Reppenstedt_Januar_2022/hinfahrt/ -d Hinfahrt -o hinfahrt
files parsed  : 32
points written: 8976

~> ll *.gpx
-rw-r--r-- 1 dede users 1233225  2. Feb 19:55 hinfahrt.gpx
-rw-r--r-- 1 dede users 1165256  2. Feb 19:53 z3d_trk.gpx
Und hinfahrt.gpx in wxOSM dann so:
Screenshot von wxOSM nach load
        von hinfahrt.gpx

Meine erzeugten gpx-Dateien scheinen hinreichend standardkonform zu sein, um z.B. auch hier korrekt angezeigt zu werden.
Den Track nachfahren kann man natürlich auch - inkl. Geschwindigkeitsanzeige (hier mal die Auffahrt auf die A7):

Jaaaa...ich bin ein Schleicher. Aber war Schietwetter. Dauerregen!

Hier mal die gesamte Rückfahrt im Schnelldurchlauf:
Auch bei der Rückfahrt hats die meiste Zeit mindestens genieselt - manchmal sogar geschüttet....
Aber unabhängig davon... fahre ich auch bei gutem Wetter ganz selten schneller als 120 km/h.
Gemäß des letzten Info-Popups im Video bin ich 222.21 km weit gefahren und habe dafür 2:23:46 h benötigt.
Wenn ich richtig gerechnet habe, ergibt das eine Durchschnittsgeschwindigkeit von 93 km/h.
Betrachte ich nur Zeit (1:50:55 h) und Strecke (191,06 km) auf der Autobahn, ergibt sich eine Durchschnittsgeschwindigkeit von 103 km/h.
Hmmm...spannend. Da hätte ich schon eher auf 110 km/h getippt.
Aber stimmt schon, dass ich immer mal wieder längere Strecken mit ~80 km/h unterwegs sein musste...weil Hamburg oder Baustelle.
Dann passt das als Mittelwert.

Und noch das Wichtigste: das Script.
#! /usr/bin/env python3
# -*- coding: utf-8 -

# z3d_map_to_gpx.py
#
# D.Ahlgrimm 01.02.2022

import argparse
import os
import sys

VERSION="1.0"

HEADER="""<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<gpx version="1.1" creator="z3d_map_to_gpx" xmlns="http://www.topografix.com/GPX/1/1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
<trk>
<!-- <desc>%s</desc> -->
<trkseg>
"""

LINE=""" <trkpt lat="%s" lon="%s"><time>%s</time><extensions><speedkmh>%s</speedkmh></extensions></trkpt>\n"""

FOOTER=""" </trkseg>
</trk>
</gpx>
"""


# ----------------------------------------------------------------------
# Liefert zu einer Koordinate in "Grad + Dezimalminuten"
# den entsprechenden Wert in "Dezimalgrad".
def ddm2dd(ddmf):
if isinstance(ddmf, str):
ddmf=float(ddmf)
ddm = ddmf/100.0
d = int(ddm)
m = int((ddm-d)*100)
s = round((((ddm-d)*100)-m)*60, 4)
return round(d+m/60.0+s/3600.0, 6)


# ----------------------------------------------------------------------
# Setzt den ArgParser auf.
def setupArgParser():
desc="%(prog)s v"+VERSION+" - Create gpx-file from all map-files in a given folder."
usage='%(prog)s [-h] [-f FOLDER] [-o OUTPUT] [-d DESCRIPTION]'
epilog='Examples:\n' \
' %(prog)s\n' \
' write ./z3d_trk.gpx from all *.map-files in .\n' \
' %(prog)s -f /tmp/tour -o ~/abc -d "some description"\n' \
' write ~/abc.gpx for all *.map-files in /tmp/tour and set\n' \
' gpx-description to "some description"'

parser=argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter,
description=desc, usage=usage, epilog=epilog)
parser.add_argument('-f', dest='folder', type=str,
help='source directory (default is ".")')
parser.add_argument('-o', dest='output', default="z3d_trk.gpx", type=str,
help='output gpx-filename (default is "z3d_trk")')
parser.add_argument('-d', dest='desc', type=str,
help='description/title in gpx-file (default is <input dirname>)')
args=parser.parse_args()

if args.folder and not os.path.isdir(args.folder):
parser.error("folder not an existing directory!")
return(args)


# ----------------------------------------------------------------------
#
if __name__=="__main__":
args=setupArgParser()

if args.folder:
args.folder=os.path.normpath(args.folder)
else:
args.folder=os.getcwd()
if not args.output.endswith(".gpx"):
args.output+=".gpx"
if not args.desc:
args.desc=os.path.basename(args.folder)

#print("args.folder", args.folder)
#print("args.output", args.output)
#print("args.desc", args.desc)

file_count=points_cnt=0
fo=open(args.output, "wt")
fo.write(HEADER%(args.desc,))
for curdir, subdirlst, filenamelst in os.walk(args.folder):
for fn in sorted(filenamelst):
if fn.endswith(".map"):
file_count+=1
with open(os.path.join(args.folder, fn), 'r') as fl:
lines=fl.readlines()
for ln in lines:
lns=ln.strip()
if lns.startswith("A,"):
elems=lns.split(",")
_, dt, tm, lat, _, lon, _, spd, *_=elems
dd, mm, yy=dt[0:2], dt[2:4], dt[4:6]
hh, mn, ss=tm[0:2], tm[2:4], tm[4:6]
dt="20%02d-%02d-%02dT%02d:%02d:%02dZ"%(int(yy), int(mm), int(dd), int(hh), int(mn), int(ss))
#print(dt, ddm2dd(lat), ddm2dd(lon), spd)
fo.write(LINE%(ddm2dd(lat), ddm2dd(lon), dt, spd))
points_cnt+=1
fo.write(FOOTER)
print("files parsed :", file_count)
print("points written:", points_cnt)

download als ZIP

Einen kleinen Pfusch habe ich mir geleistet (weils mich nicht stört): die Ortszeit aus der Dashcam wird gnadenlos als Zulu-Zeit betrachtet.
Weiterhin ignoriere ich den Inhalt des fünften und siebten Elementes. Wenn da statt N ein S stünde oder statt E ein W, müsste das Ergebnis jeweils negativ werden. Aber...in der Gegend werde ich garantiert niemals mit [m]einem Auto rumfahren - wenn doch, dann wäre es mit Sicherheit ein Mietwagen.



Eigentlich brauche ich es ja nicht....aber erkannter Pfusch wurmt mich schon irgendwie....speziell dann, wenn ich eh gerade nix besseres zu tun habe.
Beide oben genannte Pfuscherein sind in der v1.1 nun gefixed.
Der Ortszeit/Zulu-Fix tut. Jetzt passt auch die in wxOSM angezeigte Zeit zu der Zeit, die im Video eingeblendet ist.
Den zweiten Fix konnte ich (mangels Daten) nur trocken testen. Aber da tut er:
Python 3.6.15 (default, Sep 23 2021, 15:41:43) [GCC] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def ddm2dd(ddmf, d):
...     if isinstance(ddmf, str):
...         ddmf=float(ddmf)
...     if d.upper() in ("S", "W"):
...         ddmf=-ddmf
...     ddm = ddmf/100.0
...     d = int(ddm)
...     m = int((ddm-d)*100)
...     s = round((((ddm-d)*100)-m)*60, 4)
...     return round(d+m/60.0+s/3600.0, 6)
...
>>> ddm2dd(2846.05955, "S")
-28.767659
>>> ddm2dd(5636.09375, "W")
-56.601562
>>>

...passt zu dem, was ich bekomme, wenn ich hier unter "Grad, Dezimalminuten" die Koordinaten "S28° 46.05955 W56° 36.09375" rein-paste.
Unter "Dezimalgrad" erscheint dann "-28.767659105691. -56.601562500000". Und das passt zum Output meiner Funktion.
Ob die DasCam das auch als "...,2846.05955,S,5636.09375,W,..." in die map-Datei schriebe, wenn ich mit ihr gerade hier
wxOSM @ testkoordinaten
rumkurven würde, entzieht sich meiner Kenntnis :-)

Speziell kibbelig könnte es bei z.B. dieser Koordinate werden: 49.32512. 4.13085
Wenn ich die auf obiger Website umrechnen lasse, kommt das hier: N49° 19.5072 E4° 7.851
Also einstellige Minuten beim Längengrad. Aus ddm2dd(47.851, "E") folgt 0.797517.
Erst, wenn ich zwecks Zweistelligkeit eine Null einfüge, passt es wieder: ddm2dd(407.851, "E") --> 4.13085
Dementsprechend ist zu hoffen, dass in so einem Fall ...,4919.5072,N,407.851,E,..... in der map-Datei landet.

Gerade habe ich in einer meiner map-Dateien das hier gefunden: ....5314.2993,N,1005.3441,E....
Liefert: ddm2dd(5314.2993, "N") -> 53.238322   und    ddm2dd(1005.3441, "E") -> 10.089068
Laut obiger Website ergibt 53.238322, 10.089068 -> N53° 14.29932 E10° 5.34408
Fazit: diesbezüglich gut.



Heute sind mir (einige wenige) unplausible Geschwindigkeits-Angaben aufgefallen.
Offensichtlich kommt sporadisch sowas hier in den map-Dateien vor:
A,090122,113431,5333.3950,N,953.8610,E,61.75,0.00,0.00,0.00;
A,090122,113433,5333.4043,N,953.8621,E,61.78,0.00,0.00,0.00;
A,090122,113433,5333.4136,N,953.8629,E,61.12,0.00,0.00,0.00;
A,090122,113434,5333.4224,N,953.8638,E,59.38,0.00,0.00,0.00;
A,090122,113435,5333.4312,N,953.8648,E,58.49,0.00,0.00,0.00;
A,090122,113437,5333.4399,N,953.8659,E,56.69,0.00,0.00,0.00;
A,090122,113437,5333.4482,N,953.8670,E,57.17,0.00,0.00,0.00;
A,090122,113439,5333.4570,N,953.8687,E,56.87,0.00,0.00,0.00;
A,090122,113439,5333.4653,N,953.8702,E,56.13,0.00,0.00,0.00;
A,090122,113440,5333.4736,N,953.8721,E,55.73,0.00,0.00,0.00;
A,090122,113441,5333.4819,N,953.8743,E,55.71,0.00,0.00,0.00;

Die Zeit bleibt stehen, die GPS-Koordinaten laufen weiter.
Zwar stimmt die in der map-Datei angegebene Geschwindigkeit, jedoch nutzt wxOSM die dann wohl nicht - und berechnet sie sich stattdessen selbst aus Zeit und zurückgelegter Strecke.

Etwas auffällig ist, dass diese falschen Zeit-Angaben speziell nach der Ausfahrt aus dem Elbtunnel besonders gehäuft auftreten.
Als wenn das GPS-Modul der Kamera durch die 2.7km Blindheit noch einen weiteren Kilometer Wegstrecke verwirrt bleibt....

Aber wieso wird 31 km/h beim ersten Punkt@113433 und 0 km/h beim zweiten Punkt@113433 angezeigt?
Mal analysieren:
Python 3.6.15 (default, Sep 23 2021, 15:41:43) [GCC] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def ddm2dd(ddmf):
...     if isinstance(ddmf, str):
...         ddmf=float(ddmf)
...     ddm = ddmf/100.0
...     d = int(ddm)
...     m = int((ddm-d)*100)
...     s = round((((ddm-d)*100)-m)*60, 4)
...     return round(d+m/60.0+s/3600.0, 6)
...
>>> import math
>>> def haversine(point1, point2, miles=False):
...     # unpack latitude/longitude
...     lat1, lng1 = point1
...     lat2, lng2 = point2
...     # convert all latitudes/longitudes from decimal degrees to radians
...     lat1, lng1, lat2, lng2 = map(math.radians, (lat1, lng1, lat2, lng2))
...     # calculate haversine
...     lat = lat2 - lat1
...     lng = lng2 - lng1
...     d = math.sin(lat * 0.5) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(lng * 0.5) ** 2
...     h = 2 * 6371 * math.asin(math.sqrt(d))
...     if miles:
...         return h * 0.621371  # in miles
...     else:
...         return h  # in kilometers
...
>>> k31 =ddm2dd(5333.3950), ddm2dd(953.8610)
>>> k33a= ddm2dd(5333.4043), ddm2dd(953.8621)
>>> k33b= ddm2dd(5333.4136), ddm2dd(953.8629)
>>> k34 =ddm2dd(5333.4224), ddm2dd(953.8638)
>>>
>>> haversine(k31, k33a)*1000
17.28084558976084
>>> haversine(k33a, k33b)*1000
17.2565908257145
>>> haversine(k33b, k34)*1000
16.37565472996318
>>>

Wenn ich 17.28 Meter in einer Sekunde zurücklege... macht das 17.28[m/s] / 1000[m/km] * 3600[s/h] = 62.208 km/h.
Somit war ich an der Stelle durchgängig mit 62 km/h unterwegs.
Nun liegen zwischen 31 und 33 jedoch 2 Sekunden.
Also halbe Geschwindigkeit. Passt!
Aber zwischen 33 und 33 liegen 0 Sekunden.
17.28 / 0 = ... geht nicht bzw. ist nicht definiert.
Vielleicht wäre hier unendlich korrekter als Null.

Aber....ist ein selbstgemachtes Problem.
In wxOSM findet sich in der Funktion zur gpx-Trackpoint-List-Anreicherung folgender Abschnitt:
[....]
        c_dist=self.haversine(prev_posLL, posLL)  # Entfernung zwischen n-1 und n
        c_secs=timestmp-pref_timestmp             # Dauer von n-1 bis n
        if c_secs>0:
          speed=c_dist/c_secs*3600.0
        else:   # wenn selbe Zeit wie zuvor -> Geschwindigkeit =0
          speed=0.0
[....]
Heißt: works as designed.



Das nächste größere Projekt zeichnet sich ab.
Ich will sämtliche 5-Minuten-Videos in einem Verzeichnis im Stück sehen können ... und parallel bzw. synchon dazu die OSM-Ansicht haben.
Mal schauen. Allererste diesbezügliche Recherchen habe ich zwar schon betrieben - aber...wird ein eigenes Projekt. Wenn Zeit ist.