Archive

GeoServer

Today’s post is mostly notes-to-self about using Docker. These steps were tested on a fresh Ubuntu 17.04 install.

Install Docker as described in https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/ “Install using the repository” section.

Then add the current user to the docker user group (otherwise, all docker commands have to be prefixed with sudo)

$ sudo gpasswd -a $USER docker
$ newgrp docker

Test run the hello world image

$ docker run hello-world

For some more Docker basics, see https://github.com/docker/labs/blob/master/beginner/chapters/alpine.md.

Pull Geodocker images, for example from https://quay.io/organization/geodocker

$ docker pull quay.io/geodocker/base
$ docker pull quay.io/geodocker/geoserver

Get a list of pulled images

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
quay.io/geodocker/geoserver latest c60753e05956 8 months ago 904MB
quay.io/geodocker/base latest 293209905a47 8 months ago 646MB

Test run quay.io/geodocker/base

$ docker run -it --rm quay.io/geodocker/base:latest java -version
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

Run quay.io/geodocker/geoserver

$ docker run --name geoserver -e AUTHOR="Anita" \
 -d -P quay.io/geodocker/geoserver

The important options are:

-d … Run container in background and print container ID

-P … Publish all exposed ports to random ports

Check if the image is running

$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
684598b57868 quay.io/geodocker/geoserver "/opt/tomcat/bin/c..." 
2 hours ago Up 2 hours 0.0.0.0:32772->9090/tcp geoserver

You can also check which ports to access using

$ docker port geoserver
9090/tcp -> 0.0.0.0:32772

Geoserver should now run on http://localhost:32772/geoserver/ (user=admin, password=geoserver)

For more tests, let’s connect to Geoserver from QGIS

All default example layers are listed

and can be loaded into QGIS

Advertisement

“-T”, this small appendix can be found after many popular GIS-related acronym. But of course, it always means something different. Take for example GIS-T (GIS for Transportation), WFS-T (Transactional WFS) and WMS-T (WMS with time support). The world of acronyms is a fun place!

Let’s see what a WMS-T can do for us. From the WMS standard:

Some geographic information may be available at multiple times (for example, an hourly weather map). A WMS
may announce available times in its service metadata, and the GetMap operation includes a parameter for
requesting a particular time
. […] Depending on the context, time
values may appear as a single value, a list of values, or an interval, …

Currently, only Mapserver supports WMS-T but the Geoserver team is working on it.

Mapserver

MapServer 4.4 and above provides support to interpret the TIME parameter and transform the resulting values into appropriate requests.

Time attributes are specified within the metadata section:

METADATA
"wms_title" "Earthquakes"
"wms_timeextent" "2011-06-01/2011-07-01"
"wms_timeitem" "TIME"
"wms_timedefault" "2011-06-10 12:10:00"
END

Mapserver supports temporal queries for single values, multiple values, single range values or even multiple range values:

...&TIME=2011-06-10&...
...&TIME=2011-06-10, 2004-10-13, 2011-06-19&...
...&TIME=2011-06-10/2011-06-13&...
...&TIME=2011-06-10/2011-06-15, 2011-06-20/2011-06-25&...

Geoserver

GeoSolutions has developed support for TIME and ELEVATION dimensions in WMS.
There are plans to backport this feature to the stable 2.1.x series after the 2.1.1 release.

Configuration of time-enabled layers can be done via the normal user interface:

The following video by GeoSolutions demonstrates the use of Geoserver’s WMS-T:

Both server solutions seem to support only one time attribute per layer. An optional second time attribute would be nice to support datasets with start and end time like Time Manager for QGIS does.

This year, three QGIS projects made it into Google Summer of Code. Congratulations to all successful students!

These are the accepted OSGEO projects:

QGIS

GRASS

  • GRASS WXGUI WMS service rendering
  • Completion of wxGUI Nviz extension for 3D data visualization in GRASS GIS
  • r.in.modis for GRASS GIS
  • Graphical User Interface for the hydrological tools r.stream* in GRASS GIS

pgRouting

  • Multi-modal public transit routing for pgRouting
  • Time Dependent \ Dynamic Shortest Path Algorithm Implementation for pgRouting

gvSIG

  • Add support to vector data formats for gvSIG Mini
  • Design and implement an API for tiled vectorial support of geo-location data services for gvSIG Mini
    Integration of GGL2 into gvSIG

Opticks

  • Development of a ship detection and classification toolkit for SAR imagery in Opticks
  • Astronomical processing tools for Opticks
  • Photography processing tools for Opticks

Geoserver

  • Enhancing Geoserver Authentication

uDig

  • Catalog View of uDig
  • OSM data mining and editing capabilities in uDig and Geotools

MapServer

  • INSPIRE View Service for MapServer

Others

  • Geoprocessing with Neo4j Spatial
  • PyOSSIM: Python bindings for OSSIM libraries

For a full list including student names check http://www.google-melange.com/gsoc/org/google/gsoc2011/osgeo.

There are many nice examples out there of how to use a getFeatureInfo request in OpenLayers to display a feature’s attribute table. In some applications it can be useful though not to display the full attribute table but to only select one attribute value from it and output it somewhere, e.g. in a text field.

This post describes how to pick the road id from a road wms layer and write the id value into a text input field.

OpenLayers offers a convenient class to achieve this: OpenLayers.Control.WMSGetFeatureInfo.

Let’s create an object of this class, register and activate it:

roadidPicker = new OpenLayers.Control.WMSGetFeatureInfo({
                url: 'http://localhost:8080/geoserver/wms', 
                title: 'identify features on click',
                layers: [wms],
                queryVisible: true
            });
roadidPicker.infoFormat = 'application/vnd.ogc.gml';
roadidPicker.events.register("getfeatureinfo", this, pickRoadid);
map.addControl(roadidPicker);
roadidPicker.activate();

Now, every time the user clicks onto the map, a getFeatureInfo request is issued and the function pickRoadid() is called. In pickRoadid(), we’ll define which value we want to pick from the feature. The ‘id’ of the feature will be written into a text input field called ‘roadId’:

function pickRoadid(e) {
  if (e.features && e.features.length) {
     var val = e.features[0].attributes.id;
     document.getElementById('roadId').value = val;
  }
}

You might have noticed the ‘[0]’. That’s because the click event comes with a list of features within the reach of the mouse click. For my application, I can only use one value and the first feature in this list is as good as any.

GeoServer has always been good at simply publishing database tables. But anything more complex (e.g. pre-filtering data in a table, joining two tables together, or generating values on the fly) could be painful. With Geoserver 2.1 one can finally create a layer directly from an SQL query.

"Create New SQL View" interface

Even dynamic queries are possible, e.g.

select gid, state_name, the_geom from pgstates where persons between %low% and %high%

To select for example all states with 2 to 5 millions inhabitants, the following parameters can be added to the normal GetMap request:

&viewparams=low:2000000;high:5000000

Find more information on SQL layers in Geoserver 2.1 documentation.

For all of us who couldn’t attend FOSS4G in Barcelona this year there are some of the great workshops available online:

The OGC filter encoding standard – for whatever reason – lacks the useful IN operator we know and love from other languages like SQL [1]. Geoserver developers have therefore implemented this functionality as a non-standard SLD function [2]. Unfortunately this implementation requires prior knowledge of the number of arguments in the IN clause and it’s limited to 10 arguments.

An example filter would look like this:

<ogc:Filter>
   <ogc:PropertyIsEqualsTo>
       <ogc:Function name="in3">
          <ogc:PropertyName>first_name</ogc:PropertyName>
          <ogc:Literal>Paul</ogc:Literal>
          <ogc:Literal>Mary</ogc:Literal>
          <ogc:Literal>Luke</ogc:Literal>
       </ogc:Function>
       <ogc:Literal>true</ogc:Literal>
   </ogc:PropertyIsEqualsTo>
</ogc:Filter>

[1] https://underdark.wordpress.com/2010/06/14/the-missing-in-operator-ogc-filter-standards/
[2] http://docs.geoserver.org/stable/en/user/styling/sld-tipstricks/mixed-geometries.html#using-non-standard-sld-functions

It sounds incredible, but it’s true: OGC Filter Encoding in it’s current version 1.1 lacks an IN operator [1]. Combining multiple PropertyIsEqualTo using ORs performs really badly both on Arcgis Server [2] and Geoserver [3]. That calls for the implementation of this standard-exceeding IN operator. Maybe PropertyIsIn?

[1] http://www.opengeospatial.org/standards/filter
[2] http://viswaug.wordpress.com/2009/01/20/filter-encoding-standard-11-and-the-curious-case-of-the-missing-in-operator/
[3] https://underdark.wordpress.com/2010/06/10/dynamic-styling-and-filtering-of-a-geoserver-wms-using-openlayers-layer-wms-post/

There is a new layer class in OpenLayers API: OpenLayers.Layer.WMS.Post [1]. – Great work!

While the “normal” OpenLayers.Layer.WMS class requests maps via HTTP GET, this new class sends the request via HTTP POST. This way, we can now send big client-side created SLD files in our GetMap requests that used to exceed size limits of GET.

This code snippet shows the basic use of OpenLayers.Layer.WMS.Post with a client-side created SLD. You’ll need at least OpenLayers 2.9 to test this on your server. (A full example can be found at http://www.openlayers.org/dev/examples/wms-long-url.html)

var sld = 'define your SLD here';

wms = new OpenLayers.Layer.WMS.Post(
  "name",
  "http://localhost:8080/geoserver/wms",
  {
    'layers': 'myNs:layername',
    format: 'image/jpeg',
    sld_body: sld
  },
  {
    unsupportedBrowsers: []
  }
);

Setting unsupportedBrowsers to an empty list is important! This list by default contains [“mozilla”, “firefox”, “opera”]. These browsers support long GET requests so the developers argued that these browsers wouldn’t need to use POST. Well, turns out that they do ;)

I performed a small stress test using an SLD with approximately 1,000 rules being applied on a big city road network. While my browser would willingly send a GET request of that size, Apache doesn’t want to accept it. So, I tried the POST way and it turns out that it really works! (But it’s slow, very slow …)

Thanks to the developers for yet another great feature!

[1] http://trac.openlayers.org/ticket/2224

Usually, I use CQL filter statements to dynamically filter features in a Geoserver WMS. These CQL filters can be added easily to the URL and Geoserver responds accordingly. There’s just one problem: There is a size limit for URLs and (very!) long filter statements won’t fit in. (And I’m not talking about the limit imposed by IE, but the more serious one in Apache.) This makes it necessary to switch from HTTP GET to POST. An easy way to send POST requests is using curl:

curl --data-binary @getMap.xml -H "Content-Type: text/xml" -o map.png "http://localhost:8080/geoserver/wms/GetMap"

I found that it’s important to use –data-binary to avoid that the text in getMap.xml is interpreted in any way. The “@” tells curl that a filename follows. Furthermore it’s necessary to specify the Content-Type. This results in the PNG map being stored in the current directory. My getMap.xml looks like follows:

<?xml version="1.0" encoding="UTF-8"?>
<ogc:GetMap
xmlns:ogc="http://www.opengis.net/ows"
xmlns:gml="http://www.opengis.net/gml"
version="1.1.1" service="WMS">

<StyledLayerDescriptor version="1.0.0">
<NamedLayer>
 <Name>myNs:roads</Name>
 <NamedStyle>
  <Name>simple_roads</Name>
 </NamedStyle>
</NamedLayer>
</StyledLayerDescriptor>

<BoundingBox srsName="http://www.opengis.net/gml/srs/epsg.xml#31287">
<gml:coord>
<gml:X>621005.1594799113</gml:X>
<gml:Y>471313.0306986364</gml:Y>
</gml:coord>
<gml:coord>
<gml:X>635174.7245025429</gml:X>
<gml:Y>484404.226939031</gml:Y>
</gml:coord>
</BoundingBox>

<Output>
 <Format>image/png</Format>
 <Size>
 <Width>600</Width>
 <Height>320</Height>
 </Size>
</Output>

<Exceptions>application/vnd.ogc.se+xml</Exceptions>

</ogc:GetMap>

It’s also pretty straightforward to add a filter to the NamedLayer element. Basically it can be copied from any SLD you have at hand.

%d bloggers like this: