nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit e2ca3031ebc2a93a1e5ba8e830710b84e6c59508
parent da47dd99b35106e59b855e8b275fd8a81fe77d3f
Author: Igor Sidorov <igor.sidorov@binarycode.ru>
Date:   Mon,  4 Jun 2018 13:48:30 +0300

Replace coordinates context menu with radio buttons

Diffstat:
Msrc/lib/leaflet.control.coordinates/coordinates.css | 4++--
Asrc/lib/leaflet.control.coordinates/formats.js | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.coordinates/index.js | 235+++++++++++++++++++++++++++++++------------------------------------------------
3 files changed, 181 insertions(+), 146 deletions(-)

diff --git a/src/lib/leaflet.control.coordinates/coordinates.css b/src/lib/leaflet.control.coordinates/coordinates.css @@ -13,4 +13,5 @@ .leaflet-coordinates-menu-fmt{ color: #aaa; margin-left: 0.5em; -} -\ No newline at end of file + float: right; +} diff --git a/src/lib/leaflet.control.coordinates/formats.js b/src/lib/leaflet.control.coordinates/formats.js @@ -0,0 +1,88 @@ +function pad(value, size, precision = 0) { + if (value < 0) { + return value.toFixed(precision); + } + + if (precision > 0) size++; + + return value.toFixed(precision).padStart(size + precision, '0'); +} + +function parseLatLng(signedDegrees, isLat) { + const degrees = Math.abs(signedDegrees); + const intDegrees = Math.floor(degrees); + const minutes = (degrees - intDegrees) * 60; + const intMinutes = Math.floor(minutes); + const seconds = (minutes - intMinutes) * 60; + + let direction + if (isLat) { + direction = (signedDegrees < 0) ? 'S' : 'N'; + } else { + direction = (signedDegrees < 0) ? 'W' : 'E'; + } + + const degreesPadding = isLat ? 2 : 3; + + return { + signedDegrees: pad(signedDegrees, degreesPadding, 5), + degrees: pad(degrees, degreesPadding, 5), + intDegrees: pad(intDegrees, degreesPadding), + minutes: pad(minutes, 2, 3), + intMinutes: pad(minutes, 2), + seconds: pad(seconds, 2, 2), + direction + }; +} + +function transform(latlng, format) { + return { + lat: format(parseLatLng(latlng.lat, true)), + lng: format(parseLatLng(latlng.lng, false)) + } +} + +const SIGNED_DEGREES = { + code: 'd', + label: '±ddd.ddddd', + process: (latlng) => { + return transform(latlng, ({signedDegrees}) => signedDegrees); + } +}; + +const DEGREES = { + code: 'D', + label: 'ddd.ddddd°', + process: (latlng) => { + return transform(latlng, ({degrees, direction}) => { + return `${direction} ${degrees}°`; + }); + } +}; + +const DEGREES_AND_MINUTES = { + code: 'DM', + label: 'ddd°mm.mmm′', + process: (latlng) => { + return transform(latlng, ({intDegrees, minutes, direction}) => { + return `${direction} ${intDegrees}°${minutes}′`; + }); + } +}; + +const DEGREES_AND_MINUTES_AND_SECONDS = { + code: 'DMS', + label: 'ddd°mm′ss.s″', + process: (latlng) => { + return transform(latlng, ({intDegrees, intMinutes, seconds, direction}) => { + return `${direction} ${intDegrees}°${intMinutes}′${seconds}″`; + }); + } +}; + +export default { + SIGNED_DEGREES, + DEGREES, + DEGREES_AND_MINUTES, + DEGREES_AND_MINUTES_AND_SECONDS +} diff --git a/src/lib/leaflet.control.coordinates/index.js b/src/lib/leaflet.control.coordinates/index.js @@ -1,141 +1,109 @@ import L from 'leaflet' +import ko from 'vendored/knockout'; import './coordinates.css'; import copyToClipboard from 'lib/clipboardCopy'; import Contextmenu from 'lib/contextmenu'; import {makeButtonWithBar} from 'lib/leaflet.control.commons'; import safeLocalStorage from 'lib/safe-localstorage'; import 'lib/controls-styles/controls-styles.css'; +import FORMATS from './formats'; - - -function pad(s, n) { - var j = s.indexOf('.'); - if (j === -1) { - j = s.length; - } - var zeroes = (n - j); - if (zeroes > 0) { - s = Array(zeroes + 1).join('0') + s; - } - return s; -} - -function mod(n, m) { - return ((n % m) + m) % m; -} - -function normalizeLongitude(lng) { - return mod(lng + 180, 360) - 180; -} +const DEFAULT_FORMAT = FORMATS.DEGREES; +const UNKNOWN_COORDINATES = { + lat: '-------', + lng: '-------' +}; L.Control.Coordinates = L.Control.extend({ options: { position: 'bottomleft' }, - formatNames: { - 'd': 'ddd.ddddd', - 'D': 'ddd.ddddd&deg', - 'DM': 'ddd&deg;mm.mmm\'', - 'DMS': 'ddd&deg;mm\'ss.s"' + formats: [ + FORMATS.SIGNED_DEGREES, + FORMATS.DEGREES, + FORMATS.DEGREES_AND_MINUTES, + FORMATS.DEGREES_AND_MINUTES_AND_SECONDS + ], + + initialize: function(options) { + L.Control.prototype.initialize.call(this, options); + + this.latlng = ko.observable(); + this.formatCode = ko.observable(DEFAULT_FORMAT.code); + + this.format = ko.pureComputed(() => { + for (let format of this.formats) { + if (format.code === this.formatCode()) return format; + } + return DEFAULT_FORMAT; + }, this); + + this.formattedCoordinates = ko.pureComputed(() => { + if (this.latlng()) { + return this.format().process(this.latlng().wrap()); + } + return UNKNOWN_COORDINATES; + }, this); + + this.formatCode.subscribe((_) => { + this.saveStateToStorage(); + }, this); }, onAdd: function(map) { - this._map = map; const {container, link, barContainer} = makeButtonWithBar( - 'leaflet-contol-coordinates', 'Show coordinates at cursor', 'icon-coordinates'); - this._container = container; + 'leaflet-contol-coordinates', + 'Show coordinates at cursor', + 'icon-coordinates' + ); - this._coordinatesDisplayContainer = L.DomUtil.create('span', '', barContainer); + this._map = map; + this._container = container; + this._link = link; + + barContainer.innerHTML = ` + <div data-bind="with: formattedCoordinates()"> + <span data-bind="html: lat"></span> + &nbsp; + <span data-bind="html: lng"></span> + </div> + <div data-bind="foreach: formats"> + <div> + <label> + <input type="radio" data-bind="checked: $parent.formatCode, value: code" /> + <span data-bind="html: label"></span> + </label> + </div> + </div> + `; - L.DomEvent.on(link, 'click', this.onClick, this); - this.menu = new Contextmenu([ - {text: 'Click map to copy coordinates to clipboard', callback: this.prepareForClickOnMap.bind(this)}, - '-', - {text: '&plusmn;ddd.ddddd', callback: this.onMenuSelect.bind(this, 'd')}, - {text: 'ddd.ddddd&deg;', callback: this.onMenuSelect.bind(this, 'D')}, - {text: 'ddd&deg;mm.mmm\'', callback: this.onMenuSelect.bind(this, 'DM')}, - {text: 'ddd&deg;mm\'ss.s"', callback: this.onMenuSelect.bind(this, 'DMS')} - ] - ); this.loadStateFromStorage(); - this.onMouseMove(); - L.DomEvent.on(barContainer, 'contextmenu', this.onRightClick, this); + ko.applyBindings(this, container); + L.DomEvent.on(link, 'click', this.onClick, this); + return container; }, + onRemove: function() { + L.DomEvent.off(this._link, 'click', this.onClick, this); + }, + loadStateFromStorage: function() { - var active = false, - fmt = 'D'; - active = safeLocalStorage.leafletCoordinatesActive === '1'; - fmt = safeLocalStorage.leafletCoordinatesFmt || fmt; + const active = safeLocalStorage.leafletCoordinatesActive === '1'; + const code = safeLocalStorage.leafletCoordinatesFmt || DEFAULT_FORMAT.code; + + this.formatCode(code); this.setEnabled(active); - this.setFormat(fmt); }, saveStateToStorage: function() { safeLocalStorage.leafletCoordinatesActive = this.isEnabled() ? '1' : '0'; - safeLocalStorage.leafletCoordinatesFmt = this.fmt; - }, - - formatCoodinate: function(value, isLat, fmt) { - if (value === undefined || isNaN(value)) { - return '-------'; - } - fmt = fmt || this.fmt; - var h, d, m, s; - if (isLat) { - h = (value < 0) ? 'S' : 'N'; - } else { - h = (value < 0) ? 'W' : 'E'; - } - if (fmt === 'd') { - d = value.toFixed(5); - d = pad(d, isLat ? 2 : 3); - return d; - } - - value = Math.abs(value); - if (fmt === 'D') { - d = value.toFixed(5); - d = pad(d, isLat ? 2 : 3); - return `${h} ${d}&deg;`; - } - if (fmt === 'DM') { - d = Math.floor(value).toString(); - d = pad(d, isLat ? 2 : 3); - m = ((value - d) * 60).toFixed(3); - m = pad(m, 2); - return `${h} ${d}&deg;${m}'` - } - if (fmt === 'DMS') { - d = Math.floor(value).toString(); - d = pad(d, isLat ? 2 : 3); - m = Math.floor((value - d) * 60).toString(); - m = pad(m, 2); - s = ((value - d - m / 60) * 3600).toFixed(2); - s = pad(s, 2); - return `${h} ${d}&deg;${m}'${s}"`; - } - }, - - onMenuSelect: function(fmt) { - this.setFormat(fmt); - this.saveStateToStorage(); - }, - - setFormat: function(fmt) { - this.fmt = fmt; - this.onMouseMove(); + safeLocalStorage.leafletCoordinatesFmt = this.formatCode(); }, onMouseMove: function(e) { - var lat, lng; - if (e) { - ({lat, lng} = e.latlng); - } - lng = normalizeLongitude(lng); - this._coordinatesDisplayContainer.innerHTML = this.formatCoodinate(lat, true) + '&nbsp;&nbsp;&nbsp;' + this.formatCoodinate(lng, false); + this.latlng(e.latlng); }, setEnabled: function(enabled) { @@ -154,25 +122,25 @@ L.Control.Coordinates = L.Control.extend({ onMapRightClick: function(e) { L.DomEvent.stop(e); - const items = [{text: '<b>Copy coordinates to clipboard</b>', header: true}, '-']; - - const lat = e.latlng.lat, - lng = normalizeLongitude(e.latlng.lng); - - for (let fmt of ['d', 'D', 'DM', 'DMS']) { - let strLat = this.formatCoodinate(lat, true, fmt); - let strLng = this.formatCoodinate(lng, false, fmt); - let s = `${strLat} ${strLng}`; - items.push({ - text: `${s} <span class="leaflet-coordinates-menu-fmt">${this.formatNames[fmt]}</span>`, - callback: () => { - copyToClipboard(s.replace(/&deg;/g, '°'), e.originalEvent); - } - } - ) + + const createItem = (format, options = {}) => { + const {lat, lng} = format.process(e.latlng.wrap()); + const coordinates = `${lat} ${lng}`; + + return Object.assign({ + text: `${coordinates} <span class="leaflet-coordinates-menu-fmt">${format.label}</span>`, + callback: () => copyToClipboard(coordinates, e.originalEvent) + }, options); } - const menu = new Contextmenu(items); - menu.show(e); + + const header = createItem(this.format(), { + text: '<b>Copy coordinates to clipboard</b>', + header: true, + }); + const items = this.formats.map((format) => createItem(format)); + items.unshift(header, '-'); + + new Contextmenu(items).show(e); }, isEnabled: function() { @@ -182,27 +150,6 @@ L.Control.Coordinates = L.Control.extend({ onClick: function(e) { this.setEnabled(!this.isEnabled()); this.saveStateToStorage(); - this.onMouseMove(); - }, - - onRightClick: function(e) { - this.menu.show(e); - }, - - onMapClick: function(e) { - var s = this.formatCoodinate(e.latlng.lat, true) + ' ' + this.formatCoodinate(e.latlng.lng, false); - s = s.replace(/&deg;/g, '°'); - copyToClipboard(s, e.originalEvent); - }, - - prepareForClickOnMap: function() { - this._map.once('click', this.onMapClick, this); } - - - - // TODO: onRemove - } ); -