Book Image

Expert GeoServer

By : Ben Mearns
Book Image

Expert GeoServer

By: Ben Mearns

Overview of this book

GeoServer is open source, server-side software written in Java that allows users to share and edit geospatial data. In this book, you'll start by learning how to develop a spatial analysis platform with web processing services. Then you'll see how to develop an algorithm by chaining together geospatial analysis processes, which you can share with anyone in the world. Next you'll delve into a very important technique to improve the speed of your map application—tile caching. Here, you'll understand how tile caching works, how to develop an effective tile cache-supported web service, and how to leverage tile caching in your OpenLayers web application. Further on, you'll explore important tweaks to produce a performant GeoServer-backed web mapping application. Moving on, you'll enable authentication on the frontend and backend to protect sensitive map data, and deliver sensitive data to your end user. Finally, you'll see how to put your web application into production in a secure and user-friendly way. You'll go beyond traditional web hosting to explore the full range of hosting options in the cloud, and maintain a reliable server instance.
Table of Contents (7 chapters)

Process chaining

In the previous section, you learned about creating WPS requests with WPS request builder. In this section, you'll learn how to make more complex WPS requests with process chaining. First, we'll take a look at a chain process request in plain XML. Next, we'll create an even more complex request. Finally, we'll add this request to a minimal OpenLayers web app to test the results of POST.

Here is our first process-chained XML, and you can see that the first WPS process is started here with this wps:Execute operation. The identifier is gs:CollectGeometries, so it's the gs:CollectGeometries process that we're running here, as shown in the following code:

<?xml version="1.0" encoding="UTF-8"?>
<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">
<ows:Identifier>gs:CollectGeometries</ows:Identifier>
<wps:DataInputs>
...
// Full code in the code bundle
</wps:ResponseForm>
</wps:Execute>

But the interesting thing is that when you look at wps:Input of CollectGeometries, you can see it's getting the features parameter from wps:Reference. So, this is not wps:Execute, yet it still is wps:Reference, but it's referencing a text/xml type that includes an wps:Execute operation in it, which is a little bit convoluted, but that's just how it's set up. And this is usually how you're going to refer to process-chained processes, with XML inside of other processes.

So, we have our second wps:Execute operation here, inside a wps:Body section, and this is using the BufferFeatureCollection process, which also has a features input parameter:

<wps:Body>
<wps:Execute version="1.0.0" service="WPS">
<ows:Identifier>gs:BufferFeatureCollection</ows:Identifier>
<wps:DataInputs>
<wps:Input>
<ows:Identifier>features</ows:Identifier>
<wps:Reference mimeType="text/xml" xlink:href="http://geoserver/wfs" method="POST">
<wps:Body>
<wfs:GetFeature service="WFS" version="1.0.0" outputFormat="GML2" xmlns:learning-geoserver="http://packtpub.com/learning-geoserver">
<wfs:Query srsName="EPSG:2272" typeName="learning-geoserver:pois"/>
</wfs:GetFeature>
</wps:Body>
</wps:Reference>
</wps:Input>

Here, for features, we're also using wps:Reference, but rather than having an additional WPS request or WPS section inside of wps:Reference, we're using wfs. This section in here is actually just a valid wfs defined by the WFS standard:

<wfs:GetFeature service="WFS" version="1.0.0" outputFormat="GML2" xmlns:learning-geoserver="http://packtpub.com/learning-geoserver">
<wfs:Query srsName="EPSG:2272" typeName="learning-geoserver:pois"/>
</wfs:GetFeature>

So anything that you would put, for example, in a wfs:Query section on WFS would be valid, and in this section of this XML. We're just using a regular wfs:Query, getting all of the points of interest in the learning-geoserver:pois workspace. Some things to be aware of here are, again, data types, so we're going to be outputting the following little portion as text XML-based wfs-collection:

<wps:RawDataOutput mimeType="text/xml; subtype=wfs-collection/1.0">
<ows:Identifier>result</ows:Identifier>
</wps:RawDataOutput>

That is just the way this buffer features collection process returns data, and, of course, gs:CollectGeometries will turn that collection into geometries. So, be aware of data formats. You'll be using a lot of different formats through a process-chained operation. A lot of the time, you'll be changing between different formats, so of course, the spatial reference will be important. In this case, we'd have trouble doing some of these operations if we're not using a linear spatial reference, and we end up having to change or reproject the spatial reference into a different system later on, which you'll see in the next section, with OpenLayers.

The namespaces are also very important. Because this is just a feature of XML, you have to explain what your tag is referring to so that all the different data types appear in their own way. If you are using XML namespaces correctly, it separates out portions of the XML in the correct way so that the services you're feeding it to understand how to read the data.

You can see many namespaces are defined up here; not all of them are necessary, but you can see that wfs is there, defining this little section and the syntax for this section, which comes from a WFS standard:

<wps:Reference mimeType="text/xml" xlink:href="http://geoserver/wfs" method="POST">
<wps:Body>
<wfs:GetFeature service="WFS" version="1.0.0" outputFormat="GML2" xmlns:learning-geoserver="http://packtpub.com/learning-geoserver">
<wfs:Query srsName="EPSG:2272" typeName="learning-geoserver:pois"/>
</wfs:GetFeature>
</wps:Body>
</wps:Reference>

Here is the result of this as GML:

<wps:RawDataOutput mimeType="text/xml; subtype=gml/3.1.1">
<ows:Identifier>result</ows:Identifier>
</wps:RawDataOutput>

So, that's our first XML example of a WPS request. Let's look at our second example, as follows:

<ows:Identifier>JTS:difference</ows:Identifier>
<wps:DataInputs>
<wps:Input>
<ows:Identifier>a</ows:Identifier>
<wps:Reference mimeType="text/xml; subtype=gml/3.1.1" xlink:href="http://geoserver/wps" method="POST">
<wps:Body>
<wps:Execute version="1.0.0" service="WPS">
<ows:Identifier>gs:CollectGeometries</ows:Identifier>
<wps:DataInputs>
<wps:Input>
<ows:Identifier>features</ows:Identifier>
<wps:Reference mimeType="text/xml" xlink:href="http://geoserver/wps" method="POST">

This is our second WPS request, and it will be even more complex than the last one, with process chaining. In the last process chain, we look at this gs:CollectGeometries section and everything below it. In this process-chained operation, we're using this difference process, which is based on a JTS namespace, and this takes two wps:Input parameters:

<ows:Identifier>JTS:difference</ows:Identifier>

Each of these inputs gives geometry in GML, and this process will take the difference of these geometries and give us that as a separate geometry feature collection or single geometry in GeoJSON format:

<wps:RawDataOutput mimeType="application/json">
<ows:Identifier>result</ows:Identifier>
</wps:RawDataOutput>

Now I'm moving over into OpenLayers. We can run a very simple WPS request as follows:

<!DOCTYPE html>
<html lang="en">
<head>
<title>WPS-Request Example</title><!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content=
"width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- OL stylesheet -->
<link rel="stylesheet" href="https://openlayers.org/en/v4.4.2/css/ol.css" type="text/css">
</head>
<body>
<h1>WPS-Request Example</h1>

<!-- OpenLayers JS dependency, debug build -->
<script type="text/javascript" src="https://openlayers.org/en/v4.4.2/build/ol-debug.js"></script>
<!-- Begin wps-request Javascript -->
<script type="text/javascript">

The preceding code is based on a stripped-down OpenLayers app, which sends an HTTP POST request with XML. We can start with this, doing incremental testing to build up a chained WPS request in the OpenLayers code. So, it's pretty stripped down; there's not a whole lot going on there. We just have our postData with all of the WPS text XML body, which is as follows:

postData = '<?xml version="1.0" encoding="UTF-8"?><wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd"> <ows:Identifier>gs:Aggregate</ows:Identifier><wps:DataInputs><wps:Input><ows:Identifier>features</ows:Identifier><wps:Reference mimeType="text/xml" xlink:href="http://geoserver/wfs" method="POST"><wps:Body><wfs:GetFeature service="WFS" version="1.0.0" outputFormat="GML2" xmlns:sf="http://www.openplans.org/spearfish"><wfs:Query typeName="topp:states"/></wfs:GetFeature></wps:Body></wps:Reference></wps:Input><wps:Input><ows:Identifier>aggregationAttribute</ows:Identifier><wps:Data><wps:LiteralData>PERSONS</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>function</ows:Identifier><wps:Data><wps:LiteralData>Count</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>function</ows:Identifier><wps:Data><wps:LiteralData>Average</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>function</ows:Identifier><wps:Data><wps:LiteralData>Sum</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>function</ows:Identifier><wps:Data><wps:LiteralData>Min</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>function</ows:Identifier><wps:Data><wps:LiteralData>Max</wps:LiteralData></wps:Data></wps:Input><wps:Input><ows:Identifier>singlePass</ows:Identifier><wps:Data><wps:LiteralData>false</wps:LiteralData></wps:Data></wps:Input></wps:DataInputs><wps:ResponseForm><wps:RawDataOutput mimeType="application/json"><ows:Identifier>result</ows:Identifier></wps:RawDataOutput></wps:ResponseForm></wps:Execute>';

Then, postData is sent via this XML HTTP request object posted to GeoServer. Then we'll get a response in as an alert, which can be achieved with the following code:

url = 'http://localhost:8080/geoserver/wps';
var req = new XMLHttpRequest();
req.open("POST", url, true);
req.setRequestHeader('Content-type', 'text/xml');
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if (req.status != 200 && req.status != 304) {
alert('HTTP error ' + req.status);
return;
}
alert(req.responseText);
if (req.readyState == 4) return;
};
req.send(postData);

So, we can test that out, and you can see the following result of that operation, which is in JSON; it's the data corresponding to the request:

Taking that one step further, we just add the XML from the more complex chained operation that we showed in the second example XML previously. Now, if we run this process-chained POST request, running this more complex process-chained POST request will sometimes require some time before it returns a result. Here you can see the result as GeoJSON:

Again, the spatial reference system is linear, so we're seeing much longer coordinates than we would normally see, but we're getting a successful result from our complex process-chained operation.

You've seen now how to create a complex process-chained operation. In the next section, we'll look at full integration of that WPS request into OpenLayers, as well as the result.