18 Introduction to Cartographic Client Web Application Design

Cartographic client Web applications are typically designed using HTML, JavaScript, and CSS using specialized JavaScript cartographic libraries such as OpenLayers, Leafeat, or Google Maps API. The common feature of these libraries is that they are usually distributed in Application Programming Interface (API) form. API is an environment used for designing application. API provides a set of classes and methods (functionalities) which facilitate application design by providing tools such as zooming, panning, adding layers, and adding scales through ready-made API classes and methods.

Before moving on to the OpenLayers API, the readers are advised to familiarize themselves with basic principles of HTML, CSS, JavaScript. There are numerous available resources for these languages, e.g., manuals on the portal https://www.w3schools.com/ :

18.1 Introduction to OpenLayers API

OpenLayers API (OpenLayers herafter) is an open-source library, written in JavaScript and is one of the most comprehensive libraries for developing interactive Web maps, i.e., cartographic Web applications. OpenLayers is characterized by a wide range of capabilities in terms of visualization and manipulation of geospatial data, as well as support for different formats and sources of geospatial data. It is important to note that procedures enabling easier use of OGC Web services such as WMS, WFS, and WMTS are integrated into the library; demonstrating the commitment of OpenLayers to developing Web GIS applications.

Although JavaScript does not completely adhere to the principles of object-oriented programming, OpenLayers removed most deficiencies and defined a clear class structures, enabling Web programmers to write better and clearer codes. For each library, it is necessary to invest time and effort in order to properly understand the library’s philosophy, and OpenLayers has at its disposal rich documentation and numerous examples of use, which help “beginners” quickly and efficiently design their first Web map.

The OpenLayers project was initiated by a few enthusiastic programmers, and today, the project is largely supported by the open-source community; it can be seen from the official repository that over 100 programmers actively contribute to improvements and enhancements of the library. The best illustration of active contributions is the fact that the library is accompanied by a large number of so-called 3rd party add on (3rd party libraries), enriching OpenLayers with different functionalities, such as

  • Integration of the Cesium globe – OL-Cesium add-on,

  • OpenLayers component for the React library – React OpenLayers add-on,

  • Component for layer display – OL-LayerSwitcher add-on,

  • and many others, https://openlayers.org/3rd-party/ .

Recently, a new version of the OpenLayers library (v5) was released, showing that the demand for developing cartographic Web applications is constantly increasing.

18.2 Spatial data in the OpenLayers library

As previously stated, OpenLayers supports different sources of spatial data through defined classes, of which the most common are

  • Raster sources

    • ImageStatic – used for displaying static georeferenced images.

    • Raster – used for accessing and manipulating pixel values of input rasters and visualizing output rasters.

    • TileImage – used for tiled visualization of input images.

    • TileWMS – used for visualizing raster data by establishing a connection with the geospatial data server, such as GeoServer, and defining appropriate WMS parameters.

    • Predefined WMS sources – OSM, CartoDB, OpenStreetMap, and Stamen, which are mostly used as base maps.

  • Vector sources – since JavaScript can “understand” only data in the text/json format, OpenLayers has overcome this drawback and enabled loading of vector data in different formats, using their textual representation such as EsriJSON, GeoJSON, GML, GPX, KML, MVT, OSMXML, TopoJSON, and WKT.

    • Vector – used for visualization of vector data in several ways:

      • Defining a file path on the local file system;

      • Defining a WFS path to the Web cartographic server, with appropriate parameters.

Methods of using data sources will be explained in more detail in the following, along with examples.

18.3 OpenLayers library components

The greatest challenge in learning libraries is understanding their philosophy of functioning, i.e., what are the optimal ways of using the library. The structure below (Figure 18.1), partially explains the hierarchy of the most important components of the OpenLayers library.

Hierarchic structure of most important components of the OpenLayers library.

Figure 18.1: Hierarchic structure of most important components of the OpenLayers library.

As can be seen from the figure, the basic componet is a Map which contains the entire structure of an interactive Web map. Within, the following elements can be defined:

  • Layers – to each added layer, a source must be attributed;

  • Controls – control buttons on the map for executing certain interactions, such as zooming in/out, panning, and rotating;

  • Interactions container – beside default interactions, other interactions such as Draw, Modify, and Select can be added;

  • Overlays – any type of element that can be set as an overlay layer. Typically, markers are used. On the Web, the most common are simple maps showing points of interest on available backgrounds (base maps, e.g., OpenStreetMaps);

  • View – defines the initial map view using parameters such as zooming, center position, and cartographic projection.

18.4 Example of simple Web map design

When looking at the rich content of a resulting Web map, OpenLayers can seem technically abstract. However, despite the complexity of the JavaScript code, the entire Web map is generated within an HTML element – a canvas.

A canvas is an HTML element used for graphical rendering on the fly, using JavaScript. All raster and vector data are drawn on the canvas element. For example, if there is a WMS-T base, such as OpenStreetMap, rendering of a sequence of adjacent img HTML elements will be performed on the canvas, with positions defined with pixel coordinates and with each img element representing a single tile. At the same time, if vector data are involved, geometry rendering is executed using the JavaScript Canvas DOM method for drawing lines and points.

var options={
key1: value1,
key2: value2,
key3: value3
}

In the following text, a step by step explanation of designing a simple Web map will be given, taking account of the conceptual structure and functioning of the OpenLayers library. The entire application, containing OpenStreetMap as the base map and centering on the area of Belgrade, is defined by the following code containing HTML, CSS, and JavaScript.

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    <script 
src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
  </script>
    <title>OpenLayers primer</title>
  </head>
  <body>
    <div id="map" class="map"></div>
    <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 16
        })
      });
    </script>
  </body>
</html>

Before beginning to develop a Web map, it is necessary to load the library into the main HTML document. Loading means storing a script element with a path to the source JavaScript library file, as well as storing a link element with a path to the source CSS file.

Then, it is necessary to define a div element, within which a map will be rendered with a canvas element and resulting Web map. The div element must have a defined id to which the main OpenLayers library object will be tied. Besides, it is necessary to carry out the basic stylization of the div element using CSS. All of this is contained in the HTML document header.

  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height: 400px;
        width: 100%;
      }
    </style>
    <script 
src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
  </script>
    <title>OpenLayers primer</title>
  </head>

The map itself is design using JavaScript. Before instantiating the main Map class object, it is advisable to prepare mandatory parameters and data:

  • It is necessary to instantiate a Map class object and attach to it defined objects and parameters:

    • target – mandatory parameter representing the element id in which the Web map will be rendered. In this example, the id is “map”;

    • layers – a sequence of layers. For each layer, a mandatory source is defined. In this example, the OSM predefined data source will be used.

    • viewView class object instantiation, within which display center position, initial zoom, and cartographic projection are defined.

  <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 16
        })
      });
    </script>

As a result, a simple Web map (Figure 18.2), is obtained, with default controls and interactions:

Example of a simple Web map created in OpenLayers.

Figure 18.2: Example of a simple Web map created in OpenLayers.

18.5 Layers

The following section will describe how to create a Web map containing additional layers, not only the base map as in the previous example. Layers are typically raster images of spatial data in the form of WMS layers or raw vector data in GeoJSON format, etc. The specific aspect of OpenLayers is the clear separation between the layer and the source.

The most important layers used in the OpenLayers library are

  • WMS layer,

  • Tile layer,

  • Vector layer.

18.6 WMS layer

Typically, in classic cartographic Web applications, spatial data are served using one of the solutions offered by OGC web service, such as WMS. The following example provides a part of the code related to creating a map with a WMS layer. The DEM for Serbia was chosen, available through OGC services on the cartographic server OSGL Belgrade, and with a descritption of the data given in the following link.

Every tile layer is created using the ol.layer.Tile constructor. In this case, it is give with the WMS source using the ol.source.TileWMS constructor. Besides the ol.layer.Tile object, it contains the title of the tile layer, typically given an associative name, in this case “DEM”. Other properties of this object can be defined, e.g., opacity, visibility, zIndex, and extent. The ol.source.TileWMS object has parameters url and params, which are defined in the following example. Besides these parameters, server url and WMS query parameters, other parameters can be defined: attributions, cacheSize, projection, tileGrid, etc. To create a non-tile WMS, the ol.source.ImageWMS constructor would be used.

The results in shown in the Figure 18.3 below.

<!doctype html>
<html lang="en">
  <head>
    <link rel="stylesheet" 
href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/css/ol.css" 
type="text/css">
    <style>
      .map {
        height:100%;
        width: 100%;
      }
    </style>
    <script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.2.0/build/ol.js">
    </script>
    <title>OpenLayers primer</title>
  </head>
  <body>
    <div id="map" class="map"></div>
   <script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            title: 'DEM',
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>

  </body>
</html>
Display of a WMS layer.

Figure 18.3: Display of a WMS layer.

The following example creates a similar results as the previous one, except that it also includes an OSM map as a base map, and the WMS layer transparency is set to 50%, Figure 18.4.

<script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
          new ol.layer.Tile({
            source: new ol.source.OSM()
          }),

          new ol.layer.Tile({
            title: 'DEM',
            opacity:0.5,
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          })
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>
Display of a WMS layer with an OSM base map.

Figure 18.4: Display of a WMS layer with an OSM base map.

18.7 Tiled lejer

As described in Chapter 13, tile layer are very common in cartographic Web applications. Typically, the client sends requests for 256 x 256 (pixel) image parts, in such a way as to dynamically render tiles depending on the user’s interaction with the map. The tiles are usually dynamically generated on the server side, but also cached for areas that are often “visited” on the map. This leads to quick interaction with the client, i.e., efficient layer rendering.

In the previous example, a tile layer was practically already used since OSM delivers data using this approach.

The object from the previous example was created using the ol.source.OSM constructor that secures OSM data previously rendered, cached tiles. Such tiles are organized into a standard XYZ grid for each tile.

The ol.source.XYZ constructor can be used for any tile layer. Such a layer is prepared in advance, and depending on the zoom on the client side, parts of the cartographic image (tiles) are delivered. The tile notation is in XYZ form, where X and Y are columns and rows of the pre-tiled render, and Z is the zoom level. Figure 18.5 shows a model of tile rendering depending on the zoom level.

Predefined tiles depending on zoom level. (source: [*https://www.e-education.psu.edu/geog585/node/519*](https://www.e-education.psu.edu/geog585/node/519))

Figure 18.5: Predefined tiles depending on zoom level. (source: https://www.e-education.psu.edu/geog585/node/519)

An example of a tile layer is given with the code below. Generally, in newer versions of the OpenLayers library, a code for importing classes and objects into the work environment (only modules necessary for the application) can be used, so that it is not necessary to load the entire library. The following code requires that the library is downloaded and installed. An installation manual is available at https://openlayers.org/en/latest/doc/tutorials/bundle.html. Alternatively, codesandbox can be used, with an example given at https://codesandbox.io/s/9y2qwq66lp.

<script>
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import XYZ from 'ol/source/XYZ.js';

      // The tile size supported by the ArcGIS tile service.
      var tileSize = 512;

      var urlTemplate = 'https://services.arcgisonline.com/arcgis/rest/services/' +
          'ESRI_Imagery_World_2D/MapServer/tile/{z}/{y}/{x}';

      var map = new Map({
        target: 'map',
        layers: [
          new TileLayer({
            source: new XYZ({
              attributions: 'Copyright:© 2013 ESRI, i-cubed, GeoEye',
              maxZoom: 16,
              projection: 'EPSG:4326',
              tileSize: tileSize,
              tileUrlFunction: function(tileCoord) {
                return urlTemplate.replace('{z}', (tileCoord[0] - 1).toString())
                  .replace('{x}', tileCoord[1].toString())
                  .replace('{y}', (-tileCoord[2] - 1).toString());
              },
              wrapX: true
            })
          })
        ],
        view: new View({
          center: [0, 0],
          projection: 'EPSG:4326',
          zoom: 2,
          minZoom: 2
        })
      });
 </script>

The previous code gives a map shown on Figure 18.6.

Example of tailed layer using ESRI Imagery World 2D.

Figure 18.6: Example of tailed layer using ESRI Imagery World 2D.

In the previous code, tiles are automatically generated depending on the zoom level. The example available at https://openlayers.org/en/latest/examples/canvas-tiles.html illustrates how to render tile depending on the zoom level, Figure 18.7. As in the previous example, if the warp parameter is defined, the map is continuously expanded from east to west.

Example of generating tiles for zoom levels 0, 1, 3, and 7.

Figure 18.7: Example of generating tiles for zoom levels 0, 1, 3, and 7.

18.8 Vector layer

A vector layer is defined using the ol.layer.Vector constructor, when vector data are rendered at the client side. The following example illustrates how to render a vector layer in GeoJSON format. In the example, the zIndex property is used to assign the sequence of layer rendering on the map. The OSM map is given as a base, then the WMS layer is given, and finally, the distribution of weather stations. The stations are imported as a point layer in GeoJSON format, using the ol.layer.Vector constructor. In this case, the data was taken directly from the WFS link, but it can generally be delivered as a local file from disk or directly as a JavaScript object. The stylization of the vector layer is given in the ol.style.Style property. The result of the code is given in Figure 18.8.

<script type="text/javascript">
      var map = new ol.Map({
        target: 'map',
        layers: [
        new ol.layer.Vector({
        title: 'TempTrends',
        zIndex:2,
        source: new ol.source.Vector({
          url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wfs?service=WFS&version=1.0.0'+ 
            '&request=GetFeature&typeName=osgl_2:tac_trends_annual' +  
            '&outputFormat=application/json&srsName=EPSG:3857',
          format: new ol.format.GeoJSON()
        }),
        style: new ol.style.Style({
          image: new ol.style.Circle({
            radius: 3,
            fill: new ol.style.Fill({color: 'black'})
          })
        })
      }),
          new ol.layer.Tile({
            title: 'DEM Srbija',
             zIndex: 1,
            opacity:0.5,
            source: new ol.source.TileWMS({
              url: 'http://osgl.grf.bg.ac.rs/geoserver/osgl_2/wms',
              params: {LAYERS: 'osgl_2:dem25', VERSION: '1.1.1'}
            })
          }),
          new ol.layer.Tile({
            zIndex: 0,
            source: new ol.source.OSM()
          })

        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([20.4763, 44.8059]),
          zoom: 8
        })
      });
    </script>
Vector layer with WMS and base layers.

Figure 18.8: Vector layer with WMS and base layers.

18.9 Map elements

Using the OpenLayers library, map elements such as cursor position, zoom control, scale, and slider, can be created. The following example is given at https://codesandbox.io/s/mjzmr0v0y. Two additional controls are added to the standard ones: cursor position on the map and scale. Similarly, other controls can be added, Figure 18.9.

import Map from "ol/Map.js";
import View from "ol/View.js";
import { defaults as defaultControls, ScaleLine } from "ol/control.js";
import MousePosition from "ol/control/MousePosition.js";
import TileLayer from "ol/layer/Tile.js";
import OSM from "ol/source/OSM.js";
import { createStringXY } from "ol/coordinate.js";
var mousePositionControl = new MousePosition({
  coordinateFormat: createStringXY(4),
  projection: "EPSG:4326",
  undefinedHTML: "&nbsp;"
});
var scaleLineControl = new ScaleLine();
var map = new Map({
  target: "map",
  controls: defaultControls()
    .extend([mousePositionControl])
    .extend([scaleLineControl]),
  layers: [
    new TileLayer({
      source: new OSM()
    })
  ],
  view: new View({
    center: [21, 45],
    projection: "EPSG:4326",
    zoom: 10,
    minZoom: 2
  })
});
Map with a cursor position, upper right corner; and scale, bottom left corner.

Figure 18.9: Map with a cursor position, upper right corner; and scale, bottom left corner.