// ==UserScript== // @name GaiaGps Uploader // @description Allow the user to upload multiple files at once and more than 1000 waypoints // @version 3.0 // @grant none // @match https://www.gaiagps.com/map/* // @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.0/jquery.min.js // @namespace https://greasyfork.org/users/583371 // ==/UserScript== /* jshint esversion: 6 */ //Ctrl+Alt+Shift+I to open network tab on addon /* GPXParser - v3.0.6 - https://github.com/Luuka/GPXParser.js/blob/master/src/GPXParser.js */ /* Personnal modifications have been applied, care on upgrade */ let gpxParser = function () { this.xmlSource = ""; this.metadata = {}; this.waypoints = []; this.tracks = []; this.routes = []; }; gpxParser.prototype.parse = function (gpxstring) { let keepThis = this; let 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"); //let time = keepThis.getElementValue(wpt, "time"); //pt.time = time == null ? null : new Date(time); pt.time = (keepThis.getElementValue(wpt, "time") || keepThis.metadata.time) || null;; keepThis.waypoints.push(pt); } var rtes = [].slice.call(this.xmlSource.querySelectorAll('rte')); for (let idx in rtes){ let 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){ let rtept = rtepts[idxIn]; let pt = {}; pt.lat = parseFloat(rtept.getAttribute("lat")); pt.lon = parseFloat(rtept.getAttribute("lon")); pt.ele = parseFloat(keepThis.getElementValue(rtept, "ele")) || null; //let time = keepThis.getElementValue(rtept, "time"); //pt.time = time == null ? null : new Date(time); pt.time = (keepThis.getElementValue(rtept, "time") || keepThis.metadata.time) || null; routepoints.push(pt); } //route.distance = keepThis.calculDistance(routepoints); //route.elevation = keepThis.calcElevation(routepoints); //route.slopes = keepThis.calculSlope(routepoints, route.distance.cumul); route.points = routepoints; keepThis.routes.push(route); } var trks = [].slice.call(this.xmlSource.querySelectorAll('trk')); for (let idx in trks){ let 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"); track.time = keepThis.metadata.time; let type = keepThis.queryDirectSelector(trk, "type"); track.type = type != null ? type.innerHTML : null; 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 = []; let 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; //let time = keepThis.getElementValue(trkpt, "time"); //pt.time = time == null ? null : new Date(time); pt.time = (keepThis.getElementValue(trkpt, "time") || keepThis.metadata.time) || null; trackpoints.push(pt); } //track.distance = keepThis.calculDistance(trackpoints); //track.elevation = keepThis.calcElevation(trackpoints); //track.slopes = keepThis.calculSlope(trackpoints, track.distance.cumul); track.points = trackpoints; keepThis.tracks.push(track); } }; gpxParser.prototype.getElementValue = function(parent, needle){ let elem = parent.querySelector(needle); if(elem != null){ return $('
').html(elem.innerHTML != undefined ? elem.innerHTML : elem.childNodes[0].data).text(); } 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.calculDistance = function(points) { let distance = {}; let totalDistance = 0; let cumulDistance = []; for (var i = 0; i < points.length - 1; i++) { totalDistance += this.calcDistanceBetween(points[i],points[i+1]); cumulDistance[i] = totalDistance; } cumulDistance[points.length - 1] = totalDistance; distance.total = totalDistance; distance.cumul = cumulDistance; return distance; } gpxParser.prototype.calcDistanceBetween = function (wpt1, wpt2) { let latlng1 = {}; latlng1.lat = wpt1.lat; latlng1.lon = wpt1.lon; let latlng2 = {}; latlng2.lat = wpt2.lat; latlng2.lon = wpt2.lon; var rad = Math.PI / 180, lat1 = latlng1.lat * rad, lat2 = latlng2.lat * rad, sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), sinDLon = Math.sin((latlng2.lon - latlng1.lon) * rad / 2), a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return 6371000 * c; } gpxParser.prototype.calcElevation = function (points) { var dp = 0, dm = 0, ret = {}; for (var i = 0; i < points.length - 1; i++) { var diff = parseFloat(points[i + 1].ele) - parseFloat(points[i].ele); if (diff < 0) { dm += diff; } else if (diff > 0) { dp += diff; } } var elevation = []; var sum = 0; for (var i = 0, len = points.length; i < len; i++) { var ele = parseFloat(points[i].ele); elevation.push(ele); sum += ele; } ret.max = Math.max.apply(null, elevation) || null; ret.min = Math.min.apply(null, elevation) || null; ret.pos = Math.abs(dp) || null; ret.neg = Math.abs(dm) || null; ret.avg = sum / elevation.length || null; return ret; }; gpxParser.prototype.calculSlope = function(points, cumul) { let slopes = []; for (var i = 0; i < points.length - 1; i++) { let point = points[i]; let nextPoint = points[i+1]; let elevationDiff = nextPoint.ele - point.ele; let distance = cumul[i+1] - cumul[i]; let slope = (elevationDiff * 100) / distance; slopes.push(slope); } return slopes; } gpxParser.prototype.toGeoJSON = function () { var GeoJSON = { "type": "FeatureCollection", "features": [], "properties": { "name": this.metadata.name, "desc": this.metadata.desc, "time": this.metadata.time, "author": this.metadata.author, "link": this.metadata.link, }, }; for(idx in this.tracks) { let track = this.tracks[idx]; var feature = { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [] }, "properties": { } }; feature.properties.name = track.name; feature.properties.cmt = track.cmt; feature.properties.desc = track.desc; feature.properties.src = track.src; feature.properties.number = track.number; feature.properties.link = track.link; feature.properties.type = track.type; for(idx in track.points) { let pt = track.points[idx]; var geoPt = []; geoPt.push(pt.lon); geoPt.push(pt.lat); geoPt.push(pt.ele); feature.geometry.coordinates.push(geoPt); } GeoJSON.features.push(feature); } for(idx in this.routes) { let track = this.routes[idx]; var feature = { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [] }, "properties": { } }; feature.properties.name = track.name; feature.properties.cmt = track.cmt; feature.properties.desc = track.desc; feature.properties.src = track.src; feature.properties.number = track.number; feature.properties.link = track.link; feature.properties.type = track.type; for(idx in track.points) { let pt = track.points[idx]; var geoPt = []; geoPt.push(pt.lon); geoPt.push(pt.lat); geoPt.push(pt.ele); feature.geometry.coordinates.push(geoPt); } GeoJSON.features.push(feature); } for(idx in this.waypoints) { let pt = this.waypoints[idx]; var feature = { "type": "Feature", "geometry": { "type": "Point", "coordinates": [] }, "properties": { } }; feature.properties.name = pt.name; feature.properties.cmt = pt.cmt; feature.properties.desc = pt.desc; feature.geometry.coordinates = [pt.lon, pt.lat, pt.ele]; GeoJSON.features.push(feature); } return GeoJSON; } if(typeof module !== 'undefined'){ require('jsdom-global')(); module.exports = gpxParser; } 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() { this.asFiles = []; this.aoWaypoints = []; this.aoTracks = []; this.sFolderId = ''; this.asFolder = {}; this.sFolderName = ''; this.progress = {current:0, total:0}; } /* FIXME: adapts on GaiaGPS upgrade */ setLayout() { let $InputButton = $('a[href="https://help.gaiagps.com/hc/en-us/articles/360052763513"]').parent().find('button'); //If the button is found (=displayed) if($InputButton.length > 0) { this.$InputBox = $InputButton.parent(); //Add Feedback box this.$Feedback = ($('#ggu-feedback').length > 0)?$('#ggu-feedback'):($('
', { 'id':'ggu-feedback', 'style':'width:calc(100% + 64px); height:400px; margin:1em 0 0 -32px; display:inline-block; text-align:left; overflow:auto;' }).insertAfter($InputButton)); //Add Folder Name Input next to button this.$InputName = ($('#ggu-inputname').length > 0)?$('#ggu-inputname'):($('', { 'type':'text', 'id': 'ggu-inputname', 'name':'ggu-inputname', 'placeholder':'Folder Name', 'style': 'border-width:2px; border-color:rgba(0, 0, 0, 0.1); border-radius:4px; font-family:Inter,Helvetica Neue,Helvetica,Arial; font-size:15px; padding:8px 12px; margin-bottom:12px;' }).val('PCT').prependTo(this.$InputBox)); //Reset File Input let $InputFile = $('input[type=file]'); this.$InputFile = $InputFile.clone() .insertAfter($InputFile) .attr('multiple', 'multiple') .attr('name', 'files[]') .change(() => { this.readInputFiles(); }); $InputFile.remove(); //Reset button this.$InputButton = $InputButton.clone() .insertAfter($InputButton) .click(() => {this.$InputFile.click();}); $InputButton.remove(); //Clear all upload notifications this.resetNotif(); } } feedback(sType, sMsg) { var sFormattedMsg = sType.charAt(0).toUpperCase()+sType.slice(1)+': '+sMsg+(sMsg.slice(-1)=='.'?'':'.'); console.log(sFormattedMsg); let sColor = 'black'; switch(sType) { case 'error': sColor = 'red'; break; case 'warning': sColor = 'orange'; break; case 'info': sColor = '#2D5E38'; break; } this.$Feedback.append($('

', {'style': 'color: '+sColor+';'}).text(sFormattedMsg)); this.$Feedback.scrollTop(this.$Feedback.prop("scrollHeight")); } incProgress() { if(!this.progress.current) { this.progress.$Done = $('

', {'style':'overflow:hidden; background:#2D5E38; color: white; text-align:right;'}); this.progress.$Left = $('
', {'style':'overflow:hidden; background:white; color: #2D5E38; text-align:left;'}); this.progress.$Box = $('
', {'id':'ggu-progress', 'style':'margin-top:1em;'}) .append($('
', {'style':'display:flex; width:calc(100% + 64px); margin-left:-32px; border-radius:3px; border: 1px solid #2D5E38; box-sizing: border-box;'}) .append(this.progress.$Done) .append(this.progress.$Left) ); this.$Feedback.before(this.progress.$Box); } this.progress.current++; if(this.progress.current < this.progress.total) { let iRatio = Math.round(this.progress.current / this.progress.total * 100); let sTextDone = '', sTextLeft = ''; if(iRatio < 50) { sTextDone = ''; sTextLeft = ' '+iRatio+'% ('+this.progress.current+' / '+this.progress.total+')'; } else { sTextDone = '('+this.progress.current+' / '+this.progress.total+') '+iRatio+'% '; sTextLeft = ''; } this.progress.$Done.css('flex', iRatio+' 1 0%').html(sTextDone); this.progress.$Left.css('flex', (100 - iRatio)+' 1 0%').html(sTextLeft); } else { this.progress.$Box.remove(); this.progress = {current:0, total:0}; } } //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/'); } } }); } //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 iCount = 0; this.asFiles = this.$InputFile.prop('files'); for(var i=0 ; i < this.asFiles.length ; i++) { aoReaders[i] = new FileReader(); aoReaders[i].onload = ((oFileReader) => { return (asResult) => { iCount++; this.feedback('info', 'Reading file "'+oFileReader.name+'" ('+iCount+'/'+this.asFiles.length+')'); this.parseFile(oFileReader.name, asResult.target.result); if(iCount == this.asFiles.length) { this.progress.total = this.aoTracks.length + this.aoWaypoints.length + 2; //+1: Create forlder +1: Assign objects to folder this.createFolder(); } }; })(this.asFiles[i]); aoReaders[i].readAsText(this.asFiles[i]); } } //Parse GPX files to consolidate tracks & waypoints parseFile(sFileName, oContent) { this.feedback('info', 'Parsing file "'+sFileName+'"'); var oGPX = new gpxParser(); oGPX.parse(oContent); //Waypoints for(var w in oGPX.waypoints) { oGPX.waypoints[w].filename = sFileName; /*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) { oGPX.tracks[t].filename = sFileName; this.aoTracks.push(oGPX.tracks[t]); } } //Delete existing folder with same name & recreating it createFolder() { this.feedback('info', 'Looking for existing folder "'+this.sFolderName+'"...'); $.get(Gaia.API+'/folder/?search='+this.sFolderName).done((asFolders) => { if(asFolders != '' && !$.isEmptyObject(asFolders)) { for(var f in asFolders) { this.feedback('info', 'Deleting "'+asFolders[f].title+'" folder'); $.ajax({ url: Gaia.API+'/folder/'+asFolders[f].id+'/', type: 'DELETE' }); } } else this.feedback('info', 'No folder named "'+this.sFolderName+'" found'); this.feedback('info', 'Creating folder "'+this.sFolderName+'"'); $.post({ url: Gaia.API+'/folder/', contentType: 'application/json', data: JSON.stringify({imported: true, title: this.sFolderName}) }).done((asFolder) => { this.feedback('info', 'Folder "'+asFolder.properties.name+'" created'); this.asFolder = asFolder; this.sFolderId = asFolder.id; this.sFolderName = asFolder.properties.name; this.incProgress(); this.uploadTrack(); }).fail(() => { this.feedback('error', 'Folder "'+this.sFolderName+'" could not be created'); }); }); } //Build & Upload Track File uploadTrack(iIndex) { iIndex = iIndex || 0; if(iIndex == 0) this.feedback('info', 'Uploading tracks...'); let aoTrack = this.aoTracks[iIndex]; this.feedback('info', 'Uploading track "'+aoTrack.name+'"'); //Set color: Convert Garmin Colors let sColor = '#4ABD32'; switch(aoTrack.color) { case 'DarkBlue': sColor = '#2D3FC7'; break; //Personal Color case 'Magenta': sColor = '#B60DC3'; break; //Personal Color //Garmin Colors case 'Black': sColor = '#000000'; break; case 'DarkRed': sColor = '#F90553'; break; case 'DarkGreen': sColor = '#009B89'; break; case 'DarkYellow': sColor = '#DCEE0E'; break; //case 'DarkBlue': sColor = '#5E23CA'; break; case 'DarkMagenta': sColor = '#B60DC3'; break; case 'DarkCyan': sColor = '#00ACF8'; break; case 'LightGray': sColor = '#A4A4A4'; break; case 'DarkGray': sColor = '#577B8E'; break; case 'Red': sColor = '#F90553'; break; case 'Green': sColor = '#36C03B'; break; case 'Yellow': sColor = '#FFF011'; break; case 'Blue': sColor = '#2D3FC7'; break; //case 'Magenta': sColor = '#B60DC3'; break; case 'Cyan': sColor = '#00C3DD'; break; case 'White': sColor = '#FFFFFF'; break; case 'Transparent': sColor = '#784D3E'; break; } //Add track points let aoCoords = []; for(var p in aoTrack.points) { let pt = aoTrack.points[p]; aoCoords.push([pt.lon, pt.lat, pt.ele, 0]); } //Convert to geojson let sPostedData = JSON.stringify({ type: 'feature', properties: { color: sColor, title: aoTrack.name, time_created: aoTrack.time, routing_mode: null, notes: aoTrack.desc, //distance: 168405.62350073704, isValid: true, isLatestImport: true, filename: aoTrack.filename, localId: 'track'+(iIndex + 1), writable: true, archived: false, isPublicTrack: false, isLocallyCreated: true, type: 'track', parent_folder_id: this.sFolderId, hexcolor: sColor }, geometry: { type: 'MultiLineString', coordinates: [aoCoords] } }); var self = this; $.post({ url: Gaia.API+'/track/', contentType: 'application/json', data: sPostedData, trackName: aoTrack.name }).done(function(asTrack) { self.aoTracks[iIndex] = asTrack; self.confirmTrack(iIndex, asTrack, this.data); }).fail(function() { self.feedback('error', 'Track "'+this.trackName+'" upload failed (stage 1). Retrying...'); self.uploadTrack(iIndex); }); } confirmTrack(iIndex, asTrack, sPostedData) { iIndex = iIndex || 0; var self = this; $.ajax({ url: Gaia.API+'/track/'+asTrack.features[0].id+'/', type: 'PUT', contentType: 'application/json', data: sPostedData, trackName: asTrack.features[0].properties.title }).done(function() { self.feedback('info', 'Track "'+this.trackName+'" uploaded'); self.incProgress(); iIndex++; if(iIndex < self.aoTracks.length) self.uploadTrack(iIndex); else { self.feedback('info', 'All tracks uploaded'); self.uploadWayPoints(); } }).fail(function() { self.feedback('error', 'Track "'+this.trackName+'" upload failed (stage 2). Retrying...'); self.confirmTrack(iIndex, asTrack, sPostedData); }); } /* //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); }); } */ uploadWayPoints(iIndex) { iIndex = iIndex || 0; //Upload waypoints var sWaypointName = this.aoWaypoints[iIndex].name; this.feedback('info', '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: this.aoWaypoints[iIndex].time, icon: Gaia.getIconName(this.aoWaypoints[iIndex].sym), isValid: true, isLatestImport: true, filename: this.aoWaypoints[iIndex].filename, writable: true, localId: iIndex+'', notes: this.aoWaypoints[iIndex].desc, archived: false, isLocallyCreated: true, type: 'waypoint', parent_folder_id: this.sFolderId } }; let sData = JSON.stringify(asPost); var self = this; $.post({ url: Gaia.API+'/waypoint/', contentType: 'application/json', data: sData }).done(function(asWaypoint) { self.aoWaypoints[iIndex] = asWaypoint; self.confirmWayPoint(iIndex, asWaypoint, this.data); }).fail(function(){ self.feedback('error', 'Failed to upload waypoint #'+(iIndex + 1)+' "'+sWaypointName+'" (Stage 1). Trying again...'); self.uploadWayPoints(iIndex); }); } confirmWayPoint(iIndex, asWaypoint, sPostedData) { $.ajax({ url: Gaia.API+'/waypoint/'+asWaypoint.properties.id+'/', type: 'PUT', contentType: 'application/json', data: sPostedData }).done(() => { iIndex++; this.incProgress(); if(iIndex < this.aoWaypoints.length) this.uploadWayPoints(iIndex); else this.assignElementsToFolder(); }).fail(() => { this.feedback('error', 'Failed to upload waypoint #'+(iIndex + 1)+' "'+asWaypoint.properties.title+'" (Stage 2). Trying again...'); this.confirmWayPoint(iIndex, asWaypoint, sPostedData); }); } assignElementsToFolder() { this.feedback('info', 'Assigning all elements to folder "'+this.asFolder.properties.name+'"'); let asFolder = { cover_photo_id: this.asFolder.properties.cover_photo_id, id: this.asFolder.id, name: this.asFolder.properties.name, notes: this.asFolder.properties.notes, time_created: this.asFolder.properties.time_created, updated_date: this.asFolder.properties.updated_date } //Assign waypoints to folder asFolder.waypoints = []; for(var w in this.aoWaypoints) asFolder.waypoints.push(this.aoWaypoints[w].properties.id); //Assign tracks to folder asFolder.tracks = []; for(var t in this.aoTracks) asFolder.tracks.push(this.aoTracks[t].features[0].id); $.ajax({ url: Gaia.API+'/folder/'+asFolder.id+'/', type: 'PUT', contentType: 'application/json', data: JSON.stringify(asFolder) }).done(() => { this.incProgress(); this.feedback('info', 'Done'); }).fail(() => { this.feedback('warning', 'Failed to assign waypoints & tracks to folder ID "'+asFolder.id+'". Retrying...'); this.assignElementsToFolder(); }); } 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('Loading GaiaGps Uploader...'); let oGaia = new Gaia(); /* To be adjusted on Gaia's updates */ //Side panel $('path[d="M5 4v2h14V4H5zm0 10h4v6h6v-6h4l-7-7-7 7z"]').parent().parent().on('click', () => { setTimeout(() => { oGaia.setLayout(); }, 500); });