Delivering Interactive Topographic Web-maps using Open-Source Database Technology

Paper for the SVG Open conference, Vancouver, 2003

Keywords: SVG Mapping, Interactive Topographic Webmaps, Open Source, PostgeSQL, PostGIS, Spatial Databases

Andreas Neumann, Ph.D. candidate
Ph.D. student, researcher
Institute of Cartography, ETH Zurich


Andreas Neumann got a masters degree in Geography/Cartography from Vienna University, in 2001. In 1999 he joined the Cartographic Institute of the Swiss Federal Institute of Technology, first as a system administrator and later as a teaching and research assistant. At the same institute Andreas advised several theses and student projects in the webmapping domain. Currently he works on a PhD as part of a ETH project called "Distributed Publishing of Cartographic Information on Demand". Besides university he has a job as a GIS and database consultant for Dr. Heinrich Jaeckli AG, Zurich, a consulting company for geology, hydrology, hydrogeology and environmental engineering. Andreas was one of the initiators of the SVG.Open 2002 conference in Zurich, the first international SVG developers conference, co-organized by the W3C consortium.


Delivering larger geographic datasets requires that a map server is able to extract subsets of existing data. This paper describes a client-server solution using PostGIS, an open-source extension to the free SQL database PostgreSQL. PostGIS is an OpenGIS compliant spatial database and uses the OpenGIS Simple Features Specification for SQL (e.g. Multipolygons, multilinestrings, etc.). Apart from spatial queries, PostGIS, in collaboration with the "proj4 libraries", can project geographic data on the fly, can calculate distances and do simple analysis. Serverside PHP scripts take care of the communication with the spatial database, database queries, calculations, simple analysis and formatting of the resulting data, prior to being sent to the map client. The presented spatial database holds various scale levels of the vector datasets provided by the Swiss Federal Office of Topography. Depending on the scale and extent requested by the map client, the server can deliver different datasets and symbolize the data appropriate to the map's scale.

Table of Contents

1. Introduction and Motivation
2. Requirements for a Database Driven SVG Application
     2.1 Database related requirements
     2.2 Webserver related requirements
     2.3 Client issues: Browser and Plugin
     2.4 Application User Interface
3. Technical Architecture, Workflow
     3.1 PostgreSQL and PostGIS
     3.2 Workflow
         3.2.1 Loading Vector GIS geometry into the PostgreSQL database
         3.2.2 Loading raster geometry into the PostgreSQL database
         3.2.3 Index generation
     3.3 Extraction from the Database; PHP scripts
4. Userinterface issues
     4.1 Map Section
     4.2 Navigation Elements
     4.3 Information Section
5. Future Possible Enhancements

1. Introduction and Motivation

Publishing larger geographic datasets on the web often required expensive GIS or commercial database software in the past. While many commercial systems had been able to handle large databases for some time, file-formats delivered to the client were either standard-based and non-interactive (e.g. raster-based tiles) or highly proprietary, but would allow for interactivity. Most commercial systems that allow client-side interactivity, base their clients on proprietary platform-dependent plug-ins or Java-Applets, which often includes the use of proprietary and undocumented data formats. Additionally, many commercial products lack (carto)graphical quality and have severe limitations when it comes to interactivity and flexibility. With the introduction and continuous development of the SVG specification and SVG conformant tools and viewer, cartographers are for the first time able to publish high-quality cartographic data, based on an open and vendor-neutral standard.

The presentation shows a PostGIS based interactive SVG mapping system that allows to navigate in larger geographic datasets, typically migrated from ESRI GIS data to PostGIS. The map client can communicate with the map server in order to load additional map data when the map user is zooming, panning or toggeling map layers. The Adobe extensions ".getURL()", ".postURL()" and ".parseXML()" take care of the communication. The requested data can later be added to the existing map's document object model without having to reload the web application. This map client is work in progress and not finished at all.

A new version of the topographic map client [SVGTOPOMaps] , as presented at the SVG Open 2002 conference in Zurich, will be presented as well. Additions and improvements of the map-client include extensions to the interactive profiling tool, improved map navigation and animations of hiking routes selected by the map user. Work on a NMEA compatible GPS client is currently ongoing, though not finished. This client will allow to visualize GPS waypoints and routes, loaded from Garmin GPS devices.

Finally, a intranet application developed for Dr. Heinrich Jaeckli AG, a geology company in Zurich, Switzerland, is presented. The company operates as a consulter in the domains of geology, hydrogeology, cleanup operations and environmental engineering. The intranet application allows to visualize the company's GIS data and report locations. It is extensible and can be integrated with other company databases in the future.

2. Requirements for a Database Driven SVG Application

There are a number of requirements that should be taken into account when implementing database-driven webmapping applications. We split the requirements in different sections:

2.1 Database related requirements

2.2 Webserver related requirements

2.3 Client issues: Browser and Plugin

2.4 Application User Interface

The solution we found is almost entirely built around open source or free software. We chose the PostgreSQL [PostgreSQL] database and the PostGIS [PostGIS] spatial extensions. The server environment was tested on Linux (production system) and MacOSX (development system), with the Apache webserver and PHP modules. As Client we use the Adobe SVG viewer plugin version 3.0. The Macromedia Flash Player would have been a viable alternative with a slightly higher performance, but lacks standard compliance with W3C and integration with XML and server software. Furthermore the graphical capabilities and display quality of the Adobe SVG viewer is already ahead of the Macromedia Flash plugin. Java applets are another possibility, but would require more programming skills than currently available. The client runs in all Webbrowser which support the Adobe SVG plugin, whith Safari as the current development browser. Unfortunately, the Adobe viewer is currently not available as a final version for the Linux operating system, which would be the development system of choice, if all clientside tools had been available.

3. Technical Architecture, Workflow

3.1 PostgreSQL and PostGIS

PostgreSQL is a open source, highly reliable and high-performance SQL92 compliant database system. It is currently the most advanced Open Source DBMS system available, that supports to manage huge amounts of data. PostgreSQL supports replication, clustering and load balancing, in case one needs high availability and higher performance for more concurrent users. PostgreSQL supports JDBC, ODBC and offers APIs to all modern programming languages. It is extensible using the builtin PL/pgSQL procedural language and many other programming and scripting languages. PostgreSQL is available for 34 platforms, incl. virtual any Unix system, Linux, MacOSX and soon also Windows. PostgreSQL is secure, relatively easy to administrate and there are a number of client GUI tools for different platforms available. For an overview of PostgreSQL's features and advantages see [PostgreSQLAdvocacy] , [PostgreSQLDocs] and [PostgreSQLTechDocs] .

PostGIS is an open source extension to PostgreSQL, that supports spatial geometry, spatial queries and simple analysis. It can be used as a backend spatial database for geographic information systems (eg. GRASS) or Webmapping Server (e.g. Minnesota Mapserver). The geometry data format follows the OpenGIS "Simple Features Specification for SQL" [SimpleFeaturesSQL] , which supports "POINT", "MULTIPOINT", "LINESTRING", "MULTILINESTRING", "POLYGON", "MULTIPOLYGON", "GEOMETRYCOLLECTION". PostGIS allows for simple and complex spatial queries and spatial indices. Many of the functions are OpenGIS compliant. PostGIS comes with a ESRI shapefile loader and accepts OpenGIS simple features geometry.


Figure 1: Workflow and technical Architecture of the Webmapping application

3.2 Workflow

3.2.1 Loading Vector GIS geometry into the PostgreSQL database

First we export our GIS-data containing both geometry and attributes to ESRI Shapefiles. PostGIS offers a tool called "shp2pgsql" which converts the shapefile to a SQL file containing a new table definition, attributes and text-based geometry according to the Open GIS Simple Features Specification for SQL. Since we want to save computing time on the server we add another text-based column containing our SVG path-data. A self-written perl script parses the SQL column containing the Open-GIS geometry and converts it to SVG path-geometry. Since we do not regularly update the original geometry we don0t need to care about regular conversion. Otherwise one could write a SQL trigger that would track the changes and convert the data when necessary. Currently we also do not clip the requested geometry but send all geometry elements that overlap with our current viewBox within the map-client. This is sort of a bandwidth overhead but saves computing time on the server. Since our application is primarily an inhouse intranet application this can be justified.

The following Perl code can extract Open-GIS Simple Features Geometry for SQL geometry and convert it to SVG path-strings, whith using absolute Moveto and relative lineTo commands. Currently the script only handles MULTIPOLYGONS and MULTILINESTRINGS.

#!/usr/bin/perl -w

use strict;
use DBI;
use Pg;
use Math::Round qw(:all);

my ($version, $program);

$program = $0;
$program =~ s/(\.\/)//;

my $data_source = "switzerland";
my $username = "mueller";
my $password = "larifari";
my $hostname = "";
my $dbh;
my ($sth, $sti, $stb); #for database-execs
my $errstr;
my $err;
my $gid;
my $geometry;
my $layername = $ARGV[0];
my @myPolys;
my @myCoords;
my @coords;
my @interiorGeometry;
my $x_before;
my $y_before;
my $polyCount;
my ($x,$y,$j,$mypoly);
my $pcount = 0;
my $interiorPoly;
my $roundVal = 0.01;
my ($f_geometry_column, $type);

print "rounding coordinates to: $roundVal\n\n";

die "Usage: '$program <layername>'\n" unless $ARGV[0];

#database connection
$dbh = DBI->connect("DBI:Pg:dbname=$data_source;host=$hostname", "$username", "$password") or die "Can't connect to $data_source: $DBI::errstr\n";

#test which geometry it is
$stb = $dbh->prepare("SELECT f_geometry_column, type FROM geometry_columns WHERE f_table_name = '$layername'");
$stb->execute() or die "Unable to execute your query: $dbh->$err, $dbh->$errstr\n";
$stb->bind_columns(undef, \$f_geometry_column, \$type);

my @row = $stb->fetchrow;

die "Cannot find geometry table '$layername'!" unless $type;
print "Type: $type; Geometry-Column: $f_geometry_column\n";
die "Currently only Multipolygons and Multilinestrings allowed!" unless $type eq "MULTIPOLYGON" || $type eq "MULTILINESTRING";

#select statement
$sth = $dbh->prepare("SELECT gid, AsText(the_geom) AS geometry FROM $layername");
$sth->execute() or die "Unable to execute your query: $dbh->$err, $dbh->$errstr\n";
#bind result to a variable
$sth->bind_columns(undef, \$gid, \$geometry);

while (my @row = $sth->fetchrow) {
	#substitute unneccessary info, begin and end of open-gis linestring
	if ($type eq "MULTIPOLYGON") {
		$geometry =~ s/(MULTIPOLYGON\(\(\()//g;
		$geometry =~ s/(\)\)\))//g;
	elsif ($type eq "MULTILINESTRING") {
		$geometry =~ s/(MULTILINESTRING\(\()//g;		
		$geometry =~ s/(\)\))//g;
	undef @myPolys;
	#splits into an array that can hold multiple polygons, just disjunct, no holes (exterior rings)
	@myPolys = split(/\)\)\,\(\(/,$geometry);
	my $mySvgString = "";
	$polyCount = 0;
	foreach $mypoly (@myPolys) {
		#split into an array that can hold geometry with holes (interior rings)
		@interiorGeometry = split(/\)\,\(/,$mypoly);
		foreach $interiorPoly (@interiorGeometry) {
			undef @myCoords;
			@myCoords = split(/,/,$interiorPoly);
			undef @coords;
			@coords = split(/\s+/,$myCoords[0]);
			$x_before = $coords[0];
			$y_before = $coords[1];
			if ($polyCount == 0) {
				$mySvgString = $mySvgString."M".nearest($roundVal,$x_before)." ".nearest($roundVal,$y_before * -1);
			else {
				$mySvgString = $mySvgString."z M".nearest($roundVal,$x_before)." ".nearest($roundVal,$y_before * -1);
			for ($j=1;$j<scalar(@myCoords);$j++) {
				@coords = split(/\s+/,$myCoords[$j]);
				$x = $coords[0] - $x_before;
				$y = $coords[1] - $y_before;
				$x_before = $coords[0];
				$y_before = $coords[1];
				$mySvgString = $mySvgString."l".nearest($roundVal,$x)." ".nearest($roundVal,$y * -1);
	if ($type eq "MULTIPOLYGON") {
		$mySvgString = $mySvgString."z";
	my $sqlstring = "UPDATE $layername SET svg_geom = '$mySvgString' WHERE gid = $gid";
	$sti = $dbh->prepare($sqlstring);
	$sti->execute() or die "Unable to insert svg-string into table $layername, gid $gid\n";


print "Converted $pcount $type objects - Done.\n";

Following is a SQL statement that extracts the original Open GIS geometry of a Multipolygon and a converted SVG path-geometry for comparison.

select the_geom, svg_geom from gebauede_200 where gid = '1';

OpenGIS Simple Feature Geometry:
SRID=-1;MULTIPOLYGON(((630915.4 319836.4,630971.4 319874.7,631004.4 319826.4,630948.4 319788.1,630915.4 319836.4)))	

SVG path-string:
M630915.4 -319836.4l56 -38.3l33 48.3l-56 38.3l-33 -48.3z	  

3.2.2 Loading raster geometry into the PostgreSQL database

Raster data is also stored within the database as PNG tiles as a binary large object type (BLOB). Raster data can represent scanned maps, aerial images or any other raster-data type than can be represented as raster data. A perl script using the netpbm tools, available on the Unix platform, is slicing the original images. The following code shows how to tile color-separated tiff files of Swiss Federal Office 1 to 25.000 topographic map sheets into PNG slices. We can use resampling to provide images with different resolutions for different zoom factors. For binary data we can use transparent backgrounds and display the raster tiles on top of other SVG data. Unfortunately PNG raster tiles resulting from open-source software doesn't seem to be compatible with PNG files generated and used by Adobe and Microsoft tools, incl the Adobe SVG viewer. This means that we have to convert the images with transparent background, using a Adobe Photoshop action.

Following is the code of a perl-script slicing tiff-images into smaller PNG tiles. The script slices and resamples the original tiff-images to PNG tiles and creates a text-file containing SQL code to load the resulting images into the PostgreSQL database:


$xstart = 637000;
$xend = 655000;
$ystart = 242000;
$yend = 230000;
$tilepix = 400; #pixel
$tilecoor = 500; #Meter
$xinc = $xstart;
$yinc = $ystart;
$row = 1;
$directory = "25k_500x500_sit_1109";
$tablename = "pk25_500_situation";
$infile = "situation_1109.tif";
$sqlfile = "$directory.sql";
$path = "/home/kacheln";

($filename,$extension) = split(/\./, $infile, 2);

die "only files with .tif extension are allowed" unless $extension eq "tif";

#transfer file to pnm
$mycommand = "tifftopnm $infile > $filename.pbm";
$status = system "$mycommand";
die "could not convert image to pnm ...\n" unless $status == 0;

#open sql file
open(SQLFILE, ">$sqlfile") || die "Can't open $sqlfile";

$tileincY = 0;
while ($yinc > $yend) {
	$tileincX = 0;
	while ($xinc < $xend) {
		$outputfile = $directory."/".$filename."_".$xinc."_".$yinc.".pbm";
		$pngfileTemp = $directory."/".$filename."_".$xinc."_".$yinc."_temp.png";
		$pngfile = $directory."/".$filename."_".$xinc."_".$yinc.".png";
		#cut out tile
		$mycommand = "pnmcut $tileincX $tileincY $tilepix $tilepix $filename.pbm > $outputfile";
		$status = system "$mycommand";
		die "could not slice tile $outputfile ...\n" unless $status == 0;
		#resample file
		$outputfileRes = $directory."/".$filename."_".$xinc."_".$yinc."_res.pbm";
		$mycommand = "pnmscale -xysize 200 200 $outputfile > $outputfileRes";
		$status = system "$mycommand";
		die "could not resample tile $outputfile ...\n" unless $status == 0;
		#delete original file after cutting
		$mycommand = "rm $outputfile";
		$status = system "$mycommand";
		die "could not delete tile $outputfile ...\n" unless $status == 0;
		#convert to png
		$mycommand = "pnmtopng $outputfileRes > $pngfileTemp";
		$status = system "$mycommand";
		die "could not convert tile $outputfileRes to $pngfileTemp ...\n" unless $status == 0;
		#delete higher-resoluted image
		$mycommand = "rm $outputfileRes";
		$status = system "$mycommand";
		die "could not remove file $outputfileRes ...\n" unless $status == 0;
		$mycommand = "mv $pngfileTemp $pngfile";
		$status = system "$mycommand";		
		die "could not mv tile $pngfileTemp to $pngfile ...\n" unless $status == 0;
		#write SQL insert statement
		$tile_id = $xinc."_".$yinc;
		print SQLFILE "INSERT INTO $tablename (tile_id, the_geom, raster) VALUES ('$tile_id',GeometryFromText('POLYGON(($xinc $yinc,$xinc ".($yinc - $tilecoor).",".($xinc + $tilecoor)." ".($yinc - $tilecoor).",".($xinc + $tilecoor)." $yinc,$xinc $yinc))',-1),lo_import('$path/$pngfile'));\n";
		#finally done
		print "converted tile $xinc $yinc ...\n";
		$xinc = $xinc + $tilecoor;
		$tileincX = $tileincX + $tilepix;
	print "finished row $row ...\n\n";
	$tileincX = 0;
	$tileincY = $tileincY + $tilepix;
	$xinc = $xstart;
	$yinc = $yinc - $tilecoor;
	$row = $row + 1;

#remove original file
$mycommand = "rm $filename.pbm";
$status = system "$mycommand";
die "could not remove temporary file $filename.pbm ...\n" unless $status == 0;	 	 

The following SQL code inserts a PNG image to the PostgreSQL database, using binary large objects. The SQL code also inserts a unique image id (containing the coordinates of the upper left corner of the image tile) and Open GIS geometry (POLYGON geometry) that later allows to do spatial queries on the tiles.

INSERT INTO pk25_250_situation (tile_id, the_geom, raster) VALUES ('673000_290000',
GeometryFromText('POLYGON((673000 290000,673000 289750,673250 289750,673250 290000,673000 290000))',-1),

3.2.3 Index generation

To efficiently query larger database tables we should use Indizes on the columns we regularly do database queries. Queries using comparison operators like <, <=, =, >= and >= should generally use the B-tree index type, queries using operators like <<, &<, &>, >>, @, ~= and && should generally use an R-tree index type, while the Open-GIS spatial queries profit most from a GIST index type. PostgreSQL indizes are dynamically created and updated and do not need manual maintenance/recreation of the indizes as it is the case with other database-systems. PostgreSQL also supports Multicolumn Indizes (for queries involving "AND"), functional and partial (subset) indizes. In our case we index columns that are used frequently in columns, like the unique identifier, the geometry and object names.

-- Example for creating btree index
CREATE UNIQUE INDEX in_gid_btree ON communities USING btree (gid); 
-- Example for GIST index on geometry
CREATE INDEX in_the_geom_btree ON communities USING GIST (the_geom GIST_GEOMETRY_OPS);

3.3 Extraction from the Database; PHP scripts

It is impossible to load larger amounts of spatial into a SVG viewer at once, both from a bandwidth, but also clientside computing power perspective. Building DOM's from huge geographic databases is usually computationally expensive and memory consumptive. This means that we need to extract the current map-extent from a larger geographic database and send map-data appropriate for the current scale and size.

Our mapping application loads data on demand when the user zooms, pans or toggles map-layers. This feature is implemented using the .getURL() method. .getURL() loads additional data from a PHP-script with the current map-extent, zoom value and layername as a parameter. The PHP script selects the current layer using the "&&" operator (select all geometry that intersects with my map extent) and builds a group layer holding both geometry and attribute data, with the attributes residing in a foreign namespace.

Following is a ECMA script code snippet that calls the PHP script using .getURL in order to load additional data.

function getData() {
	//get map-extent
	xmin = parseFloat(svgRect.getAttribute("x"));
	ymax = parseFloat(svgRect.getAttribute("y")) * -1;
	xmax = xmin + parseFloat(svgRect.getAttribute("width"));
	ymin = ymax - parseFloat(svgRect.getAttribute("height"));
	//loop over all layers that need to be loaded
	for (i=0;i<dynamicGeometry.length;i++) {
		//checks layer visibility
		if (svgdoc.getElementById("checkCross"+dynamicGeometry[i]).getAttribute("visibility") == "visible") {
			var myUrlString = "sendGeom.php?layername="+dynamicGeometry[i]+"&xmin="+xmin+"&ymin="+ymin+"&

The next PHP code block demonstrates how to send additional data from the database, using PHP and PostGIS:

elseif ($layername == "schutzzonen_zh") {
		$myGeometry = $layername;	
		//open a new layer-group with a new attribute-data namespace
		print "<g id=\"$myId\" xmlns:jag=\"\">\n";

		//database connection
		$my_pg_connect = pg_Connect("host=$hostname dbname=$dbGeomName user=$username password=$password")
		  or die ("Can't connect to database '$dbGeomName'");
		//query distinct to get all classes (art) to display
		$my_result_set = pg_Exec($my_pg_connect, "SELECT DISTINCT \"art\" FROM $myGeometry ORDER BY \"art\" ASC") or die (pg_ErrorMessage());
		$nrResult = pg_NumRows($my_result_set);
		//loop over all classes and display them with different styles
		for ($j = 0; $j < $nrResult; $j++) {
			$resultArray = pg_Fetch_Array($my_result_set, $j);
			$myLayer = str_replace("_","",$layername);
			print "<g id=\"".$layername.$resultArray[art]."\" class=\"".$myLayer.$resultArray[art]."\">\n";

			$my_pg_connect1 = pg_Connect("host=$hostname dbname=$dbGeomName user=$username password=$password")
			  or die ("Can't connect to database '$dbGeomName'");
			$my_result_set1 = pg_Exec($my_pg_connect1, "SELECT gid, svg_geom FROM $myGeometry WHERE
			  the_geom && GeometryFromText('BOX3D($xmin $ymin, $xmax $ymax)'::box3d,-1) AND art = '$resultArray[art]'") or die (pg_ErrorMessage());

			$numRecs = pg_NumRows($my_result_set1);
			$i = 0;
			while ($i < $numRecs) {
				$resultArray1 = pg_Fetch_Array($my_result_set1, $i);
				$myId = $layername."_".$resultArray1[gid];
				$mySvgString = $resultArray1[svg_geom];
				print "\t<path id=\"$myId\" d=\"$mySvgString\" />\n";	
			$nrRecs = $nrRecs + $numRecs;
			print "</g>\n";
		print "\t<jag:layerData id=\"$layername.Data\" jag:nrRecs=\"$nrRecs\" jag:layerName=\"$layername\" />\n";

Finally we need a ECMA script callback function that adds the requested data to the DOM tree, to empty groups already prepared in the initial SVG file:

function addGeom(data) {
	if(data.success) {
		//parse SVG group received from PHP script
		var node = parseXML(data.content, svgdoc);
		//get layername again
		curDynLayer = node.firstChild.getAttribute("id").replace(/_tempGeometry/,"");
		myGeometryToAdd = svgdoc.getElementById(curDynLayer);
		//check if we have to remove already existing data
		if (geomLoaded[curDynLayer] == "true") {
			var tempGeometry = svgdoc.getElementById(curDynLayer+"_tempGeometry");
		geomLoaded[curDynLayer] = "true";
	else {
		alert("something went wrong with dynamic loading of geometry!");

Please note that the current approach is somehow using a "brute force" mechanism, meaning that we do not track whether data is already existing and can be re-used. We always replace the whole content of a dynamic data layer, even if the user always pans a little bit. This issue might be addressed in future versions.

4. Userinterface issues

The following Figure shows the mapping application with the map-section, the navigation elements and the information section.


Figure 2: Screenshot of the webmapping application "Jaeckli Report Information System"

4.1 Map Section

To be continued ...

4.2 Navigation Elements

To be continued ...

4.3 Information Section

To be continued ...

5. Future Possible Enhancements

To be continued ...


The author is grateful for the following groups of individuals for contributing, ideas, hints and code:

Institute of Cartography, ETH Zurich (

My current employer that gives me time to work on SVG and webmaps. Many colleagues at the institute have contributed ideas, tested prototypes or read my papers and corrected my english.

Dr. Heinrich Jaeckli AG (

My second employer, who gave me the opportunity to work on SVG to create diagrams and inhouse Online-GIS applications.

SVG Developers Yahoo-group (

People from the SVG Developers Yahoo-group gave numerous hints and code examples or participated in discussions on webmapping and SVG. Esp. I'd like to thank Kevin Lindsey and Michel Hirtzler for their excellent SVG sites and examples.

The Open Source Community

The Project builds on many open-source projects out there: Linux, Apache, PHP, PostgreSQL, Postgis, are only a few examples of the open-source software that had been used during the project. Open Source software had proven to be reliable in many projects that involved alternative Operating Systems and Middleware. Support is usually great and there is lot's of information on the web regarding the use and configuration of open source software.


Interactive Topographic Web-Maps Using SVG (, Isakowski Yvonne and Andreas Neumann, in: Proceedings of the SVG.Open 2003 Conference, Zurich, July 2002.
PostgreSQL (, The most advanced Open Source database system in the world, 2003
The Advantages of PostgreSQL (, 2003
PostgreSQL - Official Documentation (, 2003
PostgreSQL - Technical Documentation (, 2003
PostGIS (, Geographic Objects for PostgreSQL, 2003
OpenGIS Simple Features Specification For SQL (, Open GIS Consortium, Inc., 1999.

XHTML rendition created by gcapaper Web Publisher v2.0, © 2001-3 Schema Software Inc.