nakarte

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

index.js (7143B)


      1 import L from 'leaflet';
      2 
      3 import urlViaCorsProxy from '~/lib/CORSProxy';
      4 
      5 const origCreateTile = L.TileLayer.prototype.createTile;
      6 const origIsValidTile = L.TileLayer.prototype._isValidTile;
      7 const origOnAdd = L.TileLayer.prototype.onAdd;
      8 
      9 function coordsListBounds(coordsList) {
     10     const bounds = L.latLngBounds();
     11     coordsList.forEach(([lon, lat]) => bounds.extend([lat, lon]));
     12     return bounds;
     13 }
     14 
     15 function latLngBoundsToBounds(latlLngBounds) {
     16     return L.bounds(
     17         L.point(latlLngBounds.getWest(), latlLngBounds.getSouth()),
     18         L.point(latlLngBounds.getEast(), latlLngBounds.getNorth())
     19     );
     20 }
     21 
     22 function isCoordsListIntersectingBounds(coordsList, latLngBounds) {
     23     const latLngBoundsAsBound = latLngBoundsToBounds(latLngBounds);
     24     for (let i = 1; i < coordsList.length; i++) {
     25         if (L.LineUtil.clipSegment(L.point(coordsList[i - 1]), L.point(coordsList[i]), latLngBoundsAsBound, i > 1)) {
     26             return true;
     27         }
     28     }
     29     return Boolean(
     30         L.LineUtil.clipSegment(L.point(coordsList[coordsList.length - 1]), L.point(coordsList[0]), latLngBoundsAsBound)
     31     );
     32 }
     33 
     34 function isPointInsidePolygon(polygon, latLng) {
     35     let inside = false;
     36     let prevNode = polygon[polygon.length - 1];
     37     for (let i = 0; i < polygon.length; i++) {
     38         const node = polygon[i];
     39         if (
     40             node[0] !== prevNode[1] &&
     41             ((node[0] <= latLng.lng && latLng.lng < prevNode[0]) ||
     42                 (prevNode[0] <= latLng.lng && latLng.lng < node[0])) &&
     43             latLng.lat < ((prevNode[1] - node[1]) * (latLng.lng - node[0])) / (prevNode[0] - node[0]) + node[1]
     44         ) {
     45             inside = !inside;
     46         }
     47         prevNode = node;
     48     }
     49     return inside;
     50 }
     51 
     52 L.TileLayer.include({
     53     _drawTileClippedByCutline: function (coords, srcImg, destCanvas, done) {
     54         if (!this._map) {
     55             return;
     56         }
     57         const width = srcImg.naturalWidth;
     58         const height = srcImg.naturalHeight;
     59         destCanvas.width = width;
     60         destCanvas.height = height;
     61 
     62         const zoomScale = 2 ** coords.z;
     63         const tileScale = width / this.getTileSize().x;
     64         const tileNwPoint = coords.scaleBy(this.getTileSize());
     65         const tileLatLngBounds = this._tileCoordsToBounds(coords);
     66         const ctx = destCanvas.getContext('2d');
     67         ctx.beginPath();
     68         const projectedCutline = this.getProjectedCutline();
     69         for (let i = 0; i < projectedCutline.length; i++) {
     70             const cutlineLatLngBounds = this._cutline.bounds[i];
     71             if (tileLatLngBounds.intersects(cutlineLatLngBounds)) {
     72                 const path = projectedCutline[i].map((point) =>
     73                     point.multiplyBy(zoomScale).subtract(tileNwPoint).multiplyBy(tileScale)
     74                 );
     75                 ctx.moveTo(path[0].x, path[0].y);
     76                 for (let j = 1; j < path.length; j++) {
     77                     ctx.lineTo(path[j].x, path[j].y);
     78                 }
     79                 ctx.closePath();
     80             }
     81         }
     82         ctx.clip();
     83         ctx.drawImage(srcImg, 0, 0);
     84         destCanvas.complete = true; // HACK: emulate HTMLImageElement property to make L.TileLayer._abortLoading() happy
     85         this._tileOnLoad(done, destCanvas);
     86     },
     87 
     88     onAdd: function (map) {
     89         const result = origOnAdd.call(this, map);
     90         if (this.options.cutline && !this._cutlinePromise) {
     91             this._cutlinePromise = this._setCutline(this.options.cutline, this.options.cutlineApprox).then(() => {
     92                 this._updateProjectedCutline();
     93                 this.redraw();
     94             });
     95         }
     96         this._updateProjectedCutline();
     97         return result;
     98     },
     99 
    100     createTile: function (coords, done) {
    101         if (this._cutline && !this._cutline.approx && this.isCutlineIntersectingTile(coords, true)) {
    102             const img = document.createElement('img');
    103             img.crossOrigin = '';
    104 
    105             const tile = document.createElement('canvas');
    106             tile.setAttribute('role', 'presentation');
    107 
    108             L.DomEvent.on(img, 'load', L.bind(this._drawTileClippedByCutline, this, coords, img, tile, done));
    109             L.DomEvent.on(img, 'error', L.bind(this._tileOnError, this, done, tile));
    110 
    111             let url = this.getTileUrl(coords);
    112             if (this.options.noCors) {
    113                 url = urlViaCorsProxy(url);
    114             }
    115             img.src = url;
    116             return tile;
    117         }
    118         return origCreateTile.call(this, coords, done);
    119     },
    120 
    121     isCutlineIntersectingTile: function (coords, onlyBorder) {
    122         const tileLatLngBounds = this._tileCoordsToBounds(coords);
    123         for (let i = 0; i < this._cutline.latlng.length; i++) {
    124             const cutline = this._cutline.latlng[i];
    125             const cutlineLatLngBounds = this._cutline.bounds[i];
    126             if (
    127                 cutlineLatLngBounds.overlaps(tileLatLngBounds) &&
    128                 (isCoordsListIntersectingBounds(cutline, tileLatLngBounds) ||
    129                     (!onlyBorder && isPointInsidePolygon(cutline, tileLatLngBounds.getNorthEast())))
    130             ) {
    131                 return true;
    132             }
    133         }
    134         return false;
    135     },
    136 
    137     _isValidTile: function (coords) {
    138         const isOrigValid = origIsValidTile.call(this, coords);
    139         if (this._cutline && isOrigValid) {
    140             return this.isCutlineIntersectingTile(coords, false);
    141         }
    142         return isOrigValid;
    143     },
    144 
    145     getProjectedCutline: function () {
    146         const map = this._map;
    147         function projectCoordsList(coordsList) {
    148             return coordsList.map(([lng, lat]) => map.project([lat, lng], 0));
    149         }
    150 
    151         if (!this._cutline._projected || this._cutline._projectedWithMap !== map) {
    152             this._cutline._projected = this._cutline.latlng.map(projectCoordsList);
    153             this._cutline._projectedWithMap = map;
    154         }
    155 
    156         return this._cutline._projected;
    157     },
    158 
    159     _setCutline: async function (cutline, approx) {
    160         let cutlinePromise = cutline;
    161         if (typeof cutlinePromise === 'function') {
    162             cutlinePromise = cutlinePromise();
    163         }
    164         if (!cutlinePromise.then) {
    165             cutlinePromise = Promise.resolve(cutlinePromise);
    166         }
    167         let cutlineCoords;
    168         try {
    169             cutlineCoords = await cutlinePromise;
    170         } catch (_) {
    171             // will be handled as empty later
    172         }
    173 
    174         if (cutlineCoords) {
    175             this._cutline = {
    176                 latlng: cutlineCoords,
    177                 bounds: cutlineCoords.map(coordsListBounds),
    178                 approx: approx,
    179             };
    180         } else {
    181             this._cutline = null;
    182         }
    183     },
    184 
    185     _updateProjectedCutline: function () {
    186         const map = this._map;
    187         if (!this._cutline || !map || (this._cutline._projected && this._cutline._projectedWithMap !== map)) {
    188             return;
    189         }
    190         function projectCoordsList(coordsList) {
    191             return coordsList.map(([lng, lat]) => map.project([lat, lng], 0));
    192         }
    193 
    194         this._cutline._projected = this._cutline.latlng.map(projectCoordsList);
    195         this._cutline._projectedWithMap = map;
    196     },
    197 });