/*
* 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 static org.geotools.referencing.crs.DefaultGeographicCRS.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
/**
* A class that can perform transformations on geometries to handle the singularity of the rendering
* CRS, deal with geometries that are crossing the dateline, and eventually wrap them around to
* produce a seamless continuous map effect.<p>
*
* This basic implementation will cut the geometries that get outside of the area of validity of the
* projection (as provided by the constructor)
*
* WARNING: this API is not finalized and is meant to be used by StreamingRenderer only
* @author Andrea Aime - OpenGeo
*/
public class ProjectionHandler {
protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ProjectionHandler.class);
final ReferencedEnvelope renderingEnvelope;
final ReferencedEnvelope validArea;
public ProjectionHandler(ReferencedEnvelope renderingEnvelope, ReferencedEnvelope validArea) {
this.renderingEnvelope = renderingEnvelope;
this.validArea = validArea;
}
/**
* Returns a set of envelopes that will be used to query the data given the specified rendering
* envelope and the current query envelope
*/
public List<ReferencedEnvelope> getQueryEnvelopes(CoordinateReferenceSystem queryCRS)
throws TransformException, FactoryException {
// check if we are crossing the dateline
ReferencedEnvelope re = renderingEnvelope.transform(WGS84, true, 10);
if (re.getMinX() >= -180.0 && re.getMaxX() <= 180)
return Collections.singletonList(renderingEnvelope.transform(queryCRS, true, 10));
// We need to split reprojected envelope and normalize it. To be lenient with
// situations in which the data is just broken (people saying 4326 just because they
// have no idea at all) we don't actually split, but add elements
List<ReferencedEnvelope> envelopes = new ArrayList<ReferencedEnvelope>();
envelopes.add(re);
if (re.getMinX() < -180) {
envelopes.add(new ReferencedEnvelope(re.getMinX() + 360, 180, re.getMinY(), re
.getMaxY(), re.getCoordinateReferenceSystem()));
}
if (re.getMaxX() > 180) {
envelopes.add(new ReferencedEnvelope(-180, re.getMaxX() - 360, re.getMinY(), re
.getMaxY(), re.getCoordinateReferenceSystem()));
}
// the envelopes generated might overlap, check and merge if necessary, we
// don't want the data backend to deal with ORs against the spatial index
// unless necessary
boolean merged = true;
while (merged && envelopes.size() > 1) {
merged = false;
for (int i = 0; i < envelopes.size() - 1; i++) {
ReferencedEnvelope curr = envelopes.get(i);
for (int j = i + 1; j < envelopes.size();) {
ReferencedEnvelope next = envelopes.get(j);
if (curr.intersects((Envelope) next)) {
curr.expandToInclude(next);
envelopes.remove(j);
merged = true;
} else {
j++;
}
}
}
}
// reproject the surviving envelopes
for (int i = 0; i < envelopes.size(); i++) {
envelopes.set(i, envelopes.get(i).transform(queryCRS, true, 10));
}
return envelopes;
}
/**
* Returns true if the geometry needs special handling
*/
public boolean requiresProcessing(CoordinateReferenceSystem geomCRS, Geometry geometry) {
// if there is no valid area, no cutting is required
if(validArea == null)
return false;
// if not reprojection is going on, we don't need to cut
if (CRS.equalsIgnoreMetadata(geomCRS, renderingEnvelope.getCoordinateReferenceSystem())) {
return false;
}
return true;
}
/**
* Pre processes the geometry, e.g. cuts it, splits it, etc. in its native srs. May return null
* if the geometry is not to be drawn
*/
public Geometry preProcess(CoordinateReferenceSystem geomCRS, Geometry geometry) throws TransformException, FactoryException {
// if not reprojection is going on, we don't need to cut
if (CRS.equalsIgnoreMetadata(geomCRS, renderingEnvelope.getCoordinateReferenceSystem())) {
return geometry;
}
// if there is no valid area, no cutting is required either
if(validArea == null)
return geometry;
// if the geometry is within the valid area for this projection
// just skip expensive cutting
ReferencedEnvelope ge = new ReferencedEnvelope(geometry.getEnvelopeInternal(), geomCRS);
ReferencedEnvelope geWGS84 = ge.transform(WGS84, true);
if (validArea.contains((Envelope) geWGS84)) {
return geometry;
}
// we need to cut, first thing, we intersect the geometry envelope
// and the valid area in WGS84, which is a neutral, everything can
// be turned into it, and then turn back the intersection into
// the origin SRS
ReferencedEnvelope envIntWgs84 = new ReferencedEnvelope(validArea.intersection(geWGS84), WGS84);
// if the intersection is empty the geometry is completely outside of the valid area, skip it
if(envIntWgs84.isEmpty())
return null;
ReferencedEnvelope envInt = envIntWgs84.transform(geomCRS, true);
// turn the envelope into a geometry and perform the intersection
Geometry result = geometry.intersection(JTS.toGeometry((Envelope) envInt));
// handle in special way empty intersections
if (result instanceof GeometryCollection && ((GeometryCollection) result).isEmpty())
return null;
else
return result;
}
/**
* Processes the geometry already projected to the target SRS. May return null if the geometry
* is not to be drawn
*/
public Geometry postProcess(Geometry geometry) {
return geometry;
}
}