/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2012 - 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.junit.Assert.*; import java.io.InputStreamReader; import java.io.Reader; import java.util.List; import java.util.zip.GZIPInputStream; 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.projection.PolarStereographic; import org.geotools.referencing.operation.transform.IdentityTransform; import org.hamcrest.CoreMatchers; import org.junit.BeforeClass; import org.junit.Test; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.SingleCRS; import org.opengis.referencing.operation.MathTransform; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateFilter; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryComponentFilter; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.io.WKTReader; /** * * * @source $URL$ */ public class ProjectionHandlerTest { static final double EPS = 1e-5; static CoordinateReferenceSystem WGS84; static CoordinateReferenceSystem ED50_LATLON; static CoordinateReferenceSystem UTM32N; static CoordinateReferenceSystem MERCATOR; static CoordinateReferenceSystem MERCATOR_SHIFTED; static CoordinateReferenceSystem ED50; static CoordinateReferenceSystem OSM; @BeforeClass public static void setup() throws Exception { WGS84 = DefaultGeographicCRS.WGS84; UTM32N = CRS.decode("EPSG:32632", true); MERCATOR_SHIFTED = CRS.decode("EPSG:3349", true); MERCATOR = CRS.decode("EPSG:3395", true); OSM = CRS.decode("EPSG:3857", true); ED50 = CRS.decode("EPSG:4230", true); ED50_LATLON = CRS.decode("urn:x-ogc:def:crs:EPSG:4230", false); } @Test public void testWrappingOn3DCRS() throws Exception { CoordinateReferenceSystem crs = CRS.decode("EPSG:4939", true); SingleCRS hcrs = CRS.getHorizontalCRS(crs); ReferencedEnvelope wgs84Envelope = new ReferencedEnvelope(-190, 60, -90, 45, hcrs); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(wgs84Envelope, crs, true); assertNull(handler.validAreaBounds); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); ReferencedEnvelope expected = new ReferencedEnvelope(170, 180, -90, 45, hcrs); assertTrue(envelopes.remove(wgs84Envelope)); assertEquals(expected, envelopes.get(0)); } @Test public void testQueryWrappingWGS84() throws Exception { ReferencedEnvelope wgs84Envelope = new ReferencedEnvelope(-190, 60, -90, 45, WGS84); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(wgs84Envelope, WGS84, true); assertNull(handler.validAreaBounds); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); ReferencedEnvelope expected = new ReferencedEnvelope(170, 180, -90, 45, WGS84); assertTrue(envelopes.remove(wgs84Envelope)); assertEquals(expected, envelopes.get(0)); } @Test public void testQueryWrappingED50LatLon() throws Exception { ReferencedEnvelope envelope = new ReferencedEnvelope(-90, 45, -190, 60, ED50_LATLON); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(envelope, ED50_LATLON, true); assertNull(handler.validAreaBounds); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); ReferencedEnvelope expected = new ReferencedEnvelope(-90, 45, 170, 180, ED50_LATLON); assertTrue(envelopes.remove(envelope)); assertEquals(expected, envelopes.get(0)); } @Test public void testValidAreaMercator() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-180, 180, -89.9999, 89.9999, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR_SHIFTED, true); // check valid area ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); Envelope va = handler.validAreaBounds; assertNotNull(va); assertTrue(va.getMinX() <= -180.0); assertTrue(va.getMaxX() >= 180.0); assertTrue(-90 < va.getMinY()); assertTrue(90.0 > va.getMaxY()); } @Test public void testValidAreaLambertAzimuthalEqualArea() throws Exception { // check valid area for the north case ReferencedEnvelope wgs84north = new ReferencedEnvelope(-120, 0, 45, 90, WGS84); ReferencedEnvelope laeNorth = wgs84north.transform(CRS.decode("EPSG:3408"), true); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(laeNorth, WGS84, true); ReferencedEnvelope va = handler.validAreaBounds; assertNotNull(va); assertEquals(va.getCoordinateReferenceSystem(), WGS84); assertEquals(-180.0, va.getMinX(), 0d); assertEquals(180.0, va.getMaxX(), 0d); assertEquals(0, va.getMinY(), 0d); assertEquals(90, va.getMaxY(), 0d); // check the south case ReferencedEnvelope wgs84South = new ReferencedEnvelope(-120, 0, -90, -45, WGS84); ReferencedEnvelope laeSouth = wgs84South.transform(CRS.decode("EPSG:3409"), true); handler = ProjectionHandlerFinder.getHandler(laeSouth, WGS84, true); va = handler.validAreaBounds; assertNotNull(va); assertEquals(-180.0, va.getMinX(), 0d); assertEquals(180.0, va.getMaxX(), 0d); assertEquals(-90, va.getMinY(), 0d); assertEquals(0, va.getMaxY(), 0d); } @Test public void testValidAreaWorldVanDerGrinten() throws Exception { String wkt = "PROJCS[\"World_Van_der_Grinten_I\", \n" + " GEOGCS[\"GCS_WGS_1984\", \n" + " DATUM[\"D_WGS_1984\", \n" + " SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], \n" + " PRIMEM[\"Greenwich\", 0.0], \n" + " UNIT[\"degree\", 0.017453292519943295], \n" + " AXIS[\"Longitude\", EAST], \n" + " AXIS[\"Latitude\", NORTH]], \n" + " PROJECTION[\"World_Van_der_Grinten_I\"], \n" + " PARAMETER[\"central_meridian\", 0.0], \n" + " PARAMETER[\"false_easting\", 0.0], \n" + " PARAMETER[\"false_northing\", 0.0], \n" + " UNIT[\"m\", 1.0], \n" + " AXIS[\"x\", EAST], \n" + " AXIS[\"y\", NORTH], \n" + " AUTHORITY[\"EPSG\",\"54029\"]]"; // check valid area for the north case ReferencedEnvelope envelopeWgs84 = new ReferencedEnvelope(-180, 180, -90, 90, WGS84); ReferencedEnvelope envelope = envelopeWgs84.transform(CRS.parseWKT(wkt), true); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(envelope, WGS84, true); ReferencedEnvelope va = handler.validAreaBounds; assertNotNull(va); assertEquals(va.getCoordinateReferenceSystem(), WGS84); assertEquals(-Double.MAX_VALUE, va.getMinX(), 0d); assertEquals(Double.MAX_VALUE, va.getMaxX(), 0d); assertEquals(-90, va.getMinY(), 0d); assertEquals(90, va.getMaxY(), 0d); } @Test public void testValidAreaLambertConformal() throws Exception { // check valid area for the north case ReferencedEnvelope wgs84north = new ReferencedEnvelope(-120, 0, 45, 90, WGS84); ReferencedEnvelope laeNorth = wgs84north.transform(CRS.decode("EPSG:2062"), true); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(laeNorth, WGS84, true); Envelope va = handler.validAreaBounds; assertNotNull(va); assertEquals(-179.9, va.getMinX(), 0d); assertEquals(179.9, va.getMaxX(), 0d); assertEquals(-4, va.getMinY(), 0d); assertEquals(90, va.getMaxY(), 0d); // check the south case ReferencedEnvelope wgs84South = new ReferencedEnvelope(-180, -90, -40, 0, WGS84); ReferencedEnvelope laeSouth = wgs84South.transform(CRS.decode("EPSG:2194"), true); handler = ProjectionHandlerFinder.getHandler(laeSouth, WGS84, true); va = handler.validAreaBounds; assertNotNull(va); assertEquals(-180, va.getMinX(), 0d); assertEquals(180, va.getMaxX(), 0d); assertEquals(-90, va.getMinY(), 0d); assertEquals(29.73, va.getMaxY(), 0.01d); } @Test public void testCutGeometryLambertConformal() throws Exception { // get a lambert conformal conic with ReferencedEnvelope wgs84South = new ReferencedEnvelope(-180, -90, -40, 0, WGS84); ReferencedEnvelope laeSouth = wgs84South.transform(CRS.decode("EPSG:2194"), true); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(laeSouth, WGS84, true); // a Geometry that crosses the opposite of the central meridian Polygon geometry = JTS.toGeometry(new Envelope(5, 15, 0, 10)); Geometry preProcessed = handler.preProcess(geometry); assertTrue("Should have sliced the geometry in two parts", preProcessed instanceof MultiPolygon); } @Test public void testRobustCutting() throws Exception { String wkt = "PROJCS[\"Asia_South_Lambert_Conformal_Conic\", \n" + " GEOGCS[\"GCS_WGS_1984\", \n" + " DATUM[\"WGS_1984\", \n" + " SPHEROID[\"WGS_1984\", 6378137.0, 298.257223563]], \n" + " PRIMEM[\"Greenwich\", 0.0], \n" + " UNIT[\"degree\", 0.017453292519943295], \n" + " AXIS[\"Longitude\", EAST], \n" + " AXIS[\"Latitude\", NORTH]], \n" + " PROJECTION[\"Lambert_Conformal_Conic_2SP\"], \n" + " PARAMETER[\"central_meridian\", 125.0], \n" + " PARAMETER[\"latitude_of_origin\", -15.0], \n" + " PARAMETER[\"standard_parallel_1\", 7.0], \n" + " PARAMETER[\"false_easting\", 0.0], \n" + " PARAMETER[\"false_northing\", 0.0], \n" + " PARAMETER[\"scale_factor\", 1.0], \n" + " PARAMETER[\"standard_parallel_2\", -32.0], \n" + " UNIT[\"m\", 1.0], \n" + " AXIS[\"x\", EAST], \n" + " AXIS[\"y\", NORTH], \n" + " AUTHORITY[\"EPSG\",\"102030\"]]"; CoordinateReferenceSystem crs = CRS.parseWKT(wkt); Geometry geom; try (Reader reader = new InputStreamReader(new GZIPInputStream( ProjectionHandlerTest.class.getResourceAsStream("para.wkt.gz")))) { geom = new WKTReader().read(reader); } ReferencedEnvelope re = new ReferencedEnvelope(1.2248782489837505E7, 2.0320948299686E7, -4848266.752703998, 3223899.0571445003, crs); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(re, WGS84, true); // hard intersection, not even enhanced precision ops was able to make it Geometry preprocessed = handler.preProcess(geom); assertNotEquals(preprocessed, geom); } @Test public void testQueryWrappingMercatorWorld() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-200, 200, -89, 89, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR_SHIFTED, true); // get query area, we expect just one envelope spanning the whole world ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(1, envelopes.size()); ReferencedEnvelope env = envelopes.get(0); assertEquals(-180.0, env.getMinX(), EPS); assertEquals(180.0, env.getMaxX(), EPS); assertEquals(-89, env.getMinY(), 0.1); assertEquals(89.0, env.getMaxY(), 0.1); } @Test public void testQueryWrappingMercatorSeparate() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(160, 180, -40, 40, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR, true); // move it so that it crosses the dateline mercatorEnvelope.translate(mercatorEnvelope.getWidth() / 2, 0); // get query area, we expect two separate query envelopes ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); ReferencedEnvelope reOrig = envelopes.get(0); // original assertEquals(170.0, reOrig.getMinX(), EPS); assertEquals(180.0, reOrig.getMaxX(), EPS); ReferencedEnvelope reAdded = envelopes.get(1); // added assertEquals(-180.0, reAdded.getMinX(), EPS); assertEquals(-170.0, reAdded.getMaxX(), EPS); } @Test public void testQueryWrappingPacificMercator() throws Exception { // <wcs:DimensionTrim> // <wcs:Dimension>N</wcs:Dimension> // <wcs:TrimLow>0</wcs:TrimLow> // <wcs:TrimHigh>4838471</wcs:TrimHigh> // </wcs:DimensionTrim> // <wcs:DimensionTrim> // <wcs:Dimension>E</wcs:Dimension> // <wcs:TrimLow>1113195</wcs:TrimLow> // <wcs:TrimHigh>5565975</wcs:TrimHigh> // </wcs:DimensionTrim> // <wcs:format>image/tiff</wcs:format> // <wcs:Extension> // <!-- Mercator centered on 150°, request is roughly for long(160,-160),lat(0, 40)--> // <wcscrs:subsettingCrs>http://www.opengis.net/def/crs/EPSG/0/3832</wcscrs:subsettingCrs> // CoordinateReferenceSystem crs = CRS.decode("EPSG:3832"); ReferencedEnvelope mercatorEnvelope = new ReferencedEnvelope(1113195, 5565975, 0, 4838471, crs); // get query area, we expect two separate query envelopes across the dateline ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); ReferencedEnvelope reOrig = envelopes.get(0); assertEquals(160.0, reOrig.getMinX(), EPS); assertEquals(180.0, reOrig.getMaxX(), EPS); ReferencedEnvelope reAdded = envelopes.get(1); assertEquals(-180.0, reAdded.getMinX(), EPS); assertEquals(-160.0, reAdded.getMaxX(), EPS); } @Test public void testValidAreaUTM() throws Exception { ReferencedEnvelope wgs84Envelope = new ReferencedEnvelope(8, 10, 40, 45, WGS84); ReferencedEnvelope utmEnvelope = wgs84Envelope.transform(UTM32N, true); // check valid area ProjectionHandler handler = ProjectionHandlerFinder.getHandler(utmEnvelope, WGS84, true); Envelope va = handler.validAreaBounds; assertNotNull(va); assertTrue(9 - 90 < va.getMinX() && va.getMinX() <= 9 - 3); assertTrue(9 + 3 <= va.getMaxX() && va.getMaxX() < 9 + 90); assertEquals(-85, va.getMinY(), EPS); assertEquals(85.0, va.getMaxY(), EPS); } @Test public void testQueryUTM() throws Exception { ReferencedEnvelope wgs84Envelope = new ReferencedEnvelope(8, 10, 40, 45, WGS84); ReferencedEnvelope utmEnvelope = wgs84Envelope.transform(UTM32N, true); // get query area, we expect just one envelope, the original one ProjectionHandler handler = ProjectionHandlerFinder.getHandler(utmEnvelope, WGS84, true); ReferencedEnvelope expected = utmEnvelope.transform(WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(1, envelopes.size()); assertEquals(expected, envelopes.get(0)); } @Test public void testWrapGeometryMercator() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(160, 180, -40, 40, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR, true); // move it so that it crosses the dateline (measures are still accurate for something // crossing the dateline mercatorEnvelope.translate(mercatorEnvelope.getWidth() / 2, 0); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope Geometry g = new WKTReader().read("LINESTRING(170 -40, 190 40)"); // make sure the geometry is not wrapped ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); assertTrue(handler.requiresProcessing( g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(g, preProcessed); // transform and post process MathTransform mt = CRS.findMathTransform(WGS84, MERCATOR, true); Geometry transformed = JTS.transform(g, mt); Geometry postProcessed = handler.postProcess(mt.inverse(), transformed); Envelope env = postProcessed.getEnvelopeInternal(); // check the geometry is in the same area as the rendering envelope assertEquals(mercatorEnvelope.getMinX(), env.getMinX(), EPS); assertEquals(mercatorEnvelope.getMaxX(), env.getMaxX(), EPS); } @Test public void testWrapGeometrySmall() throws Exception { // projected dateline CRS CoordinateReferenceSystem FIJI = CRS.decode("EPSG:3460", true); // a small geometry that will cross the dateline Geometry g = new WKTReader().read("POLYGON ((2139122 5880020, 2139122 5880030, 2139922 5880030, 2139122 5880020))"); Geometry original = (Geometry) g.clone(); // rendering bounds only slightly bigger than geometry ReferencedEnvelope world = new ReferencedEnvelope(178, 181, -1, 1, WGS84); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, FIJI, true); assertTrue(handler.requiresProcessing( g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process MathTransform mt = CRS.findMathTransform(FIJI, WGS84); Geometry transformed = JTS.transform(g, mt); Geometry postProcessed = handler.postProcess(mt.inverse(), transformed); // check the geometry is in the same area as the rendering envelope assertTrue(world.contains(postProcessed.getEnvelopeInternal())); } @Test public void testWorldLargeGeometry() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-180, 180, -90, 90, WGS84); // a geometry close to the dateline Geometry g = new WKTReader() .read("POLYGON((-178 -90, -178 90, 178 90, 178 -90, -178 -90))"); Geometry original = new WKTReader() .read("POLYGON((-178 -90, -178 90, 178 90, 178 -90, -178 -90))"); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process (provide identity transform to force wrap heuristic) Geometry postProcessed = handler.postProcess(CRS.findMathTransform(WGS84, WGS84), g); // check the geometry is in the same area as the rendering envelope assertEquals(original, postProcessed); } @Test public void testWrapGeometryLatLonMultipleTimes() throws Exception { ReferencedEnvelope renderingEnvelope = new ReferencedEnvelope(-90, 90, -580, 540, ED50_LATLON); // a geometry close to the dateline Geometry g = new WKTReader() .read("POLYGON((-74 -33, -29 -33, -29 5, -74 5, -74 -33))"); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(renderingEnvelope, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); MathTransform mt = handler.getRenderingTransform(CRS.findMathTransform(WGS84, ED50_LATLON)); Geometry transformed = JTS.transform(preProcessed, mt); // post process (provide identity transform to force wrap heuristic) Geometry postProcessed = handler.postProcess(mt, transformed); assertTrue(postProcessed.isValid()); // should have been replicated three times assertEquals(3, postProcessed.getNumGeometries()); } @Test public void testWrapGeometryReprojectToLatLonED50() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-80, 80, -180, 180, ED50_LATLON); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope (with wgs84 lon/latcoordinates) String wkt = "POLYGON((178 -80, 178 80, 182 80, 182 80, 178 -80))"; Geometry g = new WKTReader().read(wkt); Geometry original = new WKTReader().read(wkt); MathTransform mt = CRS.findMathTransform(WGS84, ED50_LATLON); MathTransform prepared = handler.getRenderingTransform(CRS.findMathTransform(WGS84, ED50_LATLON)); Geometry reprojected = JTS.transform(original, prepared); assertTrue(handler.requiresProcessing( g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process, this should wrap the geometry and clone it Geometry postProcessed = handler.postProcess(prepared, reprojected); assertTrue(postProcessed instanceof MultiPolygon); } @Test public void testWrapAnctartica() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-80, 80, -180, 180, ED50_LATLON); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope (with wgs84 lon/latcoordinates) String wkt = "POLYGON((180 -90, 180 90, -180 90, -180 -90, 180 -90))"; Geometry g = new WKTReader().read(wkt); MathTransform mt = CRS.findMathTransform(WGS84, ED50_LATLON); MathTransform prepared = handler.getRenderingTransform(mt); assertTrue(handler.requiresProcessing( g)); Geometry preProcessed = handler.preProcess(g); Geometry reprojected = JTS.transform(preProcessed, prepared); assertTrue(reprojected.isValid()); reprojected.apply(new CoordinateFilter() { @Override public void filter(Coordinate coord) { assertEquals(90.0, Math.abs(coord.getOrdinate(0)), 0.1); assertEquals(180.0, Math.abs(coord.getOrdinate(1)), 5); } }); // post process, this should wrap the geometry, make sure it's valid, and avoid large jumps in its border Geometry postProcessed = handler.postProcess(prepared, reprojected); assertTrue(postProcessed instanceof MultiPolygon); assertEquals(2, postProcessed.getNumGeometries()); } @Test public void testWrapGeometryReprojectToED50() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-80, 80, -180, 180, ED50); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope (with wgs84 lon/latcoordinates) String wkt = "POLYGON((178 -80, 178 80, 182 80, 182 80, 178 -80))"; Geometry g = new WKTReader().read(wkt); Geometry original = new WKTReader().read(wkt); MathTransform mt = CRS.findMathTransform(WGS84, ED50); mt = handler.getRenderingTransform(mt); Geometry reprojected = JTS.transform(original, mt); // make sure the geometry is not wrapped, but it is preserved assertTrue(handler.requiresProcessing( g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process, this should wrap the geometry and clone it Geometry postProcessed = handler.postProcess(mt, reprojected); assertTrue(postProcessed instanceof MultiPolygon); } @Test public void testWrapJumpLast() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-180, 180, -90, 90, WGS84); Geometry g = new WKTReader().read("POLYGON((-131 -73.5,0 -90,163 -60,174 -60,-131 -73.5))"); Geometry original = new WKTReader().read("POLYGON((-131 -73.5,0 -90,163 -60,174 -60,-131 -73.5))"); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process (provide identity transform to force wrap heuristic) Geometry postProcessed = handler.postProcess(CRS.findMathTransform(WGS84, WGS84), g); // check the geometry is in the same area as the rendering envelope assertEquals(original, postProcessed); } @Test public void testWrapGeometryWGS84Duplicate() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-200, 200, -90, 90, WGS84); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope Geometry g = new WKTReader().read("POLYGON((-178 -90, -178 90, 178 90, 178 -90, -178 -90))"); Geometry original = new WKTReader().read("POLYGON((-178 -90, -178 90, 178 90, 178 -90, -178 -90))"); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process Geometry postProcessed = handler.postProcess(null, g); // check we have replicated the geometry in both directions Envelope ppEnvelope = postProcessed.getEnvelopeInternal(); Envelope expected = new Envelope(-538, 538, -90, 90); assertEquals(expected, ppEnvelope); } @Test public void testDuplicateGeometryMercator() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(-180, 180, -50, 50, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR, true); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope Geometry g = new WKTReader().read("LINESTRING(170 -50, 190 50)"); // make sure the geometry is not wrapped ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(g, preProcessed); // transform and post process MathTransform mt = CRS.findMathTransform(WGS84, MERCATOR, true); Geometry transformed = JTS.transform(g, mt); Geometry postProcessed = handler.postProcess(mt, transformed); // should have been duplicated in two parts assertTrue(postProcessed instanceof MultiLineString); MultiLineString mls = (MultiLineString) postProcessed; assertEquals(2, mls.getNumGeometries()); // the two geometries width should be the same as 20° double twentyDegWidth = mercatorEnvelope.getWidth() / 18; assertEquals(twentyDegWidth, mls.getGeometryN(0).getEnvelopeInternal().getWidth(), EPS); assertEquals(twentyDegWidth, mls.getGeometryN(1).getEnvelopeInternal().getWidth(), EPS); } @Test public void testLimitExcessiveDuplication() throws Exception { // a veeeery large rendering envelope, enough to trigger whatever are the default // protection limits (yes, it's in degrees!) ReferencedEnvelope renderingEnvelope = new ReferencedEnvelope(-1800000, 1800000, -50, 50, WGS84); // the geometry that will be wrapped Geometry g = new WKTReader().read("LINESTRING(-179 -89, 179 89)"); // make sure the geometry is not pre-processed ProjectionHandler handler = ProjectionHandlerFinder.getHandler(renderingEnvelope, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); assertEquals(g, preProcessed); Geometry postProcessed = handler.postProcess(IdentityTransform.create(2), g); // should have been copied several times, but not above the limit assertTrue(postProcessed instanceof MultiLineString); MultiLineString mls = (MultiLineString) postProcessed; assertEquals(ProjectionHandlerFinder.WRAP_LIMIT * 2 + 1 , mls.getNumGeometries()); } public void testCutGeometryUTM() throws Exception { ReferencedEnvelope wgs84Envelope = new ReferencedEnvelope(8, 10, 40, 45, WGS84); ReferencedEnvelope utmEnvelope = wgs84Envelope.transform(UTM32N, true); // a geometry that will definitely go outside of the UTM32N valid area Geometry g = new WKTReader().read("LINESTRING(-170 -40, 170, 40)"); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(utmEnvelope, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); assertTrue(!preProcessed.equalsTopo(g)); assertTrue(handler.validAreaBounds.contains(preProcessed.getEnvelopeInternal())); } @Test public void testPolarStereographic() throws Exception { ReferencedEnvelope envelope = new ReferencedEnvelope(-10700000, 14700000, -10700000, 14700000, CRS.decode("EPSG:5041", true)); ProjectionHandler handler = ProjectionHandlerFinder.getHandler(envelope, WGS84, true); assertNotNull(handler); assertEquals(envelope, handler.getRenderingEnvelope()); assertTrue(CRS.getMapProjection(envelope.getCoordinateReferenceSystem()) instanceof PolarStereographic); } @Test public void testSkipInvalidGeometries() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(160, 180, -40, 40, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR, true); // move it so that it crosses the dateline (measures are still accurate for something // crossing the dateline mercatorEnvelope.translate(mercatorEnvelope.getWidth() / 2, 0); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope Geometry g1 = new WKTReader() .read("POLYGON((150 40, 150 -90, 190 -90, 190 40, 175 40, 175 -87, 165 -87, 165 40, 150 40))"); Geometry g2 = new WKTReader() .read("POLYGON((-178 -90, -178 90, 178 90, 178 -90, -178 -90))"); // MultiPolygon containing both geometries Geometry collection = new MultiPolygon(new Polygon[] { (Polygon) g1, (Polygon) g2 }, g1.getFactory()); // make sure the geometry is not wrapped ProjectionHandler handler = ProjectionHandlerFinder.getHandler(mercatorEnvelope, WGS84, true); assertTrue(handler.requiresProcessing(collection)); Geometry preProcessed = handler.preProcess(collection); // Ensure something has changed assertNotEquals(collection, preProcessed); // Ensure the result is a Geometry collection which does not accept Geometries of the same type of the Projection Handler one. assertNotNull(preProcessed); assertTrue(preProcessed instanceof GeometryCollection); int numGeometries = preProcessed.getNumGeometries(); assertEquals(numGeometries, 3); for (int i = 0; i < numGeometries; i++) { assertTrue(preProcessed.getGeometryN(i) instanceof Polygon); } } @Test public void testSkipEmptyGeometryCollections() throws Exception { ReferencedEnvelope world = new ReferencedEnvelope(160, 180, -40, 40, WGS84); ReferencedEnvelope mercatorEnvelope = world.transform(MERCATOR, true); // move it so that it crosses the dateline (measures are still accurate for something // crossing the dateline mercatorEnvelope.translate(mercatorEnvelope.getWidth() / 2, 0); // a geometry that will cross the dateline and sitting in the same area as the // rendering envelope Geometry g1 = new WKTReader() .read("POLYGON((150 40, 150 -90, 190 -90, 190 40, 175 40, 175 -87, 165 -87, 165 40, 150 40))"); // Empty geometry collection Geometry collection = new GeometryCollection(null, g1.getFactory()); // make sure the geometry is not wrapped ProjectionHandler handler = new ProjectionHandler(WGS84, new Envelope(-0.5d, 2.0d, -0.5d, 2.0d), mercatorEnvelope); assertTrue(handler.requiresProcessing(collection)); Geometry preProcessed = handler.preProcess(collection); // Ensure something has changed assertNotEquals(collection, preProcessed); // Ensure the result is null assertNull(preProcessed); } @Test public void testQueryEnvelopesNonWrappingWGS84() throws Exception { // dateline crossing request ReferencedEnvelope request = new ReferencedEnvelope(170, 190, -40, 40, WGS84); // grab a non wrapping handler ProjectionHandler handler = ProjectionHandlerFinder.getHandler(request, WGS84, false); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(1, envelopes.size()); assertEquals(request, envelopes.get(0)); } @Test public void testQueryEnvelopesNonWrapping3857() throws Exception { // dateline crossing request ReferencedEnvelope requestWgs84 = new ReferencedEnvelope(170, 190, -40, 40, WGS84); ReferencedEnvelope request = requestWgs84.transform(OSM, true); // grab a non wrapping handler ProjectionHandler handler = ProjectionHandlerFinder.getHandler(request, WGS84, false); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(1, envelopes.size()); assertEnvelopesEqual(requestWgs84, envelopes.get(0), EPS); } private void assertEnvelopesEqual(ReferencedEnvelope re1, ReferencedEnvelope re2, double eps) { assertEquals(re1.getCoordinateReferenceSystem(), re2.getCoordinateReferenceSystem()); assertEquals(re1.getMinX(), re2.getMinX(), eps); assertEquals(re1.getMinY(), re2.getMinY(), eps); assertEquals(re1.getMaxX(), re2.getMaxX(), eps); assertEquals(re1.getMaxY(), re2.getMaxY(), eps); } @Test public void testQueryEnvelopesWrappingWGS84() throws Exception { // dateline crossing request ReferencedEnvelope request = new ReferencedEnvelope(170, 190, -40, 40, WGS84); // grab a non wrapping handler ProjectionHandler handler = ProjectionHandlerFinder.getHandler(request, WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); assertTrue(envelopes.contains(new ReferencedEnvelope(170, 180, -40, 40, WGS84))); assertTrue(envelopes.contains(new ReferencedEnvelope(-180, -170, -40, 40, WGS84))); } @Test public void testQueryEnvelopesWrapping3857() throws Exception { // dateline crossing request ReferencedEnvelope requestWgs84 = new ReferencedEnvelope(170, 190, -40, 40, WGS84); ReferencedEnvelope request = requestWgs84.transform(OSM, true); // grab a non wrapping handler ProjectionHandler handler = ProjectionHandlerFinder.getHandler(request, WGS84, true); List<ReferencedEnvelope> envelopes = handler.getQueryEnvelopes(); assertEquals(2, envelopes.size()); assertEnvelopesEqual(new ReferencedEnvelope(170, 180, -40, 40, WGS84), envelopes.get(0), EPS); assertEnvelopesEqual(new ReferencedEnvelope(-180, -170, -40, 40, WGS84), envelopes.get(1), EPS); } @Test public void testWorldMeridian() throws Exception { ReferencedEnvelope requestWgs84 = new ReferencedEnvelope(-180, 180, -85, 85, WGS84); ReferencedEnvelope requestWebMercator = requestWgs84.transform(OSM, true); // a geometry close to the dateline Geometry g = new WKTReader().read("LINESTRING(0 -90, 0 90)"); Geometry expected = new WKTReader().read("LINESTRING(0 -85, 0 85)"); // make sure the geometry is not wrapped, but it is preserved ProjectionHandler handler = ProjectionHandlerFinder.getHandler(requestWebMercator, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // should have cut at 85 degrees, the web mercator breaks at the poles assertEquals(expected, preProcessed); // post process (provide identity transform to force wrap heuristic) Geometry postProcessed = handler.postProcess(CRS.findMathTransform(WGS84, WGS84), expected); // check the geometry is in the same area as the rendering envelope assertEquals(expected, postProcessed); } @Test public void testWrapPDCMercator() throws Exception { CoordinateReferenceSystem pdc = CRS.decode("EPSG:3832", true); ReferencedEnvelope world = new ReferencedEnvelope(-20000000, 20000000, -20000000, 20000000, pdc); Geometry g = new WKTReader().read( "MULTIPOLYGON(((-73 60, -73 83, -11 83, -11 60, -73 60)),((-10 60, -10 61, -11 61, -11 60, -10 60)))"); Geometry original = (Geometry) g.clone(); // ProjectionHandler handler = ProjectionHandlerFinder.getHandler(world, WGS84, true); assertTrue(handler.requiresProcessing(g)); Geometry preProcessed = handler.preProcess(g); // no cutting expected assertEquals(original, preProcessed); // post process (provide identity transform to force wrap heuristic) MathTransform mt = CRS.findMathTransform(WGS84, pdc, true); Geometry transformed = JTS.transform(g, mt); final Geometry postProcessed = handler.postProcess(mt, transformed); // make sure we got the geometry unwrapped and replicated assertEquals(3, postProcessed.getNumGeometries()); postProcessed.apply(new GeometryComponentFilter() { @Override public void filter(Geometry geom) { if(geom != postProcessed && geom.getEnvelopeInternal().getWidth() > 40000000) { fail("The geometry did not get rewrapped properly"); } } }); } @Test public void testReprojectBackwardsTo900913() throws Exception { // use a WKT in order to miss the EPSG database support String wkt = "PROJCS[\"WGS84 / Google Mercator\", GEOGCS[\"WGS 84\", DATUM[\"World Geodetic System 1984\", " + "SPHEROID[\"WGS 84\", 6378137.0, 298.257223563, AUTHORITY[\"EPSG\",\"7030\"]], AUTHORITY[\"EPSG\",\"6326\"]], " + "PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]], UNIT[\"degree\", 0.017453292519943295], AUTHORITY[\"EPSG\",\"4326\"]], " + "PROJECTION[\"Mercator (1SP)\", AUTHORITY[\"EPSG\",\"9804\"]], PARAMETER[\"semi_major\", 6378137.0], PARAMETER[\"semi_minor\", 6378137.0], " + "PARAMETER[\"latitude_of_origin\", 0.0], PARAMETER[\"central_meridian\", 0.0], PARAMETER[\"scale_factor\", 1.0], " + "PARAMETER[\"false_easting\", 0.0], PARAMETER[\"false_northing\", 0.0], UNIT[\"m\", 1.0], AUTHORITY[\"EPSG\",\"900913\"]]"; CoordinateReferenceSystem epsg900913 = CRS.parseWKT(wkt); // assume we are rendering in WGS84 ReferencedEnvelope renderingArea = new ReferencedEnvelope(-180, 0, 0, 90, DefaultGeographicCRS.WGS84); ProjectionHandler ph = ProjectionHandlerFinder.getHandler(renderingArea, epsg900913, true); List<ReferencedEnvelope> queryEnvelopes = ph.getQueryEnvelopes(); assertEquals(1, queryEnvelopes.size()); // the expected query envelope ReferencedEnvelope expected = new ReferencedEnvelope(-180, 0, 0, 85, DefaultGeographicCRS.WGS84).transform(epsg900913, true); assertEquals(expected, queryEnvelopes.get(0)); } @Test public void testQueryEnvelopeOnInvalidArea() throws Exception { // and Envelope that does not make sense for EPSG:3003, too far away from central meridian ReferencedEnvelope re = new ReferencedEnvelope(-130, -120, -40, 30, DefaultGeographicCRS.WGS84); ProjectionHandler ph = ProjectionHandlerFinder.getHandler(re, CRS.decode("EPSG:3003", true), true); List<ReferencedEnvelope> queryEnvelopes = ph.getQueryEnvelopes(); assertEquals(0, queryEnvelopes.size()); } @Test public void testQueryEnvelopeOnInvalidArea2() throws Exception { // and Envelope that does not make sense for EPSG:3003, too far away from central meridian ReferencedEnvelope re = new ReferencedEnvelope(-130, -120, -40, 30, DefaultGeographicCRS.WGS84); ReferencedEnvelope re3857 = re.transform(CRS.decode("EPSG:3857", true), true); ProjectionHandler ph = ProjectionHandlerFinder.getHandler(re3857, CRS.decode("EPSG:3003", true), true); List<ReferencedEnvelope> queryEnvelopes = ph.getQueryEnvelopes(); assertEquals(0, queryEnvelopes.size()); } }