How to loop through json payload with Object of Objects and convert GPS points to GPX format?

I am trying to convert a json payload that has a series of GPS location points and convert this payload into a GPX file format. The json payload is formatted as an Object of Objects. For this I need to loop through the Object of Objects and get specific values from it to put in the new GPX file format. However, I can't find the correct method to do this.

Format op json payload:

{
    "1": {
        "name": "msg_ublox_location",
        "data": {
            "latitude": 52.167908,
            "longitude": 5.129468,
            "altitude": 56.758,
            "success": true,
            "hot_retry": 0,
            "cold_retry": 0,
            "ttf": 0,
            "fix_type": 3,
            "siv": 9,
            "h_acc_est": 2,
            "pDOP": 2,
            "fix_time": 1636552316,
            "active_tracking": true,
            "cog": 18000,
            "sog": 0
        },
        "port": 2,
        "timestamp": 1636552317,
        "timestamp_parsed": "2021-11-10 14:51:57"
    },
    "2": {
        "name": "msg_ublox_location",
        "data": {
            "latitude": 52.167704,
            "longitude": 5.136292,
            "altitude": 50.814,
            "success": true,
            "hot_retry": 0,
            "cold_retry": 0,
            "ttf": 0,
            "fix_type": 3,
            "siv": 9,
            "h_acc_est": 3,
            "pDOP": 1,
            "fix_time": 1636552381,
            "active_tracking": true,
            "cog": 18000,
            "sog": 0
        },
        "port": 2,
        "timestamp": 1636552381,
        "timestamp_parsed": "2021-11-10 14:53:01"
    }
}

The output format I need to convert it to is GPX, and should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.topografix.com/GPX/1/0"
  xsi:schemaLocation="http://www.topografix.com/GPX/1/0
  http://www.topografix.com/GPX/1/0/gpx.xsd">
  <name>gpx file name</name>
  <trk><name>gpx track name</name><number>1</number><trkseg>
  <trkpt lat="52.167908" lon="5.129468"><ele>"56.758"</ele><time>"2021-11-10T13:51:56.000Z"</time></trkpt>
  <trkpt lat="52.167704" lon="5.136292"><ele>"50.814"</ele><time>"2021-11-10T13:53:01.000Z"</time></trkpt>
  </trkseg></trk>
</gpx>

I have tried to use the following function node, but it does not work:

var tracks_msgs = msg.payload; 
var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +  
         "<gpx version=\"1.0\"\n" + 
         "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +  
         "  xmlns=\"http://www.topografix.com/GPX/1/0\"\n" + 
         "  xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0\n" + 
         "  http://www.topografix.com/GPX/1/0/gpx.xsd\">\n"+ 
         "  <name>gpx file name</name>\n" + 
         "  <trk><name>gpx track name</name><number>1</number><trkseg>\n"; 
for (var i = 0; i < tracks_msgs.length; i++) { 
   xml += "    <trkpt lat=\""+ tracks_msgs[i].data.latitude + 
          "\" lon=\""      + tracks_msgs[i].data.longitude + 
          "\"><ele>"       + tracks_msgs[i].data.altitude + "</ele>" + 
          "<time>" + new Date(tracks_msgs[i].data.fix_time*1000).toISOString() + "</time></trkpt>\n"; 
} 
xml += "  </trkseg></trk>\n</gpx>\n"; 
msg.payload = xml; 
return msg;

Can someone show me how to adapt this this function?

I normally loop through an object with something like this:

Object.keys(myObject).forEach( (element) => {
   // ...
})

It isn't the only way to do it but happens to be the one that I reach for first.

Here is the start of how you could do it using some template nodes and a for loop in a function node. It builds the first couple parts ot the <trkpt...> and you will have to finish up the rest (good practice :grinning_face_with_smiling_eyes:)

[{"id":"fbcfa5b0f6b8398d","type":"inject","z":"a618aa32f05d2304","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":210,"y":120,"wires":[["0a223d97d977b5e3"]]},{"id":"0a223d97d977b5e3","type":"template","z":"a618aa32f05d2304","name":"Test data input","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n    \"1\": {\n        \"name\": \"msg_ublox_location\",\n        \"data\": {\n            \"latitude\": 52.167908,\n            \"longitude\": 5.129468,\n            \"altitude\": 56.758,\n            \"success\": true,\n            \"hot_retry\": 0,\n            \"cold_retry\": 0,\n            \"ttf\": 0,\n            \"fix_type\": 3,\n            \"siv\": 9,\n            \"h_acc_est\": 2,\n            \"pDOP\": 2,\n            \"fix_time\": 1636552316,\n            \"active_tracking\": true,\n            \"cog\": 18000,\n            \"sog\": 0\n        },\n        \"port\": 2,\n        \"timestamp\": 1636552317,\n        \"timestamp_parsed\": \"2021-11-10 14:51:57\"\n    },\n    \"2\": {\n        \"name\": \"msg_ublox_location\",\n        \"data\": {\n            \"latitude\": 52.167704,\n            \"longitude\": 5.136292,\n            \"altitude\": 50.814,\n            \"success\": true,\n            \"hot_retry\": 0,\n            \"cold_retry\": 0,\n            \"ttf\": 0,\n            \"fix_type\": 3,\n            \"siv\": 9,\n            \"h_acc_est\": 3,\n            \"pDOP\": 1,\n            \"fix_time\": 1636552381,\n            \"active_tracking\": true,\n            \"cog\": 18000,\n            \"sog\": 0\n        },\n        \"port\": 2,\n        \"timestamp\": 1636552381,\n        \"timestamp_parsed\": \"2021-11-10 14:53:01\"\n    }\n}","output":"json","x":430,"y":120,"wires":[["5ae7e915433161f4","56605be699d85922"]]},{"id":"5ae7e915433161f4","type":"debug","z":"a618aa32f05d2304","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":120,"wires":[]},{"id":"861d6c69a79e580d","type":"function","z":"a618aa32f05d2304","name":"","func":"let header  = msg.header\nlet payload = msg.payload\nlet footer  = msg.footer\nlet lat = ' '\nlet lon = ' '\nlet ele = ' '\nlet trkpt   = ''\n\n//process each row and get the data bits\nfor (let row in payload) {\n    lat = payload[row].data.latitude\n    lon = payload[row].data.longitude\n    ele = payload[row].data.altitude\n    \n    node.warn(\"processing \" + payload[row].data.latitude);\n\n// now build the <trkpt...> string\n    trkpt  = \"<trkpt lat=\\\"\" + lat + \"\\\" \"\n    trkpt += \"lon=\\\"\" + lon\n    trkpt += \"><ele>\\\"\" + ele + \"\\\"</ele>\"\n\n}\n\n// put the whole CML together\nmsg.payload = msg.header + trkpt + \"\\n\" + msg.footer\nreturn msg;\n\n// data string example\n//  <trkpt lat=\"52.167908\" lon=\"5.129468\"><ele>\"56.758\"</ele><time>\"2021-11-10T13:51:56.000Z\"</time></trkpt>\n//  <trkpt lat=\"52.167704\" lon=\"5.136292\"><ele>\"50.814\"</ele><time>\"2021-11-10T13:53:01.000Z\"</time></trkpt>\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":300,"wires":[["09ce9117951a8c33"]]},{"id":"56605be699d85922","type":"template","z":"a618aa32f05d2304","name":"XML header","field":"header","fieldType":"msg","format":"text","syntax":"plain","template":"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"1.0\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xmlns=\"http://www.topografix.com/GPX/1/0\"\n  xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0\n  http://www.topografix.com/GPX/1/0/gpx.xsd\">\n  <name>gpx file name</name>\n  <trk><name>gpx track name</name><number>1</number><trkseg>\n","output":"str","x":430,"y":180,"wires":[["dc2dfc160d97c8db"]]},{"id":"dc2dfc160d97c8db","type":"template","z":"a618aa32f05d2304","name":"xml footer","field":"footer","fieldType":"msg","format":"text","syntax":"plain","template":"  </trkseg></trk>\n</gpx>","output":"str","x":420,"y":240,"wires":[["861d6c69a79e580d"]]},{"id":"09ce9117951a8c33","type":"debug","z":"a618aa32f05d2304","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":300,"wires":[]}]

Like @TotallyInformation says - it is an object, not an array (as your function uses it).

const header = `<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.topografix.com/GPX/1/0"
  xsi:schemaLocation="http://www.topografix.com/GPX/1/0
  http://www.topografix.com/GPX/1/0/gpx.xsd">
  <name>gpx file name</name>
  <trk><name>gpx track name</name><number>1</number><trkseg>`
const footer = `</trkseg></trk></gpx>`

let points = ""

for (const key of Object.keys(msg.payload)) {
    let pos = msg.payload[key].data
    
    points += `
    <trkpt lat="${pos.latitude}" lon="${pos.longitude}">
    <ele>${pos.altitude}</ele>
    <time>${new Date(pos.fix_time*1000).toISOString()}</time>
    </trkpt>`
}

msg.output = `${header}${points}${footer}`

return msg;

This creates an xml as your expected output.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.