You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by js...@apache.org on 2019/01/08 14:06:21 UTC
[sis] branch geoapi-4.0 updated: Geometry : add JTS geometry
transformation operations
This is an automated email from the ASF dual-hosted git repository.
jsorel pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
new 7f4d477 Geometry : add JTS geometry transformation operations
7f4d477 is described below
commit 7f4d477b80b44cf3e249d4b677d16c55f16b8f74
Author: jsorel <jo...@geomatys.com>
AuthorDate: Tue Jan 8 16:05:53 2019 +0100
Geometry : add JTS geometry transformation operations
---
.../java/org/apache/sis/internal/feature/JTS.java | 85 ++++++++++-
.../feature/jts/GeometryCoordinateTransform.java | 82 +++++++++++
.../internal/feature/jts/GeometryTransform.java | 161 +++++++++++++++++++++
.../org/apache/sis/internal/feature/JTSTest.java | 45 ++++++
4 files changed, 372 insertions(+), 1 deletion(-)
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
index af3a6bb..0471784 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/JTS.java
@@ -32,11 +32,17 @@ import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.ParseException;
import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.internal.feature.jts.GeometryCoordinateTransform;
import org.apache.sis.setup.GeometryLibrary;
import org.apache.sis.math.Vector;
import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
+import org.apache.sis.util.Utilities;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.CoordinateOperation;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
@@ -269,7 +275,7 @@ public final class JTS extends Geometries<Geometry> {
* <ul>
* <li>Geometry UserData value is a CoordinateReferenceSystem</li>
* <li>Geometry UserData value is a Map with a value for key 'crs'</li>
- * <li>Geometry SRID if positive, interpreted as an EPSG code</li>
+ * <li>Geometry SRID is positive, interpreted as an EPSG code</li>
* </ul>
* <p>
* If none of the above is valid, null is returned.
@@ -299,4 +305,81 @@ public final class JTS extends Geometries<Geometry> {
return null;
}
+ /**
+ * Transform given geometry to a new CoordinateReferenceSystem.
+ *
+ * <p>
+ * If CoordinateReferenceSystem of geometry is null, geometry is returned unchanged.
+ * </p>
+ * <p>
+ * If geometry has no CoordinateReferenceSystem a TransformException is throwed.
+ * </p>
+ *
+ * @param geometry source geometry
+ * @param targetCrs target CoordinateReferenceSystem
+ * @return transformed geometry, or same geometry if it is already in target crs
+ * @throws org.opengis.referencing.operation.TransformException if geometry
+ * has no CRS or failed to apply transform.
+ * @throws org.opengis.util.FactoryException
+ */
+ public static Geometry transform(Geometry geometry, CoordinateReferenceSystem targetCrs)
+ throws TransformException, FactoryException {
+ if (geometry == null || targetCrs == null) {
+ return geometry;
+ }
+
+ final CoordinateReferenceSystem sourceCrs = findCoordinateReferenceSystem(geometry);
+ if (sourceCrs == null) {
+ throw new TransformException("Geometry CRS is undefined");
+ } else if (Utilities.equalsIgnoreMetadata(sourceCrs, targetCrs)) {
+ return geometry;
+ }
+ return transform(geometry, CRS.findOperation(sourceCrs, targetCrs, null));
+ }
+
+ /**
+ * Transform geometry, result is a new geometry.
+ *
+ * <p>
+ * If geometry or operation is null, geometry is returned unchanged.
+ * </p>
+ *
+ * TODO : handle antemeridian cases.
+ *
+ * @param geometry
+ * @param operation
+ * @return
+ * @throws TransformException if transformation failed
+ */
+ public static Geometry transform(Geometry geometry, CoordinateOperation operation) throws TransformException {
+ if (geometry == null || operation == null) {
+ return geometry;
+ }
+ geometry = transform(geometry, operation.getMathTransform());
+ geometry.setUserData(operation.getTargetCRS());
+ return geometry;
+ }
+
+ /**
+ * Transform geometry, result is a new geometry.
+ *
+ * <p>
+ * If geometry or transform is null, geometry is returned unchanged.
+ * </p>
+ *
+ * @param geometry
+ * @param transform
+ * @return transformed geometry or null.
+ * @throws TransformException if transformation failed
+ */
+ public static Geometry transform(Geometry geometry, MathTransform transform) throws TransformException {
+ if (geometry == null || transform == null) {
+ return geometry;
+ }
+ final GeometryCoordinateTransform gct = new GeometryCoordinateTransform(transform, geometry.getFactory());
+ final Geometry result = gct.transform(geometry);
+ result.setSRID(geometry.getSRID());
+ result.setUserData(geometry.getUserData());
+ return result;
+ }
}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryCoordinateTransform.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryCoordinateTransform.java
new file mode 100644
index 0000000..fa9307d
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryCoordinateTransform.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature.jts;
+
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequenceFactory;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * This implementation use a MathTransform to transform each coordinate of
+ * the geometry.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public class GeometryCoordinateTransform extends GeometryTransform {
+
+ private final MathTransform transform;
+
+ public GeometryCoordinateTransform(MathTransform transform) {
+ this.transform = transform;
+ }
+
+ public GeometryCoordinateTransform(MathTransform transform, final CoordinateSequenceFactory csf) {
+ super(csf);
+ this.transform = transform;
+ }
+
+ public GeometryCoordinateTransform(MathTransform transform, final GeometryFactory gf) {
+ super(gf);
+ this.transform = transform;
+ }
+
+ @Override
+ protected CoordinateSequence transform(CoordinateSequence in, int minpoints) throws TransformException {
+ final int dim = in.getDimension();
+ final int size = in.size();
+ final CoordinateSequence out = csf.create(size, dim);
+
+ final double[] val = new double[dim];
+ for (int i = 0; i<size; i++) {
+ switch (dim) {
+ case 3 :
+ val[0] = in.getOrdinate(i, 0);
+ val[1] = in.getOrdinate(i, 1);
+ val[2] = in.getOrdinate(i, 2);
+ transform.transform(val, 0, val, 0, 1);
+ out.setOrdinate(i, 0, val[0]);
+ out.setOrdinate(i, 1, val[1]);
+ out.setOrdinate(i, 2, val[2]);
+ break;
+ default :
+ val[0] = in.getOrdinate(i, 0);
+ val[1] = in.getOrdinate(i, 1);
+ transform.transform(val, 0, val, 0, 1);
+ out.setOrdinate(i, 0, val[0]);
+ out.setOrdinate(i, 1, val[1]);
+ break;
+ }
+ }
+ return out;
+ }
+
+}
diff --git a/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryTransform.java b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryTransform.java
new file mode 100644
index 0000000..9c0be5c
--- /dev/null
+++ b/core/sis-feature/src/main/java/org/apache/sis/internal/feature/jts/GeometryTransform.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.feature.jts;
+
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequenceFactory;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryCollection;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
+import org.locationtech.jts.geom.LinearRing;
+import org.locationtech.jts.geom.MultiLineString;
+import org.locationtech.jts.geom.MultiPoint;
+import org.locationtech.jts.geom.MultiPolygon;
+import org.locationtech.jts.geom.Point;
+import org.locationtech.jts.geom.Polygon;
+import org.opengis.referencing.operation.TransformException;
+
+/**
+ * Abstract class parent to all JTS geometry transformations.
+ * This class decompose the geometry to it's most primitive element, the
+ * CoordinateSequence, then rebuild the geometry.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @version 1.0
+ * @since 1.0
+ * @module
+ */
+public abstract class GeometryTransform {
+
+ protected final GeometryFactory gf;
+ protected final CoordinateSequenceFactory csf;
+
+ public GeometryTransform() {
+ this((CoordinateSequenceFactory) null);
+ }
+
+ public GeometryTransform(final CoordinateSequenceFactory csf) {
+ if (csf == null) {
+ this.gf = new GeometryFactory();
+ this.csf = gf.getCoordinateSequenceFactory();
+ } else {
+ this.csf = csf;
+ this.gf = new GeometryFactory(csf);
+ }
+ }
+
+ public GeometryTransform(final GeometryFactory gf) {
+ if (gf == null) {
+ this.gf = new GeometryFactory();
+ this.csf = gf.getCoordinateSequenceFactory();
+ } else {
+ this.csf = gf.getCoordinateSequenceFactory();
+ this.gf = gf;
+ }
+ }
+
+ public Geometry transform(final Geometry geom) throws TransformException {
+ if (geom instanceof Point) {
+ return transform((Point) geom);
+ } else if (geom instanceof MultiPoint) {
+ return transform((MultiPoint) geom);
+ } else if (geom instanceof LineString) {
+ return transform((LineString) geom);
+ } else if (geom instanceof LinearRing) {
+ return transform((LinearRing) geom);
+ } else if (geom instanceof MultiLineString) {
+ return transform((MultiLineString) geom);
+ } else if (geom instanceof Polygon) {
+ return transform((Polygon) geom);
+ } else if (geom instanceof MultiPolygon) {
+ return transform((MultiPolygon) geom);
+ } else if (geom instanceof GeometryCollection) {
+ return transform((GeometryCollection) geom);
+ } else {
+ throw new IllegalArgumentException("Geometry type is unknowed or null : " + geom);
+ }
+ }
+
+ protected Point transform(final Point geom) throws TransformException {
+ final CoordinateSequence coord = geom.getCoordinateSequence();
+ return gf.createPoint(transform(coord, 1));
+ }
+
+ protected MultiPoint transform(final MultiPoint geom) throws TransformException {
+ final int nbGeom = geom.getNumGeometries();
+
+ final Point[] subs = new Point[geom.getNumGeometries()];
+ for (int i = 0; i < nbGeom; i++) {
+ subs[i] = transform((Point) geom.getGeometryN(i));
+ }
+ return gf.createMultiPoint(subs);
+ }
+
+ protected LineString transform(final LineString geom) throws TransformException {
+ final CoordinateSequence seq = transform(geom.getCoordinateSequence(), 2);
+ return gf.createLineString(seq);
+ }
+
+ protected LinearRing transform(final LinearRing geom) throws TransformException {
+ final CoordinateSequence seq = transform(geom.getCoordinateSequence(), 4);
+ return gf.createLinearRing(seq);
+ }
+
+ protected MultiLineString transform(final MultiLineString geom) throws TransformException {
+ final LineString[] subs = new LineString[geom.getNumGeometries()];
+ for (int i = 0; i < subs.length; i++) {
+ subs[i] = transform((LineString) geom.getGeometryN(i));
+ }
+ return gf.createMultiLineString(subs);
+ }
+
+ protected Polygon transform(final Polygon geom) throws TransformException {
+ final LinearRing exterior = transform((LinearRing) geom.getExteriorRing());
+ final LinearRing[] holes = new LinearRing[geom.getNumInteriorRing()];
+ for (int i = 0; i < holes.length; i++) {
+ holes[i] = transform((LinearRing) geom.getInteriorRingN(i));
+ }
+ return gf.createPolygon(exterior, holes);
+ }
+
+ protected MultiPolygon transform(final MultiPolygon geom) throws TransformException {
+ final Polygon[] subs = new Polygon[geom.getNumGeometries()];
+ for (int i = 0; i < subs.length; i++) {
+ subs[i] = transform((Polygon) geom.getGeometryN(i));
+ }
+ return gf.createMultiPolygon(subs);
+ }
+
+ protected GeometryCollection transform(final GeometryCollection geom) throws TransformException {
+ final Geometry[] subs = new Geometry[geom.getNumGeometries()];
+ for (int i = 0; i < subs.length; i++) {
+ subs[i] = transform(geom.getGeometryN(i));
+ }
+ return gf.createGeometryCollection(subs);
+ }
+
+ /**
+ *
+ * @param sequence Sequence to transform
+ * @param minpoints Minimum number of point to preserve
+ * @return transformed sequence
+ * @throws TransformException
+ */
+ protected abstract CoordinateSequence transform(CoordinateSequence sequence, int minpoints) throws TransformException;
+
+}
diff --git a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
index dbfb7fd..c26f1b6 100644
--- a/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
+++ b/core/sis-feature/src/test/java/org/apache/sis/internal/feature/JTSTest.java
@@ -16,7 +16,11 @@
*/
package org.apache.sis.internal.feature;
+import java.awt.geom.AffineTransform;
import java.util.Collections;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.referencing.CommonCRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
@@ -27,6 +31,8 @@ import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.util.FactoryException;
import static org.junit.Assert.*;
+import org.locationtech.jts.geom.Point;
+import org.opengis.referencing.operation.TransformException;
/**
@@ -38,6 +44,9 @@ import static org.junit.Assert.*;
* @module
*/
public final strictfp class JTSTest extends GeometriesTestCase {
+
+ private static final double DELTA = 0.0000001;
+
/**
* Creates a new test case.
*/
@@ -116,4 +125,40 @@ public final strictfp class JTSTest extends GeometriesTestCase {
assertEquals(CommonCRS.WGS84.geographic(), JTS.findCoordinateReferenceSystem(geometry));
}
+ /**
+ * Tests {@link JTS#transform(org.locationtech.jts.geom.Geometry, org.opengis.referencing.crs.CoordinateReferenceSystem) }.
+ * Tests {@link JTS#transform(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.CoordinateOperation) }.
+ * Tests {@link JTS#transform(org.locationtech.jts.geom.Geometry, org.opengis.referencing.operation.MathTransform) }.
+ */
+ @Test
+ public void testTransform() throws FactoryException, TransformException {
+ final GeometryFactory gf = new GeometryFactory();
+ final Geometry in = gf.createPoint(new Coordinate(5, 6));
+
+ // test exception when transforming geometry without CRS.
+ try {
+ JTS.transform(in, CommonCRS.WGS84.geographic());
+ fail("Geometry has no CRS, transform should have failed");
+ } catch (TransformException ex) {
+ //ok
+ }
+
+ // test axes inversion transform
+ in.setUserData(CommonCRS.WGS84.normalizedGeographic());
+ Geometry out = JTS.transform(in, CommonCRS.WGS84.geographic());
+ assertTrue(out instanceof Point);
+ assertEquals(6.0, ((Point) out).getX(), 0.0);
+ assertEquals(5.0, ((Point) out).getY(), 0.0);
+ assertEquals(CommonCRS.WGS84.geographic(), out.getUserData());
+
+ // test affine transform, user data must be preserved
+ final AffineTransform2D trs = new AffineTransform2D(1,0,0,1,10,20);
+ out = JTS.transform(in, trs);
+ assertTrue(out instanceof Point);
+ assertEquals(15.0, ((Point) out).getX(), 0.0);
+ assertEquals(26.0, ((Point) out).getY(), 0.0);
+ assertEquals(CommonCRS.WGS84.normalizedGeographic(), out.getUserData());
+
+ }
+
}