sascha@465: package de.intevation.gnv.raster;
sascha@465:
sascha@465: import com.vividsolutions.jts.algorithm.CGAlgorithms;
sascha@465:
sascha@499: import com.vividsolutions.jts.geom.Coordinate;
sascha@499: import com.vividsolutions.jts.geom.Geometry;
sascha@499: import com.vividsolutions.jts.geom.GeometryFactory;
sascha@499: import com.vividsolutions.jts.geom.LinearRing;
sascha@499: import com.vividsolutions.jts.geom.MultiPolygon;
sascha@499: import com.vividsolutions.jts.geom.Polygon;
sascha@499: import com.vividsolutions.jts.geom.PrecisionModel;
sascha@499: import com.vividsolutions.jts.geom.TopologyException;
sascha@499:
sascha@465: import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
sascha@465:
sascha@499: import de.intevation.gnv.raster.Vectorizer.Edge;
sascha@499:
sascha@499: import java.util.ArrayList;
sascha@499: import java.util.HashMap;
sascha@499: import java.util.List;
sascha@499: import java.util.Map;
sascha@499: import java.util.TreeMap;
sascha@499:
sascha@465: import org.apache.log4j.Logger;
sascha@465:
sascha@465: /**
sascha@802: * Vectorizer backend to generated polygons in form of
sascha@802: * JTS multi multi polygons.
sascha@802: *
sascha@780: * @author Sascha L. Teichmann
sascha@465: */
sascha@465: public class JTSMultiPolygonProducer
sascha@465: extends AbstractProducer
sascha@465: {
sascha@465: private static Logger log = Logger.getLogger(
sascha@465: JTSMultiPolygonProducer.class);
sascha@465:
sascha@802: /**
sascha@802: * The reference system of used coordinates: {@value}
sascha@802: */
sascha@465: public static final int SRID_WGS84 = 4326;
sascha@465:
sascha@802: /**
sascha@802: * Interface to decouple the conversion of the internal
sascha@802: * index scheme of the polygons to an external one.
sascha@802: */
sascha@465: public interface ValueConverter {
sascha@465:
sascha@802: /**
sascha@802: * Maps integer index schemes.
sascha@802: * @param value The input value.
sascha@802: * @return The resulting index.
sascha@802: */
sascha@465: Integer convert(Integer value);
sascha@465:
sascha@465: } // interface ValueConverter
sascha@465:
sascha@802: /**
sascha@802: * The JTS geometry factory.
sascha@802: */
sascha@465: protected GeometryFactory geometryFactory;
sascha@465:
sascha@802: /**
sascha@802: * The clipping polygon.
sascha@802: */
sascha@499: protected Polygon clip;
sascha@499:
sascha@802: /**
sascha@802: * Internal map to map the internal polygons indices to a
sascha@802: * list of generated polygons.
sascha@802: */
sascha@465: protected HashMap> valueToPolygons;
sascha@465:
sascha@802: /**
sascha@802: * Default constructor.
sascha@802: */
sascha@465: public JTSMultiPolygonProducer() {
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Constructor to create a JTSMultiPolygonProducer with a
sascha@802: * given clipping polygon and a world bounding box.
sascha@802: * @param clip The clipping polygon.
sascha@802: * @param minX Min x coord of the world.
sascha@802: * @param minY Min y coord of the world.
sascha@802: * @param maxX Max x coord of the world.
sascha@802: * @param maxY Max y coord of the world.
sascha@802: */
sascha@465: public JTSMultiPolygonProducer(
sascha@499: Polygon clip,
sascha@465: double minX, double minY,
sascha@465: double maxX, double maxY
sascha@465: ) {
sascha@465: this(
sascha@499: clip,
sascha@465: createDefaultGeometryFactory(),
sascha@465: minX, minY,
sascha@465: maxX, maxY);
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Constructor to create a JTSMultiPolygonProducer with a
sascha@802: * given clipping polygon, a geometry factory and a world bounding box.
sascha@802: * @param clip The clipping polygon.
sascha@802: * @param geometryFactory The geometry factory.
sascha@802: * @param minX Min x coord of the world.
sascha@802: * @param minY Min y coord of the world.
sascha@802: * @param maxX Max x coord of the world.
sascha@802: * @param maxY Max y coord of the world.
sascha@802: */
sascha@465: public JTSMultiPolygonProducer(
sascha@499: Polygon clip,
sascha@465: GeometryFactory geometryFactory,
sascha@465: double minX, double minY,
sascha@465: double maxX, double maxY
sascha@465: ) {
sascha@465: super(minX, minY, maxX, maxY);
sascha@499: this.clip = clip;
sascha@465: this.geometryFactory = geometryFactory;
sascha@465: valueToPolygons = new HashMap>();
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Creates a 3D geometry factory via
sascha@802: * {@link #createDefaultGeometryFactory(int)}.
sascha@802: * @return The new geometry factory.
sascha@802: */
sascha@465: public static GeometryFactory createDefaultGeometryFactory() {
sascha@499: return createDefaultGeometryFactory(3);
sascha@498: }
sascha@498:
sascha@802: /**
sascha@802: * Creates a geometry factory with a given dimension.
sascha@802: * @param dim The dimension.
sascha@802: * @return The new geometry factory.
sascha@802: */
sascha@498: public static GeometryFactory createDefaultGeometryFactory(int dim) {
sascha@465: return new GeometryFactory(
sascha@465: new PrecisionModel(
sascha@465: PrecisionModel.FLOATING),
sascha@465: SRID_WGS84,
sascha@465: new PackedCoordinateSequenceFactory(
sascha@465: PackedCoordinateSequenceFactory.DOUBLE,
sascha@498: dim));
sascha@465: }
sascha@465:
sascha@465: public void handleRings(
sascha@778: List rings,
sascha@778: int value,
sascha@465: int width,
sascha@465: int height
sascha@465: ) {
sascha@465: if (value == -1) {
sascha@465: return;
sascha@465: }
sascha@465:
sascha@465: double b1 = minX;
sascha@465: double m1 = width != 1
sascha@465: ? (maxX - minX)/(width-1)
sascha@465: : 0d;
sascha@465:
sascha@465: double b2 = minY;
sascha@465: double m2 = height != 1
sascha@465: ? (maxY - minY)/(height-1)
sascha@465: : 0d;
sascha@465:
sascha@465: ArrayList vertices = new ArrayList();
sascha@465:
sascha@465: LinearRing shell = null;
sascha@465: ArrayList holes = new ArrayList();
sascha@465:
sascha@465: for (Edge head: rings) {
sascha@465: Edge current = head;
sascha@465: do {
sascha@465: vertices.add(new Coordinate(
sascha@465: m1*(current.a % width) + b1,
sascha@465: m2*(current.a / width) + b2));
sascha@465: }
sascha@495: while ((current = current.prev) != head);
sascha@465: vertices.add(new Coordinate(
sascha@465: m1*(head.a % width) + b1,
sascha@465: m2*(head.a / width) + b2));
sascha@465:
sascha@465: Coordinate [] coordinates =
sascha@465: vertices.toArray(new Coordinate[vertices.size()]);
sascha@465:
sascha@465: vertices.clear();
sascha@465:
sascha@465: LinearRing ring = geometryFactory.createLinearRing(
sascha@465: coordinates);
sascha@465:
sascha@465: if (CGAlgorithms.isCCW(coordinates)) {
sascha@465: shell = ring;
sascha@465: }
sascha@465: else {
sascha@465: holes.add(ring);
sascha@465: }
sascha@465: }
sascha@465:
sascha@465: if (shell == null) {
sascha@465: log.error("polygon has no shell");
sascha@465: return;
sascha@465: }
sascha@465:
sascha@465: Polygon polygon = geometryFactory.createPolygon(
sascha@465: shell,
sascha@465: holes.toArray(new LinearRing[holes.size()]));
sascha@465:
sascha@499: if (clip == null || clip.intersects(polygon)) {
sascha@499: Integer v = Integer.valueOf(value);
sascha@499:
sascha@499: ArrayList polygons = valueToPolygons.get(v);
sascha@499:
sascha@499: if (polygons == null) {
sascha@499: valueToPolygons.put(v, polygons = new ArrayList());
sascha@499: }
sascha@499:
sascha@499: polygons.add(polygon);
sascha@499: }
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Returns a index -> multi polyon map. The internal index scheme
sascha@802: * is retained.
sascha@802: * {@link #getMultiPolygons(de.intevation.gnv.raster.JTSMultiPolygonProducer.ValueConverter)}
sascha@802: * @return The new map.
sascha@802: */
sascha@465: public Map getMultiPolygons() {
sascha@465: return getMultiPolygons(null);
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Flattend a JTS geometry to a multi polygon if possible
sascha@802: * @param result The geometry to be flattend
sascha@802: * @return The resulting multi polygon.
sascha@802: */
sascha@499: protected MultiPolygon flattenCollection(Geometry result) {
sascha@499:
sascha@499: if (result instanceof MultiPolygon) {
sascha@499: return (MultiPolygon)result;
sascha@499: }
sascha@499:
sascha@499: if (result instanceof Polygon) {
sascha@499: return geometryFactory.createMultiPolygon(
sascha@499: new Polygon[] { (Polygon)result });
sascha@499: }
sascha@499:
sascha@499: log.warn("cannot handle " + result.getClass());
sascha@499:
sascha@499: return null;
sascha@499: }
sascha@499:
sascha@802: /**
sascha@802: * Returns a index -> multi polyon map. The internal index scheme
sascha@802: * is remapped with the given value converter.
sascha@802: * @param valueConverter Converter to remap the index scheme. Maybe null
sascha@802: * if no remapping is required.
sascha@802: * @return The new map.
sascha@802: */
sascha@465: public Map getMultiPolygons(
sascha@465: ValueConverter valueConverter
sascha@465: ) {
sascha@465: TreeMap multiPolygons =
sascha@465: new TreeMap();
sascha@465:
sascha@465: for (Map.Entry> entry:
sascha@465: valueToPolygons.entrySet()
sascha@465: ) {
sascha@465: ArrayList polygons = entry.getValue();
sascha@465:
sascha@465: Integer value = valueConverter != null
sascha@465: ? valueConverter.convert(entry.getKey())
sascha@465: : entry.getKey();
sascha@465:
sascha@499: MultiPolygon mp = geometryFactory.createMultiPolygon(
sascha@499: polygons.toArray(new Polygon[polygons.size()]));
sascha@499:
sascha@499: if (clip != null) {
sascha@499: try {
sascha@499: Geometry result = mp.intersection(clip);
sascha@499:
sascha@499: MultiPolygon mp2 = flattenCollection(result);
sascha@499:
sascha@499: if (mp2 != null) { mp = mp2; }
sascha@499: }
sascha@499: catch (TopologyException te) {
sascha@499: log.error(te.getLocalizedMessage(), te);
sascha@499: }
sascha@499: }
sascha@499: multiPolygons.put(value, mp);
sascha@465: }
sascha@465:
sascha@465: return multiPolygons;
sascha@465: }
sascha@465:
sascha@802: /**
sascha@802: * Empties internal temporary data structures to save some memory.
sascha@802: */
sascha@465: public void clear() {
sascha@465: valueToPolygons.clear();
sascha@465: }
sascha@465: }
sascha@465: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :