nakarte

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

index.js (9373B)


      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, magneticModelInfo} 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.magneticModelInfo = magneticModelInfo;
     61             this.points = {
     62                 start: null,
     63                 end: null
     64             };
     65             const iconSingle = L.icon({iconUrl: iconPointer, iconSize: [30, 30]});
     66             const iconStart = L.icon({iconUrl: iconPointerStart, iconSize: [30, 30]});
     67             const iconEnd = L.icon({iconUrl: iconPointerEnd, iconSize: [30, 45]});
     68             this.azimuthLine = L.polyline([], {interactive: false, weight: 1.5});
     69             this.markers = {
     70                 single: L.marker([0, 0], {icon: iconSingle, draggable: true, which: 'start'})
     71                     .on('drag', this.onMarkerDrag, this)
     72                     .on('click', L.DomEvent.stopPropagation),
     73                 start: L.marker([0, 0], {
     74                     icon: iconStart,
     75                     draggable: true,
     76                     which: 'start',
     77                     rotationOrigin: 'center center',
     78                     projectedShift: () => this.azimuthLine.shiftProjectedFitMapView()
     79                 })
     80                     .on('drag', this.onMarkerDrag, this)
     81                     .on('click', L.DomEvent.stopPropagation)
     82                     .on('dragend', this.onMarkerDragEnd, this),
     83                 end: L.marker([0, 0], {
     84                     icon: iconEnd,
     85                     draggable: true,
     86                     which: 'end',
     87                     rotationOrigin: 'center center',
     88                     projectedShift: () => this.azimuthLine.shiftProjectedFitMapView()
     89                 })
     90                     .on('drag', this.onMarkerDrag, this)
     91                     .on('click', L.DomEvent.stopPropagation)
     92                     .on('dragend', this.onMarkerDragEnd, this)
     93             };
     94         },
     95 
     96         onAdd: function(map) {
     97             this._map = map;
     98             const {container, link, barContainer} = makeButtonWithBar(
     99                 'leaflet-control-azimuth', 'Measure bearing, display line of sight', 'icon-azimuth');
    100             this._container = container;
    101             L.DomEvent.on(link, 'click', this.onClick, this);
    102 
    103             barContainer.innerHTML = layout;
    104             ko.applyBindings(this, barContainer);
    105             return container;
    106         },
    107 
    108         onClick: function() {
    109             if (this.isEnabled()) {
    110                 this.disableControl();
    111             } else {
    112                 this.enableControl();
    113             }
    114         },
    115 
    116         onMarkerDrag: function(e) {
    117             const marker = e.target;
    118             this.setPoints({[marker.options.which]: marker.getLatLng()});
    119         },
    120 
    121         onMarkerDragEnd: function() {
    122             if (this.elevationControl) {
    123                 this.showProfile();
    124             }
    125         },
    126 
    127         enableControl: function() {
    128             L.DomUtil.addClass(this._container, 'active');
    129             L.DomUtil.addClass(this._container, 'highlight');
    130             L.DomUtil.addClass(this._map._container, 'azimuth-control-active');
    131             this._map.on('click', this.onMapClick, this);
    132             this.fire('enabled');
    133             this._map.clickLocked = true;
    134             this._enabled = true;
    135         },
    136 
    137         disableControl: function() {
    138             L.DomUtil.removeClass(this._container, 'active');
    139             L.DomUtil.removeClass(this._container, 'highlight');
    140             this.hideProfile();
    141             this.setPoints({start: null, end: null});
    142             L.DomUtil.removeClass(this._map._container, 'azimuth-control-active');
    143             this._map.off('click', this.onMapClick, this);
    144             this._map.clickLocked = false;
    145             this._enabled = false;
    146         },
    147 
    148         isEnabled: function() {
    149             return Boolean(this._enabled);
    150         },
    151 
    152         setPoints: function(points) {
    153             Object.assign(this.points, points);
    154             points = this.points;
    155             if (points.start && !points.end) {
    156                 this.markers.single
    157                     .setLatLng(points.start)
    158                     .addTo(this._map);
    159             } else {
    160                 this.markers.single.removeFrom(this._map);
    161             }
    162             if (points.start && points.end) {
    163                 const angle = calcAngle(points.start, points.end);
    164                 this.markers.start
    165                     .setLatLng(points.start)
    166                     .addTo(this._map)
    167                     .setRotationAngle(angle);
    168                 this.markers.end
    169                     .setLatLng(points.end)
    170                     .addTo(this._map)
    171                     .setRotationAngle(angle);
    172                 this.azimuthLine
    173                     .setLatLngs([[points.start, points.end]])
    174                     .addTo(this._map);
    175             } else {
    176                 this.markers.start.removeFrom(this._map);
    177                 this.markers.end.removeFrom(this._map);
    178                 this.azimuthLine.removeFrom(this._map);
    179             }
    180             this.updateValuesDisplay();
    181         },
    182 
    183         updateValuesDisplay: function() {
    184             if (this.points.start && this.points.end) {
    185                 const points = this.points;
    186                 const azimuth = calcAzimuth(points.start, points.end);
    187                 this.trueAzimuth(roundAzimuth(azimuth));
    188                 const declination = getDeclination(points.start.lat, points.start.lng);
    189                 if (declination === null) {
    190                     this.magneticAzimuth(null);
    191                 } else {
    192                     this.magneticAzimuth(roundAzimuth(azimuth - declination));
    193                 }
    194                 this.distance(points.start.distanceTo(points.end));
    195             } else {
    196                 this.distance(null);
    197                 this.trueAzimuth(null);
    198                 this.magneticAzimuth(null);
    199             }
    200         },
    201 
    202         onMapClick: function(e) {
    203             if (!this.points.start && !this.points.end) {
    204                 this.setPoints({start: e.latlng});
    205             } else if (this.points.start && !this.points.end) {
    206                 this.setPoints({end: e.latlng});
    207             } else if (this.points.start && this.points.end) {
    208                 this.hideProfile();
    209                 this.setPoints({start: e.latlng, end: null});
    210             }
    211         },
    212 
    213         showProfile: function() {
    214             if (!this.points.end) {
    215                 return;
    216             }
    217             if (this.elevationControl) {
    218                 this.elevationControl.removeFrom(this._map);
    219             }
    220 
    221             const dist = this.points.start.distanceTo(this.points.end);
    222             this.elevationControl = new ElevationProfile(this._map, [this.points.start, this.points.end], {
    223                 samplingInterval: calcSamplingInterval(dist),
    224                 sightLine: true
    225             });
    226             this.elevationControl.on('remove', () => {
    227                 this.elevationControl = null;
    228             });
    229             this.fire('elevation-shown');
    230         },
    231 
    232         hideProfile: function() {
    233             if (this.elevationControl) {
    234                 this.elevationControl.removeFrom(this._map);
    235             }
    236             this.elevationControl = null;
    237         },
    238 
    239         onProfileButtonClick: function() {
    240             this.showProfile();
    241         },
    242 
    243         onReverseButtonClick: function() {
    244             if (this.points.end && this.points.start) {
    245                 this.setPoints({start: this.points.end, end: this.points.start});
    246                 if (this.elevationControl) {
    247                     this.showProfile();
    248                 }
    249             }
    250         }
    251 
    252     }
    253 );
    254