1/* 2 Copyright (c) 2013 by Nop. 3 Copying or reuse require explicit approval by the author. 4 */ 5 6var Line = L.Polyline.extend({ 7 normalLine: { color: '#000', weight: 7, opacity: 0.3 }, 8 hoverLine: { color: '#000', weight: 7, opacity: 0.6 }, 9 leg: null, 10 initialize: function (latlngs, leg) { 11 L.Polyline.prototype.initialize.call(this, latlngs, this.normalLine); 12 13 this.leg = leg; 14 15 this.on('mouseover', this.lineHover) 16 this.on('mouseout', this.lineExit) 17 this.on('mousedown', this.lineSplit) 18 }, 19 20 // line event handler 21 lineHover: function (e) { 22 if (!gpxEditor.active) return; 23 24 e.target.setStyle(this.hoverLine); 25 }, 26 lineExit: function (e) { 27 if (!gpxEditor.active) return; 28 29 e.target.setStyle(this.normalLine); 30 }, 31 lineSplit: function (e) { 32 if (!gpxEditor.active) return; 33 34 gpxEditor.closePopup(); 35 const newLeg = this.leg.splitAt(e.latlng); 36 newLeg.mark.dragging._draggable._onDown(e.originalEvent); 37 //L.DomEvent.stopPropagation(e); 38 } 39 40}); 41 42var Route = L.Polyline.extend({ 43 namedIcon: L.icon({ iconUrl: 'static/namedpoint.png', iconSize: [20, 20] }), 44 45 initialize: function (latlngs, color) { 46 47 L.Polyline.prototype.initialize.call(this, latlngs, { 48 weight: 7, 49 opacity: 0.5, 50 color: color }); 51 52 this.on('mouseover', this.lineHover) 53 this.on('mouseout', this.lineExit) 54 this.on('click', this.takeover) 55 }, 56 57 // line event handler 58 lineHover: function (e) { 59 if (!gpxEditor.active) return; 60 61 e.target.setStyle( {opacity: 0.8} ); 62 63 var ll = this.getLatLngs(); 64 if (e.originalEvent.shiftKey) 65 this.start = L.marker( ll[ll.length-1], { icon: this.namedIcon, clickable: false, keyboard:false }).addTo(gpxEditor.background); 66 else 67 this.start = L.marker( ll[0], { icon: this.namedIcon, clickable: false, keyboard:false }).addTo(gpxEditor.background); 68 }, 69 lineExit: function (e) { 70 if (!gpxEditor.active) return; 71 72 e.target.setStyle({opacity: 0.5}); 73 74 gpxEditor.background.removeLayer( this.start ); 75 }, 76 takeover: function (e) { 77 if (!gpxEditor.active) return; 78 79 var ll = this.getLatLngs(); 80 if (e.originalEvent.shiftKey) 81 for( var i=ll.length-1; i>= 0; i-- ) { 82 gpxEditor.addWaypoint( ll[i].lng, ll[i].lat ); 83 } 84 else 85 for( var i=0; i<ll.length; i++ ) { 86 gpxEditor.addWaypoint( ll[i].lng, ll[i].lat ); 87 } 88 gpxEditor.background.removeLayer( this ); 89 updateTracklength(); 90 } 91 92}); 93 94var Waypoint = L.Marker.extend({ 95 normalIcon: L.icon({ iconUrl: 'static/waypoint.png', iconSize: [13, 13] }), 96 namedIcon: L.icon({ iconUrl: 'static/namedpoint.png', iconSize: [20, 20] }), 97 leg: null, 98 initialize: function (latlng, leg) { 99 L.Marker.prototype.initialize.call(this, latlng, { 100 icon: ( leg.name != null ? this.namedIcon : this.normalIcon ), 101 draggable: true }); 102 103 this.leg = leg; 104 } 105}); 106 107var Leg = L.Class.extend({ 108 next: null, 109 110 line: null, 111 mark: null, 112 113 latlng: null, 114 name: null, 115 116 wasDragged: false, 117 118 // create leg and insert it in linked list 119 initialize: function (latlng) { 120 this.latlng = latlng; 121 }, 122 // utility functions 123 findPrev: function () { 124 if (this == gpxEditor.firstLeg) 125 return null; 126 127 var prev = gpxEditor.firstLeg; 128 while (prev.next != null && prev.next != this) 129 prev = prev.next; 130 131 return prev; 132 }, 133 134 moved: function (latlng, ended) { 135 136 this.latlng = latlng; 137 this.removeLine(); 138 this.addLine(); 139 140 var prev = this; 141 if (this != gpxEditor.firstLeg) 142 prev = this.findPrev(); 143 if (prev != null) { 144 prev.removeLine(); 145 prev.addLine(); 146 } 147 148 if (ended) 149 nav.onMarkerChange(); 150 updateTracklength(); 151 }, 152 153 addMarker: function () { 154 155 if (!gpxEditor.active || this.mark != null) 156 return; 157 158 this.mark = new Waypoint(this.latlng, this); 159 this.mark.addTo(map); 160 161 this.mark.on('click', this.markerClicked); 162 //this.mark.on('dragstart', this.markerDragStart); 163 this.mark.on('drag', this.markerDragging); 164 this.mark.on('dragend', this.markerDragged); 165 }, 166 removeMarker: function () { 167 168 if (this.mark == null) 169 return; 170 171 map.removeLayer(this.mark); 172 this.mark = null; 173 }, 174 addLine: function () { 175 176 // cannot add line without target node 177 if (this.next == null) 178 return; 179 180 this.line = new Line([ this.latlng, this.next.latlng], this).addTo(map); 181 }, 182 removeLine: function () { 183 184 if (this.line == null) 185 return; 186 187 map.removeLayer(this.line); 188 this.line = null; 189 }, 190 191 add: function (prev) { 192 193 if (gpxEditor.firstLeg == null) 194 gpxEditor.firstLeg = this; 195 196 if (prev != null) { 197 this.next = prev.next; 198 prev.next = this; 199 200 prev.addLine(); 201 } 202 if (gpxEditor.lastLeg == prev) 203 gpxEditor.lastLeg = this; 204 205 this.addMarker(); 206 if (this.next) 207 this.addLine(); 208 209 nav.onMarkerChange(); 210 updateTracklength(); 211 }, 212 remove: function () { 213 214 this.removeMarker(); 215 this.removeLine(); 216 217 if (gpxEditor.firstLeg == this) { 218 gpxEditor.firstLeg = this.next; 219 220 if (gpxEditor.lastLeg == this) 221 gpxEditor.lastLeg = null; 222 } 223 else { 224 prev = this.findPrev(); 225 if (prev != null) { 226 prev.removeLine(); 227 prev.next = this.next; 228 prev.addLine(); 229 230 if (gpxEditor.lastLeg == this) 231 gpxEditor.lastLeg = prev; 232 } 233 } 234 235 nav.onMarkerChange(); 236 updateTracklength(); 237 }, 238 enable: function (activate) { 239 if (activate) 240 this.addMarker(); 241 else 242 this.removeMarker(); 243 }, 244 splitAt: function (latlng) { 245 246 this.removeLine(); 247 248 var leg = new Leg(latlng, this); 249 leg.add(this); 250 251 nav.onMarkerChange(); 252 253 return leg; 254 }, 255 256 // marker event handler 257 markerClicked: function (e) { 258 // do not execute click after dragging 259 if (this.wasDragged) { 260 this.wasDragged = false; 261 return; 262 } 263 264 if (e.originalEvent.ctrlKey) 265 this.leg.remove(); 266 else { 267 this.leg.showPopup(); 268 } 269 270 }, 271 showPopup: function () { 272 gpxEditor.popupLeg = this; 273 gpxEditor.popup.setLatLng(this.latlng).openOn(map); 274 275 var dist = nav.getClosestTracklength(this.latlng); 276 if (dist <= 0) 277 dist = gpxEditor.getTracklength(this); 278 var position = document.getElementById('waypoint.position'); 279 position.innerHTML = (dist / 1000).toFixed(2); 280 281 // fill in popup data 282 var hasName = document.getElementById('waypoint.hasname'); 283 var name = document.getElementById('waypoint.name'); 284 if (this.name == null) { 285 hasName.checked = false; 286 name.value = ''; 287 name.disabled = true; 288 } 289 else { 290 hasName.checked = true; 291 name.value = this.name; 292 name.disabled = false; 293 } 294 }, 295 setNameFromPopup: function () { 296 var hasName = document.getElementById('waypoint.hasname'); 297 if (hasName.checked) { 298 var name = document.getElementById('waypoint.name'); 299 this.name = name.value; 300 } 301 else 302 this.name = null; 303 304 this.removeMarker(); 305 this.addMarker(); 306 }, 307 /* 308 markerDragStart: function (e) { 309 this.leg.mark.setIcon( this.leg.mark.moveIcon ); 310 }, 311 */ 312 markerDragging: function (e) { 313 gpxEditor.closePopup(); 314 this.leg.moved(e.target.getLatLng(), false); 315 }, 316 markerDragged: function (e) { 317 this.leg.moved(e.target.getLatLng(), true); 318 //this.leg.mark.setIcon( this.leg.mark.normalIcon ); 319 this.wasDragged = true; 320 } 321}); 322 323var Editor = L.Class.extend({ 324 firstLeg: null, 325 lastLeg: null, 326 327 active: false, 328 background: null, 329 330 popup: null, 331 popupLeg: null, 332 333 initialize: function () { 334 map.on("click", this.addLeg) 335 this.background = L.layerGroup().addTo(map); 336 337 e = document.getElementById('waypoint.popup'); 338 this.popup = L.popup().setContent(e); 339 }, 340 341 clear: function () { 342 this.closePopup(); 343 344 this.background.clearLayers(); 345 var leg = this.firstLeg; 346 while (leg != null) { 347 leg.removeLine(); 348 leg.removeMarker(); 349 leg = leg.next; 350 } 351 this.firstLeg = null; 352 this.lastLeg = null; 353 354 updateTracklength(); 355 }, 356 addWaypoint: function (lon, lat) { 357 var latlng = L.latLng(lat, lon); 358 var leg = new Leg(latlng); 359 leg.add(this.lastLeg); 360 }, 361 addWaypointLL: function (latlng) { 362 var leg = new Leg(latlng); 363 leg.add(this.lastLeg); 364 }, 365 // load array of latlngs into editor 366 setRoute: function (waypoints) { 367 this.clear(); 368 369 for (var i = 0; i < waypoints.length; i++) { 370 var leg = new Leg(waypoints[i].latlng); 371 leg.name = waypoints[i].name; 372 leg.add(this.lastLeg); 373 } 374 375 updateTracklength(); 376 }, 377
378 addBackgroundRoute: function( waypoints, color ) { 379 var ll = []; 380 for (var i = 0; i < waypoints.length; i++) { 381 ll.push( waypoints[i].latlng); 382 } 383 384 var route = new Route( ll, color ); 385 this.background.addLayer( route ); 386 }, 387 388 addLeg: function (e) { 389 if (!gpxEditor.active) return; 390 391 this.closePopup(); 392 var leg = new Leg(e.latlng); 393 leg.add(gpxEditor.lastLeg); 394 }, 395 396 closePopup: function () { 397 if (!this.popupLeg) 398 return; 399 400 map.closePopup(this.popup); 401 this.popupLeg = null; 402 }, 403 setNameFromPopup: function () { 404 if (this.popupLeg == null) 405 return; 406 407 this.popupLeg.setNameFromPopup(); 408 this.closePopup(); 409 }, 410 deletePopupWaypoint: function () { 411 if (this.popupLeg == null) 412 return; 413 414 this.popupLeg.remove(); 415 this.closePopup(); 416 }, 417 418 load: function () { 419 var file = document.getElementById('uplfile').files[0]; 420 421 if (file) { 422 var reader = new FileReader(); 423 reader.onload = function (e) { 424 gpxEditor.routeLoadComplete(e.target.result); 425 }; 426 reader.readAsText(file); 427 } 428 }, 429 routeLoadComplete: function (text) { 430 var reader = new GpxReader(); 431 if (reader.readGPXString(text)) { 432 hideLayer('uploadLayer'); 433 434 if (reader.name && reader.name.length > 2) { 435 var saveName = document.getElementById('routename'); 436 saveName.value = reader.name; 437 } 438 439 gpxEditor.setRoute(reader.points); 440 //map.fitBounds(reader.area); 441 } 442 }, 443 444// build gpx string from route 445 getRouteGPX: function (routename, isRoute) { 446 if (isRoute) { 447 var xmlEl = "rte"; 448 var xmlSegOn = ""; 449 var xmlSegOff = ""; 450 } else { 451 var xmlEl = "trk"; 452 var xmlSegOn = "<trkseg>" 453 var xmlSegOff = "</trkseg>" 454 } 455 var xml = '<?xml version="1.0"?>\n' 456 + '<gpx version="1.1"\n' 457 + ' creator="route/track editor on http://' + location.hostname + '"\n' 458 + ' xmlns="http://www.topografix.com/GPX/1/1"\n' 459 + ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n' 460 + ' xsi:schemaLocation="http://www.topografix.com/GPX/1/1\n' 461 + ' http://www.topografix.com/GPX/1/1/gpx.xsd">\n' 462 ; 463 464 // store named waypoints 465 var leg = this.firstLeg; 466 while (leg != null) { 467 if (leg.name != null) { 468 xml = xml 469 + '<wpt lat="' + leg.latlng.lat.toFixed(5) + '" lon="' + leg.latlng.lng.toFixed(5) + '">\n' 470 + '\t<name>' + leg.name + '</name>\n' 471 + '</wpt>\n'; 472 } 473 leg = leg.next; 474 } 475 476 var ptBase = parseInt(document.getElementById('ptbase').value); 477 var ptName = ""; 478 479 xml = xml 480 + '<' + xmlEl + '>\n' 481 + '\t<name>' + routename + '</name>\n' 482 + xmlSegOn + '\n' 483 ; 484 leg = this.firstLeg; 485 while (leg != null) { 486 xml += '\t<' + xmlEl + 'pt lat="' + leg.latlng.lat.toFixed(5) + '" lon="' + leg.latlng.lng.toFixed(5) + '"'; 487 ptName = leg.name; 488 if (ptName == null) { 489 ptBase++; 490 ptName = "I" + ptBase; 491 } 492 xml += '>\n\t\t<name>' + ptName + '</name>\n' + '\t</' + xmlEl + 'pt>\n'; 493 494 leg = leg.next; 495 } 496 xml += xmlSegOff + '\n' 497 + '</' + xmlEl + '>\n</gpx>\n'; 498 499 return xml; 500 }, 501 // send gpx to client 502 save: function () { 503 var routename = document.getElementById('routename').value; 504 var gpx = this.getRouteGPX(routename, document.getElementById('trackoption').value == "route"); 505 506 //gpx = gpx.replace(/</g, '<').replace(/>/g, '>'); 507 508 var element = document.createElement('a'); 509 element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(gpx)); 510 element.setAttribute('download', routename + ".gpx"); 511 512 element.style.display = 'none'; 513 document.body.appendChild(element); 514 515 element.click(); 516 517 document.body.removeChild(element); 518 }, 519 520 getRoute: function () { 521 var points = []; 522 var leg = this.firstLeg; 523 while (leg != null) { 524 points.push(new GpxPoint(leg.latlng.lat, leg.latlng.lng)); 525 leg = leg.next; 526 } 527 528 return points; 529 }, 530 getEncodedRoute: function () { 531 var route = ""; 532 var leg = this.firstLeg; 533 while (leg != null) { 534 if (route.length > 5) 535 route += "|"; 536 //route += leg.latlng.lat.toFixed(5) + ',' + leg.latlng.lng.toFixed(5); 537 route += encodeCoordinate(leg.latlng.lat) + ',' + encodeCoordinate(leg.latlng.lng); 538 leg = leg.next; 539 } 540 541 return route; 542 }, 543 544 getTracklength: function (limit) { 545 var dist = 0; 546 var leg = this.firstLeg; 547 while (leg != null && leg.next != null && leg != limit) { 548 dist = dist + leg.latlng.distanceTo(leg.next.latlng); 549 leg = leg.next; 550 } 551 552 return dist; 553 }, 554 555 findEditorPointAt: function( distance ) 556 { 557 var dist = 0; 558 var leg = this.firstLeg; 559 while (leg != null && leg.next != null) { 560 const legDist = leg.latlng.distanceTo(leg.next.latlng); 561 dist = dist + legDist; 562 if( dist > distance ) 563 { 564 const partial = (distance-dist-legDist) / legDist; 565 return L.latLng( 566 leg.latlng.lat * partial + leg.next.latlng.lat * (1-partial), 567 leg.latlng.lng * partial + leg.next.latlng.lng * (1-partial) 568 ) 569 } 570 leg = leg.next; 571 } 572 return null; 573 }, 574 575 isEmpty: function (doAlert) { 576 if (this.firstLeg == null || this.firstLeg == this.lastLeg) { 577 if (doAlert) 578 alert(translations.editor_warn_empty) 579 return true; 580 } 581 return false; 582 }, 583 584 enable: function (activate) { 585 586 this.active = activate; 587 this.closePopup(); 588 589 var leg = this.firstLeg; 590 while (leg != null) { 591 leg.enable(activate); 592 leg = leg.next; 593 } 594 } 595}); 596 597const gpxEditor = new Editor(); 598 599let currentTrackLength = 0; 600function updateTracklength(distance) { 601 602 if (distance == null) { 603 if (nav.active && !gpxEditor.isEmpty()) 604 return; 605 else 606 distance = gpxEditor.getTracklength(); 607 } 608 609 var e = document.getElementById("tracklength"); 610 if (distance < 0.01) { 611 e.innerHTML = translations.route_distance + ": --"; 612 currentTrackLength = 0; 613 } else { 614 e.innerHTML = translations.route_distance + ": " + (distance / 1000).toFixed(2) + " km"; 615 currentTrackLength = distance; 616 } 617} 618 619
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.