nakarte

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

index.js (7445B)


      1 import L from 'leaflet';
      2 import {fetch} from '~/lib/xhr-promise';
      3 import config from '~/config';
      4 import './style.css';
      5 import {CloseButtonMixin, Events} from '../common';
      6 
      7 function getCoverageLayer(options) {
      8     return L.tileLayer(config.mapillaryRasterTilesUrl, L.extend({
      9         tileSize: 1024,
     10         zoomOffset: -2,
     11         minNativeZoom: 0,
     12     }, options));
     13 }
     14 
     15 async function getMapillary() {
     16     const [mapillary] = await Promise.all([
     17         import(
     18             /* webpackChunkName: "mapillary" */
     19             'mapillary-js'
     20             ),
     21         import(
     22             /* webpackChunkName: "mapillary" */
     23             'mapillary-js/dist/mapillary.css'
     24             ),
     25     ]);
     26     return mapillary;
     27 }
     28 async function getPanoramaAtPos(latlng, searchRadiusMeters) {
     29     function radiusToBbox(latlng, radiusInMeters) {
     30         const center = L.CRS.EPSG3857.project(latlng);
     31         const metersPerMapUnit = L.CRS.EPSG3857.unproject(L.point(center.x, center.y + 1)).distanceTo(latlng);
     32         const radiusInMapUnits = radiusInMeters / metersPerMapUnit;
     33         return L.latLngBounds(
     34             L.CRS.EPSG3857.unproject(L.point(center.x - radiusInMapUnits, center.y - radiusInMapUnits)),
     35             L.CRS.EPSG3857.unproject(L.point(center.x + radiusInMapUnits, center.y + radiusInMapUnits))
     36         );
     37     }
     38 
     39     function isCloser(target, a, b) {
     40         const d1 = target.distanceTo(a);
     41         const d2 = target.distanceTo(b);
     42         if (d1 < d2) {
     43             return -1;
     44         } else if (d1 === d2) {
     45             return 0;
     46         }
     47         return 1;
     48     }
     49 
     50     const searchBbox = radiusToBbox(latlng, searchRadiusMeters);
     51     const precision = 6;
     52     const searchBboxStr = [
     53         searchBbox.getWest().toFixed(precision),
     54         searchBbox.getSouth().toFixed(precision),
     55         searchBbox.getEast().toFixed(precision),
     56         searchBbox.getNorth().toFixed(precision),
     57     ].join(',');
     58     const url = `https://graph.mapillary.com/images?access_token=${config.mapillary4}&bbox=${searchBboxStr}&limit=100`;
     59     const resp = await fetch(url, {responseType: 'json', timeout: 20000});
     60     if (resp.responseJSON.data.length) {
     61         const points = resp.responseJSON.data.map((it) => ({
     62             id: it.id,
     63             lat: it.geometry.coordinates[1],
     64             lng: it.geometry.coordinates[0],
     65         }));
     66         points.sort((p1, p2) => isCloser(latlng, p1, p2));
     67         return {found: true, data: points[0].id};
     68     }
     69     return {found: false};
     70 }
     71 
     72 const Viewer = L.Evented.extend({
     73         includes: [CloseButtonMixin],
     74 
     75         initialize: function(mapillary, container) {
     76             const id = `container-${L.stamp(container)}`;
     77             container.id = id;
     78             this.viewer = new mapillary.Viewer(
     79                 {
     80                     container: id,
     81                     accessToken: config.mapillary4,
     82                     component: {cover: false, bearing: false, cache: true, zoom: false, trackResize: true},
     83                 });
     84             this.createCloseButton(container);
     85             this.invalidateSize = L.Util.throttle(this._invalidateSize, 100, this);
     86             this._updateHandler = null;
     87             this._currentImage = null;
     88             this._zoom = null;
     89             this._centerX = null;
     90             this._centerY = null;
     91             this._bearing = null;
     92             this._yawPitchZoomChangeTimer = null;
     93         },
     94 
     95         showPano: function(imageId) {
     96             this.viewer.moveTo(imageId)
     97                 .then(() => {
     98                     if (!this._updateHandler) {
     99                         this._updateHandler = setInterval(this.watchMapillaryStateChange.bind(this), 50);
    100                     }
    101                 })
    102                 .catch(() => {
    103                     // ignore error
    104                 });
    105         },
    106 
    107         watchMapillaryStateChange: function() {
    108             Promise.all([
    109                 this.viewer.getImage(),
    110                 this.viewer.getCenter(),
    111                 this.viewer.getZoom(),
    112                 this.viewer.getBearing(),
    113             ]).then(([image, center, zoom, bearing]) => {
    114                 if (this._currentImage?.id !== image.id) {
    115                     this._currentImage = image;
    116                     const lngLat = image.originalLngLat;
    117                     this.fire(Events.ImageChange, {latlng: L.latLng(lngLat.lat, lngLat.lng)});
    118                 }
    119                 const [centerX, centerY] = center;
    120                 if (centerX !== this._centerX || centerY !== this._centerY || zoom !== this._zoom) {
    121                     this._zoom = zoom;
    122                     this._centerX = centerX;
    123                     this._centerY = centerY;
    124                     if (this._yawPitchZoomChangeTimer !== null) {
    125                         clearTimeout(this._yawPitchZoomChangeTimer);
    126                         this._yawPitchZoomChangeTimer = null;
    127                     }
    128                     this._yawPitchZoomChangeTimer = setTimeout(() => {
    129                         this.fire(Events.YawPitchZoomChangeEnd);
    130                     }, 120);
    131                 }
    132                 bearing -= this.getBearingCorrection();
    133                 if (bearing !== this._bearing) {
    134                     this._bearing = bearing;
    135                     this.fire(Events.BearingChange, {bearing: bearing});
    136                 }
    137             }).catch(() => {
    138                 // ignore error
    139             });
    140         },
    141 
    142         getBearingCorrection: function() {
    143             if (this._currentImage && 'computedCompassAngle' in this._currentImage) {
    144                 return (this._currentImage.computedCompassAngle - this._currentImage.originalCompassAngle);
    145             }
    146             return 0;
    147         },
    148 
    149         deactivate: function() {
    150             this._currentImage = null;
    151             this._bearing = null;
    152             this._zoom = null;
    153             this._centerX = null;
    154             this._centerY = null;
    155             clearInterval(this._updateHandler);
    156             this._updateHandler = null;
    157             this.viewer.setCenter([0.5, 0.5]);
    158             this.viewer.setZoom(0);
    159         },
    160 
    161         activate: function() {
    162             this.viewer.resize();
    163         },
    164 
    165         getState: function() {
    166             if (
    167                 this._currentImage === null ||
    168                 this._zoom === null ||
    169                 this._centerX === null ||
    170                 this._centerY === null
    171             ) {
    172                 return [];
    173             }
    174             return [
    175                 this._currentImage.id,
    176                 this._centerX.toFixed(6),
    177                 this._centerY.toFixed(6),
    178                 this._zoom.toFixed(2)
    179             ];
    180         },
    181 
    182         setState: function(state) {
    183             const imageId = state[0];
    184             const center0 = parseFloat(state[1]);
    185             const center1 = parseFloat(state[2]);
    186             const zoom = parseFloat(state[3]);
    187             if (imageId && !isNaN(center0) && !isNaN(center1) && !isNaN(zoom)) {
    188                 this.showPano(imageId);
    189                 this.viewer.setCenter([center0, center1]);
    190                 this.viewer.setZoom(zoom);
    191                 return true;
    192             }
    193             return false;
    194         },
    195 
    196         _invalidateSize: function() {
    197             this.viewer.resize();
    198         }
    199     }
    200 );
    201 
    202 async function getViewer(container) {
    203     const mapillary = await getMapillary();
    204     return new Viewer(mapillary, container);
    205 }
    206 
    207 const mapillaryProvider = {getCoverageLayer, getPanoramaAtPos, getViewer};
    208 export default mapillaryProvider;