Application tutorial

Writing a new MapStore based application can be done following these steps:

  • create a new folder for the application, inside the MapStore directory tree (e.g. web/client/examples/myapp), and the following folder structure:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
+-- myapp
    +-- index.html
    +-- config.json
    +-- webpack.config.js
    +-- babel.config.js
    +-- app.jsx
    +-- actions
        +-- config.js
    +-- containers
    |   +-- myapp.jsx
    +-- stores
        +-- myappstore.js
  • create an index.html file inside the application folder
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>MyApp</title>
        <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css" />
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
        <style>
        html, body, #container, #map {
            position:absolute;
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        </style>
    </head>
    <body>
        <div id="container"></div>
        <script src="dist/myapp.js"></script>
    </body>
</html>
  • create an app.jsx for the main app ReactJS component
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var React = require('react');
var ReactDOM = require('react-dom');

var Provider = require('react-redux').Provider;

// include application component
var MyApp = require('./containers/myapp');
var url = require('url');

var loadMapConfig = require('./actions/config').loadMapConfig;
var ConfigUtils = require('../../utils/ConfigUtils');

// initializes Redux store
var store = require('./stores/myappstore');

// reads parameter(s) from the url
const urlQuery = url.parse(window.location.href, true).query;

// get configuration file url (defaults to config.json on the app folder)
const { configUrl, legacy } = ConfigUtils.getConfigurationOptions(urlQuery, 'config', 'json');

// dispatch an action to load the configuration from the config.json file
store.dispatch(loadMapConfig(configUrl, legacy));

// Renders the application, wrapped by the Redux Provider to connect the store to components
ReactDOM.render(
    <Provider store={store}>
        <MyApp />
    </Provider>,
    document.getElementById('container')
);
  • create a myapp.jsx component inside the containers folder, that will contain the all-in-one Smart Component of the application
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var PropTypes = require('prop-types');
var React = require('react');
var connect = require('react-redux').connect;
var LMap = require('../../../components/map/leaflet/Map');
var LLayer = require('../../../components/map/leaflet/Layer');

class MyApp extends React.Component {
    static propTypes = {
        // redux store slice with map configuration (bound through connect to store at the end of the file)
        mapConfig: PropTypes.object,
        // redux store dispatch func
        dispatch: PropTypes.func
    };

    renderLayers = (layers) => {
        if (layers) {
            return layers.map(function(layer) {
                return <LLayer type={layer.type} key={layer.name} options={layer} />;
            });
        }
        return null;
    };

    render() {
        // wait for loaded configuration before rendering
        if (this.props.mapConfig && this.props.mapConfig.map) {
            return (
                <LMap id="map" center={this.props.mapConfig.map.center} zoom={this.props.mapConfig.map.zoom}>
                     {this.renderLayers(this.props.mapConfig.layers)}
                </LMap>
            );
        }
        return null;
    }
}

// include support for OSM and WMS layers
require('../../../components/map/leaflet/plugins/OSMLayer');
require('../../../components/map/leaflet/plugins/WMSLayer');

// connect Redux store slice with map configuration
module.exports = connect((state) => {
    return {
        mapConfig: state.mapConfig
    };
})(MyApp);
  • create a myappstore.js store initalizer inside the stores folder, that will create the global Redux store for the application, combining the needed reducers and middleware components
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
var {createStore, combineReducers, applyMiddleware} = require('redux');

var thunkMiddleware = require('redux-thunk');
var mapConfig = require('../../../reducers/config');

 // reducers
const reducers = combineReducers({
    mapConfig
});

// compose middleware(s) to createStore
let finalCreateStore = applyMiddleware(thunkMiddleware)(createStore);

// export the store with the given reducers (and middleware applied)
module.exports = finalCreateStore(reducers, {});
  • create a config.js actions file inside the actions folder, that will contain the redux action (thunk) used to load the map configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const axios = require('../../../libs/ajax');
const {configureMap, configureError} = require('../../../actions/config');

/**
 * Map configuration loader action.
 * ReduxJS thunk.
 */
function loadMapConfig(configName, legacy) {
    return (dispatch) => {
        // loads the configuration file and dispatches configureMap or configureError
        return axios.get(configName).then((response) => {
            if (typeof response.data === 'object') {
                dispatch(configureMap(response.data, legacy));
            } else {
                try {
                    JSON.parse(response.data);
                } catch (e) {
                    dispatch(configureError('Configuration file broken (' + configName + '): ' + e.message));
                }

            }

        }).catch((e) => {
            dispatch(configureError(e));
        });
    };
}

module.exports = {
    loadMapConfig
};
  • create a config.json file with basic map configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "map": {
    "projection": "EPSG:900913",
    "units": "m",
    "center": {"x": 1250000.000000, "y": 5370000.000000, "crs": "EPSG:900913"},
    "zoom":5,
    "layers": [
      {
        "type": "osm",
        "title": "Open Street Map",
        "name": "mapnik",
        "group": "background",
        "visibility": true
      },
      {
        "type": "wms",
        "url":"https://demo.geo-solutions.it/geoserver/wms",
        "visibility": true,
        "opacity": 0.5,
        "title": "Weather data",
        "name": "nurc:Arc_Sample",
        "group": "Meteo",
        "format": "image/png"
      }
    ]
  }
}
  • create a webpack.config.js file with build configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var path = require("path");
var LoaderOptionsPlugin = require("webpack/lib/LoaderOptionsPlugin");

module.exports = {
    entry: {
        myapp: path.join(__dirname, "app")
    },
    output: {
        path: path.join(__dirname, "dist"),
        publicPath: "/dist/",
        filename: "myapp.js"
    },
    resolve: {
        extensions: [".js", ".jsx"]
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: [{
                    loader: "babel-loader",
                    options: {
                        configFile: path.join(__dirname, 'babel.config.js')
                    }
                }]

            }
        ]
    },
    devtool: 'inline-source-map',
    plugins: [
        new LoaderOptionsPlugin({
            debug: true
        })
    ]
};
  • create a babel.config.js file with babel presets
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module.exports = function(api) {
    api.cache(true);
    return {
        "presets": [
            "@babel/env",
            "@babel/preset-react"
        ],
        "plugins": [
            "@babel/plugin-proposal-class-properties",
            "@babel/plugin-syntax-dynamic-import"
        ]
    };
};

Now the application is ready, to launch it in development mode, you can use the following command (launch it from the MapStore main folder):

1
./node_modules/.bin/webpack-dev-server --config web/client/examples/myapp/webpack.config.js --progress --colors --port 8081 --content-base web/client/examples/myapp

Then point your preferred browser to the following url: http://localhost:8081