Exercise

Web Application Development Using MVC


Web Application Development Using MVC

Getting Started

This exercise is presented as a set of modules/sections. In each module you will perform the set of steps required to achieve a pre-defined objective. Each module builds upon steps on previous modules and will iteratively build up your knowledge base on geospatial web application development.

It is important to understand that you will be using web framework, as opposed to a web page approach. This means that you will not write much mark-up to produce pages, on the contrary you will specify application components that will be render as HTML. You will be using Webix as the framework to develop the application's components. The selected application architecture for this exercise follows the MVC pattern with Models, Views and Controllers as follows:

  • Models are application components to describe data as a collection of fields. Models can be linked to other models through associations.
  • Views are application components responsible for the presentation of content to the users. Multiple views could be defined to present certain content in different ways.
  • Controllers are the components that realize the behavior of the application. They contain what's commonly known as the application logic.

The components of the application will be as follows:

A small HTML document to include CSS resources and JavaScript libraries such as Webix, D3 and OpenLayers; JavaScript code to initialize the application, build the user interface, and manage content and user interaction; and Python code to connect to data resources (local & remote) and to take care of any intermediate data processing or manipulation required.

Use Case

In this exercise you will be creating an application for Dutch municipalities geared towards the following objectives. First, present to the general public in an intuitive way a set of predefined statistical variables to inform them about the conditions of their neighborhoods. Second, presenting in near real time the conditions of the transport system in the municipality. And third, enabling a small level of participatory planning.

The background for the application is as follows: "Municipalities are looking for a mechanism to improve services provision to citizens, A number of approaches are under consideration, but the most popular initiative calls for setting up a Web application to allow citizens to visualize the state of the public transport system, and to interact with some of the statistical data held by municipalities on their neighbourhoods. This data will help municipalities in having an overview of the maintenance needs of cities and prioritize accordingly. The information required from the citizens when submitting reports includes, incident type, location and description."

Municipalities will also like to facilitate access to spatial data (vector data per object) openly to citizens, following a so-called fair use policy. This should include, for a start, statistical data of the city at a neighbourhood or district level. As part of this implementation, municipalities want to make use of the set of geo-spatial services made available to the public by the Dutch Government via its open data portal (PDOK). The application structure should be robust enough to accommodate extra functionality in the future.

User profiles

User groupDomain/FunctionData needs &
level of use
Work environment &
file formats
Application requirements
#1Statistics DepartmentCadastral and statistical datasets for dissemination.GIS & graphics softwareEnable service dissemination via open interfaces.
#2CitizensUnderstand some relevant statistical figures of their neighborhoods and be inform of the current conditions of the public transport system.Browser onlya) Ability to interact with statistical variables in and around his/her neighborhood;
b) Keep an eye on location of the bus routes of interest for his/her mobility in the city.
#3MunicipalityUp-to-date view of the condition of the city's facilities.Browser & database clientsReport on the status of maintenance activities.

shows a subset of the user requirements. It includes only those that you will be able to cover during the time duration of the exercise. This means you will not for now create any functionality for the participatory planning part of the application.

Technology Stack

This exercise requires a running webserver, and access to a web-enabled directory. shows the technology stack that will be used in this exercise.

Architectural Design

Given the criteria stated in the use case definition, it is clear that a desktop application will not be a good choice. You are therefore going to go for a Web application. Remember that the flexibility, maintainability and scalability of a (Web) application is, by the most part, determined by its overall architecture.

The way to go about defining a solid architecture is to study the problem carefully and define application views, models, stores and controllers before any coding action takes place.

User Interface

The first milestone in this process is the selection of the views. It is the task of the architect to identify how users will interact with the application and what will be the result of such interaction. The outcome of this analysis will be a set of views with a clearly defined role in the application.

Following the developments in technology, Web applications have become more and more responsive over the last years. This trend gives developers increasing number of ways and techniques to help users to interact with different types of content. In our case this content can be classified in three distinctive groups and as such you can assume that they can be presented separately to the users. One of the possible ways to achieve that, is by creating an interface with a number of tabs, each of which provides access to a specific type of content. This means one tab for statistical data, one tab for transport data and one more for planning data. All three combined in a single main application view. The application object that will correspond to this main view will be called HomeView. This view will be the first application component to be render in the browser. The internal elements of this view will simply be the panels for the various types of content of the application.

The layout of the statistics tab will include an area for a base map and an area for various statistical charts. You will call the application component for this view StatisticsView (see ).

The transport tab is much simpler and will only include an area for a base map where all details of the status of the public transport can be displayed. The application object corresponding to this view is called TransportView (see ).

The Planning tab is the most elaborated of them all, with a larger number of elements. A map for citizen orientation. A grid for data about citizen reports. A streetview of the areas where reports have been filed. And a floating editor window which will enable citizens to type in the details of reports. The application object for this view will be called PlanningView (see ).

Application Structure

You will follow a unified directory structure that is the same for every application you build. This means that all application code components are placed inside a folder, named after the actual name of the application, which in turn contains sub-folders to namespace views, controllers, etc. To be in line with the functionality of the application, you are going to call it CityApp. Inside this folder you will encapsulate all the components of the application. shows how the folder structure for the application should look like once it is complete:You will need two folders: one on your local machine and the other in your web-accessible directory. These folders will encapsulate all the components of the application. and show what the folder structure of the application should look like once it is complete:

Application StructureTreeView
Application Structure at student's computerTreeView
Application Structure at web-accesible folderTreeView

Be aware that you will only work in your web-accesible folder in section 'Data Services.'

Use this figure as a reference for all your file and folder creation steps. If you are unsure as to where a certain file should be placed, then come back to this figure and check its precise location.

Base Modules

Before heading any further, if you have not yet created your personal Web accessible folder, check the preparation section of the WMS exercises (MapServerWMS).

To start working on the application, open the file explorer and create a directory/folder called CityApp in your personal Web accessible folder.local working folder.

For example:

__CODEPATH__\CityApp __MSG002__

Before moving on, make sure to enable the Developers Console, so that you can keep an eye on what the application is doing and also to get needed inside to trace any errors that may occur during development. To access the console press Shift+Ctrl+K if you are using Firefox, or Ctrl+Shift+I if you are using Chrome. shows a mock-up of the console in Firefox. With these elements in place, you are all set to start putting together the code for the application.

One extra remark, when using the code in the listings of this exercise, remember that the dots  • • •  are there to indicate that there is additional code, before and/or after the code depicted in the snippet. As usual, lines with new or modified code are highlighted in light blue. The lines showing previously added code give you a reference as to where to implement changes. To make this type of changes to your code, use the THINK-Copy THINK-Paste approach as discussed in the lectures.

It is good practice, when creating application files, to configure your system so that you are sure of the extension types of your various files. To that end you need to navigate to the Options menu in your file explorer and uncheck the option "Hide extensions for known file types" as shown in .

Like any site on the Web, the CityApp application requires a so-called landing document that will be served by the web server when a user points his/her browser to the application's URL. In our case, since you are not serving just a page, next to the landing document, you also need to create an initialization script. Such script will be responsible for initializing the JavaScript objects that will render the application's interface in the browser.

Configuration

This exercise assumes that you have installed in your system pgAdmin 4, Visual Studio Code and Node.js®. If you do not have any of these programs, open a Command Prompt window as administrator, and execute the corresponding command as shown in to install the missing program.

Install required applicationsPowerShell

At the heart of any development project with Node, lies the package.json file. It records important metadata about the development project and also defines functional attributes that npm uses to install dependencies, run scripts, and identify the entry point your application. Use the content shown in to create the CityApp/package.json. Make sure to update the 'author' property (line 5) with your personal details.

CityApp/package.jsonJSON
  • 6–10 The scripts property contains terminal commands to be executed at different stages during development.
  • 14–18 The dependencies property is an object with package names and versions or a version range as a value listing the dependencies your application needs.

With the metadata file in place, you can proceed with the installation of the required package dependencies by running the command shown in . This should be done in the terminal within the interface of Visual Studio Code. To open a terminal window simply select Terminal → New Terminal from the VSC menu.

Initialize the development environmentPowerShell

The output of the initialization script may look different depending on updates to versions of the packages on the day the script is run. In this particular case 211 packages were added to your environmnet to support the development process.

Application files

Lets now create the application's landing HTML document. Create a new file in the CityApp folder and call it index.html. Then, open the file in your favorite code editor and insert the code that appears in . Make absolutely sure that the extension of this new file is .html and not .txt or anything else. Note that in the header of the listing, the exact path of the file you are working on, is also shown. Always use the header of the listing as a reference to the location and name of the file that you have to work with.

CityApp/index.htmlHTML
  • 6 The application title.
  • 9, 12 The reference to the Webix library that you will use and its corresponding styles.
  • 15 The reference to our own initialization script, which is yet to be created.

The content of the CityApp/index.html file encompasses the minimum set of elements for the web page to run. The file name index.html guarantees that this code is automatically executed when a user points the browser to the URL where the application is hosted. Note that you have not added any mark-up to the body element of the document. The mark-up for the body of the application will be the responsibility of the application scripts.

Now you need to create the initialization script. This script should specify which of the various views of the application should be used first. As discussed in the previous session you will use the HomeView as the initial view. This script is the ideal place to handle application launch and initialization details.

Create the initialization script by including the code in in the file CityApp/app/Application.js. Notice that you first need to create the application scripts folder, which is called app.

CityApp/app/Application.jsJavaScript
  • 8 Import an application object which is needed to complete the initialization.
  • 15–17 Define an array object as a placeholder for global variables, which for now only specifies the internal application name.
  • 21–25 The declaration of the function that is executed when the Webix libraries are ready (the browser has finished loading the corresponding scripts).
  • 23 The name of the first view to be rendered.

Pay attention to the declaration in line 8 of import {HomeView} from './app/home/HomeView', which indicates that an external object is needed and should be loaded as part of this particular script. The import statement also indicates where the required object can be found.

What you need to do now to complete the initialization process of the application is to specify the actual code of the initial view, the code of the so-called HomeView object. Use the code in to specify this view. Make sure to create the necessary folders for the correct placement of the file HomeView.

CityApp/app/home/HomeView.jsJavaScript
  • 5 This view also has a reference to a required object in the form of a controller.
  • 7–44 Define the home view object.
  • 9–40 The view will have one main container (or one column, if you use Webix object concepts).
  • 10 The type of container is a Webix tabview
  • 15–39 As decided during the design phase, there will be, three tab panels. There is already provision for a fourth panel if needed.
  • 18, 24, 30 The id of each panel can be used to access each panel programmatically whenever necessary.
  • 19, 25, 31 For each panel some temporary dummy content has been included.
  • 41–43 You specify a function to easily access the controller object when responding to events taking place in the various view elements.
code in takes care of rendering the main interface of the application, but the controller object is still missing.

The code for the definition of the HomeController appears in . Place the code in the listing inside the HomeController.

CityApp/app/home/HomeController.jsJavaScript
  • 5–7 Basic component initialization. In this case you have chosen to use the alias HomeCtrl as the placeholder for this object.

Here you have simply defined the structure of the controller for the Home view. At this point there is no functionality on it, as you have no actions for the controller to execute yet. Needed functionality will be added as you go along.

To produce the code that should run in the browser you need to start the development server. To do that run the code in in the terminal. also shows the output messages on the console when the initialization command runs successfully. If any errors are detected, read the message and proceed to correct the problem.

Start the development serverPowerShell

It is time to test the first version of the application. Point your browser to the development URL of your CityApp application, which should look something like this:

__NODEURL__

If all went well, then your browser should display a result similar to the image shown in . Otherwise, head to the developer's console and check for error messages. Try switching panels by clicking on the headers of the tabs to display their corresponding content.

Right-click somewhere on the browser page and select the 'Inspect Element' option, then check the body element of the page to see the mark-up that has been produced for you based on the code in the HomeView definition. Quite different in comparison with the original source in the index.html file.

The Statistics View

The purpose of this view is to present to the users, landuse and population data, and allow them some level of interaction with that data. To achieve such objective, you will use three different visualization elements: a choropleth, a pie chart and a bar chart. You have already created a container for this view (see Section 'Base Modules').

Now you have to create its internal structure. You will start by creating the view object itself, which you will name, StatisticsView. This view will depict a map with a choropleth on the top and the two charts at the bottom as per the design (see Section 'Architectural Design').

contains the code required to specify the structure of the statistics view according to the description above. Use this code to create the StatisticsView.

CityApp\app\statistics\StatisticsView.jsJavaScript
  • 5–39 Define the statistics view object.
  • 7–35 Create the view's internal structure. This view is composed of three row elements, the middle one being just a spacer. The first row has no width or height specified so that it can automatically change its size as a response to browser size changes.
  • 23–33 The bottom row is in turn divided into nested columns. The last two columns have a fixed width. This means that the first column will set its width automatically according to the browser's full width.
  • 9, 17, 25, 30 Each functional interface component has its own unique identifier.
  • 3 Like with all views, this one also requires a controller.

Let us now include the code of the controller that will manage this new view. Using the code in , procceed to create the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript

For now, this controller does not have any specific tasks to perform, but it must exist for the application to run properly, therefore its code is just the bare minimum.

Although the StatisticsView object and its corresponding structure have been specified. The view itself is not yet part of the application. To include the statistics view, you need to modify already existing application objects. Let us update the HomeView file as shown in .

This time you are modifying an already existing file. As such the listing below shows both, existing code next to new code. The lines that you have to add or modify appear highlighted. Use the existing code as a reference to identify where changes have to be made. Also Notice that does not show all the code already existing in the CityApp/app/home/HomeView.js file. Instead some of that code has been replaced by the triple dot symbol (• • •), indicating that more code exist but is not shown here.

Every time you make a change, pay attention to any console messages, they will guide you in case something needs attention. Always make sure that the application works before moving forward. If you ignore errors and continue, that will make it impossible to finalize the exercise.
CityApp\app\home\HomeView.jsJavaScript
  • 6 The StatsView, or statistics view, is added to the application.
  • 15–17 An event listener is put in place to react to the onAfterRender event of the tabbar of the HomeView. This event will be fired when the tabbar is completely rendered in the browser. In this case you have specified that the OnViewReady() function, should be executed as a response of the occurrence of that event. Since you are working with the HomeView object. the corresponding function is specified as a member of the HomeCtrl object, the controller of the view. You are using the tabbar because at the level of HomeView, it is the only real component in the interface, everything else is just layout (cells) which do not exhibit any event. Notice that you do not want to execute the function, just to pass it as a parameter therefore it does not include the parenthesis.
  • 21 The previously existing dummy content for the statistics view is replaced by the actual view definition, the StatsView object.

The next step is to update the HomeCtrl to include the new function. The code for the onViewReady() function is shown in . Use it to update the HomeController.

CityApp\app\home\HomeController.jsJavaScript

At the moment, the onViewReady() function simply writes a message to the console to report the application status. Refresh your page to see the changes. The application should now look like the image in . Again, if you use the tabs you can switch between panels and hide or show the new view.

Views are self-contained objects meaning that they can operate independently from other views. So for example if you wanted to have an application where only the statistics view is available, you could simply change the choice of initial view of the application. shows the result of replacing the HomeView with the StatsView in the initialization script (for this to work, the StatsView view has to be imported first with an import call at the top of the script).

The capability of using views as independent objects opens lots of possibilities for reuse and adds flexibility to the development process.

The Web Map

Central to the functionality of the application is a map that gives spatial context to the user. There exist many options to adhere too when creating a map in a Web application. In our case you will settle for using OpenLayers to generate and interact with map elements.

Basemap

To include a basemap in the application, you start by identifying the event that you will use to determine the precise moment when the map object should be created and displayed. You would like to make sure that the StatisticsView is fully rendered in the browser before you attempt to show a map, and possibly also display other content in this view. Just like with the home view before, the event that is fired when the view is ready to receive content is called onAfterRendered. You will configure the view to listen for this particular event for the sts_map panel. You will also assign a function to respond to the occurrence of the event.

Register the event and declare its corresponding function. The necessary changes to the StatisticsView are shown in .

CityApp\app\statistics\StatisticsView.jsJavaScript
  • 10–12 Registers the onAfterRender event and assign the onViewReady() function as a handler to respond to the occurrence of the event.

Now you should define the code for the onViewReady() function, which will be responsible for handling the onAfterRendered event. The initial content of the function is shown in , use it to update the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 7 Specify a new property of the statistics controller to use as a reference to this view's map object.
  • 10–15 Declare the onViewReady() function.
  • 12 Replaces the existing (text) content of the map panel (sts_map) with a new container for the map object. The map container uses the id of the panel as a basis for the id of the map container.
  • 13 Uses the createMap() function to create a map object and store it in the navMap property of the statistics controller.
  • 14 Temporary console message to let us know that all has gone according to plan up to this point.

Although the onViewReady() function has now been declared, you are not ready yet, because in the function you make reference to another function, called createMap. You now need to create that function too. Notice that you have decided to include the createMap() function as an external function to this controller. The reason for this is that all the views in the application will have a map object and therefore they will make use of this function too. It is therefore better to have it independent of any of the views in which it is used. In you have already added the import statement for the createMap() function (see line 3).

You might have noticed that your application is being watched by the Parcel.js bundler, and as a result all the changes are automatically reflected in the browser. At this moment, that means that you have generated an error because you have include a file tool.js which does not exist yet.

Use the code on to create the common function in the CityApp\app\tools.js file and include the new function. Notice that this is a new file, make sure it is placed in the right location.

CityApp\app\tools.jsJavaScript
  • 16 The createMap() function receives one parameter and uses it as the target container for the new map object.
  • 17–31 Create a map object using some predefined parameters.
  • 18 Assign the target container of the map.
  • 19–22 Declare the parameters to adjust the view of the map. The view uses a projection to manage its content, by default this projection is Spherical Mercator EPSG:3857. The center of the view is specified using WGS84 coordinates, which need to be transformed to the projection of the view.
  • 23–30 In terms of content, a TileLayer has been added to the map, which uses OpenStreeMap (OSM) data as a source.
  • 32 The function finishes by returning the newly created map object.

In addition to the function that creates the map, you also have to declare some style elements for the corresponding map container. Those are included in a style file. The content of the style file is shown in . Use it to create the CityApp\styles\app.css file.

CityApp\styles\app.cssCSS

Finally, you need to include this style file as well as the OpenLayers style file in the application. This means you need to update the CityApp\index.html file as shown in .

CityApp\styles\index.htmlHTML

The interface of the application should now look similar to the image in . At this moment you can already interact with the map object, try it out. Check the console while the application is being refreshed so you can see how the various asynchronous function calls are being executed.

The console also shows a couple of messages that you have placed as a feedback mechanism, indicating that all code has been executed successfully. Since you are now using events, the execution flow of the application splits into different execution paths. That is why you used two messages so that you can be sure that both flows have completed successfully.

Web Map Tile Service

You can now concentrate on the real content of the map object. For a start let's work on the basemap. Although OSM looks like a valid choice, it is not the only one. In a situation like this, the obvious thing to do is to look into the available services from the national data infrastructure. In the case of The Netherlands that is the Dutch Public Mapping Services (PDOK). You can find information about PDOK and the available data services at Publieke Dienstverlening Op de Kaart (PDOK). An alphabetical list of available services can be found at PDOK services. The base map service ('BRT achtergrondkaart' in Dutch) includes various cartographic representations (see ).

You can find the description and metadata of the basemap service at:

https://www.pdok.nl/introductie/-/article/basisregistratie-topografie-achtergrondkaarten-brt-a-

This particular service is compliant with OGC's WMTS specification, which means that it serves georeferenced, 256x256 map tiles, pre-rendered at fixed zoom levels or resolutions. The overall structure of this tiles is known as a tilematrix. Since there is no native functionality in OpenLayers for this type of service, you need to specify the zoom levels and the tilematrix yourself. You also want to use the National coordinate system, Amersfoort / RD New - EPSG:28992.

The code for using one of the PDOK WMTS services as a basemap is shown in and should be added to the CityApp\app\tools.js file.

CityApp\app\tools.jsJavaScript
  • 20 Specifies the extent of the bounding box covering the whole country.
  • 21–25 Define the projection object for the Dutch coordinate system.
  • 26–29 Create the various levels of the tilematrix set.
  • 30–31 Enumerate the values of the 15 available zoom levels of the service.
  • 36–40 Reconfigured the view of the map using the new projection parameter, and set the default zoom level.
  • 45–59 Specify a new layer source using PDOK's basemap tile service.

Check your browser to see the impact of the above changes. The world image from OpenStreetMap has been replaced by one of the background basemaps from PDOK.

The createMap() function does create a map object but it does not take any action in terms of focusing the map on a specific area. You want to center the map on a specific municipality, let us say Enschede or Utrecht, etc. You expect however the application to be used for any arbitrary municipality so to prevent any hardcoding of the coordinates and city names you will create a variable to hold those values. This will make it easy to automate the procedure to change municipalities in the interface.

Extend the code in the CityApp\app\Application.js file as shown in

CityApp\app\Application.jsJavaScript
  • 6–8 Define new global variables to handle context in terms of municipality.
  • 16 Use one of the new global variables to modify the title of the application's page.

You can now use the appdata.cityCoords and appdata.zoom variables in the onViewReady() function to focus the map in the chosen city. To that end, update the StatisticsController following the changes shown in

CityApp\app\statistics\StatisticsController.jsJavaScript

By now, your application should closely resemble the image shown in

There are four types of cartographic styles for the base map: brtachtergrondkaart, brtachtergrondkaartpastel, brtachtergrondkaartwater & brtachtergrondkaartgrijs. test and pick one that will be suitable for color overlays.

This is the service URL again:

https://www.pdok.nl/introductie/-/article/basisregistratie-topografie-achtergrondkaarten-brt-a-

Since you are using cities as the context of the application, it is also important to display municipal boundaries on top of the base map. This is a straightforward procedure that requires only the inclusion of a new layer. You can add a WMS layer to the map object using for example the corresponding service from PDOK.

Use the URL of the administrative boundaries service (shown below) to obtain its metadata and determine the parameters required to display municipal (Gemeenten in Dutch) boundaries, and then use that information to add a new layer to the map object.

https://service.pdok.nl/cbs/wijkenbuurten/2023/wms/v1_0?REQUEST=GetCapabilities&SERVICE=WMS

CityApp\app\statistics\StatisticsController.jsJavaScript

One more useful component for the map will be to display coordinates. OpenLayers provides functionality to show the coordinates of the position of the mouse pointer on the map.

Include the necessary changes to show coordinates in the CityApp\app\tools.js file, using the code shown in

CityApp\app\tools.jsJavaScript
  • 20–25 Add the mouse control object to the map.
  • 22 Specify the coordinate format, one decimal digit in this case.
  • 23 Declare the style to be used to display the coordinates.

Add the style definition properties for the mouse control to the end of the CityApp\styles\app.css as shown in

CityApp\app\styles\app.csscss

Check the browser to see the impact of the latest changes. depicts the look of the application with the basemap, the municipal boundaries layer and the control to display the coordinates at the mouse position.

Data Services

The activities outlined in this section should be completed in your web-accessible folder.

Before heading any further, if you have not yet created your personal Web accessible folder, check the preparation section of the WMS exercises (MapServerWMS).

Based on the requirements of the use case you have to decide what type of statistical data will be displayed in the application. Two decisions are required, one, the administrative unit level, and two, the type of statistical data. After a careful analysis on the available data, the choice has been made to use Districts as spatial representation level and, population and landuse as statistical variables.

This means you will need to have access to the descriptive data of the districts (name, code, etc.); to the geometry of the districts, both as basemap and as features; to the population values of each district; and, to the landuse per district including type and area covered. depicts a conceptual representation of the data from the point of view of the application.

The source data for the application is stored in a PostgreSQL/PostGIS server, which can be accessed using the following parameters:

Server: __DBHOST__
Port: __DBPORT__
Database: exercises
Schema: netherlands

The source data for the application can be downloaded here: Exercises Database Backup. To use it you need to create a PostgreSQL database and restore the backup provided as follows:

Using pgAdmin:

  1. Install PostgreSQL if you have not done that before.
  2. Create a new database and name it exercises.
  3. Connect to the database and create the postgis extension.
  4. Restore the backup.

Go ahead and check the netherlands schema to familiarize yourself with the data. depicts a PSM model including only the tables and attributes relevant for our application. Carefully check the attributes and relationships so that, it is clear that the structure in which the data is stored differs from the structure in which the data is needed by the application.

The PSM model depicted in shows that the landuse table has two landuse type attributes for the year 2012, class_2012 and group_2012. Both of them associated with the class_code attribute of the landuse_class table. A quick check to the tables and their attributes reveals that group_2012 stores values for aggregated landuse types, which have single digit identifiers, and that class_2012 stores values for disaggregated landuse types. Presumably the suffix 2012 indicates the year for which the values are valid. The PSM model also shows that both tables, districts and landuse, have geometric attributes. It is important to understand the spatial relationships between those two types of polygon geometries to be able to decide how to generate the data needed by the application.

shows both, districts polygons (red) and landuse polygons (grey). Analyze the data to determine how to generate landuse areas and percentages, per land use type, per district, as depicted in the model in 1.

To satisfy the needs of the application, our next task is, to create one OGC service for serving the district polygons, and one REST service to serve population per district data and landuse data.

WMS & WFS Services

The definition of the OGC service for the district data is, more or less, straightforward as you do not need any sort of data manipulation to produce the required output. The only real task here is to create the configuration file with the necessary content to deliver the data via MapServer.

To specify the new service, proceed to create the CityApp\api\adminboundaries.map file using the code shown in .

CityApp\api\adminboundaries.mapMapfile

Not much to explain here, the code in defines all the metadata elements required when implementing compliant OGC services. A couple of important things though. First make sure to use your actual s-number in lines 11 & 19. Second, in line 11 you tell MapServer which file/folder to use to report errors.

If you have not done so already, you have to create the folder to report errors and grant privileges to the server to write on it. To do that, navigate to the following URL and fill in the folder name as shown in

https://__USERHOST_HYBRID__/manage/

Once you press the Add button, the log folder will be created, and the proper rights will be assigned to the web server. Make sure you only do this for the log folder. This action gives full access to the content of this folder to the server which means that its content can be freely manipulated. DO NOT place any critical content in this folder.

Not much to explain here, the code in defines all the metadata elements required when implementing compliant OGC services. One important thing though, in line 11 you tell MapServer which file/folder to use to report errors. If you have not done so already, you have to create that folder to report errors.

Create the error reporting folder as follows:

__CODEPATH__\logs

Create the skeleton of a layer object to serve district boundaries in the CityApp\api\adminboundaries.map file using the code in

CityApp\api\adminboundaries.mapMapfile
  • 12–18 Add an output format so that the WFS response is not only available as GML but also as GeoJSON.
  • 23–56 Specify a polygon layer object to respond to WMS or WFS request for district boundaries.
  • 48–55 Define the presentation style for the WMS GetMap response.

Note that in you have not included the content of the DATA parameter of the layer 32. To generate the content for the service. First you have to come up with the corresponding SQL code to get the data from the database, and then include that code as part of the layer definition. depicts the SQL code for the query required to produce district boundary data including geometry and a few extra attributes. Make sutre to test the code to see how the results look like.

-- Districts code --SQL
  • 3 Produces the plain value of the district name.
  • 4 Uses a regular expression to remove the prefix 'District' ('Wijk' in Dutch) and the district number from the values of the district name attribute.

Update the district layer definition in the CityApp\api\adminboundaries.map file to include the required SQL code as depicted in .

CityApp\api\adminboundaries.mapMapfile
  • 17 Notice that you have not hardcoded the name of the city in the code. The query for the service uses a so-called Runtime Variable CITYNAME which allows the retrieval of data for a specific municipality. The value for this variable can be passed on as part of the service request.
  • 20–23 Runtime variables require a default value and a validation regular expression.

It is now time to test the service. Use the example request shown below to send a WFS request using the GetCapabilities operation (As always, replace the placeholder in the example request with your own s-number). Check the response to make sure that the service is working as expected.

__BASEURL_HYBRID__/cgi-bin/mapserv.exe?MAP=__SYSPATH__/CityApp/api/adminboundaries.map&SERVICE=WFS&VERSION=2.0.0&REQUEST=GetCapabilities

If there is any issue with the service response, proceed to correct the problem. Check the logs\ms_error.txt file for additional details on the cause of the problem.

Next, use the GetFeature operation with a limited number of features to test if the data is served correctly. An example request is shown below.

__BASEURL_HYBRID__/cgi-bin/mapserv.exe?MAP=__SYSPATH__/CityApp/api/adminboundaries.map&SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&TYPENAME=district&SRSNAME=EPSG:28992&MAXFEATURES=2

shows a portion of the response to the GetFeature request. Again, if there are error messages or no data is shown, go back to the logs\ms_error.txt file to check what has gone wrong, and take the corresponding corrective action(s). shows the same response but encoded as GeoJSON.

GetFeature responseXML
GetFeature response as GeoJSONJSON

If things went well, you can state that the OGC service, required by the application, is ready. Note that you can also submit a request for a WMS request using the same service endpoint.

GraphQL Endpoint

shows the data structure required by the application. If you studied the listing carefully in combination with 2, you will notice that to generate the required output, you have to engineer the SQL code to compute and aggregate the intersections between landuse polygons and district polygons. You will also notice that the web application does not require the geometry generated by the intersection operation, but the corresponding area.

Model of the district service responseJavaScript

you cannot assume that every type of landuse appears only once inside each district (see 3). This means, you need to ensure that multiple instances of the same landuse type inside a district are grouped together for the area calculation. you know that there are two attributes for landuse types, in this case you will use the aggregated attribute, group_2012, which has only nine different classes. Certainly, for each tuple in the output you also want some district details, such as, its name, code, etc. Let's put all of these conditions together in a SQL statement.

Use pgAdmin and run Query 1 from . Check the code carefully to determine which part of it corresponds to which of the conditions stated above. For now, you will assume that you are doing this for a chosen municipality (e.g. Utrecht). This way you have an example value to use in the filter expression of the queries.

-- District/Landuse intersection code --SQL
  • 4–11 Compute the area of the intersections between districts and landuse polygons only for the municipality of Utrecht.
  • 17–25 Group landuse types in each district to produce total areas per landuse type.

Next, run Query 2 from . Compare the difference between the outputs of the two queries. The second query includes a grouping operator.

Continuing on with the process of engineering the code that generates the output that fits the structure of the Web service, you move to phase two. You already have the area in square meters covered by each landuse type in every district, but you also need to know the percentage of that landuse with respect to the area of the district itself. For that computation you need to join the output of Query 2, again, with the districts table.

Compute landuse percentages per district using Query 3 from .

-- Landuse percentages --SQL
  • Determining percentages of landuse per type for every district. Note that the code corresponding to Query 2 has to be included here for the query to work (see line 6).

This output now contains landuse data as specified in the data structure required by the application service. Having said that, if you look at the results carefully, say the first six or seven records, you will notice that this query does not generate records for landuse groups that do not exist in a specific district, which is quite logical. Unfortunately, this is not optimal for automated generation of visualizations.

To make correct visualizations you need one record for each of the nine landuse groups in every district. Since those missing records simply do not exist, you need to force them into the output. Query 4 from contains a piece of SQL code that generates a series of 9 values for every district in a given municipality.

-- Series & grouping --SQL
  • 4–6 Generation of rows for all combinations of landuse groups and districts, namely a cartesian product.
  • 12–26 Checking that the district-landuse_group pairs approach using a cartesian product actually works (see ). You used limited attributes in the output here as this is only a test.

Now you can join the list of landuse groups, which is generated in an ad hoc manner Query 4, with the data from Query 3, to produce complete sets of landuse/district value pairs. Execute Query 5 from to check if this approach works. Since this is only a test of the approach, you have only produced some of the required attributes in the output.

So far so good but you are not finish yet. If you revisit the model in 1, you will notice that the landuse data (landuse_2012) is to be delivered as an array, and not as individual records for every district. The reason for that is that the district record itself needs other attributes like, population, name, etc.

Use the code in to aggregate landuse data into a JSON array.

-- Landuse as aggregate attributes per district --SQL

shows the result of our latest query. This time the output has one attribute with the district code and one attribute with an array of landuse data values, exactly as needed. You have also added the area size of every district in square kilometers to the output (see line 11). Unfortunately, the SQL command that you have available to create the landuse array (json_agg), does not allow us to control the name of the variables inside the JSON object. What you get in the output as variable names is: "f1", "f2", etc. This is unfortunate but not necessarily a big problem. There is nothing you can do about it right now, but you certainly have to keep this in mind, as the issue needs to be addressed at some point later on.

At this point, the only thing left to do in terms of the content of the output, is to include additional attributes for every district. shows the code of the complete query that generates district data as required by the application.

-- Districts statistical data --SQL

You can now proceed to create the GraphQL endpoint. This endpoint is the one that the application will use to obtain the data. Remember that a Web application cannot directly communicate with a database. The role of this endpoint is to listen to client's requests, extract data from the database, and then send a response back. For this endpoint you will use Postgraphile.

Use the code in to create a function in the database, which will be executed through Postgraphile to extract the landuse statistics data.

-- Get Statistics Function --SQL

Install Postgraphile by running the command shown in . This should be done in the terminal wihtin the interface of Visual Studio Code.

Install PostgraphilePowerShell

Start Postgraphile by running the command shown in . This will map the database structure to GraphQL.

Start PostgraphilePowerShell

The output shows that two services were started GraphQL API and GraphiQL GUI/IDE. The former is the GraphQL endpoint that the app will use to get the landuse statistics data, and the latter is a visual interface to test GraphQL queries and mutations. Next, you will use the latter to test the get_landuse_stats function.

Open the GraphiQL GUI/IDE in your browser using the corresponding URL:

For example:

http://localhost:5000/graphiql

Execute the code in in the GraphiQL GUI/IDE, this will produce the results shown in .

GraphQL function executionJSON

It is important to note that GraphQL converts snake_case to camelCase. For this reason, the s-number_get_landuse_stats function is refer to as s-numberGetLanduseStats. Additionally, pay close attention to the structure of the response, as this will be important for extracting the data.

If the server takes too long to produce the output, users will feel uneasy, sometimes even believing that the service is not working. In such cases you will need a faster way to generate the output. There are various possibilities, but this is nor the place neither the time to discuss them (query optimization falls well outside the scope of this exercise), But a quick solution will be for you to store the results of the query in a so-called Materialized View, and then use that view as source for the GraphQL service.

Static Visualizations

You can now proceed to create the visualizations for the various statistical variables for which you have data. You will start with a bar chart to show population data per district, and then follow with a pie chart to show landuse data.

Bar Chart

You have already created the back-end service that produces the data, now you will create the front-end components to handle the data in the interface starting with the model to handle district data.

shows the code required for the definition of the model for districts data using the service you just created. Use the code to create the DistrictModel file.

CityApp\app\statistics\DistrictModel.jsJavaScript
  • 3–5 Define the model object.
  • 14–42 Override the default load() function of the new model to include a dynamic ajax request.
  • 13–43 Specify the function that you will use to retrieve the data.
  • 31 Extracts the data from the response object. Observe that the structure of the object consides with your GraphQL query.
  • 32$ndah;38 Renames the columns inside the landuse2012 elements, and parse them into JSON objects.

You can now use the controller to retrieve district data using the getDistricts() function of the model. Note that this function requires a parameter to indicate the municipality for which data should be retrieved.

Include the model in the application by adapting the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 4 Import the districts load function.
  • 12 Create a variable to store districts' data.
  • 36–41 Trigger the getDistricts() function to load the data and specify the corresponding callback actions.
  • 37 Extracts the data from the response object.
  • 38 Execute the drawBarChart() function as a response to a successful data load request.
  • 49–51 Specify the skeleton of the function that will render the bar chart. The function takes one parameter which corresponds to the container where the chart should be included.

Temporarily the drawBarChart() function will simply output to the console the data that has been stored in the district variable of the StatsCtrl object (the controller of this view). You can check the console to determine if the data was retrieved correctly (see ). Note that this variable was populated outside this function. In addition, if you remember, the service of the districts data requires a parameter which has also been added to the request. Study the code in carefully. Now let's create the real code for the drawBarChart() function, for that purpose you will use functionality form the D3 library.

Proceed to add the code of the drawBarChart() function to the StatisticsController as shown in

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 9 Include the d3.js functions.
  • 16 Create a variable for the bar chart object.
  • 25–27 Select the HTML element that will contain the bar chart, and create a placeholder inside.
  • 29–32 Compute the dimensions of the chart (width, height, etc.)
  • 42–46 Specify the functions that will generate population bars.
  • 50 Add the data object that contains the values to display in the chart.
  • 51–58 Create bars for every single district record.
  • 54–55 Assign the label property of the districts for the values of the x axis, and the pop_2020 property for the values of the y axis.
  • 60 Store the bar chart object in a controller variable.

Update the StatisticsController to include the axis elements of the bar chart as depicted in .

CityApp\app\statistics\StatisticsController.jsJavaScript

Keep an eye on the browser to see the results of the changes being made. having added the functionality of the drawBarChart() function, the interface of your application should now look similar to the image shown in

You will notice the labels of the x axis do not show the district names but district codes. Since there is not enough space in the axis, it makes no sense to put names there. You will have to find another way to tell the user which bar corresponds to which district. For now you need to take care of the labels for the axes and certainly the style of the bars.

Add the code for the style elements of the bar chart to the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 13–16 Define stroke and fill properties for the population bars.
  • 23–31 & 37–45 Add labels and their corresponding style for the x and y axes.

Check the application in the browser once more to see the result. the chart now features axes labels and styled bars. As always if there are errors, make sure to repair them before moving on.

Districts WMS

Since you are working with district as reference administrative unit the corresponding boundaries should also be included. For this purpose, you also have the OGC service you just created. You can use it to overlay the districts on the basemap. This is a straightforward procedure that requires only the inclusion of a new layer. Since the idea is, for now, data display only, you can add the districts as a WMS layer to the map object.

Use the code in to update the StatisticsController to add a layer to the map using the District WMS service.

CityApp\app\statistics\StatisticsController.jsJavaScript

Go back to the browser and check the changes to the interface of the application. Compare your results with the image shown in .

Pie Chart

To produce the landuse pie chart you can follow the same approach you used with the bar chart. You create a function responsible for the chart and you make a call to that function right after the data has been received by the application. At the start of the application the pie chart will show the distribution of the different groups of landuse at the city level. Showing data at the district level can be done as part of user interaction.

The code for the pie chart function, drawPieChart() appears in . Use it to modify the StatisticsController accordingly. Just a very rudimentary function for the time being.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 8 Create a variable for the pie chart object.
  • 17 Trigger the drawPieChart() function.
  • 31 Include the SVG container of the pie chart.

The decision to create an initial charts at the municipality level is totally valid, however, the REST service that you created does not provide data at the city level. It is disaggregated at district level. This is an inconvenience, but not necessarily a problem. You can certainly produce the aggregated values from the response that you get. An additional consideration is that the raw data comes in absolute values which are not what you need for the pie chart. In conclusion you need to compute aggregated distribution values for the default pie chart so that it can be rendered in the application.

Include the code for the computation of landuse values at the city level shown in in the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 11 Create an array with the identifiers of the landuse classes (groups).
  • 12 Compute the total area of the municipality.
  • 14–16 Loop through the districts array and compute the total area covered by each landuse class.
  • 17–21 Create a new array with the totals. The results of this computation get stored in the cityTotals variable.

With the landuse computation hurdle out of the way, you can move on to generate the code for the pie chart. A similar scenario as with the bar chart plays out. You need to specify the configuration elements, width, height, margins, etc. The necessary code for the basic pie chart elements is shown in , you can use it to update your StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript

Next, update the StatisticsController to include the pie generation functions as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript

Have a look at the application in the browser and check the results of the latest changes. shows the current status of the interface of the application. At the moment, since no style values where specified, the pie slices have no color, or rather they are drawn using the default color value, which is black.

At the moment, you can clearly see in the pie chart, the various slices and discriminate their sizes, but the lack of a visual variable makes it difficult to make sense of what the pie is trying to show. Given the nature of data that you have, different types of landuse, color (hue) will be an appropriate choice of visual variable to use in the pie. Contrary to the bar chart where you used a single color, in the pie chart, color plays a much more significant role in the basic rendering of the pie chart and potentially also when responding to user interaction. Now how do you manage the data about colors?

Like always you can hardcode the color values inside the drawPieChart function, or you can use variables and specify color values independently of the function where they are needed. Hardcoding values is almost never a good idea, and in this case, since you also want to create a level of interactivity, this is not an option. To manage the data for the style of the pie slices you will need to specify a kind of color model.

To specify the ColorSets object, create a new file called CityApp\app\statistics\ColorModel.js and place inside the code shown in .

CityApp\app\statistics\ColorModel.jsJavaScript

An important step that you have taken here is that, after checking the definition of every landuse class (group), you have specified, for every group/class, a color that is representative of that group/class. For example, blue for water, green for vegetation, and so on and so forth. For every group, you include a value for the stroke and a value for the fill of the corresponding slice. Unlike the districts model, here the data is specified inline, meaning that you do not need to create an additional request to populate this object.

Add the code to use the ColorSets object in the StatisticsController matching .

CityApp\app\statistics\StatisticsController.jsJavaScript

Check the application to see each available landuse class/group in the pie chart now rendered with different colors. To enhance the quality of the pie chart you will now include labels for the various slices. You will use the landuse percentage attribute that each landuse group has.

Add the code for the pie labels to the StatisticsController as depicted in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 18–20 Specify the position function for the labels.
  • 22–32 Create a new set of text elements as labels using the cityTotals variable as data source.
  • 32 Notice that a condition has been used, so that when the slice is too small, no label is produced.

Check the browser to see the pie including slice labels.

To improve the readability of the pie you can also rotate the labels of the slices to match the angle of the slice itself. Modify the StatisticsController using the code in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 8–11 Specify the angle computation functions.
  • 21 Use the new function to rotate the labels of the slices.

Check the application again to see impact of the latest code changes. presents an image of the corresponding result.

Pie Chart Legend

The pie chart now depicts landuse values as expected and the pie slices are properly colored and labelled. To complete the pie chart though, you need to tell the users what do the colors represent, which means you need a legend. In the pie legend you will include the color of each landuse class (group) and its corresponding name or description. Just like with the previous chart objects you have created thus far, you need a function to produce the legend.

Update the StatisticsController to include the pie legend object as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript

There is nothing new with this change that you have not already seen before. With the drawPieLegend() function in place, you can start the procedure to render the legend. Ok!, but wait a moment, you do not have data with the name and/or description of the landuse classes. This is of course not part of the districts dataset which only provides class identifiers (g1, g2, ... , g9). Well, time to use GraphQL to retrieve the class names and a corresponding class identifier.

Since this data is only needed to produce the legend, you will not create a model for it, you will simply access it using d3's functionality as part of the legend generation process. Use the new code in to update the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 18 Make a request to the get_landuse_clases function to retrieve the data for the legend. This is a simple function, feel free to explore it in the database.
  • 36–38 Callback function to handle a successful data request.
  • 39–41 Function to be executed in case of response errors.

Head to the browser and check the console (you can use CTRL+F5 to force a refresh) to see it the message containing the legend data is shown (20 writes the response data to the console). if no data is shown in the console, then check back 19 and/or 20 to determine the corrections that need to be made.

Next, procced to include the code of the success callback() function of the data load request to the StatisticsController using as a reference.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 7 Extracts the data from the response object.
  • 9–11 Compute the size for each legend item (each landuse class) based on the number of classes.
  • 15 Assign the content of the service response (landuseClasses) as the data for the legend content.
  • 26–37 Create legend elements (squares) for each type of landuse class using the getColorSet() function.

If you check the application, you will notice that the interface now partially includes a legend. The labels of the legend are still missing, so you need to take care of that.

Add the code for the legend labels to the StatisticsController. Use as a reference.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 17–22 Add labels for every legend item using the class_name property of each landuse record.
  • 25–31 Include the title for the legend.

Check the application in your browser and compare the result with the image shown in , which depicts the status of the interface including all the code for the generation of static visualizations.

You might have noticed that there are only 7 classes present in the pie chart. It seems that in Utrecht there are no areas classified as coastal water or foreign land. It might be then an idea to remove the extra classes form the legend. It all depends on who the users are and what do they know about the data.

Interactivity

You have now filled the interface with visual elements, they do provide general information to the users on population figures and landuse coverage, but there is not mechanism in place yet, for the users interact with the visualizations and perhaps get a better insight on the data they are looking at. The first task here is to decide what kind of interaction is needed or rather expected by the users. You need to analyse the possibilities and then code accordingly.

By default, the application depicts data at the municipal level. You have population bars for all districts, boundaries for all districts and land use for the whole municipality but somewhat disassociated from the map and chart objects. given this fact, you can argue that if the user interacts with one of the district polygons on the map, or one of the population bars in the chart, its corresponding element should also become recognizable. This means that if the user goes with the mouse over a population bar, its corresponding district should be highlighted. Simultaneously, you can also provide additional data on the corresponding district, for instance, the name of the district, its area. now how about the pie chart? should it be responding to the user interacting with the district. I would say yes, you can also update the pie to display the landuse distribution for the district under scrutiny.

How about the pie chart? What would a user expect to see/happen when interacting with the slices in the pie? Well, the pie slice depicts a type of landuse, so you can show data about that particular landuse per district. And, you can do this on the map by drawing a choropleth. That will be the user a feeling of where that landuse is more prominent or less prominent. In addition, you can replace the population bars with landuse data on each district. That sounds reasonable application behavior. Ok let us get at it and start adding some interactivity to our application (Note that this exercise is only an example of what kind of interactivity should be added. There are plenty of options to be used and of course implemented).

Bar Chart Interaction

You have chosen the bar chart, to start implementing the recipe that you just laid out in the introduction. To be able to respond to user interaction with the bar chart, you first need to catch the corresponding events. In this particular case you have three events: first, the event of entering a bar, second, the event of moving inside a bar, and third, the event of leaving a bar. You will assign the responsibility to handle each event to a different function.

Proceed to create the necessary code to catch and respond to the relevant user events. contains the changes to be added to the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 9–19 Specfy three new functions. Each function will handle one of the three relevant events.
  • 41–43 Register the mouseover, mousemove and mouseout events, and assign the corresponding handler functions to each event.

Temporarily you have just generated log message onto the console as function actions to check that the application behaves as expected and that you have the right events. Interact with the bar chart in the application and check the console. Check the messages as you move your mouse over the population bars. Look at the counter of the mousemove event in the console (on the right-hand side). This event occurs multiple times, whereas the other two only occur once per bar. Each event will also return a parameter with the data about the object that triggered the event. This data tells us which bar the user is interacting with.

You can now add the code to respond correctly to the occurrence of the chosen events. Update the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 6–9 Provide feedback to the user by changing the style of the population bar when the mouse pointer enters a bar.
  • 17–20 Return the bar to its original style as the mouse pointer goes out of it.

Check the application and use the mouse to interact with the bars to see the response. Simple trick really, change the color of the bar on the mouseover event. Revert to the original color on the mouseout event. As it is now, the user can estimate the population value of each district using the chart's y axis. To avoid any guessing, you can add more functionality to the bar interaction functions to provide such information to the user.

Enhance the quality of the user's interaction with the bars, by displaying additional data about the bar using a popup. To get this done, update the StatisticsController using the code in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 5–8 Create a popup object and add it to the page (this will happen only the first time the darwBarChart() function is executed).
  • 18–20 Generate the content of the popup using the population and name properties of the district.
  • 21–24 Make the popup visible by changing its style properties.
  • 29–31 Change the position of the popup as the position of the mouse pointer changes.
  • 41–43 Hide the popup when the mouse pointer leaves the bar.

Add the parameters for the style of the bar chart popup to the CityApp\styles\app.css file. The corresponding code is shown in .

CityApp\styles\app.cssCSS

Head to the browser to try and test the latest additions (see ). Check the behavior of the popup. Study the code to see what part is working on which action. Perhaps tweak it a little bit, change its behaviour one way or another to see the effect. For example, try display the popup to the left of the mouse rather than to the right of it, or perhaps on top of the mouse. Try to make the popup disappear slowly rather than instantaneously. Whatever you do, in the end make sure that there are no errors before moving on. Note that doing nothing is also considered an oversight.

Map Interaction

You do already have district boundaries visible in the map, but first, they include all districts in all municipalities, and second, they are rendered as an image because you are using a WMS as a source. Interacting with the district as an image will require the use of the click event, and that is not a very practical way to work in this case. To be able to use other events, such as mouseover for example, you need to have vector features. To that end you can include a vector layer that specifically shows the districts of the city that the application is focused on.

You need to create the application components to add the new layer, starting with the global variables. To that end, use the code in to update the CityApp\app\Application.js file.

CityApp\app\Application.jsJavaScript
  • 9–11 Specify global variables common to all the local layers.
  • 14–16 Populate the wfsUrl variable with the default WFS request paremeters.

Next, include the vector districts to the map using the code in . Add this code to the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 32 & 34 Use the newly specified global variables in the existing WMS layer, instead of hardcoded values.
  • 58–66 Define the style to be applied to the district vectors.
  • 68–76 Add a vector layer for the districts of the city being represented in the application.

The district features that have been drawn on the map, can be configured to respond to user's actions in a similar way as the population bars. In the case of the vector layer though, you have to use the event handling mechanism provided by OpenLayers and not that of d3.js that you used with the bar chart. This is because you are using OpenLayers as the underlying library in charge of the map object. Following OpenLayers API constructs, you will define an interaction object and will make it responsible to modify the style of the districts as a response to mouse events on the district layer.

contains the code needed for responding to mouse movement events on the map, use it to update the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 38–61 Specify the style to use for selected vector features, with a slightly different opacity to the district vectors and with a label.
  • 63–68 Define an interaction object to respond to events in the cityDistricts layer.

Check the interface of the application to see the effect of the latest changes. Compare your results with the image shown in . Correct any errors if necessary.

When using events and/or combining different libraries as you have done here (Webix & OpenLayers) you have to be very careful. It is important to be sure to respond properly to all relevant events for the objects managed by both libraries. You have configure OpenLayers to properly react to the mouse leaving the district polygon, but what if the mouse leaves the map before first leaving the district polygon. In such case, the label will not be removed, because the corresponding event never took place. If you zoom-in on the map so that some of the districts are partially contained within the map view and partially hidden, and then you interact with a partially visible district, you will see the problem as you leave the container of the map. You need to make sure that any selected district is unselected if the mouse pointer leaves the map before leaving the area of the district itself.

Use the mouseout event on the map container element to clear any features that might be registered by the interaction object, activeFeatures. To that end modify the StatisticsController using the code shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 10–12 Select the DOM object of the panel containing the map (sts_map_div), target its mouseout event, and attach a function to be executed as a response. The function simply clears any features existing in the interaction object.

Test the application and check again by recreating the problem conditions, the unexpected interaction behavior should have been corrected now.

Sync Map & Bar Chart

The following level of interactivity involves the creation of relationships between elements in the application. To start you will connect the vector layer and the bar chart. The idea is to have features in the map highlighted when the user interacts with its corresponding bar and vice versa.

Extend the functions that handle events in the bar chart by adding the code in to the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 20–22 Find out which district feature needs to be highlighted by searching through the array of vector features using the code of the district obtained from the bar chart. Then add the feature to the interaction object of the map.

If these changes work well, the district polygons should now be highlighted when interacting with the bars in the chart. This of course in addition to the existing response already in place, the popup. Reload the application and test its behavior. Keep an eye in the console for any unwanted messages.

Next, you will implement the reverse operation. Making sure that the bars respond to user interaction with the vector layer. But before you do that, it is critical to understand that you do not want to create an infinite loop with this approach. Let's say, you assume that a = highlight-bar and b = highlight-feature. Then what you just did in 3 is, you have added a condition that says when a then b, which is perfectly fine. But now you are also going to add the condition when b then a, which then creates an infinite loop. To avoid this issue, you can simply distinguish between action a or b being triggered by user interaction or by the application itself. This can be done using a control variable.

Add the code shown in to the StatisticsController to respond to user interaction with the vector features in the map.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 11 Define a control variable and set its default value to true.
  • 22–29 Catch the event when a vector feature is added to map interaction object.
  • 23–28 Check the value of the control variable.
  • 24–27 If the value of the control variable is true, indicating that it is a user action, then change the style of the corresponding bar.
  • 31–38 Just like when features are added (see above), respond to the event when features are removed from the map interaction object.
  • 68 Set the value of the control variable to false, to indicate that the action is being triggered by the application and not the user.
  • 89 Return the control variable to its default value.

Try out the application to test the changes. The interactivity should now work both ways; when the user interacts with the bars and when the user interacts with the vector features on the map. shows an image of one of the cases.

Syncing the Pie Chart

Just like you did with the chart and the map, you want the pie chart to respond to user interaction, both interaction with the pie chart itself and interaction with other elements of the application. Let's start by modifying the pie chart as a response to interactions with the bar chart. When the user interacts with a bar in the chart, the focus moves from the city level to the district level. That means that it is appropriate for the pie chart to also present landuse data at the district level.

The basic elements to implement the pie chart response to user interaction are shown in , use the listing to update the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 3–10 Include the skeleton of the function that will redraw the pie chart.
  • 31 Trigger the redrawPieChart as part of the response to the mouseover event in the bar chart.

Revisit the application. The redrawPieChart() function call includes two parameters, the landuse property of the district record.landuse2012, and name property of the district, record.name (31). The redrawPieChart() function, at this moment, only outputs the landuse data to the console.

Now, let's add the actual code of the redrawPieChart() function. Update the StatisticsController using the code shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 13–19 Define the pie interpolation function, so that you can control the transition from the old data (before interaction) to the new data (during interaction).
  • 33 Assign the pieData object as the data source for the pie.
  • 36 Redraw the slices using the sliceTween() transition function.
  • 38–49 Draw the labels for the updated pie slices.
  • 51–52 Update the pie chart title.

Test the application by interacting with the bars in the bar chart. In principle the pie chart should respond to the interaction and display the landuse data of the corresponding district. Notice that the label of the chart is also updated.

This is only half of the job; you also need to Return the pie to its default state when the mouse leaves the bar. Right now, this does not happen, and therefore you can say that the application is in an inconsistent state. Since you already have the required function, redrawPieChart(), you only need to call it using the data for the whole city.

The code for restoring the pie chart to the default state is shown in , update the StatisticsController accordingly.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 9 Call the redrawPieChart() function as part of the mouseout event, using as parameters variables with data for the whole city.

This should have done the trick. Now the application is consistent again. Refresh your application and test the changes. If all went well, the pie chart should now respond to both, the mouseover event and the mouseout event on any of the bars.

You want the same behavior when interacting with the district features. The code for that is shown in . Use it and modify the StatisticsController accordingly.

CityApp\app\statistics\StatisticsController.jsJavaScript

Same reasoning as before, determine in which district the mouse is hovering over, and then send its landuse data and name to the redrawPieChart() function 10–13. Then, when the mouse leaves the vector feature of the district, send the data on the totals of the city, and the city's name to the function (24). See for a screenshot of the result.

Dynamic Visualizations

Thus far, you have added code to the application to handle interactivity which is initiated on the bars or the vector layer. Now you need to add code to handle user interaction that starts trough the pie chart. As discussed in the introduction of the Interactivity section, this interaction calls for different response. In this case you are not going to display more data on the same type that is already presented on the bar chart and the district layer. No, you will use different data and as such potentially different visualizations. So if the interaction starts in the pie chart, the bars of population will be removed and landuse bars for all districts on the selected landuse class will be drawn. Simultaneously the same landuse values will be shown in the vector layer in the form of a choropleth.

Generating Choropleths

You start by catching the relevant events in the pie chart, and then associate functions to execute the actions expected as a response to those events.

Include the new functions, and the event listeners, in the StatisticsController as depicted in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 9–12 & 14–17 Functions to respond to interaction events with the pie chart.
  • 38–39 Associate the onMouseoverPie() and onMouseoutOfPie() functions to their corresponding events.

You can see in the console, which data is passed to the onMouseoverPie() function as response to the occurrence of the mouseover event. The slice variable gives us access to the landuse class property of the district, which you can use to generate the data needed to respond to the interaction, and to organize the actual interaction response.

The first action of the onMouseoverPie() function is, emphazising in the legend the landuse class that the users is interacting with. Then generating the data for the choropleth. Use the code in to update the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 7–11 Change the opacity of the non-relevant items of the legend to 20%.
  • 13–23 Populate the choroData variable with the area and percentage of the chosen landuse class in every district.

The content of the choroData variable is available in the console, check it out (see ). You can use the content of this array to create the choropleth of the corresponding landuse class. As always, you will create a function for this, which makes things easier when it comes to code reuse.

Next, add the code of the showChoropleth() function to the StatisticsController as depicted in .

CityApp\app\statistics\StatisticsController.jsJavaScript

No need for explanation on the snippet above as you have followed this path a number of times before. To draw the choropleth, you need to determine how many value (intensity) levels you want to have. In this case you do not have many districts so four intensity levels will suffice. You also need to create the style that uses the values of the four intensity levels to render vector polygons for each district. For this to work you need to specify the intensity values for each landuse class based on its specific color, for example, four intensity values in the orange range for the buildings landuse class. You already have an application object to handle color data, so you only need to extend it.

Use the new code shown in , in the ColorModel, to extend the it to include intensity values for every group.

CityApp\app\statistics\StatisticsController.jsJavaScript

Now, include the code of the showChoropleth() function in the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 15 & 16 Determine the minimum and maximum area value for the landuse class.
  • 14–17 Compute the ranges for the four levels of the choropleth.
  • 18 Get the color for the boundary lines of the districts.
  • 20–49 Specify the style function to be used to render the vector features of the districts.
  • 22–26 Determine which intensity value should be used for every district based on its area.
  • 27 Convert the hex color value to the corresponding rgb value.
  • 28 Add a transparency component to the rgb value.
  • 30–36 Style parameters for the district vectors.
  • 37–46 Style parameters for the labels (district codes).
  • 50 Apply the style to the vector layer of the city's districts.

Interact with the application and move your mouse over the various slices of the pie. Every time you move over a slice, a choropleth for that class should be shown in the map. shows the image of one of the resulting choropleths.

You also need to restore the legend and the vector layer back to its default state once the mouse pointer leaves the pie chart. Make the necessary changes in the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 10 & 34 Make the default style variable accessible at the controller level.
  • 50 Update the city district layer using the style variable with the new scope.
  • 60–62 Apply the default style to the districts vector layer as part of the response to the mouseout event of the pie.

Check if the application behaves as expected. If so the default visualization of the vector layer should be in place when the mouse moves out of the pie chart.

Rendering Landuse Bars

The next action that you are going to take is replacing the population data with landuse data in the bar chart as part of the pie chart interaction response.

Update the StatisticsController as shown in . This listing contains the basic code structure required to redraw the bar chart.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 8–16 Create the skeleton of the function to redraw the bars using landuse values.
  • 47–52 Call the redrawBars() function with landuse data (choroData) and the additional parameters for the chart.

Include the code for the redrawBars() function in the StatisticsController using as a reference.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 12–14 Compute the domain of the y axis
  • 17 Specify the barsData variable as the data source for the bars.
  • 16–26 Draw new bars based on landuse area of the relevant landuse class for each district.

Go ahead and interact with the application to test the new function. The bar chart should also now respond to the user's interaction with the pie slices. Check if the label of the y axis also responds correctly.

Just as before you are only halfway through here. You certainly want to return the bars to their original state when the mouse goes out of the pie chart, so that you keep the interface consistent. This time the rollback operation is a little bit more challenging because the datasets are different so the function that redraws the bars need some more robustness. The redrawBars() function needs to know if it has to draw the bars using population data or landuse data. You will use a boolean variable to allow the function to determine which data to use.

Configure the application to include functionality to return the bar chart to its default state, population data per district, by modifying the StatisticsController based on .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 7 Include the control variable for the type of data to use for the bar chart.
  • 43 Set the populationBars variable value to false, when landuse data should be used for the bars.
  • 57–62 Trigger the redrawBars() function using population data.

Now you need to add the necessary control flow to the redrawBars() function to behave correctly based on the value of the populationBars variable. Modify the redrawBars() function's code in the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 6 & 13 & 16 Change the property to be used in the functions based on the value of the populationBars variable.

Interact with the application and check that the correct response occurs in all the cases. If this is not the case then, analyze the wrong behavior and get back to the snippets above and fix the problem.

Now that you are displaying data on area sizes, it is a good idea to add a scale bar to provide the proper reference to the user. Update the function that creates the map object in the CityApp\app\tools.js file to incorporate a scale bar. The necessary code is shown in

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 4 Import the ScaleLine control.
  • 18 specify the function that OpenLayers will use to compute the map resolution depending on the pixel size of user's screen.
  • 33–37 Add a scale bar control to the map object (by default it shows on the bottom-left corner of the map).

Extra Legend Elements

When the choropleths are displayed, the user needs a reference as to the meaning of the colors. Yes, there are only four classes but still, you need to tell the user the ranges of the classes. This means you need a legend for the choropleth. The key question now is where to place it. The first thing is, it should not be permanent like the landuse classes legend, because it only makes sense to see it when the corresponding data is visible. Second, the content of the legend also changes as the users move between the various slices. You can, for example, use a floating window that becomes visible whenever necessary.

Create the CityApp\app\statistics\ViewWindows.js file, and include the code for the legend window as depicted in .

CityApp\app\statistics\ViewWindows.jsJavaScript

Connect the ChoroplethLegend window to the application by updating the StatisticsController as shown in .

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 26 Show the ChoroplethLegend window when a choropleth-based visualization of the city's districts is active. The position of the window is defined relative to the map container. The map container's id sts_map is used as a reference for the specified positional parameters. The position values are chosen making sure not to cover the scale bar.
  • 49 Hide the window when it is not needed anymore.

Now, use , to include the code for the generation of the content of the ChoroplethLegend window, in the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript
  • 12–15 Compute the minimum and maximum percentage values for each intensity value in the choropleth.
  • 16–25 Replace the content of the legend window with data of the visible choropleth. This includes the title for the legend and the limit values of the landuse class.
  • 27–34 Add a svg object containing colored squares for the four intensity values.

Check the application once more to test the functionality of the legends of the choropleths. The legend should dynamically adjust to the values of every landuse class in the pie.

Are you done? Perhaps you are. Let me ask you a question. Can you tell which district has the largest area covered by buildings. If you place your mouse over the slice for 'Buildings' in the pie, you will come with an answer. You will say district 06. Then I will say, OK, but which district is district 06?. Then, you will certainly be able to find out, but not while interacting with the pie chart. So, I would say you are not done yet.

Add the code for displaying district names to the CityApp\app\statistics\ViewWindows.js file using as a reference.

CityApp\app\statistics\ViewWindows.jsJavaScript

conatins the necessary code to show and hide the district names window as needed. Add the code to the StatisticsController.

CityApp\app\statistics\StatisticsController.jsJavaScript

Finally, include the code for generating the content of the DistrictList window into the StatisticsController. shows the necessary code.

CityApp\app\statistics\StatisticsController.jsJavaScript

Head to the application and run a few tests. Two floating windows should now be available to the user when interacting with the pie chart, giving the user all the information needed to make sense of the data being shown. shows an image of one interaction state.

External Events

Throughout this exercise, you have created application objects, identified relevant events on these objects, and create the expected response to the occurrence of the events. There are however other events that might interfere with the correct behavior of the application but that are not triggered of our application objects. Try resizing the browser for example. If you do so you will notice that not all the objects of our application respond accordingly. The bar chart does not react at all. You need to make sure that these external event does not impact the application negatively.

Use the code shown in to update the file StatisticsController. This will take care of the browser resize event issue.

CityApp\app\statistics\StatisticsController.jsJavaScript

Study the code carefully so that you understand what is going on. The technique used here is the same you have used in similar cases throughout this exercise.

Choose any municipality of your preference and make the necessary changes for the application to work with the data of the chosen municipality.