/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2015, 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.WGS84;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.transform.ConcatenatedTransform;
import org.geotools.referencing.operation.transform.GeocentricTransform;
import org.opengis.metadata.extent.GeographicBoundingBox;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
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;
import com.vividsolutions.jts.geom.prep.PreparedGeometry;
import com.vividsolutions.jts.geom.prep.PreparedGeometryFactory;
import com.vividsolutions.jts.precision.EnhancedPrecisionOp;
import com.vividsolutions.jts.precision.GeometryPrecisionReducer;
/**
* 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
*
*
* @source $URL$
*/
public class ProjectionHandler {
protected static final double EPS = 1e-6;
protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ProjectionHandler.class);
protected ReferencedEnvelope renderingEnvelope;
protected final ReferencedEnvelope validAreaBounds;
protected final Geometry validArea;
protected final PreparedGeometry validaAreaTester;
protected final CoordinateReferenceSystem sourceCRS;
protected final CoordinateReferenceSystem targetCRS;
protected double datelineX = Double.NaN;
protected double radius = Double.NaN;
protected boolean queryAcrossDateline;
protected SingleCRS geometryCRS;
protected boolean noReprojection;
/**
* Initializes a projection handler
*
* @param sourceCRS The source CRS
* @param validArea The valid area (used to cut geometries that go beyond it)
* @param renderingEnvelope The target rendering area and target CRS
*
* @throws FactoryException
*/
public ProjectionHandler(CoordinateReferenceSystem sourceCRS, Envelope validAreaBounds, ReferencedEnvelope renderingEnvelope) throws FactoryException {
this.renderingEnvelope = renderingEnvelope;
this.sourceCRS = CRS.getHorizontalCRS(sourceCRS);
this.targetCRS = renderingEnvelope.getCoordinateReferenceSystem();
this.validAreaBounds = validAreaBounds != null ? new ReferencedEnvelope(validAreaBounds,
DefaultGeographicCRS.WGS84) : null;
this.validArea = null;
this.validaAreaTester = null;
// query across dateline only in case of reprojection, Oracle won't use the spatial index
// with two or-ed bboxes and fixing the issue at the store level requires more
// time/resources than we presently have
this.queryAcrossDateline = !CRS.equalsIgnoreMetadata(sourceCRS,
renderingEnvelope.getCoordinateReferenceSystem());
checkReprojection();
}
/**
* Initializes a projection handler
*
* @param sourceCRS The source CRS
* @param validArea The valid area (used to cut geometries that go beyond it)
* @param renderingEnvelope The target rendering area and target CRS
*
* @throws FactoryException
*/
public ProjectionHandler(CoordinateReferenceSystem sourceCRS, Geometry validArea, ReferencedEnvelope renderingEnvelope) throws FactoryException {
if(validArea.isRectangle()) {
this.renderingEnvelope = renderingEnvelope;
this.sourceCRS = sourceCRS;
this.targetCRS = renderingEnvelope.getCoordinateReferenceSystem();
this.validAreaBounds = new ReferencedEnvelope(validArea.getEnvelopeInternal(),
DefaultGeographicCRS.WGS84);
this.validArea = null;
this.validaAreaTester = null;
} else {
this.renderingEnvelope = renderingEnvelope;
this.sourceCRS = sourceCRS;
this.targetCRS = renderingEnvelope.getCoordinateReferenceSystem();
this.validAreaBounds = new ReferencedEnvelope(validArea.getEnvelopeInternal(),
DefaultGeographicCRS.WGS84);
this.validArea = validArea;
this.validaAreaTester = PreparedGeometryFactory.prepare(validArea);
}
checkReprojection();
}
private void checkReprojection() throws FactoryException {
geometryCRS = CRS.getHorizontalCRS(sourceCRS);
noReprojection = geometryCRS == null
|| CRS.equalsIgnoreMetadata(geometryCRS, renderingEnvelope.getCoordinateReferenceSystem());
}
/**
* Returns the current rendering envelope
*/
public ReferencedEnvelope getRenderingEnvelope() {
return renderingEnvelope;
}
public CoordinateReferenceSystem getSourceCRS() {
return this.sourceCRS;
}
/**
* 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()
throws TransformException, FactoryException {
CoordinateReferenceSystem renderingCRS = renderingEnvelope.getCoordinateReferenceSystem();
if (!queryAcrossDateline) {
return Collections.singletonList(transformEnvelope(renderingEnvelope, sourceCRS));
}
if(renderingCRS instanceof GeographicCRS && !CRS.equalsIgnoreMetadata(renderingCRS, WGS84)) {
// special case, if we just transform the coordinates are going to be wrapped by the referencing
// subsystem directly
ReferencedEnvelope re = renderingEnvelope;
List<ReferencedEnvelope> envelopes = new ArrayList<ReferencedEnvelope>();
envelopes.add(re);
if (CRS.getAxisOrder(renderingCRS) == CRS.AxisOrder.NORTH_EAST) {
if (re.getMinY() >= -180.0 && re.getMaxY() <= 180) {
return Collections
.singletonList(transformEnvelope(renderingEnvelope, sourceCRS));
}
// 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
if (re.getMinY() < -180) {
envelopes.add(new ReferencedEnvelope(re.getMinX(), re.getMaxX(),
re.getMinY() + 360,
Math.min(re.getMaxY() + 360, 180), re.getCoordinateReferenceSystem()));
}
if (re.getMaxY() > 180) {
envelopes.add(new ReferencedEnvelope(re.getMinX(), re.getMaxX(),
Math.max(re.getMinY() - 360, -180), re.getMaxY() - 360,
re.getCoordinateReferenceSystem()));
}
} else {
if (re.getMinX() >= -180.0 && re.getMaxX() <= 180) {
return Collections
.singletonList(transformEnvelope(renderingEnvelope, sourceCRS));
}
// 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
if (re.getMinX() < -180) {
envelopes.add(new ReferencedEnvelope(re.getMinX() + 360,
Math.min(re.getMaxX() + 360, 180), re.getMinY(), re.getMaxY(),
re.getCoordinateReferenceSystem()));
}
if (re.getMaxX() > 180) {
envelopes.add(new ReferencedEnvelope(Math.max(re.getMinX() - 360, -180),
re.getMaxX() - 360, re.getMinY(), re.getMaxY(),
re.getCoordinateReferenceSystem()));
}
}
mergeEnvelopes(envelopes);
reprojectEnvelopes(sourceCRS, envelopes);
return envelopes;
} else {
if (!Double.isNaN(datelineX) && renderingEnvelope.getMinX() < datelineX
&& renderingEnvelope.getMaxX() > datelineX
&& renderingEnvelope.getWidth() < radius) {
double minX = renderingEnvelope.getMinX();
double minY = renderingEnvelope.getMinY();
double maxX = renderingEnvelope.getMaxX();
double maxY = renderingEnvelope.getMaxY();
ReferencedEnvelope re1 = new ReferencedEnvelope(minX, datelineX - EPS, minY,
maxY, renderingCRS);
List<ReferencedEnvelope> result = new ArrayList<ReferencedEnvelope>();
ReferencedEnvelope tx1 = transformEnvelope(re1, WGS84);
if(tx1 != null) {
tx1.expandToInclude(180, tx1.getMinY());
result.add(tx1);
}
ReferencedEnvelope re2 = new ReferencedEnvelope(datelineX + EPS, maxX, minY,
maxY, renderingCRS);
ReferencedEnvelope tx2 = transformEnvelope(re2, WGS84);
if(tx2 != null) {
if (tx2.getMinX() > 180) {
tx2.translate(-360, 0);
}
tx2.expandToInclude(-180, tx1.getMinY());
result.add(tx2);
}
mergeEnvelopes(result);
return result;
} else {
return getSourceEnvelopes(renderingEnvelope);
}
}
}
protected List<ReferencedEnvelope> getSourceEnvelopes(ReferencedEnvelope renderingEnvelope)
throws TransformException, FactoryException {
// check if we are crossing the dateline
ReferencedEnvelope re = transformEnvelope(renderingEnvelope, WGS84);
if(re == null) {
return Collections.emptyList();
}
if (re.getMinX() >= -180.0 && re.getMaxX() <= 180) {
final ReferencedEnvelope result = transformEnvelope(renderingEnvelope, sourceCRS);
if(result != null) {
return Collections.singletonList(result);
} else {
return Collections.emptyList();
}
}
// 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, Math.min(re.getMaxX() + 360, 180),
re.getMinY(), re.getMaxY(), re.getCoordinateReferenceSystem()));
}
if (re.getMaxX() > 180) {
envelopes.add(
new ReferencedEnvelope(Math.max(re.getMinX() - 360, -180), re.getMaxX() - 360,
re.getMinY(), re.getMaxY(), re.getCoordinateReferenceSystem()));
}
mergeEnvelopes(envelopes);
reprojectEnvelopes(sourceCRS, envelopes);
return envelopes.stream().filter(e -> e != null).collect(Collectors.toList());
}
protected ReferencedEnvelope transformEnvelope(ReferencedEnvelope envelope,
CoordinateReferenceSystem targetCRS) throws TransformException, FactoryException {
try {
ReferencedEnvelope transformed = envelope.transform(targetCRS, true, 10);
ProjectionHandler handler = ProjectionHandlerFinder.getHandler(new ReferencedEnvelope(targetCRS),
DefaultGeographicCRS.WGS84, true);
// does the target CRS have a strict notion of what's possible in terms of
// valid coordinate ranges?
if(handler == null || handler instanceof WrappingProjectionHandler) {
return transformed;
}
// if so, cut
final ReferencedEnvelope validAreaBounds = handler.getValidAreaBounds();
ReferencedEnvelope validArea = validAreaBounds.transform(targetCRS, true);
ReferencedEnvelope reduced = transformed.intersection(validArea);
if(reduced.isNull()) {
return null;
} else {
return reduced;
}
} catch (Exception e) {
LOGGER.fine("Failed to reproject the envelope " + envelope + " to " + targetCRS
+ " trying an area restriction");
ReferencedEnvelope envWGS84 = envelope.transform(DefaultGeographicCRS.WGS84, true);
// do we have restrictions on the target CRS?
ProjectionHandler handler = ProjectionHandlerFinder.getHandler(new ReferencedEnvelope(targetCRS),
DefaultGeographicCRS.WGS84, false);
if (handler != null && handler.validAreaBounds != null) {
ReferencedEnvelope validAreaBounds = handler.validAreaBounds;
envWGS84 = envWGS84.intersection(validAreaBounds);
}
// let's see if we can restrict the area we're reprojecting back using a projection
// handler for the source CRS
handler = ProjectionHandlerFinder.getHandler(envelope,
envelope.getCoordinateReferenceSystem(), false);
if (handler != null && handler.validAreaBounds != null) {
ReferencedEnvelope validAreaBounds = handler.validAreaBounds;
envWGS84 = envWGS84.intersection(validAreaBounds);
}
// try to reproject
if (envWGS84.isNull()) {
return null;
} else {
try {
return ReferencedEnvelope.reference(envWGS84)
.transform(targetCRS, true);
} catch (Exception e2) {
LOGGER.fine("Failed to reproject the restricted envelope " + envWGS84 + " to " + targetCRS);
}
}
// ok, let's see if we have an area of validity then
GeographicBoundingBox bbox = CRS.getGeographicBoundingBox(targetCRS);
if (bbox != null) {
ReferencedEnvelope restriction = new ReferencedEnvelope(
bbox.getEastBoundLongitude(), bbox.getWestBoundLongitude(),
bbox.getSouthBoundLatitude(), bbox.getNorthBoundLatitude(),
DefaultGeographicCRS.WGS84);
Envelope intersection = envWGS84.intersection(restriction);
if (intersection.isNull()) {
return null;
} else {
try {
return ReferencedEnvelope.reference(intersection)
.transform(targetCRS, true);
} catch (Exception e2) {
LOGGER.fine("Failed to reproject the restricted envelope " + intersection
+ " to " + targetCRS);
}
}
}
throw new TransformException("All attemptsto reproject the envelope " + envelope
+ " to " + targetCRS + " failed");
}
}
protected void reprojectEnvelopes(CoordinateReferenceSystem queryCRS,
List<ReferencedEnvelope> envelopes) throws TransformException, FactoryException {
// reproject the surviving envelopes
for (int i = 0; i < envelopes.size(); i++) {
final ReferencedEnvelope envelope = transformEnvelope(envelopes.get(i), queryCRS);
if(envelope != null) {
envelopes.set(i, envelope);
}
}
}
protected void mergeEnvelopes(List<ReferencedEnvelope> envelopes) {
// 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++;
}
}
}
}
}
/**
* Returns true if the geometry needs special handling
*/
public boolean requiresProcessing(Geometry geometry) {
// if there is no valid area, no cutting is required
if(validAreaBounds == null)
return false;
// if not reprojection is going on, we don't need to cut
if (noReprojection) {
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(Geometry geometry) throws TransformException, FactoryException {
// if there is no valid area, no cutting is required either
if(validAreaBounds == null)
return geometry;
// if not reprojection is going on, we don't need to cut
if(noReprojection) {
return geometry;
}
Geometry mask;
// fast path for the rectangular case, more complex one for the
// non rectangular one
ReferencedEnvelope ge = new ReferencedEnvelope(geometry.getEnvelopeInternal(), geometryCRS);
ReferencedEnvelope geWGS84 = ge.transform(WGS84, true);
// if the size of the envelope is less than 1 meter (1e-6 in degrees) expand it a bit
// to make intersection tests work
if (geWGS84.getWidth() < EPS || geWGS84.getHeight() < EPS) {
geWGS84.expandBy(EPS);
}
if(validArea == null) {
// if the geometry is within the valid area for this projection
// just skip expensive cutting
if (validAreaBounds.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(validAreaBounds.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(geometryCRS, true);
mask = JTS.toGeometry((Envelope) envInt);
} else {
// if the geometry is within the valid area for this projection
// just skip expensive cutting
if (validaAreaTester.contains(JTS.toGeometry(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(validAreaBounds.intersection(geWGS84), WGS84);
// if the intersection is empty the geometry is completely outside of the valid area, skip it
if(envIntWgs84.isEmpty()) {
return null;
}
Polygon polyIntWgs84 = JTS.toGeometry(envIntWgs84);
Geometry maskWgs84 = intersect(validArea, polyIntWgs84, geometryCRS);
if(maskWgs84 == null || maskWgs84.isEmpty()) {
return null;
}
mask = JTS.transform(maskWgs84, CRS.findMathTransform(WGS84, geometryCRS));
}
return intersect(geometry, mask, geometryCRS);
}
protected Geometry intersect(Geometry geometry, Geometry mask,
CoordinateReferenceSystem geometryCRS) {
// this seems to cause issues to JTS, reduce to
// single geometry when possible (http://jira.codehaus.org/browse/GEOS-6570)
if (geometry instanceof GeometryCollection) {
int numGeometries = geometry.getNumGeometries();
if (numGeometries == 1) {
geometry = geometry.getGeometryN(0);
} else {
// go piecewise, the JTS intersection can be pretty fragile in these cases
// and take a lot of time
List<Geometry> elements = new ArrayList<>();
String geometryType = numGeometries > 0 ? geometry.getGeometryN(0)
.getGeometryType() : null;
for (int i = 0; i < numGeometries; i++) {
Geometry g = geometry.getGeometryN(i);
if (g.getEnvelopeInternal().intersects(mask.getEnvelopeInternal())) {
Geometry intersected = intersect(g, mask, geometryCRS);
if (intersected != null) {
if (intersected.getGeometryType().equals(geometryType)) {
elements.add(intersected);
} else if (intersected instanceof GeometryCollection) {
addGeometries(elements, (GeometryCollection) intersected,
geometryType);
}
}
}
}
if (elements.size() == 0) {
return null;
}
if(geometry instanceof MultiPoint) {
Point[] array = elements.toArray(new Point[elements.size()]);
return geometry.getFactory().createMultiPoint(array);
} else if (geometry instanceof MultiLineString) {
LineString[] array = elements.toArray(new LineString[elements.size()]);
return geometry.getFactory().createMultiLineString(array);
} else if (geometry instanceof MultiPolygon) {
Polygon[] array = elements.toArray(new Polygon[elements.size()]);
return geometry.getFactory().createMultiPolygon(array);
} else {
Geometry[] array = elements.toArray(new Geometry[elements.size()]);
return geometry.getFactory().createGeometryCollection(array);
}
}
}
Geometry result = null;
try {
result = geometry.intersection(mask);
} catch(Exception e1) {
// try a precision reduction approach, starting from mm and scaling up to km
double precision;
if (CRS.getProjectedCRS(geometryCRS) != null) {
precision = 1e-3;
} else {
precision = 1e-3 / 100000; // 1 degree roughly 100km
}
// from mm to km
for (int i = 0; i < 6; i++) {
GeometryPrecisionReducer reducer = new GeometryPrecisionReducer(new PrecisionModel(
1 / precision));
Geometry reduced = reducer.reduce(geometry);
try {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(
Level.FINE,
"Failed to intersect the geometry with the projection area of "
+ "validity mask, trying a precision reduction approach with a precision of "
+ precision);
}
result = reduced.intersection(mask);
break;
} catch (Exception e3) {
precision *= 10;
}
}
if (result == null) {
LOGGER.log(Level.WARNING,
"Failed to intersect the geometry with the projection area of "
+ "validity mask, returning the original geometry: " + geometry);
result = geometry;
}
}
// workaround for a JTS bug, sometimes it returns empty results
// even if the two geometries are indeed intersecting
if (result.isEmpty() && geometry.intersects(mask)) {
try {
result = EnhancedPrecisionOp.intersection(geometry, mask);
} catch (Exception e2) {
result = geometry;
}
}
// handle in special way empty intersections
if (result.isEmpty()) {
return null;
} else {
return result;
}
}
/**
* Can modify/wrap the transform to handle specific projection issues
* @return
* @throws FactoryException
*/
public MathTransform getRenderingTransform(MathTransform mt) throws FactoryException {
List<MathTransform> elements = new ArrayList<MathTransform>();
accumulateTransforms(mt, elements);
List<MathTransform> wrapped = new ArrayList<MathTransform>();
List<MathTransform> datumShiftChain = null;
boolean datumShiftDetected = false;
for (MathTransform element : elements) {
if(datumShiftChain != null) {
datumShiftChain.add(element);
if(element.getClass().getName().equals(GeocentricTransform.class.getName() + "$Inverse")) {
datumShiftDetected = true;
MathTransform combined = concatenateTransforms(datumShiftChain);
GeographicOffsetWrapper wrapper = new GeographicOffsetWrapper(combined);
wrapped.add(wrapper);
datumShiftChain = null;
}
} else if(element instanceof GeocentricTransform) {
datumShiftChain = new ArrayList<MathTransform>();
datumShiftChain.add(element);
} else {
wrapped.add(element);
}
}
if(datumShiftDetected) {
if(datumShiftChain != null) {
wrapped.addAll(datumShiftChain);
}
return concatenateTransforms(wrapped);
} else {
return mt;
}
}
protected MathTransform concatenateTransforms(List<MathTransform> datumShiftChain) {
if (datumShiftChain.size() == 1) {
return datumShiftChain.get(0);
} else {
MathTransform mt = ConcatenatedTransform.create(datumShiftChain.get(0),
datumShiftChain.get(1));
for (int i = 2; i < datumShiftChain.size(); i++) {
MathTransform curr = datumShiftChain.get(i);
mt = ConcatenatedTransform.create(mt, curr);
}
return mt;
}
}
protected void accumulateTransforms(MathTransform mt, List<MathTransform> elements) {
if (mt instanceof ConcatenatedTransform) {
ConcatenatedTransform ct = (ConcatenatedTransform) mt;
accumulateTransforms(ct.transform1, elements);
accumulateTransforms(ct.transform2, elements);
} else {
elements.add(mt);
}
}
/**
* Processes the geometry already projected to the target SRS. May return null if the geometry
* is not to be drawn.
*
* @param mt optional reverse transformation to facilitate unwrapping
*/
public Geometry postProcess(MathTransform mt, Geometry geometry) {
return geometry;
}
/**
* Returns the area where the transformation from source to target is valid, expressed in the
* source coordinate reference system, or null if there is no limit
*
* @return
*/
public ReferencedEnvelope getValidAreaBounds() {
return validAreaBounds;
}
protected void setCentralMeridian(double centralMeridian) {
// compute the earth radius
try {
CoordinateReferenceSystem targetCRS = renderingEnvelope.getCoordinateReferenceSystem();
MathTransform mt = CRS.findMathTransform(WGS84, targetCRS, true);
double[] src = new double[] { centralMeridian, 0, 180 + centralMeridian, 0 };
double[] dst = new double[4];
mt.transform(src, 0, dst, 0, 2);
if (CRS.getAxisOrder(targetCRS) == CRS.AxisOrder.NORTH_EAST) {
radius = Math.abs(dst[3] - dst[1]);
} else {
radius = Math.abs(dst[2] - dst[0]);
}
if (radius <= 0) {
throw new RuntimeException("Computed Earth radius is 0, what is going on?");
}
} catch (Exception e) {
throw new RuntimeException("Unexpected error computing the Earth radius "
+ "in the current projection", e);
}
// compute the x of the dateline in the rendering CRS
try {
double[] ordinates = new double[] { 180, -80, 180, 80 };
MathTransform mt = CRS.findMathTransform(DefaultGeographicCRS.WGS84,
renderingEnvelope.getCoordinateReferenceSystem());
mt.transform(ordinates, 0, ordinates, 0, 2);
datelineX = ordinates[0];
} catch (Exception e) {
// should never happen...
throw new RuntimeException(e);
}
}
/**
* Private method for adding to the input List only the {@link Geometry} objects of the input {@link GeometryCollection} which belongs to the
* defined geometryType
*
* @param geoms
* @param geometryType
*/
protected void addGeometries(List<Geometry> geoms, GeometryCollection collection,
String geometryType) {
// Check if the list exists
if (geoms == null) {
return;
}
// Check the Geometry type
if (geometryType == null || geometryType.isEmpty()) {
return;
}
// Check the collection
if (collection == null || collection.getNumGeometries() <= 0) {
return;
}
// Get the number of Geometries
int numGeometries = collection.getNumGeometries();
// Cycle on the Geometries
for (int i = 0; i < numGeometries; i++) {
// get the Geometry
Geometry geo = collection.getGeometryN(i);
// If it belongs to the correct Geometry type, it is added to the Liats
if (geo.getGeometryType().equals(geometryType)) {
geoms.add(geo);
// Otherwise if it is a collection we try to iterate on it (recursion)
} else if (geo instanceof GeometryCollection) {
addGeometries(geoms, (GeometryCollection) geo, geometryType);
}
}
}
}