nakarte

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

App.js (13682B)


      1 import './App.css';
      2 import './leaflet-fixes.css';
      3 import L from 'leaflet';
      4 import 'leaflet/dist/leaflet.css';
      5 import {MapWithSidebars} from '~/lib/leaflet.map.sidebars';
      6 import '~/lib/leaflet.control.printPages/control';
      7 import '~/lib/leaflet.control.caption';
      8 import config from './config';
      9 import '~/lib/leaflet.control.coordinates';
     10 import enableLayersControlHotKeys from '~/lib/leaflet.control.layers.hotkeys';
     11 import '~/lib/leaflet.hashState/Leaflet.Map';
     12 import '~/lib/leaflet.hashState/Leaflet.Control.Layers';
     13 import {fixAll} from '~/lib/leaflet.fixes';
     14 import './adaptive.css';
     15 import '~/lib/leaflet.control.panoramas';
     16 import '~/lib/leaflet.control.track-list/track-list';
     17 import '~/lib/leaflet.control.track-list/control-ruler';
     18 import '~/lib/leaflet.control.track-list/track-list.hash-state';
     19 import '~/lib/leaflet.control.track-list/track-list.localstorage';
     20 import enableLayersControlAdaptiveHeight from '~/lib/leaflet.control.layers.adaptive-height';
     21 import enableLayersMinimize from '~/lib/leaflet.control.layers.minimize';
     22 import enableLayersConfig from '~/lib/leaflet.control.layers.configure';
     23 import raiseControlsOnFocus from '~/lib/leaflet.controls.raise-on-focus';
     24 import {getLayers} from './layers';
     25 import '~/lib/leaflet.control.layers.events';
     26 import '~/lib/leaflet.control.jnx';
     27 import '~/lib/leaflet.control.jnx/hash-state';
     28 import '~/lib/leaflet.control.azimuth';
     29 import {hashState, bindHashStateReadOnly} from '~/lib/leaflet.hashState/hashState';
     30 import {LocateControl} from '~/lib/leaflet.control.locate';
     31 import {notify} from '~/lib/notifications';
     32 import ZoomDisplay from '~/lib/leaflet.control.zoom-display';
     33 import * as logging from '~/lib/logging';
     34 import safeLocalStorage from '~/lib/safe-localstorage';
     35 import {ExternalMaps} from '~/lib/leaflet.control.external-maps';
     36 import {SearchControl} from '~/lib/leaflet.control.search';
     37 import '~/lib/leaflet.placemark';
     38 
     39 const locationErrorMessage = {
     40     0: 'Your browser does not support geolocation.',
     41     1: 'Geolocation is blocked for this site. Please, enable in browser setting.',
     42     2: 'Failed to acquire position for unknown reason.',
     43 };
     44 
     45 const minimizeStateAuto = 0;
     46 const minimizeStateMinimized = 1;
     47 const minimizeStateExpanded = 2;
     48 
     49 function setUp() { // eslint-disable-line complexity
     50     const startInfo = {
     51         href: window.location.href,
     52         localStorageKeys: Object.keys(safeLocalStorage),
     53         mobile: L.Browser.mobile,
     54     };
     55     fixAll();
     56 
     57     function validateMinimizeState(state) {
     58         state = Number(state);
     59         if (state === minimizeStateMinimized || state === minimizeStateExpanded) {
     60             return state;
     61         }
     62         return minimizeStateAuto;
     63     }
     64     const minimizeState = hashState.getState('min') ?? [];
     65     const minimizeControls = {
     66         tracks: validateMinimizeState(minimizeState[0]),
     67         layers: validateMinimizeState(minimizeState[1]),
     68         print: validateMinimizeState(minimizeState[2]),
     69         search: validateMinimizeState(minimizeState[3]),
     70     };
     71 
     72     const map = new MapWithSidebars('map', {
     73             zoomControl: false,
     74             fadeAnimation: false,
     75             attributionControl: false,
     76             inertiaMaxSpeed: 1500,
     77             worldCopyJump: true,
     78             maxZoom: 18
     79         }
     80     );
     81 
     82     const tracklist = new L.Control.TrackList({
     83         keysToExcludeOnCopyLink: ['q', 'r']
     84     });
     85 
     86     /* controls top-left corner */
     87 
     88     new L.Control.Caption(config.caption, {
     89             position: 'topleft'
     90         }
     91     ).addTo(map);
     92 
     93     new ZoomDisplay().addTo(map);
     94 
     95     const searchOptions = {
     96         position: 'topleft',
     97         stackHorizontally: true,
     98         maxMapWidthToMinimize: 620,
     99     };
    100     if (minimizeControls.search === minimizeStateMinimized) {
    101         searchOptions.maxMapHeightToMinimize = Infinity;
    102         searchOptions.maxMapWidthToMinimize = Infinity;
    103     } else if (minimizeControls.search === minimizeStateExpanded) {
    104         searchOptions.maxMapHeightToMinimize = 0;
    105         searchOptions.maxMapWidthToMinimize = 0;
    106     }
    107     const searchControl = new SearchControl(searchOptions)
    108         .addTo(map)
    109         .enableHashState('q');
    110     map.getPlacemarkHashStateInterface().enableHashState('r');
    111 
    112     new L.Control.Scale({
    113         imperial: false,
    114         position: 'topleft',
    115         stackHorizontally: true
    116     }).addTo(map);
    117 
    118     new ExternalMaps({position: 'topleft'}).addTo(map);
    119 
    120     new L.Control.TrackList.Ruler(tracklist).addTo(map);
    121 
    122      const panoramas = new L.Control.Panoramas()
    123         .addTo(map)
    124         .enableHashState('n2');
    125     L.Control.Panoramas.hashStateUpgrader(panoramas).enableHashState('n');
    126 
    127     new L.Control.Coordinates({position: 'topleft'}).addTo(map);
    128 
    129     const azimuthControl = new L.Control.Azimuth({position: 'topleft'}).addTo(map);
    130 
    131     const locateControl = new LocateControl({
    132         position: 'topleft',
    133         showError: function({code, message}) {
    134             let customMessage = locationErrorMessage[code];
    135             if (!customMessage) {
    136                 customMessage = `Geolocation error: ${message}`;
    137             }
    138             notify(customMessage);
    139         }
    140     }).addTo(map);
    141     let {valid: validPositionInHash} = map.validateState(hashState.getState('m'));
    142     map.enableHashState('m', [config.defaultZoom, ...config.defaultLocation]);
    143 
    144     /* controls top-right corner */
    145 
    146     const layersControl = L.control.layers(null, null, {collapsed: false})
    147         .addTo(map);
    148     enableLayersControlHotKeys(layersControl);
    149     enableLayersControlAdaptiveHeight(layersControl);
    150     enableLayersMinimize(layersControl);
    151     enableLayersConfig(layersControl, getLayers());
    152     layersControl.enableHashState('l');
    153 
    154     /* controls bottom-left corner */
    155 
    156     const attribution = L.control.attribution({
    157         position: 'bottomleft',
    158         prefix: false,
    159     });
    160     map.on('resize', function() {
    161         if (map.getSize().y > 567) {
    162             map.addControl(attribution);
    163             // Hack to keep control at the bottom of the map
    164             const container = attribution._container;
    165             const parent = container.parentElement;
    166             parent.appendChild(container);
    167         } else {
    168             map.removeControl(attribution);
    169         }
    170     });
    171     if (map.getSize().y > 567) {
    172         map.addControl(attribution);
    173     }
    174 
    175     const printControl = new L.Control.PrintPages({position: 'bottomleft'})
    176         .addTo(map)
    177         .enableHashState('p');
    178     if (
    179         minimizeControls.print === minimizeStateMinimized ||
    180         (minimizeControls.print === minimizeStateAuto && !printControl.hasPages())
    181     ) {
    182         printControl.setMinimized();
    183     }
    184 
    185     const jnxControl = new L.Control.JNX(layersControl, {position: 'bottomleft'})
    186         .addTo(map)
    187         .enableHashState('j');
    188 
    189     /* controls bottom-right corner */
    190 
    191     function trackNames() {
    192         return tracklist.tracks().map((track) => track.name());
    193     }
    194     tracklist.addTo(map);
    195     const tracksHashParams = tracklist.hashParams();
    196 
    197     let hasTrackParamsInHash = false;
    198     for (let param of tracksHashParams) {
    199         if (hashState.hasKey(param)) {
    200             hasTrackParamsInHash = true;
    201             break;
    202         }
    203     }
    204     if (!hasTrackParamsInHash) {
    205         tracklist.loadTracksFromStorage();
    206     }
    207     startInfo.tracksAfterLoadFromStorage = trackNames();
    208 
    209     if (hashState.hasKey('autoprofile') && hasTrackParamsInHash) {
    210         tracklist.once('loadedTracksFromParam', () => {
    211             const track = tracklist.tracks()[0];
    212             if (track) {
    213                 tracklist.showElevationProfileForTrack(track);
    214             }
    215         });
    216     }
    217 
    218     // This is not quite correct: minimizeControls should have effect only during loading, but the way it is
    219     // implemented, it will affect expanding when loading track from hash param during session.
    220     // But as parameter is expected to be found only when site is embedded using iframe,
    221     // the latter scenario is not very probable.
    222     if (minimizeControls.tracks !== minimizeStateMinimized) {
    223         tracklist.on('loadedTracksFromParam', () => tracklist.setExpanded());
    224     }
    225 
    226     for (let param of tracksHashParams) {
    227         bindHashStateReadOnly(param, tracklist.loadTrackFromParam.bind(tracklist, param));
    228     }
    229     startInfo.tracksAfterLoadFromHash = trackNames();
    230 
    231     /* set map position */
    232 
    233     if (!validPositionInHash) {
    234         if (hasTrackParamsInHash) {
    235             tracklist.whenLoadDone(() => tracklist.setViewToAllTracks(true));
    236         } else {
    237             locateControl.moveMapToCurrentLocation(config.defaultZoom);
    238         }
    239     }
    240 
    241     /* adaptive layout */
    242 
    243     if (
    244         minimizeControls.layers === minimizeStateAuto && L.Browser.mobile ||
    245         minimizeControls.layers === minimizeStateMinimized
    246     ) {
    247         layersControl.setMinimized();
    248     }
    249 
    250     if (L.Browser.mobile) {
    251         map.on('mousedown dragstart', () => layersControl.setMinimized());
    252     }
    253 
    254     if (
    255         minimizeControls.tracks === minimizeStateAuto && L.Browser.mobile && !tracklist.hasTracks() ||
    256         minimizeControls.tracks === minimizeStateMinimized
    257     ) {
    258         tracklist.setMinimized();
    259     }
    260 
    261     raiseControlsOnFocus(map);
    262 
    263     /* save state at unload */
    264 
    265     L.DomEvent.on(window, 'beforeunload', () => {
    266         logging.logEvent('saveTracksToStorage begin', {
    267             localStorageKeys: Object.keys(safeLocalStorage),
    268             trackNames: trackNames(),
    269         });
    270         const t = Date.now();
    271         let localStorageKeys;
    272         try {
    273             tracklist.saveTracksToStorage();
    274             localStorageKeys = Object.keys(safeLocalStorage);
    275         } catch (e) {
    276             logging.logEvent('saveTracksToStorage failed', {error: e});
    277             return;
    278         }
    279         logging.logEvent('saveTracksToStorage done', {
    280             time: Date.now() - t,
    281             localStorageKeys
    282         });
    283     });
    284 
    285     /* track list and azimuth measure interaction */
    286 
    287     tracklist.on('startedit', () => azimuthControl.disableControl());
    288     tracklist.on('elevation-shown', () => azimuthControl.hideProfile());
    289     azimuthControl.on('enabled', () => {
    290         tracklist.stopEditLine();
    291     });
    292     azimuthControl.on('elevation-shown', () => tracklist.hideElevationProfile());
    293 
    294     /* setup events logging */
    295 
    296     function getLayerLoggingInfo(layer) {
    297         if (layer.meta) {
    298             return {title: layer.meta.title};
    299         } else if (layer.__customLayer) {
    300             return {custom: true, title: layer.__customLayer.title, url: layer._url};
    301         }
    302         return null;
    303     }
    304 
    305     function getLatLngBoundsLoggingInfo(latLngBounds) {
    306         return {
    307             west: latLngBounds.getWest(),
    308             south: latLngBounds.getSouth(),
    309             east: latLngBounds.getEast(),
    310             north: latLngBounds.getNorth(),
    311         };
    312     }
    313 
    314     function getErrorLoggingInfo(error) {
    315         return error
    316             ? {
    317                   name: error.name,
    318                   message: error.message,
    319                   stack: error.stack,
    320               }
    321             : null;
    322     }
    323 
    324     function logUsedMaps() {
    325         const layers = [];
    326         map.eachLayer((layer) => {
    327             const layerInfo = getLayerLoggingInfo(layer);
    328             if (layerInfo) {
    329                 layers.push(layerInfo);
    330             }
    331         });
    332         const bounds = map.getBounds();
    333         logging.logEvent('activeLayers', {
    334             layers,
    335             view: getLatLngBoundsLoggingInfo(bounds),
    336         });
    337     }
    338 
    339     L.DomEvent.on(document, 'mousemove click touchend', L.Util.throttle(logUsedMaps, 30000));
    340 
    341     printControl.on('mapRenderEnd', function(e) {
    342         logging.logEvent('mapRenderEnd', {
    343             eventId: e.eventId,
    344             success: e.success,
    345             error: getErrorLoggingInfo(e.error),
    346         });
    347     });
    348 
    349     printControl.on('mapRenderStart', function(e) {
    350         const layers = [];
    351         map.eachLayer((layer) => {
    352             const layerInfo = getLayerLoggingInfo(layer);
    353             if (layer.options?.print && layerInfo) {
    354                 layers.push({
    355                     ...getLayerLoggingInfo(layer),
    356                     scaleDependent: layer.options.scaleDependent
    357                 });
    358             }
    359         });
    360         logging.logEvent('mapRenderStart', {
    361             eventId: e.eventId,
    362             action: e.action,
    363             scale: e.scale,
    364             resolution: e.resolution,
    365             pages: e.pages.map((page) => getLatLngBoundsLoggingInfo(page.latLngBounds)),
    366             zooms: e.zooms,
    367             layers
    368         });
    369     });
    370 
    371     jnxControl.on('tileExportStart', function(e) {
    372         logging.logEvent('tileExportStart', {
    373             eventId: e.eventId,
    374             layer: getLayerLoggingInfo(e.layer),
    375             zoom: e.zoom,
    376             bounds: getLatLngBoundsLoggingInfo(e.bounds),
    377         });
    378     });
    379 
    380     jnxControl.on('tileExportEnd', function(e) {
    381         logging.logEvent('tileExportEnd', {
    382             eventId: e.eventId,
    383             success: e.success,
    384             error: getErrorLoggingInfo(e.error),
    385         });
    386     });
    387 
    388     searchControl.on('resultreceived', function(e) {
    389         logging.logEvent('SearchProviderSelected', {
    390             provider: e.provider,
    391             query: e.query,
    392         });
    393         if (e.provider === 'Links' && e.result.error) {
    394             logging.logEvent('SearchLinkError', {
    395                 query: e.query,
    396                 result: e.result,
    397             });
    398         }
    399         if (e.provider === 'Coordinates') {
    400             logging.logEvent('SearchCoordinates', {
    401                 query: e.query,
    402                 result: e.result,
    403             });
    404         }
    405     });
    406 
    407     logging.logEvent('start', startInfo);
    408     logUsedMaps();
    409 }
    410 
    411 export {setUp};