Goal

In this tutorial, we create a basic HTML page containing a LuciadRIA map. We use webpack to bundle our JavaScript and HTML files, and webpack-dev-server to serve the app.

Note that the use of webpack isn’t required. LuciadRIA comes as a set of ES6 modules, and you can use the tool chain of your choosing to add LuciadRIA to your web application. LuciadRIA doesn’t impose any special requirements.

Although webpack features prominently, the goal of this tutorial is to show you how to integrate LuciadRIA into a web application. It doesn’t focus on the setup and use of webpack as such, nor on the project configuration for TypeScript.

The use of webpack isn’t required. LuciadRIA comes as a set of ES6 modules, and you can use the tool chain of your choosing to add LuciadRIA to your web application.

Project structure setup

As a first step, we create a new project directory with an index.html file and use webpack to serve this file. These steps are based on the webpack Getting Started tutorial and the webpack TypeScript tutorial:

We start by creating a directory, initializing npm and installing webpack and the dev-server packages:

mkdir hello-world
cd hello-world
npm init -y
npm install webpack webpack-cli webpack-dev-server raw-loader typescript ts-loader --save-dev

We set up the standard webpack configuration, with the TypeScript sources in a src directory and the output in a dist directory.

mkdir dist
mkdir src

# Already create some empty files, which we'll populate later
# The windows alternative for touch is type
# type nul > dist/index.html
touch dist/index.html #create an empty index.html file in the dist directory
touch src/index.ts #create an empty index.ts file in the src directory
touch webpack.config.js #create an empty webpack configuration file

For TypeScript, we need a tsconfig.json file specifying the compiler options. For now, we just tell the TypeScript compiler tsc to create one for us:

npx tsc --init --target es2015 #this will create a tsconfig.json file

Depending on your project, you can change the settings in that file. For this tutorial, the default values are enough.

Now, we need to configure webpack in such a way that it transpiles our TypeScript code to JavaScript. For this, we configure webpack to use the ts-loader on all .ts files. We also specify what the entry file is, and where we want to locate the output. Consult the webpack documentation for more information.

To make development easier, we also configure a development server that serves the contents of the dist folder. While it’s running, this dev-server automatically picks up changes we make to our TypeScript code, and re-transpiles them to JavaScript.

The webpack.config.js file
const path = require('path');

module.exports = {
    devtool: 'inline-source-map',
        entry: './src/index.ts',
        output: {
                filename: 'main.js',
                path: path.resolve(__dirname, 'dist')
        },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader'
            }
        ]
    },
    resolve: {
        extensions: [ '.ts', '.tsx', '.js' ]
    },
  devServer: {
    static: path.join(__dirname, 'dist')
  }
};

To test our setup, we add some contents to the index.html file in the dist folder

Initial contents of the dist/index.html file
<!doctype html>
<html lang="en">
  <head>
    <title>Hello world</title>
  </head>
  <body>
    <h1>Hello world</h1>
  </body>
</html>

The resulting folder and file structure should look like

hello-world
 |- dist
    |- index.html
 |- node_modules
 |- package.json
 |- package-lock.json
 | - src
     |- index.ts
 |- tsconfig.json
 |- webpack.config.js

To actually start the development server, we add a start:dev script in package.json:

Add an extra script to the package.json file
"scripts": {
  "start:dev": "webpack serve --mode development"
}

Now we can run

npm run start:dev

and browse to http://localhost:8080/ where we’re greeted by our web page.

Installing the LuciadRIA npm packages

Your LuciadRIA distribution comes with a number of NPM packages under the packages folder.

  • The @luciad/ria package has the core modules of the LuciadRIA library

  • The @luciad/geometry and @luciad/symbology packages are optional packages. Depending on the LuciadRIA tier you bought, you may not have access to them. Consult the LuciadRIA product tiers documentation for more information.

In this tutorial, we assume that these packages are already published in an npm registry. We’re going to install them using npm install.

# Install the package from your own private registry
npm install --registry http://my-private-registry:8073/ @luciad/ria

# Optionally: if you have purchased additional components, install them as well
# We won't be using any code from those packages in the remainder of this tutorial
# npm install  --registry http://my-private-registry:8073/ @luciad/ria-geometry
# npm install  --registry http://my-private-registry:8073/ @luciad/ria-milsym

We can now use the installed @luciad/ria package in our index.ts file.

An alternative to using the --registry flag is defining the location in a .npmrc file:

; Set a new registry for a scoped package
@luciad:registry=http://my-private-registry:8073/

Consult the npm documentation for more information.

Adding a map to our application

First, we add a <div> to our index.html page to mark where we want our map. Because we’re updating the HTML file, we immediately trigger the loading of our JavaScript code.

The updated <body> element, containing a <div> for the map and the <script> tag for the JS in the dist/index.html file
<body>
  <h1>Hello world</h1>
  <!-- Create a div where our map should appear -->
  <div id="map"/>
  <!-- Load the javascript file.
       main.js is the file that webpack will generate for the src/index.ts file
  -->
  <script src="main.js"></script>
</body>

We also add some CSS to give this <div> a certain size. To keep things simple in this tutorial, we add the CSS directly to the header of our file:

CSS added to the header of the dist/index.html file
<style>
  #map {
    position: relative;
    width: 100%;
    height: 620px;
    overflow: hidden;
    border: 1px solid grey;
 }
</style>

The complete dist/index.html file

<!doctype html>
<html lang="en">
  <head>
    <title>Hello world</title>
    <style>
      #map {
        position: relative;
        width: 100%;
        height: 620px;
        overflow: hidden;
        border: 1px solid grey;
     }
    </style>
  </head>
  <body>
    <h1>Hello world</h1>
    <div id="map"/>
    <script src="main.js"></script>
  </body>
</html>

Now it’s time to write our first lines of LuciadRIA code. In the src/index.ts file we need to create a new @luciad/ria/view/WebGLMap instance, and tell it to appear in that <div>:

import {Map} from "@luciad/ria/view/WebGLMap.js";

//Create a new map instance, and display it in the div with the "map" id
const map = new WebGLMap("map");

That’s it. We created our first LuciadRIA map.

If you visit the web page at this point, you bump into two problems, though:

  • We didn’t install the LuciadRIA license in our application yet, resulting in an error because the license can’t be found.

  • The map doesn’t contain any data, so the page doesn’t show anything.

Installing and activating the license

To activate the license, we’ll need to pass the contents of the license file to the License class:

  1. Copy the luciadria_development.txt license file to the src folder.

  2. Create a new src/license-loader.ts file

  3. Add the following contents to it:

    The src/license-loader.ts file:
    import {setLicenseText} from "@luciad/ria/util/License.js";
    import license from "raw-loader!./luciadria_development.txt";
    
    setLicenseText(license);

    We’re using the webpack raw-loader to load the contents of the license file as a string.

  4. Import the LicenseLoader in the src/index.ts file.

    import "./license-loader";

    Make sure that this import is the first import. You must load the license before you trigger any other LuciadRIA code.

Please note that index.ts imports license-loader.ts module for a side effect, which is loading the license information. If you configure webpack with sideEffects flag set to false then webpack may exclude the loading license module from the distribution assets. To overcome the issue you can mark the file explicitly as side-effect-free following the solution documented here.

Because TypeScript handles text file resources in a particular way, we also need to create a src/luciadria_development.txt.d.ts file with the following contents:

The src/luciadria_development.txt.d.ts file
//See https://github.com/webpack-contrib/raw-loader/issues/56#issuecomment-507057511 and
//https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations
declare module "raw-loader!*" {
    const content: string;
    export default content;
}

Adding some data to the map

Now that we installed the license, it’s time to add some data to the map so that we can see the map. For this tutorial, we connect to a public WMS server and display a dataset from that server.

We request the 92c09725-a9c5-46fb-bffd-d9e23b4abbf2 dataset from the https://sampleservices.luciad.com/wms server. Once we have the connection to the server, we store the data in a layer and add it to our map.

import {WMSTileSetModel}  from "@luciad/ria/model/tileset/WMSTileSetModel.js";
import {RasterTileSetLayer} from "@luciad/ria/view/tileset/RasterTileSetLayer.js";

//Add some WMS data to the map
const server = "https://sampleservices.luciad.com/wms";
const dataSetName = "4ceea49c-3e7c-4e2d-973d-c608fb2fb07e";
WMSTileSetModel.createFromURL(server, [{layer: dataSetName}])
.then(model => {
  //Once the data is available, create a layer for it
  const layer = new RasterTileSetLayer(model);
  //and add the layer to the map
  map.layerTree.addChild(layer);
});

Consult the WMS documentation for more information on how to deal with WMS data in LuciadRIA.

This results in

hello world

Full code

The src/index.ts file
import "./license-loader";
import {WebGLMap} from "@luciad/ria/view/WebGLMap.js";
import {WMSTileSetModel}  from "@luciad/ria/model/tileset/WMSTileSetModel.js";
import {RasterTileSetLayer} from "@luciad/ria/view/tileset/RasterTileSetLayer.js";

//Create a new map instance, and display it in the div with the "map" id
const map = new WebGLMap("map");

//Add some WMS data to the map
const server = "https://sampleservices.luciad.com/wms";
const dataSetName = "4ceea49c-3e7c-4e2d-973d-c608fb2fb07e";
WMSTileSetModel.createFromURL(server, [{layer: dataSetName}])
.then(model => {
  //Once the data is available, create a layer for it
  const layer = new RasterTileSetLayer(model);
  //and add the layer to the map
  map.layerTree.addChild(layer);
});
The src/license-loader.ts file
import {setLicenseText} from "@luciad/ria/util/License.js";
import license from "raw-loader!./luciadria_development.txt";

setLicenseText(license);
The src/luciadria_development.txt.d.ts file
//See https://github.com/webpack-contrib/raw-loader/issues/56#issuecomment-507057511 and
//https://www.typescriptlang.org/docs/handbook/modules.html#wildcard-module-declarations
declare module "raw-loader!*" {
    const content: string;
    export default content;
}
The dist/index.html file
<!doctype html>
<html lang="en">
  <head>
    <title>Hello world</title>
    <style>
      #map {
        position: relative;
        width: 100%;
        height: 620px;
        overflow: hidden;
        border: 1px solid grey;
     }
    </style>
  </head>
  <body>
    <h1>Hello world</h1>
    <div id="map"/>
    <script src="main.js"></script>
  </body>
</html>
The webpack.config.js file
const path = require('path');

module.exports = {
    devtool: 'inline-source-map',
        entry: './src/index.ts',
        output: {
                filename: 'main.js',
                path: path.resolve(__dirname, 'dist')
        },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                loader: 'ts-loader'
            }
        ]
    },
    resolve: {
        extensions: [ '.ts', '.tsx', '.js' ]
    },
  devServer: {
    static: path.join(__dirname, 'dist')
  }
};