nakarte

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

index.js (8429B)


      1 import L from 'leaflet';
      2 import './measured_line.css';
      3 
      4 function pointOnSegmentAtDistance(p1, p2, dist) {
      5     // FIXME: we should place markers along projected line to avoid transformation distortions
      6     var q = dist / p1.distanceTo(p2),
      7         x = p1.lng + (p2.lng - p1.lng) * q,
      8         y = p1.lat + (p2.lat - p1.lat) * q;
      9     return L.latLng(y, x);
     10 }
     11 
     12 function sinCosFromLatLonSegment(segment) {
     13     const
     14         p1 = L.CRS.EPSG3857.project(segment[0]),
     15         p2 = L.CRS.EPSG3857.project(segment[1]),
     16         dx = p2.x - p1.x,
     17         dy = p1.y - p2.y,
     18         len = Math.sqrt(dx * dx + dy * dy),
     19         sin = dy / len,
     20         cos = dx / len;
     21     return [sin, cos];
     22 }
     23 
     24 L.MeasuredLine = L.Polyline.extend({
     25         options: {
     26             minTicksIntervalMm: 15,
     27         },
     28 
     29         onAdd: function(map) {
     30             L.Polyline.prototype.onAdd.call(this, map);
     31             this._ticks = {};
     32             this.updateTicks();
     33             this._map.on('zoomend', this.updateTicks, this);
     34             // markers are created only for visible part of map, need to update when it changes
     35             this._map.on('moveend', this.updateTicks, this);
     36             this.on('nodeschanged', this.updateTicksLater, this);
     37         },
     38 
     39         updateTicksLater: function() {
     40             setTimeout(this.updateTicks.bind(this), 0);
     41         },
     42 
     43         onRemove: function(map) {
     44             this._map.off('zoomend', this.updateTicks, this);
     45             this._map.off('moveend', this.updateTicks, this);
     46             this.off('nodeschanged', this.updateTicks, this);
     47             this._clearTicks();
     48             L.Polyline.prototype.onRemove.call(this, map);
     49         },
     50 
     51         _clearTicks: function() {
     52             if (this._map) {
     53                 Object.values(this._ticks).forEach((tick) => this._map.removeLayer(tick));
     54                 this._ticks = {};
     55             }
     56         },
     57 
     58         _addTick: function(tick, marker) {
     59             var transformMatrixString = 'matrix(' + tick.transformMatrix.join(',') + ')';
     60             if (marker) {
     61                 marker._icon.childNodes[0].style.transform = transformMatrixString;
     62                 marker.setLatLng(tick.position);
     63             } else {
     64                 var labelText = Math.round((tick.distanceValue / 10)) / 100 + ' km',
     65                     icon = L.divIcon(
     66                         {
     67                             html: '<div class="measure-tick-icon-text" style="transform:' +
     68                                 transformMatrixString + '">' +
     69                             labelText + '</div>',
     70                             className: 'measure-tick-icon'
     71                         }
     72                     );
     73                 marker = L.marker(tick.position, {
     74                     icon: icon,
     75                     interactive: false,
     76                     keyboard: false,
     77                     projectedShift: () => this.shiftProjectedFitMapView(),
     78                 });
     79                 marker.addTo(this._map);
     80             }
     81             this._ticks[tick.distanceValue.toString()] = marker;
     82         },
     83 
     84         setMeasureTicksVisible: function(visible) {
     85             this.options.measureTicksShown = visible;
     86             this.updateTicks();
     87         },
     88 
     89         getTicksPositions: function(minTicksIntervalMeters, bounds) {
     90             var steps = [500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000];
     91             var ticks = [];
     92 
     93             const that = this;
     94             function addTick(position, segment, distanceValue) {
     95                 if (bounds) {
     96                     // create markers only in visible part of map
     97                     const normalizedBounds = that._map.wrapLatLngBounds(bounds);
     98                     const normalizedPosition = position.wrap();
     99                     // account for worldCopyJump
    100                     const positionMinus360 = L.latLng(normalizedPosition.lat, normalizedPosition.lng - 360);
    101                     const positionPlus360 = L.latLng(normalizedPosition.lat, normalizedPosition.lng + 360);
    102                     if (
    103                         !normalizedBounds.contains(normalizedPosition) &&
    104                         !normalizedBounds.contains(positionMinus360) &&
    105                         !normalizedBounds.contains(positionPlus360)
    106                     ) {
    107                         return;
    108                     }
    109                 }
    110 
    111                 var sinCos = sinCosFromLatLonSegment(segment),
    112                     sin = sinCos[0],
    113                     cos = sinCos[1],
    114                     transformMatrix;
    115 
    116                 if (sin > 0) {
    117                     transformMatrix = [sin, -cos, cos, sin, 0, 0];
    118                 } else {
    119                     transformMatrix = [-sin, cos, -cos, -sin, 0, 0];
    120                 }
    121                 ticks.push({position: position, distanceValue: distanceValue, transformMatrix: transformMatrix});
    122             }
    123 
    124             let step;
    125             for (step of steps) {
    126                 if (step >= minTicksIntervalMeters) {
    127                     break;
    128                 }
    129             }
    130 
    131             var lastTickMeasure = 0,
    132                 lastPointMeasure = 0,
    133                 points = this._latlngs,
    134                 points_n = points.length,
    135                 nextPointMeasure,
    136                 segmentLength;
    137             if (points_n < 2) {
    138                 return ticks;
    139             }
    140 
    141             for (var i = 1; i < points_n; i++) {
    142                 segmentLength = points[i].distanceTo(points[i - 1]);
    143                 nextPointMeasure = lastPointMeasure + segmentLength;
    144                 if (nextPointMeasure >= lastTickMeasure + step) {
    145                     while (lastTickMeasure + step <= nextPointMeasure) {
    146                         lastTickMeasure += step;
    147                         addTick(
    148                             pointOnSegmentAtDistance(points[i - 1], points[i], lastTickMeasure - lastPointMeasure),
    149                             [points[i - 1], points[i]],
    150                             lastTickMeasure
    151                         );
    152                     }
    153                 }
    154                 lastPointMeasure = nextPointMeasure;
    155             }
    156             // remove last mark if it is close to track end
    157             if (lastPointMeasure - lastTickMeasure < minTicksIntervalMeters / 2) {
    158                 ticks.pop();
    159             }
    160             // special case: if track is versy short, do not add starting mark
    161             if (lastPointMeasure > minTicksIntervalMeters / 2) {
    162                 addTick(points[0], [points[0], points[1]], 0);
    163             }
    164             addTick(points[points_n - 1], [points[points_n - 2], points[points_n - 1]], lastPointMeasure);
    165             return ticks;
    166         },
    167 
    168         updateTicks: function() {
    169             if (!this._map) {
    170                 return;
    171             }
    172             if (!this.options.measureTicksShown) {
    173                 this._clearTicks();
    174                 return;
    175             }
    176             var bounds = this._map.getBounds().pad(1),
    177                 rad = Math.PI / 180,
    178                 dpi = 96,
    179                 mercatorMetersPerPixel = 20003931 / (this._map.project([180, 0]).x),
    180                 referencePoint = this.getLatLngs().length ? this.getBounds().getCenter() : this._map.getCenter(),
    181                 realMetersPerPixel = mercatorMetersPerPixel * Math.cos(referencePoint.lat * rad),
    182                 mapScale = 1 / dpi * 2.54 / 100 / realMetersPerPixel,
    183                 minTicksIntervalMeters = this.options.minTicksIntervalMm / mapScale / 1000,
    184                 ticks = this.getTicksPositions(minTicksIntervalMeters, bounds),
    185                 oldTicks = this._ticks;
    186             this._ticks = {};
    187             ticks.forEach(function(tick) {
    188                     var oldMarker = oldTicks[tick.distanceValue.toString()];
    189                     this._addTick(tick, oldMarker);
    190                     if (oldMarker) {
    191                         delete oldTicks[tick.distanceValue.toString()];
    192                     }
    193                 }.bind(this)
    194             );
    195             Object.values(oldTicks).forEach((tick) => this._map.removeLayer(tick));
    196         },
    197 
    198         getLength: function() {
    199             var points = this._latlngs,
    200                 points_n = points.length,
    201                 length = 0;
    202 
    203             for (var i = 1; i < points_n; i++) {
    204                 length += points[i].distanceTo(points[i - 1]);
    205             }
    206             return length;
    207         }
    208     }
    209 );
    210 
    211 L.measuredLine = function(latlngs, options) {
    212     return new L.MeasuredLine(latlngs, options);
    213 };