nakarte

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

commit 76251795ee71edbbb8eea2f79ca08770b820f175
parent a034447a440a15013cc93346c93fc9466181f65c
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Tue,  3 Jul 2018 01:13:08 +0300

Merge branch 'binarycode-coordinates'

Diffstat:
Msrc/lib/contextmenu/contextmenu.css | 18++++++++----------
Msrc/lib/leaflet.control.coordinates/coordinates.css | 44++++++++++++++++++++++++++++++++++++++++----
Asrc/lib/leaflet.control.coordinates/formats.js | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.control.coordinates/index.js | 250+++++++++++++++++++++++++++++++++----------------------------------------------
4 files changed, 229 insertions(+), 159 deletions(-)

diff --git a/src/lib/contextmenu/contextmenu.css b/src/lib/contextmenu/contextmenu.css @@ -1,6 +1,6 @@ .contextmenu { position: fixed; - background-color: #fff; + background-color: white; font: 12px/1.5 "Helvetica Neue",Arial,Helvetica,sans-serif; box-shadow: 0 1px 7px rgba(0,0,0,0.4); -webkit-border-radius: 4px; @@ -14,7 +14,7 @@ .contextmenu .item { display: block; - color: #222; + color: hsl(0, 0%, 15%); font-size: 12px; line-height: 16px; height: 16px; @@ -26,30 +26,28 @@ } .contextmenu .item:hover { - background-color: #f4f4f4; - border-top: 1px solid #f0f0f0; - border-bottom: 1px solid #f0f0f0; + background-color: hsl(0, 0%, 95%); } .contextmenu .separator span { background-color: white; padding: 0 0.2em; - color: #888; + color: hsl(0, 0%, 50%); margin: 0 1em; } .contextmenu .separator { - border-bottom: 1px solid #ccc; + border-bottom: 1px solid hsl(0, 0%, 80%); text-align: center; width: 100%; - line-height: 1px; + line-height: 0; margin: 8px 0; } .contextmenu .disabled { - color: #aaa; + color: hsl(0, 0%, 66%); } .contextmenu .header { - color: #555555; + color: hsl(0, 0%, 20%); font-weight: bold; } \ No newline at end of file diff --git a/src/lib/leaflet.control.coordinates/coordinates.css b/src/lib/leaflet.control.coordinates/coordinates.css @@ -10,7 +10,44 @@ cursor: crosshair; } -.leaflet-coordinates-menu-fmt{ - color: #aaa; +.leaflet-coordinates-menu-fmt { + color: hsl(0, 0%, 66%); margin-left: 0.5em; -} -\ No newline at end of file + float: right; +} + +.leaflet-coordinates-wrapper-signed-degrees { + min-width: 11em; +} + +.leaflet-coordinates-wrapper-degrees { + min-width: 14em; +} + +.leaflet-coordinates-wrapper-degrees-and-minutes { + min-width: 14.5em; +} + +.leaflet-coordinates-wrapper-degrees-and-minutes-and-seconds { + min-width: 16em; +} + +.leaflet-coordinates-container { + padding: 0 5px; +} + +.leaflet-coordinates-longitude { + float: right; +} + +.leaflet-coordinates-format-radio { + margin-top: -1px; + vertical-align: middle; +} + +.leaflet-coordinates-divider { + border: none; + border-top: 1px solid hsl(0, 0%, 92%); + margin-top: 0px; + margin-bottom: 3px; +} diff --git a/src/lib/leaflet.control.coordinates/formats.js b/src/lib/leaflet.control.coordinates/formats.js @@ -0,0 +1,76 @@ +function formatNumber(value, size, precision = 0) { + if (value < 0) { + return value.toFixed(precision); + } + + if (precision > 0) size++; + + return value.toFixed(precision).padStart(size + precision, '0'); +} + +function coordinatePresentations(coordinate, isLat) { + const degrees = Math.abs(coordinate); + 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 = (coordinate < 0) ? 'S' : 'N'; + } else { + direction = (coordinate < 0) ? 'W' : 'E'; + } + + return { + signedDegrees: formatNumber(coordinate, 0, 5), + degrees: formatNumber(degrees, 0, 5), + intDegrees: formatNumber(intDegrees, 0), + minutes: formatNumber(minutes, 2, 3), + intMinutes: formatNumber(intMinutes, 2), + seconds: formatNumber(seconds, 2, 2), + direction + }; +} + +function formatLatLng(latlng, format) { + return { + lat: format.formatter(coordinatePresentations(latlng.lat, true)), + lng: format.formatter(coordinatePresentations(latlng.lng, false)) + } +} + +const SIGNED_DEGREES = { + code: 'd', + label: '±ddd.ddddd', + wrapperClass: 'leaflet-coordinates-wrapper-signed-degrees', + formatter: ({signedDegrees}) => signedDegrees +}; + +const DEGREES = { + code: 'D', + label: 'ddd.ddddd°', + wrapperClass: 'leaflet-coordinates-wrapper-degrees', + formatter: ({degrees, direction}) => `${direction} ${degrees}°` +}; + +const DEGREES_AND_MINUTES = { + code: 'DM', + label: 'ddd°mm.mmm′', + wrapperClass: 'leaflet-coordinates-wrapper-degrees-and-minutes', + formatter: ({intDegrees, minutes, direction}) => `${direction} ${intDegrees}°${minutes}′` +}; + +const DEGREES_AND_MINUTES_AND_SECONDS = { + code: 'DMS', + label: 'ddd°mm′ss.s″', + wrapperClass: 'leaflet-coordinates-wrapper-degrees-and-minutes-and-seconds', + formatter: ({intDegrees, intMinutes, seconds, direction}) => `${direction} ${intDegrees}°${intMinutes}′${seconds}″`}; + +export default { + SIGNED_DEGREES, + DEGREES, + DEGREES_AND_MINUTES, + DEGREES_AND_MINUTES_AND_SECONDS, + formatLatLng +} diff --git a/src/lib/leaflet.control.coordinates/index.js b/src/lib/leaflet.control.coordinates/index.js @@ -1,141 +1,121 @@ 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.format = ko.observable(DEFAULT_FORMAT); + this.formatCode = ko.pureComputed({ + read: () => this.format().code, + write: (value) => { + for (let format of this.formats) { + if (value === format.code) { + this.format(format); + break + } + } + } + }); + + this.formattedCoordinates = ko.pureComputed(() => { + if (this.latlng()) { + return formats.formatLatLng(this.latlng().wrap(), this.format()); + } + return UNKNOWN_COORDINATES; + }, this); + + this.wrapperClass = ko.pureComputed(() => { + return this.format().wrapperClass; + }, 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="css: wrapperClass"> + <div class="leaflet-coordinates-container" data-bind="with: formattedCoordinates()"> + <span class="leaflet-coordinates-latitude" data-bind="html: lat"></span> + <span class="leaflet-coordinates-longitude" data-bind="html: lng"></span> + </div> + <hr class="leaflet-coordinates-divider" /> + <div data-bind="foreach: formats"> + <div> + <label title=""> + <input type="radio" data-bind="checked: $parent.formatCode, value: code" class="leaflet-coordinates-format-radio"/> + <span data-bind="html: label"></span> + </label> + </div> + </div> + </div> + `; + + barContainer.title = "Right click on map to copy coordinates"; - 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) { @@ -150,29 +130,30 @@ L.Control.Coordinates = L.Control.extend({ this._map[eventFunc]('mousemove', this.onMouseMove, this); this._map[eventFunc]('contextmenu', this.onMapRightClick, this); this._isEnabled = !!enabled; + this.latlng(null); }, 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 menu = new Contextmenu(items); - menu.show(e); + + const createItem = (format, options = {}) => { + const {lat, lng} = formats.formatLatLng(e.latlng.wrap(), format); + 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 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 +163,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 - } ); -