index.js (8272B)
1 import L from 'leaflet'; 2 import ko from 'knockout'; 3 import './coordinates.css'; 4 import copyToClipboard from '~/lib/clipboardCopy'; 5 import Contextmenu from '~/lib/contextmenu'; 6 import {makeButtonWithBar} from '~/lib/leaflet.control.commons'; 7 import safeLocalStorage from '~/lib/safe-localstorage'; 8 import '~/lib/controls-styles/controls-styles.css'; 9 import {ElevationLayer} from '~/lib/leaflet.layer.elevation-display'; 10 import * as formats from './formats'; 11 12 const DEFAULT_FORMAT = formats.DEGREES; 13 const UNKNOWN_COORDINATES = { 14 lat: '-------', 15 lng: '-------' 16 }; 17 18 L.Control.Coordinates = L.Control.extend({ 19 options: { 20 position: 'bottomleft' 21 }, 22 23 includes: L.Mixin.Events, 24 25 formats: [ 26 formats.SIGNED_DEGREES, 27 formats.DEGREES, 28 formats.DEGREES_AND_MINUTES, 29 formats.DEGREES_AND_MINUTES_AND_SECONDS 30 ], 31 32 initialize: function(elevationTilesUrl, options) { 33 L.Control.prototype.initialize.call(this, options); 34 35 this.elevationDisplayLayer = new ElevationLayer(elevationTilesUrl); 36 this.displayElevation = ko.observable(true); 37 38 this.latlng = ko.observable(); 39 this.format = ko.observable(DEFAULT_FORMAT); 40 this.formatCode = ko.pureComputed({ 41 read: () => this.format().code, 42 write: (value) => { 43 for (let format of this.formats) { 44 if (value === format.code) { 45 this.format(format); 46 break; 47 } 48 } 49 } 50 }); 51 52 this.formattedCoordinates = ko.pureComputed(() => { 53 if (this.latlng()) { 54 return formats.formatLatLng(this.latlng().wrap(), this.format()); 55 } 56 return UNKNOWN_COORDINATES; 57 }, this); 58 59 this.wrapperClass = ko.pureComputed(() => this.format().wrapperClass, this); 60 61 this.formatCode.subscribe(this.saveFormatStateToStorage, this); 62 this.displayElevation.subscribe(this.onDisplayElevationChange, this); 63 }, 64 65 onAdd: function(map) { 66 const {container, link, barContainer} = makeButtonWithBar( 67 'leaflet-contol-coordinates', 68 'Show coordinates at cursor', 69 'icon-coordinates' 70 ); 71 72 this._map = map; 73 this._container = container; 74 this._link = link; 75 76 barContainer.innerHTML = ` 77 <div data-bind="css: wrapperClass"> 78 <div class="leaflet-coordinates-container" data-bind="with: formattedCoordinates()"> 79 <span class="leaflet-coordinates-latitude" data-bind="html: lat"></span> 80 <span class="leaflet-coordinates-longitude" data-bind="html: lng"></span> 81 </div> 82 <hr class="leaflet-coordinates-divider" /> 83 <div data-bind="foreach: formats"> 84 <div> 85 <label title=""> 86 <input type="radio" data-bind="checked: $parent.formatCode, value: code" 87 class="leaflet-coordinates-format-radio"/> 88 <span data-bind="html: label"></span> 89 </label> 90 </div> 91 </div> 92 <hr class="leaflet-coordinates-divider" /> 93 <label title=""> 94 <input type="checkbox" 95 data-bind="checked: displayElevation" 96 class="leaflet-coordinates-elevation-toggle"/> 97 Display elevation 98 </label> 99 </div> 100 `; 101 102 barContainer.title = "Right click on map to copy coordinates"; 103 104 this.loadStateFromStorage(); 105 ko.applyBindings(this, container); 106 L.DomEvent.on(link, 'click', this.onClick, this); 107 this.setEnabled(false); 108 return container; 109 }, 110 111 onRemove: function() { 112 L.DomEvent.off(this._link, 'click', this.onClick, this); 113 }, 114 115 loadStateFromStorage: function() { 116 const code = safeLocalStorage.leafletCoordinatesFmt || DEFAULT_FORMAT.code; 117 const elevationDisplayed = (safeLocalStorage.leafletCoordinatesDisplayElevation ?? '1') === '1'; 118 119 this.formatCode(code); 120 this.displayElevation(elevationDisplayed); 121 }, 122 123 saveFormatStateToStorage: function() { 124 safeLocalStorage.leafletCoordinatesFmt = this.formatCode(); 125 }, 126 127 saveElevationDisplayStateToStorage: function() { 128 safeLocalStorage.leafletCoordinatesDisplayElevation = this.displayElevation() ? '1' : '0'; 129 }, 130 131 onMouseMove: function(e) { 132 this.latlng(e.latlng); 133 }, 134 135 setEnabled: function(enabled) { 136 if (Boolean(enabled) === this.isEnabled()) { 137 return; 138 } 139 const classFunc = enabled ? 'addClass' : 'removeClass'; 140 const eventFunc = enabled ? 'on' : 'off'; 141 L.DomUtil[classFunc](this._container, 'active'); 142 L.DomUtil[classFunc](this._container, 'highlight'); 143 L.DomUtil[classFunc](this._map._container, 'coordinates-control-active'); 144 this._map[eventFunc]('mousemove', this.onMouseMove, this); 145 this._map[eventFunc]('contextmenu', this.onMapRightClick, this); 146 this._map[enabled ? 'addLayer' : 'removeLayer'](this.elevationDisplayLayer); 147 this._isEnabled = Boolean(enabled); 148 this.latlng(null); 149 }, 150 151 onMapRightClick: function(e) { 152 L.DomEvent.stop(e); 153 function createItem(format, elevation, overrides = {}) { 154 const {lat, lng} = formats.formatLatLng(e.latlng.wrap(), format); 155 let text = `${lat} ${lng}`; 156 if (elevation !== null) { 157 text += ` H=${elevation} m`; 158 } 159 160 return {text: `${text} <span class="leaflet-coordinates-menu-fmt">${format.label}</span>`, 161 callback: () => copyToClipboard(text, e.originalEvent), 162 ...overrides}; 163 } 164 165 const items = [ 166 createItem( 167 this.format(), 168 null, 169 { 170 text: '<b>Copy coordinates to clipboard</b>', 171 header: true, 172 }, 173 ), 174 ...this.formats.map((format) => createItem(format, null)), 175 ]; 176 const elevationResult = this.elevationDisplayLayer.getElevation(e.latlng); 177 if (elevationResult.ready && !elevationResult.error && elevationResult.elevation !== null) { 178 const elevation = elevationResult.elevation; 179 items.push( 180 '-', 181 { 182 text: `Copy elevation to clipboard: ${elevation}`, 183 header: true, 184 callback: () => copyToClipboard(elevation, e.originalEvent), 185 }, 186 '-', 187 createItem( 188 this.format(), 189 elevation, 190 { 191 text: '<b>Copy coordinates with elevation to clipboard</b>', 192 header: true, 193 }, 194 ), 195 ...this.formats.map((format) => createItem(format, elevation)), 196 ); 197 } 198 new Contextmenu(items).show(e); 199 }, 200 201 isEnabled: function() { 202 return Boolean(this._isEnabled); 203 }, 204 205 onClick: function() { 206 this.setEnabled(!this.isEnabled()); 207 }, 208 209 onDisplayElevationChange: function(on) { 210 this.saveElevationDisplayStateToStorage(); 211 this.elevationDisplayLayer.enableElevationDisplay(on); 212 }, 213 } 214 );
