nakarte

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

App.js (13631B)


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