Java tutorial
/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.kml; import static junit.framework.Assert.assertNull; import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.File; import java.util.Collections; import java.util.TimeZone; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.namespace.QName; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.data.test.MockData; import org.geoserver.data.test.SystemTestData; import org.geoserver.test.RemoteOWSTestSupport; import org.geoserver.wms.WMSTestSupport; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.w3c.dom.Document; import com.mockrunner.mock.web.MockHttpServletResponse; public class KMLTest extends WMSTestSupport { private static final QName STORM_OBS = new QName(MockData.CITE_URI, "storm_obs", MockData.CITE_PREFIX); private static TimeZone oldTimeZone; @Override protected void setUpTestData(SystemTestData testData) throws Exception { super.setUpTestData(testData); testData.setUpDefaultRasterLayers(); } @BeforeClass public static void setTimeZone() { // System.setProperty("user.timezone", "UTC"); oldTimeZone = TimeZone.getDefault(); TimeZone.setDefault(TimeZone.getTimeZone("UTC")); } @AfterClass public static void clearTimeZone() { // System.clearProperty("user.timezone"); TimeZone.setDefault(oldTimeZone); } @Override protected void onSetUp(SystemTestData testData) throws Exception { super.onSetUp(testData); Catalog catalog = getCatalog(); testData.addStyle("notthere", "notthere.sld", getClass(), catalog); testData.addStyle("scaleRange", "scaleRange.sld", getClass(), catalog); testData.addVectorLayer(STORM_OBS, Collections.EMPTY_MAP, "storm_obs.properties", getClass(), catalog); } @Test public void testVector() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart() + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326"); assertEquals(getFeatureSource(MockData.BASIC_POLYGONS).getFeatures().size(), doc.getElementsByTagName("Placemark").getLength()); } @Test public void testVectorScaleRange() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart() + "&styles=scaleRange&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326"); assertEquals(getFeatureSource(MockData.BASIC_POLYGONS).getFeatures().size(), doc.getElementsByTagName("Placemark").getLength()); } @Test public void testVectorWithFeatureId() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart() + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326" + "&featureid=BasicPolygons.1107531493643"); assertEquals(1, doc.getElementsByTagName("Placemark").getLength()); } @Test public void testBasicVector() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.ROAD_SEGMENTS) + "&styles=&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&featureId=RoadSegments.1107532045088"); // print(doc); assertXpathEvaluatesTo("1", "count(//kml:Placemark)", doc); assertXpathEvaluatesTo("RoadSegments.1107532045088", "//kml:Placemark/@id", doc); assertXpathEvaluatesTo("RoadSegments.1107532045088", "//kml:Placemark/kml:name", doc); String expectedDescription = "<h4>RoadSegments</h4>\n" + "\n" + "<ul class=\"textattributes\">\n" + " \n" + " <li><strong><span class=\"atr-name\">FID</span>:</strong> <span class=\"atr-value\">102</span></li>\n" + " <li><strong><span class=\"atr-name\">NAME</span>:</strong> <span class=\"atr-value\">Route 5</span></li>\n" + "</ul>\n"; assertXpathEvaluatesTo(expectedDescription, "//kml:Placemark/kml:description", doc); // check look-at assertXpathEvaluatesTo("-0.0020000000000095497", "//kml:Placemark/kml:LookAt/kml:longitude", doc); assertXpathEvaluatesTo("5.000000003008154E-5", "//kml:Placemark/kml:LookAt/kml:latitude", doc); // check style assertXpathEvaluatesTo("00ffffff", "//kml:Placemark/kml:Style/kml:IconStyle/kml:color", doc); assertXpathEvaluatesTo("0.4", "//kml:Placemark/kml:Style/kml:IconStyle/kml:scale", doc); assertXpathEvaluatesTo("http://icons.opengeo.org/markers/icon-line.1.png", "//kml:Placemark/kml:Style/kml:IconStyle/kml:Icon/kml:href", doc); assertXpathEvaluatesTo("00ffffff", "//kml:Placemark/kml:Style/kml:LabelStyle/kml:color", doc); assertXpathEvaluatesTo("ff000000", "//kml:Placemark/kml:Style/kml:LineStyle/kml:color", doc); assertXpathEvaluatesTo("4.0", "//kml:Placemark/kml:Style/kml:LineStyle/kml:width", doc); // check geometry assertXpathEvaluatesTo("-0.002000087662804264,4.997808429893395E-5", "//kml:Placemark/kml:MultiGeometry/kml:Point/kml:coordinates", doc); assertXpathEvaluatesTo("-0.0042,-6.0E-4 -0.0032,-3.0E-4 -0.0026,-1.0E-4 -0.0014,2.0E-4 2.0E-4,7.0E-4", "//kml:Placemark/kml:MultiGeometry/kml:LineString/kml:coordinates", doc); } @Test public void testNoAttributes() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.ROAD_SEGMENTS) + "&styles=&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&featureId=RoadSegments.1107532045088&kmattr=false"); // print(doc); assertXpathEvaluatesTo("1", "count(//kml:Placemark)", doc); assertXpathEvaluatesTo("RoadSegments.1107532045088", "//kml:Placemark/@id", doc); // no name or description assertXpathEvaluatesTo("0", "count(//kml:Placemark/kml:name)", doc); assertXpathEvaluatesTo("0", "count(//kml:Placemark/kml:description)", doc); // check look-at assertXpathEvaluatesTo("-0.0020000000000095497", "//kml:Placemark/kml:LookAt/kml:longitude", doc); assertXpathEvaluatesTo("5.000000003008154E-5", "//kml:Placemark/kml:LookAt/kml:latitude", doc); // style does not need icon information assertXpathEvaluatesTo("0", "count(//kml:Placemark/kml:Style/kml:IconStyle)", doc); assertXpathEvaluatesTo("0", "count(//kml:Placemark/kml:Style/kml:LabelStyle)", doc); assertXpathEvaluatesTo("ff000000", "//kml:Placemark/kml:Style/kml:LineStyle/kml:color", doc); assertXpathEvaluatesTo("4.0", "//kml:Placemark/kml:Style/kml:LineStyle/kml:width", doc); // check geometry assertXpathEvaluatesTo("0", "count(//kml:Placemark/kml:MultiGeometry)", doc); assertXpathEvaluatesTo("-0.0042,-6.0E-4 -0.0032,-3.0E-4 -0.0026,-1.0E-4 -0.0014,2.0E-4 2.0E-4,7.0E-4", "//kml:Placemark/kml:LineString/kml:coordinates", doc); } @Ignore @Test public void testTimeTemplate() throws Exception { FeatureTypeInfo ftInfo = getCatalog().getResourceByName(getLayerId(MockData.OTHER), FeatureTypeInfo.class); File resourceDir = getDataDirectory().findResourceDir(ftInfo); File templateFile = new File(resourceDir, "time.ftl"); try { // create the time template FileUtils.writeStringToFile(templateFile, "${dates.value}"); Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.OTHER) + "&styles=&height=1024&width=1024&bbox= -96.0000,0.0000,-90.0000,84.0000&srs=EPSG:4326"); // print(doc); assertXpathEvaluatesTo("1", "count(//kml:Placemark)", doc); assertXpathEvaluatesTo("2002-02-12T00:00:00Z", "//kml:Placemark/kml:TimeStamp/kml:when", doc); } finally { assertTrue(templateFile.delete()); } } @Ignore @Test public void testTimeInvervalTemplate() throws Exception { FeatureTypeInfo ftInfo = getCatalog().getResourceByName(getLayerId(MockData.OTHER), FeatureTypeInfo.class); File resourceDir = getDataDirectory().findResourceDir(ftInfo); File templateFile = new File(resourceDir, "time.ftl"); try { // create the time template FileUtils.writeStringToFile(templateFile, "${dates.value}||${dates.value}"); Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.OTHER) + "&styles=&height=1024&width=1024&bbox= -96.0000,0.0000,-90.0000,84.0000&srs=EPSG:4326"); // print(doc); assertXpathEvaluatesTo("1", "count(//kml:Placemark)", doc); assertXpathEvaluatesTo("2002-02-12T00:00:00Z", "//kml:Placemark/kml:TimeSpan/kml:begin", doc); assertXpathEvaluatesTo("2002-02-12T00:00:00Z", "//kml:Placemark/kml:TimeSpan/kml:end", doc); } finally { assertTrue(templateFile.delete()); } } @Test public void testHeightTemplate() throws Exception { FeatureTypeInfo ftInfo = getCatalog().getResourceByName(getLayerId(MockData.OTHER), FeatureTypeInfo.class); File resourceDir = getDataDirectory().findResourceDir(ftInfo); File templateFile = new File(resourceDir, "height.ftl"); try { // create the height template FileUtils.writeStringToFile(templateFile, "200"); Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.OTHER) + "&styles=&height=1024&width=1024&bbox= -96.0000,0.0000,-90.0000,84.0000&srs=EPSG:4326"); // coordinates are reprojected and we get the height at 200 assertXpathEvaluatesTo("-92.99954926766114,4.52401492058674,200.0", "//kml:Placemark/kml:Point/kml:coordinates", doc); } finally { assertTrue(templateFile.delete()); } } @Test public void testVectorWithRemoteLayer() throws Exception { if (!RemoteOWSTestSupport.isRemoteWFSStatesAvailable(LOGGER)) return; Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=topp:states" + "&styles=Default" + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326" + "&remote_ows_type=wfs" + "&remote_ows_url=" + RemoteOWSTestSupport.WFS_SERVER_URL + "&cql_filter=PERSONS>20000000"); // print(doc); assertEquals(1, doc.getElementsByTagName("Placemark").getLength()); } // see GEOS-1948 @Test public void testMissingGraphic() throws Exception { Document doc = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BRIDGES) + "&styles=notthere" + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326"); assertEquals(1, doc.getElementsByTagName("Placemark").getLength()); } @Test public void testContentDisposition() throws Exception { MockHttpServletResponse resp = getAsServletResponse("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMZMapOutputFormat.MIME_TYPE + "&layers=" + MockData.BASIC_POLYGONS.getPrefix() + ":" + MockData.BASIC_POLYGONS.getLocalPart() + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326"); assertEquals("attachment; filename=cite-BasicPolygons.kmz", resp.getHeader("Content-Disposition")); } @Test public void testEncodeTime() throws Exception { setupTemplate(STORM_OBS, "time.ftl", "${obs_datetime.value}"); // AA: for the life of me I cannot make xpath work against this output, not sure why, so going // to test with strings instead... String doc = getAsString("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(STORM_OBS) + "&styles=&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&featureId=storm_obs.1321870537475"); assertTrue(doc.contains("<when>1994-07-0")); } @Test public void testKmltitleFormatOption() throws Exception { final String kmlRequest = "wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BRIDGES) + "&styles=notthere" + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326" + "&format_options=kmltitle:myCustomLayerTitle"; Document doc = getAsDOM(kmlRequest); print(doc); assertEquals("name", doc.getElementsByTagName("Document").item(0).getFirstChild().getNextSibling().getLocalName()); assertEquals("myCustomLayerTitle", doc.getElementsByTagName("Document").item(0).getFirstChild().getNextSibling().getTextContent()); } @Test public void testKmltitleFormatOptionWithMultipleLayers() throws Exception { final String kmlRequest = "wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BRIDGES) + "," + getLayerId(MockData.BASIC_POLYGONS) + "&styles=notthere" + "," + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326" + "&format_options=kmltitle:myCustomLayerTitle"; Document doc = getAsDOM(kmlRequest); print(doc); assertEquals("name", doc.getElementsByTagName("Document").item(0).getFirstChild().getNextSibling().getLocalName()); assertEquals(1, doc.getElementsByTagName("Document").getLength()); assertEquals(2, doc.getElementsByTagName("Folder").getLength()); assertEquals("myCustomLayerTitle", doc.getElementsByTagName("Document").item(0).getFirstChild().getNextSibling().getTextContent()); assertXpathEvaluatesTo("cite:Bridges", "//kml:Folder[1]/kml:name", doc); assertXpathEvaluatesTo("cite:BasicPolygons", "//kml:Folder[2]/kml:name", doc); } @Test public void testRelativeLinks() throws Exception { final String kmlRequest = "wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BASIC_POLYGONS) + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&format_options=rellinks:true"; // first page Document dom = getAsDOM(kmlRequest + "&startIndex=0&maxFeatures=1"); // print(dom); // only one link, the "next" one assertXpathEvaluatesTo("1", "count(//kml:Folder/kml:NetworkLink)", dom); assertXpathEvaluatesTo("next", "//kml:Folder/kml:NetworkLink/@id", dom); assertXpathEvaluatesTo("Next page", "//kml:Folder/kml:NetworkLink/kml:description", dom); assertXpathEvaluatesTo( "http://localhost:8080/geoserver/rest/cite/BasicPolygons.kml?startindex=1&maxfeatures=1", "//kml:Folder/kml:NetworkLink/kml:Link/kml:href", dom); // mid page dom = getAsDOM(kmlRequest + "&startIndex=1&maxFeatures=1"); // print(dom); // only one link, the "next" one assertXpathEvaluatesTo("2", "count(//kml:Folder/kml:NetworkLink)", dom); assertXpathEvaluatesTo("prev", "//kml:Folder/kml:NetworkLink[1]/@id", dom); assertXpathEvaluatesTo("Previous page", "//kml:Folder/kml:NetworkLink[1]/kml:description", dom); assertXpathEvaluatesTo( "http://localhost:8080/geoserver/rest/cite/BasicPolygons.kml?startindex=0&maxfeatures=1", "//kml:Folder/kml:NetworkLink[1]/kml:Link/kml:href", dom); assertXpathEvaluatesTo("next", "//kml:Folder/kml:NetworkLink[2]/@id", dom); assertXpathEvaluatesTo("Next page", "//kml:Folder/kml:NetworkLink[2]/kml:description", dom); assertXpathEvaluatesTo( "http://localhost:8080/geoserver/rest/cite/BasicPolygons.kml?startindex=2&maxfeatures=1", "//kml:Folder/kml:NetworkLink[2]/kml:Link/kml:href", dom); // the last page is same as the mid one, as the code does not have enough context to know it's hitting // the last one squarely } @Test public void testForceGroundOverlay() throws Exception { Document dom = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BASIC_POLYGONS) + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&format_options=kmscore:0;autofit:true"); // print(dom); assertXpathEvaluatesTo("0", "count(//kml:Placemark)", dom); assertXpathEvaluatesTo("1", "count(//kml:GroundOverlay)", dom); String pngOverlay = "http://localhost:8080/geoserver/wms?service=wms&request=GetMap&version=1.1.1&format=image%2Fpng&layers=cite%3ABasicPolygons&styles=BasicPolygons&height=512&width=1024&transparent=true&bbox=-180.0%2C-90.0%2C180.0%2C90.0&srs=EPSG%3A4326&format_options=AUTOFIT%3Atrue%3BKMSCORE%3A0%3B"; assertXpathEvaluatesTo(pngOverlay, "//kml:GroundOverlay/kml:Icon/kml:href", dom); } @Test public void testRasterLayer() throws Exception { Document dom = getAsDOM("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMLMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.TASMANIA_DEM) + "&styles=&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326"); // print(dom); assertXpathEvaluatesTo("0", "count(//kml:Placemark)", dom); assertXpathEvaluatesTo("1", "count(//kml:GroundOverlay)", dom); String pngOverlay = "http://localhost:8080/geoserver/wms?service=wms&request=GetMap&version=1.1.1&format=image%2Fpng&layers=wcs%3ADEM&styles=raster&height=1024&width=1024&transparent=true&bbox=-180.0%2C-90.0%2C180.0%2C90.0&srs=EPSG%3A4326"; assertXpathEvaluatesTo(pngOverlay, "//kml:GroundOverlay/kml:Icon/kml:href", dom); } @Test public void testKMZMixed() throws Exception { // force vector layers to be vector dumps (kmscore 100) MockHttpServletResponse response = getAsServletResponse("wms?request=getmap&service=wms&version=1.1.1" + "&format=" + KMZMapOutputFormat.MIME_TYPE + "&layers=" + getLayerId(MockData.BASIC_POLYGONS) + "," + getLayerId(MockData.WORLD) + "&styles=" + MockData.BASIC_POLYGONS.getLocalPart() + "," + "&height=1024&width=1024&bbox=-180,-90,180,90&srs=EPSG:4326&format_options=kmscore:100"); // check the contents of the zip file assertEquals(KMZMapOutputFormat.MIME_TYPE, response.getContentType()); ByteArrayInputStream bis = getBinaryInputStream(response); ZipInputStream zis = new ZipInputStream(bis); // first entry, the kml document itself ZipEntry entry = zis.getNextEntry(); assertEquals("wms.kml", entry.getName()); // we need to clone the input stream, as dom(is) closes the stream byte[] data = IOUtils.toByteArray(zis); Document dom = dom(new ByteArrayInputStream(data)); // print(dom); // we have the placemarks in the first folder (vector), and no ground overlays assertXpathEvaluatesTo("3", "count(//kml:Folder[1]/kml:Placemark)", dom); assertXpathEvaluatesTo("0", "count(//kml:Folder[1]/kml:GroundOverlay)", dom); // we have only the ground overlay in the second folder assertXpathEvaluatesTo("0", "count(//kml:Folder[2]/kml:Placemark)", dom); assertXpathEvaluatesTo("1", "count(//kml:Folder[2]/kml:GroundOverlay)", dom); assertXpathEvaluatesTo("images/layers_1.png", "//kml:Folder[2]/kml:GroundOverlay/kml:Icon/kml:href", dom); zis.closeEntry(); // the images folder entry = zis.getNextEntry(); assertEquals("images/", entry.getName()); zis.closeEntry(); // the ground overlay for the raster layer entry = zis.getNextEntry(); assertEquals("images/layers_1.png", entry.getName()); zis.closeEntry(); assertNull(zis.getNextEntry()); } }