nakarte

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

index.js (9298B)


      1 import L from 'leaflet';
      2 import ko from 'knockout';
      3 import {makeButtonWithBar} from '~/lib/leaflet.control.commons';
      4 import layout from './control.html';
      5 import '~/lib/controls-styles/controls-styles.css';
      6 import './style.css';
      7 import {getDeclination} from '~/lib/magnetic-declination';
      8 import 'leaflet-rotatedmarker'; // eslint-disable-line import/no-unassigned-import
      9 import iconPointer from './pointer.svg';
     10 import iconPointerStart from './pointer-start.svg';
     11 import iconPointerEnd from './pointer-end.svg';
     12 import {ElevationProfile, calcSamplingInterval} from '~/lib/leaflet.control.elevation-profile';
     13 
     14 function radians(x) {
     15     return x / 180 * Math.PI;
     16 }
     17 
     18 function degrees(x) {
     19     return x / Math.PI * 180;
     20 }
     21 
     22 function calcAzimuth(latlng1, latlng2) {
     23     const lat1 = radians(latlng1.lat);
     24     const lat2 = radians(latlng2.lat);
     25     const lng1 = radians(latlng1.lng);
     26     const lng2 = radians(latlng2.lng);
     27 
     28     const y = Math.sin(lng2 - lng1) * Math.cos(lat2);
     29     const x = Math.cos(lat1) * Math.sin(lat2) -
     30         Math.sin(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1);
     31     let brng = Math.atan2(y, x);
     32     brng = degrees(brng);
     33     return brng;
     34 }
     35 
     36 function calcAngle(latlng1, latlng2) {
     37     const p1 = L.Projection.SphericalMercator.project(latlng1);
     38     const p2 = L.Projection.SphericalMercator.project(latlng2);
     39     const delta = p2.subtract(p1);
     40     const angle = Math.atan2(delta.x, delta.y);
     41     return degrees(angle);
     42 }
     43 
     44 function roundAzimuth(a) {
     45     return (Math.round(a) + 360) % 360;
     46 }
     47 
     48 L.Control.Azimuth = L.Control.extend({
     49         options: {
     50             position: 'bottomleft'
     51         },
     52 
     53         includes: L.Mixin.Events,
     54 
     55         initialize: function(options) {
     56             L.Control.prototype.initialize.call(this, options);
     57             this.trueAzimuth = ko.observable(null);
     58             this.magneticAzimuth = ko.observable(null);
     59             this.distance = ko.observable(null);
     60             this.points = {
     61                 start: null,
     62                 end: null
     63             };
     64             const iconSingle = L.icon({iconUrl: iconPointer, iconSize: [30, 30]});
     65             const iconStart = L.icon({iconUrl: iconPointerStart, iconSize: [30, 30]});
     66             const iconEnd = L.icon({iconUrl: iconPointerEnd, iconSize: [30, 45]});
     67             this.azimuthLine = L.polyline([], {interactive: false, weight: 1.5});
     68             this.markers = {
     69                 single: L.marker([0, 0], {icon: iconSingle, draggable: true, which: 'start'})
     70                     .on('drag', this.onMarkerDrag, this)
     71                     .on('click', L.DomEvent.stopPropagation),
     72                 start: L.marker([0, 0], {
     73                     icon: iconStart,
     74                     draggable: true,
     75                     which: 'start',
     76                     rotationOrigin: 'center center',
     77                     projectedShift: () => this.azimuthLine.shiftProjectedFitMapView()
     78                 })
     79                     .on('drag', this.onMarkerDrag, this)
     80                     .on('click', L.DomEvent.stopPropagation)
     81                     .on('dragend', this.onMarkerDragEnd, this),
     82                 end: L.marker([0, 0], {
     83                     icon: iconEnd,
     84                     draggable: true,
     85                     which: 'end',
     86                     rotationOrigin: 'center center',
     87                     projectedShift: () => this.azimuthLine.shiftProjectedFitMapView()
     88                 })
     89                     .on('drag', this.onMarkerDrag, this)
     90                     .on('click', L.DomEvent.stopPropagation)
     91                     .on('dragend', this.onMarkerDragEnd, this)
     92             };
     93         },
     94 
     95         onAdd: function(map) {
     96             this._map = map;
     97             const {container, link, barContainer} = makeButtonWithBar(
     98                 'leaflet-control-azimuth', 'Measure bearing, display line of sight', 'icon-azimuth');
     99             this._container = container;
    100             L.DomEvent.on(link, 'click', this.onClick, this);
    101 
    102             barContainer.innerHTML = layout;
    103             ko.applyBindings(this, barContainer);
    104             return container;
    105         },
    106 
    107         onClick: function() {
    108             if (this.isEnabled()) {
    109                 this.disableControl();
    110             } else {
    111                 this.enableControl();
    112             }
    113         },
    114 
    115         onMarkerDrag: function(e) {
    116             const marker = e.target;
    117             this.setPoints({[marker.options.which]: marker.getLatLng()});
    118         },
    119 
    120         onMarkerDragEnd: function() {
    121             if (this.elevationControl) {
    122                 this.showProfile();
    123             }
    124         },
    125 
    126         enableControl: function() {
    127             L.DomUtil.addClass(this._container, 'active');
    128             L.DomUtil.addClass(this._container, 'highlight');
    129             L.DomUtil.addClass(this._map._container, 'azimuth-control-active');
    130             this._map.on('click', this.onMapClick, this);
    131             this.fire('enabled');
    132             this._map.clickLocked = true;
    133             this._enabled = true;
    134         },
    135 
    136         disableControl: function() {
    137             L.DomUtil.removeClass(this._container, 'active');
    138             L.DomUtil.removeClass(this._container, 'highlight');
    139             this.hideProfile();
    140             this.setPoints({start: null, end: null});
    141             L.DomUtil.removeClass(this._map._container, 'azimuth-control-active');
    142             this._map.off('click', this.onMapClick, this);
    143             this._map.clickLocked = false;
    144             this._enabled = false;
    145         },
    146 
    147         isEnabled: function() {
    148             return Boolean(this._enabled);
    149         },
    150 
    151         setPoints: function(points) {
    152             Object.assign(this.points, points);
    153             points = this.points;
    154             if (points.start && !points.end) {
    155                 this.markers.single
    156                     .setLatLng(points.start)
    157                     .addTo(this._map);
    158             } else {
    159                 this.markers.single.removeFrom(this._map);
    160             }
    161             if (points.start && points.end) {
    162                 const angle = calcAngle(points.start, points.end);
    163                 this.markers.start
    164                     .setLatLng(points.start)
    165                     .addTo(this._map)
    166                     .setRotationAngle(angle);
    167                 this.markers.end
    168                     .setLatLng(points.end)
    169                     .addTo(this._map)
    170                     .setRotationAngle(angle);
    171                 this.azimuthLine
    172                     .setLatLngs([[points.start, points.end]])
    173                     .addTo(this._map);
    174             } else {
    175                 this.markers.start.removeFrom(this._map);
    176                 this.markers.end.removeFrom(this._map);
    177                 this.azimuthLine.removeFrom(this._map);
    178             }
    179             this.updateValuesDisplay();
    180         },
    181 
    182         updateValuesDisplay: function() {
    183             if (this.points.start && this.points.end) {
    184                 const points = this.points;
    185                 const azimuth = calcAzimuth(points.start, points.end);
    186                 this.trueAzimuth(roundAzimuth(azimuth));
    187                 const declination = getDeclination(points.start.lat, points.start.lng);
    188                 if (declination === null) {
    189                     this.magneticAzimuth(null);
    190                 } else {
    191                     this.magneticAzimuth(roundAzimuth(azimuth - declination));
    192                 }
    193                 this.distance(points.start.distanceTo(points.end));
    194             } else {
    195                 this.distance(null);
    196                 this.trueAzimuth(null);
    197                 this.magneticAzimuth(null);
    198             }
    199         },
    200 
    201         onMapClick: function(e) {
    202             if (!this.points.start && !this.points.end) {
    203                 this.setPoints({start: e.latlng});
    204             } else if (this.points.start && !this.points.end) {
    205                 this.setPoints({end: e.latlng});
    206             } else if (this.points.start && this.points.end) {
    207                 this.hideProfile();
    208                 this.setPoints({start: e.latlng, end: null});
    209             }
    210         },
    211 
    212         showProfile: function() {
    213             if (!this.points.end) {
    214                 return;
    215             }
    216             if (this.elevationControl) {
    217                 this.elevationControl.removeFrom(this._map);
    218             }
    219 
    220             const dist = this.points.start.distanceTo(this.points.end);
    221             this.elevationControl = new ElevationProfile(this._map, [this.points.start, this.points.end], {
    222                 samplingInterval: calcSamplingInterval(dist),
    223                 sightLine: true
    224             });
    225             this.elevationControl.on('remove', () => {
    226                 this.elevationControl = null;
    227             });
    228             this.fire('elevation-shown');
    229         },
    230 
    231         hideProfile: function() {
    232             if (this.elevationControl) {
    233                 this.elevationControl.removeFrom(this._map);
    234             }
    235             this.elevationControl = null;
    236         },
    237 
    238         onProfileButtonClick: function() {
    239             this.showProfile();
    240         },
    241 
    242         onReverseButtonClick: function() {
    243             if (this.points.end && this.points.start) {
    244                 this.setPoints({start: this.points.end, end: this.points.start});
    245                 if (this.elevationControl) {
    246                     this.showProfile();
    247                 }
    248             }
    249         }
    250 
    251     }
    252 );
    253