This document presents a final project for a computer graphics course that involves developing a terrain viewer application with a client-server architecture. The terrain viewer allows downloading and visualizing polyline data over 3D terrain models. Key features include optimizations like geomipmapping, frustum culling using a quadtree data structure, and an algorithm to correct polyline points over the terrain. The project implements various design patterns and data structures to optimize performance and synchronize concurrent data access between client and server programs.
JOGL Final Project Visualizes Polylines Over Terrain
1. JOGL Final Project
Computer Graphics Principles
University of Trento
Project: Polyline download and visualization over terrain models
Show fu llscreen snapshot
Student: Bruno Nardelli
Lecturer: Raffaele de Amicis
Assistants: Giuseppe Conti and Olga Symonova
Page index
Abstract
1 Presentation
1.1 Package description
1.2 How to run the program
2 User’s manual
3 Application architecture
3.1 General architecture
3.2 Terrain viewer architecture
3.3 Server architecture
2. 4 Most interesting features
4.1 Display optimizations
4.2 Polyline visualization over terrain models
5 Implementation notes
5.1 Design patterns
5.2 Geomipmapping and frustum culling
5.3 Polyline fragmentation algorithm
5.4 Polyline points raising algorithm
6 Conclusions and future work
Abstract
This html page presents the second final project realized by the student Bruno Nardelli as part of the
final exam for the course of Computer Graphics Principles at Trento University. By ‘second
project’ we refer to the project developed using JOGL, as opposed to the ‘first project’, realized
using GL libraries within a C++ program.
The main goal of this project is to apply JOGL functionalities -as well as other computer graphics
notions acquired during the lessons- writing a terrain viewer. As we will show later in this
document, the project implements a number of optimizations to allow better performance on the
visualization of the height model.
In addition to this, we created a client-server architecture that allows different themes to be stored
within a server, and downloaded using the terrain viewer. One can think of a theme as a vector map
in GIS terminology. Within this project, themes are implemented as sets of polylines.
The most interesting aspect of this architecture is that polyline points are stored as 2D coordinates
within the server. Therefore, extra efforts are needed in the client to fragment the lines and correct
the height of the points so as each polyline follows the terrain.
In the following sections, we present the main features of the program in much more detail, and
discuss the way they have been implemented.
back to top
1 Presentation
3. 1.1 Package description
First of all, the project consists of two Java programs. One of them is the terrain viewer, that can act
as a client, and the other is the theme server.
The folder of the project, with the source code, documentation and other files needed to use the
program, is available in a zip file. Click here to download it.
This zip file includes:
A readme file with a description of the most relevant aspects of the project, and important
information on how to launch the programs. A copy of this readme file is also available
here.
The ‘Client’ folder with the source code, precompiled classes, images and running scripts of
the terrain viewer (client). Also the correspondent JBuilder project (First.jpx) has been
attached, just in case it may be useful.
The ‘Server’ folder with the source code, precomp iled classes, images and running scripts of
the server application. Also the correspondent JBuilder project (First.jpx) has been attached,
just in case it may be useful.
Disclaimer: The content of this zip file is distributed ‘as is’, without warranties or support of any
kind. It just constitutes a final project implementation for the course of Computer Graphics
Principles.
Please note that, in addition to the files in the zip file, you will need the following to run the
programs.
Jogl libraries. In particular you will need ‘jogl.dll’, ‘jogl_awt.dll’ and ‘jogl_cg.dll’.
‘jogl.jar’ file.
A text ascii file with the dtm you want to visualize, and a GIF image to be used as a texture.
The next section explains where to copy these files if you want to launch the programs using the
provided running scripts.
1.2 How to run the program
Unzipping the zip file into a directory of your choice will extract two folders, one with the files that
correspond to the client application and another one with the files that correspond to the server
application.
Here we will only show how to launch the client program, i.e. the terrain viewer. Launching the
server implies entirely analogous procedures so we will not explain them.
Inside the client’s folder you will find a set of folders and files. Before running the program, put the
jogl libraries (*.dll files – see previous section) into the ‘libs’ folder, and the ‘jogl.jar’ file into the
‘jars’ folder.
4. Now the application is ready to be launched. Just double-click on the ‘Run.bat’ icon and the running
script will execute the program for you. If this does not work (for example, if you are using a Unix
system instead of Windows), try the following command.
> java -classpath "…Clientclasses, …Clientjarsjogl.jar" -Djava.library.path=…Clientlibs -
Xms512m -Xmx1024m first.MainClass
The expected result is the following ‘Application launcher’ window to show up. Otherwise an error
message should be displayed in the command prompt.
Figure 1.1: Application launcher window
If you want to recompile the program, you can open the attached JBuilder project with JBuilder and
click on the ‘make’ button. Otherwise, follow the compiling procedures that correspond to your
favourite environment.
Please note that the ‘classes’ folder contains not only the compiled classes but also other files
needed to launch the application, as for example some image files. You must be careful not to
remove them when deleting classes or performing cleaning procedures, since they will not be
regenerated by any ‘make’ procedure.
back to top
5. 2 User’s manual
Because of its size, the application user’s manual has been written on a separate http page. You can
access this page by clicking here.
back to top
3 Application architecture
3.1 General architecture
As introduced before in this document, the application architecture follows the client-server
paradigm. This is shown in Figure 3.1, which presents the application architecture at a high level of
abstraction.
Figure 3.1: Application architecture at a high level of abstraction
In this figure you can also note that both client and server present a three-tier architecture internally.
At the lowest level we find all the functionalities that regard data management, included
synchronized structures that allow the correct execution of concurrent read and write operations.
At the logic level we find different functionalities; some of them are related to the control of the
connections and sessions between the client and the server. Some others are related to the
management of the models that are drawn on the GL canvas, their position and display
optimizations.
The presentation layer is not highly decoupled from the other layers of the architecture. This is
because many data structures used within lower layers have specific methods to display themselves
onto the GL canvas. Then a few of the presentation functionalities can be considered to be spread
within the lower layers.
6. However, the functionalities related to the management of windows and input graphical controls,
and the definition of the interface through which the user can interact with the program still remain
in the presentation layer.
3.2 Terrain viewer architecture
The terrain viewer architecture at a lower level of abstraction is shown in Figure 3.2.
Figure 3.2: Te rrain vie wer architecture
Most of the logic of the program is defined within the Control module. This module uses different
structures as the height model and geomipmap module to manage and display the correspondent
models onto the GL canvas. In addition to this, the control module uses many other utility modules
as the DTM module, textures and geometries, and controls the display optimizations. Finally, it also
allows the creation of a connection thread when needed (for example, when downloading themes
from the theme server).
The vertical bar at one side of the connection thread module (in Figure 3.2) indicates that it is a
‘boundary module’. It uses a socket connection to communicate with the theme server and gather
the results of the query.
The synchronization of the data manager within the data layer is very important. For example, a
connection thread may start writing a new theme while the control module is reading the data
buffer.
7. 3.3 Server architecture
The server architecture is under many aspects very similar to the terrain viewer architecture, as it
can be seen in Figure 3.3.
However in this case the logic of the program is distributed in a different way. The control module
does not handle any kind of display optimization apart from the retained mode, which is always
active. There exist other two modules entirely dedicated to the service of the clients; the daemon
and the handler.
Figure 3.3: Server architecture
If active, the daemon is continuously listening to a server socket for incoming connections. When a
client connects to the server, the daemon creates a handler thread that will serve the client and
handle all aspects related to its session. Handlers also register within the control module when a
session starts. In this way it is possible to know the number of active sessions at a given time and to
interrupt the handlers when needed (for example, think of a hard shutdown).
The synchronization of the data layer is even more important in the server than in the terrain viewer.
In fact, many handler threads may be accessing the data manager at the same time. Moreover, they
may access it while the server administrator is modifying the data in the data manager.
back to top
4 Most interesting features
8. 4.1 Display optimizations
Without any doubt, some of the most interesting features of the application are the optimizations
that it implements. Using these optimizations can increase the animation speed dramatically, and
reduce the load of the system on which the application is running.
As you start the terrain viewer program, the first window to show up is the application launcher. On
this window, click on the ‘Display settings’ tab, and you will be able to pre-configure the
optimizations.
If you want to test these optimizations, deselect all of them before the application starts, as shown in
Figure 4.1.
Figure 4.1: Disabling display optimizations
Also, to force the system to work to its limit during the testing, set the display to run as fast as
possible as shown in the figure. Otherwise you can also set a very big frame rate.
Now, click on the ‘Ok’ button to start the application with no optimizations enabled.
9. Figure 4.2: Pe rformance testing with no dis play optimizations enabled
In our machine, the obtained results were a low frame rate that did not exceed 15 fps, and the
general system response to slow down, showing that it was heavily loaded. Moreover, the graphic
card fan was continuously working.
Turning on the face culling did not make the situation better, since we have obtained the same
results as before. We have also tried reversing the model so no polygons were rendered (they were
all culled by the face-culling procedure), but it did not make any change.
Then we decided to turn on the retained mode, and the animation speed increased enormously (up
to 100 fps, the limit of the machine where this tests were performed). Even the graphic card fan
stopped working. In fact, the retained mode is the optimization that gave better r esults during our
tests. However, within the project context it is the less interesting optimization since it was
developed by direct usage of already implemented functionalities in the used graphic libraries.
Much more interesting to us is the geomipmapping optimization. It consists of the tessellation of the
terrain into many different cells. In principle these cells can have any shape, but for simplicity we
have chosen to use a square-cell tessellation. Each of these cells can be displayed separately using a
different LOD (Level Of Detail). A lower level of detail uses less polygons to approximate a model,
and applies lower definition textures to it.
A particular instance of a cell at a given LOD is called ‘patch’.
10. The advantage of this approach is that, when an object is far away from the camera point of view, a
much low level of detail may be sufficient to obtain acceptable results when rendering it. Then,
within the structure that we have defined, we can choose the most appropriate level of detail to be
used for each cell, and obtain animation speed increments when some of then are far away from the
camera point of view.
In Figure 4.3 you can see a geomipmapped model displayed in wireframe mode. We added blue
lines approximating the boundaries between different LODs.
Figure 4.3: Wireframe visualization of a geomipmapped model
The increments on the animation speed given by the use of a geomipmapped model may vary a lot,
depending on the position of the model. For far distances, the results we obtained were similar to
those of the retained mode. However, the animation speed decreased significantly as the model
approached positions nearer to the camera point of view.
The lowest animation speed observed with the geomipmapping optimization enabled was about 20
fps. This happened when the model was so close to the camera that most of the cells where
displayed using the best LOD.
The major disadvantage of the naïve geomipmapping techniques is that they may introduce cracks
in the model surface. Cracks are boundary edges of some patches that do not coincide with the
correspondent edges in the adjacent patches. They can be better understood by looking at the
following figure.
11. Figure 4.4: Cracks in a geomipmapped model
Cracks lay in the conjunction points between patches with different LODs. To solve this problem,
we designed a different geomipmapping algorithm that impleme nts anticracking techniques to patch
up the cracks in the geomipmapped model.
The results were really satisfying; the cracks on the geomipmapped model were patched by the
implemented algorithm and it didn’t seem to represent a significant overhead when displaying the
model.
To test the algorithm, we suggest you to increase the height factor (generally this enlarges the
cracks in the geomipmapped model) and to change the model’s position until some cracks become
notable. Then turn on and off anticracking to check if these cracks are actually covered.
The last implemented optimization is the frustum culling. It requires a tessellation of the height
model to work, and we reused the geomipmapped model for this. This is the reason why, in our
application, the frustum culling only works when the geomipmapping optimization is active.
To speed up the culling process, the algorithm uses a quadtree as a support structure. The quadtree
is a special type of tree that finds many applications, especially when dealing with geographical
data or geometries over a plane. Within a quadtree, elements are ordered according to their x, y
location in the space.
More precisely, each node in the quadtree corresponds to a rectangular zone of the terrain. This
rectangular zone can be further divided into 4 zones (NW, NE, SW, and SE) to which the children
of the node correspond.
Using this structure, the frustum culling algorithm can be enhanced very much, since, generally,
when culling great areas of the terrain it does not need to descend deep in the quadtree to find their
correspondent nodes. Suppose, as an example, that the algorithm determines that the entire model is
inside the frustum. Then it sets the root of the quadtree to be displayed completely and it does not
repeat the same check for each of the subzones.
The algorithm descends to the lower level of the tree only when the zone that has been checked is
partially inside the frustum (and the correspondent node in the quadtree is not a leaf).
12. Testing the frustum culling algorithm we have implemented gave very good results. In fact, in those
situations in which the geomipmapping becomes less performing, i.e. when the model approaches
some position very near to the camera, greater zones of the terrain tend to be outside of the frust um.
The minimum frame rate obtained by enabling the frustum culling optimization (with the retained
mode disabled) was about 50 fps.
Finally, to test the correctness of the frustum culling algorithm, you can use the quadtree viewer
integrated within the application. The quadtree viewer is shown by clicking on the ‘Show
QuadTree’ button in the ‘Optimizations’ panel.
Doing so, the following window will show up.
Figure 4.5: Quadtree viewer
The quadtree viewer shows the state of the quadtree nodes by representing them as rectangles. Note
that it only shows the nodes that where visited during the frustum culling process. If the culling
algorithm did not descend deep in the quadtree, just a few rectangles will be shown. A green square
represents a node whose correspondent zone is displayed, while a red square represents a node
whose correspondent zone has been culled. For example, the snapshot of the quadtree state in
Figure 4.5 has been taken when the model was entirely inside the frustum.
For performance reasons, automatic refresh of the quadtree viewer is not enabled by default.
However, it can be convenient for you to turn it on when testing the correctness of the frustum
culling algorithm. Another recommendation that may be useful at this scope is to evidence the
model bounding box (to see which parts of the model are actually inside the frustum and which of
them are not).
13. Figure 4.6: Frustum culling evaluation
Please note that the automatic refresh of the quadtree viewer has a bad impact over the animation
speed, so we strongly recommend to turn it off when testing the anima tion speed increments of each
optimization.
4.2 Polyline visualization over terrain models
Another amazing feature of the application is it capability of modifying the downloaded polylines
so as they follow the terrain. In particular, what the server provides are 2D themes (only x-y
coordinates are specified for the theme points). This means that these polylines cannot be displayed
properly over the 3D terrain model.
Note: Sometimes height models are called 2,5D or 2D+, because they are not considered comp lete
3D structures, however, for simplicity, let us say here that we are displaying a 3D model.
Clearly, the naïve solution of modifying the height of the points in the polyline directly does not
work. Consider, for example, the following case.
14. Figure 4.7: A polyline over a height model
The polyline has only two points, and if we modify their height so as they touch the ground, the
mayor part of the polyline will still not touch the ground. There exist even worse cases in which
part of the polyline will become covered by a bump of the terrain model.
Then the solution is to apply a fragmentation algorithm to divide the original lines into proper
segments whose height values can be modified so every point of the resulting polyline will touch
the ground.
Now the question is: Where to divide the polyline? The answer is that the division of the polyline
must be driven by the triangle mesh of the height model.
Considering an orthogonal projection, a height model mesh has the following aspect if seen from
above.
Figure 4.8: A triangle mesh
Now, consider the following line over the mesh. We want to fragment it so as to make it follow the
terrain.
15. Figure 4.9: A line over the mesh
The problem is surely solved if we divide the line at each correspondence with a mesh edge (note
that this correspondence is not an intersection, since the height of the polyline points is not yet
defined). The following Figure illustrates this.
Figure 4.10: Mesh-driven line fragme ntation
Someone may say that this is an overkill, since in some cases the surface slope may not vary from
one triangle of the mesh to an adjacent one. However, we claim that in most height models the slope
variation is high, and an algorithm that divides a line at every correspondence with a mesh edge is
much simpler and easier to understand than one that divides lines only when necessary.
The final step of the algorithm has to set the height of each point to matc h that of the model’s
surface. In the next section we explain deeper in detail how this was implemented. Figure 4.11
shows an example of the results obtained with the implemented algorithm.
16. Figure 4.11: Modified polylines follow the terrain
Remark: Remember that results like the one shown by the figure can only be obtained turning off
geomipmapping optimizations.
Now we will present some guidelines that may help you testing the polyline modification algorithm
in Computer GraGIS.
Usually, the first thing that one would like to check is if the fragmentation algorithm is working
properly. To do this, it may be better to turn on the wireframe mode and to evidence theme points.
For more information about how to turn on these features, please read the user’s manual.
Now, if the bumps or variations of the surface disturb your analysis, it can be useful to reduce the
height factor to the minimum (0,1), and to put the model in vertical position (this can be done by
clicking on the ‘Reset angles’ button on the ‘Position’ panel – see the user’s manual). You should
see something like this:
17. Figure 4.12: Fragmentation algorithm evaluation
In this way it is easy to see if at every correspondence between the polylines and the mesh edges the
algorithm has added one point (i.e. fragmented the polyline).
Now, to check if the height of each point has been determined correctly, put the model in an oblique
position to obtain a better perspective. Then, set the height factor to the maximum. This will not
only exaggerate the bumps on the model, but it will also enlarge any difference between the surface
height and the height of the points in the polylines.
Figure 4.13: Point height evaluation
By performing this tests, we have always found that the height of the points in the resulting polyline
coincides with that of the surface of the model at the same (x, y) location.
18. However, we remark here that the algorithm we have implemented aims at raising the points to
exactly the same height that the one of the model’s surface at the same (x, y) location, not to a
greater height. Then, even an insignificant numerical computation error, due to the fact that we are
working with a limited precision representation of numbers –or a little imprecision on the depth
test-, could cause part of the line to be covered by the ground. Although this happens very rarely,
you can overcome the problem by setting a height greater than zero for the lines on the theme
selection table (even a very little height will work). Augmenting the line width can also help in
evidencing the lines.
The following figure shows a snapshot of the theme selection table.
Figure 4.14: The me selection table
back to top
5 Implementation notes
5.1 Design patterns
When implementing the application, we have applied different design patterns. The use of design
patterns has been always considered a good design practice, since it allows reusing solutions that
have been experienced by others and proved to perform well in the contexts of application.
One very diffused design pattern is the one we implemented with the daemon and server t hreads to
handle the clients connections.
Another example is the design pattern ‘Singleton’ that turned out to be particularly useful for the
instantiation of the data manager, in both client and server, since within each program a unique
instance of the data manager is required.
5.2 Geomipmapping and frustum culling
The geomipmapping algorithm we implemented for the project divides the height model into square
cells. Points in these cells are organized in rows and columns, uniformly distributed in the pla ne (if
we consider only their x and y coordinates). The mesh is then obtained by considering some edges
that these points determine, in the same way it is done for the original height model.
19. Patches with best LOD are obtained from the original model just by tessellating it. Other patches
are obtained from patches with better LOD by removing some of their columns and rows. More
precisely, a patch of LOD n has twice as columns (or, equivalently, rows) than a patch of LOD n +
1 (the higher the LOD number, the worse the level of detail).
The height model can only be properly tessellated if the number of columns in it is a multiple of the
number of columns of a patch with best LOD (this holds also for the rows). Then it follows that in
some cases it is necessary to add (or alternatively, remove) columns or rows to the height model
before creating the geomipmapped model. This is exactly what we have done.
Textures at different LODs are computed in a very similar way. However, in this case the algorithm
does not simply remove values from one texture to obtain one texture at a worse LOD, but
recomputes the RGB values of each pixel determining the average of the four pixels it is replacing
(also for a texture the width and height are the half of those of a texture of better LOD). This is
more complex, but gives much better results than simply dropping the values of the original
textures.
As introduced before, for the frustum culling, nodes in the quadtree correspond to portions of the
geomipmapped terrain model.
More precisely, each node of the quadtree stores a bounding box. Then the zone that corresponds to
a node is the set of cells inside the node’s bounding box.
Because of this, a perfect quadTree could only be obtained if the number of ‘column cells’ and the
number of ‘row cells’ in the tessellation of the height model are both powers of two.
However, we considered that it wasn’t a good idea to modify the geomipmapped model adding cells
to obtain a better quadtree. In fact, some worst cases show that the resulting structure could be
almost four times bigger than the original one. Consider, for example, a geomipmapped model of
33x33 cells. The minimum power of two greater than 33 is 64. Then if we added the necessary cells
to obtain a better quadtree we would obtain a geomipmapped model of 64x64 cells.
It is also true that we can remove cells instead of adding them. We didn’t want to do that because it
would potentially drop parts of the terrain that the user of the program considers important.
Our approach was to separate the zones at the best possible points. This means that if the model has
33 ‘column cells’ and 33 ‘row cells’, the zone that corresponds to the first child of the quadtree
node root should be 16x16 cells, the second child’s zone 16x17, the third child’s zone 17x16 and
the fourth child’s zone 17x17.
There is no real disadvantage in performing the separation into zones this way, apart from the fact
that the algorithm that constructs the quadtree can become a little more complex.
5.3 Polyline fragmentation algorithm
In section 4.2 we have presented the algorithm that modifies the polylines so as to make them
follow the terrain. We have also explained the reasons why the algorithm fragments each polyline
before modifying the height of the points.
20. Now we want to show how the fragmentation algorithm was implemented. Since the algorithm is
long, we are not going to include its source code here (however, it is part of the distributed package,
see section 1.1), but we will show how it works graphically.
The algorithm was entirely written by the student Bruno Nardelli, as part of the final project for the
course of Computer Graphics Principles at the University of Trento.
To simplify the algorithm, we have written it to perform three different fragmentation steps. At the
first step, lines are fragmented at the correspondences with horizontal edges of the terrain mesh, as
shown by Figure 5.1.
Figure 5.1: Fragmentation driven by the horizontal edges of the mesh
Knowing that the mesh edges under consideration are horizontal, the division point coordinates can
be determined easily, using an algorithm that finds a point in a line given its y coordinate.
After fragmenting the polyline according to the horizontal edges of the triangle mesh, the algorithm
continues with a fragmentation driven by the vertical edges of the mesh. This can be seen in Figure
5.2.
Figure 5.2: Fragmentation driven by the vertical edges of the mesh
Also in this case the division point coordinates can be easily determined using an algorithm that
finds a point in a line given its x coordinate.
Finally, the algorithm applies a fragmentation driven by the oblique edges of the mesh. To do this, it
simply projects the points of the polyline onto the y axis and performs a vertical-edge-driven
fragmentation (see Figure 5.3). Then it reprojects the new points back to the line.
21. Figure 5.3: Fragmentation driven by the oblique edges of the mesh
Note that projecting the points onto the y axis as shown in Figure 5.3 is extremely easy, since the
oblique edges in the mesh form a 135° angle with respect to the x axis. This means that the y’
coordinate of a projected point is equal to x + y, where x and y are the original coordinates of the
point.
That’s it. Now the points in the polyline are ready to be raised so as they touch the model’s surface.
5.4 Polyline points raising algorithm
To explain how the points raising algorithm works, we will consider the simple case of only one
point to be raised.
Again, we will consider an orthogonal projection and look at the model from above, as shown in
Figure 5.4.
Figure 5.4: The point to be raised
Different situations can happen. One of them is the one presented in Figure 5.4. In other cases, the
point coordinates correspond to the x- y coordinates of some point of the mesh edges. Here we are
22. going to explain how the algorithm behaves in a situation like the shown in the Figure 5.4. The
other cases are much simpler.
The first thing that the algorithm does is to choose the three points in the mesh that it will use to
determine the height of the polyline point. Observe that these points describe a triangle.
Figure 5.5: Mesh triangle considered by the algorithm
Then it computes the intersection points between the triangle edges and the vertical plane that the
polyline point characterizes (see Figure 5.6). Note that the new points describe a line. Let’s call it
auxiliary line.
Figure 5.6: Two other auxiliary points determined by the algorithm
Finally, the algorithm determines the raised point as the intersection between the auxiliary line and
the horizontal plane characterized by the polyline point.
Figure 5.7: Final step
23. back to top
6 Conclusions and future work
During the lessons, we have learnt different concepts, useful for writing 3D applications.
Particularly important were the lessons about the display optimizations. These optimizations
provided a great enhancement in the animation speed of our program, that only displays one terrain
model. Just think of the importance of them when writing, for example, a complex 3D videogame,
with the scenery, lots of ‘enemies’ and animations, requiring a huge number of polygons to be
rendered and a good response to the user’s commands.
Many of the guidelines and examples in class helped motivating the participation and creativity of
the student, which succeeded writing his own fragmentation and point raising algorithm as part of
the final project.
As a future work proposal, it could be interesting to modify the fragmentation algorithm to add new
points only where absolutely necessary (read the discussions about it in section 4.2). In average
cases, this may represent an insignificant enhancement. However, in some cases were the terrain
presents just a few slope variations, the number of points in the polylines can be reduced
considerably.
Another interesting feature that we would like to implement as a project extension is the concept of
generic maps. This should provide means of treating two types of maps, regardless their location or
format:
Raster maps (this category also includes elevation maps, like the one in an ascii dtm file).
Vector maps.
Finally, adding proper support for dealing with different reference systems could allow the
application to work with different data sources at the same time (for example, displaying a raster
map loaded from a file and retrieving vector maps from a PostGIS database).
back to top