Using Mapserver

The Minnesota Mapserver is an internet web-mapping server which conforms to the OpenGIS Web Mapping Server specification.

Basic Usage

To use PostGIS with Mapserver, you will need to know about how to configure Mapserver, which is beyond the scope of this documentation. This section will cover specific PostGIS issues and configuration details.

To use PostGIS with Mapserver, you will need:

Mapserver accesses PostGIS/PostgreSQL data like any other PostgreSQL client -- using libpq. This means that Mapserver can be installed on any machine with network access to the PostGIS server, as long as the system has the libpq PostgreSQL client libraries.

  1. Compile and install Mapserver, with whatever options you desire, including the "--with-postgis" configuration option.

  2. In your Mapserver map file, add a PostGIS layer. For example:

      LAYER
        CONNECTIONTYPE postgis
        NAME "widehighways"
        # Connect to a remote spatial database
        CONNECTION "user=dbuser dbname=gisdatabase host=bigserver"
        # Get the lines from the 'geom' column of the 'roads' table
        DATA "geom from roads"
        STATUS ON
        TYPE LINE
        # Of the lines in the extents, only render the wide highways
        FILTER "type = 'highway' and numlanes >= 4"
        CLASS
          # Make the superhighways brighter and 2 pixels wide
          EXPRESSION ([numlanes] >= 6)
          COLOR 255 22 22      
          SYMBOL "solid"
          SIZE 2
        END
        CLASS
          # All the rest are darker and only 1 pixel wide
          EXPRESSION ([numlanes] < 6)
          COLOR 205 92 82      
        END
      END

    In the example above, the PostGIS-specific directives are as follows:

    CONNECTIONTYPE

    For PostGIS layers, this is always "postgis".

    CONNECTION

    The database connection is governed by the a 'connection string' which is a standard set of keys and values like this (with the default values in <>):

    user=<username> password=<password> dbname=<username> hostname=<server> port=<5432>

    An empty connection string is still valid, and any of the key/value pairs can be omitted. At a minimum you will generally supply the database name and username to connect with.

    DATA

    The form of this parameter is "<column> from <tablename>" where the column is the spatial column to be rendered to the map.

    FILTER

    The filter must be a valid SQL string corresponding to the logic normally following the "WHERE" keyword in a SQL query. So, for example, to render only roads with 6 or more lanes, use a filter of "num_lanes >= 6".

  3. In your spatial database, ensure you have spatial (GiST) indexes built for any the layers you will be drawing.

      CREATE INDEX [indexname]
        ON [tablename] 
        USING GIST ( [geometrycolumn] GIST_GEOMETRY_OPS );
  4. If you will be querying your layers using Mapserver you will also need an "oid index".

    Mapserver requires unique identifiers for each spatial record when doing queries, and the PostGIS module of Mapserver uses the PostgreSQL oid value to provide these unique identifiers. A side-effect of this is that in order to do fast random access of records during queries, an index on the oid is needed.

    To build an "oid index", use the following SQL:

      CREATE INDEX [indexname] ON [tablename] ( oid );

Advanced Usage

The USING pseudo-SQL clause is used to add some information to help mapserver understand the results of more complex queries. More specifically, when either a view or a subselect is used as the source table (the thing to the right of "FROM" in a DATA definition) it is more difficult for mapserver to automatically determine a unique identifier for each row and also the SRID for the table. The USING clause can provide mapserver with these two pieces of information as follows:

  DATA "the_geom FROM (SELECT table1.the_geom AS the_geom, table1.oid AS oid, table2.data AS data
  	FROM table1 LEFT JOIN table2 ON table1.id = table2.id) AS new_table USING UNIQUE oid USING SRID=-1"

USING UNIQUE <uniqueid>

Mapserver requires a unique id for each row in order to identify the row when doing map queries. Normally, it would use the oid as the unique identifier, but views and subselects don't automatically have an oid column. If you want to use Mapserver's query functionality, you need to add a unique column to your view or subselect, and declare it with USING UNIQUE. For example, you could explicitly select one of the table's oid values for this purpose, or any other column which is guaranteed to be unique for the result set.

The USING statement can also be useful even for simple DATA statements, if you are doing map queries. It was previously recommended to add an index on the oid column of tables used in query-able layers, in order to speed up the performance of map queries. However, with the USING clause, it is possible to tell mapserver to use your table's primary key as the identifier for map queries, and then it is no longer necessary to have an additional index.

Note: "Querying a Map" is the action of clicking on a map to ask for information about the map features in that location. Don't confuse "map queries" with the SQL query in a DATA definition.

USING SRID=<srid>

PostGIS needs to know which spatial referencing system is being used by the geometries in order to return the correct data back to mapserver. Normally it is possible to find this information in the "geometry_columns" table in the PostGIS database, however, this is not possible for tables which are created on the fly such as subselects and views. So the USING SRID= option allows the correct SRID to be specified in the DATA definition.

Examples

Lets start with a simple example and work our way up. Consider the following Mapserver layer definition:

  LAYER
  	CONNECTIONTYPE postgis
  	NAME "roads"
  	CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
  	DATA "the_geom FROM roads"
  	STATUS ON
  	TYPE LINE
  	CLASS
  		COLOR 0 0 0
  	END
  END

This layer will display all the road geometries in the roads table as black lines.

Now lets say we want to show only the highways until we get zoomed in to at least a 1:100000 scale - the next two layers will acheive this effect:

  LAYER
  	CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
  	DATA "the_geom FROM roads"
  	MINSCALE 100000
  	STATUS ON
  	TYPE LINE
  	FILTER "road_type = 'highway'"
  	CLASS
  		COLOR 0 0 0
  	END
  END
  
  LAYER
  	CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
  	DATA "the_geom FROM roads"
  	MAXSCALE 100000
  	STATUS ON
  	TYPE LINE
  	CLASSITEM road_type
  	CLASS
  		EXPRESSION "highway"
  		SIZE 2
  		COLOR 255 0 0
  	END
  	CLASS
  		COLOR 0 0 0
  	END
  END

The first layer is used when the scale is greater than 1:100000, and displays only the roads of type "highway" as black lines. The FILTER option causes only roads of type "highway" to be displayed.

The second layer is used when the scale is less than 1:100000, and will display highways as double-thick red lines, and other roads as regular black lines.

So, we have done a couple of interesting things using only mapserver functionality, but our DATA SQL statement has remained simple. Suppose that the name of the road is stored in another table (for whatever reason) and we need to do a join to get it and label our roads.

  LAYER
  	CONNECTION "user=theuser password=thepass dbname=thedb host=theserver"
  	DATA "the_geom FROM (SELECT roads.oid AS oid, roads.the_geom AS the_geom, road_names.name as name
  			FROM roads LEFT JOIN road_names ON roads.road_name_id = road_names.road_name_id) AS named_roads
  			USING UNIQUE oid USING SRID=-1"
  	MAXSCALE 20000
  	STATUS ON
  	TYPE ANNOTATION
  	LABELITEM name
  	CLASS
  		LABEL
  			ANGLE auto
  			SIZE 8
  			COLOR 0 192 0
  			TYPE truetype
  			FONT arial
  		END
  	END
  END

This annotation layer adds green labels to all the roads when the scale gets down to 1:20000 or less. It also demonstrates how to use an SQL join in a DATA definition.