/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2002-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.renderer.crs; import com.vividsolutions.jts.geom.CoordinateSequence; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryComponentFilter; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.LinearRing; import java.util.logging.Level; import org.geotools.util.logging.Logging; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; /** * Wraps the coordinates at the discontinuity so that they are always moved towards East * (fundamental in order to have all pieces of a complex geometry moved into the same direction). * * @author Andrea Aime - OpenGeo */ class WrappingCoordinateFilter implements GeometryComponentFilter { static final int EAST_TO_WEST = 0; static final int WEST_TO_EAST = 1; static final int NOWRAP = 2; final double wrapLimit; final double offset; final MathTransform mt; /** * Builds a new wrapper * * @param wrapLimit * Subsequent coordinates whose X differ from more than {@code wrapLimit} are * supposed to be wrapping the dateline and need to be offsetted * @param offset * The offset to be applied to coordinates to unwrap them */ public WrappingCoordinateFilter(double wrapLimit, double offset, MathTransform mt) { this.wrapLimit = wrapLimit; this.offset = offset; this.mt = mt; } public void filter(Geometry geom) { // this filter will receive either points, which we don't consider, // or lines, that we need to wrap if (geom instanceof LineString) { LineString ls = (LineString) geom; CoordinateSequence cs = ls.getCoordinateSequence(); int direction = getDisconinuityDirection(cs); if (direction == NOWRAP) return; boolean ring = geom instanceof LinearRing || cs.getCoordinate(0).equals(cs.getCoordinate(cs.size() - 1)); applyOffset(cs, direction == EAST_TO_WEST ? 0 : wrapLimit * 2, ring); } } private int getDisconinuityDirection(CoordinateSequence cs) { double lastX = cs.getX(0); for (int i = 0; i < cs.size(); i++) { double x = cs.getX(i); if (Math.abs(x - lastX) > wrapLimit) { if (x > lastX) return WEST_TO_EAST; else if (x < lastX) return EAST_TO_WEST; } lastX = x; } return NOWRAP; } private void applyOffset(CoordinateSequence cs, double offset, boolean ring) { final double maxWrap = wrapLimit * 1.9; double lastX = cs.getX(0); int last = ring ? cs.size() - 1 : cs.size(); for (int i = 0; i < last; i++) { final double x = cs.getX(i); final double distance = Math.abs(x - lastX); // heuristic: an object crossing the dateline is not as big as the world, if it // is, it's probably something like Antarctica that does not need coordinate rewrapping if (distance > wrapLimit) { boolean wraps = distance < maxWrap; // if we fail here, revert to more expensive calculation if // we have a reverse transform // this is analagous to the technique mentioned here: // http://trac.osgeo.org/mapserver/ticket/15 if (!wraps && mt != null) { // convert back to projected coordinates double[] src = new double[]{lastX, cs.getY(i - 1), x, cs.getY(i)}; double[] dest = new double[4]; try { mt.transform(src, 0, dest, 0, 2); // find the midpoint coordinate src[0] = Math.min(dest[0], dest[2]) + Math.abs(dest[2] - dest[0]) / 2; src[1] = Math.min(dest[1], dest[3]) + Math.abs(dest[3] - dest[1]) / 2; // and convert back again mt.inverse().transform(src, 0, dest, 0, 1); // if the midpoint isn't between the two end points, it's a wrap wraps = !(dest[0] > Math.min(lastX, x) && dest[0] < Math.max(lastX, x)); } catch (TransformException ex) { Logging.getLogger("org.geotools.rendering").log(Level.WARNING, "Unable to perform transform to detect dateline wrapping", ex); } } // toggle between offset if (wraps) { if (offset != 0) { offset = 0; } else { offset = wrapLimit * 2; } } } if (offset != 0) cs.setOrdinate(i, 0, x + offset); lastX = x; } if(ring) { cs.setOrdinate(last, 0, cs.getOrdinate(0, 0)); } } }