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());
+
+    }
+
 }