Skip to content

Commit

Permalink
GIS #462: added transform functions
Browse files Browse the repository at this point in the history
  • Loading branch information
danylokravchenko committed Nov 11, 2023
1 parent f66a776 commit ad719b3
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 26 deletions.
3 changes: 1 addition & 2 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,9 @@ dependencies {
implementation group: "com.github.docker-java", name: "docker-java-transport-httpclient5", version: java_docker_version // TODO: should probably be independent version in future

// GIS
// TODO: remove esri library
implementation group: "com.esri.geometry", name: "esri-geometry-api", version: esri_geometry_api_version // Apache 2.0
implementation group: "org.locationtech.jts", name: "jts-core", version: jts_version // Eclipse Public License 2.0 && Eclipse Distribution License 1.0 (BSD-3 Clause)
implementation group: "org.locationtech.proj4j", name: "proj4j", version: proj4j_version // Apache 2.0
implementation group: "org.locationtech.proj4j", name: "proj4j-epsg", version: proj4j_version // Apache 2.0

// uses config internally...
/*implementation(group: "com.datastax.oss", name: "java-driver-core", version: cassandra_driver_core_version) { // Apache 2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package org.polypheny.db.algebra.constant;


import com.esri.core.geometry.Operator;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Locale;
Expand Down Expand Up @@ -69,7 +67,7 @@
* kind, and for these we use the {@code SqlOperator}. But for really the common ones, e.g. the many places where we are
* looking for {@code AND}, {@code OR} and {@code EQUALS}, the enum helps.
*
* (If we were using Scala, {@link Operator} would be a case class, and we wouldn't need {@code Kind}. But we're not.)
* (If we were using Scala, {@link org.polypheny.db.nodes.Operator} would be a case class, and we wouldn't need {@code Kind}. But we're not.)
*/
public enum Kind {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ public Expression implement( RexToLixTranslator translator, RexCall call, List<E

// geo functions
defineMethod( OperatorRegistry.get( OperatorName.ST_GEOFROMTEXT ), BuiltInMethod.ST_GEOFROMTEXT.method, NullPolicy.STRICT );
defineMethod( OperatorRegistry.get( OperatorName.ST_ASTEXT ), BuiltInMethod.ST_ASTEXT.method, NullPolicy.STRICT );
defineMethod( OperatorRegistry.get( OperatorName.ST_TRANSFORM ), BuiltInMethod.ST_TRANSFORM.method, NullPolicy.STRICT );
// Common properties
defineMethod( OperatorRegistry.get( OperatorName.ST_ISSIMPLE ), BuiltInMethod.ST_ISSIMPLE.method, NullPolicy.STRICT );
defineMethod( OperatorRegistry.get( OperatorName.ST_ISEMPTY ), BuiltInMethod.ST_ISEMPTY.method, NullPolicy.STRICT );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,16 @@ public enum OperatorName {
*/
ST_GEOFROMTEXT( Function.class ),

/**
* The <code>ST_AsText</code> operator function: output the WKT representation of {@link org.polypheny.db.type.entity.spatial.PolyGeometry}
*/
ST_ASTEXT( Function.class ),

/**
* The <code>ST_Transform</code> operator function: transform coordinates of {@link org.polypheny.db.type.entity.spatial.PolyGeometry} to another SRID
*/
ST_TRANSFORM( Function.class ),

// Common properties

/**
Expand Down
18 changes: 15 additions & 3 deletions core/src/main/java/org/polypheny/db/functions/GeoFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.polypheny.db.functions.Functions.toUnchecked;

import java.util.Objects;
import org.polypheny.db.functions.spatial.GeoTransformFunctions;
import org.polypheny.db.type.entity.PolyBoolean;
import org.polypheny.db.type.entity.PolyFloat;
import org.polypheny.db.type.entity.PolyInteger;
Expand Down Expand Up @@ -69,18 +70,28 @@ public static PolyGeometry stGeoFromText( PolyString wkt, PolyNumber srid ) {
}
}

// TODO: transform to another SRID

@SuppressWarnings("UnusedDeclaration")
public static PolyString stAsText( PolyGeometry geometry ) {
return PolyString.of( geometry.toString() );
}


@SuppressWarnings("UnusedDeclaration")
public static PolyGeometry stTransform( PolyGeometry geometry, PolyNumber srid ) {
// todo:
return null;
try {
return GeoTransformFunctions.transform( geometry, srid.intValue() );
} catch ( InvalidGeometryException e ) {
throw toUnchecked( e );
}
}


/*
* Common properties
*/


@SuppressWarnings("UnusedDeclaration")
public static PolyBoolean stIsSimple( PolyGeometry geometry ) {
return PolyBoolean.of( geometry.isSimple() );
Expand Down Expand Up @@ -179,6 +190,7 @@ public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance,
* Yield metric values
*/


@SuppressWarnings("UnusedDeclaration")
public static PolyNumber stDistance( PolyGeometry g1, PolyGeometry g2 ) {
restrictToSrid( g1, g2 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.polypheny.db.functions;
package org.polypheny.db.functions.spatial;

import org.jetbrains.annotations.NotNull;
import org.locationtech.jts.geom.Coordinate;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2019-2023 The Polypheny Project
*
* Licensed 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.polypheny.db.functions.spatial;

import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.locationtech.jts.geom.PrecisionModel;
import org.locationtech.proj4j.CRSFactory;
import org.locationtech.proj4j.CoordinateReferenceSystem;
import org.locationtech.proj4j.CoordinateTransform;
import org.locationtech.proj4j.CoordinateTransformFactory;
import org.locationtech.proj4j.Proj4jException;
import org.locationtech.proj4j.ProjCoordinate;
import org.polypheny.db.type.entity.spatial.InvalidGeometryException;
import org.polypheny.db.type.entity.spatial.PolyGeometry;
import org.polypheny.db.type.entity.spatial.PolyGeometryCollection;

/**
* Transform coordinates to various SRID
*/
public class GeoTransformFunctions {

private static final String SRID_PREFIX = "EPSG:";


private GeoTransformFunctions() {
// empty on purpose
}


public static PolyGeometry transform( PolyGeometry geometry, int srid ) throws InvalidGeometryException {
// Create a CRSFactory to manage coordinate reference systems
CRSFactory crsFactory = new CRSFactory();
// Create the original and target coordinate reference systems
CoordinateReferenceSystem sourceCrs = crsFactory.createFromName( SRID_PREFIX + geometry.getSRID() );
CoordinateReferenceSystem targetCrs = crsFactory.createFromName( SRID_PREFIX + srid );
// create Geometry factory with new srid
GeometryFactory geometryFactory = new GeometryFactory( new PrecisionModel( PrecisionModel.FLOATING ), srid );

if ( geometry.isGeometryCollection() ) {
PolyGeometryCollection geometryCollection = geometry.asGeometryCollection();
Geometry[] geometries = new Geometry[geometryCollection.getNumGeometries()];
for ( int i = 0; i < geometryCollection.getNumGeometries(); i++ ) {
Geometry geom = geometryCollection.getGeometryN( i ).getJtsGeometry();
// Convert the Geometry to Proj4J coordinates
ProjCoordinate[] originalCoords = convertToProj4JCoordinates( geom );
// Perform the SRID conversion
ProjCoordinate[] targetCoords = convertCoordinates( originalCoords, sourceCrs, targetCrs );
// Convert Proj4J coordinates back to Geometry
geometries[i] = convertToGeometry( targetCoords, geom, geometryFactory );
}
switch ( geometry.getGeometryType() ) {
case GEOMETRYCOLLECTION:
return PolyGeometry.of( geometryFactory.createGeometryCollection( geometries ) );
case MULTIPOINT:
Point[] points = new Point[geometries.length];
for ( int i = 0; i < geometries.length; i++ ) {
points[i] = geometryFactory.createPoint( geometries[i].getCoordinate() );
}
return PolyGeometry.of( geometryFactory.createMultiPoint( points ) );
case MULTILINESTRING:
LineString[] lineStrings = new LineString[geometries.length];
for ( int i = 0; i < geometries.length; i++ ) {
lineStrings[i] = geometryFactory.createLineString( geometries[i].getCoordinates() );
}
return PolyGeometry.of( geometryFactory.createMultiLineString( lineStrings ) );
case MULTIPOLYGON:
Polygon[] polygons = new Polygon[geometries.length];
for ( int i = 0; i < geometries.length; i++ ) {
polygons[i] = geometryFactory.createPolygon( geometries[i].getCoordinates() );
}
return PolyGeometry.of( geometryFactory.createMultiPolygon( polygons ) );
default:
throw new InvalidGeometryException( "Cannot convert back to GeometryCollection" );
}
} else {
ProjCoordinate[] originalCoords = convertToProj4JCoordinates( geometry.getJtsGeometry() );
ProjCoordinate[] targetCoords = convertCoordinates( originalCoords, sourceCrs, targetCrs );
return PolyGeometry.of( convertToGeometry( targetCoords, geometry.getJtsGeometry(), geometryFactory ) );
}


}


// Convert Geometry coordinates to Proj4J coordinates
private static ProjCoordinate[] convertToProj4JCoordinates( Geometry geometry ) {
ProjCoordinate[] projCoords = new ProjCoordinate[geometry.getNumPoints()];
for ( int i = 0; i < geometry.getNumPoints(); i++ ) {
projCoords[i] = new ProjCoordinate( geometry.getCoordinates()[i].getX(), geometry.getCoordinates()[i].getY() );
}
return projCoords;
}


// Convert Proj4J coordinates back to Geometry
private static Geometry convertToGeometry( ProjCoordinate[] projCoords, Geometry geometry, GeometryFactory geometryFactory ) throws InvalidGeometryException {
Coordinate[] originalCoordinates = geometry.getCoordinates();
Coordinate[] coordinates = new Coordinate[projCoords.length];

for ( int i = 0; i < projCoords.length; i++ ) {
// X and Y are converted, Z is original
coordinates[i] = new Coordinate( projCoords[i].x, projCoords[i].y, originalCoordinates[i].getZ() );
}
switch ( geometry.getGeometryType() ) {
case Geometry.TYPENAME_POINT:
return geometryFactory.createPoint( coordinates[0] );
case Geometry.TYPENAME_LINESTRING:
return geometryFactory.createLineString( coordinates );
case Geometry.TYPENAME_LINEARRING:
return geometryFactory.createLinearRing( coordinates );
case Geometry.TYPENAME_POLYGON:
return geometryFactory.createPolygon( coordinates );
default:
throw new InvalidGeometryException( "Cannot convert back to Geometry" );
}

}


// Helper method to perform the SRID conversion
private static ProjCoordinate[] convertCoordinates(
ProjCoordinate[] originalCoords, CoordinateReferenceSystem sourceCrs, CoordinateReferenceSystem targetCrs ) {
try {
CoordinateTransform trans = new CoordinateTransformFactory().createTransform( sourceCrs, targetCrs );
ProjCoordinate[] targetCoords = new ProjCoordinate[originalCoords.length];
for ( int i = 0; i < originalCoords.length; i++ ) {
targetCoords[i] = new ProjCoordinate();
trans.transform( originalCoords[i], targetCoords[i] );
}
return targetCoords;
} catch ( Proj4jException e ) {
throw new RuntimeException( "Error in coordinate transformation.", e );
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,11 @@
import org.apache.commons.lang3.NotImplementedException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Coordinates;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.TopologyException;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.polypheny.db.functions.GeoDistanceFunctions;
import org.polypheny.db.functions.spatial.GeoDistanceFunctions;
import org.polypheny.db.type.PolySerializable;
import org.polypheny.db.type.PolyType;
import org.polypheny.db.type.entity.PolyValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* <p>
* The {@link PolyPoint} is valid if
* <strong>X</strong> and <strong>Y</strong> coordinates are provided.
* The {@link PolyPoint} could store up to 4 dimensions
* The {@link PolyPoint} could store up to 3 dimensions
*/
public class PolyPoint extends PolyGeometry {

Expand Down Expand Up @@ -87,14 +87,4 @@ public double getZ() {
return jtsPoint.getCoordinate().getZ();
}


public boolean hasM() {
return !Double.isNaN( jtsPoint.getCoordinate().getM() );
}


public double getM() {
return jtsPoint.getCoordinate().getM();
}

}
2 changes: 2 additions & 0 deletions core/src/main/java/org/polypheny/db/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ public enum BuiltInMethod {
UNWRAP_INTERVAL( RefactorFunctions.class, "unwrap", PolyInterval.class ),
// GEO METHODS
ST_GEOFROMTEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ),
ST_ASTEXT( GeoFunctions.class, "stAsText", PolyGeometry.class ),
ST_TRANSFORM( GeoFunctions.class, "stTransform", PolyGeometry.class, PolyNumber.class ),
// Common properties
ST_ISSIMPLE( GeoFunctions.class, "stIsSimple", PolyGeometry.class ),
ST_ISEMPTY( GeoFunctions.class, "stIsEmpty", PolyGeometry.class ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,46 @@ public void commonPropertiesFunctions() throws SQLException {
}
}

@Test
public void transformFunctions() throws SQLException {
try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) {
Connection connection = polyphenyDbConnection.getConnection();
try ( Statement statement = connection.createStatement() ) {
// transform single point to other SRID
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Transform(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), 2056)" ),
ImmutableList.of(
new Object[]{ "SRID=2056;POINT (2630923.876654428 1316590.5631470187)" }
) );
// transform linestring to other SRID
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Transform(ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 4326), 2056)" ),
ImmutableList.of(
new Object[]{ "SRID=2056;LINESTRING (2736179.275459154 1400721.6498003295, 2813774.868277359 1291774.167120458, 2977340.286067275 1077215.140782387)" }
) );
// transform polygon to other SRID
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Transform(ST_GeoFromText('POLYGON ((9.289382 48.741588, 10.289382 47.741588, 9.289382 47.741588, 9.289382 48.741588))', 4326), 2056)" ),
ImmutableList.of(
new Object[]{ "SRID=2056;POLYGON ((2736179.275459154 1400721.6498003295, 2813774.868277359 1291774.167120458, 2738803.7053598273 1289526.6432951635, 2736179.275459154 1400721.6498003295))" }
) );
// transform geometry collection to other SRID
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Transform(ST_GeoFromText('GEOMETRYCOLLECTION ( POINT(7.852923 47.998949), POINT(9.289382 48.741588))', 4326), 2056)" ),
ImmutableList.of(
new Object[]{ "SRID=2056;GEOMETRYCOLLECTION (POINT (2630923.876654428 1316590.5631470187), POINT (2736179.275459154 1400721.6498003295))" }
) );
// transform multipoint to other SRID
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Transform(ST_GeoFromText('MULTIPOINT ((7.852923 47.998949), (9.289382 48.741588))', 4326), 2056)" ),
ImmutableList.of(
new Object[]{ "SRID=2056;MULTIPOINT ((2630923.876654428 1316590.5631470187), (2736179.275459154 1400721.6498003295))" }
) );
}
}

}

@Test
public void distanceFunctions() throws SQLException {
try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) {
Expand All @@ -144,7 +184,7 @@ public void distanceFunctions() throws SQLException {
new Object[]{ 134.45105 } // still the same closest point
) );
// calculate the distance between point and polygon
TestHelper.checkResultSet( // -1 -1, 2 2, -1 2, -1 -1
TestHelper.checkResultSet(
statement.executeQuery( "SELECT ST_Distance(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('POLYGON ((9.289382 48.741588, 10.289382 47.741588, 9.289382 47.741588, 9.289382 48.741588))', 4326))" ),
ImmutableList.of(
new Object[]{ 106.87882 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public void testPointValidity() {
assertEquals( 13.4050, point.getX(), GeometryConstants.DELTA );
assertEquals( 52.5200, point.getY(), GeometryConstants.DELTA );
assertFalse( point.hasZ() );
assertFalse( point.hasM() );
} );

assertAll( "Group assertions of valid Point in WKT",
Expand All @@ -97,7 +96,6 @@ public void testPointValidity() {
assertEquals( 52.5200, point.getY(), GeometryConstants.DELTA );
assertTrue( point.hasZ() );
assertEquals( 36.754, point.getZ(), GeometryConstants.DELTA );
assertFalse( point.hasM() );
} );

assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POINT (13.4050)" ) );
Expand Down
Loading

0 comments on commit ad719b3

Please sign in to comment.