Skip to content

Commit

Permalink
Adding KMZ support to the map download process
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Dec 18, 2017
1 parent e133444 commit 0bae736
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 7 deletions.
5 changes: 5 additions & 0 deletions src/community/wps-download/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<artifactId>gs-wms</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-kml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,31 @@
*/
package org.geoserver.wps.gs.download;

import de.micromata.opengis.kml.v_2_2_0.Document;
import de.micromata.opengis.kml.v_2_2_0.Folder;
import de.micromata.opengis.kml.v_2_2_0.GroundOverlay;
import de.micromata.opengis.kml.v_2_2_0.Icon;
import de.micromata.opengis.kml.v_2_2_0.Kml;
import de.micromata.opengis.kml.v_2_2_0.LatLonBox;
import de.micromata.opengis.kml.v_2_2_0.ViewRefreshMode;
import org.geoserver.config.GeoServer;
import org.geoserver.kml.KMLEncoder;
import org.geoserver.kml.KMZMapOutputFormat;
import org.geoserver.kml.KmlEncodingContext;
import org.geoserver.kml.icons.IconRenderer;
import org.geoserver.ows.util.CaseInsensitiveMap;
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.Operation;
import org.geoserver.platform.Service;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetMap;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.map.AbstractMapOutputFormat;
import org.geoserver.wms.map.GetMapKvpRequestReader;
import org.geoserver.wms.map.PNGMapResponse;
import org.geoserver.wms.map.RenderedImageMap;
import org.geoserver.wms.map.RenderedImageMapOutputFormat;
import org.geoserver.wms.map.RenderedImageMapResponse;
Expand All @@ -23,26 +37,34 @@
import org.geoserver.wps.process.ByteArrayRawData;
import org.geoserver.wps.process.RawData;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.MapContent;
import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.styling.Style;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.ProgressListener;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.imageio.ImageIO;
import javax.media.jai.PlanarImage;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@DescribeProcess(title = "Map Download Process", description = "Builds a large map given a set of layer definitions, " +
"area of interest, size and eventual target time.")
Expand All @@ -52,6 +74,7 @@ public class DownloadMapProcess implements GeoServerProcess, ApplicationContextA
private final WMS wms;
private final GetMapKvpRequestReader getMapReader;
private Service service;
private ApplicationContext applicationContext;

public DownloadMapProcess(GeoServer geoServer) {
// TODO: make these configurable
Expand Down Expand Up @@ -91,23 +114,86 @@ public RawData execute(
layers,
@DescribeParameter(name = "format", min = 1, description = "The output format", minValue = 1) Format format,
final ProgressListener progressListener) throws Exception {
RenderedImage result = buildImage(bbox, decorationName, time, width, height, layers, format);
// if kmlOutput, reproject request to WGS84 (test is done indirectly to make the code work should KML not be available)
AbstractMapOutputFormat kmlOutputFormat = (AbstractMapOutputFormat) GeoServerExtensions.bean("KMZMapProducer");
boolean kmlOutput = format.getName() != null && kmlOutputFormat.getOutputFormatNames().contains(format.getName());
if(kmlOutput) {
bbox = bbox.transform(DefaultGeographicCRS.WGS84, true);
}

// assemble image
RenderedImage result = buildImage(bbox, decorationName, time, width, height, layers, format);

// encode the output by faking a normal request
// encode output (by faking a normal request)
GetMapRequest request = new GetMapRequest();
request.setRawKvp(Collections.emptyMap());
request.setFormat(format.getName());
WMSMapContent mapContent = new WMSMapContent(request);
mapContent.getViewport().setBounds(bbox);
Operation operation = new Operation("GetMap", service, null, new Object[]{request});
if (kmlOutput) {
return buildKMLResponse(bbox, format, result, mapContent, operation);
} else {
RawData response = buildImageResponse(format, result, mapContent, operation);
if (response != null) {
return response;
}
}

// we got here, no supported format found
throw new WPSException("Could not find a image map encoder for format: " + format);
}

public RawData buildImageResponse(@DescribeParameter(name = "format", min = 1, description = "The output format", minValue = 1) Format format, RenderedImage result, WMSMapContent mapContent, Operation operation) throws IOException {
List<RenderedImageMapResponse> encoders = GeoServerExtensions.extensions(RenderedImageMapResponse.class);
Operation op = new Operation("GetMap", service, null, new Object[]{request});
for (RenderedImageMapResponse encoder : encoders) {
if (encoder.canHandle(op)) {
if (encoder.canHandle(operation)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
encoder.formatImageOutputStream(result, bos, new WMSMapContent(request));
encoder.formatImageOutputStream(result, bos, mapContent);
return new ByteArrayRawData(bos.toByteArray(), format.getName());
}
}
return null;
}

public RawData buildKMLResponse(@DescribeParameter(name = "bbox", min = 1, description = "The map area and output projection") ReferencedEnvelope bbox, @DescribeParameter(name = "format", min = 1, description = "The output format", minValue = 1) Format format, RenderedImage result, WMSMapContent mapContent, Operation operation) throws IOException {
// custom KMZ building
Kml kml = new Kml();
Document document = kml.createAndSetDocument();
Folder folder = document.createAndAddFolder();
GroundOverlay go = folder.createAndAddGroundOverlay();
go.setName("Map");
Icon icon = go.createAndSetIcon();
icon.setHref("image.png");
icon.setViewRefreshMode(ViewRefreshMode.NEVER);
icon.setViewBoundScale(0.75);

LatLonBox gobox = go.createAndSetLatLonBox();
gobox.setEast(bbox.getMinX());
gobox.setWest(bbox.getMaxX());
gobox.setNorth(bbox.getMaxY());
gobox.setSouth(bbox.getMinY());

// create the outupt zip
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(bos)) {
ZipEntry entry = new ZipEntry("wms.kml");
zip.putNextEntry(entry);
KMLEncoder kmlEncoder = GeoServerExtensions.bean(KMLEncoder.class);
kmlEncoder.encode(kml, zip, new KmlEncodingContext(mapContent, wms, true));

// build and encode the image
final PNGMapResponse pngEncoder = new PNGMapResponse(wms);
entry = new ZipEntry("image.png");
zip.putNextEntry(entry);
pngEncoder.write(new RenderedImageMap(mapContent, result, "image/png"), zip, operation);
zip.closeEntry();

zip.finish();
zip.flush();
}

throw new WPSException("Could not find a map encoder for format: " + format);
return new ByteArrayRawData(bos.toByteArray(), KMZMapOutputFormat.MIME_TYPE);
}

RenderedImage buildImage(ReferencedEnvelope bbox, String decorationName, String time, int width, int height,
Expand Down Expand Up @@ -228,6 +314,7 @@ public RenderedImageMap renderInternalLayer(Layer layer, Map kvpTemplate) throws
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.wms.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
List<Service> services = GeoServerExtensions.extensions(Service.class, applicationContext);
this.service = services.stream().filter(s -> "WMS".equalsIgnoreCase(s.getId())).findFirst().orElse(null);
if (service == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.custommonkey.xmlunit.XMLUnit;
import org.geoserver.catalog.DimensionPresentation;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.data.test.MockData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.kml.KMZMapOutputFormat;
import org.geoserver.wps.WPSTestSupport;
import org.geotools.image.test.ImageAssert;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;
import org.w3c.dom.Document;

import javax.imageio.ImageIO;
import javax.xml.namespace.QName;
Expand All @@ -27,11 +30,21 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class DownloadMapProcessTest extends BaseDownloadImageProcessTest {

@Override
protected void registerNamespaces(Map<String, String> namespaces) {
super.registerNamespaces(namespaces);
namespaces.put("kml", "http://www.opengis.net/kml/2.2");
}

@Test
public void testExecuteSingleLayer() throws Exception {
String xml = IOUtils.toString(getClass().getResourceAsStream("mapSimple.xml"));
Expand Down Expand Up @@ -77,7 +90,52 @@ public void testExecuteMultiLayer() throws Exception {
// not a typo, the output should indeed be the same as testExecuteMultiName
ImageAssert.assertEquals(new File(SAMPLES + "mapMultiName.png"), image, 100);
}


@Test
public void testExecuteMultiLayerKmz() throws Exception {
testExecutMultiLayerKmz(KMZMapOutputFormat.MIME_TYPE);
}

@Test
public void testExecuteMultiLayerKmzShort() throws Exception {
testExecutMultiLayerKmz("kmz");
}


public void testExecutMultiLayerKmz(String mime) throws Exception {
String request = IOUtils.toString(getClass().getResourceAsStream("mapMultiLayer.xml"));
request = request.replaceAll("image/png", mime);
MockHttpServletResponse response = postAsServletResponse("wps", request);
assertEquals(KMZMapOutputFormat.MIME_TYPE, response.getContentType());

ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(response.getContentAsByteArray()));
try {
// first entry, the kml document itself
ZipEntry entry = zis.getNextEntry();
assertEquals("wms.kml", entry.getName());
byte[] data = IOUtils.toByteArray(zis);
Document dom = dom(new ByteArrayInputStream(data));
// print(dom);
assertXpathEvaluatesTo("1", "count(//kml:Folder/kml:GroundOverlay)", dom);
String href = XMLUnit.newXpathEngine().evaluate(
"//kml:Folder/kml:GroundOverlay/kml:Icon/kml:href", dom);
assertEquals("image.png", href);
zis.closeEntry();

// the ground overlay for the raster layer
entry = zis.getNextEntry();
assertEquals("image.png", entry.getName());
BufferedImage image = ImageIO.read(zis);
zis.closeEntry();
assertNull(zis.getNextEntry());

// check the output, same as mapMultiName
ImageAssert.assertEquals(new File(SAMPLES + "mapMultiName.png"), image, 100);
} finally {
zis.close();
}
}

@Test
public void testTimeFilter() throws Exception {
String xml = IOUtils.toString(getClass().getResourceAsStream("mapTimeFilter.xml"));
Expand Down

0 comments on commit 0bae736

Please sign in to comment.