nakarte

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

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 );