view gnv-artifacts/src/main/java/de/intevation/gnv/raster/JTSMultiPolygonProducer.java @ 1115:f953c9a559d8

Added license file and license headers. gnv-artifacts/trunk@1260 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author Ingo Weinzierl <ingo.weinzierl@intevation.de>
date Tue, 02 Nov 2010 17:46:55 +0000
parents feae2f9d6c6f
children
line wrap: on
line source
/*
 * Copyright (c) 2010 by Intevation GmbH
 *
 * This program is free software under the LGPL (>=v2.1)
 * Read the file LGPL.txt coming with the software for details
 * or visit http://www.gnu.org/licenses/ if it does not exist.
 */

package de.intevation.gnv.raster;

import com.vividsolutions.jts.algorithm.CGAlgorithms;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.geom.TopologyException;

import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;

import de.intevation.gnv.raster.Vectorizer.Edge;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;

/**
 * Vectorizer backend to generated polygons in form of
 * JTS multi multi polygons.
 *
 * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
 */
public class JTSMultiPolygonProducer
extends      AbstractProducer
{
    private static Logger log = Logger.getLogger(
        JTSMultiPolygonProducer.class);

    /**
     * The reference system of used coordinates: {@value}
     */
    public static final int SRID_WGS84 = 4326;

    /**
     * Interface to decouple the conversion of the internal
     * index scheme of the polygons to an external one.
     */
    public interface ValueConverter {

        /**
         * Maps integer index schemes.
         * @param value The input value.
         * @return The resulting index.
         */
        Integer convert(Integer value);

    } // interface ValueConverter

    /**
     * The JTS geometry factory.
     */
    protected GeometryFactory geometryFactory;

    /**
     * The clipping polygon.
     */
    protected Polygon clip;

    /**
     * Internal map to map the internal polygons indices to a
     * list of generated polygons.
     */
    protected HashMap<Integer, ArrayList<Polygon>> valueToPolygons;

    /**
     * Default constructor.
     */
    public JTSMultiPolygonProducer() {
    }

    /**
     * Constructor to create a JTSMultiPolygonProducer with a
     * given clipping polygon and a world bounding box.
     * @param clip The clipping polygon.
     * @param minX Min x coord of the world.
     * @param minY Min y coord of the world.
     * @param maxX Max x coord of the world.
     * @param maxY Max y coord of the world.
     */
    public JTSMultiPolygonProducer(
        Polygon clip,
        double minX, double minY,
        double maxX, double maxY
    ) {
        this(
            clip,
            createDefaultGeometryFactory(),
            minX, minY,
            maxX, maxY);
    }

    /**
     *  Constructor to create a JTSMultiPolygonProducer with a
     * given clipping polygon, a geometry factory and a world bounding box.
     * @param clip The clipping polygon.
     * @param geometryFactory The geometry factory.
     * @param minX Min x coord of the world.
     * @param minY Min y coord of the world.
     * @param maxX Max x coord of the world.
     * @param maxY Max y coord of the world.
     */
    public JTSMultiPolygonProducer(
        Polygon clip,
        GeometryFactory geometryFactory,
        double minX, double minY,
        double maxX, double maxY
    ) {
        super(minX, minY, maxX, maxY);
        this.clip = clip;
        this.geometryFactory = geometryFactory;
        valueToPolygons = new HashMap<Integer, ArrayList<Polygon>>();
    }

    /**
     * Creates a 3D geometry factory via
     * {@link #createDefaultGeometryFactory(int)}.
     * @return The new geometry factory.
     */
    public static GeometryFactory createDefaultGeometryFactory() {
        return createDefaultGeometryFactory(3);
    }

    /**
     * Creates a geometry factory with a given dimension.
     * @param dim The dimension.
     * @return The new geometry factory.
     */
    public static GeometryFactory createDefaultGeometryFactory(int dim) {
        return new GeometryFactory(
            new PrecisionModel(
                PrecisionModel.FLOATING),
            SRID_WGS84,
            new PackedCoordinateSequenceFactory(
                PackedCoordinateSequenceFactory.DOUBLE,
                dim));
    }

    public void handleRings(
        List<Edge> rings,
        int        value,
        int        width,
        int        height
    ) {
        if (value == -1) {
            return;
        }

        double b1 = minX;
        double m1 = width != 1
           ? (maxX - minX)/(width-1)
           : 0d;

        double b2 = minY;
        double m2 = height != 1
           ? (maxY - minY)/(height-1)
           : 0d;

        ArrayList<Coordinate> vertices = new ArrayList<Coordinate>();

        LinearRing shell = null;
        ArrayList<LinearRing> holes = new ArrayList<LinearRing>();

        for (Edge head: rings) {
            Edge current = head;
            do {
                vertices.add(new Coordinate(
                    m1*(current.a % width) + b1,
                    m2*(current.a / width) + b2));
            }
            while ((current = current.prev) != head);
            vertices.add(new Coordinate(
                m1*(head.a % width) + b1,
                m2*(head.a / width) + b2));

            Coordinate [] coordinates =
                vertices.toArray(new Coordinate[vertices.size()]);

            vertices.clear();

            LinearRing ring = geometryFactory.createLinearRing(
                coordinates);

            if (CGAlgorithms.isCCW(coordinates)) {
                shell = ring;
            }
            else {
                holes.add(ring);
            }
        }

        if (shell == null) {
            log.error("polygon has no shell");
            return;
        }

        Polygon polygon = geometryFactory.createPolygon(
            shell,
            holes.toArray(new LinearRing[holes.size()]));

        if (clip == null || clip.intersects(polygon)) {
            Integer v = Integer.valueOf(value);

            ArrayList<Polygon> polygons = valueToPolygons.get(v);

            if (polygons == null) {
                valueToPolygons.put(v, polygons = new ArrayList<Polygon>());
            }

            polygons.add(polygon);
        }
    }

    /**
     * Returns a index -&gt; multi polyon map. The internal index scheme
     * is retained.
     * {@link #getMultiPolygons(de.intevation.gnv.raster.JTSMultiPolygonProducer.ValueConverter)}
     * @return The new map.
     */
    public Map<Integer, MultiPolygon> getMultiPolygons() {
        return getMultiPolygons(null);
    }

    /**
     * Flattend a JTS geometry to a multi polygon if possible
     * @param result The geometry to be flattend
     * @return The resulting multi polygon.
     */
    protected MultiPolygon flattenCollection(Geometry result) {

        if (result instanceof MultiPolygon) {
            return (MultiPolygon)result;
        }

        if (result instanceof Polygon) {
            return geometryFactory.createMultiPolygon(
                new Polygon[] { (Polygon)result });
        }

        log.warn("cannot handle " + result.getClass());

        return null;
    }

    /**
     * Returns a index -&gt; multi polyon map. The internal index scheme
     * is remapped with the given value converter.
     * @param valueConverter Converter to remap the index scheme. Maybe null
     * if no remapping is required.
     * @return The new map.
     */
    public Map<Integer, MultiPolygon> getMultiPolygons(
        ValueConverter valueConverter
    ) {
        TreeMap<Integer, MultiPolygon> multiPolygons =
            new TreeMap<Integer, MultiPolygon>();

        for (Map.Entry<Integer, ArrayList<Polygon>> entry:
            valueToPolygons.entrySet()
        ) {
            ArrayList<Polygon> polygons = entry.getValue();

            Integer value = valueConverter != null
                ? valueConverter.convert(entry.getKey())
                : entry.getKey();

            MultiPolygon mp = geometryFactory.createMultiPolygon(
                polygons.toArray(new Polygon[polygons.size()]));

            if (clip != null) {
                try {
                    Geometry result = mp.intersection(clip);

                    MultiPolygon mp2 = flattenCollection(result);

                    if (mp2 != null) { mp = mp2; }
                }
                catch (TopologyException te) {
                    log.error(te.getLocalizedMessage(), te);
                }
            }
            multiPolygons.put(value, mp);
        }

        return multiPolygons;
    }

    /**
     * Empties internal temporary data structures to save some memory.
     */
    public void clear() {
        valueToPolygons.clear();
    }
}
// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org