/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. ****************************************************************/ package no.ecc.vectortile; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import junit.framework.TestCase; import no.ecc.vectortile.VectorTileDecoder.Feature; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.locationtech.jts.geom.Point; public class VectorTileDecoderTest extends TestCase { private GeometryFactory gf = new GeometryFactory(); public void testZigZagDecode() { assertEquals(0, VectorTileDecoder.zigZagDecode(0)); assertEquals(-1, VectorTileDecoder.zigZagDecode(1)); assertEquals(1, VectorTileDecoder.zigZagDecode(2)); assertEquals(-2, VectorTileDecoder.zigZagDecode(3)); } public void testWithoutScaling() throws IOException { Coordinate c = new Coordinate(2, 3); Geometry geometry = gf.createPoint(c); Coordinate c2 = new Coordinate(4, 6); Geometry geometry2 = gf.createPoint(c2); VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature("layer", Collections.emptyMap(), geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); d.setAutoScale(false); assertEquals(geometry2, d.decode(encoded, "layer").iterator().next().getGeometry()); } public void testPoint() throws IOException { Coordinate c = new Coordinate(2, 3); Geometry geometry = gf.createPoint(c); Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("hello", 123); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).iterator().next().getAttributes()); assertEquals(geometry, d.decode(encoded, layerName).iterator().next().getGeometry()); } public void testMultiPoint() throws IOException { Coordinate c1 = new Coordinate(2, 3); Coordinate c2 = new Coordinate(3, 4); Geometry geometry = gf.createMultiPointFromCoords(new Coordinate[] { c1, c2 }); Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("hello", 123); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes()); assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry()); } public void testLineString() throws IOException { Coordinate c1 = new Coordinate(1, 2); Coordinate c2 = new Coordinate(10, 20); Coordinate c3 = new Coordinate(100, 200); Geometry geometry = gf.createLineString(new Coordinate[] { c1, c2, c3 }); Map<String, Object> attributes = new HashMap<String, Object>(); attributes.put("aa", "bb"); attributes.put("cc", "bb"); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes()); assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry()); } public void testMultiLineString() throws IOException { Coordinate c1 = new Coordinate(1, 2); Coordinate c2 = new Coordinate(5, 6); Coordinate c3 = new Coordinate(7, 8); Coordinate c4 = new Coordinate(8, 10); Coordinate c5 = new Coordinate(11, 12); LineString ls1 = gf.createLineString(new Coordinate[] { c1, c2, c3 }); LineString ls2 = gf.createLineString(new Coordinate[] { c4, c5 }); Geometry geometry = gf.createMultiLineString(new LineString[] { ls1, ls2 }); Map<String, Object> attributes = new HashMap<String, Object>(); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes()); assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry()); } public void testPolygon() throws IOException { // Exterior ring in counter-clockwise order. LinearRing shell = gf.createLinearRing(new Coordinate[] { new Coordinate(10, 10), new Coordinate(20, 10), new Coordinate(20, 20), new Coordinate(10, 20), new Coordinate(10, 10) }); assertTrue(shell.isClosed()); assertTrue(shell.isValid()); Geometry geometry = gf.createPolygon(shell, new LinearRing[] {}); assertTrue(geometry.isValid()); Map<String, Object> attributes = new HashMap<String, Object>(); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes()); assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry()); } public void testPolygonWithHole() throws IOException { // Exterior ring in counter-clockwise order. LinearRing shell = gf.createLinearRing(new Coordinate[] { new Coordinate(10, 10), new Coordinate(20, 10), new Coordinate(20, 20), new Coordinate(10, 20), new Coordinate(10, 10) }); assertTrue(shell.isClosed()); assertTrue(shell.isValid()); Geometry geometry = gf.createPolygon(shell, new LinearRing[] {}); assertTrue(geometry.isValid()); // Interior ring in clockwise order. LinearRing hole = gf.createLinearRing(new Coordinate[] { new Coordinate(11, 11), new Coordinate(11, 19), new Coordinate(19, 19), new Coordinate(19, 11), new Coordinate(11, 11) }); assertTrue(hole.isClosed()); assertTrue(hole.isValid()); assertTrue(geometry.contains(hole)); geometry = gf.createPolygon(shell, new LinearRing[] { hole }); assertTrue(geometry.isValid()); Map<String, Object> attributes = new HashMap<String, Object>(); String layerName = "layer"; VectorTileEncoder e = new VectorTileEncoder(512); e.addFeature(layerName, attributes, geometry); byte[] encoded = e.encode(); VectorTileDecoder d = new VectorTileDecoder(); assertEquals(1, d.decode(encoded).getLayerNames().size()); assertEquals(layerName, d.decode(encoded).getLayerNames().iterator().next()); assertEquals(attributes, d.decode(encoded, layerName).asList().get(0).getAttributes()); assertEquals(geometry.toText(), d.decode(encoded, layerName).asList().get(0).getGeometry().toText()); assertEquals(geometry, d.decode(encoded, layerName).asList().get(0).getGeometry()); } public void testExternal() throws IOException { // from // https://github.com/mapbox/vector-tile-js/tree/master/test/fixtures InputStream is = getClass().getResourceAsStream("/14-8801-5371.vector.pbf"); assertNotNull(is); byte[] encoded = toBytes(is); VectorTileDecoder d = new VectorTileDecoder(); d.decode(encoded).getLayerNames() .equals(new HashSet<String>(Arrays.asList("landuse", "waterway", "water", "barrier_line", "building", "landuse_overlay", "tunnel", "road", "bridge", "place_label", "water_label", "poi_label", "road_label", "waterway_label"))); assertEquals(558, d.decode(encoded, "poi_label").asList().size()); Feature park = d.decode(encoded, "poi_label").asList().get(11); assertEquals("Mauerpark", park.getAttributes().get("name")); assertEquals("Park", park.getAttributes().get("type")); Geometry parkGeometry = park.getGeometry(); assertTrue(parkGeometry instanceof Point); assertEquals(1, parkGeometry.getCoordinates().length); assertEquals(new Coordinate(3898.0, 1731.0), park.getExtent(), parkGeometry.getCoordinates()[0]); Feature building = d.decode(encoded, "building").asList().get(0); Geometry buildingGeometry = d.decode(encoded, "building").asList().get(0).getGeometry(); assertNotNull(building); assertEquals(5, buildingGeometry.getCoordinates().length); assertEquals(new Coordinate(2039, -32), building.getExtent(), buildingGeometry.getCoordinates()[0]); assertEquals(new Coordinate(2035, -31), building.getExtent(), buildingGeometry.getCoordinates()[1]); assertEquals(new Coordinate(2032, -31), building.getExtent(), buildingGeometry.getCoordinates()[2]); assertEquals(new Coordinate(2032, -32), building.getExtent(), buildingGeometry.getCoordinates()[3]); assertEquals(new Coordinate(2039, -32), building.getExtent(), buildingGeometry.getCoordinates()[4]); } public void testBigTile() throws IOException { for (int i = 0; i < 10; i++) { System.gc(); long memoryStart = Runtime.getRuntime().totalMemory(); InputStream is = getClass().getResourceAsStream("/bigtile.vector.pbf"); assertNotNull(is); VectorTileDecoder d = new VectorTileDecoder(); for (Iterator<VectorTileDecoder.Feature> it = d.decode(toBytes(is)).iterator(); it.hasNext();) { it.next(); if (!it.hasNext()) { long memoryDiff = Runtime.getRuntime().totalMemory() - memoryStart; System.out.println(memoryDiff / (1024 * 1024)); } } } } public void testLineWithOnePoint() throws IOException { InputStream is = getClass().getResourceAsStream("/cells-11-1065-567.mvt"); assertNotNull(is); VectorTileDecoder d = new VectorTileDecoder(); int numberOfFeatures = 0; for (Iterator<VectorTileDecoder.Feature> it = d.decode(toBytes(is)).iterator(); it.hasNext();) { Feature f = it.next(); assertNotNull(f.getGeometry()); numberOfFeatures++; } assertEquals(306, numberOfFeatures); } public void testPolygonWithThreePointHole() throws IOException { InputStream is = getClass().getResourceAsStream("/cells-11-1058-568.mvt"); assertNotNull(is); VectorTileDecoder d = new VectorTileDecoder(); int numberOfFeatures = 0; for (Iterator<VectorTileDecoder.Feature> it = d.decode(toBytes(is)).iterator(); it.hasNext();) { Feature f = it.next(); assertNotNull(f.getGeometry()); numberOfFeatures++; } assertEquals(699, numberOfFeatures); } private void assertEquals(Coordinate expected, int extent, Coordinate actual) { double scale = extent / 256.0; assertEquals(expected.x / scale, actual.x); assertEquals(expected.y / scale, actual.y); } private void assertEquals(Map<String, Object> expected, Map<String, Object> real) { assertEquals(expected.size(), real.size()); for (Map.Entry<String, Object> e : expected.entrySet()) { String key = e.getKey(); assertTrue(real.containsKey(key)); Object expectedValue = e.getValue(); Object realValue = real.get(key); if (expectedValue instanceof Number) { assertTrue(realValue instanceof Number); Number exp = (Number) expectedValue; Number rea = (Number) realValue; assertEquals(exp.intValue(), rea.intValue()); assertEquals(exp.floatValue(), rea.floatValue(), 0.003); assertEquals(exp.doubleValue(), rea.doubleValue(), 0.003); } else { assertEquals(expectedValue.getClass(), realValue.getClass()); assertEquals(expectedValue, realValue); } } } private static byte[] toBytes(InputStream in) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[8192]; int bytesRead = 0; while ((bytesRead = in.read(buf)) != -1) { baos.write(buf, 0, bytesRead); } return baos.toByteArray(); } }