// ==UserScript== // @name GaiaGps Uploader v2 // @version 2.0 // @grant none // @match https://www.gaiagps.com/upload/ // @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.0/jquery.min.js // ==/UserScript== var gpxParser = function () { this.xmlSource = ""; this.metadata = {}; this.waypoints = []; this.tracks = []; this.routes = []; }; gpxParser.prototype.parse = function (gpxstring) { var keepThis = this; var domParser = new window.DOMParser(); this.xmlSource = domParser.parseFromString(gpxstring, 'text/xml'); metadata = this.xmlSource.querySelector('metadata'); if(metadata != null){ this.metadata.name = this.getElementValue(metadata, "name"); this.metadata.desc = this.getElementValue(metadata, "desc"); this.metadata.time = this.getElementValue(metadata, "time"); let author = {}; let authorElem = metadata.querySelector('author'); if(authorElem != null){ author.name = this.getElementValue(authorElem, "name"); author.email = {}; let emailElem = authorElem.querySelector('email'); if(emailElem != null){ author.email.id = emailElem.getAttribute("id"); author.email.domain = emailElem.getAttribute("domain"); } let link = {}; let linkElem = authorElem.querySelector('link'); if(linkElem != null){ link.href = linkElem.getAttribute('href'); link.text = this.getElementValue(linkElem, "text"); link.type = this.getElementValue(linkElem, "type"); } author.link = link; } this.metadata.author = author; let link = {}; let linkElem = this.queryDirectSelector(metadata, 'link'); if(linkElem != null){ link.href = linkElem.getAttribute('href'); link.text = this.getElementValue(linkElem, "text"); link.type = this.getElementValue(linkElem, "type"); this.metadata.link = link; } } var wpts = [].slice.call(this.xmlSource.querySelectorAll('wpt')); for (let idx in wpts){ var wpt = wpts[idx]; let pt = {}; pt.name = keepThis.getElementValue(wpt, "name") pt.lat = parseFloat(wpt.getAttribute("lat")); pt.lon = parseFloat(wpt.getAttribute("lon")); pt.ele = parseFloat(keepThis.getElementValue(wpt, "ele")) || null; pt.cmt = keepThis.getElementValue(wpt, "cmt"); pt.desc = keepThis.getElementValue(wpt, "desc"); pt.sym = keepThis.getElementValue(wpt, "sym"); keepThis.waypoints.push(pt); } var rtes = [].slice.call(this.xmlSource.querySelectorAll('rte')); for (let idx in rtes){ var rte = rtes[idx]; let route = {}; route.name = keepThis.getElementValue(rte, "name"); route.cmt = keepThis.getElementValue(rte, "cmt"); route.desc = keepThis.getElementValue(rte, "desc"); route.src = keepThis.getElementValue(rte, "src"); route.number = keepThis.getElementValue(rte, "number"); let type = keepThis.queryDirectSelector(rte, "type"); route.type = type != null ? type.innerHTML : null; let link = {}; let linkElem = rte.querySelector('link'); if(linkElem != null){ link.href = linkElem.getAttribute('href'); link.text = keepThis.getElementValue(linkElem, "text"); link.type = keepThis.getElementValue(linkElem, "type"); } route.link = link; let routepoints = []; var rtepts = [].slice.call(rte.querySelectorAll('rtept')); for (let idxIn in rtepts){ var rtept = rtepts[idxIn]; let pt = {}; pt.lat = parseFloat(rtept.getAttribute("lat")); pt.lon = parseFloat(rtept.getAttribute("lon")); pt.ele = parseFloat(keepThis.getElementValue(rtept, "ele")); routepoints.push(pt); } route.points = routepoints; keepThis.routes.push(route); } var trks = [].slice.call(this.xmlSource.querySelectorAll('trk')); for (let idx in trks){ var trk = trks[idx]; let track = {}; track.name = keepThis.getElementValue(trk, "name"); track.cmt = keepThis.getElementValue(trk, "cmt"); track.desc = keepThis.getElementValue(trk, "desc"); track.src = keepThis.getElementValue(trk, "src"); track.number = keepThis.getElementValue(trk, "number"); track.color = keepThis.getElementValue(trk, "DisplayColor"); let type = keepThis.queryDirectSelector(trk, "type"); let link = {}; let linkElem = trk.querySelector('link'); if(linkElem != null){ link.href = linkElem.getAttribute('href'); link.text = keepThis.getElementValue(linkElem, "text"); link.type = keepThis.getElementValue(linkElem, "type"); } track.link = link; let trackpoints = []; var trkpts = [].slice.call(trk.querySelectorAll('trkpt')); for (let idxIn in trkpts){ var trkpt = trkpts[idxIn]; let pt = {}; pt.lat = parseFloat(trkpt.getAttribute("lat")); pt.lon = parseFloat(trkpt.getAttribute("lon")); pt.ele = parseFloat(keepThis.getElementValue(trkpt, "ele")) || null; trackpoints.push(pt); } track.points = trackpoints; keepThis.tracks.push(track); } }; gpxParser.prototype.getElementValue = function(parent, needle){ let elem = parent.querySelector(needle); if(elem != null){ return elem.innerHTML != undefined ? elem.innerHTML : elem.childNodes[0].data; } return elem; } gpxParser.prototype.queryDirectSelector = function(parent, needle) { let elements = parent.querySelectorAll(needle); let finalElem = elements[0]; if(elements.length > 1) { let directChilds = parent.childNodes; for(idx in directChilds) { elem = directChilds[idx]; if(elem.tagName === needle) { finalElem = elem; } } } return finalElem; } gpxParser.prototype.getGPX = function(sName) { var sTrack = ''; for(var i=0 ; i < this.tracks.length ; i++) { var sTrkSeg = ''; for(var j=0 ; j < this.tracks[i].points.length ; j++) { sTrkSeg += ''+ ''+(this.tracks[i].points[j].ele || '')+''+ ''; } sTrack += ''+ ''+(this.tracks[i].name || '')+''+ ''+(this.tracks[i].desc || '')+''+ ''+(this.tracks[i].src || '')+''+ ''+sTrkSeg+''+ ''; } var sGPX = ''+ ''+ ''+sName+''+ sTrack+ ''; return sGPX; }; class Gaia { static get URL() { return 'https://www.gaiagps.com'; } static get API() { return Gaia.URL+'/api/objects'; } constructor($Form) { this.aoWaypoints = []; this.aoTracks = []; this.sFolderId = ''; this.sFolderName = ''; this.$Form = $Form; this.$InputName = $Form.find('input[name=name]'); this.$InputFile = $Form.find('input[type=file]'); this.$Feedback = $Form.after($('
')); this.setLayout(); } feedback(sType, sMsg) { var sFormattedMsg = sType.charAt(0).toUpperCase()+sType.slice(1)+': '+sMsg+'.'; console.log(sFormattedMsg); let sColor = 'black'; switch(sType) { case 'error': sColor = 'red'; break; case 'warning': sColor = 'orange'; break; case 'info': sColor = 'green'; break; } this.$Feedback.append($('

', {'style': 'color: '+sColor+';'}).text(sFormattedMsg)); } //Modify Gaia DOM Interface setLayout() { //Set event on file selection this.$InputFile .attr('multiple', 'multiple') .attr('name', 'files[]') .change(() => {this.readInputFiles();}); //Remove submit button & edit label this.$Form.find('#fileuploadtext').text('Select files'); this.$Form.find('button[type=submit]').hide(); //Set default Name this.$InputName.val('PCT'); //Clear all upload notifications this.resetNotif(); } //Parse files from input (multiple) readInputFiles() { if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { this.feedback('error', 'File APIs are not fully supported in this browser'); return; } else this.feedback('info', 'Parsing input files'); //Get Folder Name (text input) this.sFolderName = this.$InputName.val(); let aoReaders = []; let asFiles = this.$InputFile.prop('files'); let asContents = []; for(var i=0 ; i < asFiles.length ; i++) { aoReaders[i] = new FileReader(); aoReaders[i].onload = ((asResult) => { asContents.push(asResult.target.result); this.feedback('info', 'Reading file '+asContents.length+'/'+asFiles.length); if(asContents.length == asFiles.length) this.parseFile(asContents); }); aoReaders[i].readAsText(asFiles[i]); } } //Parse GPX files to consolidate tracks & waypoints parseFile(asContents) { this.feedback('info', 'Merging files'); for(var i in asContents) { var oGPX = new gpxParser(); oGPX.parse(asContents[i]); //Waypoints for(var w in oGPX.waypoints) { var sWaypointName = oGPX.waypoints[w].name; if(sWaypointName.indexOf('Milestone - ') != -1 && sWaypointName.substr(-2) != '00') { //1 milestone every 100 miles this.feedback('info', 'Ignoring milestone waypoint "'+sWaypointName+'"'); } else this.aoWaypoints.push(oGPX.waypoints[w]); } //Tracks for(var t in oGPX.tracks) { this.aoTracks.push(oGPX.tracks[t]); } } this.deleteFolder(); } //Delete existing folder with same name deleteFolder() { this.feedback('info', 'Looking for existing "'+this.sFolderName+'" folder...'); $.get(Gaia.API+'/folder/?routepoints=false&show_archived=true&show_filed=true').done((asFolders) => { var bCalled = false; for(var f in asFolders) { if(asFolders[f].title == this.sFolderName) { bCalled = true; this.feedback('info', 'Deleting "'+this.sFolderName+'" folder'); $.ajax({ url: Gaia.API+'/folder/'+asFolders[f].id+'/', type: 'DELETE' }); } } if(!bCalled) this.feedback('info', 'No folder named "'+this.sFolderName+'" found'); this.uploadTrack(); }); } //Marking all upload notifications as read resetNotif() { this.feedback('info', 'Marking all upload notifications as read'); $.get(Gaia.URL+'/social/notifications/popup/').done((asNotifs) => { for(var i in asNotifs) { if(!asNotifs[i].isViewed && asNotifs[i].html.indexOf('has completed') != -1) { $.post(Gaia.URL+'/social/notifications/'+asNotifs[i].id+'/markviewed/'); } } }); } //Build & Upload Track File uploadTrack() { //Build track file this.feedback('info', 'Building consolidated track'); var oGPX = new gpxParser(); oGPX.tracks = this.aoTracks; var sTrack = oGPX.getGPX(this.sFolderName); //Send consolidated Tracks as one file var oForm = new FormData(); oForm.append('name', this.sFolderName); oForm.append('files', new Blob([sTrack]), 'Consolidated.gpx'); this.feedback('info', 'Uploading consolidated track'); $.ajax({ url: Gaia.URL+'/upload/', type: 'POST', data: oForm, enctype: 'multipart/form-data', processData: false, contentType: false, cache: false }).done(() => {this.checkNotif();}); } //Wait for file to be processed by Gaia checkNotif() { this.feedback('info', 'Waiting for Gaia to process consolidated track'); $.get(Gaia.URL+'/social/notifications/popup/').done((asNotifs) => { for(var i in asNotifs) { if(!asNotifs[i].isViewed && asNotifs[i].html.indexOf('has completed') != -1) { this.feedback('info', 'Notification '+asNotifs[i].id+' found. Marking as read'); var $Notif = $('').html(asNotifs[i].html); this.sFolderId = $Notif.find('a').attr('href').split('/')[3]; $.post(Gaia.URL+'/social/notifications/'+asNotifs[i].id+'/markviewed/'); } } if(this.sFolderId != '') { this.setTracksColor(); this.uploadWayPoints(); } else setTimeout((() => {this.checkNotif();}), 1000); }); } //Convert QMapshack Track Colors into Gaia Colors setTracksColor() { this.feedback('info', 'Track Color - Matching Gaia track IDs with GPX tracks'); var iCount = 0; $.get(Gaia.API+'/folder/'+this.sFolderId+'/').done((asFolder) => { var self = this; for(var iGaiaIndex in asFolder.properties.tracks) { for(var iGpxIndex in this.aoTracks) { if(asFolder.properties.tracks[iGaiaIndex].title == this.aoTracks[iGpxIndex].name) { this.aoTracks[iGpxIndex].id = asFolder.properties.tracks[iGaiaIndex].id; this.feedback('info', 'Track Color - Track "'+this.aoTracks[iGpxIndex].name+'" found a match ('+(++iCount)+'/'+this.aoTracks.length+')'); $.ajax({ url: Gaia.API+'/track/'+this.aoTracks[iGpxIndex].id+'/', color: this.aoTracks[iGpxIndex].color }).done(function(asGaiaTrackDisplay) { var asGaiaTrack = asGaiaTrackDisplay.features[0]; delete asGaiaTrack.id; delete asGaiaTrack.style; //Set color var sColor = '#ff0000'; switch(this.color) { case 'DarkBlue': sColor = '#2D3FC7'; break; case 'Magenta': sColor = '#B60DC3'; break; } asGaiaTrack.properties.color = sColor; asGaiaTrack.properties.hexcolor = sColor; self.feedback('info', 'Track Color - Setting track color "'+sColor+'" ('+this.color+') to "'+asGaiaTrack.properties.title+'"'); $.ajax({ url: Gaia.API+'/track/'+asGaiaTrack.properties.id+'/', type: 'PUT', contentType: 'application/json', data: JSON.stringify(asGaiaTrack) }); }); } } } }); } uploadWayPoints(iIndex, bSecondTry) { iIndex = iIndex || 0; bSecondTry = bSecondTry || false; //Upload waypoints if(iIndex < this.aoWaypoints.length) { var sWaypointName = this.aoWaypoints[iIndex].name; this.feedback('info', 'Waypoints Upload - Uploading waypoint '+(iIndex + 1)+'/'+this.aoWaypoints.length+' ('+sWaypointName+')'); var asPost = { type: 'Feature', geometry: { type: 'Point', coordinates: [ this.aoWaypoints[iIndex].lon, this.aoWaypoints[iIndex].lat, this.aoWaypoints[iIndex].ele ] }, properties: { title: sWaypointName, time_created: "2020-06-07T14:01:03.944Z", icon: Gaia.getIconName(this.aoWaypoints[iIndex].sym), writable: true, localId: iIndex+'', archived: false, isTitleLoaded: true, isLocallyCreated: true, type: 'waypoint' } }; if(this.aoWaypoints[iIndex].desc) asPost.properties.notes = this.aoWaypoints[iIndex].desc; $.ajax({ url: Gaia.API+'/waypoint/', type: 'POST', contentType: 'application/json', data: JSON.stringify(asPost) }).done((asWaypoint) => { //Update local waypoint with all server info (including ID) this.aoWaypoints[iIndex] = asWaypoint; $.get(Gaia.URL+'/public/generate?type=waypoint&objectid='+asWaypoint.properties.id).always(() => { this.uploadWayPoints(++iIndex); }); }).fail(function(){ this.feedback('error', 'Waypoints Upload - Failed to upload waypoint #'+(iIndex + 1)+' ('+sWaypointName+'). Trying again...'); this.uploadWayPoints(iIndex, true); }); } //Done uploading, assigning waypoints to folder else { this.feedback('info', 'Waypoints Upload - Getting folders list'); $.get(Gaia.API+'/folder/').done((asFolders) => { //Find Folder for(var f in asFolders) { if(asFolders[f].id == this.sFolderId) { var asFolder = asFolders[f]; } } //Assign waypoints to folder for(var i in this.aoWaypoints) asFolder.waypoints.push(this.aoWaypoints[i].properties.id); this.feedback('info', 'Waypoints Upload - Assigning waypoints to folder '+this.sFolderId); $.ajax({ url: Gaia.API+'/folder/'+this.sFolderId+'/', type: 'PUT', contentType: 'application/json', data: JSON.stringify(asFolder) }).done(() => { this.feedback('info', 'Waypoints Upload - Finished successfully'); }).fail(() => { this.feedback('error', 'Waypoints Upload - Failed to assign waypoints to folder "'+sFolderId+'"'); }); }); } } static getIconName(sGarminName) { var asMapping = { 'Trail Head': 'trailhead', 'Water Source': 'water-24.png', 'Truck': 'car-24.png', 'Post Office': 'resupply', 'Flag, Blue': 'blue-pin-down.png', 'Car': 'car-24.png', 'Flag, Red': 'red-pin-down.png', 'Campground': 'campsite-24.png', 'Flag, Green': 'green-pin.png', 'Powerline': 'petroglyph', 'Shopping Center': 'cafe-24.png', 'Lodging': 'lodging-24.png', 'Water Hydrant': 'red-pin-down.png', 'Drinking Water': 'potable-water', 'Toll Booth': 'ranger-station', 'Summit': 'peak', 'Park': 'park-24.png', 'Forest': 'forest', 'Cemetery': 'cemetery-24.png', 'Bridge': 'dam-24.png', 'Restaurant': 'restaurant-24.png', 'Picnic Area': 'picnic', 'Residence': 'city-24.png', 'City (Capitol)': 'city-24.png', 'Ski Resort': 'skiing-24.png', 'Restroom': 'toilets-24.png', 'Ground Transportation': 'car-24.png', 'Church': 'ghost-town' }; return (sGarminName in asMapping)?asMapping[sGarminName]:'red-pin-down.png'; } } console.log('computes'); let oGaia = new Gaia($('#uploadForm'));