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 Original file line Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<artifactId>gs-wms</artifactId> <artifactId>gs-wms</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-kml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.jcodec</groupId> <groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId> <artifactId>jcodec</artifactId>
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,17 +4,31 @@
*/ */
package org.geoserver.wps.gs.download; 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.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.CaseInsensitiveMap;
import org.geoserver.ows.util.KvpUtils; import org.geoserver.ows.util.KvpUtils;
import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.Operation; import org.geoserver.platform.Operation;
import org.geoserver.platform.Service; import org.geoserver.platform.Service;
import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetMap; import org.geoserver.wms.GetMap;
import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS; import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.WMSMapContent;
import org.geoserver.wms.map.AbstractMapOutputFormat;
import org.geoserver.wms.map.GetMapKvpRequestReader; import org.geoserver.wms.map.GetMapKvpRequestReader;
import org.geoserver.wms.map.PNGMapResponse;
import org.geoserver.wms.map.RenderedImageMap; import org.geoserver.wms.map.RenderedImageMap;
import org.geoserver.wms.map.RenderedImageMapOutputFormat; import org.geoserver.wms.map.RenderedImageMapOutputFormat;
import org.geoserver.wms.map.RenderedImageMapResponse; import org.geoserver.wms.map.RenderedImageMapResponse;
Expand All @@ -23,26 +37,34 @@
import org.geoserver.wps.process.ByteArrayRawData; import org.geoserver.wps.process.ByteArrayRawData;
import org.geoserver.wps.process.RawData; import org.geoserver.wps.process.RawData;
import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.MapContent;
import org.geotools.process.factory.DescribeParameter; import org.geotools.process.factory.DescribeParameter;
import org.geotools.process.factory.DescribeProcess; import org.geotools.process.factory.DescribeProcess;
import org.geotools.process.factory.DescribeResult; import org.geotools.process.factory.DescribeResult;
import org.geotools.referencing.CRS; 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.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.ProgressListener; import org.opengis.util.ProgressListener;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;


import javax.imageio.ImageIO;
import javax.media.jai.PlanarImage; import javax.media.jai.PlanarImage;
import java.awt.*; import java.awt.*;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage; import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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, " + @DescribeProcess(title = "Map Download Process", description = "Builds a large map given a set of layer definitions, " +
"area of interest, size and eventual target time.") "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 WMS wms;
private final GetMapKvpRequestReader getMapReader; private final GetMapKvpRequestReader getMapReader;
private Service service; private Service service;
private ApplicationContext applicationContext;


public DownloadMapProcess(GeoServer geoServer) { public DownloadMapProcess(GeoServer geoServer) {
// TODO: make these configurable // TODO: make these configurable
Expand Down Expand Up @@ -91,23 +114,86 @@ public RawData execute(
layers, layers,
@DescribeParameter(name = "format", min = 1, description = "The output format", minValue = 1) Format format, @DescribeParameter(name = "format", min = 1, description = "The output format", minValue = 1) Format format,
final ProgressListener progressListener) throws Exception { 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(); GetMapRequest request = new GetMapRequest();
request.setRawKvp(Collections.emptyMap());
request.setFormat(format.getName()); 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); List<RenderedImageMapResponse> encoders = GeoServerExtensions.extensions(RenderedImageMapResponse.class);
Operation op = new Operation("GetMap", service, null, new Object[]{request});
for (RenderedImageMapResponse encoder : encoders) { for (RenderedImageMapResponse encoder : encoders) {
if (encoder.canHandle(op)) { if (encoder.canHandle(operation)) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();
encoder.formatImageOutputStream(result, bos, new WMSMapContent(request)); encoder.formatImageOutputStream(result, bos, mapContent);
return new ByteArrayRawData(bos.toByteArray(), format.getName()); 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, 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 @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.wms.setApplicationContext(applicationContext); this.wms.setApplicationContext(applicationContext);
this.applicationContext = applicationContext;
List<Service> services = GeoServerExtensions.extensions(Service.class, applicationContext); List<Service> services = GeoServerExtensions.extensions(Service.class, applicationContext);
this.service = services.stream().filter(s -> "WMS".equalsIgnoreCase(s.getId())).findFirst().orElse(null); this.service = services.stream().filter(s -> "WMS".equalsIgnoreCase(s.getId())).findFirst().orElse(null);
if (service == null) { if (service == null) {
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@


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


import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
Expand All @@ -27,11 +30,21 @@
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.assertEquals;
import static org.junit.Assert.assertNull;


public class DownloadMapProcessTest extends BaseDownloadImageProcessTest { 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 @Test
public void testExecuteSingleLayer() throws Exception { public void testExecuteSingleLayer() throws Exception {
String xml = IOUtils.toString(getClass().getResourceAsStream("mapSimple.xml")); 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 // not a typo, the output should indeed be the same as testExecuteMultiName
ImageAssert.assertEquals(new File(SAMPLES + "mapMultiName.png"), image, 100); 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 @Test
public void testTimeFilter() throws Exception { public void testTimeFilter() throws Exception {
String xml = IOUtils.toString(getClass().getResourceAsStream("mapTimeFilter.xml")); String xml = IOUtils.toString(getClass().getResourceAsStream("mapTimeFilter.xml"));
Expand Down

0 comments on commit 0bae736

Please sign in to comment.