MapStore Framework - Frontend Only Project (Experimental) ========================================================== This session focus on the use of MapStore as a frontend framework where the GeoStore backend is excluded. This approach could be useful when we need to connect MapStore to a different backend or when we want create a viewer/dashboard that extract data from static configuration files. The exercise in this session will allow to create a map viewer using an experimental project creation process, the result can be viewed `here `__. Some useful links: - `@mapstore/project `__ an experimental library to create mapstore project - `mapstore static examples `__ a mapstore viewer that uses static files created with the @mapstore/project scripts Install a MapStore project frontend only ----------------------------------------- navigate in your workspace folder and run the creation script using npx .. code-block:: sh npx @mapstore/project create the script will prompt some options before to create and these are the value selected for this custom project .. code-block:: sh - Name of project (default mapstore-project): static-map - Include backend (yes/no default yes): no - Optional features (printing, ldap): - Run npm install after creation setup (yes/no default yes): no navigate inside the ``static-map`` folder .. code-block:: sh cd static-map/ change the mapstore dependencies inside the ``package.json`` to point to the latest stable branch instead of master .. code-block:: sh touch package.json replace the mapstore dependencies with the needed branch version .. code-block:: sh "dependencies": { "mapstore": "git+https://github.com/geosolutions-it/MapStore2.git#" } add configuration to package.json to target specific folder of this project instead of the default one .. code-block:: sh "mapstore": { "apps": [ "js/apps" ], "html": [ "" ], "themes": [ "themes/" ] } remove all the default apps inside the ``static-map/js/apps`` directory create a new entry called ``static-map/js/apps/static-map.js`` with the following content .. code-block:: js /* * Copyright 2023, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ // add this binding to ensure all the streams inside components are working import '@mapstore/framework/libs/bindings/rxjsRecompose'; import url from 'url'; import main from '@mapstore/framework/product/main'; import pluginsDef from '@js/plugins/def'; import { setLocalConfigurationFile, setConfigProp } from '@mapstore/framework/utils/ConfigUtils'; import axios from '@mapstore/framework/libs/ajax'; import MapViewer from '@mapstore/framework/containers/MapViewer'; import { configureMap } from '@mapstore/framework/actions/config'; import { setControlProperty } from '@mapstore/framework/actions/controls'; import security from '@mapstore/framework/reducers/security'; import omit from 'lodash/omit'; setLocalConfigurationFile('configs/localConfig.json'); setConfigProp('translationsPath', ['translations', 'ms-translations']); setConfigProp('extensionsRegistry', 'configs/extensions.json'); // list of path that need version parameter const pathsNeedVersion = [ 'configs/', 'assets/', 'translations/', 'ms-translations/', 'print.json' ]; const version = __MAPSTORE_PROJECT_CONFIG__.version || 'dev'; axios.interceptors.request.use( config => { if (config.url && version && pathsNeedVersion.filter(urlEntry => config.url.match(urlEntry))[0]) { return { ...config, params: { ...config.params, v: version } }; } return config; } ); const pages = [{ name: 'home', path: '/', component: MapViewer }]; const MAP_TYPE = 'openlayers'; document.addEventListener('DOMContentLoaded', function() { // example of initial security state // with null this state is not initialized const user = null; const securityState = user && { security: { user: user, token: '' // this token is applied to the request defined in the localConfig authenticationRules properties } }; // this is an example of dynamic map loading via query param // there are other possibilities such use injected map data in the page // or use the internal react routing // the important steps is to populate the configureMap action with a valid map config const params = url.parse(window.location.href, true).query || {}; const mapName = params.map || 'new'; // load a base map configuration axios.get(`configs/${mapName}.json`) .then(({ data }) => { // initialize the mapstore app main( { targetId: 'container', pages, initialState: { defaultState: { ...securityState, maptype: { mapType: MAP_TYPE, last2dMapType: 'openlayers' } } }, appReducers: { security }, appEpics: {}, printingEnabled: false }, pluginsDef, // TODO: use default main import to avoid override (cfg) => ({ ...cfg, // remove epics that manage the map type for the standard product appEpics: omit(cfg.appEpics, [ 'syncMapType', 'updateLast2dMapTypeOnChangeEvents', 'restore2DMapTypeOnLocationChange' ]), initialActions: [ setControlProperty.bind(null, 'toolbar', 'expanded', false), configureMap.bind(null, data, 1, true) ] })); }); }); add a new ``static-map/index.ejs`` template where to load the new app entry and theme with following content .. code-block:: html Static Map - MapStore
Loading MapStore
add a ``static-map/js/plugins/def.js`` where to include all the plugins available in the app .. code-block:: js /* * Copyright 2023, GeoSolutions Sas. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ import MapPlugin from '@mapstore/framework/plugins/Map'; export default { plugins: { // framework plugins MapPlugin }, requires: {} }; add a ``static-map/configs/localConfig.json`` where to configure all the plugins inside the viewer or simple-viewer modes with following content .. code-block:: js { "proxyUrl": { "url": "proxy/?url=", "autoDetectCORS": true, "useCORS": [] }, "monitorState": [ { "name": "mapType", "path": "maptype.mapType" } ], "projectionDefs": [], "authenticationRules": [], "plugins": [ { "name": "Map", "cfg": { "mapOptions": { "openlayers": { "interactions": { "pinchRotate": false, "altShiftDragRotate": false }, "attribution": { "container": "#footer-attribution-container" } }, "leaflet": { "attribution": { "container": "#footer-attribution-container" } } }, "toolsOptions": { "scalebar": { "container": "#footer-scalebar-container" } } } } ] } install all the dependencies with .. code-block:: sh npm install Development ----------------- Run the application with the following command .. code-block:: sh npm start app runs at http://localhost:8081/ npm install is needed only once at first setup or if the dependencies have been updated. If the installation does not solve correctly the dependencies use npm install --legacy-peer-deps (eg latest version of node/npm) Build the app ----------------- You can run the build script to generate the client static application in the dist folder .. code-block:: sh ./build.sh the compiled app will be copied to dist/ folder Best practices ----------------- Here some suggestions on how to improve the setup of a custom project: - ensure all the configuration path are initialized correctly. An example are the symbols for annotation and their location in the repository. - add a custom plugins definition and import only the plugins used by the app. This will reduce the size of final js bundle - use the alias @mapstore/framework/ to have access to the components inside the folder MapStore2/web/client/ of MapStore - use the alias @js/ to have access to the components inside the js folder of the custom project - use the options available in the main entries to initialize the state of the app with particular conditions (an example is the security state)