/* (c) 2015 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.topojson; import static org.geoserver.wms.topojson.TopoJSONBuilderFactory.MIME_TYPE; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import org.apache.commons.io.output.DeferredFileOutputStream; import org.geoserver.wms.WMSMapContent; import org.geoserver.wms.map.RawMap; import org.geoserver.wms.topojson.TopoGeom.GeometryColleciton; import org.geoserver.wms.vector.DeferredFileOutputStreamWebMap; import org.geoserver.wms.vector.VectorTileBuilder; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.renderer.lite.RendererUtilities; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.operation.TransformException; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multimap; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.PrecisionModel; public class TopologyBuilder implements VectorTileBuilder { private AffineTransform worldToScreen; private AffineTransform screenToWorld; private List<LineString> arcs = new ArrayList<>(); private Multimap<String, TopoGeom> layers = ArrayListMultimap.create(); private GeometryFactory fixedGeometryFactory; public TopologyBuilder(Rectangle mapSize, ReferencedEnvelope mapArea) { this.worldToScreen = RendererUtilities.worldToScreenTransform(mapArea, mapSize); this.screenToWorld = new AffineTransform(this.worldToScreen); try { this.screenToWorld.invert(); } catch (NoninvertibleTransformException e) { throw Throwables.propagate(e); } PrecisionModel precisionModel = new PrecisionModel(10.0); fixedGeometryFactory = new GeometryFactory(precisionModel); } @Override public void addFeature(String layerName, String featureId, String geometryName, Geometry geometry, Map<String, Object> properties) { TopoGeom topoObj; try { topoObj = createObject(featureId, geometry, properties); } catch (MismatchedDimensionException | TransformException e) { e.printStackTrace(); throw Throwables.propagate(e); } if (topoObj != null) { layers.put(layerName, topoObj); } } @Override public RawMap build(WMSMapContent mapContent) throws IOException { Map<String, TopoGeom.GeometryColleciton> layers = new HashMap<>(); for (String layer : this.layers.keySet()) { Collection<TopoGeom> collection = this.layers.get(layer); GeometryColleciton layerCollection = new TopoGeom.GeometryColleciton(collection); layers.put(layer, layerCollection); } List<LineString> arcs = this.arcs; this.arcs = null; this.layers = null; Topology topology = new Topology(screenToWorld, arcs, layers); final int threshold = 8096; DeferredFileOutputStream out = new DeferredFileOutputStream(threshold, "topology", ".topojson", null); TopoJSONEncoder encoder = new TopoJSONEncoder(); Writer writer = new OutputStreamWriter(out, Charsets.UTF_8); encoder.encode(topology, writer); writer.flush(); writer.close(); out.close(); long length; RawMap map; if (out.isInMemory()) { byte[] data = out.getData(); length = data.length; map = new RawMap(mapContent, data, MIME_TYPE); } else { File f = out.getFile(); length = f.length(); map = new DeferredFileOutputStreamWebMap(mapContent, out, MIME_TYPE); } map.setResponseHeader("Content-Length", String.valueOf(length)); return map; } @Nullable private TopoGeom createObject(String featureId, Geometry geom, Map<String, Object> properties) throws MismatchedDimensionException, TransformException { // // snap to pixel geom = fixedGeometryFactory.createGeometry(geom); if (geom.isEmpty()) { return null; } if (geom instanceof GeometryCollection && geom.getNumGeometries() == 1) { geom = geom.getGeometryN(0); } TopoGeom geometry = createGeometry(geom); geometry.setProperties(properties); geometry.setId(featureId); return geometry; } private TopoGeom createGeometry(Geometry geom) { Preconditions.checkNotNull(geom); TopoGeom topoGeom; if (geom instanceof Point) { topoGeom = createPoint((Point) geom); } else if (geom instanceof MultiPoint) { topoGeom = createMultiPoint((MultiPoint) geom); } else if (geom instanceof LineString) { topoGeom = createLineString((LineString) geom); } else if (geom instanceof MultiLineString) { topoGeom = createMultiLineString((MultiLineString) geom); } else if (geom instanceof Polygon) { topoGeom = createPolygon((Polygon) geom); } else if (geom instanceof MultiPolygon) { topoGeom = createMultiPolygon((MultiPolygon) geom); } else if (geom instanceof GeometryCollection) { topoGeom = createGeometryCollection((GeometryCollection) geom); } else { throw new IllegalArgumentException("Unknown geometry type: " + geom.getGeometryType()); } return topoGeom; } private TopoGeom.LineString createLineString(LineString geom) { int arcIndex = this.arcs.size(); this.arcs.add(geom); return new TopoGeom.LineString(ImmutableList.of(Integer.valueOf(arcIndex))); } private TopoGeom.Polygon createPolygon(Polygon geom) { List<TopoGeom.LineString> arcs = new ArrayList<>(1 + geom.getNumInteriorRing()); arcs.add(createLineString(geom.getExteriorRing())); for (int n = 0; n < geom.getNumInteriorRing(); n++) { arcs.add(createLineString(geom.getInteriorRingN(n))); } return new TopoGeom.Polygon(arcs); } private TopoGeom.GeometryColleciton createGeometryCollection(GeometryCollection geom) { Collection<TopoGeom> members = new ArrayList<>(geom.getNumGeometries()); for (int n = 0; n < geom.getNumGeometries(); n++) { TopoGeom o = createGeometry(geom.getGeometryN(n)); members.add(o); } TopoGeom.GeometryColleciton collection = new TopoGeom.GeometryColleciton(members); return collection; } private TopoGeom.MultiPolygon createMultiPolygon(MultiPolygon geom) { List<TopoGeom.Polygon> polygons = new ArrayList<>(geom.getNumGeometries()); for (int n = 0; n < geom.getNumGeometries(); n++) { polygons.add(createPolygon((Polygon) geom.getGeometryN(n))); } return new TopoGeom.MultiPolygon(polygons); } private TopoGeom.MultiLineString createMultiLineString(MultiLineString geom) { List<TopoGeom.LineString> arcs = new ArrayList<>(geom.getNumGeometries()); for (int n = 0; n < geom.getNumGeometries(); n++) { arcs.add(createLineString((LineString) geom.getGeometryN(n))); } return new TopoGeom.MultiLineString(arcs); } private TopoGeom.MultiPoint createMultiPoint(MultiPoint geom) { List<TopoGeom.Point> points = new ArrayList<>(geom.getNumGeometries()); for (int n = 0; n < geom.getNumGeometries(); n++) { points.add(createPoint((Point) geom.getGeometryN(n))); } return new TopoGeom.MultiPoint(points); } private TopoGeom.Point createPoint(Point geom) { return new TopoGeom.Point(geom.getX(), geom.getY()); } }