Has anybody thought of using The DJI OSMO with Mapillary? I’d like to but can’t see, to figure out how.
I guess you should use the uploader on the website or an upload script @tryl knows all about upload scripts.
I don’t know if the osmo has gps, otherwise you could use a free app on your phone to record a gpx-track
If you have the images you needs to geotag them. Either with your favorite GUI program or with the python scripts in https://github.com/mapillary/mapillary_tools/tree/master/python . Then you need to upload them, as @Harry described.
Basically it is the standard action cam workflow. Just ask any question here ![]()
Hello! I made a tutorial but I can’t post it since I’m too new of a user.
I made a post on the OpenStreetMap community forum: Tutorial: How to make geotagged image sequences with a GPX track and a timelapse video - General talk - OpenStreetMap Community Forum
Update February 2026:
I’m currently working on a pipeline to process video recordings from the DJI OSMO 360 for use with Mapillary. Here’s a draft:
Previous experiences and misconceptions: Unfortunately, a DJI OSMO 360 (firmware version November 2025) doesn’t record GPS information in time-shift mode. Therefore, for recordings suitable for Mapillary, you have to use video mode, which generates enormous amounts of data.
Operating a DJI Osmo 360 in video mode consumes a lot of power. Who wants to climb onto the car roof every 100 minutes during a long trip to change the battery? And since you’re usually not traveling alone, the constant worry about the camera’s battery level, as well as whether a sudden downpour might damage the USB-C port, can be nerve-wracking for fellow passengers. Fortunately, the Osmo 360 has hidden charging contacts on its underside. Third-party clamp mounts with these contacts are already available online. This allows for a waterproof, continuous power supply and uninterrupted recording. And now for the particularly welcome news: unlike the Insta360, DJI doesn’t impose an 8-second pause after 30 minutes. Uninterrupted long-distance recording – that’s a statement from DJI.
Dean Zwikel’s UL2GSV tool extracts a GPX file from the raw OSMO data. This tool also patches the MP4 file created with DJI Studio so that it contains the correct time. Unfortunately, UL2GSV currently doesn’t take video editing, for example using DJI Studio, into account. For Mapillary purposes, we therefore need to pay attention to the recording length during the recording process itself. Anyone who tries to upload an unprocessed MP4 file (without time information) to Mapillary will find that the video plays twice as fast as the GPX track..
The UL2GSV team certainly deserves our support in the form of a donation for their excellent preliminary work in making DJI GPS recordings accessible to us.
| Step | Description | Result / Purpose |
|---|---|---|
| 1. Recording | Record a 360-degree video with the DJI OSMO 360 camera. Select video resolution: 8K 24 frames. To obtain a usable GPX file, I recommend using a universal Bluetooth GPX remote from STARTRC. | Raw video + GPS data embedded in camera files |
| 2. Software | You will need the OpenStreetMap Editor JOSM, DJI Studio, and Excel installed on your computer, along with the Excel VBA macros I described here: No more GPS stress thanks to navigation using video frames - #6 by osmplus_org, and the tool OSV2GPX.exe by Dean Zwikel and the Mapillary Desktor Uploader | I use the following computer setup: A MacBook Pro with additional cooling fans for DJI Studio and JOSM. A Windows PC for Excel macros and OSV2GPX.exe. An Ubuntu machine with 16GB of RAM for uploading with the desktop uploader. The result is performance and perfection. |
| 3. Preparation of data | Copy a maximum of 8 DJI Studio raw recording files (without a directory) to an external SSD. Also copy the OSV2GPX.exe tool and a shortcut to the Excel file to the SSD. | |
| 4. MP4 Generation | Create MP4 files in 24 frames per second, 8K resolution using DJI Studio. The MP4 file size, independent of the actual video size, should not exceed 40 GB. Use a separate SSD as the storage location for the DJI project file. It’s best to save the exported MP4 files on the same external SSD drive as the DJI raw files. I name each MP4 export file with the same sequential number as the original OSV file. | For example, CAM_20260214113010_0004_D.OSV becomes 4.MP4 |
| 5. MP4 Rename | Rename the exported MP4 files so that they subsequently have the same name as the original OSV file. | For example, CAM_20260214113010_0004_D.OSV and the exported 4.mp4 file are renamed to CAM_20260214113010_0004_D.mp4 |
| 6. GPX Generation | Count the number of exported MP4 files and open a corresponding number of OSV2GPX windows. Select OSV raw data, analogous to the MP4 files, and extract a GPX file from each of these. Leave the OSV2GPX windows open for now. For each window, select the MP4 export that corresponds to the OSV raw data and transfer the date and time from the OSV file to the MP4 file. | You now have GPX files and videos with the correct time for each recording. |
| 7. GPX Reduktion | To reduce GPX density, run my Excel VBA BatchReduceGPX_XM function. | You will receive GPX files whose GPX node density has been reduced by a factor of 28. The macro creates a directory called “Reduced” for reduced GPX datasets. |
| 8. Error Correction in JOSM | Run my VBA macro OpenJOSM, which opens all GPX datasets in the JOSM editor. Convert individual GPX datasets into JOSM tracks and check their location using aerial photos or OpenStreetMap. You can now correct errors, add additional GPX nodes for tunnels, or, as I described, set frame points for terrain crossings where you know the location of the change in direction. Make sure you export the edited GPX track to the GPX_Reduced directory by overwriting the original GPX file. | Clean GPX file without signal loss |
| 9. GPX Filler | Now, for GPX data records contained in the GPX_Reduced# directory, run my macro BatchProcessFüller. | The macro calculates the appropriate time for each frame point and adds the appropriate time for newly added GPX points through interpolation. The processed GPX datasets are then exported to a directory called Export. |
| 10. Merging data | Move the MP4 files named #dated.mp4 to the Export directory. | Preparation for upload completed |
| 11. Upload | Connect the SSD drive to an Ubuntu computer and upload the data using the Mapillary Desktop Uploader. | complete |
The concept is good, but why using a Macro? It’s more easy to use a powershell script with Exiftool (and ffprobe/ffmpeg). With Gemini or ChatGPT is a script easy to produce. You’re also more flexible when you’re using menu choices. But that’s my opion ![]()
My experience with AI so far is that it promises a lot but delivers little. I prefer to develop a workflow step by step, ignoring all the grandiose promises of AI solving everything at once. With Excel, every step is understandable. For Insta360, I’ve already created my own working batch processing workflow—a workflow for which I’ve applied AI, but whose code I can trace step by step and therefore doesn’t slip out of my control.
I’m very open to parallel suggestions for implementing these steps with other tools, which is precisely why I’m publishing this project here at a very early stage. May the best solution win.
@osmplus_org - thank you for sharing your experience! Do you think you might be able to share an example of a raw video file you get from the DJI Osmo 360 and an example of a raw GPX trace you get from the DJI Remote (for the same video file).
We’d appreciate having a copy for our testing at Mapillary - again these are the raw unprocessed versions (before sending through any tool). Thank you so much in advance!
Thanks so much @osmplus_org !
Looks like exiftool also supports extracting .gpx from the .osv file.
`*Thank you, that’s very interesting information for me. I’m currently noticing that the GPX track extracted with the OSV2GPX tool shows extremely different speeds compared to the video. https://www.mapillary.com/app/user/osmplus_org?lat=47.457603236764015&lng=12.363177758953952&z=17&panos=true&username[]=osmplus_org&pKey=1551988939181586&x=0.30513986230430395&y=0.6091746677677109&zoom=0.42990654205607476&focus=photo Even a test reduction of the GPX points showed no improvement. I’m currently a bit stumped, and would next try changing the GPX times by recalculating them. *
OSV2GPX consists of two separate functions: one for extracting the GPX track, and the second function adds the video time, which GSV requires, to the video file. Since Mapillary can easily process the video file exported by DJI Studio even without timestamps, we can focus solely on the GPX track for Mapillary with the DJI Osmo 360. Your information that exiftool can also extract a GPX track from the camera’s raw file .osv is therefore very helpful. The question is whether the GPX track generated in this way remains synchronized with the video.
I have original .OSV files from the DJI Osmo 360 camera (example filename: E:\Sonntag\DCIM\CAM_001\CAM_20251116120149_0002_D.OSV).
Could you please tell me the exact exiftool command you use to successfully extract a working .gpx file from these?
I would really appreciate the precise command. Thank you!
Update:
My assumption in this post that recordings with 25 frames per second were required later proved to be incorrect. The frame rate chosen during OSMO recording has no influence on the subsequent GPX speed. The MP4 files exported by DJI Studio lack the time information. Therefore, in order to use OSMO 360 recordings with Mapillary, this information must be added to the MP4 file afterward. OSV2GPX.exe software by Dean Zwikel performs this task. After such processing of the MP4 files, the GPX speed and the GPX track run synchronously in Mapillary.
Sure thing, I used:
exiftool -p /usr/local/bin/gpx.fmt -ee3 -api largefilesupport=1 "FILENAMEHERE.OSV" > out.gpx
Note that this does require you to have the gpx.fmt file saved to /usr/local/bin/gpx.fmt
However, after uploading the capture to Mapillary I think I see the same issue you’re describing (gpx out of sync with video). I don’t have the time to troubleshoot at the moment, would be worth looking at the the fps of the video perhaps?
Apparently, the DJI OSMO features really nice 360° image quality.
I bet the original image quality is even better than what we can see on Mapillary. And, although at closer look the imagery suffers from blurry edges due to that wide fixed aperture, the quality overall may be even a bit better than what MAX2 is able to deliver.
Could you please also share some 16 MP planar images that the OSMO, according to the specs, should be capable of?
I had something similar on my mind for what the root cause may be. Try recording at 25 fps because at this recording speed the frame time is a finite fraction of a second, not an infinite fraction. Perhaps this may help?
A new problem on the horizon for the productive use of the DJI Remote:
During my testing of the DJI Osmo 360 with the DJI GPS Remote (for the 360° setup), a new issue has appeared. Having been spoiled by the excellent reception of the Insta360 GPS Remote, I mounted the DJI remote in the same place inside the car – on the left side of the steering wheel, above the driver-side air vent.On mountain drives this mounting position now causes a clear problem: as soon as the GPS signal gets weaker, the GPX track suddenly jumps up to 20 meters off the actual road. Even after driving further and regaining better reception, the track often doesn’t recover for many kilometers and stays offset. I have never seen this behavior with the Insta360 remote. As soon as it picks up the signal again, it very quickly snaps back to the correct road with almost no deviation.Has anyone else noticed similar GPS tracking issues with the DJI remote when it’s placed inside the car (especially in the A-pillar/dashboard area)?
To determine the performance of the DJI GPX Remote with regard to its GPS reception, I will conduct tests and install the GPX Remote on the roof of my vehicle. The DJI Mimo smartphone app will then be responsible for controlling the camera.
Does anyone have experience with 3D-printed housings for GPX receivers on the vehicle roof? Such a dome would certainly look very professional and also provide optimal GPS reception.
Thanks!
Update February 2026
The solution here is to use an alternative GPX remote from StarTRC to generate usable GPX tracks. Even with the best intentions, I cannot currently recommend the original DJI remote for generating GPX tracks suitable for Mapillary.
@osmplus_org - we did not do any backend changes - so I think setting the camera to record 25 FPS did the trick.
First DJI OSMO 360 impressions in better lighting conditions.
8k 25ffs
Lens alignment sideways to the vehicle.
The quality is really stunning.
Maybe even better than the GoPro MAX2! I just do not know whether this is due to the Mapillary processing pipeline botching MAX2 imagery, MAX2 quality being slightly worse, or just the weather.
Anyhow, one can though clearly see that the wide fixed aperture really does not do the OSMO any good. DJI would have been well advised to tighten the aperture because this would have also de facto increased the OSMO’s effective scanning range. Imho digital sharpening cannot compensate for true physical sharpness. AI sharpening will not help here either. You just cannot argue with physics. ![]()
The timestamps on the sequence you shared clearly indicate that the video has been captured at 25 fps. Good job! ![]()
Now, since you have a tool for it and have been modifying point density in GPX tracks, why don’t you increase point density to match every frame? This should give best results on Mapillary.
*I made a mistake and simply tried to transfer my workflow that worked perfectly with the Insta360 X3 straight to the DJI Osmo 360. It didn’t work at all. I quickly realized that with the DJI I was losing every single microsecond in the timing data.I stayed up all night yesterday, coding until 2 a.m. The first upload result was disappointing, but my current workflow is now looking extremely promising. *
A new upload with microsecond-accurate GPX timestamps is running right now.
*I’m now getting far more usable GPX points by drastically reducing the number of nodes the camera outputs – currently by an astonishing factor of 28. Before, almost all nodes were identical duplicates. This reduction finally gives me a GPX track that’s actually editable, which is absolutely essential with this camera.The next challenge is figuring out how to deal with the sometimes massive outliers in the GPX track. If I can’t solve that, the whole investment in this camera will unfortunately have been for nothing. Yes, the image quality is fantastic – a real joy to watch. But if I can’t eliminate these GPX errors, the DJI Osmo will end up being just my secondary camera for quick shots during driving breaks. *
Right now, that means I have to remove the roof camera each time, take the quick shots, and then re-mount it on the rig before continuing. Instead, I’d then just put an Insta360 X5 on the car roof.I love driving through tunnels – and that’s exactly where the risk of a rollover is much higher when the camera restarts recording every 14 minutes (DJI) instead of only every 30 minutes like with the Insta360.On the plus side, the big advantage of the DJI is that it starts recording the moment I drive off and only stops after 5 hours – of course powered via USB-C the whole time. That would finally allow me to mount it permanently on the roof together with the remote.
I’m trying to streamline the rather tedious prep work needed to get images from this camera into Mapillary. Right now, the beautiful results are the only thing that makes it worth the hassle.
A process to make DJI OSMO 360 recordings usable for Streetview purposes.
EXPERIMENTAL
Starting Point 1
The Excel VBA macro Sub C_BatchReduceGPX_28_XML() uses the UL2GSV tool OSV2GPX to reduce GPX data from raw recordings obtained with a DJI OSMO 360 camera by a factor of 28 (the optimal reduction value still needs to be determined). The macro requires a directory containing GPX data for processing. The reduced GPX data is then exported to a directory named “reduced”.
Intermediate Step Missing GPX points (for example, in tunnels) are checked and added using the JOSM Editor. Ensure an even distribution of added points; I use a consistent 30-meter spacing. Drive through tunnels at a constant speed.
Starting Point 2
The macro Sub D_BatchProcessFüller() imports GPX data from the “reduced” directory, including any data that has been processed or supplemented with JOSM. It then adds missing timestamps via interpolation and finally exports the GPX data to a directory named “Export”. The GPX datasets also receive the suffix “_dated” (since Mapillary requires identical .gpx and .mp4 file pairs).
GPX-Zeitverarbeitung in Excel: Ablauf und Logik
Spalte D: Rohzeit aus GPX
- Enthält den originalen Zeitstempel aus der GPX-Datei, z. B.
2025-11-19T12:32:47.956376Z. - Format: ISO 8601 mit Mikrosekunden.
Spalte E: Excel-kompatibles Datum
- Hier wird aus Spalte D ein Excel-Datum extrahiert, das für Berechnungen nutzbar ist.
- Die Mikrosekunden werden separat gespeichert (z. B. in Spalte G), aber Spalte E enthält nur das
Date-Objekt. - Wird mit
ParseIsoTimeToDateAndUserzeugt.
Spalte F: Zeit für Export (bereinigt/interpoliert)
- Zielspalte für die bereinigte oder interpolierte Zeit.
- Wenn in Spalte E ein gültiger Zeitwert steht → wird 1:1 übernommen.
- Wenn Spalte E leer ist → wird interpoliert:
- Suche rückwärts den letzten gültigen Zeitwert.
- Suche vorwärts den nächsten gültigen Zeitwert.
- Zähle die Anzahl der leeren Zeilen dazwischen.
- Berechne gleichmäßige Zeitabstände (inkl. Mikrosekunden).
- Fülle die Zwischenwerte in Spalte F ein.
Sub C_BatchReduceGPX_28_XML()
Dim fso As Object, folderPath As String, file As Object
Dim xmlDoc As Object, trkpts As Object, newDoc As Object
Dim root As Object, trkNode As Object, trksegNode As Object
Dim inputFile As String, outputFolder As String, outputPath As String
Dim i As Long, hasTime As Boolean
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "GPX-Verzeichnis auswählen"
If .Show <> -1 Then Exit Sub
folderPath = .SelectedItems(1)
End With
Set fso = CreateObject("Scripting.FileSystemObject")
Dim parentFolder As String
parentFolder = fso.GetParentFolderName(folderPath)
outputFolder = parentFolder & "\GPX_Reduced_" & Format(Now, "yyyymmdd_hhnnss")
If Not fso.FolderExists(outputFolder) Then fso.CreateFolder outputFolder
For Each file In fso.GetFolder(folderPath).Files
If LCase(fso.GetExtensionName(file.Name)) = "gpx" Then
inputFile = file.Path
Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
xmlDoc.async = False
xmlDoc.Load inputFile
If xmlDoc.ParseError.ErrorCode <> 0 Then GoTo NextFile
xmlDoc.SetProperty "SelectionNamespaces", _
"xmlns:gpx='http://www.topografix.com/GPX/1/1'"
Set trkpts = xmlDoc.SelectNodes("//gpx:trkpt")
If trkpts Is Nothing Then GoTo NextFile
Set newDoc = CreateObject("MSXML2.DOMDocument.6.0")
newDoc.appendChild newDoc.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'")
Set root = newDoc.createElement("gpx")
root.setAttribute "xmlns", "http://www.topografix.com/GPX/1/1"
root.setAttribute "version", "1.1"
root.setAttribute "creator", "GPX Reducer"
' Optional: SchemaLocation
root.setAttribute "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"
root.setAttribute "xsi:schemaLocation", "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
newDoc.appendChild root
Set trkNode = newDoc.createElement("trk")
root.appendChild trkNode
Set trksegNode = newDoc.createElement("trkseg")
trkNode.appendChild trksegNode
For i = 0 To trkpts.Length - 1
hasTime = Not trkpts.Item(i).SelectSingleNode("gpx:time") Is Nothing
If hasTime Then
If i Mod 28 = 0 Then
trksegNode.appendChild trkpts.Item(i).CloneNode(True)
End If
Else
trksegNode.appendChild trkpts.Item(i).CloneNode(True)
End If
Next i
outputPath = outputFolder & "\" & file.Name
newDoc.Save outputPath
End If
NextFile:
Next file
MsgBox "Alle GPX-Dateien erfolgreich reduziert: " & vbCrLf & outputFolder, vbInformation
End Sub
Sub D_BatchProcessFüller()
Dim fso As Object, folderPath As String, file As Object
Dim ws As Worksheet
Dim fileName As String, ext As String
Dim Pfad_und_Datei As String
With Application.FileDialog(msoFileDialogFolderPicker)
.Title = "GPX-Verzeichnis auswählen"
If .Show <> -1 Then Exit Sub
folderPath = .SelectedItems(1)
End With
Set fso = CreateObject("Scripting.FileSystemObject")
Sheets("GPX_Import").Select
Set ws = Worksheets("GPX_Import")
For Each file In fso.GetFolder(folderPath).Files
fileName = file.Name
ext = LCase(fso.GetExtensionName(fileName))
' DJI-Dateien beginnen mit "CAM_" – Bedingung darauf, plus .gpx
If ext = "gpx" And Left$(fileName, 4) = "CAM_" Then
Pfad_und_Datei = file.Path
ws.Cells.Clear
ws.Range("K5").value = fileName
ws.Range("K6").value = Pfad_und_Datei
ImportAndFixGPX Pfad_und_Datei
ExportGPXWithBounds
End If
Next file
Set fso = Nothing
Set file = Nothing
MsgBox "Alle GPX-Dateien wurden verarbeitet und exportiert.", vbInformation
End Sub
Sub ImportAndFixGPX(Pfad_und_Datei As String)
Dim xmlDoc As Object, trkpts As Object, boundsNode As Object
Dim i As Long
Dim ws As Worksheet
Dim lastRow As Long
Dim ergänzteZeitpunkte As Long
Dim boundsText As String
Dim prevDt As Date, prevUs As Long
Dim nextDt As Date, nextUs As Long
Dim gapStart As Long, gapEnd As Long
Dim gapCount As Long
Dim prevSec As Double, nextSec As Double
Dim stepSec As Double, newSec As Double
Dim dtOut As Date, usOut As Long
ergänzteZeitpunkte = 0
Sheets("GPX_Import").Select
Set ws = Worksheets("GPX_Import")
ws.Cells.Clear
' Datei-Infos
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
ws.Range("K5").value = fso.GetFileName(Pfad_und_Datei)
ws.Range("K6").value = Pfad_und_Datei
' XML laden
Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
xmlDoc.async = False: xmlDoc.Load Pfad_und_Datei
If xmlDoc.ParseError.ErrorCode <> 0 Then
MsgBox "Fehler beim Laden der GPX-Datei: " & xmlDoc.ParseError.Reason, vbCritical
Exit Sub
End If
xmlDoc.SetProperty "SelectionNamespaces", "xmlns:gpx='http://www.topografix.com/GPX/1/1'"
' Bounds
Set boundsNode = xmlDoc.SelectSingleNode("//gpx:bounds")
If Not boundsNode Is Nothing Then
boundsText = "<bounds minlat=""" & boundsNode.getAttribute("minlat") & _
""" minlon=""" & boundsNode.getAttribute("minlon") & _
""" maxlon=""" & boundsNode.getAttribute("maxlon") & _
""" maxlat=""" & boundsNode.getAttribute("maxlat") & """ />"
ws.Range("K2").value = boundsText
Else
ws.Range("K2").value = "Keine Bounds gefunden"
End If
' Trackpunkte extrahieren
Set trkpts = xmlDoc.SelectNodes("//gpx:trkpt")
ws.Cells(1, 1).value = "Lat"
ws.Cells(1, 2).value = "Lon"
ws.Cells(1, 3).value = "Ele"
ws.Cells(1, 4).value = "Time (raw)"
ws.Cells(1, 5).value = "Time (Date)"
ws.Cells(1, 6).value = "Time (ISO us)"
ws.Cells(1, 7).value = "Microseconds"
Dim timeFound As Boolean
timeFound = False
For i = 0 To trkpts.Length - 1
ws.Cells(i + 2, 1).value = trkpts.Item(i).getAttribute("lat")
ws.Cells(i + 2, 2).value = trkpts.Item(i).getAttribute("lon")
If Not trkpts.Item(i).SelectSingleNode("gpx:ele") Is Nothing Then
ws.Cells(i + 2, 3).value = trkpts.Item(i).SelectSingleNode("gpx:ele").Text
End If
If Not trkpts.Item(i).SelectSingleNode("gpx:time") Is Nothing Then
Dim rawTime As String, dt As Date, us As Long
rawTime = trkpts.Item(i).SelectSingleNode("gpx:time").Text
ws.Cells(i + 2, 4).value = rawTime
dt = ParseIsoTimeToDateAndUs(rawTime, us)
ws.Cells(i + 2, 5).value = dt
ws.Cells(i + 2, 5).NumberFormat = "yyyy-mm-dd hh:mm:ss"
ws.Cells(i + 2, 7).value = us
ws.Cells(i + 2, 6).value = FormatDateWithMicroseconds(dt, us)
timeFound = True
End If
Next i
If Not timeFound Then
MsgBox "Keine Zeitinformationen gefunden, Import abgebrochen.", vbCritical
Exit Sub
End If
' Interpolation mit Mikrosekunden startet hier ...
' Interpolation mit Mikrosekunden
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
prevDt = 0: prevUs = 0
For i = 2 To lastRow
Dim hasDate As Boolean
hasDate = IsDate(ws.Cells(i, 5).value)
If hasDate Then
' Zeitwert vorhanden ? direkt übernehmen
Dim curDt As Date, curUs As Long
curDt = ws.Cells(i, 5).value
curUs = CLng(Val(ws.Cells(i, 7).value))
' Ordnung sicherstellen: keine Rückwärtszeit
If DateUsToSeconds(curDt, curUs) < DateUsToSeconds(prevDt, prevUs) Then
Dim adjSec As Double, dtAdj As Date, usAdj As Long
adjSec = DateUsToSeconds(prevDt, prevUs) + 0.000001 ' +1 µs
SecondsToDateUs prevDt, adjSec, dtAdj, usAdj
curDt = dtAdj: curUs = usAdj
ws.Cells(i, 5).value = curDt
ws.Cells(i, 7).value = curUs
End If
' ISO-Zeit mit Mikrosekunden schreiben
ws.Cells(i, 6).value = FormatDateWithMicroseconds(curDt, curUs)
prevDt = curDt: prevUs = curUs
Else
' Lücke erkennen
gapStart = i - 1
gapEnd = i
Do While gapEnd <= lastRow And Not IsDate(ws.Cells(gapEnd, 5).value)
gapEnd = gapEnd + 1
Loop
If gapEnd <= lastRow And IsDate(ws.Cells(gapEnd, 5).value) Then
' Zeitpunkte vor und nach der Lücke holen
prevDt = ws.Cells(gapStart, 5).value
prevUs = CLng(Val(ws.Cells(gapStart, 7).value))
nextDt = ws.Cells(gapEnd, 5).value
nextUs = CLng(Val(ws.Cells(gapEnd, 7).value))
' Sekundenbasis berechnen
prevSec = DateUsToSeconds(prevDt, prevUs)
nextSec = DateUsToSeconds(nextDt, nextUs)
gapCount = gapEnd - gapStart - 1
stepSec = (nextSec - prevSec) / (gapCount + 1)
' Interpolierte Werte einfügen
For j = gapStart + 1 To gapEnd - 1
newSec = prevSec + stepSec * (j - gapStart)
SecondsToDateUs prevDt, newSec, dtOut, usOut
ws.Cells(j, 5).value = dtOut
ws.Cells(j, 5).NumberFormat = "yyyy-mm-dd hh:mm:ss"
ws.Cells(j, 7).value = usOut
ws.Cells(j, 6).value = FormatDateWithMicroseconds(dtOut, usOut)
ergänzteZeitpunkte = ergänzteZeitpunkte + 1
Next j
' Schleifenindex vorziehen und prev aktualisieren
i = gapEnd - 1
prevDt = ws.Cells(i, 5).value
prevUs = CLng(Val(ws.Cells(i, 7).value))
End If
End If
Next i
' Anzahl der ergänzten Zeitpunkte ausgeben
ws.Range("K4").value = ergänzteZeitpunkte
' Spaltenbreite anpassen
ws.Columns(4).AutoFit
ws.Columns(5).AutoFit
ws.Columns(6).AutoFit
ws.Columns(7).AutoFit
End Sub
'======================================================================================================
Sub ExportGPXWithBounds()
Dim ws As Worksheet
Dim i As Long, lastRow As Long
Dim minLat As Double, maxLat As Double, minLon As Double, maxLon As Double
Dim boundsCalculated As String
Dim gpxText As String, trkptText As String
Dim boundsText As String, fileName As String
Dim exportFolder As String, outputPath As String
Dim TimeZoneOffset As Integer
Dim currentTime As String
Dim fso As Object
Dim originalPath As String, originalFolder As String, parentFolder As String
Dim exportBaseName As String
Dim folderItem As Object
Dim exportFound As Boolean
Set ws = Worksheets("GPX_Import")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
' === Schritt 6: Eigene Bounds berechnen ===
minLat = 90: maxLat = -90: minLon = 180: maxLon = -180
For i = 2 To lastRow
If IsNumeric(ws.Cells(i, 1).value) And IsNumeric(ws.Cells(i, 2).value) Then
If ws.Cells(i, 1).value < minLat Then minLat = ws.Cells(i, 1).value
If ws.Cells(i, 1).value > maxLat Then maxLat = ws.Cells(i, 1).value
If ws.Cells(i, 2).value < minLon Then minLon = ws.Cells(i, 2).value
If ws.Cells(i, 2).value > maxLon Then maxLon = ws.Cells(i, 2).value
End If
Next i
boundsCalculated = "<bounds minlat=""" & Format(minLat, "0.000000000000000") & _
""" minlon=""" & Format(minLon, "0.000000000000000") & _
""" maxlon=""" & Format(maxLon, "0.000000000000000") & _
""" maxlat=""" & Format(maxLat, "0.000000000000000") & """ />"
ws.Range("K2").value = boundsCalculated
' === Zeitstempel für Header ===
TimeZoneOffset = 2 ' UTC+2 für MESZ
currentTime = Format(Now - (TimeZoneOffset / 24), "yyyy-mm-dd\THH:NN:SS") & "Z"
' === Bounds aus Zelle K2 übernehmen und systemabhängig korrigieren ===
boundsText = Replace(ws.Range("K2").value, Application.International(xlDecimalSeparator), ".")
' === Dateiname aus Zelle K5 übernehmen ===
fileName = ws.Range("K5").value
If fileName = "" Then
MsgBox "Kein Dateiname in Zelle K5 gefunden.", vbCritical
Exit Sub
End If
' === Pfad zur GPX-Datei aus Zelle K6 übernehmen ===
originalPath = ws.Range("K6").value
Set fso = CreateObject("Scripting.FileSystemObject")
originalFolder = fso.GetParentFolderName(originalPath)
' === Übergeordnetes Verzeichnis ermitteln ===
On Error Resume Next
parentFolder = fso.GetParentFolderName(originalFolder)
On Error GoTo 0
If parentFolder = "" Or parentFolder = originalFolder Then parentFolder = originalFolder
' === Export#<Datum>-Verzeichnis suchen oder erzeugen ===
exportBaseName = "Export#" & Format(Date, "yyyymmdd")
exportFolder = parentFolder & "\" & exportBaseName
exportFound = False
For Each folderItem In fso.GetFolder(parentFolder).SubFolders
If folderItem.Name = exportBaseName Then
exportFolder = folderItem.Path
exportFound = True
Exit For
End If
Next folderItem
If Not exportFound Then
If Not fso.FolderExists(exportFolder) Then fso.CreateFolder exportFolder
End If
' === Exportpfad festlegen ===
' outputPath = exportFolder & "\" & fileName
' === Exportpfad festlegen mit Appendix "_dated" ===
Dim baseName As String, extName As String
' Dateiname in Basis und Erweiterung zerlegen
baseName = fso.GetBaseName(fileName) ' z.B. CAM_20251119151440_0016_D
extName = fso.GetExtensionName(fileName) ' z.B. gpx
' neuen Namen zusammensetzen
fileName = baseName & "_dated." & extName
outputPath = exportFolder & "\" & fileName
' === GPX-Header aufbauen ===
gpxText = "<?xml version=""1.0"" encoding=""UTF-8""?>" & vbCrLf & _
"<gpx xmlns=""http://www.topografix.com/GPX/1/1"" creator=""Insta360 Studio"" version=""1.1"">" & vbCrLf & _
"<metadata>" & vbCrLf & _
"<link href=""https://www.insta360.com"">" & vbCrLf & _
"<text>Insta360 GPS Dashboard</text>" & vbCrLf & _
"</link>" & vbCrLf & _
"<time>" & currentTime & "</time>" & vbCrLf & _
boundsText & vbCrLf & _
"</metadata>" & vbCrLf & _
"<trk>" & vbCrLf & _
"<name>Insta360 GPS Data</name>" & vbCrLf & _
"<trkseg>" & vbCrLf
' === Trackpunkte erzeugen ===
For i = 2 To lastRow
If ws.Cells(i, 1).value <> "" And ws.Cells(i, 2).value <> "" Then
trkptText = "<trkpt lat=""" & FormatDecimalForGPX(ws.Cells(i, 1).value) & _
""" lon=""" & FormatDecimalForGPX(ws.Cells(i, 2).value) & """>" & vbCrLf
If ws.Cells(i, 3).value <> "" Then
trkptText = trkptText & "<ele>" & FormatDecimalForGPX(ws.Cells(i, 3).value) & "</ele>" & vbCrLf
End If
' Zeitstempel aus Spalte 5 (Date) + Spalte 7 (Microseconds)
If IsDate(ws.Cells(i, 5).value) Then
Dim dtExp As Date, usExp As Long
dtExp = ws.Cells(i, 5).value
usExp = CLng(Val(ws.Cells(i, 7).value))
trkptText = trkptText & "<time>" & FormatDateWithMicroseconds(dtExp, usExp) & "</time>" & vbCrLf
End If
trkptText = trkptText & "</trkpt>" & vbCrLf
gpxText = gpxText & trkptText
End If
Next i
' === GPX-Ende anhängen ===
gpxText = gpxText & "</trkseg>" & vbCrLf & "</trk>" & vbCrLf & "</gpx>"
' === Datei schreiben ===
Dim stream As Object
Set stream = CreateObject("ADODB.Stream")
stream.Type = 2
stream.Charset = "utf-8"
stream.Open
stream.WriteText gpxText
stream.SaveToFile outputPath, 2
stream.Close
Sheets("Start").Select
End Sub
' === Funktion zum automatischen Anpassen des Trennzeichens ===
Function FormatDecimalForGPX(value As Variant) As String
Dim systemSeparator As String
systemSeparator = Application.International(xlDecimalSeparator)
FormatDecimalForGPX = Replace(Format(CDbl(value), "0.000000"), systemSeparator, ".")
End Function
' Zeitstring parsen und Mikrosekunden extrahieren
Public Function ParseIsoTimeToDateAndUs(iso As String, ByRef usOut As Long) As Date
Dim basePart As String, fracPart As String
Dim pDot As Long, pZ As Long
usOut = 0
If Len(iso) = 0 Then Exit Function
pZ = InStr(iso, "Z")
If pZ = 0 Then pZ = Len(iso) + 1
pDot = InStrRev(iso, ".")
If pDot > 0 And pDot < pZ Then
basePart = Left$(iso, pDot - 1)
fracPart = Mid$(iso, pDot + 1, pZ - pDot - 1)
' Nur die ersten 6 Ziffern nehmen (Mikrosekunden)
If Len(fracPart) > 6 Then fracPart = Left$(fracPart, 6)
If IsNumeric(fracPart) Then
usOut = CLng(fracPart)
End If
Else
basePart = Left$(iso, pZ - 1)
End If
ParseIsoTimeToDateAndUs = CDate(Replace(basePart, "T", " "))
End Function
' Sekunden + Mikrosekunden innerhalb eines Tages
Public Function DateUsToSeconds(dt As Date, us As Long) As Double
DateUsToSeconds = (Hour(dt) * 3600# + Minute(dt) * 60# + Second(dt)) + (us / 1000000#)
End Function
Public Sub SecondsToDateUs(baseDate As Date, secVal As Double, ByRef dtOut As Date, ByRef usOut As Long)
Dim wholeSec As Long, frac As Double
Dim h As Long, m As Long, s As Long
' Ganze Sekunden und Bruchteil trennen
wholeSec = Fix(secVal)
frac = secVal - wholeSec
' Zerlegen in Stunden, Minuten, Sekunden
h = wholeSec \ 3600
m = (wholeSec Mod 3600) \ 60
s = wholeSec Mod 60
' Datum + Uhrzeit zusammensetzen
dtOut = DateSerial(Year(baseDate), Month(baseDate), Day(baseDate)) + TimeSerial(h, m, s)
' Mikrosekunden berechnen
usOut = CLng(frac * 1000000#)
If usOut < 0 Then usOut = 0
If usOut > 999999 Then usOut = 999999
End Sub
Public Function FormatDateWithMicroseconds(dt As Date, us As Long) As String
If us < 0 Then us = 0
If us > 999999 Then us = 999999
FormatDateWithMicroseconds = Format(dt, "yyyy-mm-dd\THH:nn:ss") & "." & Format(us, "000000") & "Z"
End Function
Several test drives using the DJI Osmo 360° camera together with a DJI remote connected via Bluetooth have shown that the GPX data quality achieved with this setup still falls short of current technological standards. It must be acknowledged that DJI itself does not position its GPX Remote as a GPX Tracker, but rather that this accessory is intended as a remote control, and its GPX capabilities are intended for generating stats in videos.
Even mounting the DJI remote on the car roof to improve GPS reception did not result in any significant improvement.The 360° camera itself delivers excellent image quality and shows great promise, but to make it viable for productive Street View applications, a different approach to positioning is required. For this reason, I am pausing the current setup.
Update:
My order for an alternative GPX Bluetooth remote from STARTRC has arrived. This remote is not described as compatible with the DJI OSMO 360. https://www.startrc.com/startrc-gps-bluetooth-remote-controller-for-dji-action-5-pro-action-4.html Initial tests with my OSMO 360 show promising results regarding the GPX tracks generated. This remote can control the OSMO 360, but it doesn’t indicate an ongoing recording with a flashing red light. A successful recording start can be inferred by the remote’s status indicator changing from standby to active.
Since this GPS remote is also sold in slightly different versions as a third-party alternative for the GoPro and Insta360 ecosystems.
Operating the StarTRC GPX Remote (already paired):
Turn on the system:
-
Insert the DJI Osmo 360 battery and press the power button.
-
Press and hold the StarTRC power button for three seconds.
-
Wait for Bluetooth pairing to complete (this takes a few seconds).
-
Now put the camera into readiness mode. To do this, press and hold the red front button on the StarTRC for several seconds. A moon icon will appear on the remote’s display.
-
Start recording. Briefly press the front button on the StarTRC.
-
Stop recording. Briefly press the front button on the StarTRC; the moon icon will reappear.
-
Start next recording. Briefly press the front button on the StarTRC.
-
Stop recording. Briefly press the front button on the StarTRC; the moon icon will reappear.
-
Leave the system in readiness mode for a longer period. The camera consumes very little power in this mode.
-
After a few minutes, the remote’s display goes blank after a few minutes, and the remote enters standby mode.
Reactivate the camera (for example, if you were away for an hour and return to your car).
-
Press and hold the StarTRC front button for several seconds. After successful pairing, the remote display will be blank. To obtain a definitive system state, press and hold the front button on the TRC again for several seconds. A moon icon will now reappear on the StarTRC display. (The camera is now in a definitive state for the next recording and is ready for the next recording.)
-
Turn off the system by pressing and holding the left On button on the StarTRC for several seconds. Press the DJI Osmo Power button and remove the camera battery. (If the StarTRC was turned off using the TRC Power button, remote reactivation of the camera using the TRC remote is no longer possible.) The camera must first be manually turned on to turn the system back on.
Background: The bidirectional communication between the DJI Osmo 360 and the GPX Remote StarTRC does not transmit the current camera status to the remote. The remote can detect the following status states: readiness (moon symbol), paired, and disconnected (for example, due to a depleted camera battery).
Switching between photo and video modes is possible by briefly pressing the remote’s power button, but the lack of a display indicator can be confusing regarding the camera’s current mode. Therefore, I advise against briefly pressing the remote’s power button.
The remote moon symbol is particularly relevant here, as briefly pressing the red remote’s front button in this state directly switches the camera to recording mode.
Osmo 360 battery extension rod replacement
I’ve been running controlled lab tests on the external charging contacts of the DJI Osmo 360 to understand how they behave electrically and what is actually required to power the camera through them.
These four contacts are intended for the official DJI Battery Extension Tube. The rear‑right pin is the negative terminal, and the front‑left pin is the positive terminal. When applying 5 V across these two pins, the camera draws a small current of about 200 mA. At 6 V, the camera begins to charge, and once recording starts, it no longer uses the internal battery. As the voltage increases, the current draw gradually decreases. At 13 V, the current flow stops abruptly and the camera enters a protective safety mode, which resets once the power source is removed.
Based on these measurements, the magnetic clamp mounts (search term DJI 1/ “6327001336A”), which are supposed to power an Osmo 360 via the waterproof contacts, do not work with a standard USB-C power adapter. For power supply, a USB-C charging cable needs to be slightly modified. A 7.5V 1.5A power adapter connected Example: https://www.amazon.de/dp/B0DTG8DX8K to the positive (+) and negative (-) wires of a USB-C cable works perfectly. Data lines are not used in this quick-release mount, as it contains no electronics.
This mount has a practical problem: the USB-C connector attached to this magnetic mount protrudes into the 360° field of view, disrupting the “invisible rod” effect and rendering the mount unsuitable for clean mapillary or Street View shots. Therefore, I drilled two 0.3 mm holes on the left and right sides of the mount and connected the positive and negative terminals directly to the internal circuit board. I sealed the USB-C socket with waterproof adhesive and filled the aluminum housing containing the small circuit board, to which the two spring contacts are attached, with leak-proof ball bearing grease. This solution is now waterproof, and the cable no longer obstructs the 360-degree camera’s field of view.
When an OSMO 360 is placed on this magnetic mount, it wobbles slightly. Take a patch pad from a bicycle repair kit, the kind used to vulcanize a puncture in an inner tube. I cut a thin strip from one of these pads and stretched it across the center of the DJI magnetic mount. This now acts as a shock absorber, and the DJI Osmo no longer wobbles on the quick-release mount.
Ever since I solved the Osmo 360’s electrical quirks by routing power through its external housing contacts, life on the road has become noticeably calmer. The USB‑C connector is gone from the power line now — a small victory, but one that feels surprisingly liberating. I’ve already taken the camera through several drives on salt‑soaked winter roads, the kind that leave a thin white crust on everything they touch.
Because of that, I now travel with a water bag in the car. It’s a simple ritual: whenever the Osmo 360 comes back looking like it survived a minor ocean storm, I dip it into the bag and wash the salt film off its shell. Strange as it sounds, the moment has become part of the workflow — a quiet pause between stretches of road.
If you look closely at my recordings, you’ll recently notice an analog multimeter mounted directly in front of the steering wheel. This allows me to monitor the camera’s power consumption in real time. I can see exactly what the camera is doing, whether it’s charging or recording. This works much better than just a flashing red light. I recently experienced an unusual issue where recordings were suddenly interrupted. Thousands of kilometers of trouble-free recordings, and now this. The solution was to perform a one-time reset of the DJI Osmo 360. To detect such problems, including a full memory card, monitoring the camera is very helpful. An analog display showing the power flow from 0 to 1 amp is perfect.
Technically, I solved this by connecting a simple steel spring in series with the 7.5-volt power supply line on the roof of my vehicle. I chose the spring coils so that a current of 1 amp results in a voltage drop of 100 millivolts. I tap into this voltage, and a simple digital multimeter (DV) in the 100-millivolt range displays the instantaneous current flow to the camera on the steering wheel. Here’s what happens: The camera is off, the battery is empty, and therefore charging. The camera then draws almost exactly 1 amp at 7.5 volts. When the battery is fully charged, the current draw drops to zero. If I start a recording while it’s charging, the current draw drops from 100 Millivolt (1A) to approximately 40 millivolts. If the battery is fully charged and I start a recording, the current draw rises from 0 to approximately 40 millivolts. In every operating state, I expect the analog multimeter to read in the 40-millivolt range during a perfectly running recording. That’s perfect.
I’ve reached an interesting point in this project — several separate ideas and experiments have finally merged into one coherent, reliable workflow. And at this stage, it’s fair to say that what I’m doing is far removed from the simple approach Mapillary officially promotes: buy a GoPro Max 2, record a raw 360° video, and upload it untouched. Anyone who follows that path accepts a long list of compromises..
Right now, my M1 MacBook is busy rendering .mp4 files in DJI Studio. It takes its time — and without a PC cooling pad underneath, the machine would probably run hot enough to fry breakfast. At least this setup, along with the entire Mapillary upload workflow, has finally reached a point where everything runs reliably.
DJI Studio itself still feels like early‑stage software when it comes to processing speed. It gets the job done, but you can sense that there’s a lot of untapped potential waiting for future updates.

