<<

Institutionen för datavetenskap Department of Computer and Information Science

Final thesis

An Evaluation of Map Engines

by Alexander Magnusson

LIU-IDA/LITH-EX-G—12/022—SE

2012-10-08

Linköpings universitet Linköpings universitet SE-581 83 Linköping, Sweden 581 83 Linköping Linköping University Department of Computer and Information Science

Final Thesis An Evaluation of Map Engines

by Alexander Magnusson

LIU-IDA/LITH-EX-G—12/022—SE

2012-10-08

Supervisors: Erik Berglund Johan Hagelin Examiner: Erik Berglund På svenska

Detta dokument hålls tillgängligt på Internet – eller dess framtida ersättare – under en längre tid från publiceringsdatum under förutsättning att inga extra- ordinära omständigheter uppstår. Tillgång till dokumentet innebär tillstånd för var och en att läsa, ladda ner, skriva ut enstaka kopior för enskilt bruk och att använda det oförändrat för ickekommersiell forskning och för undervisning. Överföring av upphovsrätten vid en senare tidpunkt kan inte upphäva detta tillstånd. All annan användning av dokumentet kräver upphovsmannens medgivande. För att garantera äktheten, säkerheten och tillgängligheten finns det lösningar av teknisk och administrativ art. Upphovsmannens ideella rätt innefattar rätt att bli nämnd som upphovsman i den omfattning som god sed kräver vid användning av dokumentet på ovan beskrivna sätt samt skydd mot att dokumentet ändras eller presenteras i sådan form eller i sådant sammanhang som är kränkande för upphovsmannens litterära eller konstnärliga anseende eller egenart. För ytterligare information om Linköping University Electronic Press se förlagets hemsida http://www.ep.liu.se/

In English

The publishers will keep this document online on the Internet - or its possible replacement - for a considerable time from the date of publication barring exceptional circumstances. The online availability of the document implies a permanent permission for anyone to read, to download, to print out single copies for your own use and to use it unchanged for any non-commercial research and educational purpose. Subsequent transfers of copyright cannot revoke this permission. All other uses of the document are conditional on the consent of the copyright owner. The publisher has taken technical and administrative measures to assure authenticity, security and accessibility. According to intellectual property law the author has the right to be mentioned when his/her work is accessed as described above and to be protected against infringement. For additional information about the Linköping University Electronic Press and its procedures for publication and for assurance of document integrity, please refer to its WWW home page: http://www.ep.liu.se/

© Alexander Magnusson Disclaimer

This report is the culmination of a bachelor thesis. The company Saab AB has provided me with a working environment and the necessary equipment (software and hardware). All opinions, judgements and subjective thoughts presented in this report belongs to the author and does not necessarily reflect the views of Saab. Acknowledgements

I would like to thank my two supervisors, Johan Hagelin at Saab AB, and Erik Berglund at Link¨opingUniversity, for their assistance and guidance during this work. Furthermore I extend my thanks to Mattias Lindblad for helping me with various technical issues, Henrik Nyberg for providing me with necessary test data, Patrik Andersson for information and material regarding OGC, and all my other co-workers at Saab.

Also I would like to thank the involved companies: Carmenta, Envitia, Esri and Luciad for lending their products and giving helpful support, and everyone contributing to WorldWind. Abstract

Geographic data is often used and a necessary part for various tasks, examples could be city planning, air-traffic control and navigation. In today’s modern computer systems are an integral part for accomplishing such tasks. Consequently, software is required to handle geographic data.

This report is a study of a number of map engines used in such software. The choice of which map engine to use for producing the desired software is important. A particular map engine might not live up to the requirements set up.

First I will examine the map engines from a more subjective point of view by developing a basic application. This phase evaluates how easy it is for a software developer to use this map engine. Once a basic application using this map engine has been produced it will be tested. The testing consists of both evaluating the functional capabilities and performance. Summary

Different philosophies Investigating the map engines led to an important discovery regarding their reliance of preprocessed data. LuciadLightspeed and WorldWind are intended to produce entirely standalone applications that function by themselves. The other three, Carmenta Engine, MapLink Pro and ArcGIS Runtime to varying degrees relies on a studio application that takes data, processes it, and delivers it in a format for the client application to use. The degree of reliance corresponds to the to the order they were mentioned in, Carmenta Engine is the least dependent and ArcGIS Runtime the most. Comparing the map engines is sometimes difficult due to this fundamental difference.

Format support LuciadLightspeed performs the best, followed by Carmenta Engine. Both map engines can read all the formats looked at in this project. LuciadLight- speed can use all OGC services while Carmenta Engine cannot use WFS services and the support for WCS services have some issues. It should be said that all map engines can use a WMS service.

Trying to give a simple ranking of MapLink Pro, ArcGIS Runtime and WorldWind is a bit difficult, WorldWind is intended to produce a standalone application whereas the two other to varying degrees relies on a studio application to ”preprocess” data. WorldWind has some shortcomings in format support, ArcGIS Runtime relies almost wholly on its studio application and MapLink Pro does that too for some formats while reading data straight away requires a high degree of effort.

This test and judgement is biased in favour of map engines that directly reads data formats. MapLink Pro and ArcGIS Runtime has quite good support when also taking the studio application into account.

Performance In the test on handling symbols WorldWind performs the best, closely followed by MapLink Pro and LuciadLightspeed. Carmenta Engine and ArcGIS Runtime perform relatively poorly compared to the others and end up second last and last respectively. When it comes to handling imagery LuciadLightspeed performs the best overall. There are some dissimilarities between the map engines that make a further ranking difficult, but by large they perform equally well. However WorldWind processes images before displaying them. If an image is relatively ”small” this is not an issue, but if the image is ”large” the processing takes some time. ArcGIS Runtime cannot directly read images so this aspect is not applicable to it, instead it reads package files which would have been more appropriate to test with. This was however not done in this project.

Ease of use & quality of documentation This judgement regarding ease of use is primarily derived from the supplied documentation and my ability to use it. LuciadLightspeed has the best documentation. The documentation for Carmenta Engine and ArcGIS Run- time is generally equal in quality. The documentation for MapLink Pro seems to be targeted for a technically experienced audience with familiarity of this particular field of software. WorldWind is a special case, there is not much in the way of a conceptual documentation or a developer’s guide. However all the source code and a javadoc reference is available.

In terms of ease of use I would place Carmenta Engine and ArcGIS Runtime in a tied first place mainly due to the simplicity of the map engines. Luciad- Lightspeed comes second place because of its complexity but outstanding guides. WorldWind and Maplink Pro are tied, as WorldWind is pretty complex and the documentation for Maplink Pro did not provide much help.

Which Map Engine is the best? No map engine can be said to be absolutely superior in every conceivable way. Depending upon the particular situation the requirements set forth might favour one map engine while disfavouring another one that could have been suitable in another situation. Contents

1 Introduction 8 1.1 Goal ...... 8 1.2 Limitations ...... 8 1.3 Structure of the report ...... 9

2 Carmenta Engine 10 2.1 General introduction ...... 10 2.2 Developing the application ...... 10 2.2.1 A simple example ...... 11 2.2.2 Reading Data ...... 14

3 MapLink Pro 18 3.1 General introduction ...... 18 3.2 Developing the application ...... 18 3.2.1 A simple example ...... 19 3.2.2 Reading data ...... 20

4 ArcGIS Runtime 22 4.1 General introduction ...... 22 4.2 Developing the application ...... 22 4.2.1 A Simple Example ...... 22 4.2.2 Reading Data ...... 24

5 LuciadLightspeed 26 5.1 General introduction ...... 26 5.2 Developing the application ...... 26 5.2.1 A simple example ...... 26 5.2.2 Reading data ...... 27

6 WorldWindJava 42 6.1 General introduction ...... 42 6.2 Developing the application ...... 42 6.2.1 A simple example ...... 43 6.2.2 Reading data ...... 44

7 Testing 50 7.1 The testing platform ...... 50 7.2 Identifying test cases ...... 50 7.3 Symbols performance ...... 50

1 7.4 Carrying out the test ...... 51 7.5 Image handling ...... 52 7.5.1 Carmenta Engine ...... 53 7.5.2 MapLink Pro ...... 53 7.5.3 ArcGIS Runtime ...... 54 7.5.4 LuciadLightSpeed ...... 54 7.5.5 WorldWind ...... 56 7.6 Carrying out the test ...... 56

8 Test results 57 8.1 Symbols performance ...... 57 8.1.1 The quality of the measurement ...... 57 8.1.2 Comments on the findings ...... 58 8.2 Reading raster data ...... 58 8.2.1 Carmenta Engine ...... 58 8.2.2 MapLink Pro ...... 59 8.2.3 ArcGIS Runtime ...... 59 8.2.4 LuciadLightspeed ...... 59 8.2.5 WorldWindJava ...... 60 8.2.6 Summary: Image handling ...... 60

9 Conclusions 61 9.1 Comments regarding the findings ...... 62 9.1.1 Carmenta Engine ...... 62 9.1.2 Maplink Pro ...... 64 9.1.3 ArcGIS Runtime ...... 65 9.1.4 LuciadLightspeed ...... 65 9.1.5 WorldWindJava ...... 65

10 Reflections 67 10.1 The different philosophies ...... 67 10.2 Detecting a pattern, Producer & Consumer ...... 68 10.3 Various Reflections ...... 69 10.3.1 Carmenta Engine ...... 69 10.3.2 MapLink Pro ...... 70 10.3.3 ArcGIS Runtime ...... 70 10.3.4 LuciadLightspeed ...... 70 10.3.5 WorldWind ...... 71 10.4 Flaws regarding the method ...... 72

11 Future Work 73

2 References 74

12 Appendix A 76 12.1 Code for symbol test ...... 76 12.1.1 Carmenta Engine ...... 76 12.1.2 Maplink Pro ...... 79 12.1.3 ArcGIS Runtime ...... 90 12.1.4 LuciadLightspeed ...... 93 12.1.5 WorldWind ...... 98 12.2 Various bits of code ...... 101 12.2.1 Maplink Pro ...... 101 12.2.2 LuciadLightspeed ...... 104 12.2.3 WorldWind ...... 108

3 List of Figures

1 The Carmenta Engine architecture ...... 10 2 The various objects that constitute a map in Carmenta Engine 11 3 DTED data visualized as a shading effect ...... 17 4 The major WorldWind interfaces ...... 43 5 Worldwind symbol test ...... 52

List of Tables

1 Symbol performance result ...... 57 2 WorldWind image processing time ...... 60 3 Format support summary ...... 61 4 Format support legend ...... 61

4 Abbreviations, Terms and Keywords

Formats ARINC 424 ARINC 424 is an international standard file format for aircraft navigation data.1

DTED Digital Terrain Elevation Data is a standard of digital datasets which consists of a matrix of terrain elevation values.2

GeoTIFF GeoTIFF is a metadata standard in the public domain which allows georeferencing information to be embedded within a TIFF image. With the additional information it is possible to establish the spatial reference for the file.3

KML is an XML notation for expressing geo- graphic annotation and visualization. KML files are often distributed in KMZ files, which are zipped files with a .kmz extension.4

S-57 S-57 is a set of regulations approved by the International Maritime Organization and the International Hydrographic Organization for standard- ization of nautical cartographic data.5

Shapefile A Shapefile is a popular geospatial vector data format for GIS software. It is developed and regulated by the company Esri as a (mostly) open specification. Shapefiles spatially describe geometries: points, polylines, and polygons. These, for example, could represent water wells, rivers, and lakes, respectively. Each item may also have attributes that describe the items, such as the name or temperature.6

1Wikipedia. Article: Arinc424. accessed April 8, 2012. url: http://en.wikipedia.org/ wiki/ARINC 424. 2Wikipedia. Article: DTED. accessed April 8, 2012. url: http://en.wikipedia.org/ wiki/DTED. 3Wikipedia. Article: GeoTIFF. accessed April 8, 2012. url: http://en.wikipedia.org/ wiki/GeoTIFF. 4Wikipedia. Article: Keyhole Markup Language. accessed April 8, 2012. url: http : //en.wikipedia.org/wiki/Keyhole Markup Language. 5Morintech Navigation A/S. Electronic Charts - Standards. accessed April 8, 2012. url: http://www.dkart.ru/enc det.htm. 6Wikipedia. Article: Shapefile. accessed April 8, 2012. url: http://en.wikipedia.org/ wiki/Shapefile.

5 VPF Vector Product Format is a military standard published by the US Department of Defence for vector-based digital map products.7

OGC-related Terms OGC The Open Geospatial Consortium is an international voluntary consensus standards organization.8

WMS A is a standard protocol for serving georeferenced map images over the Internet that are generated by a map server.9

WFS The Interface Standard provides an interface allowing requests for geographical features across the web using platform- independent calls.10

WCS The Interface Standard defines Web-based retrieval of geospatial data.11

7Wikipedia. Article: Vector Product Format. accessed April 8, 2012. url: http://en. wikipedia.org/wiki/Vector Product Format. 8Wikipedia. Article: Open Geospatial Consortium. accessed June 7, 2012. url: http: //en.wikipedia.org/wiki/Open Geospatial Consortium. 9Wikipedia. Article: Web Map Service. accessed June 7, 2012. url: http://en.wikipedia. org/wiki/Web Map Service. 10Wikipedia. Article: Web Feature Service. accessed June 7, 2012. url: http : / / en . wikipedia.org/wiki/Web Feature Service. 11Wikipedia. Article: Web Coverage Service. accessed June 7, 2012. url: http://en. wikipedia.org/wiki/Web Coverage Service.

6 Other terms Map engine A map engine is a system for creating software capable of handling geographic data.

MFC Foundation Class Library is a software library that enables the creation of graphical ++ applications.

SDK . A software development kit is typically a set of software development tools that allows for the creation of applications.

WorldWind A SDK and API for creating a . Developed by NASA. Released under the NASA Open Source Agreement license. The terms ’WorldWind’ and ’WorldWindJava’ are used interchangeably in this report unless otherwise explicitly stated.

WPF Windows Presentation Foundation is a technology to create graphical user interfaces for .NET applications.

7 Chapter 1, Introduction

Saab AB is a Swedish company in the aerospace and defence industry. The company produces various systems that uses geographic data in some form. Examples are the Gripen multirole fighter aircraft, weapons and surveillance systems.

There are incompatibilities between the software in these systems as they have been developed using different map engines. This was caused by various reasons. One such reason is that different systems often places different demands on the software, which leads to selecting a map engine that fulfills the particular demands.

Examining a set of map engines to evaluate their abilities would be beneficial, as discovering that one or several map engines can be replaced leads to various benefits, among these an increased compatibility between software. The map engines to be examined in this project have been selected by Saab, and are the following:

• Carmenta Engine • Envitia Maplink Pro • Esri ArcGIS Runtime • LuciadLightspeed • WorldWindJava

1.1 Goal The desired result of this project is an analysis of the selected map engines. The purpose of the analysis is to answer how suitable each map engine is for software development at Saab. This will mainly be done by producing an application utilizing each map engine to get a subjective opinion of how easy it is to work with the map engine in question, and then by testing the functionality and performance of the software.

1.2 Limitations The time available for this project, roughly 10 weeks (16 credit points), is the principal limitation. To produce results in this time frame I will have to develop applications that only implements basic functionality. The testing and performance measurement will have to be done on a basic level.

8 It was decided that the resulting software should fulfill the following functional requirements:

• Visualization, support for basic functionality such as moving around, zooming, panning, layer control. • Be able to read the following data formats: ARINC 424, DTED, GeoTIFF, KML, S-57 nautical charts, shapefiles, VPF. • Using OGC services, more specifically, WMS, WFS and WCS.

1.3 Structure of the report This report is divided into eleven chapters and an appendix. Chapters 2 - 6 deals each with a map engine where a general introduction is given first, followed by a combined technical and development description. Chapters 7 and 8 deals with the tests carried out and the results of them. The next two chapters, 9 and 10, are conclusions drawn about the format support and general reflections on the map engines. Some considerations on how this work could be continued are found in chapter 11. Prior to this chapter a glossary of various terms and keywords can be found which could be helpful to understand this report.

The appendix contains the code for the carried out test on symbol performance for each map engine and various bits of code.

9 Chapter 2, Carmenta Engine

2.1 General introduction Carmenta is a Swedish software company specialized in the GIS software sector. One of their products is the map engine ”Carmenta Engine” for developing GIS software. The latest version is 5.2 at the time of writing and the version used in this project.

One advantage of Carmenta Engine is the support for various programming languages. A program using Carmenta Engine can be written in C++, .NET languages12, and Python.13 In this project I choose to create a C# application using the Windows Forms technology for creating a graphical .

2.2 Developing the application The kernel and the API:s As briefly touched upon earlier in the introduction Carmenta Engine consists of a kernel, written in C++, and several API:s to access it.

Figure 1: The Carmenta Engine architecture.

Since an application can be written in different languages the description varies somewhat depending on the language/API. This description will be applicable to developing a C# application. Before going hands-on on software

12C#, , C++/CLI or any other .NET language. 13Carmenta Engine Documentation: Carmenta Engine Overview.

10 development some general concepts of Carmenta Engine will be presented, first how a map is built up.

How data is displayed

Figure 2: The various objects that constitute a map in Carmenta Engine.

The above figure depicts a complete chain of components to display data. The ”data flow” should be interpreted as going from right to left. A DataSet object reads some source of data, for example a file. The next step is a chain of Operators. Operators perform some sort of operation on the data. To simply just display the data this chain would likely consist of a ReadOperator that reads data from the dataset and a Visualization- Operator that has one or more Visualizers attached to itself. The next step is to connect a Layer to the VisualizationOperator and add the layer to a View which is the map display seen in the resulting program.14

Configuration Files & Carmenta Studio One concept of Carmenta Engine is that a program can essentially read a configuration file15 that can extensively configure the program to the point where little would have to be done in code. However a program does not have to read such a file, and everything can be done in code.

2.2.1 A simple example A Forms application commonly consist of a class Program that acts as the entry point for the program, and one or more Forms which are program windows a user will see. A simple application using Carmenta Engine could be written as following:

14Carmenta Engine Documentation: How Carmenta Engine Displays a map. 15Carmenta Engine Documentation: Carmenta Studio User’s Guide.

11 using System; using System.Windows.Forms;

/* * This is the Program class, the * entry point of the program. */ namespace CarmentaApp { static class Program { ///

/// The main entry point for the application. /// [STAThread] static void Main() { try { // Initialize Carmenta Engine. Carmenta.Engine.Runtime.Initialize();

//This is obligatory for all applications. Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false);

//Createa MainWindow. Application.Run(new MainWindow()); } catch (Carmenta.Engine.EngineException ex) { // Display error message from exception. MessageBox.Show(ex.Message, "Carmenta Engine error", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // Shutdown Carmenta Engine when the program terminates. Carmenta.Engine.Runtime.Shutdown(); } } } }

12 using System; using System.Windows.Forms; using Carmenta.Engine; using Carmenta.Engine.Forms;

/* * This the windowa user will see and interact with. */ namespace CarmentaApp { public partial class MainWindow : Form { //A control that displaysa View and let’s the user interact with it. MapControl mapControl_;

public MainWindow() { //An initialization is required in any application. InitializeComponent();

mapControl_ = new MapControl();

//Create and assigna View to the MapControl. mapControl_.View = new Carmenta.Engine.View(new BitmapDrawable(1,1));

//Seta tool so the user can interact with the map. mapControl_.Tool = new StandardTool();

//Make the map take up all available space in the window. mapControl_.Dock = DockStyle.Fill;

//Add it to the window. panel1.Controls.Add(mapControl_); } } }

13 2.2.2 Reading Data Reading data is done in a uniform way. The main difference between different formats is the type of DataSet to use, as each dataset type is usually specialized to handle a specific type of data format.16 For example reading a shapefile one would use a ShapefileDataSet and to read a ARINC file one would use a ArincDataSet. These are the different steps associated with reading data into an application:

Step 1 The first step is to instantiate the Dataset. Note that for WMS a layer is created straight away and no prior work needs to be done. The procedure for WMS skips from here to Step 5. // Dataset ds = new ShapefileDataSet("path to the file");

// KML Dataset ds = new OgrDataSet("path to the file");

// GeoTIFF Dataset ds = new GdalDataSe("path to the file");

// ARINC 424 Dataset ds = new ArincDataSet("path", "filename");

//S-57 Dataset ds = new S57DataSet("path to the file");

// VPF Dataset ds = new VpfDataSet("path to the vpf database", "library ", "coverage", "feature");

// DTED Dataset ds = = new DtedDataSet("path");

// WMS, note thata layer is created straight away. OgcWmsLayer owl = OgcWmsLayer("url to the WMS service");

// Specify what layers from the WMS sevice should be retrieved. owl.Layers.Add("a layer ");

//WCS Dataset ds = new OgcWcsDataSet("url to the WCS service", " coverage id");

16Carmenta Engine Documentation: DataSet Class.

14 Step 2 After creating a Dataset, it is connected to a ReadOperator. ReadOperator ro = new ReadOperator(ds);

Step 3 The next thing to do is to connect the ReadOperator to a VisualizationOperator. The only exception is for GeoTIFF where this step and the next is skipped, a RasterVisualizer is automatically added.17 VisualizationOperator vo = new VisualizationOperator(ro);

Step 4 The VisualizationOperator needs Visualizers to perform visualization. Thus, a number of visualizers are added in the next step. Usually three visualizers are sufficient: Symbol-, Line- and Polygon- Visualizer. There are a few exceptions to the norm. When dealing with S-57 data there is a specialized visualizer, the S52Visualizer, that visualizes the data according to the S-52 standard.18 // This works most of the time. vo.Visualizers.Add(new PolygonVisualizer()); vo.Visualizers.Add(new LineVisualizer()); vo.Visualizers.Add(new SymbolVisualizer());

// ForS-57 data onlya S52Visualizer needs to be added. vo.Visualizers.Add(new S52Visualizer());

Step 5 Finally, the data is added by creating a OrdinaryLayer and adding it to the MapControl object. Note that when retrieving data from WMS service a layer was created straight away and it can directly be added to the MapControl. mapControl.View.Layers.Add(new OrdinaryLayer(vo));

// For GeoTIFFs the ReadOperator is // directly"connected" toa layer. mapControl.View.Layers.Add(new OrdinaryLayer(ro));

// or, in the case of WMS,a layer is // created and can be added straight away mapControl.View.Layers.Add(OgcWmsLayer);

17Carmenta Engine Documentation: Raster Data Guide. 18Carmenta Engine Documentation: S52Visualizer Class.

15 To get a better performance it is usually advisable to create a TileLayer19 and add it to the MapControl. The procedure to do so is to create a LayerCollection, initialize it with the layer from Step 5, and create a TileLayer initialized with the LayerCollection. The below snippet of code will demonstrate: LayerCollection lc = new LayerCollection(); lc.Add(layer); mapControl.View.Layers.Add(new TileLayer(lc));

Reading Data, DTED Handling DTED data deviates from the above convention. The operator chain connected to the DtedDataset is a bit longer and more complex.20 The below snippet of code demonstrates a basic handling of DTED data: DtedDataSet dds = new DtedDataSet(dir); dds.ConnectRasters = false;

ReadOperator ro = new ReadOperator(dds);

RasterConversionOperator rco = new RasterConversionOperator(ro); rco.ChangeRasterFormat = false;

ShadeOperator so = new ShadeOperator(rco);

VisualizationOperator vo = new VisualizationOperator(so); vo.Mode = VisualizationOperatorMode.Merge;

RasterVisualizer rv = new RasterVisualizer(); rv.Alpha = 80; vo.Visualizers.Add(rv);

// Step5 again mapControl.View.Layers.Add(new OrdinaryLayer(vo));

19Carmenta Engine Documentation: Carmenta Engine FAQ, ”When Should I have my data in a TileLayer?”. 20However, correspondence with Carmenta revealed that it could be simplified.

16 In effect this will create a ”shading” effect. See the below image for a demonstration.

Figure 3: DTED data visualized as a shading effect. Note the difference between the area covered by the DTED data (left) and the area outside the coverage (right).

The example of handling DTED demonstrates Carmenta Engine’s philosophy of providing extensive controls specifying how data should be processed and visualized. To a beginner this can seem labour-intensive.

17 Chapter 3, MapLink Pro

3.1 General introduction Envitia is a British company specialized in the GIS software sector. In this project their software product ”MapLink Pro” has been examined. The latest version is 7.0, which is the one used in this project.

One problem encountered working with MapLink Pro was the insufficient documentation. My impression of MapLink Pro is that it requires a higher degree of knowledge and more prerequisites that should be fulfilled in order properly use it. Due to my inexperience this led to a limited understanding of the map engine and how to develop an application using it. My lack of insight into MapLink Pro will affect every section where it is discussed throughout this report.

The idea behind MapLink Pro is that a resulting application to a degree will be a client that takes preprocessed data. Application are somewhat dependent on reading map files, which are produced in a program called ”MapLink Studio”. In MapLink Studio a user can with ease import data, such as images, shapefiles and so on, and create a single map file that the client can simply read.21 The reliance on the studio application is only partial, the client can by itself read several formats.22

There are three API:s which a developer can use to produce a program, C++, .NET and COM.23 The C++ API is the most fully-featured, all SDKs are available in C++, so I decided to develop an application in C++ in order to take full advantage of MapLink Pro. MFC is used to provide a graphical user interface.

3.2 Developing the application At its core a MapLink Pro application consists of DrawingSurfaces and Layers. A DrawingSurface is the map display of an application and Layers represent some form of displayed data.24

21MapLink Pro Developer’s Guide, section 2.1: MapLink Studio, p. 12. 22MapLink Pro - Data Formats Support. Data Sheet. 23MapLink Pro SDKs. Data Sheet. 24MapLink Pro Developer’s Guide, section 3: Basic MapLink Applications, p. 17.

18 3.2.1 A simple example An MFC application normally consist of a Document and one or several Views. Programmatically there are three core classes in a MFC program. An App class which is the program, and a Doc and a View class. MapLink Pro relies on some configuration files for style information. These should be loaded on the startup of the application in the App class’ InitInstance() method.25 BOOL TestAppApp::InitInstance() { /* * The path to the configuration files. Setting it to zero * makes MapLink Pro assumea full installation has taken * taken place(which is true for my computer). */ const char* configDirPath = 0;

// Clear the error stack TSLThreadedErrorStack::clear();

// Load the configuration. TSLDrawingSurface::loadStandardConfig( configDirPath );

TSLSimpleString msg( "" ); bool anyErrors = TSLThreadedErrorStack::errorString( msg, "Initialisation Errors : \n" ) ; if ( anyErrors ) { //Do something. }

... // InitInstance continues but it’s nothing interesting... }

On termination of the program a cleanup should be done. int TestAppApp::ExitInstance() { TSLDrawingSurface::cleanup();

AfxOleTerm(FALSE);

return CWinApp::ExitInstance(); }

25MapLink Pro Developer’s Guide, section 7.4: Initialisation and Clean Up, p. 29.

19 3.2.2 Reading data The support for reading data in a running application is limited. About half of all supported formats cannot be imported at runtime. One goal of this project was to import ARINC and VPF data but this could not be done. The documentation provides some guidance on how to import data, which I found insufficient to enable me to implement support for reading data at runtime. Implementing this support can require a high degree of knowledge, making it hard for a novice to achieve results. In this project reading map files, GeoTIFFs and Shapefiles was achieved, while I failed to implement support for importing DTED and S-57 data.

Map files An application can read a map file which is represented by the class TSLMap- DataLayer. The below code will demonstrate: void ImportMap(CString file) { TSLMapDataLayer* mapDataLayer = new TSLMapDataLayer(); if(mapDataLayer->loadData(file)) { drawingSurface->addDataLayer(mapDataLayer, "A map layer");

RECT cr; GetClientRect(&cr); drawingSurface->wndResize(cr.left,cr.top,cr.right, cr. bottom, false);

drawingSurface->reset(false); } else // Something went wrong. { TSLSimpleString msg(""); bool anyErrors = TSLThreadedErrorStack::errorString( msg,"Cannot load map : \n" ) ;

if(anyErrors) { // Do something. } //Destroy the layer to preventa memory leak. mapDataLayer->destroy(); mapDataLayer = NULL ; } }

20 GeoTIFF GeoTIFF images can be imported by using the TSLRasterFilterData- Layer class. The procedure is demonstrated in the below sample code. void ImportGeoTIFF(CString file) { //Get the coordinate system used by the loaded map. const TSLCoordinateSystem* cs = NULL; cs = m_mapDataLayer->queryCoordinateSystem();

// Createa TSLRasterFilterDataLayer TSLRasterFilterDataLayer* layer = new TSLRasterFilterDataLayer (TSLFilterTypeGeoTIFF, "GeoTIFFFilter");

if(layer->loadData(file)) { // Make the layer use the same coordinate system. layer->setCoordinateSystem(cs);

//Processing must take place before the loaded data // may be drawn. layer->process();

// Add the layer to the DrawingSurface. drawingSurface->addDataLayer(layer,"A GeoTIFF"); } else // Something went wrong. { //Destroy the layer to preventa memory leak. layer->destroy(); layer = NULL; } }

This approach results in some issues when the images are large. The image will be loaded into memory in its entirety which results in a hefty memory usage. Correspondence with Envitia revealed that it could have been better to utilize the TSLRasterUtilityFunctions::rasterToPyramid() method to generate a tiled image which can then be displayed. This could lead to a better memory usage. However the license expired before this could have been tested.

Shapefile The code is lengthy and is available in appendix A, page 101.

21 Chapter 4, ArcGIS Runtime

4.1 General introduction Esri is a international American company providing GIS software. It is one of the largest actors on the market.26 In this project their software ”ArcGIS Runtime” have been examined. It is in a pre-release stage and still in development. As such, the software could go through changes that might invalidate the assessments made about it in this report. The version used in this project is version 1.0 (pre-release).

One thing to note is that ArcGIS Runtime is markedly different from the other map engines regarding the of the resulting application.27 The resulting software will be a particularly thin client that relies extensively on preprocessed data. The main focus of this project was to read data directly into an application, but this approach conflicts with the one intended by the map engine. As such, the resulting application was produced quickly since there was little to implement.

There are several SDKs to choose from to develop an application. There are SDKs for Java, WPF and others.28 In this project the WPF SDK has been used since it was the one I was provided with and the following description will be applicable for developing a C# WPF application.

4.2 Developing the application 4.2.1 A Simple Example Simply put a WPF application consists of two parts, XAML documents and code. XAML is a XML derivative used to specify a user interface for the application and to define objects. Most of this project is done in code. A basic XAML configuration could look like the following:

26Esri. COTS GIS: The Value of a Commercial Geographic Information System. 2002, pp. 1–2. 27See chapter 10, Reflections, on page 67 for more information on this. 28Common Questions: What programming languages are supported for ArcGIS Runtime? accessed June 7, 2012. url: http://www.esri.com/software/arcgis/runtime/common- questions.

22 xmlns:esri="http://schemas.esri.com/arcgis/client/2009" Title="MainWindow" Height="350" Width="525">

And the code for the application will be: using System; using System.; using System.Windows; using ESRI.ArcGIS.Client.Local; using ESRI.ArcGIS.Client; using ESRI.ArcGIS.Client.Tasks; namespace EsriTestApp { public partial class MainWindow : Window { public MainWindow() { // Set the ArcGIS Runtime license. ArcGISRuntime.SetLicense("A secret string");

// Initialize the ArcGIS Runtime before any components // are created. ArcGISRuntime.Initialize();

// An initialization is required in any application. InitializeComponent(); } } }

Not going into extreme detail the configuration defines a main window for the application that will contain a Map object. A Map is a central part of an application and acts as the display.29 One important member of a Map object is the LayerCollection which naturally is a collection of Layers to be displayed. In ArcGIS Runtime layers are a link between the application and data sources.30 This leads us onto how data is added. Data is mainly read in the form of ”packages”. There are two important types of packages that

29ArcGIS Runtime SDK for WPF, API Reference: Map Class. 30ArcGIS Runtime SDK for WPF, API Reference: Layer Class.

23 data is delivered in: ”Map Package” and ”Tile Map Package”, the former is a collection of various layers and the latter is just one single, tiled, layer.31 These package files are created in ArcGIS Desktop, which is a very potent full blown GIS application.

4.2.2 Reading Data Layers act as a connection to data in ArcGIS Runtime. There are various types of layers, with the difference between them being what type of data they handle. Some examples of layers are:

• ArcGISLocalDynamicMapServiceLayer • ArcGISLocalTiledLayer • KmlLayer • WmsLayer

The first two are used to handle locally stored Map and Tiled Map Package files respectively. The second two are almost self-explanatory, a KmlLayer reads data from a KML file and a WmsLayer reads data from a WMS service. Reading data is trivially easy. The below snippet of code will demonstrate: //A ArcGISLocalDynamicMapServiceLayer is instantiated. Layer mpklayer = new ArcGISLocalDynamicMapServiceLayer( "Path to a .mpk file");

//A ArcGISLocalTiledLayer is instantiated. Layer tpklayer = new ArcGISLocalTiledLayer( "Path to a .tpk file");

//A KmlLayer is instantiated. KmlLayer kmllayer = new KmlLayer();

/* * Unfortunately the Url property ofa KmlLayer * cannot be passed onto the constructor. * * A KmlLayer can connect toa file either stored * locally or acessible on the Web. */

Kmlayer.Url = new Uri("path to a local kml file, or a url");

/*

31The term ”layer” here does not refer to the class Layer in ArcGIS Runtime but instead data layers such as imagery, shapefiles et.c..

24 * Usinga WMS Service. */

//A WmsLayer is instantiated. WmsLayer wmslayer = new WmsLayer();

//A url to the WMS server has to be set. wmslayer.Url = "Url to a WMS service";

/* The layers to retrieve from the server has to be specified. * The’Layers’ property isa string array, so multiple layers * can be specified. */ wmslayer.Layers = new string[] {"a layer name","another one"};

/* * The layers are then added by calling the Add() * method of the LayerCollection’Layers’ property * of the Map. */ map.Layers.Add(mpklayer); map.Layers.Add(tpklayer); map.Layers.Add(kmllayer); map.Layers.Add(wmslayer);

25 Chapter 5, LuciadLightspeed

5.1 General introduction Luciad is a Belgian GIS software company headquartered in USA and . In this project I have examined their software product ”LuciadLightspeed”. LuciadLightspeed is entirely java software. It is a continuation of their previous product ”LuciadMap”. LuciadLightspeed is a relatively new product, as it was released this year, but being a continuation and replacement of an existing product it is already quite mature. The version used in this project is 2012.0.

5.2 Developing the application LuciadLightspeed is modeled as a Model - View - Controller architecture. Data is contained in Models and can be displayed in Views through Layers.32 The controller part of the architecture is not relevant in this project since the focus is on reading data and displaying it.

5.2.1 A simple example A simple example of a LuciadLightspeed application could be written as following: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import java.awt.EventQueue; import javax.swing.JFrame; import com.luciad.geodesy.TLcdGeodeticDatum; import com.luciad.reference.TLcdGeocentricReference; import com.luciad.view.lightspeed.TLspAWTView; import com.luciad.view.lightspeed.util. TLspViewTransformationUtil; public class SimpleExample { //A view. private TLspAWTView view = new TLspAWTView();

public static void main( String[] args )

32LuciadLightspeed v2012.0 Developer’s Guide, chapter 2: LuciadLightspeed architecture, p. 9.

26 { EventQueue.invokeLater( new Runnable() { @Override public void run() { new SimpleExample().start(); } } ); }

public void start() { // Make the view displaya3D globe instead ofa2D map. TLspViewTransformationUtil.setup3DView(view, new TLcdGeocentricReference( new TLcdGeodeticDatum() ), false );

// Createa JFrame,a program window to hold the view. JFrame frame = new JFrame(); frame.setSize( 800, 800 ); frame.setVisible( true ); frame.setResizable(true);

// Add the view to the frame. frame.add(view.getHostComponent()); } }

A program window, a JFrame, is created and a view is added to it.

5.2.2 Reading data A number of data formats cannot be displayed natively in a Lightspeed View,33 but can be displayed nevertheless by performing an adapting operation.34 The following formats this project is concerned with has to be adapted:

• ARINC 424 • KML • S-57 • VPF

33LuciadLightspeed v2012.0 Developer’s Guide, appendix E: Supported file formats, p. 447. 34LuciadLightspeed v2012.0 Developer’s Guide, section 27.2.3: Adapting GXY Layers, p. 305.

27 GeoTIFF GeoTIFF images can be imported by first creating an appropriate decoder, a TLcdGeoTIFFModelDecoder, then use the decoder to decode the file and get a model. Next a layer must be created for the data to be displayed in the view. This is done by using the utility class TLspRasterLayerBuilder. During the construction of the layer a model which the layer represents, a label (name) for the layer, and a styler which specifies how the data will be visualized is set. The below code will demonstrate: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import com.luciad.view.lightspeed.ILspView; import com.luciad.model.ILcdModel; import com.luciad.view.lightspeed.layer.ILspLayer; import com.luciad.format.raster.TLcdGeoTIFFModelDecoder; import com.luciad.view.lightspeed.layer. TLspPaintRepresentationState; import com.luciad.view.lightspeed.layer.raster. TLspRasterLayerBuilder; import com.luciad.view.lightspeed.style.TLspRasterStyle; private void importGeoTIFF(String file) { try { // Createa GeoTIFFModelDecoder TLcdGeoTIFFModelDecoder decoder = new TLcdGeoTIFFModelDecoder();

// Decode the file and geta Model. ILcdModel model= decoder.decode(file);

// Createa RasterStyle TLspRasterStyle rasterStyle = TLspRasterStyle.newBuilder() .build();

// Createa Layer by usinga RasterLayerBuilder ILspLayer layer = TLspRasterLayerBuilder.newBuilder(). model(model). label(model.getModelDescriptor().getDisplayName()). styler(TLspPaintRepresentationState.REGULAR_BODY, rasterStyle). build();

//Add the layer to the view. view.addLayer(layer);

28 } catch(Exception e) { // Do something. } }

This is what happens at the lowest level. However LuciadLightspeed comes with supplied factories for creating layers, which are preferable to use instead as they hide this low-level work of creating layers.

Shapefile Importing shapefiles follow the usual procedure, which is to create an appropriate decoder, decode to get a model, use a specialized factory to create a layer and finally add the layer to the view. The below code will demonstrate: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import com.luciad.model.ILcdModel; import com.luciad.view.lightspeed.ILspView; import com.luciad.view.lightspeed.layer.ILspLayer; import com.luciad.format.shp.TLcdSHPModelDecoder; private void importShapefile(String file) { try { //Createa SHPModelDecoder TLcdSHPModelDecoder decoder = new TLcdSHPModelDecoder();

// Decode the file and geta Model. ILcdModel model = decoder.decode(file);

// Createa ShapeFileLayerFactory. ShapeFileLayerFactory factory = new ShapeFileLayerFactory ();

//Use the factory to createa layer from the model. ILspLayer layer = factory.createLayer(model);

// Add the layer to the view. view.addLayer(layer); } catch(Exception e) { // Do something.

29 } }

KML Reading a KML file follows the usual procedure of creating a decoder and decoding the file to obtain a model. Before a layer can be created the model will first have to be wrapped in a dynamic KML model, and then flattened. Once a flat model has been produced a layer can be created using a supplied layer factory and added to the view. The below sample will demonstrate. //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import samples.decoder.kml22.quickStart.KMLLayerFactory; import com.luciad.format.kml22.model.TLcdKML22DynamicModel; import com.luciad.format.kml22.model.TLcdKML22Kml; import com.luciad.format.kml22.model.TLcdKML22Parameters; import com.luciad.format.kml22.model.TLcdKML22RenderableModel; import com.luciad.format.kml22.util.TLcdKML22ResourceProvider; import com.luciad.format.kml22.view.gxy.TLcdKML22GXYRegionFilter ; import com.luciad.format.kml22..TLcdKML22ModelDecoder; import com.luciad.model.ILcdModel; import com.luciad.view.gxy.ILcdGXYLayer; import com.luciad.view.lightspeed.ILspView; import com.luciad.view.map.TLcdMapJPanel; void importKML(String file) { try { // Createa KML22ModelDecoder. TLcdKML22ModelDecoder modelDecoder = new TLcdKML22ModelDecoder();

// Get the resource provider. TLcdKML22ResourceProvider resourceProvider = modelDecoder. getResourceProvider();

// Decode the file. ILcdModel decodedModel = modelDecoder.decode(file);

// Create parameters. TLcdKML22Parameters parameterProvider = new TLcdKML22Parameters();

30 // Createa region filter. TLcdKML22GXYRegionFilter regionFilter = new TLcdKML22GXYRegionFilter( new TLcdMapJPanel() );

// Wrap the decoded model ina dynamic KML Model. TLcdKML22DynamicModel kmlModelTreeNode = new TLcdKML22DynamicModel( ( TLcdKML22Kml ) decodedModel, resourceProvider, parameterProvider, regionFilter );

// Convert the dynamic model intoa flat model. TLcdKML22RenderableModel flatModel = new TLcdKML22RenderableModel( kmlModelTreeNode, null );

// Createa KMLLayerFactory. KMLLayerFactory factory = new KMLLayerFactory(modelDecoder .getResourceProvider());

//Createa layer from the flat model. ILcdGXYLayer layer = factory.createGXYLayer(flatModel);

// Adapt the GXYLayer toa Lightspeed layer and insert it. view.addLayer( adapt(layer) ); } catch(Exception e) { // Do something. } }

Notice the adaptation that will have to be applied to the GXY layer prior to adding it to the Lightspeed view. The code for adapting a GXY layer can be found in appendix A, on page 104.

VPF VPF data is commonly structured as a database, and with LuciadLightspeed a database can easily be explored. The procedure is simply to create a TLcdVPFDatabase and retrieve the libraries it holds. In turn a library contains a number of coverages and a coverage in turn holds feature classes. A feature class can then be decoded and visualized. The below sample will demonstrate the procedure: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import samples.vpf.VPFLayerFactory;

31 import com.luciad.format.vpf.TLcdVPFCoverage; import com.luciad.format.vpf.TLcdVPFDatabase; import com.luciad.format.vpf.TLcdVPFFeatureClass; import com.luciad.format.vpf.TLcdVPFLibrary; import com.luciad.format.vpf.TLcdVPFModelDecoder; import com.luciad.model.ILcdModel; import com.luciad.view.lightspeed.ILspView; import com.luciad.view.lightspeed.layer.ILspLayer; void importVPF(String dhtfile) { try { // Createa VPFModelDecoder. TLcdVPFModelDecoder model_decoder = new TLcdVPFModelDecoder();

// Createa VPFLayerFactory VPFLayerFactory factory = new VPFLayerFactory();

// Createa VPFDatabase TLcdVPFDatabase database = new TLcdVPFDatabase(dhtfile);

ILcdModel model = null;

// Get all the libraries the database contains. TLcdVPFLibrary libs[] = database.getLibraries();

// All the coverages ofa library. TLcdVPFCoverage coverages[];

// All the feature classes ofa coverage. TLcdVPFFeatureClass ftclass[];

// Iterate over all libraries of the database. for (int i = 0; i < libs.length; ++i) {

// Get the coverages of the current library. coverages = libs[i].getCoverages();

// Iterate over all coverages the current library. for(int j = 0; j < coverages.length; ++j) { // Get the feature classes of the current coverage ftclass = coverages[j].getFeatureClasses();

// Iterate over all feature classes of the

32 // current coverage. for(int k = 0; k < ftclass.length; ++k) { // Decode the data. model = model_decoder.decode( ftclass[k], null );

//Createa layer. ILcdGXYLayer layer = factory.createGXYLayer( model );

// Adapt and add the layer to the view. view.addLayer( adapt(layer) ); } } } } catch(Exception e) { //Do something. } }

ARINC 424 Importing ARINC data requires an amount of work and some knowledge about the data, but it is not so hard in practice. First, a primary decoder is created and several auxiliary decoders. Next a number of handlers are added in order for the decoder(s) to be able to handle specific data records of the ARINC file. Some handlers/data records rely on other data records, so the ARINC file will have to be partially decoded first and the affected handlers will be assigned the data they rely upon to successfully decode the file. When that is done a full decoding of the file can be done and a layer created by a special layer factory. The below code will demonstrate how reading an ARINC file is carried out. //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import com.luciad.model.ILcdModel; import com.luciad.view.gxy.ILcdGXYLayer; import com.luciad.view.lightspeed.ILspView; import com.luciad.format.arinc.decoder.TLcdARINCDecoder; import com.luciad.format.arinc.TLcdARINCDefaultLayerFactory;

33 // Createa ARINCDecoder. private TLcdARINCDecoder ARINCDecoder = new ARINCDecoder();

/* * Airways, holdings and communications need some * special treatment, because they rely on other * domain object models(waypoints, vor, dme, ...) */

// Decoders used to create the helper models. private TLcdARINCDecoder waypointDecoder = new ARINCDecoder(); private TLcdARINCDecoder VORDecoder = new ARINCDecoder(); private TLcdARINCDecoder TACANDecoder = new ARINCDecoder(); private TLcdARINCDecoder NDBDecoder = new ARINCDecoder(); private TLcdARINCDecoder DMEDecoder = new ARINCDecoder(); private TLcdARINCDecoder airportDecoder = new ARINCDecoder(); private TLcdARINCDecoder heliportDecoder = new ARINCDecoder(); private TLcdARINCDecoder runwayDecoder = new ARINCDecoder();

//a.k.a. FIR/UIR private TLcdARINCDecoder airspaceDecoder = new ARINCDecoder(); private void importARINC(String file) { try { //Setup decoders, add handlers. initializeDecoders();

// Initialize handlers that rely on some other domain // object models. initializeHandlers(file);

// Decode the file and geta Model. ILcdModel model = ARINCDecoder.decode(file);

// Createa layer factory to producea layer from the data. TLcdARINCDefaultLayerFactory factory = new TLcdARINCDefaultLayerFactory();

//Createa layer. ILcdGXYLayer layer = factory.createGXYLayer(model);

// Adapt and add the layer to the view. view.addLayer(adapt(layer)); } catch(Exception e) { System.out.println("Exception e: " + e.getMessage());

34 } }

Abbreviated code for initializing decoders and handlers can be found in appendix A, starting on page 105.

S-57 Reading S-57 data is done by first instantiating S-57 and S-52 product configurations, which allow easy creation of decoder, model list builder and layer factory. Then a decoder can be created and the file decoded. Both individual cell files and entire catalogues can be handled. After the decoding the resulting model should be reorganized so the data is sorted according to display priority. Then a layer can be created by using a supplied layer factory. The below sample will demonstrate. //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import com.luciad.format.s52.TLcdS52DisplaySettings; import com.luciad.format.s52.TLcdS52ModelListBuilder; import com.luciad.format.s52.TLcdS52ProductConfiguration; import com.luciad.format.s57.ELcdS57ProductType; import com.luciad.format.s57.TLcdS57CatalogueModelDecoder; import com.luciad.format.s57.TLcdS57ProductConfiguration; import com.luciad.model.ILcdModel; import com.luciad.view.gxy.ILcdGXYLayerFactory; import com.luciad.view.gxy.ILcdGXYLayer; import com.luciad.view.lightspeed.ILspView; void importS57(String file) { try { /* * CreateS-57 andS-52 product configurations for the * ENC product. They allow easy creation of decoder, * model list builder and layer factory. */ TLcdS57ProductConfiguration s57ProductConfiguration = TLcdS57ProductConfiguration.newInstance( ELcdS57ProductType.ENC ); TLcdS52ProductConfiguration s52ProductConfiguration = TLcdS52ProductConfiguration.newInstance( ELcdS57ProductType.ENC );

// Createa S57CatalogueModelDecoder,a decoder that // can decode catalogue files.

35 TLcdS57CatalogueModelDecoder modelDecoder = s57ProductConfiguration.createCatalogueModelDecoder();

// Decode the catalogue file and geta Model. ILcdModel model = modelDecoder.decode(file);

// TheS-52 display settings define how the data // should be displayed. TLcdS52DisplaySettings displaySettings = new TLcdS52DisplaySettings();

// Sort the model according to display priority, // using anS-52 model list builder. TLcdS52ModelListBuilder modelListBuilder = s52ProductConfiguration.createModelListBuilder( displaySettings); ILcdModel orderedModel = modelListBuilder.buildModelList( model );

// Createa layer factory. ILcdGXYLayerFactory factory = s52ProductConfiguration. createGXYLayerFactory(displaySettings);

// Createa layer. ILcdGXYLayer layer = factory.createGXYLayer(orderedModel);

// Adapt the layer and add it to the view. view.addLayer(adapt(layer)); } catch(Exception e) { //Do something. } }

WMS There are two alternative methods for using OGC services, the first is to directly communicate with the service and the second is to use a proxy. I have gone for the second approach since it is a more pure programming approach.

The first step is to create a client that communicates with the service. Next a decoder is created and a model can be created, from the model the root layer of the WMS service can be obtained. When the root layer has been obtained its child layers can be obtained and added to the proxy. Adding

36 layers to the proxy is preferably done recursively, since a layer can contain layers. When the layers have been traversed a layer can easily be created by instantiating a TLspLayer, assigning the model and setting a painter. The below code will demonstrate. //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import java.net.URI; import com.luciad.wms.client.model.ALcdWMSNamedLayer; import com.luciad.wms.client.model.ALcdWMSProxy; import com.luciad.wms.client.model.TLcdOGCWMSProxyModelDecoder; import com.luciad.wms.client.model.TLcdWMSClient; import com.luciad.model.ILcdModel; import com.luciad.view.lightspeed.ILspView; private void importWMS(String server) { try { // Createa URI to identify the WMS service. URI wmsUri = new URI(server);

// Createa WMSClient. TLcdWMSClient client = TLcdWMSClient.createWMSClient( wmsUri);

// Createa WMSProxyModelDecoder. TLcdOGCWMSProxyModelDecoder wmsModelDecoder = new TLcdOGCWMSProxyModelDecoder();

//Decode and get the data. ILcdModel wmsModel = wmsModelDecoder.decode( client );

// Createa proxy. ALcdWMSProxy proxy = (ALcdWMSProxy) wmsModel.elements(). nextElement();

// Some configuration that’s not really critical. proxy.setFeatureInfoFormat( "text/plain" ); proxy.setBackgroundImageTransparent( true ); proxy.setMapFormat( "image/png" );

//Get the root. ALcdWMSNamedLayer root = proxy.getWMSRootNamedLayer(0);

// Add layers to the proxy. addRecursivelyLayers( proxy, root );

37 // Createa layer and add it to the view. view.addLayer(createWMSLayer(wmsModel)); } catch(Exception e) { //Do something } }

Code for recursively adding layers and wms layer creation can be found in appendix A, starting on page 107.

WFS Using a WFS service is similar to the approach for the two other services. A client is created, the capabilities of the service is retrieved and from it the available features. When the list of features have been obtained it can simply be iterated over and a model for each feature in the list can be created. From these models layers are created and added to the view. The below sample will demonstrate: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import java.net.URI; import com.luciad.view.lightspeed.ILspView; import com.luciad.ogc.wfs.client.TLcdWFSClient; import com.luciad.ogc.wfs.client.TLcdWFSProxyModel; import com.luciad.ogc.wfs.client.TLcdWFSProxyModelFactory; import com.luciad.ogc.wfs.common.model.TLcdWFSCapabilities; import com.luciad.ogc.wfs.common.model.TLcdWFSFeatureTypeList; import com.luciad.view.gxy.ILcdGXYLayer; private void importWFS(String server) { try { // Createa URI to identify the WFS service. URI wfsUri = new URI(server);

// Createa new TLcdWFSClient. TLcdWFSClient wfsClient = TLcdWFSClient.createWFSClient( wfsUri);

// Createa ProxyModelFactory.

38 TLcdWFSProxyModelFactory proxyFactory = new TLcdWFSProxyModelFactory();

// Createa layer factory. WFSLayerFactory factory = new WFSLayerFactory();

// Get the capabilites of the WFS service. TLcdWFSCapabilities wfsCapabilities = wfsClient. getCachedCapabilities();

// Get the feature types(data) of the WFS service. TLcdWFSFeatureTypeList featureTypeList = wfsCapabilities. getFeatureTypeList();

// Iterate over the feature type list. for ( int i = 0; i < featureTypeList.getFeatureTypeCount() ; i++ ) { // Createa proxy model for the current feature type. TLcdWFSProxyModel model = proxyFactory. createProxyModel( wfsClient, featureTypeList. getFeatureType(i).getName() );

// Createa layer. ILcdGXYLayer layer = factory.createGXYLayer( model );

// Add it to the view. view.addLayer( adapt(layer) ); } } catch(Exception e) {

} }

WCS The approach for using a WCS service is very similar to that of using a WFS service. A client is created, the capabilities of the service is retrieved and from it metadata of the coverages. When the available coverages are known they can simply be iterated over and a model for each coverage can be created. Layers are created from these models and added to the view. The below sample will demonstrate: //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its

39 express prior written consent. import java.net.URI; import com.luciad.view.lightspeed.ILspView; import com.luciad.geodesy.TLcdGeodeticDatum; import com.luciad.ogc.wcs.client.TLcdWCSClient; import com.luciad.ogc.wcs.client.TLcdWCSProxy; import com.luciad.ogc.wcs.client.TLcdWCSProxyModel; import com.luciad.ogc.wcs.client.TLcdWCSProxyModelFactory; import com.luciad.ogc.wcs.common.model.TLcdWCSCapabilities; import com.luciad.ogc.wcs.common.model.TLcdWCSContentMetadata; import com.luciad.ogc.wcs.common.model. TLcdWCSCoverageOfferingBrief; import com.luciad.reference.ILcdGeoReference; import com.luciad.reference.TLcdGeodeticReference; import com.luciad.shape.ILcdBounds; import com.luciad.shape.shape2D.TLcdLonLatBounds; import com.luciad.shape.shape3D.ILcd3DEditableBounds; import com.luciad.transformation.TLcdGeoReference2GeoReference; import com.luciad.view.gxy.ILcdGXYLayer; private void importWCS(String server) { try { // Createa cWCSClient. TLcdWCSClient client = TLcdWCSClient.createWCSClient(new URI(server));

// Get the capabilities of the WCS service. TLcdWCSCapabilities capabilities = client. getCachedCapabilities();

//Createa ProxyModelFactory. TLcdWCSProxyModelFactory factory = new TLcdWCSProxyModelFactory();

// Createa layer factory. WCSLayerFactory layerFactory = new WCSLayerFactory();

// Get the contents of the WCS service. TLcdWCSContentMetadata contentMetadata = capabilities. getContentMetadata();

// Iterate over the coverages(data). for ( int i = 0; i < contentMetadata. getCoverageOfferingBriefCount() ; i++ ) { TLcdWCSCoverageOfferingBrief brief = contentMetadata.

40 getCoverageOfferingBrief( i ); //Createa proxy model for the coverage. TLcdWCSProxyModel model = factory.createProxyModel( client, brief.getName() );

// Obtain the proxy object from the model, which is an ILcdMultilevelRaster TLcdWCSProxy proxy = model.getWCSProxy();

//Doa transformation of geographic references. ILcdBounds wgs84bounds = new TLcdLonLatBounds(-180, -90, 360, 180);

TLcdGeoReference2GeoReference transform = new TLcdGeoReference2GeoReference( (ILcdGeoReference) new TLcdGeodeticReference(new TLcdGeodeticDatum()), (ILcdGeoReference) model. getModelReference() );

ILcd3DEditableBounds proxy_bounds = model.getModelReference().makeModelPoint().getBounds ().cloneAs3DEditableBounds(); transform.sourceBounds2destinationSFCT( wgs84bounds, proxy_bounds );

//Set new bounds on the proxy. proxy.setBounds( proxy_bounds );

// Createa layer. ILcdGXYLayer layer = layerFactory.createGXYLayer( model, brief.getName() );

// Adapt the layer and add it to the view. view.addLayer( adapt(layer) ); } } catch(Exception e) { //Do something } }

41 Chapter 6, WorldWindJava

6.1 General introduction WorldWindJava35 is an open source map engine developed by NASA. As the name implies it is written in Java. Originally WorldWind was written in C# but that version has been largely abandoned in favour of the java version.WorldWind actually comes with a codebase for developing both a client and a server application, however this project is concerned with client applications so the server part of WorldWind has been ignored. WorldWind uses Java OpenGL as the underlying technology to display a three-dimensional globe, as such, the display is entirely 3D which sets WorldWind apart from the other map engines. WorldWindJava was initially released as a preview in May 2007, the current stable release, version 1.3, was released in April 2012. Daily builds are available at the official website.36

In this project I have used a daily build of WorldWind.37 The initial goals set up also included investigating the support for 2525B symbology. When I began working with WorldWind the latest stable release as of then, 1.2, didn’t include any support for 2525B symbology at all. However this release was close to a year old, as it was released in July 2011, and since then support for 2525B symbology had been added and was available in the daily builds. Since a daily build was necessary to examine the support for 2525B symbology, I decided to use one. Investigating the support for 2525B was later dropped as it became evident that there was insufficient time to do so.

6.2 Developing the application The most central component of a WorldWind application is a WorldWindow object. A WorldWindow is the actual view seen in the resulting application. Programmatically, the WorldWindow is an interface providing a uniform access to the various concrete classes implementing it. A critical part of a WorldWindow is the Model which aggregates a Globe and multiple Layers.38 Currently there is only one class, BasicModel, that implements the Model

35The terms ’WorldWind’ and ’WorldWindJava’ are used interchangeably in this report unless otherwise explicitly stated and refers to the java version. 36http://worldwind.arc.nasa.gov/java/ 37Build 243.457 38Concepts. accessed June 7, 2012. url: http://goworldwind.org/developers- guide/ concepts/.

42 Figure 4: A diagram showing the major WorldWind interfaces. interface. Those are the core WorldWind classes a developer has to be concerned with.

6.2.1 A simple example A simple WorldWind application could be written as following: import gov..worldwind.BasicModel; import gov.nasa.worldwind.awt.WorldWindowGLCanvas; import javax.swing.JFrame; import java.awt.Dimension; public class SimplestExample { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { //Createa WorldWindowGLCanvas WorldWindowGLCanvas wwd = new WorldWindowGLCanvas();

//Seta Model wwd.setModel(new BasicModel());

43 //Seta size wwd.setPreferredSize(new Dimension(1000, 800));

//Createa JFrame,a window to hold the //WorldWindowGLCanvas JFrame frame = new JFrame();

//Add the WorldWindowGLCanvas to the JFrame frame.getContentPane().add(wwd);

frame.pack(); frame.setVisible(true); } }); } }

The application creates a WorldWindowGLCanvas object. This is an implementation of WorldWindow, and the class most often used in the samples, so using the WorldWindowGLCanvas seems like a good decision.

A default BasicModel is created and set as the model for the WorldWindow. During the creation of the model some default layers and a default globe are created.

A preferred size is set for the WorldWindow. A JFrame (program window) is created to hold the WorldWindow and the WorldWindow is then added to the JFrame. What is left is only to do a pack() and a setVisible() operation to make the application visible.

6.2.2 Reading data Unsupported formats WorldWind does not support ARINC 424 and S-57 data, and it cannot use a WCS service. Besides these definitively unsupported things the support for importing DTED data and using a WFS service is a bit unclear and has not been demonstrated to work in this project.

44 GeoTIFF WorldWind has quite well developed methods for importing imagery. There are a few alternative ways to go about it, but the method I will describe here is quite flexible and should be recommendable in most situations. import java.io.File; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.cache.FileStore; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.layers.LayerList; void ImportImage(File file) { // Geta reference to the FileStore into which we’ll // install the imagery. FileStore fileStore = WorldWind.getDataFileStore();

// Install the imagery into the FileStore. final Layer layer = installSurfaceImage(file.getName(), file, fileStore);

// If something went wrong. if (layer == null) return;

//Retrieve the LayerList, which holds all the layers. //wwd isa common abbreviation fora WorldWindow object. LayerList layers = wwd.getModel().getLayers();

//Add the created layer. layers.add(layer); }

A WorldWind application has a utility class FileStore that can be used to handle files. First the program retrieves a reference to the FileStore then calls the method installSurfaceImage. A layer is returned and inserted. A lot of the work is done in the method installSurfaceImage which is supplied in a sample application of the SDK. In short the method will use a TiledImageProducer to process the image and then create a layer out of the results. Processing the image takes some time, which becomes noticeable as the size of the image increases. A test has been carried out on this, for more details see section 8.2.5, page 60.

45 Shapefile Shapefiles are imported by using the utility class ShapefileLoader the below sample will demonstrate: import java.io.File; import gov.nasa.worldwind.WorldWindow; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwindx.examples.util.ShapefileLoader; import java.util.List; private void importShapefile(File file) { // Createa ShapefileLoader ShapefileLoader loader = new ShapefileLoader();

// Createa list of layers froma shapefile. List layers = loader.createLayersFromSource(file);

// Settinga name for the layers is good but not obligatory. String name = file.getName(); for (int i = 0; i < layers.size(); i++) { layers.get(i).setName(i == 0 ? name : name + "-" + Integer .toString(i)); }

// Add the layers for (Layer layer : layers) { wwd.getModel().getLayers().add(layer); } }

The importing a shapefile could result in several layers if the file contains a large number of polygon features. This is because the ShapefileLoader sets a limit to the amount of polygons a single layer should hold for performance reasons.

46 KML KML files are imported by using the utility class KMLRoot to parse the KML document and then by using a KMLController that will translate the KML file into analogous WorldWind objects. The below sample will demonstrate: import java.io.File; import gov.nasa.worldwind.WorldWindow; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.layers.RenderableLayer; import gov.nasa.worldwind.ogc.kml.KMLRoot; import gov.nasa.worldwind.ogc.kml.impl.KMLController; void importKML(File file) { try { // Createa KMLRoot KMLRoot kmlRoot = KMLRoot.createAndParse(file);

// Set the document’s display name to the filename. kmlRoot.setField(AVKey.DISPLAY_NAME, file.getName());

//Createa KMLController. KMLController kmlController = new KMLController(kmlRoot);

// Createa layer. RenderableLayer layer = new RenderableLayer();

// Seta name. layer.setName((String)kmlRoot.getField(AVKey.DISPLAY_NAME));

// Add the KMLController to the layer. layer.addRenderable(kmlController);

// Insert the layer. wwd.getModel().getLayers().add(layer); } catch (Exception e) { // Do something } }

47 VPF WorldWind has good support for handling VPF data. Importing VPF is as simple as instantiating a VPFDatabase object from a database header table (dht) file, and then creating a VPFLayer from it. In addition there is also a supplied user interface component, VPFCoveragePanel, that lets the user easily control the VPF data. The below sample will demonstrate: import gov.nasa.worldwind.WorldWindow; import gov.nasa.worldwind.formats.vpf.VPFCoveragePanel; import gov.nasa.worldwind.formats.vpf.VPFDatabase; import gov.nasa.worldwind.formats.vpf.VPFLayer; import gov.nasa.worldwind.formats.vpf.VPFUtils; import java.io.File; import javax.swing.JFrame; void importVPF(File dhtfile) { // Createa VPFDatabase. VPFDatabase db = VPFUtils.readDatabase(dhtfile);

// Createa VPFLayer. VPFLayer layer = new VPFLayer(db);

// Add the layer. wwd.getModel().getLayers().add(layer);

/* * Createa VPFCoveragePanel,a control panel * that enables the user to select which coverages * to display. */ VPFCoveragePanel panel = new VPFCoveragePanel(wwd, db); panel.setLayer(layer);

//Createa JFrame to hold the panel. JFrame frame = new JFrame();

frame.add(panel); frame.pack(); frame.setVisible(true); }

48 WMS WorldWind already comes with a high-level component that conceals much of the work of interacting with a WMS service. Adding support for using a WMS service is done by creating a WMSLayersPanel, the below snippet of code will demonstrate: private void importWMS(String server) { try { // Createa WMSLayersPanel,aUI component that enables // the user to easily select layers. WMSLayersPanel panel = new WMSLayersPanel(wwd, server, new Dimension(400, 600));

// Createa window to hold the WMSLayersPanel. JFrame wmsFrame = new JFrame();

// Add the WMSLayersPanel. wmsFrame.getContentPane().add(panel); wmsFrame.pack(); wmsFrame.setVisible(true); } catch (Exception e) { // Do something. } }

49 Chapter 7, Testing

7.1 The testing platform The testing was carried out on a computer with the following specification:

Intel Core i5 2320 3 GHz, 6 MB. 8 GB DIMM DDR3 1333 MHz Nvidia Geforce GT 530, 1 GB. Graphic card driver: Geforce 296.10 : Windows 7 Home Premium

7.2 Identifying test cases I mainly took the advice from other personnel at Saab on what aspects of performance of map engines were deemed important. Two areas were identified, how well it handles raster data and symbols. I consequently decided to test this performance.

7.3 Symbols performance The test for symbols performance was carried out in a uniform manner for the map engines. A number of symbols are displayed and are then continually moved randomly. The code for each map engine is available in appendix A, starting on page 76. For some map engines the test were implemented in the main application and in those cases code irrelevant to the test has been omitted. This omission should not be of any relevancy.

There were several reasons for creating separate applications for the purpose of testing. The main reason was mostly out of practicality, implementing the test in the main application would in some cases result in an increased complexity of the program and implementing the test in a separate application would be much simpler and require considerably less effort. The impact from the fact that some of the testing was carried out in a ”larger” application while some were carried out in applications that only ran this test is negligible, it should in no way have an affect on performance.

Measuring the fps was implemented in various ways, tailored to the specific map engine. WorldWind already has this functionality implemented so no work was required.

The appended code is adequately commented and explained so even readers

50 with limited relevant technical knowledge should be able to get a good understanding. Going into detail here on how the test was implemented seems superfluous then and the interested reader is better off reading the actual code.

7.4 Carrying out the test The testing was carried out by executing the developed application and starting the procedure for the symbol test, normally through the click of a button in the application. For some of the map engines the test was implemented in a separate application, apart from the ”main” developed program, in those cases the test simply started automatically after a period of time after the start of the program.

What happens next is that a number of symbols are inserted and displayed while being continually moved. The test is then run for an arbitrary amount of time. While the test is run the frequency of updates of the display is measured. Unfortunately, there was no way to programmatically perform this measurement in ArcGIS Runtime, instead I had to rely on visually measuring the number of updates.

As for the others the number of updates are measured and periodical results are displayed in a console. Naturally, the results for each measured period varies slightly, but a precision of one or two decimals can safely be established. The results obtained from measuring the fps internally in the application was also confirmed by visually measuring the frequency of updates.

51 Figure 5: A screenshot of running the WorldWind symbol performance test.

7.5 Image handling The challenge with this test is to somehow perform an empirical analysis and measure some hard statistics, which I found no way of doing. To still show some results I can only describe the general user-experience when reading images.

As the performance of handling images depends on how this was implemented I will give a description for each map engine. This will largely be a repetition from the preceding chapters were each map engine was described.

52 7.5.1 Carmenta Engine An image is added by creating a GdalDataSet and ultimately by displaying a TileLayer. // Createa GdalDataSet to connect to the file. GdalDataSet gds = new GdalDataSet(Crs.Wgs84LongLat, "path", " filename");

// Createa ReadOperator to read the data from the DataSet. ReadOperator ro = new ReadOperator(gds);

// Createa LayerCollection, adda Layer with our image. LayerCollection lc = new LayerCollection(); lc.Add(new OrdinaryLayer(ro));

// Createa TileLayer and initialize it with the LayerCollection . TileLayer tl = new TileLayer(lc); tl.Name = filename;

// Add the TileLayer to the Map. map.add(tl);

7.5.2 MapLink Pro An image is imported by creating a TSLRasterFilterDataLayer, loading the data, setting the coordinate system to the one used by the map document, processing and adding the layer to the DrawingSurface. void ImportGeoTIFF(CString file) { //Get the coordinate system used by the loaded map. const TSLCoordinateSystem* cs = NULL; cs = m_mapDataLayer->queryCoordinateSystem();

// Createa TSLRasterFilterDataLayer TSLRasterFilterDataLayer* layer = new TSLRasterFilterDataLayer (TSLFilterTypeGeoTIFF, "GeoTIFFFilter");

if(layer->loadData(file)) { // Make the layer use the same coordinate system. layer->setCoordinateSystem(cs);

//Processing must take place before the loaded data // may be drawn. layer->process();

53 // Add the layer to the DrawingSurface. drawingSurface->addDataLayer(layer,"A GeoTIFF"); } else // Something went wrong. { //Destroy the layer to preventa memory leak. layer->destroy(); layer = NULL; } }

7.5.3 ArcGIS Runtime ArcGIS Runtime cannot directly read images as it is meant to read package files.39 There are a few possibilities to circumvent this limitation. For example it can natively read KML files which have the ability to include images, and WMS services can be used which largely delivers data as images. However I have some doubts on how adequate these methods are to determine the map engine’s handling of imagery. My impression is that KML files are generally meant to deliver ”lightweight” data, and not for example images one gigabyte large. The possible fallacy would then be that the map engine will not handle images in a manner required for large images since KML files are generally not intended to be used in such a way. As for WMS services the workload is mainly put on the server and the client is not meant to have to process the received image to any extent.

It would have been preferable to test ArcGIS Runtime’s ability to read package files, however the test was defined as to evaluate reading images, and in addition we had no access to ArcGIS Desktop 10.1 which is required to create package files.

7.5.4 LuciadLightSpeed Images are imported by creating the appropriate decoder, a TLcdGeoTIFF- ModelDecoder, then use the decoder to decode the file and get a model. Next a layer must be created for the data to be displayed in the view. This is done by using the utility class TLspRasterLayerBuilder. During the construction of the layer a model which the layer represents, a label (name) for the layer, and a styler which specifies how the data will be visualized is set. 39This is not entirely true, see Chapter 9, on page 61 for more information.

54 //(C) 1999-2012 LuciadNV All published code is Luciad’sNV exclusive property and cannot be used in any way without its express prior written consent. import com.luciad.view.lightspeed.ILspView; import com.luciad.model.ILcdModel; import com.luciad.view.lightspeed.layer.ILspLayer; import com.luciad.format.raster.TLcdGeoTIFFModelDecoder; import com.luciad.view.lightspeed.layer. TLspPaintRepresentationState; import com.luciad.view.lightspeed.layer.raster. TLspRasterLayerBuilder; import com.luciad.view.lightspeed.style.TLspRasterStyle; private void importGeoTIFF(String file) { try { // Createa GeoTIFFModelDecoder TLcdGeoTIFFModelDecoder decoder = new TLcdGeoTIFFModelDecoder();

// Decode the file and geta Model. ILcdModel model= decoder.decode(file);

// Createa RasterStyle TLspRasterStyle rasterStyle = TLspRasterStyle.newBuilder() .build();

// Createa Layer by usinga RasterLayerBuilder ILspLayer layer = TLspRasterLayerBuilder.newBuilder(). model(model). label(model.getModelDescriptor().getDisplayName()). styler(TLspPaintRepresentationState.REGULAR_BODY, rasterStyle). build();

//Add the layer to the view. view.addLayer(layer); } catch(Exception e) { // Do something. } }

55 7.5.5 WorldWind Images are imported by the supplied method installSurfaceImage which creates a TiledImageProducer to process the image and install the result on disk. The code for installSurfaceImage can be found in appendix A, page 108. import java.io.File; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.cache.FileStore; import gov.nasa.worldwind.layers.Layer; import gov.nasa.worldwind.layers.LayerList; void ImportImage(File file) { // Geta reference to the FileStore into which we’ll // install the imagery. FileStore fileStore = WorldWind.getDataFileStore();

// Install the imagery into the FileStore. final Layer layer = installSurfaceImage(file.getName(), file, fileStore);

// If something went wrong. if (layer == null) return;

//Retrieve the LayerList, which holds all the layers. //wwd isa common abbreviation fora WorldWindow object. LayerList layers = wwd.getModel().getLayers();

//Add the created layer. layers.add(layer); }

7.6 Carrying out the test The execution of this test was done by starting the application and loading an image. The time it took to load the image was gauged but not stringently measured. After the image was loaded general performance was evaluated by panning, zooming in and out on the image and looking at the CPU and memory usage with the Task Manager application.

56 Chapter 8, Test results

8.1 Symbols performance The following table lists the obtained frames per second, FPS, when simulating a certain number of symbols. Note that ArcGIS Runtime has an option to use an accelerated display or not. The results in parentheses are when not using the accelerated display.

# Symbols Carmenta Maplink ArcGIS Luciad- WorldWind Engine Pro Runtime Lightspeed 25 000 2,3 13,2 2,5 (2,6) 10,4 14 50 000 1,2 6,8 1,3 (1,3) 5,7 7,5 100 000 0,5 3,3 1 2,9 3 200 000 0,3 1,7 0,1 1,5 1,5 400 000 0,15 0,83 0,05 0,78 1

Table 1: The achieved FPS when simulating a certain number of symbols.

8.1.1 The quality of the measurement In general I believe that this measurement is accurate and that a comparison between the different map engines based upon this measurement is valid.

There are a number of flaws that I can immediately detect in this test, which likely impacts the result to a degree. The measurement for LuciadLightspeed used the GXY view, using a Lightspeed view would likely yield different results. Some testing has been carried out using a Lightspeed view but due to technical issues no conclusive results were had. These tests with a Lightspeed view indicated a general good performance but should not be relied on.

The test for Carmenta Engine was carried out with a developed C#/.NET application. As Carmenta Engine supports a variety of platforms it would have been desirable to measure the performance on a number of platforms. However, there was no time available to test performance across a range of platforms in the scope of this project.

I found no way to measure the fps in ArcGIS Runtime. So the measurements was done by simply looking at the screen and counting the updates during

57 a period of time, a minute specifically, and dividing the number of updates by sixty to get the number of frames per second. Thus these results are somewhat inaccurate but should still be good enough to serve as a general indicator on its performance.

8.1.2 Comments on the findings The achieved fps for most map engines follow a linear correlation, doubling the number of symbols results in a halving of the fps. The performance of ArcGIS Runtime fluctuates seemingly somewhat unpredictably, doubling the number of symbols from 50 000 to 100 000 results in a mild drop in performance by about 25%, which is just half the drop one could expect. Doubling from 100 000 to 200 000 results in a severe performance hit reducing the fps by 90%. As previously mentioned ArcGIS Runtime has an option to use an accelerated display or not. Not using this seemingly has little effect on the performance but limits the number of symbols that can be displayed, trying to display 100 000 symbols resulted in very poor responsiveness and the program essentially froze.

8.2 Reading raster data In addition to the symbol test another test was carried out to determine how well the map engines handles raster data, GeoTIFFs in this case.

The challenge with this test is to somehow perform an empirical analysis, which I found no way of doing. The following is just a general description of how the different map engines handle raster data.

8.2.1 Carmenta Engine One important factor on how well Carmenta Engine handles raster data is the dataset object used. There are primarily two datasets to choose between, ImageDataSet and GdalDataSet. In this project I have mainly used the latter and the following description is applicable only for it. As a side note the GdalDataSet seems to be generally ”better” than ImageDataSet.

The general consumption of memory and cpu usage is low. It takes some time to read large images. One thing I noticed was that the program ”reloads” the image. For example, having loaded an image the user zooms in on a particular area. That will trigger loading a new level of detail (if available) suitable to the new zoom level. If the user then zooms back to the previous

58 altitude the image will have to be reloaded, even though it was loaded a little while ago. This leads to a relatively sluggish handling compared to the others. However after correspondence with Carmenta we discovered some simple improvements to the approach of handling raster data which gives better performance and by large eliminated the above described issue.

8.2.2 MapLink Pro Due to the difficulties I encountered with MapLink Pro the implemented image handling might be suboptimal and could have been improved to achieve substantially better results.40 Anyway this is how MapLink Pro performed:

Reading an image goes fast, this is likely explained by the fact that the entire image is put into memory. An index is created for later access. The image is not processed in any manner. Most noticeably no levels of detail are generated so there is only one level of detail, the actual image. As the image is required to be loaded in memory, images larger than the available memory are not possible to be read. Furthermore the program is not responsive while the image is loaded.

As an alternative to the above way of loading images there is support for creating a pyramid which would likely perform better in regards of handling large images. Unfortunately the trial license for MapLink Pro expired before this could be tested.

8.2.3 ArcGIS Runtime ArcGIS Runtime cannot directly read images as it is meant to read package files.41 There are a few possibilities to circumvent this limitation. One could for example be to read a package file containing images, read a KML file that includes an image or fetch data from a WMS server. All of these alternatives seem inadequate to base an evaluation on and subsequently an evaluation would be of spurious validity.

8.2.4 LuciadLightspeed Reading an image goes very fast. Initially a low resolution version of the image is displayed. The image is processed continuously as the application is running and different levels of detail are generated as needed.

40See section 3.1 for more information. 41This is not entirely true, see Chapter 9, on page 61 for more information.

59 One issue I encountered was that it was not possible to load images larger than approximately 3.5 gigabytes. This limitation might be an issue but could probably be solved by splitting the image.

8.2.5 WorldWindJava As mentioned earlier in this report there are a few possible ways to load raster data in WorldWind. The following description is applicable to the result using the method described in section 7.5.5.

WorldWind uses a third part library, GDAL, to process images. Different levels of detail are generated and the image will not be displayed until this work is done. Consequently, reading an image takes time and disk space. This will likely not be of any inconvenience unless the image is rather large. Once the work is done, performance is good. I have carried out a test to see how long it takes to process images and the result can be found below.

# Image size (MB) Average processing time (seconds) 11 1 21 2.5 42 3.5 84 13.5 167 14 334 55

Table 2: WorldWind image processing time.

8.2.6 Summary: Image handling To summarize my findings regarding image handling I could simply say that all map engines perform quite well. Memory consumption is generally good with the exception of MapLink Pro that likes to have the entire image loaded into memory, which leads to a significant memory usage with larger images. However this might be remedied by processing the image and creating a pyramid.

60 Chapter 9, Conclusions

This project led to the following conclusions42 regarding the support for the data formats:

Format Carmenta Maplink ArcGIS Luciad- WorldWind Engine Pro Runtime Lightspeed ARINC 424 Yes Preprocessed Unknown Yes No DTED Yes Preprocessed Preprocessed43 Yes Limited GeoTIFF Yes Yes Preprocessed43 Yes Yes KML Yes* No Yes Yes Yes S-57 charts Yes Preprocessed Preprocessed Yes No Shapefile Yes Limited Preprocessed43 Yes Yes VPF Yes* Preprocessed Preprocessed Yes Yes

OGC Services WMS Yes* Yes Yes* Yes Yes WFS Unknown Unknown No Yes Unknown WCS Limited No No Yes* No

Table 3: Format support summary.

Yes Very comprehensive support. An asterisk signifies some sort of minor shortcoming. Limited A limited support for directly reading the format. Preprocessed Cannot be read directly, but can be processed by an auxiliary application into a format that can be read. No No support at all. Unknown The support has not been investigated.

Table 4: Format support legend.

42Take note: This is strictly what has been demonstrated to work in this project. Additional capabilities might exist but if so they have not been proven in the scope of this project. The prudent reader should also read my comments, which follows on the next page. 43It has surfaced that it might be possible to read GeoTIFFs, Dted and Shapefiles in ArcGIS Runtime, however this was revealed very late into the project and could not be tested.

61 9.1 Comments regarding the findings 9.1.1 Carmenta Engine In general the main effort when it comes to Carmenta Engine is to specify how data should be visualized. The philosophy of Carmenta Engine is to let the developer visualize the data as he desires, only fundamental visualization tools are provided and it’s up to the user to construct an visualization plan as elaborate as he desires.

S-57 There is a specialized visualizer, the S52Visualizer that visualizes the data according to the S-52 standard. This makes the displaying of S-57 data very easy.

VPF VPF data is usually structured in a database that contains libraries. A library is divided into coverages that in turn are divided into features. Just as an example a library might be ””, a coverage might be ”vegetation” and features in this coverage could be grassland, forested regions and swamps.

The VPFDataSet that enables an application to read VPF data binds to a specific feature. The implication of this is that the program will either have to ”explore” a VPF database to find all available libraries, coverages and features, then let the user choose what features to display. Alternatively the program can rely on the user to input all the necessary data (path, library, coverage, feature). Both options are left up for the developer to implement, particularly the former alternative requires a bit of work.

What would likely be preferable to the above would be that Carmenta Engine could do this discovering for us. A VPF database has a standardized structure and LuciadLightspeed and WorldWind can automatically explore a VPF database.

KML Carmenta can successfully handle points, lines and polygons, but I was not able to visualize rasters. I contacted Carmenta about this issue, they replied saying that Carmenta Engine relies on a third-party library, OGR Simple Features Library. This enables Carmenta Engine to extract geometries, points, lines and such along with their attributes. More advanced functionality, such as ”embedded” rasters are not supported. They think it might be possible to achieve greater support for these advanced features of KML files by creating a custom dataset.

62 Another slight shortcoming regarding KML support is that KMZ files are not supported. KMZ files are simply zip archives containing a KML file so including support for KMZ files should not be a large undertaking.

OGC Services It is my opinion that the support for OGC services in Carmenta Engine is somewhat limited. Let us first look at WMS. There is a class, OgcWmsLayer, that essentially handles the final step, the GetMap request, in the process of retrieving data from a WMS server. A GetMap request is normally preceded by a few other stages of communication, and it will be up to the developer to handle these stages more or less all by himself. Expounding on the communication left for the developer to handle would first be the the GetCapabilities request. The result of a GetCapabilities request will be a returned xml document describing amongst other things the available data that can be retrieved from the server, and it will be up to the developer to parse this document. Secondly, there is also a optional step after the GetMap request, GetFeatureInfo, that will entirely be up to the developer to support. WMS is a standard, and it would most likely be possible for Carmenta Engine to provide support for handling the additional communication that is not currently supported. Any developer creating some form of WMS client, or for any OGC service in general, will likely have to ”reinvent the wheel”, that is, write code that will do the same work as a piece of code someone else has written before. I think it would be desirable if Carmenta Engine provided tools for such a generic task.

I would describe the support for WMS as rudimentary, but sufficient. The same cannot be said for WCS. While trying to implement support for using a WCS service I encountered some problems. The workflow is quite similar as for WMS. There is a class, OgcWcsDataSet, that essentially handles the final step, the GetCoverage request, in the process of retrieving data from a WCS server. However testing adding a coverage gave no results, nothing happened. Checking the server’s log gave me some more information on what went wrong. The log confirms that the server received a request from the client, but the request was flawed. Here is an excerpt from the server log showing first the faulty request from the Carmenta Engine and then a valid request from another developed application: INFO [.wcs] - Request: getCapabilities acceptVersions = null sections = null acceptFormats = null

63 updateSequence = null baseUrl = http://localhost:8080/geoserver/ namespace = null extendedProperties = {} service = WCS WARN [geoserver.ows] - Could not get a ServiceInfo for service w cs thus could not check if the service is enabled

INFO [geoserver.wcs] - Request: getCapabilities section = / service = WCS updateSequence = null version = 1.0.0 baseUrl = http://localhost:8080/geoserver/ extendedProperties = {}

I contacted Carmenta and inquired about this. Their reply was that Carmenta Engine was incompatible with Geoserver when it came to WCS, thus explaining the faulty request. There is also the option to use the GdalDataSet which can handle both WMS and WCS data. Carmenta discourages the usage of GdalDataSet for this task though due to experienced difficulties to get it working correctly. Nevertheless I tried with a GdalDataSet but it did not work.

These issues lead me to conclude that the support for WCS services are not satisfactory. Furthermore, Carmenta states that there exists a plugin that enables support for WFS. Unfortunately this was revealed at the very end of the project and there was no time available to test it.

9.1.2 Maplink Pro Due to the difficulties I encountered with MapLink Pro my ability to draw conclusions on supported formats is hampered.44 I will start by stating that a number of formats cannot be directly read by a running application, among these are ARINC and VPF data which this project is particularly concerned with.

DTED & S-57 The ability to directly import these two formats into a running application has not been investigated. They have only been proven able to be read into MapLink Studio, which can subsequently generate map files which an application can read.

44See section 3.1 for more information.

64 Shapefiles MapLink Pro can import shapefiles into a running application, though the approach is oppressively difficult. I suspect that the approach of importing shapefiles can give a general indication of how difficult it would be to import DTED and S-57 data. Due to the amount of effort, which I consider to be beyond what is reasonable, to implement support for reading shapefiles at runtime my resulting judgement is that MapLink Pro scores ”Limited”.

9.1.3 ArcGIS Runtime ArcGIS Runtime is meant to read package files created in ArcGIS Desktop, as such it cannot directly read any of the formats, with the exception of KML files which it handles well. Packages can in turn include data from the formats. It is unclear if ARINC data can be imported into ArcGIS Desktop.

It can use a WMS service but as was the case for Carmenta Engine only the final step, the GetMap request, is handled by the map engine. The developer will have to handle the other requests by himself.

9.1.4 LuciadLightspeed LuciadLightspeed performs very well and I have generally few comments to add. The only comment I have to add is that I experienced some issues with the WCS capabilities of LuciadLightspeed. I setup a default Geoserver installation that includes some sample data. Trying to retrieve a coverage failed for most of the available coverages, giving various error messages. The reason for these errors could stem from many sources.

• There might be some incompatibility specifically between Luciad- Lightspeed and Geoserver, as was the case for Carmenta Engine. • The coverages and its metadata might be erroneous in some way. • LuciadLightspeed does something wrong, or Geoserver does.

9.1.5 WorldWindJava The primary source of information on WorldWind is the code itself, and it is sometimes hard to get a good understanding.

DTED It seems as if WorldWind can read individual .dt* files, which are the files that contain the actual elevation data. However, normally a .dt* file covers only a small area and DTED data is usually structured as a dataset.

65 It is more desired for a program to read a DMED file which is the file that specifies the content of such a DTED dataset.

WFS WorldWind can use a WFS service, since by it’s default configuration displays some place-name data it obtains from a WFS service. However adding support for WFS in an application requires a degree of knowledge about the WFS standard I do not have. In conclusion this capability of WorldWind has not been sufficiently investigated to provide an adequate answer.

66 Chapter 10, Reflections

10.1 The different philosophies In this project a number of map engines have been examined. During the course of this work I have discovered that the philosophies behind each map engine differ to varying degrees. I will try to give an description below for each software product.

The idea behind Carmenta Engine is to provide a map engine and relatively low level tools that lets the developer have a extensive degree of control. Development using Carmenta Engine can start at more rudimentary level compared to for example LuciadLightspeed and WorldWind since more basic utilities have to be created by the developer.

The map configuration concept should also be mentioned. The idea is that a map is created in Carmenta Studio, a configuration file is created that reflects the produced map and can then be loaded in applications. This approach is not at all mandatory, but perhaps should be recommended. Everything done in a configuration file can also be done in a running application. However it would likely require of the developer to produce functionality for the user to access and modify the various data objects that constitutes a map.

MapLink Pro has some similarities to Carmenta Engine in concept. There is generally few supplied utilities and software components that would be nice to have in a desktop application. It seems that the idea is that developed applications should mainly read map files and interact with them. Adding functionality to a program built upon MapLink Pro can take a lot of effort, compared to the other map engines. 45

There is a program ”MapLink Studio” where a map is created and a file is generated which can then be read by a program. My impression is that MapLink Pro is oriented towards reading predesigned map files instead of ”raw” data.

Another thing I must point out regarding MapLink Pro is the support for reading data formats. All data formats that are supported can be read into the studio application. However the support for reading data formats

45Envitia has answered stating that this is a conscious decision on the design of MapLink Pro, with the intention of increasing its flexibility.

67 in a running application is only a subset of all supported data formats, presently, roughly half of the supported data formats can be read in a running application.

There is also a COM interface that enables an application to access some of the functionality of MapLink Studio, which would enhance the abilities the client program. However this has not been investigated in this project.

ArcGIS Runtime is a very thin client concept. An application can read ”packages” that are produced in ArcGIS Desktop, which I for now will just describe as a sort of studio application. Besides package files the application can directly read KML/KMZ files and retrieve data through WMS. These two abilities could provide some workarounds, for example it would likely be possible to read raster data and shapefiles by first packaging it as KML files.

It should also be mentioned that there is a software product called ”ArcGIS Engine”. I have not looked at it more closely but I think more ”fully featured” applications can be created with it.

LuciadLightspeed has a lot of support for producing a desktop program with extensive functionality and I think it could be said that LuciadLightspeed has a similar philosophy to WorldWind regarding what the developer is provided with. There are many already-made components, tools and utilities that could be used in a final application.

The idea behind WorldWind is generally to help the developer produce a standalone client with a lot of functionality. There are many already-made components, tools and utilities that could be used in a final application.

10.2 Detecting a pattern, Producer & Consumer One thing that dawned upon me as this project was drawing to its conclusion was that the map engines to varying degrees followed a certain pattern, Producer and Consumer. The meaning of this pattern is that there are two defined and separate roles. A producer creating a product and a consumer using this product.

ArcGIS Runtime is likely the most pronounced example of this pattern, a thin client that can read few data formats besides the preprocessed package files. As a counterweight to the clients limited capabilities the production program, ArcGIS Desktop, is very capable and the user has extensive abilities

68 to read and modify data to produce a map according to his desires.

Carmenta Engine and MapLink Pro also adhere to the pattern of Producer - Consumer. MapLink Pro could perhaps be regarded as further out on the scale compared to Carmenta Engine, since the capabilities to read data formats are limited and the studio application is required to handle roughly half of the supported data formats. Carmenta Engine might be walking a golden middle path, where it combines a studio application and a client program with few limitations.

At the other end of the spectrum is LuciadLightspeed and WorldWind that not at all rely on a studio application and the workload is put entirely on the client application. It would be up to the developer to create controls for the user to manipulate the data as done in a studio application.

After reflecting upon these fundamental differences, one might justifiably ask the question:

What is preferable: A client with extensive abilities to read various data formats, but where the appearance has to be configured in runtime, or a client that essentially takes a predesigned map which ensures a good appearance?

Both approaches have their strengths and weaknesses, and whichever solution is preferable likely depends upon the situation and intended usage of the software.

10.3 Various Reflections 10.3.1 Carmenta Engine One advantage of Carmenta Engine is the support for various programming languages. The kernel itself is written in C++ and can be accessed by several language specific API:s. API:s exists currently for C++, .NET46, Java and Python. It should be noted that API-specific limitations occur, one example would be the java API which currently has no support for 64-bit.47

The documentation is quite good. It has a good introductory section on Carmenta Engine in general and application development.

46C#, Visual Basic, C++/CLI or any other .NET language 47Carmenta Engine Documentation: The Carmenta Engine Java API.

69 One difficulty I encountered working with Carmenta Engine was the need for me to have a fair degree of knowledge regarding various data formats.

10.3.2 MapLink Pro As a novice with no previous knowledge regarding the product I found the documentation to be of limited help. The main developer’s guide feels as if it is written for a technically experienced audience with familiarity of this segment of software. Besides the developer’s guide there is also the API reference. A positive thing about it is the class and interaction diagrams that gives a good view of how classes are related. On the downside the API reference has certain ”holes” where important information is missing. I consider this to be a serious flaw. One example can be found in the entry for the class TSLRasterFilterDataLayer:

”Currently the filters supported is very limited. Please contact sales for additional information.”48

10.3.3 ArcGIS Runtime ArcGIS Runtime is essentially a thin client concept that relies on others to provide data, with the exception that it can read KML files by itself. The documentation is quite good and there are a good number of examples displaying the potential of the map engine. There are a number of possibilities for support. Esri maintains a free forum where the product can be discussed, in addition to that it is possible to pay for more qualified support.

10.3.4 LuciadLightspeed LuciadLightspeed strikes me as a particularly high-quality product. The supplied documentation is very comprehensive. The documentation range from giving a basic ’no-prerequisites-required’ introduction to the various subjects to discussing advanced topics. The amount of effort put into elabo- rating on various advanced topics sets LuciadLightspeed apart from the other products.

There is a comprehensive collection of samples to get a head start with. As a rule there is one sample for every functionality, e.g. one example on reading VPF data, one example on reading KML data, and so on. On the downside many samples and much of the documentation uses the older ’GXY

48MapLink Pro API Documentation: Class TSLRasterFilterDataLayer.

70 view’ instead of the newer ’Lightspeed view’ which leads to having to adapt the knowledge gained if the developer intends to produce an application using the new ’Lightspeed view’.

The new Lightspeed view has a incomplete support for the various data formats, roughly half of the supported data formats are also supported in a Lightspeed view. However, even if a certain data format is not natively supported in a Lightspeed view it can easily be adapted and thus still be displayed in a Lightspeed view.

10.3.5 WorldWind WorldWind seems to be a potent product since it’s open source, and thusly the developer has access to all source code and the ability to modify and extend it as he desires.

A downside is the lacklustre documentation. WorldWind has a javadoc documentation but it lacks a more easily digested guide to development and a general guide to the structure of WorldWind. WorldWindJava is a fairly complex product, and understanding it is challenging when the primary source of information is the code itself.

On the upside a lot of sample code is included in the SDK which can help the developer get a understanding of WorldWind.

There is little opportunity for support. WorldWind has some degree of popularity and there are several software projects built upon WorldWind49, but there isn’t any active developer community to turn to for asking questions. The WorldWind project maintains a forum that is the only place to seek help. The forum isn’t very active nor frequented by a substantial number of users with technical knowledge. On the positive side the forum is frequented by some users who are fairly knowledgeable and also by some of the developers of WorldWind. The forum occasionally goes offline and the staff asks the users for donations to keep the forum running. There is also a wiki that is quite disused and the information is rather dated.

49For a list of software using WorldWindJava see the section ”User Applications” http://goworldwind.org/demos/ and the wiki page ”WWJ Applications” http://worldwindcentral.com/wiki/Java links

71 10.4 Flaws regarding the method One important factor that determined the resulting judgement for each map engine was the order in which they were investigated. I worked with them in the following order: WorldWind, Carmenta Engine, LuciadLightspeed, MapLink Pro, and then finally ArcGIS Runtime. At the start of this project I was considerably less experienced when it came to software development and my general knowledge on various topics related to this project (, the data formats, OGC services and so on). During this project I have gained an amount of knowledge in these areas which in turn made working with a map engine easier. If the map engines had been investigated in another order the judgement might have been somewhat different, however I still think the judgements are sound as they are now.

72 Chapter 11, Future Work

As this project was strongly limited by the allotted time there is plenty of potential for continuing working on this subject. The initial goals of this project could most likely take up another ten weeks. The initial goals, in addition to the ones listed in section 1.2, were as following:

• Investigate the support for Mil 2525B symbology. • 3D view, most of the map engines are 2D displays, but have some form of 3D add-on. • General interaction and editing abilities, to be able to place symbols, edit and move them as an example.

One area of further research would be the performance measurement. In addition to the tests carried out in this project some more have been identified as possibly interesting to conduct. The following are suggestions for further testing:

• Navigation, finding a passable path between two points on terrain. • Mathematical calculations, such as great circle route, reprojections and rotating the map.

The test for data format support looks at the possibility to read formats directly into an application, however some map engines are intended to take preprocessed data from a studio application. It would be good to perform a test that takes this workflow into consideration.

Memory and CPU consumption could be looked into more closely.

73 References

[1] Wikipedia. Article: Arinc424. accessed April 8, 2012. url: http://en. wikipedia.org/wiki/ARINC 424. [2] Wikipedia. Article: DTED. accessed April 8, 2012. url: http://en. wikipedia.org/wiki/DTED. [3] Wikipedia. Article: GeoTIFF. accessed April 8, 2012. url: http://en. wikipedia.org/wiki/GeoTIFF. [4] Wikipedia. Article: Keyhole Markup Language. accessed April 8, 2012. url: http://en.wikipedia.org/wiki/Keyhole Markup Language. [5] Morintech Navigation A/S. Electronic Charts - Standards. accessed April 8, 2012. url: http://www.dkart.ru/enc det.htm. [6] Wikipedia. Article: Shapefile. accessed April 8, 2012. url: http://en. wikipedia.org/wiki/Shapefile. [7] Wikipedia. Article: Vector Product Format. accessed April 8, 2012. url: http://en.wikipedia.org/wiki/Vector Product Format. [8] Wikipedia. Article: Open Geospatial Consortium. accessed June 7, 2012. url: http://en.wikipedia.org/wiki/Open Geospatial Consortium. [9] Wikipedia. Article: Web Map Service. accessed June 7, 2012. url: http: //en.wikipedia.org/wiki/Web Map Service. [10] Wikipedia. Article: Web Feature Service. accessed June 7, 2012. url: http://en.wikipedia.org/wiki/Web Feature Service. [11] Wikipedia. Article: Web Coverage Service. accessed June 7, 2012. url: http://en.wikipedia.org/wiki/Web Coverage Service. [13] Carmenta Engine Documentation: Carmenta Engine Overview. [14] Carmenta Engine Documentation: How Carmenta Engine Displays a map. [15] Carmenta Engine Documentation: Carmenta Studio User’s Guide. [16] Carmenta Engine Documentation: DataSet Class. [17] Carmenta Engine Documentation: Raster Data Guide. [18] Carmenta Engine Documentation: S52Visualizer Class. [19] Carmenta Engine Documentation: Carmenta Engine FAQ, ”When Should I have my data in a TileLayer?”. [21] MapLink Pro Developer’s Guide, section 2.1: MapLink Studio, p. 12.

74 [22] MapLink Pro - Data Formats Support. Data Sheet. [23] MapLink Pro SDKs. Data Sheet. [24] MapLink Pro Developer’s Guide, section 3: Basic MapLink Applications, p. 17. [25] MapLink Pro Developer’s Guide, section 7.4: Initialisation and Clean Up, p. 29. [26] Esri. COTS GIS: The Value of a Commercial Geographic Information System. 2002, pp. 1–2. [28] Common Questions: What programming languages are supported for ArcGIS Runtime? accessed June 7, 2012. url: http://www.esri.com/ software/arcgis/runtime/common-questions. [29] ArcGIS Runtime SDK for WPF, API Reference: Map Class. [30] ArcGIS Runtime SDK for WPF, API Reference: Layer Class. [32] LuciadLightspeed v2012.0 Developer’s Guide, chapter 2: LuciadLightspeed architecture, p. 9. [33] LuciadLightspeed v2012.0 Developer’s Guide, appendix E: Supported file formats, p. 447. [34] LuciadLightspeed v2012.0 Developer’s Guide, section 27.2.3: Adapting GXY Layers, p. 305. [38] Concepts. accessed June 7, 2012. url: http://goworldwind.org/developers- guide/concepts/. [47] Carmenta Engine Documentation: The Carmenta Engine Java API. [48] MapLink Pro API Documentation: Class TSLRasterFilterDataLayer.

75 Appendix A

12.1 Code for symbol test 12.1.1 Carmenta Engine Main Program using System; using System.Threading; using System.Collections.Generic; using System.Windows.Forms; using Carmenta.Engine; using Carmenta.Engine.Forms;

/* * This is the main application developed using * Carmenta Engine. This code is meant to demonstrate * the application and how the symbol test was implemented. * Some code has been omitted due to its irrelevancy * for the symbol test implementation. */ namespace CarmentaApp { public partial class MainWindow : Form { //A control that displaysa View. MapControl mapControl_;

//A MemoryDataSet for the symbols. MemoryDataSet symbolsDataset_ = new MemoryDataSet();

//A list to hold the symbols List symbolList_ = new List();

//A number generator. Random = new Random();

//The number of symbols to create. int NUM_SYMBOLS = 25000;

public MainWindow() { // An initialization is required in any application. InitializeComponent();

// Createa MapControl

76 mapControl_ = new MapControl();

// Set the MapControl to fill out. mapControl_.Dock = DockStyle.Fill;

// Add the MapControl to the program window. panel1.Controls.Add(mapControl_);

/* * This application usesa configuration file * */ try { Configuration configuration = new Configuration(@"../../emptyworldmap.px"); mapControl_.View = configuration.GetPublicObject(@"emptyworldmap.View0") as Carmenta.Engine.View; } catch (Exception e) { MessageBox.Show(e.Message); }

// Seta Tool to let the user interact with the map. mapControl_.Tool = new StandardTool();

//A FpsMonitor will monitor the frame rate. FpsMonitor fps = new FpsMonitor();

// The mapControl_.Updated += new EventHandler(fps.onUpdated); } private void startTestToolStripMenuItem_Click(object sender, EventArgs e) { /*Add features*/ for (int i = 0; i < NUM_SYMBOLS; ++i) { Feature ft = new Feature(new PointGeometry(0, 0), Crs.Wgs84LongLat); symbolList_.Add(ft); symbolsDataset_.Insert(ft); }

ReadOperator ro = new ReadOperator(symbolsDataset_);

VisualizationOperator vo = new VisualizationOperator(ro);

SymbolVisualizer sv = new SymbolVisualizer(); sv.Symbol = Symbol.SmallDot;

77 sv.Color = System.Drawing.Color.White;

vo.Visualizers.Add(sv);

OrdinaryLayer l = new OrdinaryLayer(vo); l.Name = "Symbols layer";

mapControl_.View.Layers.Add(l);

mapControl_.UpdateTimerEnabled = true; mapControl_.UpdateTimerInterval = 1;

TimerCallback tcb = this.SymbolUpdater; System.Threading.Timer t = new System.Threading.Timer(tcb, null, 0, Timeout. Infinite); }

public void SymbolUpdater(object o) { while (true) { foreach (Feature ft in symbolList_) { Point p = new Point(getRandom(), getRandom()); ft.Geometry.Move(p); } } }

//A function that will returna random value between -1 and 1. private double getRandom() { double d = r.NextDouble(); if(r.NextDouble() > 0.5) { return d; } else return -d; }

} }

FPS Monitor

78 using System; using System.Diagnostics; namespace CarmentaApp { class FpsMonitor { long paintcount = 0; Stopwatch sw;

// Number of samples to use for calculating an average. private const int FPS_AVG = 20;

public FpsMonitor() { sw = new Stopwatch(); sw.Start(); }

public void onUpdated(object sender, EventArgs e) { ++paintcount;

if (paintcount % FPS_AVG == 0)//20 updates has ocurred. { sw.Stop(); float t = sw.ElapsedMilliseconds;

System.Console.Out.WriteLine("Average FPS: " + 1f/(t/(FPS_AVG*1000)));

sw.Reset(); sw.Start(); } } } }

12.1.2 Maplink Pro Application class

// envitia2.cpp: Defines the class behaviors for the application.

#include "stdafx.h" #include "afxwinappex.h" #include "afxdialogex.h" #include "envitia2.h" #include "MainFrm.h"

79 #include "envitia2Doc.h" #include "envitia2View.h"

#ifdef _DEBUG #define new DEBUG_NEW #endif

/* This is the application object of the program. The header file is completely automatically generated and of no interest.

*/

// Cenvitia2App initialization

BOOL Cenvitia2App::InitInstance() { const char * configDirPath = 0 ;// Replace if deployed

TSLThreadedErrorStack::clear() ;

TSLDrawingSurface::loadStandardConfig( configDirPath ) ;

TSLSimpleString msg( "" ); bool anyErrors = TSLThreadedErrorStack::errorString( msg, "Initialisation Errors : \n" ) ; if ( anyErrors ) { DBOUT( msg ) ; return( 0 ) ; }

... (Default MFC generated code) } int Cenvitia2App::ExitInstance() { TSLDrawingSurface::cleanup(); //TODO: handle additional resources you may have added AfxOleTerm(FALSE);

return CWinAppEx::ExitInstance(); }

80 View class header file

// envitia2View.h: interface of the Cenvitia2View class

#pragma once class Cenvitia2View : public CView { protected:// create from serialization only Cenvitia2View(); DECLARE_DYNCREATE(Cenvitia2View)

// Attributes public: Cenvitia2Doc* GetDocument() const; private: //A Map data layer. TSLMapDataLayer* mapDataLayer; protected: //A DDO Layer. TSLObjectDataLayer* DDOLayer; private: //The DrawingSurface TSLWGLAcceleratedSurface* drawingSurface; //boost::mutex* drawingSurfaceLock;

//Render Control TSLAcceleratedMTRenderControl* renderControl;

// Surface Configuraton TSLAcceleratorConfiguration acceleratorConfiguration;

class symbolUpdater { public: symbolUpdater(Cenvitia2View* const parent_, TSLObjectDataLayer* DDOLayer_); ˜symbolUpdater(); void operator()(); void stop(); private: Cenvitia2View* const parent; TSLObjectDataLayer* DDOLayer; bool running; //boost::mutex* shutdown; inline double getRand(); };

Cenvitia2View::symbolUpdater* updater;

81 bool bool_threadStopped;

static const int NUM_SYMBOLS = 400000;

long drawCount; //boost::chrono::system_clock::time_point start; //boost::chrono::duration sec;

// Operations private: void stopBackgroundThread(); void resumeBackgroundThread(); void redrawDrawingSurface(); // Overrides public: virtual void OnDraw(CDC* pDC);// overridden to draw this view // Implementation public: virtual ˜Cenvitia2View(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif

// Generated message map functions protected: DECLARE_MESSAGE_MAP() public: virtual void OnInitialUpdate(); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnRuntest(); };

#ifndef _DEBUG// debug version in envitia2View.cpp inline Cenvitia2Doc* Cenvitia2View::GetDocument() const { return reinterpret_cast(m_pDocument); } #endif

View class source file

// envitia2View.cpp: implementation of the Cenvitia2View class

#include "stdafx.h" // SHARED_HANDLERS can be defined in an ATL project implementing preview, thumbnail // and search filter handlers and allows sharing of document code with that project .

82 #ifndef SHARED_HANDLERS #include "envitia2.h" #endif

#include "envitia2Doc.h" #include "envitia2View.h" #include "MyDDO.h"

#ifdef _DEBUG #define new DEBUG_NEW #endif static void redrawCallback(void *arg) { // Callback received to notify us that the screen needs up-dating. // This will only occur if the tile drawing queue becomes empty. Cenvitia2View *view = (Cenvitia2View*)arg;

// The callback will be in the context of the background thread // so we must not call back into MapLink. if (view->m_hWnd != NULL) { view->InvalidateRect( 0, FALSE ); } }

// Cenvitia2View IMPLEMENT_DYNCREATE(Cenvitia2View, CView)

BEGIN_MESSAGE_MAP(Cenvitia2View, CView) ON_WM_SIZE() ON_WM_ERASEBKGND() ON_COMMAND(ID_RUNTEST, &Cenvitia2View::OnRuntest) END_MESSAGE_MAP()

// Cenvitia2View construction/destruction Cenvitia2View::Cenvitia2View() { drawingSurface = NULL; mapDataLayer = NULL; DDOLayer = NULL; updater = NULL;

renderControl = 0; bool_threadStopped = true;

drawCount = 0; //start= boost::chrono::system_clock::now(); }

83 Cenvitia2View::˜Cenvitia2View() { if(updater) { updater->stop(); delete updater; updater = NULL; } if (bool_threadStopped) { // restart the stopped thread so we can doa clean shutdown. if (renderControl) { renderControl->resumeThread(); } } renderControl = 0;// owned by the Surface.

if ( drawingSurface ) { delete drawingSurface; drawingSurface = NULL; } if (DDOLayer) { DDOLayer->destroy(); DDOLayer = NULL; } }

// Cenvitia2View drawing void Cenvitia2View::OnDraw(CDC* pDC) { Cenvitia2Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return;

if ( drawingSurface ) { RECT rect ; if ( pDC->GetClipBox( &rect ) == NULLREGION ) GetClientRect( &rect ) ;

++drawCount;

drawingSurface->drawDU( rect.left, rect.bottom, rect.right, rect.top, true );

84 if(drawCount % 20 == 0) { sec = boost::chrono::system_clock::now() - start; DBOUT2("Average FPS: ", (20.0/sec.count())); start = boost::chrono::system_clock::now(); } } }

// Cenvitia2View diagnostics #ifdef _DEBUG void Cenvitia2View::AssertValid() const { CView::AssertValid(); }

void Cenvitia2View::Dump(CDumpContext& dc) const { CView::Dump(dc); }

Cenvitia2Doc* Cenvitia2View::GetDocument() const // non-debug version is inline { ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(Cenvitia2Doc))); return (Cenvitia2Doc*)m_pDocument; } #endif //_DEBUG

// Cenvitia2View message handlers void Cenvitia2View::OnInitialUpdate() { CView::OnInitialUpdate(); if ( !drawingSurface ) { drawingSurface = new TSLWGLAcceleratedSurface((void*)m_hWnd, false); drawingSurface->setOption(TSLOptionDynamicArcSupportEnabled, true );

//Usea Multi-threaded render control renderControl = new TSLAcceleratedMTRenderControl(false, false, redrawCallback, this); drawingSurface->addRenderControl(renderControl); drawingSurface->setOption(TSLOptionAcceleratorZoomAsynchronous, false);

acceleratorConfiguration.tileSize(256); acceleratorConfiguration.numberTilesX(10); acceleratorConfiguration.numberTilesY(10); acceleratorConfiguration.textureFilterNearest(true); acceleratorConfiguration.textureBorder(false); acceleratorConfiguration.dynamicArcTolerance(10);

85 acceleratorConfiguration.viewExpansion(12.5);

drawingSurface->setConfiguration(acceleratorConfiguration);

RECT cr; GetClientRect( &cr ) ; drawingSurface->wndResize( cr.left, cr.top, cr.right, cr.bottom, false ); } } void Cenvitia2View::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); if ( drawingSurface ) { drawingSurface->wndResize(0, 0, cx, cy, false, TSLResizeActionMaintainTopLeft ); } }

BOOL Cenvitia2View::OnEraseBkgnd(CDC* pDC) { return true; } void Cenvitia2View::stopBackgroundThread() { if (renderControl) { if (!bool_threadStopped) { bool_threadStopped = true; renderControl->stopThread(); } } } void Cenvitia2View::resumeBackgroundThread() { if (renderControl) { if (bool_threadStopped) { bool_threadStopped = false; renderControl->resumeThread(); } } }

86 void Cenvitia2View::OnRuntest() { if(drawingSurface && mapDataLayer) { srand((unsigned) time(NULL));

DDOLayer = new TSLObjectDataLayer();

for(int i = 0; i < NUM_SYMBOLS; ++i) { DDOLayer->addDDO(new MyDDO()); }

if ( drawingSurface->addDataLayer(DDOLayer, "DDO Layer") ) { updater = new symbolUpdater(this, DDOLayer); boost::thread t(*updater); } } }

Cenvitia2View::symbolUpdater::symbolUpdater(Cenvitia2View* const parent_, TSLObjectDataLayer* DDOLayer_) : parent(parent_), DDOLayer(DDOLayer_), running(true) {}; void Cenvitia2View::symbolUpdater::operator()() { const TSLlist& DDOList = DDOLayer->getDDOList(); TSLlist::const_iterator itEnd = DDOList.end(); TSLCoord* pos; while(running) { TSLlist::iterator it = DDOList.begin(); while ( it != itEnd ) { MyDDO* dataObject = (MyDDO*)(*it);

pos = new TSLCoord(500000*getRand(),500000*getRand()); pos->operator+=(dataObject->position()); dataObject->move(*pos,true); delete pos; ++it; } DDOLayer->notifyChanged(true); parent->InvalidateRect( NULL, FALSE ); }

87 }

inline double Cenvitia2View::symbolUpdater::getRand() { if ( ((double)rand()/(double)RAND_MAX) > 0.5 ) return ((double)rand()/(double)RAND_MAX); else return -((double)rand()/(double)RAND_MAX); }

void Cenvitia2View::symbolUpdater::stop() { running = false; }

Cenvitia2View::symbolUpdater::˜symbolUpdater() { DDOLayer = NULL; }

Data Object class header file

#pragma once #include "tsldisplayobject.h"

#define OUTLINE_COLOUR 0x00f0f0f0 class MyDO : public TSLDisplayObject { public: MyDO(void); virtual ˜MyDO(void); virtual bool draw(TSLRenderingInterface* ri, TSLEnvelope* extent); private: int m_xPos, m_yPos ;// Store for the position };

Data Object class source file

#include "StdAfx.h" #include "MyDO.h"

MyDO::MyDO(void) { // Without this, the symbol disappears when the centre goes off screen

88 setPixSize( -5, -5, 5, 5 ); }

MyDO::˜MyDO(void){} bool MyDO::draw(TSLRenderingInterface* ri, TSLEnvelope* extent) { HDC surfaceDC = (HDC)(ri->handleToDrawable()) ;

/////////////////////////////////////////////////////////////// // Createa brush with the track’s hostility colour code // anda black outline HPEN pen = ::CreatePen( PS_SOLID, 0, 0x00F0F0F0 ) ; HBRUSH brush = ::CreateSolidBrush( 0x00F0F0F0 ) ; HANDLE oldPen = ::SelectObject( surfaceDC, pen ) ; HANDLE oldBrush = ::SelectObject( surfaceDC, brush ) ; ///////////////////////////////////////////////////////////////

TSLCoord coord = m_ddo->position(); TSLTMC xCo, yCo; ri->TMCToDU( coord.x(), coord.y(), &xCo, &yCo ); m_xPos = xCo; m_yPos = yCo;

::Ellipse( surfaceDC, m_xPos - 3, m_yPos - 3, m_xPos + 3, m_yPos + 3 ) ;

//////////////////////////////////////////////////////// // Reselect previous attributes and delete these ones ::SelectObject( surfaceDC, oldPen ); ::SelectObject( surfaceDC, oldBrush ); ::DeleteObject( pen ); ::DeleteObject( brush ); ///////////////////////////////////////////////////////

return true; }

Dynamic Data Object class header file

#pragma once class MyDDO : public TSLDynamicDataObject { public: MyDDO(void); virtual ˜MyDDO(void); virtual TSLDisplayObject* instantiateDO(TSLDisplayType key, int dsID=0) const; };

89 Dynamic Data Object class source file

#include "StdAfx.h" #include "MyDDO.h" #include "MyDO.h"

MyDDO::MyDDO(void) {} MyDDO::˜MyDDO(void) {}

TSLDisplayObject* MyDDO::instantiateDO(TSLDisplayType key,int dsID) const { return new MyDO(); }

12.1.3 ArcGIS Runtime Main Program using System; using System.IO; using System.Windows; using System.Collections.Generic; using System.Threading; using ESRI.ArcGIS.Client.Local; using ESRI.ArcGIS.Client; using ESRI.ArcGIS.Client.Tasks; using ESRI.ArcGIS.Client.Symbols;

/* * This is the main application developed using * ArcGIS Runtime. This code is meant to demonstrate * the application and how the symbol test was implemented. * Some code(but not much) has been omitted due to its * irrelevancy for the symbol test implementation. * * Also see the belonging axml document. */ namespace EsriTestApp { public partial class MainWindow : Window { //The number of symbols to create.

90 int NUM_SYMBOLS = 50000;

//A GraphicsLayer that will hold the symbols. GraphicsLayer glayer;

//A number generator. Random r = new Random(); public MainWindow() { try { // Set the ArcGIS Runtime license by providing the license string. ArcGISRuntime.SetLicense("a secret string");

// Initialize the ArcGIS Runtime before any components are created ArcGISRuntime.Initialize();

// An initialization is required in any application. InitializeComponent();

// Using an accelerated display is by default disabled. _mapControl.UseAcceleratedDisplay = true;

// Setup the GraphicsLayer that will hold the symbols. glayer = new GraphicsLayer() { // Seta Renderer witha specified symbol to use. Renderer = new SimpleRenderer() { // The symbol is defined in the xaml document. Symbol = LayoutRoot.Resources["DefaultMarkerSymbol"] as Symbol } };

_mapControl.Layers.Add(glayer); } catch (Exception e) { MessageBox.Show("An exception ocurred: " + e.Message); } }

/* * This is where the symbol test is setup and started. * The user clicksa button of the application to start * the test. * * A number of Graphic objects are created and their Geometry

91 * are set to MapPoints. When it is done the SymbolUpdater is * setup and started. */ private void button2_Click(object sender, RoutedEventArgs e) { for (int i = 0; i < NUM_SYMBOLS; ++i) { glayer.Graphics.Add(new ESRI.ArcGIS.Client.Graphic() { Geometry = new ESRI.ArcGIS.Client.Geometry.MapPoint(0, 0), }); } SymbolUpdater(); }

private void SymbolUpdater() { var timer = new System.Windows.Threading.DispatcherTimer();

timer.Tick += (s, e) => { foreach (var g in glayer.Graphics) { ESRI.ArcGIS.Client.Geometry.MapPoint pt = g.Geometry as ESRI.ArcGIS. Client.Geometry.MapPoint; // Note: Multiplication is done in order to geta noticeable movement. pt.X += getRandom() * 100000; pt.Y += getRandom() * 100000; } }; timer.Start(); }

//A function that will returna random value between -1 and 1. private double getRandom() { double d = r.NextDouble(); if (r.NextDouble() > 0.5) { return d; } else return -d; } } }

92 Axml/GUI Definition