From fa05d81553ec3bcef1db622457f40f23d5d89678 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Tue, 31 Oct 2023 17:10:28 +0100 Subject: [PATCH 01/16] initialized new branch for the GIS features; added libraries --- build.gradle | 14 +++++++------- core/build.gradle | 31 +++++++++++++++++++++---------- dbms/build.gradle | 14 +++++++++----- gradle.properties | 6 ++++-- plugins/build.gradle | 4 ++-- webui/build.gradle | 6 +++--- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index efc786ed1a..f63ac241dd 100644 --- a/build.gradle +++ b/build.gradle @@ -48,10 +48,10 @@ allprojects { compileTestJava.options.encoding = "UTF-8" javadoc.options.encoding = "UTF-8" - sourceCompatibility = 1.11 - targetCompatibility = 1.11 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 - tasks.withType(JavaCompile) { + tasks.withType(JavaCompile).configureEach { options.encoding = "UTF-8" } @@ -71,16 +71,16 @@ allprojects { } } - tasks.withType(JavaCompile) { + tasks.withType(JavaCompile).configureEach { options.encoding = "UTF-8" } - configurations.all { + configurations.configureEach { // check for updates every build resolutionStrategy.cacheChangingModulesFor 0, "seconds" } - tasks.withType(Javadoc) { + tasks.withType(Javadoc).configureEach { if (JavaVersion.current().isJava9Compatible()) { options.addBooleanOption("html5", true) } @@ -92,7 +92,7 @@ allprojects { String storeName = System.getProperty("store.default") != null ? System.getProperty("store.default") : 'hsqldb' - task integrationTests(type: Test) { + tasks.register('integrationTests', Test) { description = 'Runs integration tests.' group = 'verification' systemProperty 'store.default', storeName diff --git a/core/build.gradle b/core/build.gradle index 0cdb5f05ea..af6886fc48 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -43,23 +43,25 @@ dependencies { api group: "org.apache.commons", name: "commons-dbcp2", version: commons_dbcp2_version // Apache 2.0 api group: "com.google.guava", name: "guava", version: guava_version - api(group: "org.reflections", name: "reflections", version: reflections_version) // BSD 2-clause + api group: "org.reflections", name: "reflections", version: reflections_version // BSD 2-clause api group: "com.google.code.gson", name: "gson", version: gson_version // Apache 2.0 - implementation group: "com.esri.geometry", name: "esri-geometry-api", version: esri_geometry_api_version // Apache 2.0 api group: "com.fasterxml.jackson.core", name: "jackson-core", version: jackson_core_version // Apache 2.0 api group: "com.fasterxml.jackson.core", name: "jackson-databind", version: jackson_databind_version // Apache 2.0 api group: "com.google.code.findbugs", name: "jsr305", version: jsr305_version // Apache 2.0 - implementation(group: "com.jayway.jsonpath", name: "json-path", version: json_path_version) { exclude(module: "json-smart"); exclude(group: "org.slf4j") } // Apache 2.0 + implementation(group: "com.jayway.jsonpath", name: "json-path", version: json_path_version) { + exclude module: "json-smart" + exclude group: "org.slf4j" + } // Apache 2.0 implementation group: "org.codehaus.janino", name: "janino", version: janino_version // BSD implementation group: "org.codehaus.janino", name: "commons-compiler", version: commons_compiler_version // BSD implementation group: "com.j256.simplemagic", name: "simplemagic", version: simplemagic_version // ISC api(group: "org.apache.calcite", name: "calcite-linq4j", version: calcite_linq4j_version) { - exclude(module: "guava") - exclude(module: "avatica-core") + exclude module: "guava" + exclude module: "avatica-core" } // Apache 2.0 api(group: "org.polypheny.avatica", name: "avatica-core", version: avatica_core_version) { - exclude(module: "protobuf-java") + exclude module: "protobuf-java" exclude group: "org.apache.calcite", module: "avatica-core" } // Apache 2.0 @@ -75,6 +77,12 @@ dependencies { implementation group: "com.github.docker-java", name: "docker-java", version: java_docker_version // Apache 2.0 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 + // uses config internally... /*implementation(group: "com.datastax.oss", name: "java-driver-core", version: cassandra_driver_core_version) { // Apache 2.0 exclude group: "com.github.spotbugs" @@ -121,7 +129,7 @@ sourceSets { } -task generateJdbcVersionProperties(type: Copy) { +tasks.register('generateJdbcVersionProperties', Copy) { from "src/main/resources/version" into project.buildDir.absolutePath + "/classes" expand(version: project.version, versionMajor: versionMajor, versionMinor: versionMinor, buildTimestamp: new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")) @@ -184,15 +192,18 @@ jar { attributes "Version": "$project.version" } } -task sourcesJar(type: Jar, dependsOn: classes) { +tasks.register('sourcesJar', Jar) { + dependsOn classes classifier "sources" from sourceSets.main.allSource } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register('javadocJar', Jar) { + dependsOn javadoc classifier "javadoc" from javadoc.destinationDir } -task testJar(type: Jar, dependsOn: testClasses) { +tasks.register('testJar', Jar) { + dependsOn testClasses classifier "tests" from sourceSets.test.output } diff --git a/dbms/build.gradle b/dbms/build.gradle index 3d88f9696e..fc22f6e62d 100644 --- a/dbms/build.gradle +++ b/dbms/build.gradle @@ -189,11 +189,13 @@ jar { }) } } -task sourcesJar(type: Jar, dependsOn: classes) { +tasks.register('sourcesJar', Jar) { + dependsOn classes classifier "sources" from sourceSets.main.allSource } -task javadocJar(type: Jar, dependsOn: javadoc) { +tasks.register('javadocJar', Jar) { + dependsOn javadoc classifier "javadoc" from javadoc.destinationDir } @@ -206,10 +208,11 @@ shadowJar { } } -task deletePlugins(type: Delete) { +tasks.register('deletePlugins', Delete) { delete 'src/main/resources/plugins' } -task copyPlugins(type: Copy, dependsOn: deletePlugins) { +tasks.register('copyPlugins', Copy) { + dependsOn deletePlugins from('../build/plugins') into('src/main/resources/plugins') include('*.zip') @@ -226,7 +229,8 @@ configurations { } -task testJar(type: Jar, dependsOn: testClasses) { +tasks.register('testJar', Jar) { + dependsOn testClasses classifier 'tests' from sourceSets.test.output } diff --git a/gradle.properties b/gradle.properties index b5d266c402..f743ffe8f6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -82,6 +82,7 @@ jetty_websocket_api_version = 9.4.48.v20220622 json_path_version = 2.4.0 jsoup_version = 1.11.3 jsr305_version = 3.0.1 +jts_version = 1.19.0 junit_jupiter_version = 5.7.0 junit_version = 4.13.1 lang_painless_version = 6.2.4 @@ -93,7 +94,7 @@ log4j_slf4j_impl_version = 2.19.0 lombok_version = 8.0.1 mapdb_version = 3.0.9 mariadb_version = 2.7.2 -metadata_extractor_version = 2.15.0 +metadata_extractor_version = 2.18.0 mockito_core_version = 2.22.0 monetdb_version = 2.37 mongodb_driver_sync_version = 4.10.2 @@ -110,9 +111,10 @@ polypheny_jdbc_driver_version = 1.5.3 polypheny_ui_version = 1.0-SNAPSHOT postgresql_version = 42.2.19 pf4j_version = 3.9.0 -quidem_version = 0.9 +proj4j_version = 1.3.0 protobuf_version = 3.23.4 protobuf_plugin_version = 0.9.4 +quidem_version = 0.9 reflections_version = 0.10.2 shadow_plugin_version = 7.1.1 simplemagic_version = 1.16 diff --git a/plugins/build.gradle b/plugins/build.gradle index 56d1fee467..5169e6822d 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -37,7 +37,7 @@ subprojects { } } - task plugin(type: Jar) { + tasks.register('plugin', Jar) { manifest { attributes 'Plugin-Class': "${pluginClass}", 'Plugin-Id': "${pluginId}", @@ -60,7 +60,7 @@ subprojects { extension('zip') } - task assemblePlugin(type: Copy) { + tasks.register('assemblePlugin', Copy) { from plugin into pluginsDir } diff --git a/webui/build.gradle b/webui/build.gradle index be96fa9de0..6b8a86def0 100644 --- a/webui/build.gradle +++ b/webui/build.gradle @@ -1,7 +1,7 @@ group "org.polypheny" -configurations.all { +configurations.configureEach { // check for updates every build resolutionStrategy.cacheChangingModulesFor 0, "seconds" } @@ -65,7 +65,7 @@ processResources { // unzip ui files -task unzipUiFiles(type: Copy) { +tasks.register('unzipUiFiles', Copy) { from zipTree(configurations.uiFiles.singleFile) into "$buildDir/webapp" doLast { @@ -93,7 +93,7 @@ jar { attributes "Version": "$project.version" } from("$buildDir/webapp") // include webapp files - duplicatesStrategy = 'include' + duplicatesStrategy = DuplicatesStrategy.INCLUDE } java { //withJavadocJar() From 0695fdcf39e9aa02d5dadb5161c0c6c1b26a83b2 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Wed, 1 Nov 2023 18:09:45 +0100 Subject: [PATCH 02/16] created a base class for spatial data types and basic functions to query it; first successor of it - Point; added tests to check validity of points and equality --- .../java/org/polypheny/db/type/PolyType.java | 3 +- .../org/polypheny/db/type/PolyTypeFamily.java | 2 + .../polypheny/db/type/entity/PolyValue.java | 13 +- .../spatial/InvalidGeometryException.java | 25 ++ .../db/type/entity/spatial/PolyGeometry.java | 226 ++++++++++++++++++ .../db/type/entity/spatial/PolyPoint.java | 79 ++++++ dbms/build.gradle | 4 + .../db/type/spatial/GeometryTest.java | 91 +++++++ 8 files changed, 439 insertions(+), 4 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/InvalidGeometryException.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java create mode 100644 dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java diff --git a/core/src/main/java/org/polypheny/db/type/PolyType.java b/core/src/main/java/org/polypheny/db/type/PolyType.java index 38e545ad9b..e86df4c0c4 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyType.java +++ b/core/src/main/java/org/polypheny/db/type/PolyType.java @@ -366,7 +366,8 @@ public enum PolyType { GEOMETRY( PrecScale.NO_NO, true, - ExtraPolyTypes.GEOMETRY, + // TODO: or should it be Types.VARCHAR because of WKT or Types.JAVA_OBJECT + Types.JAVA_OBJECT, // ExtraPolyTypes.GEOMETRY, PolyTypeFamily.GEO ), FILE( diff --git a/core/src/main/java/org/polypheny/db/type/PolyTypeFamily.java b/core/src/main/java/org/polypheny/db/type/PolyTypeFamily.java index 2ecb1cef54..8cce1d94d2 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyTypeFamily.java +++ b/core/src/main/java/org/polypheny/db/type/PolyTypeFamily.java @@ -93,6 +93,7 @@ public enum PolyTypeFamily implements AlgDataTypeFamily { // PolyType.MULTISET shares Types.ARRAY with PolyType.ARRAY; // PolyType.MAP has no corresponding JDBC type // PolyType.COLUMN_LIST has no corresponding JDBC type + // All spatial data types .put( Types.BIT, NUMERIC ) .put( Types.TINYINT, NUMERIC ) .put( Types.SMALLINT, NUMERIC ) @@ -174,6 +175,7 @@ public Collection getTypeNames() { case DATETIME_INTERVAL: return PolyType.INTERVAL_TYPES; case GEO: + // TODO: add all spatial types return ImmutableList.of( PolyType.GEOMETRY ); case MULTISET: return ImmutableList.of( PolyType.MULTISET ); diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java index a3af9a5f21..93818075ef 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java @@ -64,6 +64,9 @@ import org.polypheny.db.type.entity.PolyBoolean.PolyBooleanSerializerDef; import org.polypheny.db.type.entity.PolyDouble.PolyDoubleSerializerDef; import org.polypheny.db.type.entity.PolyFloat.PolyFloatSerializerDef; +//import org.polypheny.db.type.entity.spatial.PolyGeometry.PolyGeometrySerializerDef; +import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyGeometry.PolyGeometrySerializerDef; import org.polypheny.db.type.entity.PolyInteger.PolyIntegerSerializerDef; import org.polypheny.db.type.entity.PolyList.PolyListSerializerDef; import org.polypheny.db.type.entity.PolyLong.PolyLongSerializerDef; @@ -108,7 +111,9 @@ PolyBinary.class, PolyNode.class, PolyEdge.class, - PolyPath.class }) // add on Constructor already exists exception + PolyPath.class, + PolyGeometry.class +}) // add on Constructor already exists exception @JsonTypeInfo(use = Id.CLASS) // to allow typed json serialization @JsonSubTypes({ @JsonSubTypes.Type(value = PolyList.class, name = "LIST"), @@ -131,7 +136,8 @@ @JsonSubTypes.Type(value = PolyEdge.class, name = "EDGE"), @JsonSubTypes.Type(value = PolyPath.class, name = "PATH"), @JsonSubTypes.Type(value = PolyDictionary.class, name = "DICTIONARY"), - @JsonSubTypes.Type(value = PolyUserDefinedValue.class, name = "UDV") + @JsonSubTypes.Type(value = PolyUserDefinedValue.class, name = "UDV"), + @JsonSubTypes.Type(value = PolyGeometry.class, name = "GEOMETRY") }) public abstract class PolyValue implements Expressible, Comparable, PolySerializable { @@ -153,6 +159,7 @@ public abstract class PolyValue implements Expressible, Comparable, P .with( PolyBoolean.class, ctx -> new PolyBooleanSerializerDef() ) .with( PolyGraph.class, ctx -> new PolyGraphSerializerDef() ) .with( PolyLong.class, ctx -> new PolyLongSerializerDef() ) + .with( PolyGeometry.class, ctx -> new PolyGeometrySerializerDef() ) .build( PolyValue.class ); @@ -432,7 +439,7 @@ public static Class classFrom( PolyType polyType ) { case DYNAMIC_STAR: return PolyValue.class; case GEOMETRY: - return PolyValue.class; + return PolyGeometry.class; case FILE: case IMAGE: case VIDEO: diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/InvalidGeometryException.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/InvalidGeometryException.java new file mode 100644 index 0000000000..9ff81917ee --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/InvalidGeometryException.java @@ -0,0 +1,25 @@ +/* + * 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.type.entity.spatial; + +public class InvalidGeometryException extends Exception { + + public InvalidGeometryException( String message ) { + super( message ); + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java new file mode 100644 index 0000000000..ed1b1f9b3d --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -0,0 +1,226 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.BinaryInput; +import io.activej.serializer.BinaryOutput; +import io.activej.serializer.BinarySerializer; +import io.activej.serializer.CompatibilityLevel; +import io.activej.serializer.CorruptedDataException; +import io.activej.serializer.SimpleSerializerDef; +import io.activej.serializer.annotations.Deserialize; +import java.util.Objects; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.calcite.linq4j.tree.Expression; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKTReader; +import org.polypheny.db.type.PolySerializable; +import org.polypheny.db.type.PolyType; +import org.polypheny.db.type.entity.PolyValue; + +/** + * {@link PolyGeometry} is an abstraction for all spatial data types. + *

+ * {@link PolyGeometry} wraps the {@link Geometry} of the JTS library + * and the functionality of {@link PolyGeometry} is provided my means of JTS. + */ +@Slf4j +public class PolyGeometry extends PolyValue { + + // default plane + private static final int NO_SRID = 0; + // World Geodetic System 1984 + private static final int WGS_84 = 4326; + + /** + * Wrap the JTS {@link Geometry} class. + *

+ * It is possible to extract useful information from the {@link Geometry}: + * jtsGeometry.getGeometryType() + * jtsGeometry.getSRID() + * jtsGeometry.getCoordinate() + */ + protected Geometry jtsGeometry; + // Spatial Reference System ID + @Getter + protected Integer SRID; + + /** + * Constructor creates the {@link Geometry} from the WKT text. + *

+ * The WKT may contain SRID in the text, e.g. "SRID=4326;POINT (13.4050 52.5200)". + * In this case SRID from the WKT would be used, otherwise the default one is selected. + *

+ * @param wkt Well Know Text representation of the geometry + * @throws InvalidGeometryException if {@link Geometry} is invalid or provided WKT is invalid. + */ + public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + this( PolyType.GEOMETRY ); + this.SRID = NO_SRID; + WKTReader reader = new WKTReader(); + try { + // WKT is actually an extended EWKT with SRID before the WKT + if (wkt.startsWith( "SRID" )) { + // in WKT semicolon is invalid character, so we could safely split by it + String[] ewktParts = wkt.split( ";" ); + this.SRID = Integer.valueOf( ewktParts[0].replace( "SRID=", "" ) ); + wkt = ewktParts[1]; + } + this.jtsGeometry = reader.read( wkt ); + if (!jtsGeometry.isValid()) { + throw new ParseException("Provided geometry is not valid."); + } + this.jtsGeometry.setSRID( this.SRID ); + } catch ( ParseException | NumberFormatException e) { + throw new InvalidGeometryException(e.getMessage()); + } + } + + public PolyGeometry( Geometry geometry ) { + super( PolyType.GEOMETRY ); + this.jtsGeometry = geometry; + this.SRID = geometry.getSRID(); + } + + protected PolyGeometry( PolyType type ) { + super( type ); + } + + public static PolyGeometry of( String wkt ) throws InvalidGeometryException { + return new PolyGeometry( wkt ); + } + + public static PolyGeometry of( Geometry geometry ) { + return new PolyGeometry( geometry ); + } + + public String getGeometryType() { + return jtsGeometry.getGeometryType(); + } + + public PolyPoint asPoint() { + if (jtsGeometry.getGeometryType().equals( "Point" )) { + return PolyPoint.of( jtsGeometry ); + } + return null; + } + + + /** + * Tests whether this {@link Geometry} is simple. + * @return true if {@link Geometry} is simple. + */ + public boolean isSimple() { + return jtsGeometry.isSimple(); + } + + + /** + * Bound the {@link Geometry} my the minimum box that could fit this geometry. + * @return {@link PolyGeometry} with minimum bounding box + */ + public PolyGeometry getMinimumBoundingBox() { + return PolyGeometry.of( jtsGeometry.getEnvelope() ); + } + + + /** + * {@link #equals(Object) equals} ensures that the {@link Geometry} types and coordinates are the same. + * And {@link #SRID} used for representation of coordinates are also identical. + */ + @Override + public boolean equals( Object o ) { + if ( !( o instanceof PolyGeometry )) { + return false; + } + PolyGeometry that = (PolyGeometry) o; + return jtsGeometry.equals( that.jtsGeometry ) && Objects.equals( SRID, that.SRID ); + } + + + @Override + public int hashCode() { + return Objects.hash( super.hashCode(), jtsGeometry.hashCode(), SRID ); + } + + + @Override + public PolySerializable copy() { + return PolySerializable.deserialize( serialize(), PolyGeometry.class ); + } + + + @Override + public Expression asExpression() { + // TODO: dont know what is it + return null; + } + + + @Override + public int compareTo( @NotNull PolyValue o ) { + if ( !isSameType( o ) ) { + return -1; + } + return jtsGeometry.compareTo( o ); + } + + + @Override + public @Nullable Long deriveByteSize() { + String wkt = toString(); + return (long) (wkt == null ? 1 : wkt.getBytes().length); + } + + + /** + * Output the {@link Geometry} in a WKT format with its SRID. So-called EWKT + */ + @Override + public String toString() { + return String.format( "SRID=%d;%s" , SRID, jtsGeometry.toString() ); + } + + public static class PolyGeometrySerializerDef extends SimpleSerializerDef { + + @Override + protected BinarySerializer createSerializer( int version, CompatibilityLevel compatibilityLevel ) { + return new BinarySerializer<>() { + @Override + public void encode( BinaryOutput out, PolyGeometry item ) { + out.writeUTF8( item.toString() ); + } + + @Override + public PolyGeometry decode( BinaryInput in ) throws CorruptedDataException { + try { + return PolyGeometry.of( PolySerializable.deserialize( in.readUTF8(), serializer ).asString().value ); + } catch ( InvalidGeometryException e ) { + throw new CorruptedDataException( e.getMessage() ); + } + } + }; + } + + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java new file mode 100644 index 0000000000..745cc0377e --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -0,0 +1,79 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; +import org.polypheny.db.type.PolyType; + +/** + * Represent a single point in the space. + * The {@link PolyPoint} is valid if + * X and Y coordinates are provided. + * The {@link PolyPoint} could store up to 4 dimensions + */ +public class PolyPoint extends PolyGeometry { + + private Point jtsPoint; + + public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.jtsPoint = (Point) jtsGeometry; + } + + protected PolyPoint( Geometry geometry ) { + super( PolyType.GEOMETRY ); + this.jtsGeometry = geometry; + this.jtsPoint = (Point) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + protected PolyPoint( PolyType type ) { + super( type ); + } + + public static PolyPoint of( Geometry geometry ) { + return new PolyPoint( geometry ); + } + + public double getX() { + return jtsPoint.getX(); + } + + public double getY() { + return jtsPoint.getY(); + } + + public boolean hasZ() { + return !Double.isNaN( jtsPoint.getCoordinate().getZ() ); + } + + public double getZ() { + return jtsPoint.getCoordinate().getZ(); + } + + public boolean hasM() { + return !Double.isNaN( jtsPoint.getCoordinate().getM() ); + } + + public double getM() { + return jtsPoint.getCoordinate().getM(); + } + +} diff --git a/dbms/build.gradle b/dbms/build.gradle index fc22f6e62d..e5c4766a38 100644 --- a/dbms/build.gradle +++ b/dbms/build.gradle @@ -70,6 +70,7 @@ dependencies { } // Apache 2.0 testImplementation group: "junit", name: "junit", version: junit_version + testImplementation group: "org.junit.jupiter", name: "junit-jupiter", version: junit_jupiter_version // Apache 2.0 testImplementation group: "org.hamcrest", name: "hamcrest-core", version: hamcrest_core_version // BSD 3-clause testImplementation group: "com.konghq", name: "unirest-java", version: unirest_version // MIT @@ -80,6 +81,9 @@ dependencies { testImplementation group: "monetdb", name: "monetdb-java-lite", version: embedded_monetdb_version implementation group: "org.mongodb", name: "mongodb-driver-sync", version: mongodb_driver_sync_version // Apache 2.0 + + // GIS + testImplementation group: "org.locationtech.jts", name: "jts-core", version: jts_version // Eclipse Public License 2.0 && Eclipse Distribution License 1.0 (BSD-3 Clause) } diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java new file mode 100644 index 0000000000..9fb1bd8375 --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -0,0 +1,91 @@ +/* + * 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.type.spatial; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.polypheny.db.TestHelper; +import org.polypheny.db.type.entity.spatial.InvalidGeometryException; +import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyPoint; + +public class GeometryTest { + + private static final String VALID_POINT_EWKT = "SRID=4326;POINT (13.4050 52.5200)"; + private static final String VALID_POINT_WKT = "POINT (13.4050 52.5200 36.754)"; + private static final double DELTA = 1e-15; + private static final int NO_SRID = 0; + + @BeforeClass + public static void start() { + TestHelper.getInstance(); + } + + @Test + public void testGeometryValidity() { + // Point + assertAll( + "Group assertions of valid Point in EWKT", + () -> assertDoesNotThrow( () -> PolyGeometry.of( VALID_POINT_EWKT ) ), + () -> { + PolyGeometry geometry = PolyGeometry.of( VALID_POINT_EWKT ); + assertEquals( "Point", geometry.getGeometryType() ); + assertEquals( 4326, (long) geometry.getSRID() ); + PolyPoint point = geometry.asPoint(); + assertEquals( 13.4050, point.getX(), DELTA ); + assertEquals( 52.5200, point.getY(), DELTA ); + assertFalse( point.hasZ() ); + assertFalse( point.hasM() ); + }); + + assertAll( + "Group assertions of valid Point in WKT", + () -> assertDoesNotThrow(() -> PolyGeometry.of( VALID_POINT_WKT )), + () -> { + PolyGeometry geometry = PolyGeometry.of( VALID_POINT_WKT ); + assertEquals( "Point", geometry.getGeometryType() ); + assertEquals( NO_SRID, (long) geometry.getSRID() ); + PolyPoint point = geometry.asPoint(); + assertEquals( 13.4050, point.getX(), DELTA ); + assertEquals( 52.5200, point.getY(), DELTA ); + assertTrue( point.hasZ() ); + assertEquals( 36.754, point.getZ(), DELTA ); + assertFalse( point.hasM() ); + }); + + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POINT (13.4050)" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POINT (13.4050 13.4050 13.4050 13.4050 13.4050)" ) ); + } + + @Test + public void testPointsEquality() throws InvalidGeometryException { + PolyGeometry point1 = PolyGeometry.of( VALID_POINT_EWKT ); + PolyGeometry point2 = PolyGeometry.of( VALID_POINT_EWKT ); + PolyGeometry point3 = PolyGeometry.of( VALID_POINT_WKT ); + assertEquals( point1, point2 ); + assertNotEquals( point1, point3 ); + } + +} \ No newline at end of file From 018e966f31c407a650a08edcab692f84f234f47b Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Wed, 1 Nov 2023 22:03:52 +0100 Subject: [PATCH 03/16] GIS #462: introduced new PolyGeomotryTypes enum to remove string values; added test to check serialize/deserialize process --- .../java/org/polypheny/db/type/PolyType.java | 6 +- .../polypheny/db/type/entity/PolyValue.java | 240 +++++++++--------- .../db/type/entity/spatial/PolyGeometry.java | 32 ++- .../type/entity/spatial/PolyGeometryType.java | 23 ++ .../db/type/entity/spatial/PolyPoint.java | 3 + .../db/type/spatial/GeometryTest.java | 15 +- 6 files changed, 196 insertions(+), 123 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java diff --git a/core/src/main/java/org/polypheny/db/type/PolyType.java b/core/src/main/java/org/polypheny/db/type/PolyType.java index e86df4c0c4..1a41b1abc7 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyType.java +++ b/core/src/main/java/org/polypheny/db/type/PolyType.java @@ -367,9 +367,11 @@ public enum PolyType { PrecScale.NO_NO, true, // TODO: or should it be Types.VARCHAR because of WKT or Types.JAVA_OBJECT - Types.JAVA_OBJECT, // ExtraPolyTypes.GEOMETRY, + Types.JAVA_OBJECT, PolyTypeFamily.GEO ), + // TODO: add all other Geo types + FILE( PrecScale.NO_NO, true, @@ -461,6 +463,8 @@ public enum PolyType { public static final List BLOB_TYPES = ImmutableList.of( FILE, AUDIO, IMAGE, VIDEO ); + // TODO: add Geometry types + public static final Set YEAR_INTERVAL_TYPES = Sets.immutableEnumSet( PolyType.INTERVAL_YEAR, diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java index 93818075ef..77d68cefcd 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java @@ -64,7 +64,6 @@ import org.polypheny.db.type.entity.PolyBoolean.PolyBooleanSerializerDef; import org.polypheny.db.type.entity.PolyDouble.PolyDoubleSerializerDef; import org.polypheny.db.type.entity.PolyFloat.PolyFloatSerializerDef; -//import org.polypheny.db.type.entity.spatial.PolyGeometry.PolyGeometrySerializerDef; import org.polypheny.db.type.entity.spatial.PolyGeometry; import org.polypheny.db.type.entity.spatial.PolyGeometry.PolyGeometrySerializerDef; import org.polypheny.db.type.entity.PolyInteger.PolyIntegerSerializerDef; @@ -141,6 +140,13 @@ }) public abstract class PolyValue implements Expressible, Comparable, PolySerializable { + public static final ObjectMapper JSON_WRAPPER = JsonMapper.builder() + .configure( MapperFeature.REQUIRE_TYPE_ID_FOR_SUBTYPES, true ) + .configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ) + .configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false ) + .configure( MapperFeature.USE_STATIC_TYPING, true ) + .build(); + @JsonIgnore // used internally to serialize into binary format public static BinarySerializer serializer = SerializerBuilder.create( CLASS_LOADER ) @@ -163,15 +169,6 @@ public abstract class PolyValue implements Expressible, Comparable, P .build( PolyValue.class ); - public static final ObjectMapper JSON_WRAPPER = JsonMapper.builder() - - .configure( MapperFeature.REQUIRE_TYPE_ID_FOR_SUBTYPES, true ) - .configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false ) - .configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false ) - .configure( MapperFeature.USE_STATIC_TYPING, true ) - .build(); - - static { JSON_WRAPPER.setSerializationInclusion( JsonInclude.Include.NON_NULL ) .setVisibility( JSON_WRAPPER.getSerializationConfig().getDefaultVisibilityChecker() @@ -235,6 +232,8 @@ public static Function1 getPolyToJava( AlgDataType type, bool case AUDIO: case VIDEO: return o -> o.asBlob().asByteArray(); + case GEOMETRY: + return o -> o.asGeometry().getJtsGeometry(); default: throw new org.apache.commons.lang3.NotImplementedException( "meta" ); } @@ -261,17 +260,6 @@ public static Function1 wrapNullableIfNecessary( Function1 E fromTypedJson( String value, Class clazz ) { try { @@ -283,30 +271,6 @@ public static E fromTypedJson( String value, Class claz } - public String toJson() { - // fallback serializer - try { - return JSON_WRAPPER.writeValueAsString( this ); - } catch ( JsonProcessingException e ) { - log.warn( "Error on deserialize JSON." ); - return null; - } - } - - - @NotNull - public Optional getByteSize() { - if ( byteSize == null ) { - byteSize = deriveByteSize(); - } - return Optional.ofNullable( byteSize ); - } - - - @Nullable - public abstract Long deriveByteSize(); - - public static Expression getInitialExpression( Type type ) { if ( PolyDefaults.DEFAULTS.get( type ) != null ) { return PolyDefaults.DEFAULTS.get( type ).asExpression(); @@ -343,17 +307,15 @@ public static Class classFrom( PolyType polyType ) { case BOOLEAN: return PolyBoolean.class; case TINYINT: + case INTEGER: return PolyInteger.class; case SMALLINT: return PolyInteger.class; - case INTEGER: - return PolyInteger.class; case BIGINT: return PolyLong.class; case DECIMAL: return PolyBigDecimal.class; case FLOAT: - return PolyFloat.class; case REAL: return PolyFloat.class; case DOUBLE: @@ -439,6 +401,7 @@ public static Class classFrom( PolyType polyType ) { case DYNAMIC_STAR: return PolyValue.class; case GEOMETRY: + // TODO: should here be all Geometry classes? return PolyGeometry.class; case FILE: case IMAGE: @@ -457,6 +420,107 @@ public static PolyValue deserialize( String json ) { } + public static PolyValue convert( PolyValue value, PolyType type ) { + + switch ( type ) { + case INTEGER: + return PolyInteger.from( value ); + case DOCUMENT: + // docs accept all + return value; + case BIGINT: + return PolyLong.from( value ); + } + if ( type.getFamily() == value.getType().getFamily() ) { + return value; + } + + throw new GenericRuntimeException( "%s does not support conversion to %s.", value, type ); + } + + + public static PolyValue fromType( Object object, PolyType type ) { + // TODO: should GIS be here? + switch ( type ) { + case BOOLEAN: + return PolyBoolean.of( (Boolean) object ); + case TINYINT: + case SMALLINT: + case INTEGER: + return PolyInteger.of( (Number) object ); + case BIGINT: + return PolyLong.of( (Number) object ); + case DECIMAL: + return PolyBigDecimal.of( object.toString() ); + case FLOAT: + case REAL: + return PolyFloat.of( (Number) object ); + case DOUBLE: + return PolyDouble.of( (Number) object ); + case DATE: + if ( object instanceof Number ) { + return PolyDate.of( (Number) object ); + } + throw new NotImplementedException(); + + case TIME: + case TIME_WITH_LOCAL_TIME_ZONE: + if ( object instanceof Number ) { + return PolyTime.of( (Number) object ); + } + throw new NotImplementedException(); + case TIMESTAMP: + case TIMESTAMP_WITH_LOCAL_TIME_ZONE: + if ( object instanceof Timestamp ) { + return PolyTimeStamp.of( (Timestamp) object ); + } + throw new NotImplementedException(); + case CHAR: + case VARCHAR: + return PolyString.of( (String) object ); + case BINARY: + case VARBINARY: + return PolyBinary.of( (ByteString) object ); + } + throw new NotImplementedException(); + } + + + @Nullable + public String toTypedJson() { + try { + return JSON_WRAPPER.writeValueAsString( this ); + } catch ( JsonProcessingException e ) { + log.warn( "Error on serializing typed JSON." ); + return null; + } + } + + + public String toJson() { + // fallback serializer + try { + return JSON_WRAPPER.writeValueAsString( this ); + } catch ( JsonProcessingException e ) { + log.warn( "Error on deserialize JSON." ); + return null; + } + } + + + @NotNull + public Optional getByteSize() { + if ( byteSize == null ) { + byteSize = deriveByteSize(); + } + return Optional.ofNullable( byteSize ); + } + + + @Nullable + public abstract Long deriveByteSize(); + + @Override public String serialize() { return PolySerializable.serialize( serializer, this ); @@ -499,7 +563,7 @@ public PolyBoolean asBoolean() { @NotNull - private GenericRuntimeException cannotParse( PolyValue value, Class clazz ) { + protected GenericRuntimeException cannotParse( PolyValue value, Class clazz ) { return new GenericRuntimeException( "Cannot parse %s to type %s", value, clazz.getSimpleName() ); } @@ -816,83 +880,31 @@ public PolyBlob asBlob() { throw cannotParse( this, PolyBlob.class ); } - - public boolean isUserDefinedValue() { - return PolyType.USER_DEFINED_TYPE == type; + public boolean isGeometry() { + return type == PolyType.GEOMETRY; } @NotNull - public PolyUserDefinedValue asUserDefinedValue() { - if ( isUserDefinedValue() ) { - return (PolyUserDefinedValue) this; + public PolyGeometry asGeometry() { + if ( isGeometry() ) { + return (PolyGeometry) this; } - throw cannotParse( this, PolyUserDefinedValue.class ); + throw cannotParse( this, PolyGeometry.class ); } - public static PolyValue convert( PolyValue value, PolyType type ) { - - switch ( type ) { - case INTEGER: - return PolyInteger.from( value ); - case DOCUMENT: - // docs accept all - return value; - case BIGINT: - return PolyLong.from( value ); - } - if ( type.getFamily() == value.getType().getFamily() ) { - return value; - } - - throw new GenericRuntimeException( "%s does not support conversion to %s.", value, type ); + public boolean isUserDefinedValue() { + return PolyType.USER_DEFINED_TYPE == type; } - public static PolyValue fromType( Object object, PolyType type ) { - switch ( type ) { - case BOOLEAN: - return PolyBoolean.of( (Boolean) object ); - case TINYINT: - case SMALLINT: - case INTEGER: - return PolyInteger.of( (Number) object ); - case BIGINT: - return PolyLong.of( (Number) object ); - case DECIMAL: - return PolyBigDecimal.of( object.toString() ); - case FLOAT: - case REAL: - return PolyFloat.of( (Number) object ); - case DOUBLE: - return PolyDouble.of( (Number) object ); - case DATE: - if ( object instanceof Number ) { - return PolyDate.of( (Number) object ); - } - throw new NotImplementedException(); - - case TIME: - case TIME_WITH_LOCAL_TIME_ZONE: - if ( object instanceof Number ) { - return PolyTime.of( (Number) object ); - } - throw new NotImplementedException(); - case TIMESTAMP: - case TIMESTAMP_WITH_LOCAL_TIME_ZONE: - if ( object instanceof Timestamp ) { - return PolyTimeStamp.of( (Timestamp) object ); - } - throw new NotImplementedException(); - case CHAR: - case VARCHAR: - return PolyString.of( (String) object ); - case BINARY: - case VARBINARY: - return PolyBinary.of( (ByteString) object ); + @NotNull + public PolyUserDefinedValue asUserDefinedValue() { + if ( isUserDefinedValue() ) { + return (PolyUserDefinedValue) this; } - throw new NotImplementedException(); + throw cannotParse( this, PolyUserDefinedValue.class ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index ed1b1f9b3d..880526a478 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -28,6 +28,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.calcite.linq4j.tree.Expression; +import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.locationtech.jts.geom.Geometry; @@ -59,11 +60,15 @@ public class PolyGeometry extends PolyValue { * jtsGeometry.getSRID() * jtsGeometry.getCoordinate() */ + @Getter protected Geometry jtsGeometry; // Spatial Reference System ID @Getter protected Integer SRID; + @Getter + protected PolyGeometryType geometryType; + /** * Constructor creates the {@link Geometry} from the WKT text. *

@@ -78,6 +83,7 @@ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throw this.SRID = NO_SRID; WKTReader reader = new WKTReader(); try { + wkt = wkt.trim(); // WKT is actually an extended EWKT with SRID before the WKT if (wkt.startsWith( "SRID" )) { // in WKT semicolon is invalid character, so we could safely split by it @@ -93,12 +99,14 @@ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throw } catch ( ParseException | NumberFormatException e) { throw new InvalidGeometryException(e.getMessage()); } + this.geometryType = getPolyGeometryType(); } public PolyGeometry( Geometry geometry ) { super( PolyType.GEOMETRY ); this.jtsGeometry = geometry; this.SRID = geometry.getSRID(); + this.geometryType = getPolyGeometryType(); } protected PolyGeometry( PolyType type ) { @@ -113,21 +121,33 @@ public static PolyGeometry of( Geometry geometry ) { return new PolyGeometry( geometry ); } - public String getGeometryType() { - return jtsGeometry.getGeometryType(); + protected PolyGeometryType getPolyGeometryType() { + switch ( jtsGeometry.getGeometryType() ) { + case "Point": + return PolyGeometryType.POINT; + case "LineString": + return PolyGeometryType.LINESTRING; + default: + throw new NotImplementedException( "value" ); + } } + public boolean isPoint() { + return geometryType.equals( PolyGeometryType.POINT ); + } + + @NotNull public PolyPoint asPoint() { - if (jtsGeometry.getGeometryType().equals( "Point" )) { + if ( isPoint() ) { return PolyPoint.of( jtsGeometry ); } - return null; + throw cannotParse( this, PolyPoint.class ); } /** * Tests whether this {@link Geometry} is simple. - * @return true if {@link Geometry} is simple. + * @return true if {@link Geometry} is simple. */ public boolean isSimple() { return jtsGeometry.isSimple(); @@ -213,7 +233,7 @@ public void encode( BinaryOutput out, PolyGeometry item ) { @Override public PolyGeometry decode( BinaryInput in ) throws CorruptedDataException { try { - return PolyGeometry.of( PolySerializable.deserialize( in.readUTF8(), serializer ).asString().value ); + return PolyGeometry.of( in.readUTF8() ); } catch ( InvalidGeometryException e ) { throw new CorruptedDataException( e.getMessage() ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java new file mode 100644 index 0000000000..efb99b0e9e --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java @@ -0,0 +1,23 @@ +/* + * 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.type.entity.spatial; + +public enum PolyGeometryType { + + POINT, + LINESTRING +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java index 745cc0377e..2f1083bf2e 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -34,11 +34,13 @@ public class PolyPoint extends PolyGeometry { public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { super( wkt ); + this.geometryType = PolyGeometryType.POINT; this.jtsPoint = (Point) jtsGeometry; } protected PolyPoint( Geometry geometry ) { super( PolyType.GEOMETRY ); + this.geometryType = PolyGeometryType.POINT; this.jtsGeometry = geometry; this.jtsPoint = (Point) jtsGeometry; this.SRID = geometry.getSRID(); @@ -46,6 +48,7 @@ protected PolyPoint( Geometry geometry ) { protected PolyPoint( PolyType type ) { super( type ); + this.geometryType = PolyGeometryType.POINT; } public static PolyPoint of( Geometry geometry ) { diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java index 9fb1bd8375..dbef0409d9 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -27,8 +27,10 @@ import org.junit.BeforeClass; import org.junit.Test; import org.polypheny.db.TestHelper; +import org.polypheny.db.type.entity.PolyValue; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyGeometryType; import org.polypheny.db.type.entity.spatial.PolyPoint; public class GeometryTest { @@ -51,7 +53,7 @@ public void testGeometryValidity() { () -> assertDoesNotThrow( () -> PolyGeometry.of( VALID_POINT_EWKT ) ), () -> { PolyGeometry geometry = PolyGeometry.of( VALID_POINT_EWKT ); - assertEquals( "Point", geometry.getGeometryType() ); + assertEquals( PolyGeometryType.POINT, geometry.getGeometryType() ); assertEquals( 4326, (long) geometry.getSRID() ); PolyPoint point = geometry.asPoint(); assertEquals( 13.4050, point.getX(), DELTA ); @@ -65,7 +67,7 @@ public void testGeometryValidity() { () -> assertDoesNotThrow(() -> PolyGeometry.of( VALID_POINT_WKT )), () -> { PolyGeometry geometry = PolyGeometry.of( VALID_POINT_WKT ); - assertEquals( "Point", geometry.getGeometryType() ); + assertEquals( PolyGeometryType.POINT, geometry.getGeometryType() ); assertEquals( NO_SRID, (long) geometry.getSRID() ); PolyPoint point = geometry.asPoint(); assertEquals( 13.4050, point.getX(), DELTA ); @@ -88,4 +90,13 @@ public void testPointsEquality() throws InvalidGeometryException { assertNotEquals( point1, point3 ); } + @Test + public void testSerializability() throws InvalidGeometryException { + PolyGeometry geometry = PolyGeometry.of( VALID_POINT_EWKT ); + String serialized = geometry.serialize(); + PolyValue polyValue = PolyGeometry.deserialize( serialized ); + PolyGeometry deserializedGeometry = polyValue.asGeometry(); + assertEquals( geometry, deserializedGeometry ); + } + } \ No newline at end of file From 127c2c8a78bff6cf1b61ed57d8e1e45844e97d3c Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 2 Nov 2023 13:15:43 +0100 Subject: [PATCH 04/16] GIS #462: added LineString and LinearRing types with tests for them --- .../polypheny/db/type/entity/PolyValue.java | 3 +- .../db/type/entity/spatial/PolyGeometry.java | 140 +++++++++++++++-- .../type/entity/spatial/PolyGeometryType.java | 6 +- .../type/entity/spatial/PolyLineString.java | 139 +++++++++++++++++ .../type/entity/spatial/PolyLinearRing.java | 66 ++++++++ .../db/type/entity/spatial/PolyPoint.java | 6 + .../db/type/spatial/GeometryConstants.java | 27 ++++ .../db/type/spatial/GeometryTest.java | 143 ++++++++++++------ 8 files changed, 470 insertions(+), 60 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java create mode 100644 dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java index 77d68cefcd..d95759f9d3 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java @@ -233,7 +233,8 @@ public static Function1 getPolyToJava( AlgDataType type, bool case VIDEO: return o -> o.asBlob().asByteArray(); case GEOMETRY: - return o -> o.asGeometry().getJtsGeometry(); + // TODO: check how does it work + return o -> o.asGeometry().toString(); default: throw new org.apache.commons.lang3.NotImplementedException( "meta" ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 880526a478..c962049d2e 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -69,37 +69,45 @@ public class PolyGeometry extends PolyValue { @Getter protected PolyGeometryType geometryType; + /** * Constructor creates the {@link Geometry} from the WKT text. *

* The WKT may contain SRID in the text, e.g. "SRID=4326;POINT (13.4050 52.5200)". * In this case SRID from the WKT would be used, otherwise the default one is selected. *

+ * * @param wkt Well Know Text representation of the geometry * @throws InvalidGeometryException if {@link Geometry} is invalid or provided WKT is invalid. */ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { this( PolyType.GEOMETRY ); - this.SRID = NO_SRID; - WKTReader reader = new WKTReader(); + int SRID = NO_SRID; try { wkt = wkt.trim(); // WKT is actually an extended EWKT with SRID before the WKT - if (wkt.startsWith( "SRID" )) { + if ( wkt.startsWith( "SRID" ) ) { // in WKT semicolon is invalid character, so we could safely split by it String[] ewktParts = wkt.split( ";" ); - this.SRID = Integer.valueOf( ewktParts[0].replace( "SRID=", "" ) ); + SRID = Integer.parseInt( ewktParts[0].replace( "SRID=", "" ) ); wkt = ewktParts[1]; } - this.jtsGeometry = reader.read( wkt ); - if (!jtsGeometry.isValid()) { - throw new ParseException("Provided geometry is not valid."); - } - this.jtsGeometry.setSRID( this.SRID ); - } catch ( ParseException | NumberFormatException e) { - throw new InvalidGeometryException(e.getMessage()); + } catch ( NumberFormatException e ) { + throw new InvalidGeometryException( e.getMessage() ); } - this.geometryType = getPolyGeometryType(); + init(wkt, SRID); + } + + /** + * Constructor creates the {@link Geometry} from the WKT text using the provided SRID. + * + * @param wkt Well Know Text representation of the geometry + * @param SRID Spatial reference system of the geometry + * @throws InvalidGeometryException if {@link Geometry} is invalid or provided WKT is invalid. + */ + public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { + this( PolyType.GEOMETRY ); + init( wkt, SRID ); } public PolyGeometry( Geometry geometry ) { @@ -109,33 +117,61 @@ public PolyGeometry( Geometry geometry ) { this.geometryType = getPolyGeometryType(); } + protected PolyGeometry( PolyType type ) { super( type ); } + private void init( String wkt, int SRID) throws InvalidGeometryException { + this.SRID = SRID; + WKTReader reader = new WKTReader(); + try { + this.jtsGeometry = reader.read( wkt ); + if ( !jtsGeometry.isValid() ) { + throw new ParseException( "Provided geometry is not valid." ); + } + this.jtsGeometry.setSRID( this.SRID ); + } catch ( ParseException | IllegalArgumentException e) { + // IllegalArgumentException is thrown in case geometry conditions are not met + throw new InvalidGeometryException( e.getMessage() ); + } + this.geometryType = getPolyGeometryType(); + } + + public static PolyGeometry of( String wkt ) throws InvalidGeometryException { return new PolyGeometry( wkt ); } + public static PolyGeometry of( String wkt, int SRID ) throws InvalidGeometryException { + return new PolyGeometry( wkt, SRID ); + } + + public static PolyGeometry of( Geometry geometry ) { return new PolyGeometry( geometry ); } + protected PolyGeometryType getPolyGeometryType() { switch ( jtsGeometry.getGeometryType() ) { case "Point": return PolyGeometryType.POINT; case "LineString": return PolyGeometryType.LINESTRING; + case "LinearRing": + return PolyGeometryType.LINEAR_RING; default: throw new NotImplementedException( "value" ); } } + public boolean isPoint() { return geometryType.equals( PolyGeometryType.POINT ); } + @NotNull public PolyPoint asPoint() { if ( isPoint() ) { @@ -144,6 +180,32 @@ public PolyPoint asPoint() { throw cannotParse( this, PolyPoint.class ); } + public boolean isLineString() { + return geometryType.equals( PolyGeometryType.LINESTRING ); + } + + + @NotNull + public PolyLineString asLineString() { + if ( isLineString() ) { + return PolyLineString.of( jtsGeometry ); + } + throw cannotParse( this, PolyLineString.class ); + } + + public boolean isLinearRing() { + return geometryType.equals( PolyGeometryType.LINEAR_RING ); + } + + + @NotNull + public PolyLinearRing asLinearRing() { + if ( isLinearRing() ) { + return PolyLinearRing.of( jtsGeometry ); + } + throw cannotParse( this, PolyLinearRing.class ); + } + /** * Tests whether this {@link Geometry} is simple. @@ -154,8 +216,54 @@ public boolean isSimple() { } + /** + * @return true if the set of points covered by this {@link Geometry} is empty. + */ + public boolean isEmpty() { + return jtsGeometry.isEmpty(); + } + + + /** + * @return the count of this {@link Geometry} vertices. + */ + public int getNumPoints() { + return jtsGeometry.getNumPoints(); + } + + + /** + * @return the dimension of this {@link Geometry}. + */ + public int getDimension() { + return jtsGeometry.getDimension(); + } + + + /** + * Linear geometries return their length. + * Areal geometries return their perimeter. + * + * @return the length of this {@link Geometry} + */ + public double getLength() { + return jtsGeometry.getLength(); + } + + + /** + * Areal Geometries have a non-zero area. + * + * @return the area of this {@link Geometry} + */ + public double getArea() { + return jtsGeometry.getArea(); + } + + /** * Bound the {@link Geometry} my the minimum box that could fit this geometry. + * * @return {@link PolyGeometry} with minimum bounding box */ public PolyGeometry getMinimumBoundingBox() { @@ -169,11 +277,11 @@ public PolyGeometry getMinimumBoundingBox() { */ @Override public boolean equals( Object o ) { - if ( !( o instanceof PolyGeometry )) { + if ( !(o instanceof PolyGeometry) ) { return false; } PolyGeometry that = (PolyGeometry) o; - return jtsGeometry.equals( that.jtsGeometry ) && Objects.equals( SRID, that.SRID ); + return geometryType.equals( that.geometryType ) && jtsGeometry.equals( that.jtsGeometry ) && Objects.equals( SRID, that.SRID ); } @@ -217,9 +325,10 @@ public int compareTo( @NotNull PolyValue o ) { */ @Override public String toString() { - return String.format( "SRID=%d;%s" , SRID, jtsGeometry.toString() ); + return String.format( "SRID=%d;%s", SRID, jtsGeometry.toString() ); } + public static class PolyGeometrySerializerDef extends SimpleSerializerDef { @Override @@ -230,6 +339,7 @@ public void encode( BinaryOutput out, PolyGeometry item ) { out.writeUTF8( item.toString() ); } + @Override public PolyGeometry decode( BinaryInput in ) throws CorruptedDataException { try { diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java index efb99b0e9e..8d6b622991 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java @@ -16,8 +16,12 @@ package org.polypheny.db.type.entity.spatial; +/** + * Represent Geometry types supported by Polypheny. + */ public enum PolyGeometryType { POINT, - LINESTRING + LINESTRING, + LINEAR_RING } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java new file mode 100644 index 0000000000..bffbee4a19 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java @@ -0,0 +1,139 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Point; +import org.polypheny.db.type.PolyType; + +/** + * Represent a connected sequence of points (line segments) in the space. + * The line segments may intersect other segments. + * If line segments do not intersect each other, the line is simple + * The line may start and end at the same point. In this case, it is called closed. + * The {@link PolyLineString} is valid if it has + * either 0 or 2 or more points. + * The {@link PolyLineString} could store up to 4 dimensions. + * {@link PolyLineString} is a base class for various types of lines. + */ +public class PolyLineString extends PolyGeometry { + + private LineString jtsLineString; + + + public PolyLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.LINESTRING; + this.jtsLineString = (LineString) jtsGeometry; + } + + public PolyLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { + super( wkt, SRID ); + this.geometryType = PolyGeometryType.LINESTRING; + this.jtsLineString = (LineString) jtsGeometry; + } + + + protected PolyLineString( Geometry geometry ) { + super( PolyType.GEOMETRY ); + this.geometryType = PolyGeometryType.LINESTRING; + this.jtsGeometry = geometry; + this.jtsLineString = (LineString) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + + protected PolyLineString( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.LINESTRING; + } + + + public static PolyLineString of( Geometry geometry ) { + return new PolyLineString( geometry ); + } + + + /** + * Test whether {@link PolyLineString} is closed: line starts and ends at the same point. + * + * @return true if {@link LineString} is closed. + */ + public boolean isClosed() { + return jtsLineString.isClosed(); + } + + + /** + * Test whether {@link PolyLineString} is a ring: simple and closed at the same time. + * + * @return true if {@link LineString} is ring. + */ + public boolean isRing() { + return jtsLineString.isRing(); + } + + + /** + * Test whether {@link PolyPoint} is a vertex of this {@link PolyLineString}. + * + * @param point coordinate to test + * @return true if {@link PolyPoint} is part of this {@link PolyLineString}. + */ + public boolean isCoordinate( PolyPoint point ) { + return jtsLineString.isCoordinate( point.jtsGeometry.getCoordinate() ); + } + + + /** + * Get the nth point in the sequence. + * + * @param n number of the point in the sequence + * @return the nth point in the sequence. + */ + public PolyPoint getPoint( int n ) { + return PolyPoint.of( jtsLineString.getPointN( n ) ); + } + + + /** + * @return the first (start) point in the sequence + */ + public PolyPoint getStartPoint() { + return PolyPoint.of( jtsLineString.getStartPoint() ); + } + + + /** + * @return the last (end) point in the sequence + */ + public PolyPoint getEndPoint() { + return PolyPoint.of( jtsLineString.getEndPoint() ); + } + + + /** + * @return new {@link PolyLineString} with coordinates in a reverse order. + */ + public PolyLineString reverse() { + return PolyLineString.of( jtsLineString.reverse() ); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java new file mode 100644 index 0000000000..9b96c29437 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java @@ -0,0 +1,66 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.polypheny.db.type.PolyType; + +/** + * {@link PolyLinearRing} is {@link PolyLineString} that is simple and closed. + * The {@link PolyLinearRing} is valid if it has + * either 0 or 3 or more points. + */ +public class PolyLinearRing extends PolyLineString { + + private LinearRing jtsLinearRing; + + public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.LINEAR_RING; + this.jtsLinearRing = (LinearRing) jtsGeometry; + } + + public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { + super( wkt, SRID ); + this.geometryType = PolyGeometryType.LINEAR_RING; + this.jtsLinearRing = (LinearRing) jtsGeometry; + } + + + protected PolyLinearRing( Geometry geometry ) { + super( geometry ); + this.geometryType = PolyGeometryType.LINEAR_RING; + this.jtsGeometry = geometry; + this.jtsLinearRing = (LinearRing) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + + protected PolyLinearRing( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.LINEAR_RING; + } + + public static PolyLinearRing of( Geometry geometry ) { + return new PolyLinearRing( geometry ); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java index 2f1083bf2e..67a96c5864 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -38,6 +38,12 @@ public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws I this.jtsPoint = (Point) jtsGeometry; } + public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { + super( wkt, SRID ); + this.geometryType = PolyGeometryType.POINT; + this.jtsPoint = (Point) jtsGeometry; + } + protected PolyPoint( Geometry geometry ) { super( PolyType.GEOMETRY ); this.geometryType = PolyGeometryType.POINT; diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java new file mode 100644 index 0000000000..d155ea6b2a --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java @@ -0,0 +1,27 @@ +/* + * 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.type.spatial; + +public class GeometryConstants { + + static final String POINT_EWKT = "SRID=4326;POINT (13.4050 52.5200)"; + static final String POINT_WKT = "POINT (13.4050 52.5200 36.754)"; + static final String LINESTRING_WKT = "LINESTRING(-1 -1, 2 2, 4 5, 6 7)"; + static final String LINEAR_RING_WKT = "LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)"; + static final double DELTA = 1e-5; + static final int NO_SRID = 0; +} diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java index dbef0409d9..fa41a0cf98 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.polypheny.db.TestHelper; @@ -31,72 +32,128 @@ import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; import org.polypheny.db.type.entity.spatial.PolyGeometryType; +import org.polypheny.db.type.entity.spatial.PolyLineString; +import org.polypheny.db.type.entity.spatial.PolyLinearRing; import org.polypheny.db.type.entity.spatial.PolyPoint; public class GeometryTest { - private static final String VALID_POINT_EWKT = "SRID=4326;POINT (13.4050 52.5200)"; - private static final String VALID_POINT_WKT = "POINT (13.4050 52.5200 36.754)"; - private static final double DELTA = 1e-15; - private static final int NO_SRID = 0; + private PolyGeometry point2d; + private PolyGeometry point3d; + private PolyGeometry lineString; + private PolyGeometry linearRing; + @BeforeClass public static void start() { TestHelper.getInstance(); } + @Before + public void prepareGeometries() throws InvalidGeometryException { + point2d = PolyGeometry.of( GeometryConstants.POINT_EWKT ); + point3d = PolyGeometry.of( GeometryConstants.POINT_WKT ); + lineString = PolyGeometry.of( GeometryConstants.LINESTRING_WKT ); + linearRing = PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ); + } + + @Test - public void testGeometryValidity() { - // Point - assertAll( - "Group assertions of valid Point in EWKT", - () -> assertDoesNotThrow( () -> PolyGeometry.of( VALID_POINT_EWKT ) ), - () -> { - PolyGeometry geometry = PolyGeometry.of( VALID_POINT_EWKT ); - assertEquals( PolyGeometryType.POINT, geometry.getGeometryType() ); - assertEquals( 4326, (long) geometry.getSRID() ); - PolyPoint point = geometry.asPoint(); - assertEquals( 13.4050, point.getX(), DELTA ); - assertEquals( 52.5200, point.getY(), DELTA ); - assertFalse( point.hasZ() ); - assertFalse( point.hasM() ); - }); - - assertAll( - "Group assertions of valid Point in WKT", - () -> assertDoesNotThrow(() -> PolyGeometry.of( VALID_POINT_WKT )), - () -> { - PolyGeometry geometry = PolyGeometry.of( VALID_POINT_WKT ); - assertEquals( PolyGeometryType.POINT, geometry.getGeometryType() ); - assertEquals( NO_SRID, (long) geometry.getSRID() ); - PolyPoint point = geometry.asPoint(); - assertEquals( 13.4050, point.getX(), DELTA ); - assertEquals( 52.5200, point.getY(), DELTA ); - assertTrue( point.hasZ() ); - assertEquals( 36.754, point.getZ(), DELTA ); - assertFalse( point.hasM() ); - }); + public void testPointValidity() { + assertAll( "Group assertions of valid Point in EWKT", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.POINT_EWKT ) ), + () -> { + assertEquals( PolyGeometryType.POINT, point2d.getGeometryType() ); + assertEquals( 4326, (long) point2d.getSRID() ); + PolyPoint point = point2d.asPoint(); + 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", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.POINT_WKT ) ), + () -> { + assertEquals( PolyGeometryType.POINT, point3d.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) point3d.getSRID() ); + PolyPoint point = point3d.asPoint(); + assertEquals( 13.4050, point.getX(), GeometryConstants.DELTA ); + 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)" ) ); assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POINT (13.4050 13.4050 13.4050 13.4050 13.4050)" ) ); } + + @Test + public void testLineStringValidity() { + assertAll( "Group assertions of valid LineString", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.LINESTRING_WKT ) ), + () -> { + assertEquals( PolyGeometryType.LINESTRING, lineString.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) lineString.getSRID() ); + PolyLineString line = lineString.asLineString(); + assertEquals( 10.6766191, line.getLength(), GeometryConstants.DELTA ); + assertEquals( 4, line.getNumPoints() ); + assertEquals( PolyPoint.of( "POINT(6 7)" ), line.getEndPoint() ); + assertFalse( line.isEmpty() ); + assertTrue( line.isSimple() ); + assertFalse( line.isClosed() ); + } ); + + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINESTRING(0 0)" ) ); + } + + @Test + public void testLinearRingValidity() { + assertAll( "Group assertions of valid LinearRing", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ) ), + () -> { + assertEquals( PolyGeometryType.LINEAR_RING, linearRing.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) linearRing.getSRID() ); + PolyLinearRing ring = linearRing.asLinearRing(); + assertTrue( ring.isRing() ); + assertEquals( 40, ring.getLength(), GeometryConstants.DELTA ); + assertEquals( 5, ring.getNumPoints() ); + } ); + + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINEARRING(0 0, 0 10, 10 10, 10 5, 5 5)" ) ); + } + + @Test public void testPointsEquality() throws InvalidGeometryException { - PolyGeometry point1 = PolyGeometry.of( VALID_POINT_EWKT ); - PolyGeometry point2 = PolyGeometry.of( VALID_POINT_EWKT ); - PolyGeometry point3 = PolyGeometry.of( VALID_POINT_WKT ); - assertEquals( point1, point2 ); - assertNotEquals( point1, point3 ); + PolyGeometry point2 = PolyGeometry.of( GeometryConstants.POINT_EWKT ); + assertEquals( point2d, point2 ); + assertNotEquals( point2d, point3d ); } @Test - public void testSerializability() throws InvalidGeometryException { - PolyGeometry geometry = PolyGeometry.of( VALID_POINT_EWKT ); - String serialized = geometry.serialize(); + public void testLineStringEquality() throws InvalidGeometryException { + PolyGeometry line = PolyGeometry.of( GeometryConstants.LINESTRING_WKT ); + assertEquals( lineString, line ); + assertNotEquals( lineString, linearRing ); + } + + @Test + public void testLinearRingEquality() throws InvalidGeometryException { + PolyGeometry ring = PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ); + assertEquals( linearRing, ring ); + assertNotEquals( linearRing, lineString ); + } + + + @Test + public void testSerializability() { + String serialized = point2d.serialize(); PolyValue polyValue = PolyGeometry.deserialize( serialized ); PolyGeometry deserializedGeometry = polyValue.asGeometry(); - assertEquals( geometry, deserializedGeometry ); + assertEquals( point2d, deserializedGeometry ); } } \ No newline at end of file From 47f040c8baf2b7de3472c483bb49e814acf29532 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 2 Nov 2023 20:07:57 +0100 Subject: [PATCH 05/16] GIS #462: added Polygons and test for them --- .../db/type/entity/spatial/PolyGeometry.java | 176 +++++++++++++++--- .../type/entity/spatial/PolyGeometryType.java | 53 +++++- .../type/entity/spatial/PolyLineString.java | 11 +- .../type/entity/spatial/PolyLinearRing.java | 4 +- .../db/type/entity/spatial/PolyPoint.java | 12 ++ .../db/type/entity/spatial/PolyPolygon.java | 89 +++++++++ .../db/type/spatial/GeometryConstants.java | 1 + .../db/type/spatial/GeometryTest.java | 22 +++ 8 files changed, 324 insertions(+), 44 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index c962049d2e..28c6a58685 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -37,6 +37,7 @@ import org.polypheny.db.type.PolySerializable; import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyValue; +import org.polypheny.db.type.entity.spatial.PolyGeometryType.BufferCapStyle; /** * {@link PolyGeometry} is an abstraction for all spatial data types. @@ -54,11 +55,6 @@ public class PolyGeometry extends PolyValue { /** * Wrap the JTS {@link Geometry} class. - *

- * It is possible to extract useful information from the {@link Geometry}: - * jtsGeometry.getGeometryType() - * jtsGeometry.getSRID() - * jtsGeometry.getCoordinate() */ @Getter protected Geometry jtsGeometry; @@ -71,14 +67,14 @@ public class PolyGeometry extends PolyValue { /** - * Constructor creates the {@link Geometry} from the WKT text. + * Constructor creates the {@link PolyGeometry} from the WKT text. *

* The WKT may contain SRID in the text, e.g. "SRID=4326;POINT (13.4050 52.5200)". * In this case SRID from the WKT would be used, otherwise the default one is selected. *

* * @param wkt Well Know Text representation of the geometry - * @throws InvalidGeometryException if {@link Geometry} is invalid or provided WKT is invalid. + * @throws InvalidGeometryException if {@link PolyGeometry} is invalid or provided WKT is invalid. */ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { this( PolyType.GEOMETRY ); @@ -95,21 +91,23 @@ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throw } catch ( NumberFormatException e ) { throw new InvalidGeometryException( e.getMessage() ); } - init(wkt, SRID); + init( wkt, SRID ); } + /** - * Constructor creates the {@link Geometry} from the WKT text using the provided SRID. + * Constructor creates the {@link PolyGeometry} from the WKT text using the provided SRID. * * @param wkt Well Know Text representation of the geometry * @param SRID Spatial reference system of the geometry - * @throws InvalidGeometryException if {@link Geometry} is invalid or provided WKT is invalid. + * @throws InvalidGeometryException if {@link PolyGeometry} is invalid or provided WKT is invalid. */ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { this( PolyType.GEOMETRY ); init( wkt, SRID ); } + public PolyGeometry( Geometry geometry ) { super( PolyType.GEOMETRY ); this.jtsGeometry = geometry; @@ -122,27 +120,12 @@ protected PolyGeometry( PolyType type ) { super( type ); } - private void init( String wkt, int SRID) throws InvalidGeometryException { - this.SRID = SRID; - WKTReader reader = new WKTReader(); - try { - this.jtsGeometry = reader.read( wkt ); - if ( !jtsGeometry.isValid() ) { - throw new ParseException( "Provided geometry is not valid." ); - } - this.jtsGeometry.setSRID( this.SRID ); - } catch ( ParseException | IllegalArgumentException e) { - // IllegalArgumentException is thrown in case geometry conditions are not met - throw new InvalidGeometryException( e.getMessage() ); - } - this.geometryType = getPolyGeometryType(); - } - public static PolyGeometry of( String wkt ) throws InvalidGeometryException { return new PolyGeometry( wkt ); } + public static PolyGeometry of( String wkt, int SRID ) throws InvalidGeometryException { return new PolyGeometry( wkt, SRID ); } @@ -153,6 +136,23 @@ public static PolyGeometry of( Geometry geometry ) { } + private void init( String wkt, int SRID ) throws InvalidGeometryException { + this.SRID = SRID; + WKTReader reader = new WKTReader(); + try { + this.jtsGeometry = reader.read( wkt ); + if ( !jtsGeometry.isValid() ) { + throw new ParseException( "Provided geometry is not valid." ); + } + this.jtsGeometry.setSRID( this.SRID ); + } catch ( ParseException | IllegalArgumentException e ) { + // IllegalArgumentException is thrown in case geometry conditions are not met + throw new InvalidGeometryException( e.getMessage() ); + } + this.geometryType = getPolyGeometryType(); + } + + protected PolyGeometryType getPolyGeometryType() { switch ( jtsGeometry.getGeometryType() ) { case "Point": @@ -161,6 +161,8 @@ protected PolyGeometryType getPolyGeometryType() { return PolyGeometryType.LINESTRING; case "LinearRing": return PolyGeometryType.LINEAR_RING; + case "Polygon": + return PolyGeometryType.POLYGON; default: throw new NotImplementedException( "value" ); } @@ -180,6 +182,7 @@ public PolyPoint asPoint() { throw cannotParse( this, PolyPoint.class ); } + public boolean isLineString() { return geometryType.equals( PolyGeometryType.LINESTRING ); } @@ -193,6 +196,7 @@ public PolyLineString asLineString() { throw cannotParse( this, PolyLineString.class ); } + public boolean isLinearRing() { return geometryType.equals( PolyGeometryType.LINEAR_RING ); } @@ -207,8 +211,23 @@ public PolyLinearRing asLinearRing() { } + public boolean isPolygon() { + return geometryType.equals( PolyGeometryType.POLYGON ); + } + + + @NotNull + public PolyPolygon asPolygon() { + if ( isPolygon() ) { + return PolyPolygon.of( jtsGeometry ); + } + throw cannotParse( this, PolyPolygon.class ); + } + + /** * Tests whether this {@link Geometry} is simple. + * * @return true if {@link Geometry} is simple. */ public boolean isSimple() { @@ -239,10 +258,16 @@ public int getDimension() { return jtsGeometry.getDimension(); } + // TODO: add geometry collections (@link) to documentation + /** * Linear geometries return their length. * Areal geometries return their perimeter. + * The length of a {@link PolyPoint} or {link PolyMultiPoint} is 0. + * The length of a {@link PolyLineString} is the sum of the lengths of each line segment: distance from the start point to the end point + * The length of a {@link PolyPolygon} is the sum of the lengths of the exterior boundary and any interior boundaries. + * The length of any {link GeometryCollection} is the sum of the lengths of all {@link PolyGeometry} it contains. * * @return the length of this {@link Geometry} */ @@ -262,17 +287,108 @@ public double getArea() { /** - * Bound the {@link Geometry} my the minimum box that could fit this geometry. + * Bound the {@link PolyGeometry} my the minimum box that could fit this geometry. * * @return {@link PolyGeometry} with minimum bounding box */ - public PolyGeometry getMinimumBoundingBox() { + public PolyGeometry getEnvelope() { return PolyGeometry.of( jtsGeometry.getEnvelope() ); } /** - * {@link #equals(Object) equals} ensures that the {@link Geometry} types and coordinates are the same. + * The boundary of a {@link PolyGeometry} is a set of {@link PolyGeometry}s of the next lower dimension + * that define the limit of this {@link PolyGeometry}. + * + * @return the closure of the combinatorial boundary of this {@link PolyGeometry} + */ + public PolyGeometry getBoundary() { + return PolyGeometry.of( jtsGeometry.getBoundary() ); + } + + + /** + * Calculate the smallest convex {@link PolyGeometry} that contains this {@link PolyGeometry}. + * @return the minimum area {@link PolyGeometry} containing all points in this {@link PolyGeometry} + */ + public PolyGeometry convexHull() { + return PolyGeometry.of( jtsGeometry.convexHull() ); + } + + + /** + * Computes the geometric center - centroid - of this {@link PolyGeometry}. + *

+ * When a geometry contains a combination of {@link PolyPoint}s, {@link PolyLineString}s, and {@link PolyPolygon}s, + * the centroid calculation is influenced solely by the {@link PolyPolygon}s within the {@link PolyGeometry}. + *

+ * Likewise, in the presence of both {@link PolyPoint}s and {@link PolyLineString}s within a {@link PolyGeometry}, + * the contribution of {@link PolyPoint}s to the centroid calculation is disregarded. + * + * @return {@link PolyPoint} that is the centroid of this {@link PolyGeometry} + */ + public PolyPoint getCentroid() { + return PolyPoint.of( jtsGeometry.getCentroid() ); + } + + + /** + * Compute a buffer area around this {@link PolyGeometry} within the given distance. + * The buffer maybe empty. + * The negative or zero-distance buffer of {@link PolyLineString}s and {@link PolyPoint}s + * is always an {@link PolyPolygon}. + * + * @param distance width of the buffer + * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} + */ + public PolyGeometry buffer( double distance ) { + return PolyPoint.of( jtsGeometry.buffer( distance ) ); + } + + + /** + * Compute a buffer area around this {@link PolyGeometry} within the given distance + * and accuracy of approximation for circular arcs. + * The buffer maybe empty. + * The negative or zero-distance buffer of {@link PolyLineString}s and {@link PolyPoint}s + * is always an {@link PolyPolygon}. + * + * @param distance width of the buffer + * @param quadrantSegments number of line segments to represent a quadrant of a circle + * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} + */ + public PolyGeometry buffer( double distance, int quadrantSegments ) { + return PolyPoint.of( jtsGeometry.buffer( distance, quadrantSegments ) ); + } + + + /** + * Compute a buffer area around this {@link PolyGeometry} within the given distance + * and accuracy of approximation for circular arcs, and use provided buffer end cap style. + * The buffer maybe empty. + * The negative or zero-distance buffer of {@link PolyLineString}s and {@link PolyPoint}s + * is an empty {@link PolyPolygon}. + * + * @param distance width of the buffer + * @param quadrantSegments number of line segments to represent a quadrant of a circle + * @param endCapStyle specifies how the buffer command terminates the end of a line. + * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} + */ + public PolyGeometry buffer( double distance, int quadrantSegments, BufferCapStyle endCapStyle ) { + return PolyPoint.of( jtsGeometry.buffer( distance, quadrantSegments, endCapStyle.code ) ); + } + + + /** + * @return new {@link PolyGeometry} with coordinates in a reverse order. + */ + public PolyGeometry reverse() { + return PolyGeometry.of( jtsGeometry.reverse() ); + } + + + /** + * {@link #equals(Object) equals} ensures that the {@link PolyGeometry} types and coordinates are the same. * And {@link #SRID} used for representation of coordinates are also identical. */ @Override @@ -321,7 +437,7 @@ public int compareTo( @NotNull PolyValue o ) { /** - * Output the {@link Geometry} in a WKT format with its SRID. So-called EWKT + * Output the {@link PolyGeometry} in a WKT format with its SRID. So-called EWKT */ @Override public String toString() { diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java index 8d6b622991..56fdbcf773 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java @@ -17,11 +17,56 @@ package org.polypheny.db.type.entity.spatial; /** - * Represent Geometry types supported by Polypheny. + * Geometry types, with the names and codes assigned by OGC. */ public enum PolyGeometryType { + GEOMETRY( 0 ), + POINT( 1 ), + LINESTRING( 2 ), + // not actually a full type + LINEAR_RING( 2 ), + POLYGON(3), + MULTIPOINT( 4 ), + MULTILINESTRING( 5 ), + MULTIPOLYGON( 6 ), + GEOMCOLLECTION( 7 ), + CURVE( 13 ), + SURFACE( 14 ), + POLYHEDRALSURFACE( 15 ); - POINT, - LINESTRING, - LINEAR_RING + final int code; + + + PolyGeometryType( int code ) { + this.code = code; + } + + + /** + * How the "buffer" command terminates the end of a line. + */ + public enum BufferCapStyle { + ROUND( 1 ), FLAT( 2 ), SQUARE( 3 ); + + final int code; + + + BufferCapStyle( int code ) { + this.code = code; + } + + + static BufferCapStyle of( String value ) { + switch ( value ) { + case "round": + return ROUND; + case "flat": + return FLAT; + case "square": + return SQUARE; + default: + throw new IllegalArgumentException( "unknown endcap value: " + value ); + } + } + } } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java index bffbee4a19..c37b7dd44c 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java @@ -20,7 +20,6 @@ import io.activej.serializer.annotations.Deserialize; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.Point; import org.polypheny.db.type.PolyType; /** @@ -28,6 +27,7 @@ * The line segments may intersect other segments. * If line segments do not intersect each other, the line is simple * The line may start and end at the same point. In this case, it is called closed. + *

* The {@link PolyLineString} is valid if it has * either 0 or 2 or more points. * The {@link PolyLineString} could store up to 4 dimensions. @@ -44,6 +44,7 @@ public PolyLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) thr this.jtsLineString = (LineString) jtsGeometry; } + public PolyLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { super( wkt, SRID ); this.geometryType = PolyGeometryType.LINESTRING; @@ -128,12 +129,4 @@ public PolyPoint getEndPoint() { return PolyPoint.of( jtsLineString.getEndPoint() ); } - - /** - * @return new {@link PolyLineString} with coordinates in a reverse order. - */ - public PolyLineString reverse() { - return PolyLineString.of( jtsLineString.reverse() ); - } - } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java index 9b96c29437..3277655f38 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.activej.serializer.annotations.Deserialize; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.LineString; import org.locationtech.jts.geom.LinearRing; import org.polypheny.db.type.PolyType; @@ -32,12 +31,14 @@ public class PolyLinearRing extends PolyLineString { private LinearRing jtsLinearRing; + public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { super( wkt ); this.geometryType = PolyGeometryType.LINEAR_RING; this.jtsLinearRing = (LinearRing) jtsGeometry; } + public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { super( wkt, SRID ); this.geometryType = PolyGeometryType.LINEAR_RING; @@ -59,6 +60,7 @@ protected PolyLinearRing( PolyType type ) { this.geometryType = PolyGeometryType.LINEAR_RING; } + public static PolyLinearRing of( Geometry geometry ) { return new PolyLinearRing( geometry ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java index 67a96c5864..5b49a4a751 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -24,6 +24,7 @@ /** * Represent a single point in the space. + *

* The {@link PolyPoint} is valid if * X and Y coordinates are provided. * The {@link PolyPoint} could store up to 4 dimensions @@ -32,18 +33,21 @@ public class PolyPoint extends PolyGeometry { private Point jtsPoint; + public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { super( wkt ); this.geometryType = PolyGeometryType.POINT; this.jtsPoint = (Point) jtsGeometry; } + public PolyPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { super( wkt, SRID ); this.geometryType = PolyGeometryType.POINT; this.jtsPoint = (Point) jtsGeometry; } + protected PolyPoint( Geometry geometry ) { super( PolyType.GEOMETRY ); this.geometryType = PolyGeometryType.POINT; @@ -52,35 +56,43 @@ protected PolyPoint( Geometry geometry ) { this.SRID = geometry.getSRID(); } + protected PolyPoint( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.POINT; } + public static PolyPoint of( Geometry geometry ) { return new PolyPoint( geometry ); } + public double getX() { return jtsPoint.getX(); } + public double getY() { return jtsPoint.getY(); } + public boolean hasZ() { return !Double.isNaN( jtsPoint.getCoordinate().getZ() ); } + public double getZ() { return jtsPoint.getCoordinate().getZ(); } + public boolean hasM() { return !Double.isNaN( jtsPoint.getCoordinate().getM() ); } + public double getM() { return jtsPoint.getCoordinate().getM(); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java new file mode 100644 index 0000000000..39b652f419 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java @@ -0,0 +1,89 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Polygon; +import org.polypheny.db.type.PolyType; + +/** + * Represent a polygon with linear edges, may contain holes. + * The outer boundary (shell) and inner boundaries (holes) are represented by {@link PolyLinearRing}. + * It means {@link PolyPolygon} is simple and closed. + * Interior holes should not split the {@link PolyPolygon} into more than one part. + *

+ * The {@link PolyPolygon} is valid if + * outer and inner boundaries are valid ({@link PolyLinearRing}) + * holes touch the shell or another hole at most one point + * Interior holes should not make the interior of the polygon disconnected + */ +public class PolyPolygon extends PolyGeometry { + + private Polygon jtsPolygon; + + + public PolyPolygon( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.POLYGON; + this.jtsPolygon = (Polygon) jtsGeometry; + } + + + public PolyPolygon( String wkt, int SRID ) throws InvalidGeometryException { + super( wkt, SRID ); + this.geometryType = PolyGeometryType.POLYGON; + this.jtsPolygon = (Polygon) jtsGeometry; + } + + + public PolyPolygon( Geometry geometry ) { + super( geometry ); + this.geometryType = PolyGeometryType.POLYGON; + this.jtsGeometry = geometry; + this.jtsPolygon = (Polygon) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + + protected PolyPolygon( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.POLYGON; + } + + + public static PolyPolygon of( Geometry geometry ) { + return new PolyPolygon( geometry ); + } + + + public PolyLinearRing getExteriorRing() { + return PolyLinearRing.of( jtsPolygon.getExteriorRing() ); + } + + + public int getNumInteriorRing() { + return jtsPolygon.getNumInteriorRing(); + } + + + public PolyLinearRing getInteriorRingN( int n ) { + return PolyLinearRing.of( jtsPolygon.getInteriorRingN( n ) ); + } + +} diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java index d155ea6b2a..c3aace1a66 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java @@ -22,6 +22,7 @@ public class GeometryConstants { static final String POINT_WKT = "POINT (13.4050 52.5200 36.754)"; static final String LINESTRING_WKT = "LINESTRING(-1 -1, 2 2, 4 5, 6 7)"; static final String LINEAR_RING_WKT = "LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)"; + static final String POLYGON_WKT = "POLYGON((-1 -1, 2 2, -1 2, -1 -1))"; static final double DELTA = 1e-5; static final int NO_SRID = 0; } diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java index fa41a0cf98..c5080ad9a0 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -35,6 +35,7 @@ import org.polypheny.db.type.entity.spatial.PolyLineString; import org.polypheny.db.type.entity.spatial.PolyLinearRing; import org.polypheny.db.type.entity.spatial.PolyPoint; +import org.polypheny.db.type.entity.spatial.PolyPolygon; public class GeometryTest { @@ -42,6 +43,7 @@ public class GeometryTest { private PolyGeometry point3d; private PolyGeometry lineString; private PolyGeometry linearRing; + private PolyGeometry polygon; @BeforeClass @@ -55,6 +57,7 @@ public void prepareGeometries() throws InvalidGeometryException { point3d = PolyGeometry.of( GeometryConstants.POINT_WKT ); lineString = PolyGeometry.of( GeometryConstants.LINESTRING_WKT ); linearRing = PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ); + polygon = PolyGeometry.of( GeometryConstants.POLYGON_WKT ); } @@ -125,6 +128,25 @@ public void testLinearRingValidity() { assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINEARRING(0 0, 0 10, 10 10, 10 5, 5 5)" ) ); } + @Test + public void testPolygonValidity() { + assertAll( "Group assertions of valid Polygon", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.POLYGON_WKT ) ), + () -> { + assertEquals( PolyGeometryType.POLYGON, polygon.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) polygon.getSRID() ); + PolyPolygon poly = polygon.asPolygon(); + assertEquals( PolyPoint.of( "POINT (-0 1)" ), poly.getCentroid() ); + assertEquals( PolyLinearRing.of( "LINEARRING (-1 -1, 2 2, -1 2, -1 -1)" ), poly.getBoundary() ); + assertEquals( poly.getExteriorRing(), poly.getBoundary() ); + assertEquals( 10.2426406, poly.getLength(), GeometryConstants.DELTA ); + assertEquals( 4.5, poly.getArea(), GeometryConstants.DELTA ); + assertEquals( 4, poly.getNumPoints() ); + } ); + + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POLYGON((-1 -1, 2 2, -1 1, -1 2, 2 2, -1 -1))" ) ); + } + @Test public void testPointsEquality() throws InvalidGeometryException { From 2d817d3a8a8c06d84693fa7e140de29ba8979d2e Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Fri, 3 Nov 2023 15:19:47 +0100 Subject: [PATCH 06/16] GIS #462: added GeometryCollections and tests for them --- .../db/type/entity/spatial/PolyGeometry.java | 91 ++++++++---- .../spatial/PolyGeometryCollection.java | 131 ++++++++++++++++++ .../type/entity/spatial/PolyGeometryType.java | 4 +- .../type/entity/spatial/PolyLineString.java | 4 +- .../type/entity/spatial/PolyLinearRing.java | 8 +- .../entity/spatial/PolyMultiLineString.java | 77 ++++++++++ .../type/entity/spatial/PolyMultiPoint.java | 69 +++++++++ .../type/entity/spatial/PolyMultiPolygon.java | 70 ++++++++++ .../db/type/spatial/GeometryConstants.java | 8 +- .../db/type/spatial/GeometryTest.java | 87 +++++++++++- 10 files changed, 506 insertions(+), 43 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 28c6a58685..2787708916 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -24,6 +24,7 @@ import io.activej.serializer.CorruptedDataException; import io.activej.serializer.SimpleSerializerDef; import io.activej.serializer.annotations.Deserialize; +import java.util.List; import java.util.Objects; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -78,20 +79,20 @@ public class PolyGeometry extends PolyValue { */ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { this( PolyType.GEOMETRY ); - int SRID = NO_SRID; + int srid = NO_SRID; try { wkt = wkt.trim(); // WKT is actually an extended EWKT with SRID before the WKT if ( wkt.startsWith( "SRID" ) ) { // in WKT semicolon is invalid character, so we could safely split by it String[] ewktParts = wkt.split( ";" ); - SRID = Integer.parseInt( ewktParts[0].replace( "SRID=", "" ) ); + srid = Integer.parseInt( ewktParts[0].replace( "SRID=", "" ) ); wkt = ewktParts[1]; } } catch ( NumberFormatException e ) { throw new InvalidGeometryException( e.getMessage() ); } - init( wkt, SRID ); + init( wkt, srid ); } @@ -99,12 +100,12 @@ public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throw * Constructor creates the {@link PolyGeometry} from the WKT text using the provided SRID. * * @param wkt Well Know Text representation of the geometry - * @param SRID Spatial reference system of the geometry + * @param srid Spatial reference system of the geometry * @throws InvalidGeometryException if {@link PolyGeometry} is invalid or provided WKT is invalid. */ - public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { + public PolyGeometry( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int srid ) throws InvalidGeometryException { this( PolyType.GEOMETRY ); - init( wkt, SRID ); + init( wkt, srid ); } @@ -126,8 +127,8 @@ public static PolyGeometry of( String wkt ) throws InvalidGeometryException { } - public static PolyGeometry of( String wkt, int SRID ) throws InvalidGeometryException { - return new PolyGeometry( wkt, SRID ); + public static PolyGeometry of( String wkt, int srid ) throws InvalidGeometryException { + return new PolyGeometry( wkt, srid ); } @@ -136,8 +137,8 @@ public static PolyGeometry of( Geometry geometry ) { } - private void init( String wkt, int SRID ) throws InvalidGeometryException { - this.SRID = SRID; + private void init( String wkt, int srid ) throws InvalidGeometryException { + this.SRID = srid; WKTReader reader = new WKTReader(); try { this.jtsGeometry = reader.read( wkt ); @@ -160,9 +161,17 @@ protected PolyGeometryType getPolyGeometryType() { case "LineString": return PolyGeometryType.LINESTRING; case "LinearRing": - return PolyGeometryType.LINEAR_RING; + return PolyGeometryType.LINEARRING; case "Polygon": return PolyGeometryType.POLYGON; + case "GeometryCollection": + return PolyGeometryType.GEOMETRYCOLLECTION; + case "MultiPoint": + return PolyGeometryType.MULTIPOINT; + case "MultiLineString": + return PolyGeometryType.MULTILINESTRING; + case "MultiPolygon": + return PolyGeometryType.MULTIPOLYGON; default: throw new NotImplementedException( "value" ); } @@ -198,7 +207,7 @@ public PolyLineString asLineString() { public boolean isLinearRing() { - return geometryType.equals( PolyGeometryType.LINEAR_RING ); + return geometryType.equals( PolyGeometryType.LINEARRING ); } @@ -225,10 +234,25 @@ public PolyPolygon asPolygon() { } + public boolean isGeometryCollection() { + return List.of( PolyGeometryType.GEOMETRYCOLLECTION, PolyGeometryType.MULTIPOINT, PolyGeometryType.MULTILINESTRING, PolyGeometryType.MULTIPOLYGON ).contains( geometryType ); + } + + + @NotNull + public PolyGeometryCollection asGeometryCollection() { + if ( isGeometryCollection() ) { + return PolyGeometryCollection.of( jtsGeometry ); + } + throw cannotParse( this, PolyGeometryCollection.class ); + } + + /** - * Tests whether this {@link Geometry} is simple. + * Tests whether this {@link PolyGeometry} is a simple geometry: does not intersect itself. + * May touch its own boundary at any point. * - * @return true if {@link Geometry} is simple. + * @return true if {@link PolyGeometry} is simple. */ public boolean isSimple() { return jtsGeometry.isSimple(); @@ -236,7 +260,7 @@ public boolean isSimple() { /** - * @return true if the set of points covered by this {@link Geometry} is empty. + * @return true if the set of points covered by this {@link PolyGeometry} is empty. */ public boolean isEmpty() { return jtsGeometry.isEmpty(); @@ -244,7 +268,7 @@ public boolean isEmpty() { /** - * @return the count of this {@link Geometry} vertices. + * @return the count of this {@link PolyGeometry} vertices. */ public int getNumPoints() { return jtsGeometry.getNumPoints(); @@ -252,24 +276,22 @@ public int getNumPoints() { /** - * @return the dimension of this {@link Geometry}. + * @return the dimension of this {@link PolyGeometry}. */ public int getDimension() { return jtsGeometry.getDimension(); } - // TODO: add geometry collections (@link) to documentation - /** * Linear geometries return their length. * Areal geometries return their perimeter. - * The length of a {@link PolyPoint} or {link PolyMultiPoint} is 0. - * The length of a {@link PolyLineString} is the sum of the lengths of each line segment: distance from the start point to the end point - * The length of a {@link PolyPolygon} is the sum of the lengths of the exterior boundary and any interior boundaries. - * The length of any {link GeometryCollection} is the sum of the lengths of all {@link PolyGeometry} it contains. + * The length of a {@link PolyPoint} or {@link PolyMultiPoint} is 0. + * The length of a {@link PolyLineString} or {@link PolyMultiLineString} is the sum of the lengths of each line segment: distance from the start point to the end point + * The length of a {@link PolyPolygon} or {@link PolyMultiPolygon} is the sum of the lengths of the exterior boundary and any interior boundaries. + * The length of any {@link PolyGeometryCollection} is the sum of the lengths of all {@link PolyGeometry} it contains. * - * @return the length of this {@link Geometry} + * @return the length of this {@link PolyGeometry} */ public double getLength() { return jtsGeometry.getLength(); @@ -279,7 +301,7 @@ public double getLength() { /** * Areal Geometries have a non-zero area. * - * @return the area of this {@link Geometry} + * @return the area of this {@link PolyGeometry} */ public double getArea() { return jtsGeometry.getArea(); @@ -287,7 +309,7 @@ public double getArea() { /** - * Bound the {@link PolyGeometry} my the minimum box that could fit this geometry. + * Bound the {@link PolyGeometry} by the minimum box that could fit this geometry. * * @return {@link PolyGeometry} with minimum bounding box */ @@ -298,17 +320,28 @@ public PolyGeometry getEnvelope() { /** * The boundary of a {@link PolyGeometry} is a set of {@link PolyGeometry}s of the next lower dimension - * that define the limit of this {@link PolyGeometry}. + * that define the limit of this {@link PolyGeometry}: + * For {@link PolyLineString} the boundary is start and end {@link PolyPoint}: {@link PolyMultiPoint}. + * For {@link PolyPolygon} the boundary is an exterior shell {@link PolyLinearRing}. * - * @return the closure of the combinatorial boundary of this {@link PolyGeometry} + * @return the set of the combinatorial boundary {@link PolyGeometry} that define the limit of this {@link PolyGeometry} */ public PolyGeometry getBoundary() { return PolyGeometry.of( jtsGeometry.getBoundary() ); } + /** + * @return the dimension of the boundary of this {@link PolyGeometry}. + */ + public int getBoundaryDimension() { + return jtsGeometry.getBoundaryDimension(); + } + + /** * Calculate the smallest convex {@link PolyGeometry} that contains this {@link PolyGeometry}. + * * @return the minimum area {@link PolyGeometry} containing all points in this {@link PolyGeometry} */ public PolyGeometry convexHull() { @@ -342,7 +375,7 @@ public PolyPoint getCentroid() { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance ) { - return PolyPoint.of( jtsGeometry.buffer( distance ) ); + return PolyPoint.of( jtsGeometry.buffer( distance ) ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java new file mode 100644 index 0000000000..b24d450215 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java @@ -0,0 +1,131 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.jetbrains.annotations.NotNull; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.polypheny.db.type.PolyType; + +/** + * {@link PolyGeometryCollection} is collection that holds {@link PolyGeometry} + * and it is a base class for all Multi spatial data types. + *

+ * Collection of {@link PolyGeometry} of any {@link PolyGeometryType} and dimension. + */ +public class PolyGeometryCollection extends PolyGeometry { + + private GeometryCollection jtsGeometryCollection; + + + public PolyGeometryCollection( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = getPolyGeometryType(); + this.jtsGeometryCollection = (GeometryCollection) jtsGeometry; + } + + + public PolyGeometryCollection( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int srid ) throws InvalidGeometryException { + super( wkt, srid ); + this.geometryType = getPolyGeometryType(); + this.jtsGeometryCollection = (GeometryCollection) jtsGeometry; + } + + + public PolyGeometryCollection( Geometry geometry ) { + super( geometry ); + this.geometryType = getPolyGeometryType(); + this.jtsGeometry = geometry; + this.jtsGeometryCollection = (GeometryCollection) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + + protected PolyGeometryCollection( PolyType type ) { + super( type ); + this.geometryType = getPolyGeometryType(); + } + + + public static PolyGeometryCollection of( Geometry geometry ) { + return new PolyGeometryCollection( geometry ); + } + + + /** + * @return the number of {@link PolyGeometry} geometries in the {@link PolyGeometryCollection}. + */ + public int getNumGeometries() { + return jtsGeometryCollection.getNumGeometries(); + } + + + /** + * Get the nth {@link PolyGeometry} in the collection. + * + * @param n number of the {@link PolyGeometry} in the collection + * @return the nth {@link PolyGeometry} in the collection. + */ + public PolyGeometry getGeometryN( int n ) { + return PolyGeometry.of( jtsGeometryCollection.getGeometryN( n ) ); + } + + + public boolean isMultiPoint() { + return geometryType.equals( PolyGeometryType.MULTIPOINT ); + } + + + @NotNull + public PolyMultiPoint asMultiPoint() { + if ( isMultiPoint() ) { + return PolyMultiPoint.of( jtsGeometry ); + } + throw cannotParse( this, PolyMultiPoint.class ); + } + + + public boolean isMultiLineString() { + return geometryType.equals( PolyGeometryType.MULTILINESTRING ); + } + + + @NotNull + public PolyMultiLineString asMultiLineString() { + if ( isMultiLineString() ) { + return PolyMultiLineString.of( jtsGeometry ); + } + throw cannotParse( this, PolyMultiLineString.class ); + } + + + public boolean isMultiPolygon() { + return geometryType.equals( PolyGeometryType.MULTIPOLYGON ); + } + + + @NotNull + public PolyMultiPolygon asMultiPolygon() { + if ( isMultiPolygon() ) { + return PolyMultiPolygon.of( jtsGeometry ); + } + throw cannotParse( this, PolyMultiPolygon.class ); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java index 56fdbcf773..cee3083c3a 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java @@ -24,12 +24,12 @@ public enum PolyGeometryType { POINT( 1 ), LINESTRING( 2 ), // not actually a full type - LINEAR_RING( 2 ), + LINEARRING( 2 ), POLYGON(3), MULTIPOINT( 4 ), MULTILINESTRING( 5 ), MULTIPOLYGON( 6 ), - GEOMCOLLECTION( 7 ), + GEOMETRYCOLLECTION( 7 ), CURVE( 13 ), SURFACE( 14 ), POLYHEDRALSURFACE( 15 ); diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java index c37b7dd44c..a67af10ef8 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java @@ -75,7 +75,7 @@ public static PolyLineString of( Geometry geometry ) { /** * Test whether {@link PolyLineString} is closed: line starts and ends at the same point. * - * @return true if {@link LineString} is closed. + * @return true if {@link PolyLineString} is closed. */ public boolean isClosed() { return jtsLineString.isClosed(); @@ -85,7 +85,7 @@ public boolean isClosed() { /** * Test whether {@link PolyLineString} is a ring: simple and closed at the same time. * - * @return true if {@link LineString} is ring. + * @return true if {@link PolyLineString} is ring. */ public boolean isRing() { return jtsLineString.isRing(); diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java index 3277655f38..331b604dff 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java @@ -34,21 +34,21 @@ public class PolyLinearRing extends PolyLineString { public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { super( wkt ); - this.geometryType = PolyGeometryType.LINEAR_RING; + this.geometryType = PolyGeometryType.LINEARRING; this.jtsLinearRing = (LinearRing) jtsGeometry; } public PolyLinearRing( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int SRID ) throws InvalidGeometryException { super( wkt, SRID ); - this.geometryType = PolyGeometryType.LINEAR_RING; + this.geometryType = PolyGeometryType.LINEARRING; this.jtsLinearRing = (LinearRing) jtsGeometry; } protected PolyLinearRing( Geometry geometry ) { super( geometry ); - this.geometryType = PolyGeometryType.LINEAR_RING; + this.geometryType = PolyGeometryType.LINEARRING; this.jtsGeometry = geometry; this.jtsLinearRing = (LinearRing) jtsGeometry; this.SRID = geometry.getSRID(); @@ -57,7 +57,7 @@ protected PolyLinearRing( Geometry geometry ) { protected PolyLinearRing( PolyType type ) { super( type ); - this.geometryType = PolyGeometryType.LINEAR_RING; + this.geometryType = PolyGeometryType.LINEARRING; } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java new file mode 100644 index 0000000000..93f5272531 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java @@ -0,0 +1,77 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiLineString; +import org.polypheny.db.type.PolyType; + +/** + * {@link PolyMultiLineString} is a collection of any valid {@link PolyLineString}. + * {@link PolyLineString}s may overlap. + */ +public class PolyMultiLineString extends PolyGeometryCollection { + + private MultiLineString jtMultiLineString; + + + public PolyMultiLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.MULTILINESTRING; + this.jtMultiLineString = (MultiLineString) jtsGeometry; + } + + + public PolyMultiLineString( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int srid ) throws InvalidGeometryException { + super( wkt, srid ); + this.geometryType = PolyGeometryType.MULTILINESTRING; + this.jtMultiLineString = (MultiLineString) jtsGeometry; + } + + + public PolyMultiLineString( Geometry geometry ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTILINESTRING; + this.jtsGeometry = geometry; + this.jtMultiLineString = (MultiLineString) jtsGeometry; + this.SRID = jtsGeometry.getSRID(); + } + + + protected PolyMultiLineString( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.MULTILINESTRING; + } + + + public static PolyMultiLineString of( Geometry geometry ) { + return new PolyMultiLineString( geometry ); + } + + + /** + * Test whether {@link PolyMultiLineString} is closed: line starts and ends at the same point. + * + * @return true if {@link PolyMultiLineString} is closed. + */ + public boolean isClosed() { + return jtMultiLineString.isClosed(); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java new file mode 100644 index 0000000000..4c2b819038 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java @@ -0,0 +1,69 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPoint; +import org.polypheny.db.type.PolyType; + +/** + * A collection of valid {@link PolyPoint}. + * {@link PolyMultiPoint} does not introduce any validity rules for the collection of {@link PolyPoint}: + * {@link PolyPoint}s may overlap. + * {@link PolyMultiPoint} does not introduce any new methods. + */ +public class PolyMultiPoint extends PolyGeometryCollection { + + private MultiPoint jtsMultiPoint; + + + public PolyMultiPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.MULTIPOINT; + this.jtsMultiPoint = (MultiPoint) jtsGeometry; + } + + + public PolyMultiPoint( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int srid ) throws InvalidGeometryException { + super( wkt, srid ); + this.geometryType = PolyGeometryType.MULTIPOINT; + this.jtsMultiPoint = (MultiPoint) jtsGeometry; + } + + + public PolyMultiPoint( Geometry geometry ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTIPOINT; + this.jtsGeometry = geometry; + this.jtsMultiPoint = (MultiPoint) jtsGeometry; + this.SRID = geometry.getSRID(); + } + + + protected PolyMultiPoint( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.MULTIPOINT; + } + + + public static PolyMultiPoint of( Geometry geometry ) { + return new PolyMultiPoint( geometry ); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java new file mode 100644 index 0000000000..1ea6d9a1d4 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java @@ -0,0 +1,70 @@ +/* + * 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.type.entity.spatial; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.activej.serializer.annotations.Deserialize; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.MultiPolygon; +import org.polypheny.db.type.PolyType; + +/** + * {@link PolyMultiPolygon} is a collection of valid {@link PolyPolygon}s: non-overlapping and non-adjacent. + * {@link PolyPolygon}s in the {@link PolyMultiPolygon} may touch only at a finite number of points. + * The reason is - in case of overlapping {@link PolyPolygon}s, + * it is possible then to represent overlapping {@link PolyPolygon}s as one bigger {@link PolyPolygon} with an increased area. + * {@link PolyMultiPolygon} does not introduce any new methods. + */ +public class PolyMultiPolygon extends PolyGeometryCollection { + + private MultiPolygon jtsMultiPolygon; + + + public PolyMultiPolygon( @JsonProperty("wkt") @Deserialize("wkt") String wkt ) throws InvalidGeometryException { + super( wkt ); + this.geometryType = PolyGeometryType.MULTIPOLYGON; + this.jtsMultiPolygon = (MultiPolygon) jtsGeometry; + } + + + public PolyMultiPolygon( @JsonProperty("wkt") @Deserialize("wkt") String wkt, int srid ) throws InvalidGeometryException { + super( wkt, srid ); + this.geometryType = PolyGeometryType.MULTIPOLYGON; + this.jtsMultiPolygon = (MultiPolygon) jtsGeometry; + } + + + public PolyMultiPolygon( Geometry geometry ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTIPOLYGON; + this.jtsGeometry = geometry; + this.jtsMultiPolygon = (MultiPolygon) jtsGeometry; + this.SRID = jtsGeometry.getSRID(); + } + + + protected PolyMultiPolygon( PolyType type ) { + super( type ); + this.geometryType = PolyGeometryType.MULTIPOLYGON; + } + + + public static PolyMultiPolygon of( Geometry geometry ) { + return new PolyMultiPolygon( geometry ); + } + +} diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java index c3aace1a66..11c8c8832c 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryConstants.java @@ -20,9 +20,13 @@ public class GeometryConstants { static final String POINT_EWKT = "SRID=4326;POINT (13.4050 52.5200)"; static final String POINT_WKT = "POINT (13.4050 52.5200 36.754)"; - static final String LINESTRING_WKT = "LINESTRING(-1 -1, 2 2, 4 5, 6 7)"; + static final String LINESTRING_WKT = "LINESTRING (-1 -1, 2 2, 4 5, 6 7)"; static final String LINEAR_RING_WKT = "LINEARRING (0 0, 0 10, 10 10, 10 0, 0 0)"; - static final String POLYGON_WKT = "POLYGON((-1 -1, 2 2, -1 2, -1 -1))"; + static final String POLYGON_WKT = "POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )"; + static final String GEOMETRYCOLLECTION_WKT = "GEOMETRYCOLLECTION ( POINT (2 3), LINESTRING (2 3, 3 4) )"; + static final String MULTIPOINT_WKT = "MULTIPOINT ( (2 3), (13.4050 52.5200) )"; + static final String MULTILINESTRING_WKT = "MULTILINESTRING ( (0 0, 1 1, 1 2), (2 3, 3 2, 5 4) )"; + static final String MULTIPOLYGON_WKT = "MULTIPOLYGON ( ( (1 5, 5 5, 5 1, 1 1, 1 5) ), ( (6 5, 9 1, 6 1, 6 5) ) )"; static final double DELTA = 1e-5; static final int NO_SRID = 0; } diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java index c5080ad9a0..2a31281f32 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -31,9 +31,13 @@ import org.polypheny.db.type.entity.PolyValue; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyGeometryCollection; import org.polypheny.db.type.entity.spatial.PolyGeometryType; import org.polypheny.db.type.entity.spatial.PolyLineString; import org.polypheny.db.type.entity.spatial.PolyLinearRing; +import org.polypheny.db.type.entity.spatial.PolyMultiLineString; +import org.polypheny.db.type.entity.spatial.PolyMultiPoint; +import org.polypheny.db.type.entity.spatial.PolyMultiPolygon; import org.polypheny.db.type.entity.spatial.PolyPoint; import org.polypheny.db.type.entity.spatial.PolyPolygon; @@ -44,6 +48,10 @@ public class GeometryTest { private PolyGeometry lineString; private PolyGeometry linearRing; private PolyGeometry polygon; + private PolyGeometry geometryCollection; + private PolyGeometry multiPoint; + private PolyGeometry multiLineString; + private PolyGeometry multiPolygon; @BeforeClass @@ -58,6 +66,10 @@ public void prepareGeometries() throws InvalidGeometryException { lineString = PolyGeometry.of( GeometryConstants.LINESTRING_WKT ); linearRing = PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ); polygon = PolyGeometry.of( GeometryConstants.POLYGON_WKT ); + geometryCollection = PolyGeometry.of( GeometryConstants.GEOMETRYCOLLECTION_WKT ); + multiPoint = PolyGeometry.of( GeometryConstants.MULTIPOINT_WKT ); + multiLineString = PolyGeometry.of( GeometryConstants.MULTILINESTRING_WKT ); + multiPolygon = PolyGeometry.of( GeometryConstants.MULTIPOLYGON_WKT ); } @@ -109,7 +121,7 @@ public void testLineStringValidity() { assertFalse( line.isClosed() ); } ); - assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINESTRING(0 0)" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINESTRING (0 0)" ) ); } @Test @@ -117,7 +129,7 @@ public void testLinearRingValidity() { assertAll( "Group assertions of valid LinearRing", () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.LINEAR_RING_WKT ) ), () -> { - assertEquals( PolyGeometryType.LINEAR_RING, linearRing.getGeometryType() ); + assertEquals( PolyGeometryType.LINEARRING, linearRing.getGeometryType() ); assertEquals( GeometryConstants.NO_SRID, (long) linearRing.getSRID() ); PolyLinearRing ring = linearRing.asLinearRing(); assertTrue( ring.isRing() ); @@ -125,7 +137,7 @@ public void testLinearRingValidity() { assertEquals( 5, ring.getNumPoints() ); } ); - assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINEARRING(0 0, 0 10, 10 10, 10 5, 5 5)" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "LINEARRING (0 0, 0 10, 10 10, 10 5, 5 5)" ) ); } @Test @@ -144,7 +156,74 @@ public void testPolygonValidity() { assertEquals( 4, poly.getNumPoints() ); } ); - assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POLYGON((-1 -1, 2 2, -1 1, -1 2, 2 2, -1 -1))" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "POLYGON ((-1 -1, 2 2, -1 1, -1 2, 2 2, -1 -1))" ) ); + } + + @Test + public void testGeometryCollectionValidity() { + assertAll( "Group assertions of valid GeometryCollection", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.GEOMETRYCOLLECTION_WKT ) ), + () -> { + assertEquals( PolyGeometryType.GEOMETRYCOLLECTION, geometryCollection.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) geometryCollection.getSRID() ); + PolyGeometryCollection collection = geometryCollection.asGeometryCollection(); + assertEquals( 2, collection.getNumGeometries() ); + assertEquals( PolyLinearRing.of( "POINT(2 3)" ), collection.getGeometryN( 0 ) ); + assertEquals( 3, collection.getNumPoints() ); + } ); + // GEOMETRYCOLLECTION do not have any extra validity rules, geometries may overlap + assertDoesNotThrow( () -> PolyGeometry.of( "GEOMETRYCOLLECTION ( POINT (2 3), POINT (2 3) )" ) ); + assertDoesNotThrow( () -> PolyGeometry.of( "GEOMETRYCOLLECTION ( POLYGON ((-1 -1, 2 2, -1 2, -1 -1 )), POLYGON ((-1 -1, 2 2, -1 2, -1 -1 )) )" ) ); + } + + @Test + public void testMultiPointValidity() { + assertAll( "Group assertions of valid MultiPoint", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.MULTIPOINT_WKT ) ), + () -> { + assertEquals( PolyGeometryType.MULTIPOINT, multiPoint.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) multiPoint.getSRID() ); + PolyMultiPoint multi = multiPoint.asGeometryCollection().asMultiPoint(); + assertEquals( 2, multi.getNumGeometries() ); + assertEquals( 2, multi.getNumPoints() ); + } ); + // points may overlap + assertDoesNotThrow( () -> PolyGeometry.of( "MULTIPOINT ( (2 3), (2 3) )" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "MULTIPOINT( (2 3), LINEARRING (-1 -1, 2 2, -1 2, -1 -1) )" ) ); + } + + @Test + public void testMultiLineStringValidity() { + assertAll( "Group assertions of valid MultiLineString", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.MULTILINESTRING_WKT ) ), + () -> { + assertEquals( PolyGeometryType.MULTILINESTRING, multiLineString.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) multiLineString.getSRID() ); + PolyMultiLineString multi = multiLineString.asGeometryCollection().asMultiLineString(); + assertEquals( 2, multi.getNumGeometries() ); + assertEquals( 6, multi.getNumPoints() ); + } ); + // line strings may overlap + assertDoesNotThrow( () -> PolyGeometry.of( "MULTILINESTRING ( (0 0, 1 1, 1 2), (0 0, 1 1, 1 2) )" ) ); + assertThrows( InvalidGeometryException.class, () -> PolyGeometry.of( "MULTILINESTRING ( (2 3), (2 3) )" ) ); + } + + @Test + public void testMultiPolygonValidity() { + assertAll( "Group assertions of valid MultiPolygon", + () -> assertDoesNotThrow( () -> PolyGeometry.of( GeometryConstants.MULTIPOLYGON_WKT ) ), + () -> { + assertEquals( PolyGeometryType.MULTIPOLYGON, multiPolygon.getGeometryType() ); + assertEquals( GeometryConstants.NO_SRID, (long) multiPolygon.getSRID() ); + PolyMultiPolygon multi = multiPolygon.asGeometryCollection().asMultiPolygon(); + assertEquals( 2, multi.getNumGeometries() ); + assertEquals( 9, multi.getNumPoints() ); + } ); + // Polygons are not allowed to overlap + assertThrows( InvalidGeometryException.class, + () -> PolyGeometry.of( + "MULTIPOLYGON (( (1 5, 5 5, 5 1, 1 1, 1 5) ), (-1 -1, 2 2, -1 2, -1 -1 ))" ) + ); } From ceb7dc8af9b6546139e909d2173573c38b042f59 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Sun, 5 Nov 2023 16:59:56 +0100 Subject: [PATCH 07/16] GIS #462: added some basic function to query spatial relationships of objects --- .../spatial/GeometryTopologicalException.java | 25 ++ .../db/type/entity/spatial/PolyGeometry.java | 337 +++++++++++++++++- .../spatial/PolyGeometryCollection.java | 48 ++- .../db/type/entity/spatial/PolyPolygon.java | 5 + 4 files changed, 388 insertions(+), 27 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/type/entity/spatial/GeometryTopologicalException.java diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/GeometryTopologicalException.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/GeometryTopologicalException.java new file mode 100644 index 0000000000..4080ceefa8 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/GeometryTopologicalException.java @@ -0,0 +1,25 @@ +/* + * 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.type.entity.spatial; + +public class GeometryTopologicalException extends Exception { + + public GeometryTopologicalException( String message ) { + super( message ); + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 2787708916..4ecd124edf 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -33,6 +33,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; 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.type.PolySerializable; @@ -156,27 +157,31 @@ private void init( String wkt, int srid ) throws InvalidGeometryException { protected PolyGeometryType getPolyGeometryType() { switch ( jtsGeometry.getGeometryType() ) { - case "Point": + case Geometry.TYPENAME_POINT: return PolyGeometryType.POINT; - case "LineString": + case Geometry.TYPENAME_LINESTRING: return PolyGeometryType.LINESTRING; - case "LinearRing": + case Geometry.TYPENAME_LINEARRING: return PolyGeometryType.LINEARRING; - case "Polygon": + case Geometry.TYPENAME_POLYGON: return PolyGeometryType.POLYGON; - case "GeometryCollection": + case Geometry.TYPENAME_GEOMETRYCOLLECTION: return PolyGeometryType.GEOMETRYCOLLECTION; - case "MultiPoint": + case Geometry.TYPENAME_MULTIPOINT: return PolyGeometryType.MULTIPOINT; - case "MultiLineString": + case Geometry.TYPENAME_MULTILINESTRING: return PolyGeometryType.MULTILINESTRING; - case "MultiPolygon": + case Geometry.TYPENAME_MULTIPOLYGON: return PolyGeometryType.MULTIPOLYGON; default: throw new NotImplementedException( "value" ); } } + /* + * Casting methods + */ + public boolean isPoint() { return geometryType.equals( PolyGeometryType.POINT ); @@ -247,6 +252,10 @@ public PolyGeometryCollection asGeometryCollection() { throw cannotParse( this, PolyGeometryCollection.class ); } + /* + * Query Geometry properties + */ + /** * Tests whether this {@link PolyGeometry} is a simple geometry: does not intersect itself. @@ -419,6 +428,318 @@ public PolyGeometry reverse() { return PolyGeometry.of( jtsGeometry.reverse() ); } + /* + * Topological relationships + */ + + /* + * Binary predicates + */ + + + /** + * Check that another {@link PolyGeometry} is withing the Euclidean distance. + * + * @param g another {@link PolyGeometry} + * @param distance metric value to compare, measured in Cartesian coordinate units + * @return true if {@link PolyGeometry} is withing the given distance + */ + public boolean isWithinDistance( @NotNull PolyGeometry g, double distance ) { + return jtsGeometry.isWithinDistance( g.getJtsGeometry(), distance ); + } + + + /** + * Check that another {@link PolyGeometry} is withing the distance. + * + * @param g another {@link PolyGeometry} + * @param distance metric value to compare, measured in meters if spheroid, otherwise in Cartesian coordinate units + * @param spheroid use the spheroid physical model to calculate the distance + * @return true if {@link PolyGeometry} is withing the given distance + */ + public boolean isWithinDistance( @NotNull PolyGeometry g, double distance, boolean spheroid ) { + if ( !spheroid ) { + return isWithinDistance( g, distance ); + } + // TODO: implement on Perfect Sphere and for WGS84 + return false; + } + + + /** + * Check that this {@link PolyGeometry} is disjoint from another {@link PolyGeometry}: + * 2 {@link PolyGeometry} do not have points in common + * + * @param g another {@link PolyGeometry} + * @return true if 2 {@link PolyGeometry} are disjoint + */ + public boolean disjoint( @NotNull PolyGeometry g ) { + return jtsGeometry.disjoint( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} touches another {@link PolyGeometry}: + * 2 {@link PolyGeometry} have at least one point in common, but interiors do not intersect + * + * @param g another {@link PolyGeometry} + * @return true if one {@link PolyGeometry} touches another. false if two {@link PolyGeometry} are {@link PolyPoint}s + */ + public boolean touches( @NotNull PolyGeometry g ) { + return jtsGeometry.touches( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} intersects another {@link PolyGeometry}: + * 2 {@link PolyGeometry} have at least one point in common + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} intersects another. + */ + public boolean intersects( @NotNull PolyGeometry g ) { + return jtsGeometry.intersects( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} crosses another {@link PolyGeometry}: + * 2 {@link PolyGeometry} have some, but not all interior points in common + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} crosses another. + */ + public boolean crosses( @NotNull PolyGeometry g ) { + return jtsGeometry.crosses( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} within another {@link PolyGeometry}: + * Every point of this {@link PolyGeometry} is a point of another {@link PolyGeometry} + * and the interiors of two {@link PolyGeometry} intersect (have at least one point in common) + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} within another. + */ + public boolean within( @NotNull PolyGeometry g ) { + return jtsGeometry.within( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} contains another {@link PolyGeometry}: + * Every point of another {@link PolyGeometry} is a point of this {@link PolyGeometry} + * and the interiors of two {@link PolyGeometry} intersect (have at least one point in common) + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} contains another. + */ + public boolean contains( @NotNull PolyGeometry g ) { + return jtsGeometry.contains( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} overlaps another {@link PolyGeometry}: + * at least one point is not shared by both {@link PolyGeometry}s, they have the same dimension + * and the intersection of interiors have the same dimension as {@link PolyGeometry}s have. + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} overlaps another. + */ + public boolean overlaps( @NotNull PolyGeometry g ) { + return jtsGeometry.overlaps( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} covers another {@link PolyGeometry}: + * Every point of another {@link PolyGeometry} is a point of this {@link PolyGeometry}. + * More inclusive than {@link #contains(PolyGeometry)} + * (does not differentiate between points in interior and boundary) + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} covers another. + */ + public boolean covers( @NotNull PolyGeometry g ) { + return jtsGeometry.covers( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} is covered by another {@link PolyGeometry}: + * Every point of this {@link PolyGeometry} is a point of another {@link PolyGeometry}. + * More inclusive than {@link #within(PolyGeometry)} + * (does not differentiate between points in interior and boundary) + * + * @param g another {@link PolyGeometry} + * @return true if {@link PolyGeometry} is covered by another. + */ + public boolean coveredBy( @NotNull PolyGeometry g ) { + return jtsGeometry.coveredBy( g.getJtsGeometry() ); + } + + + /** + * Check that this {@link PolyGeometry} is spatially related to another {@link PolyGeometry} + * based on the given intersection DE-9IM pattern matrix. + * The DE-9IM pattern consists of 9 characters: + *

    + *
  • 0 (dimension 0)
  • + *
  • 1 (dimension 1)
  • + *
  • 2 (dimension 2)
  • + *
  • T (matches 0, 1 or 2)
  • + *
  • F (matches FALSE)
  • + *
  • * (matches any value)
  • + *
+ * For more details about the pattern, see DE-9IM. + *

+ *

+     * PolyGeometry g1 = PolyGeometry.of( "LINESTRING (0 1, 2 2)" );
+     * PolyGeometry g2 = PolyGeometry.of( "LINESTRING (2 2, 0 1)" );
+     * g1.relate( g2, "T*F**FFF2" ); // true
+     * 
+     * 
+ * + * @param g another {@link PolyGeometry} + * @param intersectionPattern pattern to check the intersection matrix of two {@link PolyGeometry} + * @return true if the DE-9IM intersection matrix for both {@link PolyGeometry} matches the intersectionPattern + * @throws GeometryTopologicalException in case intersectionPattern contains not of 9 characters + */ + public boolean relate( @NotNull PolyGeometry g, @NotNull String intersectionPattern ) throws GeometryTopologicalException { + if ( intersectionPattern.length() == 9 ) { + throw new GeometryTopologicalException( "DE-9IM pattern should contain 9 characters." ); + } + return jtsGeometry.relate( g.getJtsGeometry(), intersectionPattern ); + } + + /* + * Yield metric values + */ + + + /** + * Calculate the Euclidean distance between two {@link PolyGeometry}. + * The distance is measured in Cartesian coordinate units. + * + * @param g another {@link PolyGeometry} + * @return the distance in Cartesian coordinate units + */ + public double distance( @NotNull PolyGeometry g ) { + return jtsGeometry.distance( g.getJtsGeometry() ); + } + + + /** + * Calculate the distance between two {@link PolyGeometry}. + * + * @param g another {@link PolyGeometry} + * @param spheroid use the spheroid physical model to calculate the distance + * @return the distance in meters if spheroid, otherwise in Cartesian coordinate units + */ + public double distance( @NotNull PolyGeometry g, boolean spheroid ) { + if ( !spheroid ) { + return jtsGeometry.distance( g.getJtsGeometry() ); + } + // TODO: implement on Perfect Sphere and for WGS84 + return 42; + } + + + /* + * Set operations + */ + + + /** + * Compute the intersection set of both {@link PolyGeometry}s. + * The produced {@link PolyGeometry} is less than original or equal to the minimum dimension of both {@link PolyGeometry} + * {@link PolyGeometryCollection} is allowed only for homogeneous collection types. + * + * @param g another {@link PolyGeometry} + * @return a {@link PolyGeometry} that represent an intersection set of both {@link PolyGeometry}s + * @throws GeometryTopologicalException in case a non-empty heterogeneous {@link PolyGeometryCollection} as an input + * or a robustness error occurs + */ + public PolyGeometry intersection( @NotNull PolyGeometry g ) throws GeometryTopologicalException { + try { + return PolyGeometry.of( jtsGeometry.intersection( g.getJtsGeometry() ) ); + } catch ( TopologyException | IllegalArgumentException e ) { + // TopologyException: robustness error occurs + // IllegalArgumentException: non-empty heterogeneous GeometryCollection as an input + throw new GeometryTopologicalException( e.getMessage() ); + } + } + + + /** + * Compute the union set of both {@link PolyGeometry}s. + * The dimension of the produced union set is equal to the maximum dimension of both {@link PolyGeometry}. + * The result may be a heterogeneous {@link PolyGeometryCollection}. + * + * @param g another {@link PolyGeometry} + * @return a {@link PolyGeometry} that represent a union set of both {@link PolyGeometry}s + * @throws GeometryTopologicalException in case a non-empty {@link PolyGeometryCollection} as an input + * or a robustness error occurs + */ + public PolyGeometry union( @NotNull PolyGeometry g ) throws GeometryTopologicalException { + try { + return PolyGeometry.of( jtsGeometry.union( g.getJtsGeometry() ) ); + } catch ( TopologyException | IllegalArgumentException e ) { + // TopologyException: robustness error occurs + // IllegalArgumentException: non-empty GeometryCollection as an input + throw new GeometryTopologicalException( e.getMessage() ); + } + } + + + /** + * Compute the difference set of both {@link PolyGeometry}s. + * Produced difference set consists of points contained in this {@link PolyGeometry} not contained in other {@link PolyGeometry}. + * + * @param g another {@link PolyGeometry} + * @return a {@link PolyGeometry} that represent a difference set of both {@link PolyGeometry}s + * @throws GeometryTopologicalException in case a non-empty {@link PolyGeometryCollection} as an input + * or a robustness error occurs + */ + public PolyGeometry difference( @NotNull PolyGeometry g ) throws GeometryTopologicalException { + try { + return PolyGeometry.of( jtsGeometry.difference( g.getJtsGeometry() ) ); + } catch ( TopologyException | IllegalArgumentException e ) { + // TopologyException: robustness error occurs + // IllegalArgumentException: non-empty GeometryCollection as an input + throw new GeometryTopologicalException( e.getMessage() ); + } + } + + + /** + * Compute the symmetric difference set of both {@link PolyGeometry}s. + * Produced symmetric difference set consists of the union + * of points contained in this {@link PolyGeometry} not contained in other {@link PolyGeometry} + * and of points contained in other {@link PolyGeometry} not contained in this {@link PolyGeometry}. + * + * @param g another {@link PolyGeometry} + * @return a {@link PolyGeometry} that represent a difference set of both {@link PolyGeometry}s + * @throws GeometryTopologicalException in case a non-empty {@link PolyGeometryCollection} as an input + * or a robustness error occurs + */ + public PolyGeometry symDifference( @NotNull PolyGeometry g ) throws GeometryTopologicalException { + try { + return PolyGeometry.of( jtsGeometry.symDifference( g.getJtsGeometry() ) ); + } catch ( TopologyException | IllegalArgumentException e ) { + // TopologyException: robustness error occurs + // IllegalArgumentException: non-empty GeometryCollection as an input + throw new GeometryTopologicalException( e.getMessage() ); + } + } + + + /* + * PolyType methods + */ + /** * {@link #equals(Object) equals} ensures that the {@link PolyGeometry} types and coordinates are the same. diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java index b24d450215..5a2f251710 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java @@ -68,25 +68,6 @@ public static PolyGeometryCollection of( Geometry geometry ) { } - /** - * @return the number of {@link PolyGeometry} geometries in the {@link PolyGeometryCollection}. - */ - public int getNumGeometries() { - return jtsGeometryCollection.getNumGeometries(); - } - - - /** - * Get the nth {@link PolyGeometry} in the collection. - * - * @param n number of the {@link PolyGeometry} in the collection - * @return the nth {@link PolyGeometry} in the collection. - */ - public PolyGeometry getGeometryN( int n ) { - return PolyGeometry.of( jtsGeometryCollection.getGeometryN( n ) ); - } - - public boolean isMultiPoint() { return geometryType.equals( PolyGeometryType.MULTIPOINT ); } @@ -128,4 +109,33 @@ public PolyMultiPolygon asMultiPolygon() { throw cannotParse( this, PolyMultiPolygon.class ); } + + /** + * @return the number of {@link PolyGeometry} geometries in the {@link PolyGeometryCollection}. + */ + public int getNumGeometries() { + return jtsGeometryCollection.getNumGeometries(); + } + + + /** + * Get the nth {@link PolyGeometry} in the collection. + * + * @param n number of the {@link PolyGeometry} in the collection + * @return the nth {@link PolyGeometry} in the collection. + */ + public PolyGeometry getGeometryN( int n ) { + return PolyGeometry.of( jtsGeometryCollection.getGeometryN( n ) ); + } + + + /** + * Compute the union set of all {@link PolyGeometry} in the collection. + * + * @return the union set of all {@link PolyGeometry} in the collection. + */ + public PolyGeometry union() { + return PolyGeometry.of( jtsGeometryCollection.union() ); + } + } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java index 39b652f419..87f84b94a6 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java @@ -72,6 +72,11 @@ public static PolyPolygon of( Geometry geometry ) { } + public boolean isRectangle() { + return jtsPolygon.isRectangle(); + } + + public PolyLinearRing getExteriorRing() { return PolyLinearRing.of( jtsPolygon.getExteriorRing() ); } From 02edd6289a20cd4f094e952694fac92e678be49b Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 9 Nov 2023 00:06:47 +0100 Subject: [PATCH 08/16] GIS #462: added first basic function into SQL --- .../db/algebra/constant/FunctionCategory.java | 6 +- .../db/algebra/enumerable/RexImpTable.java | 4 + .../db/algebra/operators/OperatorName.java | 7 + .../org/polypheny/db/functions/Functions.java | 2 +- .../polypheny/db/functions/GeoFunctions.java | 775 +---------------- .../db/functions/GeoFunctions_old.java | 793 ++++++++++++++++++ .../db/prepare/JavaTypeFactoryImpl.java | 5 +- .../type/JavaToPolyTypeConversionRules.java | 5 +- .../java/org/polypheny/db/type/PolyType.java | 7 +- .../db/type/checker/OperandTypes.java | 2 + .../polypheny/db/type/entity/PolyValue.java | 13 +- .../db/type/entity/spatial/PolyGeometry.java | 2 +- .../db/type/inference/InferTypes.java | 12 + .../db/type/inference/ReturnTypes.java | 5 + .../org/polypheny/db/util/BuiltInMethod.java | 5 + .../db/sql/fun/GeoFunctionsTest.java | 59 ++ .../polypheny/db/sql/SqlLanguagePlugin.java | 24 + .../db/sql/language/fun/SqlStGeoFromText.java | 47 ++ 18 files changed, 1001 insertions(+), 772 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/functions/GeoFunctions_old.java create mode 100644 dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java create mode 100644 plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java diff --git a/core/src/main/java/org/polypheny/db/algebra/constant/FunctionCategory.java b/core/src/main/java/org/polypheny/db/algebra/constant/FunctionCategory.java index 76b693ae72..da9e22ddff 100644 --- a/core/src/main/java/org/polypheny/db/algebra/constant/FunctionCategory.java +++ b/core/src/main/java/org/polypheny/db/algebra/constant/FunctionCategory.java @@ -18,6 +18,7 @@ import static org.polypheny.db.algebra.constant.FunctionCategory.Property.DISTANCE_FUNCTION; import static org.polypheny.db.algebra.constant.FunctionCategory.Property.FUNCTION; +import static org.polypheny.db.algebra.constant.FunctionCategory.Property.GEO_FUNCTION; import static org.polypheny.db.algebra.constant.FunctionCategory.Property.MULTIMEDIA_FUNCTION; import static org.polypheny.db.algebra.constant.FunctionCategory.Property.SPECIFIC; import static org.polypheny.db.algebra.constant.FunctionCategory.Property.TABLE_FUNCTION; @@ -44,7 +45,8 @@ public enum FunctionCategory { USER_DEFINED_TABLE_SPECIFIC_FUNCTION( "TABLE_UDF_SPECIFIC", "User-defined table function with SPECIFIC name", USER_DEFINED, TABLE_FUNCTION, SPECIFIC ), MATCH_RECOGNIZE( "MATCH_RECOGNIZE", "MATCH_RECOGNIZE function", TABLE_FUNCTION ), DISTANCE( "DISTANCE", "distance function", DISTANCE_FUNCTION ), - MULTIMEDIA( "MULTIMEDIA", "Multimedia function", MULTIMEDIA_FUNCTION ); + MULTIMEDIA( "MULTIMEDIA", "Multimedia function", MULTIMEDIA_FUNCTION ), + GEOMETRY( "GEOMETRY", "Geo function", GEO_FUNCTION ); private final EnumSet properties; @@ -97,7 +99,7 @@ public boolean isUserDefinedNotSpecificFunction() { * Property of a SqlFunctionCategory. */ enum Property { - USER_DEFINED, TABLE_FUNCTION, SPECIFIC, FUNCTION, DISTANCE_FUNCTION, MULTIMEDIA_FUNCTION + USER_DEFINED, TABLE_FUNCTION, SPECIFIC, FUNCTION, DISTANCE_FUNCTION, MULTIMEDIA_FUNCTION, GEO_FUNCTION } } diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index e50e636714..ca262a1156 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -366,6 +366,10 @@ public Expression implement( RexToLixTranslator translator, RexCall call, List - * Remaining tasks: - * - *
    - *
  • Determine type code for {@link ExtraPolyTypes#GEOMETRY}
  • - *
  • Should we create aliases for functions in upper-case? Without ST_ prefix?
  • - *
  • Consider adding spatial literals, e.g. `GEOMETRY 'POINT (30 10)'`
  • - *
  • Integer arguments, e.g. SELECT ST_MakePoint(1, 2, 1.5), ST_MakePoint(1, 2)
  • - *
  • Are GEOMETRY values comparable? If so add ORDER BY test
  • - *
  • We have to add 'Z' to create 3D objects. This is inconsistent with PostGIS. Who is right? At least document the difference.
  • - *
  • Should add GeometryEngine.intersects; similar to disjoint etc.
  • - *
  • Make {@link #ST_MakeLine(Geom, Geom)} varargs
  • - *
- */ -@SuppressWarnings({ "WeakerAccess", "unused" }) -@Deterministic -@Strict -@Experimental public class GeoFunctions { - private static final int NO_SRID = 0; - private static final SpatialReference SPATIAL_REFERENCE = SpatialReference.create( 4326 ); - - private GeoFunctions() { // empty on purpose } - - private static UnsupportedOperationException todo() { - return new UnsupportedOperationException(); - } - - - protected static Geom bind( Geometry geometry, int srid ) { - if ( geometry == null ) { - return null; - } - if ( srid == NO_SRID ) { - return new SimpleGeom( geometry ); - } - return bind( geometry, SpatialReference.create( srid ) ); - } - - - private static MapGeom bind( Geometry geometry, SpatialReference sr ) { - return new MapGeom( new MapGeometry( geometry, sr ) ); - } - - - public static String ST_AsText( Geom g ) { - return ST_AsWKT( g ); - } - - - public static String ST_AsWKT( Geom g ) { - return GeometryEngine.geometryToWkt( g.g(), WktExportFlags.wktExportDefaults ); - } - - - public static Geom ST_GeomFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_GeomFromText( String s, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( s, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); - return bind( g, srid ); - } - - - public static Geom ST_LineFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_LineFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Line ); - return bind( g, srid ); - } - - - public static Geom ST_MPointFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_MPointFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.MultiPoint ); - return bind( g, srid ); - } - - - public static Geom ST_PointFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_PointFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Point ); - return bind( g, srid ); - } - - - public static Geom ST_PolyFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_PolyFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Polygon ); - return bind( g, srid ); - } - - - public static Geom ST_MLineFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_MLineFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); // NOTE: there is no Geometry.Type.MultiLine - return bind( g, srid ); - } - - - public static Geom ST_MPolyFromText( String s ) { - return ST_GeomFromText( s, NO_SRID ); - } - - - public static Geom ST_MPolyFromText( String wkt, int srid ) { - final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); // NOTE: there is no Geometry.Type.MultiPolygon - return bind( g, srid ); - } - - - /** - * Creates a line-string from the given POINTs (or MULTIPOINTs). - */ - public static Geom ST_MakeLine( Geom geom1, Geom geom2 ) { - return makeLine( geom1, geom2 ); - } - - - public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3 ) { - return makeLine( geom1, geom2, geom3 ); - } - - - public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4 ) { - return makeLine( geom1, geom2, geom3, geom4 ); - } - - - public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4, Geom geom5 ) { - return makeLine( geom1, geom2, geom3, geom4, geom5 ); - } - - - public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4, Geom geom5, Geom geom6 ) { - return makeLine( geom1, geom2, geom3, geom4, geom5, geom6 ); - } - - - private static Geom makeLine( Geom... geoms ) { - final Polyline g = new Polyline(); - Point p = null; - for ( Geom geom : geoms ) { - if ( geom.g() instanceof Point ) { - final Point prev = p; - p = (Point) geom.g(); - if ( prev != null ) { - final Line line = new Line(); - line.setStart( prev ); - line.setEnd( p ); - g.addSegment( line, false ); - } - } - } - return new SimpleGeom( g ); - } - - - /** - * Alias for {@link #ST_Point(BigDecimal, BigDecimal)}. - */ - public static Geom ST_MakePoint( BigDecimal x, BigDecimal y ) { - return ST_Point( x, y ); - } - - - /** - * Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}. - */ - public static Geom ST_MakePoint( BigDecimal x, BigDecimal y, BigDecimal z ) { - return ST_Point( x, y, z ); - } - - - /** - * Constructs a 2D point from coordinates. - */ - public static Geom ST_Point( BigDecimal x, BigDecimal y ) { - // NOTE: Combine the double and BigDecimal variants of this function - return point( x.doubleValue(), y.doubleValue() ); - } - - - /** - * Constructs a 3D point from coordinates. - */ - public static Geom ST_Point( BigDecimal x, BigDecimal y, BigDecimal z ) { - final Geometry g = new Point( x.doubleValue(), y.doubleValue(), z.doubleValue() ); - return new SimpleGeom( g ); - } - - - private static Geom point( double x, double y ) { - final Geometry g = new Point( x, y ); - return new SimpleGeom( g ); - } - - - /** - * Returns whether {@code geom} has at least one z-coordinate. - */ - public static boolean ST_Is3D( Geom geom ) { - return geom.g().hasZ(); - } - - - /** - * Returns the x-value of the first coordinate of {@code geom}. - */ - public static Double ST_X( Geom geom ) { - return geom.g() instanceof Point ? ((Point) geom.g()).getX() : null; - } - - - /** - * Returns the y-value of the first coordinate of {@code geom}. - */ - public static Double ST_Y( Geom geom ) { - return geom.g() instanceof Point ? ((Point) geom.g()).getY() : null; - } - - - /** - * Returns the z-value of the first coordinate of {@code geom}. - */ - public static Double ST_Z( Geom geom ) { - return geom.g().getDescription().hasZ() && geom.g() instanceof Point ? ((Point) geom.g()).getZ() : null; - } - - - /** - * Returns the boundary of {@code geom}. - */ - public static Geom ST_Boundary( Geom geom ) { - OperatorBoundary op = OperatorBoundary.local(); - Geometry result = op.execute( geom.g(), null ); - return geom.wrap( result ); - } - - - /** - * Returns the distance between {@code geom1} and {@code geom2}. - */ - public static double ST_Distance( Geom geom1, Geom geom2 ) { - return GeometryEngine.distance( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns the type of {@code geom}. - */ - public static String ST_GeometryType( Geom geom ) { - return type( geom.g() ).name(); - } - - - /** - * Returns the OGC SFS type code of {@code geom}. - */ - public static int ST_GeometryTypeCode( Geom geom ) { - return type( geom.g() ).code; - } - - - /** - * Returns the OGC type of a geometry. - */ - private static Type type( Geometry g ) { - switch ( g.getType() ) { - case Point: - return Type.POINT; - case Polyline: - return Type.LINESTRING; - case Polygon: - return Type.POLYGON; - case MultiPoint: - return Type.MULTIPOINT; - case Envelope: - return Type.POLYGON; - case Line: - return Type.LINESTRING; - case Unknown: - return Type.Geometry; - default: - throw new AssertionError( g ); - } - } - - - /** - * Returns the minimum bounding box of {@code geom} (which may be a GEOMETRYCOLLECTION). - */ - public static Geom ST_Envelope( Geom geom ) { - final Envelope env = envelope( geom.g() ); - return geom.wrap( env ); - } - - - private static Envelope envelope( Geometry g ) { - final Envelope env = new Envelope(); - g.queryEnvelope( env ); - return env; - } - - - /** - * Returns whether {@code geom1} contains {@code geom2}. - */ - public static boolean ST_Contains( Geom geom1, Geom geom2 ) { - return GeometryEngine.contains( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} contains {@code geom2} but does not intersect its boundary. - */ - public static boolean ST_ContainsProperly( Geom geom1, Geom geom2 ) { - return GeometryEngine.contains( geom1.g(), geom2.g(), geom1.sr() ) && !GeometryEngine.crosses( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether no point in {@code geom2} is outside {@code geom1}. - */ - private static boolean ST_Covers( Geom geom1, Geom geom2 ) { - throw todo(); - } - - - /** - * Returns whether {@code geom1} crosses {@code geom2}. - */ - public static boolean ST_Crosses( Geom geom1, Geom geom2 ) { - return GeometryEngine.crosses( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} and {@code geom2} are disjoint. - */ - public static boolean ST_Disjoint( Geom geom1, Geom geom2 ) { - return GeometryEngine.disjoint( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether the envelope of {@code geom1} intersects the envelope of {@code geom2}. - */ - public static boolean ST_EnvelopesIntersect( Geom geom1, Geom geom2 ) { - final Geometry e1 = envelope( geom1.g() ); - final Geometry e2 = envelope( geom2.g() ); - return intersects( e1, e2, geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} equals {@code geom2}. - */ - public static boolean ST_Equals( Geom geom1, Geom geom2 ) { - return GeometryEngine.equals( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} intersects {@code geom2}. - */ - public static boolean ST_Intersects( Geom geom1, Geom geom2 ) { - final Geometry g1 = geom1.g(); - final Geometry g2 = geom2.g(); - final SpatialReference sr = geom1.sr(); - return intersects( g1, g2, sr ); - } - - - private static boolean intersects( Geometry g1, Geometry g2, SpatialReference sr ) { - final OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal.getInstance().getOperator( Operator.Type.Intersects ); - return op.execute( g1, g2, sr, null ); - } - - - /** - * Returns whether {@code geom1} equals {@code geom2} and their coordinates and component Geometries are listed in the same order. - */ - public static boolean ST_OrderingEquals( Geom geom1, Geom geom2 ) { - return GeometryEngine.equals( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns {@code geom1} overlaps {@code geom2}. - */ - public static boolean ST_Overlaps( Geom geom1, Geom geom2 ) { - return GeometryEngine.overlaps( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} touches {@code geom2}. - */ - public static boolean ST_Touches( Geom geom1, Geom geom2 ) { - return GeometryEngine.touches( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} is within {@code geom2}. - */ - public static boolean ST_Within( Geom geom1, Geom geom2 ) { - return GeometryEngine.within( geom1.g(), geom2.g(), geom1.sr() ); - } - - - /** - * Returns whether {@code geom1} and {@code geom2} are within {@code distance} of each other. - */ - public static boolean ST_DWithin( Geom geom1, Geom geom2, double distance ) { - final double distance1 = GeometryEngine.distance( geom1.g(), geom2.g(), geom1.sr() ); - return distance1 <= distance; - } - - - /** - * Computes a buffer around {@code geom}. - */ - public static Geom ST_Buffer( Geom geom, double distance ) { - final Polygon g = GeometryEngine.buffer( geom.g(), geom.sr(), distance ); - return geom.wrap( g ); - } - - - /** - * Computes a buffer around {@code geom} with . - */ - public static Geom ST_Buffer( Geom geom, double distance, int quadSegs ) { - throw todo(); - } - - - /** - * Computes a buffer around {@code geom}. - */ - public static Geom ST_Buffer( Geom geom, double bufferSize, String style ) { - int quadSegCount = 8; - CapStyle endCapStyle = CapStyle.ROUND; - JoinStyle joinStyle = JoinStyle.ROUND; - float mitreLimit = 5f; - int i = 0; - parse: - for ( ; ; ) { - int equals = style.indexOf( '=', i ); - if ( equals < 0 ) { - break; - } - int space = style.indexOf( ' ', equals ); - if ( space < 0 ) { - space = style.length(); - } - String name = style.substring( i, equals ); - String value = style.substring( equals + 1, space ); - switch ( name ) { - case "quad_segs": - quadSegCount = Integer.valueOf( value ); - break; - case "endcap": - endCapStyle = CapStyle.of( value ); - break; - case "join": - joinStyle = JoinStyle.of( value ); - break; - case "mitre_limit": - case "miter_limit": - mitreLimit = Float.parseFloat( value ); - break; - default: - // ignore the value - } - i = space; - for ( ; ; ) { - if ( i >= style.length() ) { - break parse; - } - if ( style.charAt( i ) != ' ' ) { - break; - } - ++i; - } - } - return buffer( geom, bufferSize, quadSegCount, endCapStyle, joinStyle, mitreLimit ); - } - - - private static Geom buffer( Geom geom, double bufferSize, int quadSegCount, CapStyle endCapStyle, JoinStyle joinStyle, float mitreLimit ) { - Util.discard( endCapStyle + ":" + joinStyle + ":" + mitreLimit + ":" + quadSegCount ); - throw todo(); - } - - - /** - * Computes the union of {@code geom1} and {@code geom2}. - */ - public static Geom ST_Union( Geom geom1, Geom geom2 ) { - SpatialReference sr = geom1.sr(); - final Geometry g = GeometryEngine.union( new Geometry[]{ geom1.g(), geom2.g() }, sr ); - return bind( g, sr ); - } - - - /** - * Computes the union of the geometries in {@code geomCollection}. - */ - @SemiStrict - public static Geom ST_Union( Geom geomCollection ) { - SpatialReference sr = geomCollection.sr(); - final Geometry g = GeometryEngine.union( new Geometry[]{ geomCollection.g() }, sr ); - return bind( g, sr ); - } - - - /** - * Transforms {@code geom} from one coordinate reference system (CRS) to the CRS specified by {@code srid}. - */ - public static Geom ST_Transform( Geom geom, int srid ) { - return geom.transform( srid ); - } - - - /** - * Returns a copy of {@code geom} with a new SRID. - */ - public static Geom ST_SetSRID( Geom geom, int srid ) { - return geom.transform( srid ); - } - - - /** - * How the "buffer" command terminates the end of a line. - */ - enum CapStyle { - ROUND, FLAT, SQUARE; - - - static CapStyle of( String value ) { - switch ( value ) { - case "round": - return ROUND; - case "flat": - case "butt": - return FLAT; - case "square": - return SQUARE; - default: - throw new IllegalArgumentException( "unknown endcap value: " + value ); - } - } - } - - - /** - * How the "buffer" command decorates junctions between line segments. - */ - enum JoinStyle { - ROUND, MITRE, BEVEL; - - - static JoinStyle of( String value ) { - switch ( value ) { - case "round": - return ROUND; - case "mitre": - case "miter": - return MITRE; - case "bevel": - return BEVEL; - default: - throw new IllegalArgumentException( "unknown join value: " + value ); - } - } - } - - - /** - * Geometry. It may or may not have a spatial reference associated with it. - */ - public interface Geom { - - Geometry g(); - - SpatialReference sr(); - - Geom transform( int srid ); - - Geom wrap( Geometry g ); - - } - - - /** - * Sub-class of geometry that has no spatial reference. - */ - static class SimpleGeom implements Geom { - - final Geometry g; - - - SimpleGeom( Geometry g ) { - this.g = Objects.requireNonNull( g ); - } - - - @Override - public String toString() { - return g.toString(); - } - - - @Override - public Geometry g() { - return g; - } - - - @Override - public SpatialReference sr() { - return SPATIAL_REFERENCE; + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stGeoFromText( PolyString wkt ) { + try { + return PolyGeometry.of( wkt.value ); } - - - @Override - public Geom transform( int srid ) { - if ( srid == SPATIAL_REFERENCE.getID() ) { - return this; - } - return bind( g, srid ); + catch ( InvalidGeometryException e ) { + throw toUnchecked( e ); } - - - @Override - public Geom wrap( Geometry g ) { - return new SimpleGeom( g ); - } - } - - /** - * Sub-class of geometry that has a spatial reference. - */ - static class MapGeom implements Geom { - - final MapGeometry mg; - - - MapGeom( MapGeometry mg ) { - this.mg = Objects.requireNonNull( mg ); - } - - - @Override - public String toString() { - return mg.toString(); - } - - - @Override - public Geometry g() { - return mg.getGeometry(); + @SuppressWarnings("UnusedDeclaration") + public static PolyFloat stX( PolyGeometry geometry ) { + if (!geometry.isPoint()) { + throw toUnchecked( new InvalidGeometryException( "This function could be applied only to points" )); } - - - @Override - public SpatialReference sr() { - return mg.getSpatialReference(); - } - - - @Override - public Geom transform( int srid ) { - if ( srid == NO_SRID ) { - return new SimpleGeom( mg.getGeometry() ); - } - if ( srid == mg.getSpatialReference().getID() ) { - return this; - } - return bind( mg.getGeometry(), srid ); - } - - - @Override - public Geom wrap( Geometry g ) { - return bind( g, this.mg.getSpatialReference() ); - } - + return PolyFloat.of( geometry.asPoint().getX() ); } - - /** - * Geometry types, with the names and codes assigned by OGC. - */ - enum Type { - Geometry( 0 ), - POINT( 1 ), - LINESTRING( 2 ), - POLYGON( 3 ), - MULTIPOINT( 4 ), - MULTILINESTRING( 5 ), - MULTIPOLYGON( 6 ), - GEOMCOLLECTION( 7 ), - CURVE( 13 ), - SURFACE( 14 ), - POLYHEDRALSURFACE( 15 ); - - final int code; - - - Type( int code ) { - this.code = code; - } + public static PolyString test1( PolyString wkt ) { + return wkt; } } - diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions_old.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions_old.java new file mode 100644 index 0000000000..23feeda39c --- /dev/null +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions_old.java @@ -0,0 +1,793 @@ +///* +// * 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; +// +// +//import com.esri.core.geometry.Envelope; +//import com.esri.core.geometry.Geometry; +//import com.esri.core.geometry.GeometryEngine; +//import com.esri.core.geometry.Line; +//import com.esri.core.geometry.MapGeometry; +//import com.esri.core.geometry.Operator; +//import com.esri.core.geometry.OperatorBoundary; +//import com.esri.core.geometry.OperatorFactoryLocal; +//import com.esri.core.geometry.OperatorIntersects; +//import com.esri.core.geometry.Point; +//import com.esri.core.geometry.Polygon; +//import com.esri.core.geometry.Polyline; +//import com.esri.core.geometry.SpatialReference; +//import com.esri.core.geometry.WktExportFlags; +//import com.esri.core.geometry.WktImportFlags; +//import java.math.BigDecimal; +//import java.util.Objects; +//import org.apache.calcite.linq4j.function.Deterministic; +//import org.apache.calcite.linq4j.function.Experimental; +//import org.apache.calcite.linq4j.function.SemiStrict; +//import org.apache.calcite.linq4j.function.Strict; +//import org.polypheny.db.type.ExtraPolyTypes; +//import org.polypheny.db.util.Util; +// +// +///** +// * Helper methods to implement Geo-spatial functions in generated code. +// *

+// * Remaining tasks: +// * +// *

    +// *
  • Determine type code for {@link ExtraPolyTypes#GEOMETRY}
  • +// *
  • Should we create aliases for functions in upper-case? Without ST_ prefix?
  • +// *
  • Consider adding spatial literals, e.g. `GEOMETRY 'POINT (30 10)'`
  • +// *
  • Integer arguments, e.g. SELECT ST_MakePoint(1, 2, 1.5), ST_MakePoint(1, 2)
  • +// *
  • Are GEOMETRY values comparable? If so add ORDER BY test
  • +// *
  • We have to add 'Z' to create 3D objects. This is inconsistent with PostGIS. Who is right? At least document the difference.
  • +// *
  • Should add GeometryEngine.intersects; similar to disjoint etc.
  • +// *
  • Make {@link #ST_MakeLine(Geom, Geom)} varargs
  • +// *
+// */ +//@SuppressWarnings({ "WeakerAccess", "unused" }) +//@Deterministic +//@Strict +//@Experimental +//public class GeoFunctions_old { +// +// private static final int NO_SRID = 0; +// private static final SpatialReference SPATIAL_REFERENCE = SpatialReference.create( 4326 ); +// +// +// private GeoFunctions_old() { +// // empty on purpose +// } +// +// +// private static UnsupportedOperationException todo() { +// return new UnsupportedOperationException(); +// } +// +// +// protected static Geom bind( Geometry geometry, int srid ) { +// if ( geometry == null ) { +// return null; +// } +// if ( srid == NO_SRID ) { +// return new SimpleGeom( geometry ); +// } +// return bind( geometry, SpatialReference.create( srid ) ); +// } +// +// +// private static MapGeom bind( Geometry geometry, SpatialReference sr ) { +// return new MapGeom( new MapGeometry( geometry, sr ) ); +// } +// +// +// public static String ST_AsText( Geom g ) { +// return ST_AsWKT( g ); +// } +// +// +// public static String ST_AsWKT( Geom g ) { +// return GeometryEngine.geometryToWkt( g.g(), WktExportFlags.wktExportDefaults ); +// } +// +// +// public static Geom ST_GeomFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_GeomFromText( String s, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( s, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); +// return bind( g, srid ); +// } +// +// +// public static Geom ST_LineFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_LineFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Line ); +// return bind( g, srid ); +// } +// +// +// public static Geom ST_MPointFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_MPointFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.MultiPoint ); +// return bind( g, srid ); +// } +// +// +// public static Geom ST_PointFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_PointFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Point ); +// return bind( g, srid ); +// } +// +// +// public static Geom ST_PolyFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_PolyFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Polygon ); +// return bind( g, srid ); +// } +// +// +// public static Geom ST_MLineFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_MLineFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); // NOTE: there is no Geometry.Type.MultiLine +// return bind( g, srid ); +// } +// +// +// public static Geom ST_MPolyFromText( String s ) { +// return ST_GeomFromText( s, NO_SRID ); +// } +// +// +// public static Geom ST_MPolyFromText( String wkt, int srid ) { +// final Geometry g = GeometryEngine.geometryFromWkt( wkt, WktImportFlags.wktImportDefaults, Geometry.Type.Unknown ); // NOTE: there is no Geometry.Type.MultiPolygon +// return bind( g, srid ); +// } +// +// +// /** +// * Creates a line-string from the given POINTs (or MULTIPOINTs). +// */ +// public static Geom ST_MakeLine( Geom geom1, Geom geom2 ) { +// return makeLine( geom1, geom2 ); +// } +// +// +// public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3 ) { +// return makeLine( geom1, geom2, geom3 ); +// } +// +// +// public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4 ) { +// return makeLine( geom1, geom2, geom3, geom4 ); +// } +// +// +// public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4, Geom geom5 ) { +// return makeLine( geom1, geom2, geom3, geom4, geom5 ); +// } +// +// +// public static Geom ST_MakeLine( Geom geom1, Geom geom2, Geom geom3, Geom geom4, Geom geom5, Geom geom6 ) { +// return makeLine( geom1, geom2, geom3, geom4, geom5, geom6 ); +// } +// +// +// private static Geom makeLine( Geom... geoms ) { +// final Polyline g = new Polyline(); +// Point p = null; +// for ( Geom geom : geoms ) { +// if ( geom.g() instanceof Point ) { +// final Point prev = p; +// p = (Point) geom.g(); +// if ( prev != null ) { +// final Line line = new Line(); +// line.setStart( prev ); +// line.setEnd( p ); +// g.addSegment( line, false ); +// } +// } +// } +// return new SimpleGeom( g ); +// } +// +// +// /** +// * Alias for {@link #ST_Point(BigDecimal, BigDecimal)}. +// */ +// public static Geom ST_MakePoint( BigDecimal x, BigDecimal y ) { +// return ST_Point( x, y ); +// } +// +// +// /** +// * Alias for {@link #ST_Point(BigDecimal, BigDecimal, BigDecimal)}. +// */ +// public static Geom ST_MakePoint( BigDecimal x, BigDecimal y, BigDecimal z ) { +// return ST_Point( x, y, z ); +// } +// +// +// /** +// * Constructs a 2D point from coordinates. +// */ +// public static Geom ST_Point( BigDecimal x, BigDecimal y ) { +// // NOTE: Combine the double and BigDecimal variants of this function +// return point( x.doubleValue(), y.doubleValue() ); +// } +// +// +// /** +// * Constructs a 3D point from coordinates. +// */ +// public static Geom ST_Point( BigDecimal x, BigDecimal y, BigDecimal z ) { +// final Geometry g = new Point( x.doubleValue(), y.doubleValue(), z.doubleValue() ); +// return new SimpleGeom( g ); +// } +// +// +// private static Geom point( double x, double y ) { +// final Geometry g = new Point( x, y ); +// return new SimpleGeom( g ); +// } +// +// +// /** +// * Returns whether {@code geom} has at least one z-coordinate. +// */ +// public static boolean ST_Is3D( Geom geom ) { +// return geom.g().hasZ(); +// } +// +// +// /** +// * Returns the x-value of the first coordinate of {@code geom}. +// */ +// public static Double ST_X( Geom geom ) { +// return geom.g() instanceof Point ? ((Point) geom.g()).getX() : null; +// } +// +// +// /** +// * Returns the y-value of the first coordinate of {@code geom}. +// */ +// public static Double ST_Y( Geom geom ) { +// return geom.g() instanceof Point ? ((Point) geom.g()).getY() : null; +// } +// +// +// /** +// * Returns the z-value of the first coordinate of {@code geom}. +// */ +// public static Double ST_Z( Geom geom ) { +// return geom.g().getDescription().hasZ() && geom.g() instanceof Point ? ((Point) geom.g()).getZ() : null; +// } +// +// +// /** +// * Returns the boundary of {@code geom}. +// */ +// public static Geom ST_Boundary( Geom geom ) { +// OperatorBoundary op = OperatorBoundary.local(); +// Geometry result = op.execute( geom.g(), null ); +// return geom.wrap( result ); +// } +// +// +// /** +// * Returns the distance between {@code geom1} and {@code geom2}. +// */ +// public static double ST_Distance( Geom geom1, Geom geom2 ) { +// return GeometryEngine.distance( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns the type of {@code geom}. +// */ +// public static String ST_GeometryType( Geom geom ) { +// return type( geom.g() ).name(); +// } +// +// +// /** +// * Returns the OGC SFS type code of {@code geom}. +// */ +// public static int ST_GeometryTypeCode( Geom geom ) { +// return type( geom.g() ).code; +// } +// +// +// /** +// * Returns the OGC type of a geometry. +// */ +// private static Type type( Geometry g ) { +// switch ( g.getType() ) { +// case Point: +// return Type.POINT; +// case Polyline: +// return Type.LINESTRING; +// case Polygon: +// return Type.POLYGON; +// case MultiPoint: +// return Type.MULTIPOINT; +// case Envelope: +// return Type.POLYGON; +// case Line: +// return Type.LINESTRING; +// case Unknown: +// return Type.Geometry; +// default: +// throw new AssertionError( g ); +// } +// } +// +// +// /** +// * Returns the minimum bounding box of {@code geom} (which may be a GEOMETRYCOLLECTION). +// */ +// public static Geom ST_Envelope( Geom geom ) { +// final Envelope env = envelope( geom.g() ); +// return geom.wrap( env ); +// } +// +// +// private static Envelope envelope( Geometry g ) { +// final Envelope env = new Envelope(); +// g.queryEnvelope( env ); +// return env; +// } +// +// +// /** +// * Returns whether {@code geom1} contains {@code geom2}. +// */ +// public static boolean ST_Contains( Geom geom1, Geom geom2 ) { +// return GeometryEngine.contains( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} contains {@code geom2} but does not intersect its boundary. +// */ +// public static boolean ST_ContainsProperly( Geom geom1, Geom geom2 ) { +// return GeometryEngine.contains( geom1.g(), geom2.g(), geom1.sr() ) && !GeometryEngine.crosses( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether no point in {@code geom2} is outside {@code geom1}. +// */ +// private static boolean ST_Covers( Geom geom1, Geom geom2 ) { +// throw todo(); +// } +// +// +// /** +// * Returns whether {@code geom1} crosses {@code geom2}. +// */ +// public static boolean ST_Crosses( Geom geom1, Geom geom2 ) { +// return GeometryEngine.crosses( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} and {@code geom2} are disjoint. +// */ +// public static boolean ST_Disjoint( Geom geom1, Geom geom2 ) { +// return GeometryEngine.disjoint( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether the envelope of {@code geom1} intersects the envelope of {@code geom2}. +// */ +// public static boolean ST_EnvelopesIntersect( Geom geom1, Geom geom2 ) { +// final Geometry e1 = envelope( geom1.g() ); +// final Geometry e2 = envelope( geom2.g() ); +// return intersects( e1, e2, geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} equals {@code geom2}. +// */ +// public static boolean ST_Equals( Geom geom1, Geom geom2 ) { +// return GeometryEngine.equals( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} intersects {@code geom2}. +// */ +// public static boolean ST_Intersects( Geom geom1, Geom geom2 ) { +// final Geometry g1 = geom1.g(); +// final Geometry g2 = geom2.g(); +// final SpatialReference sr = geom1.sr(); +// return intersects( g1, g2, sr ); +// } +// +// +// private static boolean intersects( Geometry g1, Geometry g2, SpatialReference sr ) { +// final OperatorIntersects op = (OperatorIntersects) OperatorFactoryLocal.getInstance().getOperator( Operator.Type.Intersects ); +// return op.execute( g1, g2, sr, null ); +// } +// +// +// /** +// * Returns whether {@code geom1} equals {@code geom2} and their coordinates and component Geometries are listed in the same order. +// */ +// public static boolean ST_OrderingEquals( Geom geom1, Geom geom2 ) { +// return GeometryEngine.equals( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns {@code geom1} overlaps {@code geom2}. +// */ +// public static boolean ST_Overlaps( Geom geom1, Geom geom2 ) { +// return GeometryEngine.overlaps( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} touches {@code geom2}. +// */ +// public static boolean ST_Touches( Geom geom1, Geom geom2 ) { +// return GeometryEngine.touches( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} is within {@code geom2}. +// */ +// public static boolean ST_Within( Geom geom1, Geom geom2 ) { +// return GeometryEngine.within( geom1.g(), geom2.g(), geom1.sr() ); +// } +// +// +// /** +// * Returns whether {@code geom1} and {@code geom2} are within {@code distance} of each other. +// */ +// public static boolean ST_DWithin( Geom geom1, Geom geom2, double distance ) { +// final double distance1 = GeometryEngine.distance( geom1.g(), geom2.g(), geom1.sr() ); +// return distance1 <= distance; +// } +// +// +// /** +// * Computes a buffer around {@code geom}. +// */ +// public static Geom ST_Buffer( Geom geom, double distance ) { +// final Polygon g = GeometryEngine.buffer( geom.g(), geom.sr(), distance ); +// return geom.wrap( g ); +// } +// +// +// /** +// * Computes a buffer around {@code geom} with . +// */ +// public static Geom ST_Buffer( Geom geom, double distance, int quadSegs ) { +// throw todo(); +// } +// +// +// /** +// * Computes a buffer around {@code geom}. +// */ +// public static Geom ST_Buffer( Geom geom, double bufferSize, String style ) { +// int quadSegCount = 8; +// CapStyle endCapStyle = CapStyle.ROUND; +// JoinStyle joinStyle = JoinStyle.ROUND; +// float mitreLimit = 5f; +// int i = 0; +// parse: +// for ( ; ; ) { +// int equals = style.indexOf( '=', i ); +// if ( equals < 0 ) { +// break; +// } +// int space = style.indexOf( ' ', equals ); +// if ( space < 0 ) { +// space = style.length(); +// } +// String name = style.substring( i, equals ); +// String value = style.substring( equals + 1, space ); +// switch ( name ) { +// case "quad_segs": +// quadSegCount = Integer.valueOf( value ); +// break; +// case "endcap": +// endCapStyle = CapStyle.of( value ); +// break; +// case "join": +// joinStyle = JoinStyle.of( value ); +// break; +// case "mitre_limit": +// case "miter_limit": +// mitreLimit = Float.parseFloat( value ); +// break; +// default: +// // ignore the value +// } +// i = space; +// for ( ; ; ) { +// if ( i >= style.length() ) { +// break parse; +// } +// if ( style.charAt( i ) != ' ' ) { +// break; +// } +// ++i; +// } +// } +// return buffer( geom, bufferSize, quadSegCount, endCapStyle, joinStyle, mitreLimit ); +// } +// +// +// private static Geom buffer( Geom geom, double bufferSize, int quadSegCount, CapStyle endCapStyle, JoinStyle joinStyle, float mitreLimit ) { +// Util.discard( endCapStyle + ":" + joinStyle + ":" + mitreLimit + ":" + quadSegCount ); +// throw todo(); +// } +// +// +// /** +// * Computes the union of {@code geom1} and {@code geom2}. +// */ +// public static Geom ST_Union( Geom geom1, Geom geom2 ) { +// SpatialReference sr = geom1.sr(); +// final Geometry g = GeometryEngine.union( new Geometry[]{ geom1.g(), geom2.g() }, sr ); +// return bind( g, sr ); +// } +// +// +// /** +// * Computes the union of the geometries in {@code geomCollection}. +// */ +// @SemiStrict +// public static Geom ST_Union( Geom geomCollection ) { +// SpatialReference sr = geomCollection.sr(); +// final Geometry g = GeometryEngine.union( new Geometry[]{ geomCollection.g() }, sr ); +// return bind( g, sr ); +// } +// +// +// /** +// * Transforms {@code geom} from one coordinate reference system (CRS) to the CRS specified by {@code srid}. +// */ +// public static Geom ST_Transform( Geom geom, int srid ) { +// return geom.transform( srid ); +// } +// +// +// /** +// * Returns a copy of {@code geom} with a new SRID. +// */ +// public static Geom ST_SetSRID( Geom geom, int srid ) { +// return geom.transform( srid ); +// } +// +// +// /** +// * How the "buffer" command terminates the end of a line. +// */ +// enum CapStyle { +// ROUND, FLAT, SQUARE; +// +// +// static CapStyle of( String value ) { +// switch ( value ) { +// case "round": +// return ROUND; +// case "flat": +// case "butt": +// return FLAT; +// case "square": +// return SQUARE; +// default: +// throw new IllegalArgumentException( "unknown endcap value: " + value ); +// } +// } +// } +// +// +// /** +// * How the "buffer" command decorates junctions between line segments. +// */ +// enum JoinStyle { +// ROUND, MITRE, BEVEL; +// +// +// static JoinStyle of( String value ) { +// switch ( value ) { +// case "round": +// return ROUND; +// case "mitre": +// case "miter": +// return MITRE; +// case "bevel": +// return BEVEL; +// default: +// throw new IllegalArgumentException( "unknown join value: " + value ); +// } +// } +// } +// +// +// /** +// * Geometry. It may or may not have a spatial reference associated with it. +// */ +// public interface Geom { +// +// Geometry g(); +// +// SpatialReference sr(); +// +// Geom transform( int srid ); +// +// Geom wrap( Geometry g ); +// +// } +// +// +// /** +// * Sub-class of geometry that has no spatial reference. +// */ +// static class SimpleGeom implements Geom { +// +// final Geometry g; +// +// +// SimpleGeom( Geometry g ) { +// this.g = Objects.requireNonNull( g ); +// } +// +// +// @Override +// public String toString() { +// return g.toString(); +// } +// +// +// @Override +// public Geometry g() { +// return g; +// } +// +// +// @Override +// public SpatialReference sr() { +// return SPATIAL_REFERENCE; +// } +// +// +// @Override +// public Geom transform( int srid ) { +// if ( srid == SPATIAL_REFERENCE.getID() ) { +// return this; +// } +// return bind( g, srid ); +// } +// +// +// @Override +// public Geom wrap( Geometry g ) { +// return new SimpleGeom( g ); +// } +// +// } +// +// +// /** +// * Sub-class of geometry that has a spatial reference. +// */ +// static class MapGeom implements Geom { +// +// final MapGeometry mg; +// +// +// MapGeom( MapGeometry mg ) { +// this.mg = Objects.requireNonNull( mg ); +// } +// +// +// @Override +// public String toString() { +// return mg.toString(); +// } +// +// +// @Override +// public Geometry g() { +// return mg.getGeometry(); +// } +// +// +// @Override +// public SpatialReference sr() { +// return mg.getSpatialReference(); +// } +// +// +// @Override +// public Geom transform( int srid ) { +// if ( srid == NO_SRID ) { +// return new SimpleGeom( mg.getGeometry() ); +// } +// if ( srid == mg.getSpatialReference().getID() ) { +// return this; +// } +// return bind( mg.getGeometry(), srid ); +// } +// +// +// @Override +// public Geom wrap( Geometry g ) { +// return bind( g, this.mg.getSpatialReference() ); +// } +// +// } +// +// +// /** +// * Geometry types, with the names and codes assigned by OGC. +// */ +// enum Type { +// Geometry( 0 ), +// POINT( 1 ), +// LINESTRING( 2 ), +// POLYGON( 3 ), +// MULTIPOINT( 4 ), +// MULTILINESTRING( 5 ), +// MULTIPOLYGON( 6 ), +// GEOMCOLLECTION( 7 ), +// CURVE( 13 ), +// SURFACE( 14 ), +// POLYHEDRALSURFACE( 15 ); +// +// final int code; +// +// +// Type( int code ) { +// this.code = code; +// } +// } +// +//} +// diff --git a/core/src/main/java/org/polypheny/db/prepare/JavaTypeFactoryImpl.java b/core/src/main/java/org/polypheny/db/prepare/JavaTypeFactoryImpl.java index aa04e951f0..aae92abf22 100644 --- a/core/src/main/java/org/polypheny/db/prepare/JavaTypeFactoryImpl.java +++ b/core/src/main/java/org/polypheny/db/prepare/JavaTypeFactoryImpl.java @@ -58,7 +58,6 @@ import org.polypheny.db.algebra.type.AlgRecordType; import org.polypheny.db.algebra.type.DocumentType; import org.polypheny.db.algebra.type.GraphType; -import org.polypheny.db.functions.GeoFunctions; import org.polypheny.db.runtime.Unit; import org.polypheny.db.type.AbstractPolyType; import org.polypheny.db.type.JavaToPolyTypeConversionRules; @@ -81,6 +80,8 @@ import org.polypheny.db.type.entity.graph.PolyNode; import org.polypheny.db.type.entity.graph.PolyPath; import org.polypheny.db.type.entity.relational.PolyMap; +import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyPoint; import org.polypheny.db.util.Pair; import org.polypheny.db.util.Util; @@ -250,7 +251,7 @@ public Type getJavaClass( AlgDataType type ) { case VARBINARY: return PolyBinary.class; case GEOMETRY: - return GeoFunctions.Geom.class; + return PolyGeometry.class; case SYMBOL: return PolySymbol.class; case GRAPH: diff --git a/core/src/main/java/org/polypheny/db/type/JavaToPolyTypeConversionRules.java b/core/src/main/java/org/polypheny/db/type/JavaToPolyTypeConversionRules.java index 940b8a912e..48210dc26d 100644 --- a/core/src/main/java/org/polypheny/db/type/JavaToPolyTypeConversionRules.java +++ b/core/src/main/java/org/polypheny/db/type/JavaToPolyTypeConversionRules.java @@ -43,7 +43,8 @@ import java.util.List; import java.util.Map; import org.apache.calcite.avatica.util.ArrayImpl; -import org.polypheny.db.functions.GeoFunctions; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; /** @@ -83,7 +84,7 @@ public class JavaToPolyTypeConversionRules { .put( Time.class, PolyType.TIME ) .put( BigDecimal.class, PolyType.DECIMAL ) - .put( GeoFunctions.Geom.class, PolyType.GEOMETRY ) + .put ( Geometry.class, PolyType.GEOMETRY ) .put( ResultSet.class, PolyType.CURSOR ) .put( ColumnList.class, PolyType.COLUMN_LIST ) diff --git a/core/src/main/java/org/polypheny/db/type/PolyType.java b/core/src/main/java/org/polypheny/db/type/PolyType.java index 1a41b1abc7..c84133c087 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyType.java +++ b/core/src/main/java/org/polypheny/db/type/PolyType.java @@ -370,8 +370,6 @@ public enum PolyType { Types.JAVA_OBJECT, PolyTypeFamily.GEO ), - // TODO: add all other Geo types - FILE( PrecScale.NO_NO, true, @@ -431,7 +429,8 @@ public enum PolyType { TINYINT, SMALLINT, BIGINT, REAL, DOUBLE, SYMBOL, INTERVAL_YEAR, INTERVAL_YEAR_MONTH, INTERVAL_MONTH, INTERVAL_DAY, INTERVAL_DAY_HOUR, INTERVAL_DAY_MINUTE, INTERVAL_DAY_SECOND, INTERVAL_HOUR, INTERVAL_HOUR_MINUTE, INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND, INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, - TIMESTAMP_WITH_LOCAL_TIME_ZONE, FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST ); + TIMESTAMP_WITH_LOCAL_TIME_ZONE, FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST, + GEOMETRY ); public static final List BOOLEAN_TYPES = ImmutableList.of( BOOLEAN ); @@ -464,6 +463,7 @@ public enum PolyType { public static final List BLOB_TYPES = ImmutableList.of( FILE, AUDIO, IMAGE, VIDEO ); // TODO: add Geometry types + public static final List GEOMETRY_TYPES = ImmutableList.of( GEOMETRY ); public static final Set YEAR_INTERVAL_TYPES = Sets.immutableEnumSet( @@ -505,6 +505,7 @@ public enum PolyType { // TODO: provide real support for these eventually .put( ExtraPolyTypes.NCHAR, CHAR ) .put( ExtraPolyTypes.NVARCHAR, VARCHAR ) + .put( ExtraPolyTypes.GEOMETRY, GEOMETRY ) // TODO: additional types not yet supported. See ExtraSqlTypes. // .put(Types.LONGVARCHAR, Longvarchar) diff --git a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java index 68b7159035..06225558eb 100644 --- a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java +++ b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java @@ -285,6 +285,8 @@ public Consistency getConsistency() { public static final PolySingleOperandTypeChecker ARRAY = family( PolyTypeFamily.ARRAY ); + public static final PolySingleOperandTypeChecker GEOMETRY = family( PolyTypeFamily.GEO ); + /** * Checks that returns whether a value is a multiset or an array. Cf Java, where list and set are collections but a map is not. */ diff --git a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java index d95759f9d3..2a3a4577e6 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java +++ b/core/src/main/java/org/polypheny/db/type/entity/PolyValue.java @@ -53,6 +53,8 @@ import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.Point; import org.polypheny.db.algebra.type.AlgDataType; import org.polypheny.db.algebra.type.AlgDataTypeFactory; import org.polypheny.db.catalog.exceptions.GenericRuntimeException; @@ -86,6 +88,7 @@ import org.polypheny.db.type.entity.graph.PolyPath; import org.polypheny.db.type.entity.relational.PolyMap; import org.polypheny.db.type.entity.relational.PolyMap.PolyMapSerializerDef; +import org.polypheny.db.type.entity.spatial.PolyPoint; @Value @Slf4j @@ -136,7 +139,8 @@ @JsonSubTypes.Type(value = PolyPath.class, name = "PATH"), @JsonSubTypes.Type(value = PolyDictionary.class, name = "DICTIONARY"), @JsonSubTypes.Type(value = PolyUserDefinedValue.class, name = "UDV"), - @JsonSubTypes.Type(value = PolyGeometry.class, name = "GEOMETRY") + @JsonSubTypes.Type(value = PolyGeometry.class, name = "GEOMETRY"), + @JsonSubTypes.Type(value = PolyPoint.class, name = "POINT") }) public abstract class PolyValue implements Expressible, Comparable, PolySerializable { @@ -233,8 +237,8 @@ public static Function1 getPolyToJava( AlgDataType type, bool case VIDEO: return o -> o.asBlob().asByteArray(); case GEOMETRY: - // TODO: check how does it work - return o -> o.asGeometry().toString(); + // TODO: we need to convert to types that avatica has / that are in JDBC + return o -> o.asGeometry().toString(); // (Object) o.asGeometry().getJtsGeometry(); default: throw new org.apache.commons.lang3.NotImplementedException( "meta" ); } @@ -441,7 +445,6 @@ public static PolyValue convert( PolyValue value, PolyType type ) { public static PolyValue fromType( Object object, PolyType type ) { - // TODO: should GIS be here? switch ( type ) { case BOOLEAN: return PolyBoolean.of( (Boolean) object ); @@ -482,6 +485,8 @@ public static PolyValue fromType( Object object, PolyType type ) { case BINARY: case VARBINARY: return PolyBinary.of( (ByteString) object ); + case GEOMETRY: + return PolyGeometry.of( (Geometry) object ); // or of( (String) object ) } throw new NotImplementedException(); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 4ecd124edf..c3d1886e77 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -608,7 +608,7 @@ public boolean coveredBy( @NotNull PolyGeometry g ) { * @throws GeometryTopologicalException in case intersectionPattern contains not of 9 characters */ public boolean relate( @NotNull PolyGeometry g, @NotNull String intersectionPattern ) throws GeometryTopologicalException { - if ( intersectionPattern.length() == 9 ) { + if ( intersectionPattern.length() != 9 ) { throw new GeometryTopologicalException( "DE-9IM pattern should contain 9 characters." ); } return jtsGeometry.relate( g.getJtsGeometry(), intersectionPattern ); diff --git a/core/src/main/java/org/polypheny/db/type/inference/InferTypes.java b/core/src/main/java/org/polypheny/db/type/inference/InferTypes.java index dcf02058f2..9392285349 100644 --- a/core/src/main/java/org/polypheny/db/type/inference/InferTypes.java +++ b/core/src/main/java/org/polypheny/db/type/inference/InferTypes.java @@ -98,6 +98,18 @@ private InferTypes() { }; + /** + * Operand type-inference strategy where an unknown operand type is assumed to be GEOMETRY. + */ + public static final PolyOperandTypeInference GEOMETRY = + ( callBinding, returnType, operandTypes ) -> { + AlgDataTypeFactory typeFactory = callBinding.getTypeFactory(); + for ( int i = 0; i < operandTypes.length; ++i ) { + operandTypes[i] = typeFactory.createPolyType( PolyType.GEOMETRY ); + } + }; + + /** * Returns an {@link PolyOperandTypeInference} that returns a given list of types. */ diff --git a/core/src/main/java/org/polypheny/db/type/inference/ReturnTypes.java b/core/src/main/java/org/polypheny/db/type/inference/ReturnTypes.java index 3e74bf3f14..6c29d0f602 100644 --- a/core/src/main/java/org/polypheny/db/type/inference/ReturnTypes.java +++ b/core/src/main/java/org/polypheny/db/type/inference/ReturnTypes.java @@ -654,5 +654,10 @@ public int size() { } }; + /** + * Type-inference strategy whereby the result type of a call is GEOMETRY. + */ + public static final PolyReturnTypeInference GEOMETRY = explicit( PolyType.GEOMETRY ); + } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index ee15e65b25..0acf55cd0c 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -113,6 +113,7 @@ import org.polypheny.db.functions.CypherFunctions; import org.polypheny.db.functions.Functions; import org.polypheny.db.functions.Functions.FlatProductInputType; +import org.polypheny.db.functions.GeoFunctions; import org.polypheny.db.functions.MqlFunctions; import org.polypheny.db.functions.RefactorFunctions; import org.polypheny.db.functions.TemporalFunctions; @@ -148,6 +149,7 @@ import org.polypheny.db.type.entity.graph.PolyGraph; import org.polypheny.db.type.entity.graph.PolyNode; import org.polypheny.db.type.entity.graph.PolyPath; +import org.polypheny.db.type.entity.spatial.PolyGeometry; /** @@ -440,6 +442,9 @@ public enum BuiltInMethod { RESULTSET_GETBYTES( ResultSet.class, "getBytes", int.class ), RESULTSET_GETBINARYSTREAM( ResultSet.class, "getBinaryStream", int.class ), UNWRAP_INTERVAL( RefactorFunctions.class, "unwrap", PolyInterval.class ), + // GEO METHODS + ST_GEO_FROM_TEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ), + ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), /// MQL BUILT-IN METHODS MQL_EQ( MqlFunctions.class, "docEq", PolyValue.class, PolyValue.class ), MQL_GT( MqlFunctions.class, "docGt", PolyValue.class, PolyValue.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java new file mode 100644 index 0000000000..d6bbfd0265 --- /dev/null +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -0,0 +1,59 @@ +/* + * 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.sql.fun; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import lombok.extern.slf4j.Slf4j; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.polypheny.db.AdapterTestSuite; +import org.polypheny.db.TestHelper; +import org.polypheny.db.excluded.CassandraExcluded; + +@SuppressWarnings({ "SqlDialectInspection", "SqlNoDataSourceInspection" }) +@Slf4j +@Category({ AdapterTestSuite.class, CassandraExcluded.class }) +public class GeoFunctionsTest { + + @BeforeClass + public static void start() throws SQLException { + // Ensures that Polypheny-DB is running + //noinspection ResultOfMethodCallIgnored + TestHelper.getInstance(); + } + + @Test + public void geoFromText() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + ResultSet resultSet = statement.executeQuery( "SELECT ST_GeoFromText('Point(0 1)')" ); + List received = TestHelper.convertResultSetToList( resultSet ); + System.out.println(received); + resultSet = statement.executeQuery( "SELECT ST_X(ST_GeoFromText('Point(0 1)'))" ); + received = TestHelper.convertResultSetToList( resultSet ); + System.out.println(received); + } + } + } + +} diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index ef64acab64..b2680b7139 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -143,6 +143,7 @@ import org.polypheny.db.sql.language.fun.SqlRowOperator; import org.polypheny.db.sql.language.fun.SqlSequenceValueOperator; import org.polypheny.db.sql.language.fun.SqlSingleValueAggFunction; +import org.polypheny.db.sql.language.fun.SqlStGeoFromText; import org.polypheny.db.sql.language.fun.SqlStdOperatorTable; import org.polypheny.db.sql.language.fun.SqlStringContextVariable; import org.polypheny.db.sql.language.fun.SqlSubstringFunction; @@ -2485,6 +2486,29 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec register( OperatorName.UNWRAP_INTERVAL, new LangFunctionOperator( OperatorName.UNWRAP_INTERVAL.name(), Kind.OTHER_FUNCTION ) ); + // GEO functions + // TODO: move it to separate class and add a varying argument [SRID] how it's done for distances +// register( +// OperatorName.ST_GEOFROMTEXT, +// new SqlFunction( +// "ST_GEOFROMTEXT", +// Kind.OTHER_FUNCTION, +// ReturnTypes.GEOMETRY, +// null, +// OperandTypes.STRING, +// FunctionCategory.GEOMETRY ) ); + register( OperatorName.ST_GEOFROMTEXT, new SqlStGeoFromText() ); + + register( + OperatorName.ST_X, + new SqlFunction( + "ST_X", + Kind.OTHER_FUNCTION, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + isInit = true; } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java new file mode 100644 index 0000000000..3974ded02f --- /dev/null +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java @@ -0,0 +1,47 @@ +/* + * 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.sql.language.fun; + +import java.util.List; +import org.polypheny.db.algebra.constant.FunctionCategory; +import org.polypheny.db.algebra.constant.Kind; +import org.polypheny.db.sql.language.SqlFunction; +import org.polypheny.db.type.checker.OperandTypes; +import org.polypheny.db.type.inference.InferTypes; +import org.polypheny.db.type.inference.ReturnTypes; + +/** + * Definition of the "ST_GeoFromText" spatial function. + */ +public class SqlStGeoFromText extends SqlFunction { + + /** + * Creates the SqlStGeoFromText. + */ + public SqlStGeoFromText() { + super( + "ST_GEOFROMTEXT", + Kind.OTHER_FUNCTION, + ReturnTypes.GEOMETRY, + null, + OperandTypes.STRING, + FunctionCategory.GEOMETRY ); + } + + // TODO: add varying arguments for SRID + +} From 4b478203615d062d17cf6b3d79cfdee9cf4a8af7 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 9 Nov 2023 10:44:00 +0100 Subject: [PATCH 09/16] GIS #462: made 2nd argument optional for ST_GeoFromText; extended tests and validate for points before execution methods --- .../polypheny/db/algebra/constant/Kind.java | 5 + .../db/algebra/enumerable/RexImpTable.java | 2 + .../db/algebra/operators/OperatorName.java | 17 +++ .../polypheny/db/functions/GeoFunctions.java | 44 ++++++- .../org/polypheny/db/util/BuiltInMethod.java | 2 + .../db/sql/fun/GeoFunctionsTest.java | 34 +++++- .../polypheny/db/sql/SqlLanguagePlugin.java | 32 ++++-- .../db/sql/language/fun/SqlStGeoFromText.java | 107 ++++++++++++++++-- 8 files changed, 212 insertions(+), 31 deletions(-) diff --git a/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java b/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java index 5295f8fb39..64a97ce7fa 100644 --- a/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java +++ b/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java @@ -117,6 +117,11 @@ public enum Kind { */ DISTANCE, + /** + * GEO functions. + */ + GEO, + /** * POSITION Function */ diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index ca262a1156..4c48315be8 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -370,6 +370,8 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_GeoFromText operator function: create the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} from text + */ ST_GEOFROMTEXT( Function.class ), + + /** + * The ST_X operator function: receive the X coordinate of the {@link org.polypheny.db.type.entity.spatial.PolyPoint} + */ ST_X( Function.class ), + /** + * The ST_Y operator function: receive the Y coordinate of the {@link org.polypheny.db.type.entity.spatial.PolyPoint} + */ + ST_Y( Function.class ), + + /** + * The ST_Z operator function: receive the Z coordinate of the {@link org.polypheny.db.type.entity.spatial.PolyPoint} + */ + ST_Z( Function.class ), + //------------------------------------------------------------- // SET OPERATORS //------------------------------------------------------------- diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 19e35f9fc5..5c1d468240 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -20,35 +20,67 @@ import org.polypheny.db.type.entity.PolyFloat; import org.polypheny.db.type.entity.PolyString; +import org.polypheny.db.type.entity.category.PolyNumber; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; +/** + * Implementations of Geo functions + */ public class GeoFunctions { + private static final String POINT_RESTRICTION = "This function could be applied only to points"; + + private GeoFunctions() { // empty on purpose } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stGeoFromText( PolyString wkt ) { try { return PolyGeometry.of( wkt.value ); + } catch ( InvalidGeometryException e ) { + throw toUnchecked( e ); } - catch ( InvalidGeometryException e ) { + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stGeoFromText( PolyString wkt, PolyNumber srid ) { + try { + return PolyGeometry.of( wkt.value, srid.intValue() ); + } catch ( InvalidGeometryException e ) { throw toUnchecked( e ); } } + @SuppressWarnings("UnusedDeclaration") public static PolyFloat stX( PolyGeometry geometry ) { - if (!geometry.isPoint()) { - throw toUnchecked( new InvalidGeometryException( "This function could be applied only to points" )); - } + restrictToPoints( geometry ); return PolyFloat.of( geometry.asPoint().getX() ); } - public static PolyString test1( PolyString wkt ) { - return wkt; + + @SuppressWarnings("UnusedDeclaration") + public static PolyFloat stY( PolyGeometry geometry ) { + restrictToPoints( geometry ); + return PolyFloat.of( geometry.asPoint().getY() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyFloat stZ( PolyGeometry geometry ) { + restrictToPoints( geometry ); + return PolyFloat.of( geometry.asPoint().getZ() ); + } + + private static void restrictToPoints( PolyGeometry geometry ) { + if ( !geometry.isPoint() ) { + throw toUnchecked( new InvalidGeometryException( POINT_RESTRICTION ) ); + } } } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 0acf55cd0c..84d7f6dcfa 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -445,6 +445,8 @@ public enum BuiltInMethod { // GEO METHODS ST_GEO_FROM_TEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ), ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), + ST_Y( GeoFunctions.class, "stY", PolyGeometry.class ), + ST_Z( GeoFunctions.class, "stZ", PolyGeometry.class ), /// MQL BUILT-IN METHODS MQL_EQ( MqlFunctions.class, "docEq", PolyValue.class, PolyValue.class ), MQL_GT( MqlFunctions.class, "docGt", PolyValue.class, PolyValue.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index d6bbfd0265..eb25d25de7 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -16,6 +16,7 @@ package org.polypheny.db.sql.fun; +import com.google.common.collect.ImmutableList; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -46,12 +47,33 @@ public void geoFromText() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { Connection connection = polyphenyDbConnection.getConnection(); try ( Statement statement = connection.createStatement() ) { - ResultSet resultSet = statement.executeQuery( "SELECT ST_GeoFromText('Point(0 1)')" ); - List received = TestHelper.convertResultSetToList( resultSet ); - System.out.println(received); - resultSet = statement.executeQuery( "SELECT ST_X(ST_GeoFromText('Point(0 1)'))" ); - received = TestHelper.convertResultSetToList( resultSet ); - System.out.println(received); + // ST_GeoFromText with only 1 parameter (WKT) + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_GeoFromText('POINT (0 1)')" ), + ImmutableList.of( + new Object[]{ "SRID=0;POINT (0 1)" } + ) ); + // ST_GeoFromText with 2 parameters (WKT, SRID) + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_GeoFromText('POINT (0 1)', 1)" ), + ImmutableList.of( + new Object[]{ "SRID=1;POINT (0 1)" } + ) ); + } + } + } + + @Test + public void pointFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // get X coordinate of the point + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_X(ST_GeoFromText('POINT (0 1)'))" ), + ImmutableList.of( + new Object[]{ 0.0 } + ) ); } } } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index b2680b7139..4288a909fa 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2487,23 +2487,33 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec register( OperatorName.UNWRAP_INTERVAL, new LangFunctionOperator( OperatorName.UNWRAP_INTERVAL.name(), Kind.OTHER_FUNCTION ) ); // GEO functions - // TODO: move it to separate class and add a varying argument [SRID] how it's done for distances -// register( -// OperatorName.ST_GEOFROMTEXT, -// new SqlFunction( -// "ST_GEOFROMTEXT", -// Kind.OTHER_FUNCTION, -// ReturnTypes.GEOMETRY, -// null, -// OperandTypes.STRING, -// FunctionCategory.GEOMETRY ) ); register( OperatorName.ST_GEOFROMTEXT, new SqlStGeoFromText() ); register( OperatorName.ST_X, new SqlFunction( "ST_X", - Kind.OTHER_FUNCTION, + Kind.GEO, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_Y, + new SqlFunction( + "ST_Y", + Kind.GEO, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_Z, + new SqlFunction( + "ST_Z", + Kind.GEO, ReturnTypes.DOUBLE, InferTypes.GEOMETRY, OperandTypes.GEOMETRY, diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java index 3974ded02f..38470af57a 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStGeoFromText.java @@ -16,32 +16,123 @@ package org.polypheny.db.sql.language.fun; +import static org.polypheny.db.util.Static.RESOURCE; + import java.util.List; import org.polypheny.db.algebra.constant.FunctionCategory; import org.polypheny.db.algebra.constant.Kind; +import org.polypheny.db.algebra.type.AlgDataType; +import org.polypheny.db.nodes.CallBinding; +import org.polypheny.db.nodes.Operator; import org.polypheny.db.sql.language.SqlFunction; +import org.polypheny.db.type.OperandCountRange; +import org.polypheny.db.type.PolyOperandCountRanges; +import org.polypheny.db.type.PolyTypeUtil; import org.polypheny.db.type.checker.OperandTypes; +import org.polypheny.db.type.checker.PolyOperandTypeChecker; import org.polypheny.db.type.inference.InferTypes; import org.polypheny.db.type.inference.ReturnTypes; +import org.polypheny.db.util.CoreUtil; +import org.polypheny.db.util.Util; /** * Definition of the "ST_GeoFromText" spatial function. + * The function has a required parameter - WKT string representation + * and an optional SRID integer. */ public class SqlStGeoFromText extends SqlFunction { + private static final PolyOperandTypeChecker ST_GEOFROMTEXT_ARG_CHECKER = new PolyOperandTypeChecker() { + + @Override + public boolean checkOperandTypes( CallBinding callBinding, boolean throwOnFailure ) { + int nOperandsActual = callBinding.getOperandCount(); + + // Make sure the first argument is not null + if ( CoreUtil.isNullLiteral( callBinding.operand( 0 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 0 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + + // Make sure the first argument is a string + if ( !PolyTypeUtil.inCharFamily( callBinding.getOperandType( 0 ) ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 0 ), RESOURCE.expectedCharacter() ); + } else { + return false; + } + } + + // Check, if present, whether second argument is a number + if ( nOperandsActual == 2 ) { + if ( CoreUtil.isNullLiteral( callBinding.operand( 1 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 1 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + + if ( (!PolyTypeUtil.isNumeric( callBinding.getOperandType( 1 ) )) ) { + if ( throwOnFailure ) { + throw callBinding.newValidationSignatureError(); + } else { + return false; + } + } + } + + return true; + + } + + + @Override + public String getAllowedSignatures( Operator op, String opName ) { + return "'ST_GeoFromText()'" + "\n" + "'ST_GeoFromText(, )'"; + } + + + @Override + public Consistency getConsistency() { + return Consistency.NONE; + } + + + @Override + public OperandCountRange getOperandCountRange() { + return PolyOperandCountRanges.between( 1, 2 ); + } + + + @Override + public boolean isOptional( int i ) { + return i == 1; + } + }; + + /** * Creates the SqlStGeoFromText. */ public SqlStGeoFromText() { - super( - "ST_GEOFROMTEXT", - Kind.OTHER_FUNCTION, - ReturnTypes.GEOMETRY, - null, - OperandTypes.STRING, - FunctionCategory.GEOMETRY ); + super( "ST_GEOFROMTEXT", Kind.GEO, ReturnTypes.GEOMETRY, null, ST_GEOFROMTEXT_ARG_CHECKER, FunctionCategory.GEOMETRY ); } - // TODO: add varying arguments for SRID + + @Override + public String getSignatureTemplate( int operandsCount ) { + switch ( operandsCount ) { + case 1: + return "{0}({1})"; + case 2: + return "{0}({1}, {2})"; + default: + throw new AssertionError(); + } + } } From 352d96bbe6209a909d7c22fe220eac1915c77a4f Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 9 Nov 2023 12:06:55 +0100 Subject: [PATCH 10/16] GIS #462: added some functions on LineStrings --- .../db/algebra/enumerable/RexImpTable.java | 7 +++ .../db/algebra/operators/OperatorName.java | 29 +++++++++++ .../polypheny/db/functions/GeoFunctions.java | 46 ++++++++++++++++ .../db/type/checker/OperandTypes.java | 1 + .../db/type/entity/spatial/PolyGeometry.java | 2 +- .../org/polypheny/db/util/BuiltInMethod.java | 7 +++ .../db/sql/fun/GeoFunctionsTest.java | 39 ++++++++++++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 52 +++++++++++++++++++ 8 files changed, 182 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 4c48315be8..2fa3051d53 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -369,9 +369,16 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_X operator function: receive the X coordinate of the {@link org.polypheny.db.type.entity.spatial.PolyPoint} */ @@ -1328,6 +1330,33 @@ public enum OperatorName { */ ST_Z( Function.class ), + // Functions on LineStrings + + /** + * The ST_IsClosed operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyLineString} is closed + */ + ST_ISCLOSED( Function.class ), + + /** + * The ST_IsRing operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyLineString} is a ring + */ + ST_ISRING( Function.class ), + + /** + * The ST_IsCoordinate operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyPoint} is coordinate of {@link org.polypheny.db.type.entity.spatial.PolyLineString} + */ + ST_ISCOORDINATE( Function.class ), + + /** + * The ST_StartPoint operator function: return the start {@link org.polypheny.db.type.entity.spatial.PolyPoint} of {@link org.polypheny.db.type.entity.spatial.PolyLineString} + */ + ST_STARTPOINT( Function.class ), + + /** + * The ST_EndPoint operator function: return the end {@link org.polypheny.db.type.entity.spatial.PolyPoint} of {@link org.polypheny.db.type.entity.spatial.PolyLineString} + */ + ST_ENDPOINT( Function.class ), + //------------------------------------------------------------- // SET OPERATORS //------------------------------------------------------------- diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 5c1d468240..72e71427df 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -18,6 +18,7 @@ import static org.polypheny.db.functions.Functions.toUnchecked; +import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyFloat; import org.polypheny.db.type.entity.PolyString; import org.polypheny.db.type.entity.category.PolyNumber; @@ -30,6 +31,7 @@ public class GeoFunctions { private static final String POINT_RESTRICTION = "This function could be applied only to points"; + private static final String LINE_STRING_RESTRICTION = "This function could be applied only to line strings"; private GeoFunctions() { @@ -77,10 +79,54 @@ public static PolyFloat stZ( PolyGeometry geometry ) { return PolyFloat.of( geometry.asPoint().getZ() ); } + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsClosed( PolyGeometry geometry ) { + restrictToLineStrings( geometry ); + return PolyBoolean.of( geometry.asLineString().isClosed() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsRing( PolyGeometry geometry ) { + restrictToLineStrings( geometry ); + return PolyBoolean.of( geometry.asLineString().isRing() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsCoordinate( PolyGeometry geometry, PolyGeometry point ) { + restrictToLineStrings( geometry ); + restrictToPoints( point ); + return PolyBoolean.of( geometry.asLineString().isCoordinate( point.asPoint() ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stStartPoint( PolyGeometry geometry ) { + restrictToLineStrings( geometry ); + return PolyGeometry.of( geometry.asLineString().getStartPoint().getJtsGeometry() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stEndPoint( PolyGeometry geometry ) { + restrictToLineStrings( geometry ); + return PolyGeometry.of( geometry.asLineString().getEndPoint().getJtsGeometry() ); + } + + private static void restrictToPoints( PolyGeometry geometry ) { if ( !geometry.isPoint() ) { throw toUnchecked( new InvalidGeometryException( POINT_RESTRICTION ) ); } } + + private static void restrictToLineStrings( PolyGeometry geometry ) { + if ( !geometry.isLineString() ) { + throw toUnchecked( new InvalidGeometryException( LINE_STRING_RESTRICTION ) ); + } + } + } diff --git a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java index 06225558eb..146aa6157c 100644 --- a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java +++ b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java @@ -286,6 +286,7 @@ public Consistency getConsistency() { public static final PolySingleOperandTypeChecker ARRAY = family( PolyTypeFamily.ARRAY ); public static final PolySingleOperandTypeChecker GEOMETRY = family( PolyTypeFamily.GEO ); + public static final PolySingleOperandTypeChecker GEOMETRY_GEOMETRY = family( PolyTypeFamily.GEO, PolyTypeFamily.GEO ); /** * Checks that returns whether a value is a multiset or an array. Cf Java, where list and set are collections but a map is not. diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index c3d1886e77..21fbad4091 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -198,7 +198,7 @@ public PolyPoint asPoint() { public boolean isLineString() { - return geometryType.equals( PolyGeometryType.LINESTRING ); + return List.of( PolyGeometryType.LINESTRING, PolyGeometryType.LINEARRING ).contains( geometryType ); } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 84d7f6dcfa..4c6a5148ab 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -444,9 +444,16 @@ public enum BuiltInMethod { UNWRAP_INTERVAL( RefactorFunctions.class, "unwrap", PolyInterval.class ), // GEO METHODS ST_GEO_FROM_TEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ), + // on Points ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), ST_Y( GeoFunctions.class, "stY", PolyGeometry.class ), ST_Z( GeoFunctions.class, "stZ", PolyGeometry.class ), + // on LineStrings + ST_ISCLOSED( GeoFunctions.class, "stIsClosed", PolyGeometry.class ), + ST_ISRING( GeoFunctions.class, "stIsRing", PolyGeometry.class ), + ST_ISCOORDINATE( GeoFunctions.class, "stIsCoordinate", PolyGeometry.class, PolyGeometry.class ), + ST_STARTPOINT( GeoFunctions.class, "stStartPoint", PolyGeometry.class ), + ST_ENDPOINT( GeoFunctions.class, "stEndPoint", PolyGeometry.class ), /// MQL BUILT-IN METHODS MQL_EQ( MqlFunctions.class, "docEq", PolyValue.class, PolyValue.class ), MQL_GT( MqlFunctions.class, "docGt", PolyValue.class, PolyValue.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index eb25d25de7..82177dc5f6 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -78,4 +78,43 @@ public void pointFunctions() throws SQLException { } } + @Test + public void lineStringsFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // test if line is closed + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsClosed(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ false } + ) ); + // test if line is a ring + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsRing(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ false } + ) ); + // test if point is a coordinate of a line + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsCoordinate(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'), ST_GeoFromText('POINT (-1 -1)'))" ), + ImmutableList.of( + new Object[]{ true } + ) ); + // check the starting point + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_StartPoint(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;POINT (-1 -1)" } + ) ); + // check the ending point + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_EndPoint(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;POINT (6 7)" } + ) ); + } + } + } + } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 4288a909fa..0500a8bb69 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2489,6 +2489,7 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec // GEO functions register( OperatorName.ST_GEOFROMTEXT, new SqlStGeoFromText() ); + // on Points register( OperatorName.ST_X, new SqlFunction( @@ -2519,6 +2520,57 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec OperandTypes.GEOMETRY, FunctionCategory.GEOMETRY ) ); + // on LineStrings + register( + OperatorName.ST_ISCLOSED, + new SqlFunction( + "ST_ISCLOSED", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_ISRING, + new SqlFunction( + "ST_ISRING", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_ISCOORDINATE, + new SqlFunction( + "ST_ISCOORDINATE", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_STARTPOINT, + new SqlFunction( + "ST_STARTPOINT", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_ENDPOINT, + new SqlFunction( + "ST_ENDPOINT", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + isInit = true; } From ded05a079a2b8ef632e7bcf16bac4f1155f2b92e Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Thu, 9 Nov 2023 19:10:26 +0100 Subject: [PATCH 11/16] GIS #462: added some functions on Polygons --- .../db/algebra/enumerable/RexImpTable.java | 5 +++ .../db/algebra/operators/OperatorName.java | 21 +++++++++ .../polypheny/db/functions/GeoFunctions.java | 44 +++++++++++++++++++ .../db/type/checker/OperandTypes.java | 1 + .../org/polypheny/db/util/BuiltInMethod.java | 6 +++ .../db/sql/fun/GeoFunctionsTest.java | 34 ++++++++++++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 41 +++++++++++++++++ 7 files changed, 152 insertions(+) diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 2fa3051d53..390a36249d 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -379,6 +379,11 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_IsRectangle operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyPolygon} is rectangle + */ + ST_ISRECTANGLE( Function.class ), + + /** + * The ST_ExteriorRing operator function: return the exterior ring {@link org.polypheny.db.type.entity.spatial.PolyGeometry} of the {@link org.polypheny.db.type.entity.spatial.PolyPolygon} + */ + ST_EXTERIORRING( Function.class ), + + /** + * The ST_NumInteriorRing operator function: return the number of interior rings of the {@link org.polypheny.db.type.entity.spatial.PolyPolygon} + */ + ST_NUMINTERIORRING( Function.class ), + + /** + * The ST_InteriorRingN operator function: return the nth interior ring of the {@link org.polypheny.db.type.entity.spatial.PolyPolygon} + */ + ST_INTERIORRINGN( Function.class ), + //------------------------------------------------------------- // SET OPERATORS //------------------------------------------------------------- diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 72e71427df..f87ed22bf7 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -20,6 +20,7 @@ import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyFloat; +import org.polypheny.db.type.entity.PolyInteger; import org.polypheny.db.type.entity.PolyString; import org.polypheny.db.type.entity.category.PolyNumber; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; @@ -32,6 +33,7 @@ public class GeoFunctions { private static final String POINT_RESTRICTION = "This function could be applied only to points"; private static final String LINE_STRING_RESTRICTION = "This function could be applied only to line strings"; + private static final String POLYGON_RESTRICTION = "This function could be applied only to polygons"; private GeoFunctions() { @@ -58,6 +60,10 @@ public static PolyGeometry stGeoFromText( PolyString wkt, PolyNumber srid ) { } } + /* + * on Points + */ + @SuppressWarnings("UnusedDeclaration") public static PolyFloat stX( PolyGeometry geometry ) { @@ -79,6 +85,10 @@ public static PolyFloat stZ( PolyGeometry geometry ) { return PolyFloat.of( geometry.asPoint().getZ() ); } + /* + * on LineStrings + */ + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsClosed( PolyGeometry geometry ) { @@ -115,6 +125,34 @@ public static PolyGeometry stEndPoint( PolyGeometry geometry ) { return PolyGeometry.of( geometry.asLineString().getEndPoint().getJtsGeometry() ); } + /* + * on Polygons + */ + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsRectangle( PolyGeometry geometry ) { + restrictToPolygons( geometry ); + return PolyBoolean.of( geometry.asPolygon().isRectangle() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stExteriorRing( PolyGeometry geometry ) { + restrictToPolygons( geometry ); + return geometry.asPolygon().getExteriorRing(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyInteger stNumInteriorRing( PolyGeometry geometry ) { + restrictToPolygons( geometry ); + return PolyInteger.of( geometry.asPolygon().getNumInteriorRing() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stInteriorRingN( PolyGeometry geometry, PolyNumber n ) { + restrictToPolygons( geometry ); + return PolyGeometry.of( geometry.asPolygon().getInteriorRingN( n.intValue() ).getJtsGeometry() ); + } + private static void restrictToPoints( PolyGeometry geometry ) { if ( !geometry.isPoint() ) { @@ -129,4 +167,10 @@ private static void restrictToLineStrings( PolyGeometry geometry ) { } } + private static void restrictToPolygons( PolyGeometry geometry ) { + if ( !geometry.isPolygon() ) { + throw toUnchecked( new InvalidGeometryException( POLYGON_RESTRICTION ) ); + } + } + } diff --git a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java index 146aa6157c..ce54ee442f 100644 --- a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java +++ b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java @@ -287,6 +287,7 @@ public Consistency getConsistency() { public static final PolySingleOperandTypeChecker GEOMETRY = family( PolyTypeFamily.GEO ); public static final PolySingleOperandTypeChecker GEOMETRY_GEOMETRY = family( PolyTypeFamily.GEO, PolyTypeFamily.GEO ); + public static final PolySingleOperandTypeChecker GEOMETRY_INTEGER = family( PolyTypeFamily.GEO, PolyTypeFamily.INTEGER ); /** * Checks that returns whether a value is a multiset or an array. Cf Java, where list and set are collections but a map is not. diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 4c6a5148ab..a722011497 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -136,6 +136,7 @@ import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyDate; +import org.polypheny.db.type.entity.PolyInteger; import org.polypheny.db.type.entity.PolyInterval; import org.polypheny.db.type.entity.PolyList; import org.polypheny.db.type.entity.PolyString; @@ -454,6 +455,11 @@ public enum BuiltInMethod { ST_ISCOORDINATE( GeoFunctions.class, "stIsCoordinate", PolyGeometry.class, PolyGeometry.class ), ST_STARTPOINT( GeoFunctions.class, "stStartPoint", PolyGeometry.class ), ST_ENDPOINT( GeoFunctions.class, "stEndPoint", PolyGeometry.class ), + // on Polygons + ST_ISRECTANGLE( GeoFunctions.class, "stIsRectangle", PolyGeometry.class ), + ST_EXTERIORRING( GeoFunctions.class, "stExteriorRing", PolyGeometry.class ), + ST_NUMINTERIORRING( GeoFunctions.class, "stNumInteriorRing", PolyGeometry.class ), + ST_INTERIORRINGN( GeoFunctions.class, "stInteriorRingN", PolyGeometry.class, PolyInteger.class ), /// MQL BUILT-IN METHODS MQL_EQ( MqlFunctions.class, "docEq", PolyValue.class, PolyValue.class ), MQL_GT( MqlFunctions.class, "docGt", PolyValue.class, PolyValue.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index 82177dc5f6..ed9398b855 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -117,4 +117,38 @@ public void lineStringsFunctions() throws SQLException { } } + + @Test + public void polygonFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // test if polygon is rectangle + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsRectangle(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ false } + ) ); + // retrieve the exterior ring of the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_ExteriorRing(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;LINEARRING (-1 -1, 2 2, -1 2, -1 -1)" } + ) ); + // retrieve the number of interior ring of the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_NumInteriorRing(ST_GeoFromText('POLYGON((0.5 0.5,5 0,5 5,0 5,0.5 0.5), (1.5 1,4 3,4 1,1.5 1))'))" ), + ImmutableList.of( + new Object[]{ 1 } + ) ); + // retrieve the nth interior ring of the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_InteriorRingN(ST_GeoFromText('POLYGON((0.5 0.5,5 0,5 5,0 5,0.5 0.5), (1.5 1,4 3,4 1,1.5 1))'), 0)" ), + ImmutableList.of( + new Object[]{ "SRID=0;LINEARRING (1.5 1, 4 3, 4 1, 1.5 1)" } + ) ); + } + } + } + } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 0500a8bb69..e6e39c4c32 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2571,6 +2571,47 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec OperandTypes.GEOMETRY, FunctionCategory.GEOMETRY ) ); + // on Polygons + register( + OperatorName.ST_ISRECTANGLE, + new SqlFunction( + "ST_ISRECTANGLE", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_EXTERIORRING, + new SqlFunction( + "ST_EXTERIORRING", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_NUMINTERIORRING, + new SqlFunction( + "ST_NUMINTERIORRING", + Kind.GEO, + ReturnTypes.INTEGER, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_INTERIORRINGN, + new SqlFunction( + "ST_INTERIORRINGN", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_INTEGER, + FunctionCategory.GEOMETRY ) ); + isInit = true; } From 528fdc9e37ea79b15b6f02f4e85a922e3f5f4c77 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Fri, 10 Nov 2023 16:17:30 +0100 Subject: [PATCH 12/16] GIS #462: added functions to query geometry properties --- .../db/algebra/enumerable/RexImpTable.java | 19 +- .../db/algebra/operators/OperatorName.java | 89 ++++++++- .../polypheny/db/functions/GeoFunctions.java | 121 ++++++++++++ .../db/runtime/PolyphenyDbResource.java | 3 + .../org/polypheny/db/type/PolyTypeUtil.java | 7 + .../db/type/entity/spatial/PolyGeometry.java | 6 +- .../type/entity/spatial/PolyGeometryType.java | 2 +- .../org/polypheny/db/util/BuiltInMethod.java | 20 +- .../db/sql/fun/GeoFunctionsTest.java | 84 +++++++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 145 ++++++++++++++ .../db/sql/language/fun/SqlStBuffer.java | 177 ++++++++++++++++++ 11 files changed, 658 insertions(+), 15 deletions(-) create mode 100644 plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStBuffer.java diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 390a36249d..79b2b27766 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -368,7 +368,21 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_IsSimple operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyGeometry} is simple + */ + ST_ISSIMPLE( Function.class ), + + /** + * The ST_IsEmpty operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyGeometry} is empty + */ + ST_ISEMPTY( Function.class ), + + /** + * The ST_NumPoints operator function: receive the number of points in the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_NUMPOINTS( Function.class ), + + /** + * The ST_Dimension operator function: receive the dimension of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_DIMENSION( Function.class ), + + /** + * The ST_Length operator function: receive the length of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_LENGTH( Function.class ), + + /** + * The ST_Area operator function: receive the area of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_AREA( Function.class ), + + /** + * The ST_Envelope operator function: receive the minimum bounding box {@link org.polypheny.db.type.entity.spatial.PolyGeometry} that would include the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_ENVELOPE( Function.class ), + + /** + * The ST_Boundary operator function: receive the boundary of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_BOUNDARY( Function.class ), + + /** + * The ST_BoundaryDimension operator function: receive the boundary dimension of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_BOUNDARYDIMENSION( Function.class ), + + /** + * The ST_ConvexHull operator function: receive the convex full of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_CONVEXHULL( Function.class ), + + /** + * The ST_Centroid operator function: receive the centroid of the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_CENTROID( Function.class ), + + /** + * The ST_Reverse operator function: reverse the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_REVERSE( Function.class ), + + /** + * The ST_Buffer operator function: receive the buffer {@link org.polypheny.db.type.entity.spatial.PolyGeometry} around the {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_BUFFER( Function.class ), + // Functions on Points /** @@ -1358,6 +1425,7 @@ public enum OperatorName { ST_ENDPOINT( Function.class ), // Functions on Polygons + /** * The ST_IsRectangle operator function: check that {@link org.polypheny.db.type.entity.spatial.PolyPolygon} is rectangle */ @@ -1378,6 +1446,17 @@ public enum OperatorName { */ ST_INTERIORRINGN( Function.class ), + // Functions on GeometryCollection + /** + * The ST_NumGeometries operator function: return the number of {@link org.polypheny.db.type.entity.spatial.PolyGeometry} in {@link org.polypheny.db.type.entity.spatial.PolyGeometryCollection} + */ + ST_NUMGEOMETRIES( Function.class ), + + /** + * The ST_GeometryN operator function: return the nth geometry in the {@link org.polypheny.db.type.entity.spatial.PolyGeometryCollection} + */ + ST_GEOMETRYN( Function.class ), + //------------------------------------------------------------- // SET OPERATORS //------------------------------------------------------------- @@ -1507,6 +1586,7 @@ public enum OperatorName { EXTRACT_NAME( LangFunctionOperator.class ); + final public static List MQL_OPERATORS = Arrays.asList( MQL_EQUALS, MQL_GT, MQL_GTE, MQL_LT, MQL_LTE ); @Getter private final Class clazz; @@ -1514,13 +1594,4 @@ public enum OperatorName { OperatorName( Class clazz ) { this.clazz = clazz; } - - - final public static List MQL_OPERATORS = Arrays.asList( - MQL_EQUALS, - MQL_GT, - MQL_GTE, - MQL_LT, - MQL_LTE - ); } \ No newline at end of file diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index f87ed22bf7..82ff05abf0 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -25,6 +25,7 @@ import org.polypheny.db.type.entity.category.PolyNumber; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyGeometryType.BufferCapStyle; /** * Implementations of Geo functions @@ -34,12 +35,17 @@ public class GeoFunctions { private static final String POINT_RESTRICTION = "This function could be applied only to points"; private static final String LINE_STRING_RESTRICTION = "This function could be applied only to line strings"; private static final String POLYGON_RESTRICTION = "This function could be applied only to polygons"; + private static final String GEOMETRY_COLLECTION_RESTRICTION = "This function could be applied only to geometry collections"; private GeoFunctions() { // empty on purpose } + /* + * Create Geometry + */ + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stGeoFromText( PolyString wkt ) { @@ -60,6 +66,89 @@ public static PolyGeometry stGeoFromText( PolyString wkt, PolyNumber srid ) { } } + /* + * Common properties + */ + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsSimple( PolyGeometry geometry ) { + return PolyBoolean.of( geometry.isSimple() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIsEmpty( PolyGeometry geometry ) { + return PolyBoolean.of( geometry.isEmpty() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyInteger stNumPoints( PolyGeometry geometry ) { + return PolyInteger.of( geometry.getNumPoints() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyInteger stDimension( PolyGeometry geometry ) { + return PolyInteger.of( geometry.getDimension() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyFloat stLength( PolyGeometry geometry ) { + return PolyFloat.of( geometry.getLength() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyFloat stArea( PolyGeometry geometry ) { + return PolyFloat.of( geometry.getArea() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stEnvelope( PolyGeometry geometry ) { + return geometry.getEnvelope(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stBoundary( PolyGeometry geometry ) { + return geometry.getBoundary(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyInteger stBoundaryDimension( PolyGeometry geometry ) { + return PolyInteger.of (geometry.getBoundaryDimension() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stConvexHull( PolyGeometry geometry ) { + return geometry.convexHull(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stCentroid( PolyGeometry geometry ) { + return geometry.getCentroid(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stReverse( PolyGeometry geometry ) { + return geometry.reverse(); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance ) { + return geometry.buffer( distance.doubleValue() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance, PolyNumber quadrantSegments ) { + return geometry.buffer( distance.doubleValue(), quadrantSegments.intValue() ); + } + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance, PolyNumber quadrantSegments, PolyString endCapStyle ) { + return geometry.buffer( distance.doubleValue(), quadrantSegments.intValue(), BufferCapStyle.of( endCapStyle.value ) ); + } + + + /* + * Geometry Specific Functions + */ + /* * on Points */ @@ -128,6 +217,7 @@ public static PolyGeometry stEndPoint( PolyGeometry geometry ) { /* * on Polygons */ + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsRectangle( PolyGeometry geometry ) { restrictToPolygons( geometry ); @@ -141,18 +231,41 @@ public static PolyGeometry stExteriorRing( PolyGeometry geometry ) { return geometry.asPolygon().getExteriorRing(); } + @SuppressWarnings("UnusedDeclaration") public static PolyInteger stNumInteriorRing( PolyGeometry geometry ) { restrictToPolygons( geometry ); return PolyInteger.of( geometry.asPolygon().getNumInteriorRing() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stInteriorRingN( PolyGeometry geometry, PolyNumber n ) { restrictToPolygons( geometry ); return PolyGeometry.of( geometry.asPolygon().getInteriorRingN( n.intValue() ).getJtsGeometry() ); } + /* + * on GeometryCollection + */ + + + @SuppressWarnings("UnusedDeclaration") + public static PolyInteger stNumGeometries( PolyGeometry geometry ) { + restrictToGeometryCollection( geometry ); + return PolyInteger.of( geometry.asGeometryCollection().getNumGeometries() ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stGeometryN( PolyGeometry geometry, PolyNumber n ) { + restrictToGeometryCollection( geometry ); + return geometry.asGeometryCollection().getGeometryN( n.intValue() ); + } + + /* + * Helpers + */ private static void restrictToPoints( PolyGeometry geometry ) { if ( !geometry.isPoint() ) { @@ -167,10 +280,18 @@ private static void restrictToLineStrings( PolyGeometry geometry ) { } } + private static void restrictToPolygons( PolyGeometry geometry ) { if ( !geometry.isPolygon() ) { throw toUnchecked( new InvalidGeometryException( POLYGON_RESTRICTION ) ); } } + + private static void restrictToGeometryCollection( PolyGeometry geometry ) { + if ( !geometry.isGeometryCollection() ) { + throw toUnchecked( new InvalidGeometryException( GEOMETRY_COLLECTION_RESTRICTION ) ); + } + } + } diff --git a/core/src/main/java/org/polypheny/db/runtime/PolyphenyDbResource.java b/core/src/main/java/org/polypheny/db/runtime/PolyphenyDbResource.java index d6f32d085d..39ac91c81d 100644 --- a/core/src/main/java/org/polypheny/db/runtime/PolyphenyDbResource.java +++ b/core/src/main/java/org/polypheny/db/runtime/PolyphenyDbResource.java @@ -257,6 +257,9 @@ public interface PolyphenyDbResource { @BaseMessage("Expected a multimedia type") ExInst expectedMultimedia(); + @BaseMessage("Expected a geo type") + ExInst expectedGeometry(); + @BaseMessage("ELSE clause or at least one THEN clause must be non-NULL") ExInst mustNotNullInElse(); diff --git a/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java b/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java index 2e0b835124..81005d05e4 100644 --- a/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java +++ b/core/src/main/java/org/polypheny/db/type/PolyTypeUtil.java @@ -319,6 +319,13 @@ public static boolean inBooleanFamily( AlgDataType type ) { return type.getFamily() == PolyTypeFamily.BOOLEAN; } + /** + * @return true if type is in SqlTypeFamily.Geo + */ + public static boolean inGeoFamily( AlgDataType type ) { + return type.getFamily() == PolyTypeFamily.GEO; + } + /** * @return true if two types are in same type family diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 21fbad4091..48731f5937 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -384,7 +384,7 @@ public PolyPoint getCentroid() { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance ) { - return PolyPoint.of( jtsGeometry.buffer( distance ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance ) ); } @@ -400,7 +400,7 @@ public PolyGeometry buffer( double distance ) { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance, int quadrantSegments ) { - return PolyPoint.of( jtsGeometry.buffer( distance, quadrantSegments ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments ) ); } @@ -417,7 +417,7 @@ public PolyGeometry buffer( double distance, int quadrantSegments ) { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance, int quadrantSegments, BufferCapStyle endCapStyle ) { - return PolyPoint.of( jtsGeometry.buffer( distance, quadrantSegments, endCapStyle.code ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments, endCapStyle.code ) ); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java index cee3083c3a..3791223fc3 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryType.java @@ -56,7 +56,7 @@ public enum BufferCapStyle { } - static BufferCapStyle of( String value ) { + public static BufferCapStyle of( String value ) { switch ( value ) { case "round": return ROUND; diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index a722011497..ff276d9503 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -136,6 +136,7 @@ import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyDate; +import org.polypheny.db.type.entity.PolyFloat; import org.polypheny.db.type.entity.PolyInteger; import org.polypheny.db.type.entity.PolyInterval; import org.polypheny.db.type.entity.PolyList; @@ -444,7 +445,21 @@ public enum BuiltInMethod { RESULTSET_GETBINARYSTREAM( ResultSet.class, "getBinaryStream", int.class ), UNWRAP_INTERVAL( RefactorFunctions.class, "unwrap", PolyInterval.class ), // GEO METHODS - ST_GEO_FROM_TEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ), + ST_GEOFROMTEXT( GeoFunctions.class, "stGeoFromText", PolyString.class ), + // Common properties + ST_ISSIMPLE( GeoFunctions.class, "stIsSimple", PolyGeometry.class ), + ST_ISEMPTY( GeoFunctions.class, "stIsEmpty", PolyGeometry.class ), + ST_NUMPOINTS( GeoFunctions.class, "stNumPoints", PolyGeometry.class ), + ST_DIMENSION( GeoFunctions.class, "stDimension", PolyGeometry.class ), + ST_LENGTH( GeoFunctions.class, "stLength", PolyGeometry.class ), + ST_AREA( GeoFunctions.class, "stArea", PolyGeometry.class ), + ST_ENVELOPE( GeoFunctions.class, "stEnvelope", PolyGeometry.class ), + ST_BOUNDARY( GeoFunctions.class, "stBoundary", PolyGeometry.class ), + ST_BOUNDARYDIMENSION( GeoFunctions.class, "stBoundaryDimension", PolyGeometry.class ), + ST_CONVEXHULL( GeoFunctions.class, "stConvexHull", PolyGeometry.class ), + ST_CENTROID( GeoFunctions.class, "stCentroid", PolyGeometry.class ), + ST_REVERSE( GeoFunctions.class, "stReverse", PolyGeometry.class ), + ST_BUFFER( GeoFunctions.class, "stBuffer", PolyGeometry.class, PolyNumber.class ), // on Points ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), ST_Y( GeoFunctions.class, "stY", PolyGeometry.class ), @@ -460,6 +475,9 @@ public enum BuiltInMethod { ST_EXTERIORRING( GeoFunctions.class, "stExteriorRing", PolyGeometry.class ), ST_NUMINTERIORRING( GeoFunctions.class, "stNumInteriorRing", PolyGeometry.class ), ST_INTERIORRINGN( GeoFunctions.class, "stInteriorRingN", PolyGeometry.class, PolyInteger.class ), + // on GeometryCollection + ST_NUMGEOMETRIES( GeoFunctions.class, "stNumGeometries", PolyGeometry.class ), + ST_GEOMETRYN( GeoFunctions.class, "stGeometryN", PolyGeometry.class, PolyInteger.class ), /// MQL BUILT-IN METHODS MQL_EQ( MqlFunctions.class, "docEq", PolyValue.class, PolyValue.class ), MQL_GT( MqlFunctions.class, "docGt", PolyValue.class, PolyValue.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index ed9398b855..a836428001 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -63,6 +63,69 @@ public void geoFromText() throws SQLException { } } + @Test + public void commonPropertiesFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // check that the geometry is simple + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsSimple(ST_GeoFromText('POINT (0 1)'))" ), + ImmutableList.of( + new Object[]{ true } + ) ); + // check that the geometry is empty + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_IsEmpty(ST_GeoFromText('POINT (0 1)'))" ), + ImmutableList.of( + new Object[]{ false } + ) ); + // get the number of points in the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_NumPoints(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ 4 } + ) ); + // get the dimension of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Dimension(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ 1 } + ) ); + // get the length of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Length(ST_GeoFromText('LINESTRING (-1 -1, 2 2, 4 5, 6 7)'))" ), + ImmutableList.of( + new Object[]{ 10.67662 } + ) ); + // get the area of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Area(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ 4.5 } + ) ); + // get the minimum bounding box of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Envelope(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;POLYGON ((-1 -1, -1 2, 2 2, 2 -1, -1 -1))" } + ) ); + // get the convex hull of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_ConvexHull(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;POLYGON ((-1 -1, -1 2, 2 2, -1 -1))" } + ) ); + // get the centroid of the geometry + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Centroid(ST_GeoFromText('POLYGON ( (-1 -1, 2 2, -1 2, -1 -1 ) )'))" ), + ImmutableList.of( + new Object[]{ "SRID=0;POINT (-0 1)" } + ) ); + } + } + } + @Test public void pointFunctions() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { @@ -151,4 +214,25 @@ public void polygonFunctions() throws SQLException { } } + @Test + public void geometryCollectionFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // get the number of geometries in the collection + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_NumGeometries(ST_GeoFromText('GEOMETRYCOLLECTION ( POINT (2 3), LINESTRING (2 3, 3 4) )'))" ), + ImmutableList.of( + new Object[]{ 2 } + ) ); + // retrieve the nth geometry in the collection + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_GeometryN(ST_GeoFromText('GEOMETRYCOLLECTION ( POINT (2 3), LINESTRING (2 3, 3 4) )'), 1)" ), + ImmutableList.of( + new Object[]{ "SRID=0;LINESTRING (2 3, 3 4)" } + ) ); + } + } + } + } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index e6e39c4c32..9ac283d30c 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -143,6 +143,7 @@ import org.polypheny.db.sql.language.fun.SqlRowOperator; import org.polypheny.db.sql.language.fun.SqlSequenceValueOperator; import org.polypheny.db.sql.language.fun.SqlSingleValueAggFunction; +import org.polypheny.db.sql.language.fun.SqlStBuffer; import org.polypheny.db.sql.language.fun.SqlStGeoFromText; import org.polypheny.db.sql.language.fun.SqlStdOperatorTable; import org.polypheny.db.sql.language.fun.SqlStringContextVariable; @@ -2489,6 +2490,129 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec // GEO functions register( OperatorName.ST_GEOFROMTEXT, new SqlStGeoFromText() ); + // Common properties + register( + OperatorName.ST_ISSIMPLE, + new SqlFunction( + "ST_ISSIMPLE", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_ISEMPTY, + new SqlFunction( + "ST_ISEMPTY", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_NUMPOINTS, + new SqlFunction( + "ST_NUMPOINTS", + Kind.GEO, + ReturnTypes.INTEGER, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_DIMENSION, + new SqlFunction( + "ST_DIMENSION", + Kind.GEO, + ReturnTypes.INTEGER, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_LENGTH, + new SqlFunction( + "ST_LENGTH", + Kind.GEO, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_AREA, + new SqlFunction( + "ST_AREA", + Kind.GEO, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_ENVELOPE, + new SqlFunction( + "ST_ENVELOPE", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_BOUNDARY, + new SqlFunction( + "ST_BOUNDARY", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_BOUNDARYDIMENSION, + new SqlFunction( + "ST_BOUNDARYDIMENSION", + Kind.GEO, + ReturnTypes.INTEGER, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_CONVEXHULL, + new SqlFunction( + "ST_CONVEXHULL", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_CENTROID, + new SqlFunction( + "ST_CENTROID", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_REVERSE, + new SqlFunction( + "ST_REVERSE", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( OperatorName.ST_BUFFER, new SqlStBuffer() ); + // on Points register( OperatorName.ST_X, @@ -2612,6 +2736,27 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec OperandTypes.GEOMETRY_INTEGER, FunctionCategory.GEOMETRY ) ); + // on GeometryCollection + register( + OperatorName.ST_NUMGEOMETRIES, + new SqlFunction( + "ST_NUMGEOMETRIES", + Kind.GEO, + ReturnTypes.INTEGER, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_GEOMETRYN, + new SqlFunction( + "ST_GEOMETRYN", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_INTEGER, + FunctionCategory.GEOMETRY ) ); + isInit = true; } diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStBuffer.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStBuffer.java new file mode 100644 index 0000000000..9010c4204d --- /dev/null +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/language/fun/SqlStBuffer.java @@ -0,0 +1,177 @@ +/* + * 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.sql.language.fun; + +import static org.polypheny.db.util.Static.RESOURCE; + +import org.polypheny.db.algebra.constant.FunctionCategory; +import org.polypheny.db.algebra.constant.Kind; +import org.polypheny.db.nodes.CallBinding; +import org.polypheny.db.nodes.Operator; +import org.polypheny.db.sql.language.SqlFunction; +import org.polypheny.db.type.OperandCountRange; +import org.polypheny.db.type.PolyOperandCountRanges; +import org.polypheny.db.type.PolyTypeUtil; +import org.polypheny.db.type.checker.PolyOperandTypeChecker; +import org.polypheny.db.type.inference.ReturnTypes; +import org.polypheny.db.util.CoreUtil; + +/** + * Definition of the "ST_Buffer" spatial function. + * The function has 2 required parameters: + * geometry {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + * distance {@link org.polypheny.db.type.entity.PolyFloat}. + * and 2 optional: + * quadrantSegments {@link org.polypheny.db.type.entity.category.PolyNumber} + * endCapStyle {@link org.polypheny.db.type.entity.PolyString} + */ +public class SqlStBuffer extends SqlFunction { + + private static final PolyOperandTypeChecker ST_BUFFER_ARG_CHECKER = new PolyOperandTypeChecker() { + + @Override + public boolean checkOperandTypes( CallBinding callBinding, boolean throwOnFailure ) { + int nOperandsActual = callBinding.getOperandCount(); + + // Make sure the first argument is not null + if ( CoreUtil.isNullLiteral( callBinding.operand( 0 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 0 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + + // Make sure the first argument is a geometry + if ( !PolyTypeUtil.inGeoFamily( callBinding.getOperandType( 0 ) ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 0 ), RESOURCE.expectedGeometry() ); + } else { + return false; + } + } + + // Check whether second argument is not null + if ( CoreUtil.isNullLiteral( callBinding.operand( 1 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 1 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + // Check whether second argument is a float + if ( (!PolyTypeUtil.isNumeric( callBinding.getOperandType( 1 ) )) ) { + if ( throwOnFailure ) { + throw callBinding.newValidationSignatureError(); + } else { + return false; + } + } + + // Check, if present, whether third argument is a number + if ( nOperandsActual == 3 ) { + // Make sure the argument is not null + if ( CoreUtil.isNullLiteral( callBinding.operand( 2 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 2 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + // Make sure the argument is a number + if ( (!PolyTypeUtil.isNumeric( callBinding.getOperandType( 2 ) )) ) { + if ( throwOnFailure ) { + throw callBinding.newValidationSignatureError(); + } else { + return false; + } + } + } + + // Check, if present, whether fourth argument is a string + if ( nOperandsActual == 4 ) { + // Make sure the argument is not null + if ( CoreUtil.isNullLiteral( callBinding.operand( 3 ), false ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 3 ), RESOURCE.nullIllegal() ); + } else { + return false; + } + } + // Make sure the argument is a string + if ( !PolyTypeUtil.inCharFamily( callBinding.getOperandType( 3 ) ) ) { + if ( throwOnFailure ) { + throw callBinding.getValidator().newValidationError( callBinding.operand( 3 ), RESOURCE.expectedCharacter() ); + } else { + return false; + } + } + } + + return true; + + } + + + @Override + public String getAllowedSignatures( Operator op, String opName ) { + return "'ST_Buffer(, )'" + "\n" + "'ST_Buffer(, , )'" + "\n" + "'ST_Buffer(, , , )'"; + } + + + @Override + public Consistency getConsistency() { + return Consistency.NONE; + } + + + @Override + public OperandCountRange getOperandCountRange() { + return PolyOperandCountRanges.between( 2, 4 ); + } + + + @Override + public boolean isOptional( int i ) { + return i == 3 || i == 4; + } + }; + + + /** + * Creates the SqlStBuffer. + */ + public SqlStBuffer() { + super( "ST_BUFFER", Kind.GEO, ReturnTypes.GEOMETRY, null, ST_BUFFER_ARG_CHECKER, FunctionCategory.GEOMETRY ); + } + + + @Override + public String getSignatureTemplate( int operandsCount ) { + switch ( operandsCount ) { + case 2: + return "{0}({1}, {2})"; + case 3: + return "{0}({1}, {2}, {3})"; + case 4: + return "{0}({1}, {2}, {3}, {4})"; + default: + throw new AssertionError(); + } + } + +} From f66a776f81cdaf6142ffa147f5382d16ba580967 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Fri, 10 Nov 2023 18:26:17 +0100 Subject: [PATCH 13/16] GIS #462: added functions to calculate distances --- .../db/algebra/enumerable/RexImpTable.java | 2 + .../db/algebra/operators/OperatorName.java | 6 + .../db/functions/GeoDistanceFunctions.java | 151 ++++++++++++++++++ .../polypheny/db/functions/GeoFunctions.java | 57 ++++++- .../db/type/entity/spatial/PolyGeometry.java | 56 +++---- .../org/polypheny/db/util/BuiltInMethod.java | 3 + .../db/sql/fun/GeoFunctionsTest.java | 27 ++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 11 ++ 8 files changed, 274 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 79b2b27766..2caf4eb949 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -383,6 +383,8 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_Distance operator function: compute the distance between two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_DISTANCE( Function.class ), + // Functions on Points /** diff --git a/core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java new file mode 100644 index 0000000000..05e6478ac0 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java @@ -0,0 +1,151 @@ +/* + * 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; + +import org.jetbrains.annotations.NotNull; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.LineSegment; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.Point; +import org.polypheny.db.type.entity.spatial.GeometryTopologicalException; +import org.polypheny.db.type.entity.spatial.PolyGeometry; +import org.polypheny.db.type.entity.spatial.PolyLineString; +import org.polypheny.db.type.entity.spatial.PolyPoint; + +/** + * Calculate the spherical distances between various geometries + */ +public class GeoDistanceFunctions { + + // Define the radius of the Earth's sphere (in kilometers) + private static final double EARTH_RADIUS_KM = 6371.0; + + + private GeoDistanceFunctions() { + // empty on purpose + } + + + public static boolean isWithinSphericalDistance( @NotNull PolyGeometry g1, @NotNull PolyGeometry g2, double distanceThreshold ) throws GeometryTopologicalException { + return sphericalDistance( g1, g2 ) <= distanceThreshold; + } + + + public static double sphericalDistance( @NotNull PolyGeometry g1, @NotNull PolyGeometry g2 ) throws GeometryTopologicalException { + if ( g1.isPoint() && g2.isPoint() ) { + return calculateSphericalDistance( g1.asPoint(), g2.asPoint() ); + } else if ( g1.isLineString() && g2.isLineString() ) { + return calculateSphericalDistance( g1.asLineString(), g2.asLineString() ); + } else if ( g1.isLineString() && g2.isPoint() ) { + return calculateSphericalDistance( g2.asPoint(), g1.asLineString() ); + } else if ( g1.isPoint() && g2.isLineString() ) { + return calculateSphericalDistance( g1.asPoint(), g2.asLineString() ); + } else if ( g1.isPoint() && g2.isPolygon() ) { + // we could use exterior ring (linestring) + return calculateSphericalDistance( g1.asPoint(), g2.asPolygon().getExteriorRing() ); + } else if ( g1.isPolygon() && g2.isPoint() ) { + return calculateSphericalDistance( g2.asPoint(), g1.asPolygon().getExteriorRing() ); + } else if ( g1.isPolygon() && g2.isPolygon() ) { + return calculateSphericalDistance( g1.asPolygon().getExteriorRing(), g2.asPolygon().getExteriorRing() ); + } + throw new GeometryTopologicalException( "Distance calculation is not supported for this data type." ); + } + + + private static double calculateSphericalDistance( @NotNull PolyPoint polyPoint, @NotNull PolyLineString polyLine ) { + LineString lineString = (LineString) polyLine.getJtsGeometry(); + Point point = (Point) polyPoint.getJtsGeometry(); + + // Get coordinates from the LineString + Coordinate[] coordinates = lineString.getCoordinates(); + // Initialize variables for closest distance and closest point + double closestDistance = Double.MAX_VALUE; + // Iterate over line segments and find the closest point + for ( int i = 0; i < coordinates.length - 1; i++ ) { + Coordinate start = coordinates[i]; + Coordinate end = coordinates[i + 1]; + + LineSegment lineSegment = new LineSegment( start, end ); + Coordinate closestPointOnSegment = lineSegment.closestPoint( point.getCoordinate() ); + + double distance = calculateSphericalDistance( PolyPoint.of( point ), PolyPoint.of( point.getFactory().createPoint( closestPointOnSegment ) ) ); + + // Update the closest distance and point if the new distance is smaller + if ( distance < closestDistance ) { + closestDistance = distance; + } + } + + return closestDistance; + } + + + private static double calculateSphericalDistance( @NotNull PolyLineString polyLine1, @NotNull PolyLineString polyLine2 ) { + LineString lineString1 = (LineString) polyLine1.getJtsGeometry(); + LineString lineString2 = (LineString) polyLine2.getJtsGeometry(); + // Get coordinates from LineStrings + Coordinate[] coords1 = lineString1.getCoordinates(); + Coordinate[] coords2 = lineString2.getCoordinates(); + + // Initialize cumulative distance + double cumulativeDistance = 0.0; + + // Iterate over coordinates and calculate cumulative spherical distance + cumulativeDistance = getCumulativeDistance( lineString1, coords1, cumulativeDistance ); + cumulativeDistance = getCumulativeDistance( lineString2, coords2, cumulativeDistance ); + + return cumulativeDistance; + } + + + /** + * Iterate over coordinates and calculate cumulative spherical distance + */ + private static double getCumulativeDistance( LineString lineString, Coordinate[] coords, double cumulativeDistance ) { + for ( int i = 0; i < coords.length - 1; i++ ) { + Point point1 = lineString.getFactory().createPoint( coords[i] ); + Point point2 = lineString.getFactory().createPoint( coords[i + 1] ); + cumulativeDistance += calculateSphericalDistance( PolyPoint.of( point1 ), PolyPoint.of( point2 ) ); + } + return cumulativeDistance; + } + + + private static double calculateSphericalDistance( @NotNull PolyPoint point1, @NotNull PolyPoint point2 ) { + // Convert latitude and longitude from degrees to radians + double lat1 = Math.toRadians( point1.getY() ); + double lon1 = Math.toRadians( point1.getX() ); + double lat2 = Math.toRadians( point2.getY() ); + double lon2 = Math.toRadians( point2.getX() ); + + // Haversine formula for spherical distance + double dLat = lat2 - lat1; + double dLon = lon2 - lon1; + double a = Math.sin( dLat / 2 ) * Math.sin( dLat / 2 ) + Math.cos( lat1 ) * Math.cos( lat2 ) * Math.sin( dLon / 2 ) * Math.sin( dLon / 2 ); + double c = 2 * Math.atan2( Math.sqrt( a ), Math.sqrt( 1 - a ) ); + double distance = EARTH_RADIUS_KM * c; + + if ( point1.hasZ() ) { + double dAlt = point2.getZ() - point1.getZ(); + // Incorporate altitude difference + distance = Math.sqrt( Math.pow( distance, 2 ) + Math.pow( dAlt, 2 ) ); + } + + return distance; + } + +} diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 82ff05abf0..40209bbcbb 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -18,11 +18,13 @@ import static org.polypheny.db.functions.Functions.toUnchecked; +import java.util.Objects; import org.polypheny.db.type.entity.PolyBoolean; import org.polypheny.db.type.entity.PolyFloat; import org.polypheny.db.type.entity.PolyInteger; import org.polypheny.db.type.entity.PolyString; import org.polypheny.db.type.entity.category.PolyNumber; +import org.polypheny.db.type.entity.spatial.GeometryTopologicalException; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; import org.polypheny.db.type.entity.spatial.PolyGeometryType.BufferCapStyle; @@ -36,6 +38,7 @@ public class GeoFunctions { private static final String LINE_STRING_RESTRICTION = "This function could be applied only to line strings"; private static final String POLYGON_RESTRICTION = "This function could be applied only to polygons"; private static final String GEOMETRY_COLLECTION_RESTRICTION = "This function could be applied only to geometry collections"; + private static final String SRID_RESTRICTION = "Geometries of the same SRID should be used"; private GeoFunctions() { @@ -66,84 +69,126 @@ public static PolyGeometry stGeoFromText( PolyString wkt, PolyNumber srid ) { } } + // TODO: transform to another SRID + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stTransform( PolyGeometry geometry, PolyNumber srid ) { + // todo: + return null; + } + + /* * Common properties */ + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsSimple( PolyGeometry geometry ) { return PolyBoolean.of( geometry.isSimple() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsEmpty( PolyGeometry geometry ) { return PolyBoolean.of( geometry.isEmpty() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyInteger stNumPoints( PolyGeometry geometry ) { return PolyInteger.of( geometry.getNumPoints() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyInteger stDimension( PolyGeometry geometry ) { return PolyInteger.of( geometry.getDimension() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyFloat stLength( PolyGeometry geometry ) { return PolyFloat.of( geometry.getLength() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyFloat stArea( PolyGeometry geometry ) { return PolyFloat.of( geometry.getArea() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stEnvelope( PolyGeometry geometry ) { return geometry.getEnvelope(); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stBoundary( PolyGeometry geometry ) { return geometry.getBoundary(); } + @SuppressWarnings("UnusedDeclaration") public static PolyInteger stBoundaryDimension( PolyGeometry geometry ) { - return PolyInteger.of (geometry.getBoundaryDimension() ); + return PolyInteger.of( geometry.getBoundaryDimension() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stConvexHull( PolyGeometry geometry ) { return geometry.convexHull(); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stCentroid( PolyGeometry geometry ) { return geometry.getCentroid(); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stReverse( PolyGeometry geometry ) { return geometry.reverse(); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance ) { return geometry.buffer( distance.doubleValue() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance, PolyNumber quadrantSegments ) { return geometry.buffer( distance.doubleValue(), quadrantSegments.intValue() ); } + @SuppressWarnings("UnusedDeclaration") public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance, PolyNumber quadrantSegments, PolyString endCapStyle ) { return geometry.buffer( distance.doubleValue(), quadrantSegments.intValue(), BufferCapStyle.of( endCapStyle.value ) ); } + /* + * TODO: Spatial relationships + */ + + + /* + * Yield metric values + */ + + @SuppressWarnings("UnusedDeclaration") + public static PolyNumber stDistance( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + try { + return PolyFloat.of( g1.distance( g2 ) ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + /* * Geometry Specific Functions @@ -195,6 +240,7 @@ public static PolyBoolean stIsRing( PolyGeometry geometry ) { @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsCoordinate( PolyGeometry geometry, PolyGeometry point ) { + restrictToSrid( geometry, point ); restrictToLineStrings( geometry ); restrictToPoints( point ); return PolyBoolean.of( geometry.asLineString().isCoordinate( point.asPoint() ) ); @@ -218,6 +264,7 @@ public static PolyGeometry stEndPoint( PolyGeometry geometry ) { * on Polygons */ + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsRectangle( PolyGeometry geometry ) { restrictToPolygons( geometry ); @@ -267,6 +314,7 @@ public static PolyGeometry stGeometryN( PolyGeometry geometry, PolyNumber n ) { * Helpers */ + private static void restrictToPoints( PolyGeometry geometry ) { if ( !geometry.isPoint() ) { throw toUnchecked( new InvalidGeometryException( POINT_RESTRICTION ) ); @@ -294,4 +342,11 @@ private static void restrictToGeometryCollection( PolyGeometry geometry ) { } } + + private static void restrictToSrid( PolyGeometry g1, PolyGeometry g2 ) { + if ( !Objects.equals( g1.getSRID(), g2.getSRID() ) ) { + throw toUnchecked( new GeometryTopologicalException( SRID_RESTRICTION ) ); + } + } + } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 48731f5937..12969b1057 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -32,10 +32,13 @@ 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.type.PolySerializable; import org.polypheny.db.type.PolyType; import org.polypheny.db.type.entity.PolyValue; @@ -438,31 +441,18 @@ public PolyGeometry reverse() { /** - * Check that another {@link PolyGeometry} is withing the Euclidean distance. + * Check that another {@link PolyGeometry} is withing the given distance. * * @param g another {@link PolyGeometry} - * @param distance metric value to compare, measured in Cartesian coordinate units + * @param distance threshold, measured in Cartesian coordinate units for Euclidean, and in meters for others * @return true if {@link PolyGeometry} is withing the given distance */ - public boolean isWithinDistance( @NotNull PolyGeometry g, double distance ) { - return jtsGeometry.isWithinDistance( g.getJtsGeometry(), distance ); - } - - - /** - * Check that another {@link PolyGeometry} is withing the distance. - * - * @param g another {@link PolyGeometry} - * @param distance metric value to compare, measured in meters if spheroid, otherwise in Cartesian coordinate units - * @param spheroid use the spheroid physical model to calculate the distance - * @return true if {@link PolyGeometry} is withing the given distance - */ - public boolean isWithinDistance( @NotNull PolyGeometry g, double distance, boolean spheroid ) { - if ( !spheroid ) { - return isWithinDistance( g, distance ); + public boolean isWithinDistance( @NotNull PolyGeometry g, double distance ) throws GeometryTopologicalException { + if (this.SRID == NO_SRID) { + // Euclidean distance + return jtsGeometry.isWithinDistance( g.getJtsGeometry(), distance ); } - // TODO: implement on Perfect Sphere and for WGS84 - return false; + return GeoDistanceFunctions.isWithinSphericalDistance( this, g, distance ); } @@ -620,30 +610,20 @@ public boolean relate( @NotNull PolyGeometry g, @NotNull String intersectionPatt /** - * Calculate the Euclidean distance between two {@link PolyGeometry}. - * The distance is measured in Cartesian coordinate units. - * - * @param g another {@link PolyGeometry} - * @return the distance in Cartesian coordinate units - */ - public double distance( @NotNull PolyGeometry g ) { - return jtsGeometry.distance( g.getJtsGeometry() ); - } - - - /** - * Calculate the distance between two {@link PolyGeometry}. + * Calculate the distance between two {@link PolyGeometry} taking the SRID into account. + * By default, (for SRID=0) Euclidean distance is calculated. Otherwise, the spheroid model is used. + * The distance is measured in Cartesian coordinate units for SRID=0 + * and in meters for spheroid model. * * @param g another {@link PolyGeometry} - * @param spheroid use the spheroid physical model to calculate the distance * @return the distance in meters if spheroid, otherwise in Cartesian coordinate units */ - public double distance( @NotNull PolyGeometry g, boolean spheroid ) { - if ( !spheroid ) { + public double distance( @NotNull PolyGeometry g ) throws GeometryTopologicalException { + if (this.SRID == NO_SRID) { + // Euclidean distance return jtsGeometry.distance( g.getJtsGeometry() ); } - // TODO: implement on Perfect Sphere and for WGS84 - return 42; + return GeoDistanceFunctions.sphericalDistance( this, g ); } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index ff276d9503..2a3a87ca7a 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -120,6 +120,7 @@ import org.polypheny.db.interpreter.Context; import org.polypheny.db.interpreter.Row; import org.polypheny.db.interpreter.Scalar; +import org.polypheny.db.nodes.Function; import org.polypheny.db.rex.RexNode; import org.polypheny.db.runtime.ArrayBindable; import org.polypheny.db.runtime.BinarySearch; @@ -460,6 +461,8 @@ public enum BuiltInMethod { ST_CENTROID( GeoFunctions.class, "stCentroid", PolyGeometry.class ), ST_REVERSE( GeoFunctions.class, "stReverse", PolyGeometry.class ), ST_BUFFER( GeoFunctions.class, "stBuffer", PolyGeometry.class, PolyNumber.class ), + // Yield metric values + ST_DISTANCE( GeoFunctions.class, "stDistance", PolyGeometry.class, PolyGeometry.class ), // on Points ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), ST_Y( GeoFunctions.class, "stY", PolyGeometry.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index a836428001..2cf0112d1d 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -126,6 +126,33 @@ public void commonPropertiesFunctions() throws SQLException { } } + @Test + public void distanceFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // calculate the distance between two points + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Distance(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('POINT (9.289382 48.741588)', 4326))" ), + ImmutableList.of( + new Object[]{ 134.45105 } + ) ); + // calculate the distance between point and linestring + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Distance(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 4326))" ), + ImmutableList.of( + 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 + 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 } + ) ); + } + } + } + @Test public void pointFunctions() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 9ac283d30c..5496a70bb0 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2613,6 +2613,17 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec register( OperatorName.ST_BUFFER, new SqlStBuffer() ); + // Yield metric values + register( + OperatorName.ST_DISTANCE, + new SqlFunction( + "ST_DISTANCE", + Kind.GEO, + ReturnTypes.DOUBLE, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + // on Points register( OperatorName.ST_X, From ad719b3f8b711f5d907f7f5a7ca3ca0b2ab7d4e5 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Sat, 11 Nov 2023 11:35:00 +0100 Subject: [PATCH 14/16] GIS #462: added transform functions --- core/build.gradle | 3 +- .../polypheny/db/algebra/constant/Kind.java | 4 +- .../db/algebra/enumerable/RexImpTable.java | 2 + .../db/algebra/operators/OperatorName.java | 10 ++ .../polypheny/db/functions/GeoFunctions.java | 18 +- .../{ => spatial}/GeoDistanceFunctions.java | 2 +- .../spatial/GeoTransformFunctions.java | 155 ++++++++++++++++++ .../db/type/entity/spatial/PolyGeometry.java | 4 +- .../db/type/entity/spatial/PolyPoint.java | 12 +- .../org/polypheny/db/util/BuiltInMethod.java | 2 + .../db/sql/fun/GeoFunctionsTest.java | 42 ++++- .../db/type/spatial/GeometryTest.java | 2 - .../polypheny/db/sql/SqlLanguagePlugin.java | 20 +++ 13 files changed, 250 insertions(+), 26 deletions(-) rename core/src/main/java/org/polypheny/db/functions/{ => spatial}/GeoDistanceFunctions.java (99%) create mode 100644 core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java diff --git a/core/build.gradle b/core/build.gradle index af6886fc48..0ec51b1972 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -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 diff --git a/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java b/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java index 64a97ce7fa..58d87bec21 100644 --- a/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java +++ b/core/src/main/java/org/polypheny/db/algebra/constant/Kind.java @@ -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; @@ -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 { diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 2caf4eb949..e6c94821ae 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -369,6 +369,8 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_AsText operator function: output the WKT representation of {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_ASTEXT( Function.class ), + + /** + * The ST_Transform operator function: transform coordinates of {@link org.polypheny.db.type.entity.spatial.PolyGeometry} to another SRID + */ + ST_TRANSFORM( Function.class ), + // Common properties /** diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 40209bbcbb..4b3d26c97b 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -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; @@ -69,11 +70,20 @@ 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 ); + } } @@ -81,6 +91,7 @@ public static PolyGeometry stTransform( PolyGeometry geometry, PolyNumber srid ) * Common properties */ + @SuppressWarnings("UnusedDeclaration") public static PolyBoolean stIsSimple( PolyGeometry geometry ) { return PolyBoolean.of( geometry.isSimple() ); @@ -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 ); diff --git a/core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java b/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java similarity index 99% rename from core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java rename to core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java index 05e6478ac0..310c4736a1 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoDistanceFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java @@ -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; diff --git a/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java b/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java new file mode 100644 index 0000000000..46d3fffc99 --- /dev/null +++ b/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java @@ -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 ); + } + } + +} diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 12969b1057..718a177b6b 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -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; diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java index 5b49a4a751..127121fa33 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -27,7 +27,7 @@ *

* The {@link PolyPoint} is valid if * X and Y 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 { @@ -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(); - } - } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 2a3a87ca7a..4773c09a07 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -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 ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index 2cf0112d1d..68be6ff3db 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -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 ) ) { @@ -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 } diff --git a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java index 2a31281f32..86a3a1278a 100644 --- a/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java +++ b/dbms/src/test/java/org/polypheny/db/type/spatial/GeometryTest.java @@ -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", @@ -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)" ) ); diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 5496a70bb0..09c764744f 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2490,6 +2490,26 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec // GEO functions register( OperatorName.ST_GEOFROMTEXT, new SqlStGeoFromText() ); + register( + OperatorName.ST_ASTEXT, + new SqlFunction( + "ST_ASTEXT", + Kind.GEO, + ReturnTypes.VARCHAR_2000, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_TRANSFORM, + new SqlFunction( + "ST_TRANSFORM", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_INTEGER, + FunctionCategory.GEOMETRY ) ); + // Common properties register( OperatorName.ST_ISSIMPLE, From 92b9d37a94c173aad6923b170a62cdfd59f05efd Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Sat, 11 Nov 2023 14:58:11 +0100 Subject: [PATCH 15/16] GIS #462: added projection function; added spatial relationships functions to SQL --- .../db/algebra/enumerable/RexImpTable.java | 12 ++ .../db/algebra/operators/OperatorName.java | 58 ++++++ .../polypheny/db/functions/GeoFunctions.java | 88 +++++++- .../spatial/GeoDistanceFunctions.java | 20 +- .../spatial/GeoTransformFunctions.java | 192 ++++++++++++++---- .../db/type/checker/OperandTypes.java | 2 + .../db/type/entity/spatial/PolyGeometry.java | 2 +- .../org/polypheny/db/util/BuiltInMethod.java | 12 ++ .../db/sql/fun/GeoFunctionsTest.java | 51 +++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 114 +++++++++++ 10 files changed, 507 insertions(+), 44 deletions(-) diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index e6c94821ae..1d9626669c 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -385,6 +385,18 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_WithinDistance operator function: check if two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} are withing the given distance + */ + ST_WITHINDISTANCE( Function.class ), + + /** + * The ST_Disjoint operator function: check if two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} are disjoint + */ + ST_DISJOINT( Function.class ), + + /** + * The ST_Touches operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} touches another + */ + ST_TOUCHES( Function.class ), + + /** + * The ST_Intersects operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} intersects another + */ + ST_INTERSECTS( Function.class ), + + /** + * The ST_Crosses operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} crosses another + */ + ST_CROSSES( Function.class ), + + /** + * The ST_Within operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} is within another + */ + ST_WITHIN( Function.class ), + + /** + * The ST_Contains operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} contains another + */ + ST_CONTAINS( Function.class ), + + /** + * The ST_Overlaps operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} overlaps another + */ + ST_OVERLAPS( Function.class ), + + /** + * The ST_Covers operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} covers another + */ + ST_COVERS( Function.class ), + + /** + * The ST_CoveredBy operator function: check if {@link org.polypheny.db.type.entity.spatial.PolyGeometry} is covered by another + */ + ST_COVEREDBY( Function.class ), + + /** + * The ST_Relate operator function: check if two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} are relate + */ + ST_RELATE( Function.class ), + // Yield metric values + /** * The ST_Distance operator function: compute the distance between two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} */ diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index 4b3d26c97b..d9e02c36ab 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -182,10 +182,96 @@ public static PolyGeometry stBuffer( PolyGeometry geometry, PolyNumber distance, } /* - * TODO: Spatial relationships + * Spatial relationships */ + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stWithinDistance( PolyGeometry g1, PolyGeometry g2, PolyNumber distance ) { + restrictToSrid( g1, g2 ); + try { + return PolyBoolean.of( g1.isWithinDistance( g2, distance.doubleValue() ) ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stDisjoint( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.disjoint( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stTouches( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.touches( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stIntersects( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.intersects( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stCrosses( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.crosses( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stWithin( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.within( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stContains( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.contains( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stOverlaps( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.overlaps( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stCovers( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.covers( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stCoveredBy( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + return PolyBoolean.of( g1.coveredBy( g2 ) ); + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyBoolean stRelate( PolyGeometry g1, PolyGeometry g2, PolyString pattern ) { + restrictToSrid( g1, g2 ); + try { + return PolyBoolean.of( g1.relate( g2, pattern.value ) ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + + + /* * Yield metric values */ diff --git a/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java b/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java index 310c4736a1..689e770365 100644 --- a/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/spatial/GeoDistanceFunctions.java @@ -27,7 +27,7 @@ import org.polypheny.db.type.entity.spatial.PolyPoint; /** - * Calculate the spherical distances between various geometries + * Calculate the spherical distances between various geometries on the PERFECT SPHERE */ public class GeoDistanceFunctions { @@ -40,12 +40,30 @@ private GeoDistanceFunctions() { } + /** + * Check that the distance between two geometries is within the given threshold + * + * @param g1 one geometry + * @param g2 another one + * @param distanceThreshold limit the distance between geometries + * @return TRUE if two geometries are within the given distance + * @throws GeometryTopologicalException if distance cannot be calculated + */ public static boolean isWithinSphericalDistance( @NotNull PolyGeometry g1, @NotNull PolyGeometry g2, double distanceThreshold ) throws GeometryTopologicalException { return sphericalDistance( g1, g2 ) <= distanceThreshold; } + /** + * Calculate the spherical distance between 2 geometries + * + * @param g1 one geometry + * @param g2 another + * @return the distance between geometries + * @throws GeometryTopologicalException if distance cannot be calculated + */ public static double sphericalDistance( @NotNull PolyGeometry g1, @NotNull PolyGeometry g2 ) throws GeometryTopologicalException { + // distance calculation are dependent on the type of the geometry if ( g1.isPoint() && g2.isPoint() ) { return calculateSphericalDistance( g1.asPoint(), g2.asPoint() ); } else if ( g1.isLineString() && g2.isLineString() ) { diff --git a/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java b/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java index 46d3fffc99..3cb83cb16b 100644 --- a/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/spatial/GeoTransformFunctions.java @@ -29,16 +29,22 @@ import org.locationtech.proj4j.CoordinateTransformFactory; import org.locationtech.proj4j.Proj4jException; import org.locationtech.proj4j.ProjCoordinate; +import org.locationtech.proj4j.proj.Projection; import org.polypheny.db.type.entity.spatial.InvalidGeometryException; import org.polypheny.db.type.entity.spatial.PolyGeometry; import org.polypheny.db.type.entity.spatial.PolyGeometryCollection; /** + * Geo functions to: * Transform coordinates to various SRID + * Project SRID coordinates on the plane */ public class GeoTransformFunctions { private static final String SRID_PREFIX = "EPSG:"; + private static final PrecisionModel PRECISION_MODEL = new PrecisionModel( PrecisionModel.FLOATING ); + // Create a CRSFactory to manage coordinate reference systems + private static final CRSFactory crsFactory = new CRSFactory(); private GeoTransformFunctions() { @@ -46,62 +52,83 @@ private GeoTransformFunctions() { } + /** + * Transform coordinates {@link PolyGeometry} into the given SRID. + * + * @param geometry to transform + * @param srid new spatial reference + * @return {@link PolyGeometry} with coordinates in given SRID + * @throws InvalidGeometryException if coordinates cannot be transformed + */ 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 ); + GeometryFactory geometryFactory = new GeometryFactory( PRECISION_MODEL, srid ); + // transform coordinates + return modifyCoordinates( geometry, geometryFactory, (coordinates -> transformCoordinates( coordinates, sourceCrs, targetCrs )) ); + } + + + /** + * Project coordinates of {@link PolyGeometry} with SRID on the plane. + * + * @param geometry to transform + * @return {@link PolyGeometry} with coordinates projected on the plane(SRID=0) + * @throws InvalidGeometryException if coordinates cannot be projected + */ + public static PolyGeometry project( PolyGeometry geometry ) throws InvalidGeometryException { + // Create the coordinate reference systems + CoordinateReferenceSystem crs = crsFactory.createFromName( SRID_PREFIX + geometry.getSRID() ); + // create Geometry factory with srid of the plane + GeometryFactory geometryFactory = new GeometryFactory( PRECISION_MODEL, PolyGeometry.NO_SRID ); + // project coordinates + return modifyCoordinates( geometry, geometryFactory, (coordinates -> projectCoordinates( coordinates, crs )) ); + } + + /** + * Modify the coordinates using the modification function. + * + * @param geometry {@link PolyGeometry} original geometry + * @param geometryFactory {@link GeometryFactory} with SRID that will create correct geometries + * @param func to apply to coordinates + * @return {@link PolyGeometry} with modified coordinates + * @throws InvalidGeometryException in case modification of coordinates failed + */ + private static PolyGeometry modifyCoordinates( PolyGeometry geometry, GeometryFactory geometryFactory, CoordinatesModificationFunction func ) throws InvalidGeometryException { if ( geometry.isGeometryCollection() ) { + // for GeometryCollection every geometry is needed to be modified + // and then the new GeometryCollection of modified geometries is created 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 ); + ProjCoordinate[] coordinates = convertToProj4JCoordinates( geom ); + // Perform the SRID modification of coordinates + ProjCoordinate[] modifiedCoordinates = func.apply( coordinates ); // 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" ); + geometries[i] = convertToGeometry( modifiedCoordinates, geom, geometryFactory ); } + return createCorrectGeometryCollection( geometry, geometries, geometryFactory ); } else { - ProjCoordinate[] originalCoords = convertToProj4JCoordinates( geometry.getJtsGeometry() ); - ProjCoordinate[] targetCoords = convertCoordinates( originalCoords, sourceCrs, targetCrs ); - return PolyGeometry.of( convertToGeometry( targetCoords, geometry.getJtsGeometry(), geometryFactory ) ); + // coordinates of a single Geometry are modified + // and Geometry of the same type with coordinates is returned + ProjCoordinate[] coordinates = convertToProj4JCoordinates( geometry.getJtsGeometry() ); + ProjCoordinate[] modifiedCoordinates = func.apply( coordinates ); + return PolyGeometry.of( convertToGeometry( modifiedCoordinates, geometry.getJtsGeometry(), geometryFactory ) ); } - - } - // Convert Geometry coordinates to Proj4J coordinates + /** + * Convert Geometry coordinates to Proj4J coordinates + * + * @param geometry original {@link Geometry} + * @return array of {@link ProjCoordinate} objects with coordinates of original {@link Geometry} + */ private static ProjCoordinate[] convertToProj4JCoordinates( Geometry geometry ) { ProjCoordinate[] projCoords = new ProjCoordinate[geometry.getNumPoints()]; for ( int i = 0; i < geometry.getNumPoints(); i++ ) { @@ -111,7 +138,15 @@ private static ProjCoordinate[] convertToProj4JCoordinates( Geometry geometry ) } - // Convert Proj4J coordinates back to Geometry + /** + * Convert Proj4J coordinates back to Geometry of correct type. + * + * @param projCoords coordinates in {@link ProjCoordinate} + * @param geometry original {@link Geometry} for the reference + * @param geometryFactory {@link GeometryFactory} with SRID that will create correct geometries + * @return {@link Geometry} of correct type with new coordinates + * @throws InvalidGeometryException if geometry cannot be created from provided coordinates + */ private static Geometry convertToGeometry( ProjCoordinate[] projCoords, Geometry geometry, GeometryFactory geometryFactory ) throws InvalidGeometryException { Coordinate[] originalCoordinates = geometry.getCoordinates(); Coordinate[] coordinates = new Coordinate[projCoords.length]; @@ -136,8 +171,51 @@ private static Geometry convertToGeometry( ProjCoordinate[] projCoords, Geometry } - // Helper method to perform the SRID conversion - private static ProjCoordinate[] convertCoordinates( + /** + * Create the geometry collection of correct types + * + * @param geometry original of {@link PolyGeometry} + * @param geometries array of {@link Geometry} + * @param geometryFactory {@link GeometryFactory} with SRID that will create correct geometries + * @return {@link PolyGeometry} that incorporates the correct {@link org.locationtech.jts.geom.GeometryCollection} + */ + private static PolyGeometry createCorrectGeometryCollection( PolyGeometry geometry, Geometry[] geometries, GeometryFactory geometryFactory ) throws InvalidGeometryException { + 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" ); + } + } + + + /** + * Perform the SRID transformation + * + * @param originalCoords in the original SRID + * @param sourceCrs original reference system + * @param targetCrs target reference system with new SRID + * @return coordinates in target SRID + */ + private static ProjCoordinate[] transformCoordinates( ProjCoordinate[] originalCoords, CoordinateReferenceSystem sourceCrs, CoordinateReferenceSystem targetCrs ) { try { CoordinateTransform trans = new CoordinateTransformFactory().createTransform( sourceCrs, targetCrs ); @@ -146,10 +224,42 @@ private static ProjCoordinate[] convertCoordinates( targetCoords[i] = new ProjCoordinate(); trans.transform( originalCoords[i], targetCoords[i] ); } - return targetCoords; + return targetCoords; } catch ( Proj4jException e ) { throw new RuntimeException( "Error in coordinate transformation.", e ); } } + + /** + * Perform the SRID projection + * + * @param originalCoords in the original SRID + * @param crs spatial reference system + * @return projected coordinates on the plane + */ + private static ProjCoordinate[] projectCoordinates( + ProjCoordinate[] originalCoords, CoordinateReferenceSystem crs ) { + try { + Projection projection = crs.getProjection(); + ProjCoordinate[] targetCoords = new ProjCoordinate[originalCoords.length]; + for ( int i = 0; i < originalCoords.length; i++ ) { + targetCoords[i] = new ProjCoordinate(); + // project radians on the plane + projection.projectRadians( originalCoords[i], targetCoords[i] ); + } + return targetCoords; + } catch ( Proj4jException e ) { + throw new RuntimeException( "Error in coordinate projection.", e ); + } + } + + + @FunctionalInterface + interface CoordinatesModificationFunction { + + ProjCoordinate[] apply( ProjCoordinate[] coordinates ); + + } + } diff --git a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java index ce54ee442f..9903b05515 100644 --- a/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java +++ b/core/src/main/java/org/polypheny/db/type/checker/OperandTypes.java @@ -288,6 +288,8 @@ public Consistency getConsistency() { public static final PolySingleOperandTypeChecker GEOMETRY = family( PolyTypeFamily.GEO ); public static final PolySingleOperandTypeChecker GEOMETRY_GEOMETRY = family( PolyTypeFamily.GEO, PolyTypeFamily.GEO ); public static final PolySingleOperandTypeChecker GEOMETRY_INTEGER = family( PolyTypeFamily.GEO, PolyTypeFamily.INTEGER ); + public static final PolySingleOperandTypeChecker GEOMETRY_GEOMETRY_STRING = family( PolyTypeFamily.GEO, PolyTypeFamily.GEO, PolyTypeFamily.STRING ); + public static final PolySingleOperandTypeChecker GEOMETRY_GEOMETRY_NUMERIC = family( PolyTypeFamily.GEO, PolyTypeFamily.GEO, PolyTypeFamily.NUMERIC ); /** * Checks that returns whether a value is a multiset or an array. Cf Java, where list and set are collections but a map is not. diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 718a177b6b..50c4a41c78 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -52,7 +52,7 @@ public class PolyGeometry extends PolyValue { // default plane - private static final int NO_SRID = 0; + public static final int NO_SRID = 0; // World Geodetic System 1984 private static final int WGS_84 = 4326; diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 4773c09a07..3ea5eeb3a8 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -463,6 +463,18 @@ public enum BuiltInMethod { ST_CENTROID( GeoFunctions.class, "stCentroid", PolyGeometry.class ), ST_REVERSE( GeoFunctions.class, "stReverse", PolyGeometry.class ), ST_BUFFER( GeoFunctions.class, "stBuffer", PolyGeometry.class, PolyNumber.class ), + // Spatial relationships + ST_WITHINDISTANCE( GeoFunctions.class, "stWithinDistance", PolyGeometry.class, PolyGeometry.class, PolyNumber.class ), + ST_DISJOINT( GeoFunctions.class, "stDisjoint", PolyGeometry.class, PolyGeometry.class ), + ST_TOUCHES( GeoFunctions.class, "stTouches", PolyGeometry.class, PolyGeometry.class ), + ST_INTERSECTS( GeoFunctions.class, "stIntersects", PolyGeometry.class, PolyGeometry.class ), + ST_CROSSES( GeoFunctions.class, "stCrosses", PolyGeometry.class, PolyGeometry.class ), + ST_WITHIN( GeoFunctions.class, "stWithin", PolyGeometry.class, PolyGeometry.class ), + ST_CONTAINS( GeoFunctions.class, "stContains", PolyGeometry.class, PolyGeometry.class ), + ST_OVERLAPS( GeoFunctions.class, "stOverlaps", PolyGeometry.class, PolyGeometry.class ), + ST_COVERS( GeoFunctions.class, "stCovers", PolyGeometry.class, PolyGeometry.class ), + ST_COVEREDBY( GeoFunctions.class, "stCoveredBy", PolyGeometry.class, PolyGeometry.class ), + ST_RELATE( GeoFunctions.class, "stRelate", PolyGeometry.class, PolyGeometry.class, PolyString.class ), // Yield metric values ST_DISTANCE( GeoFunctions.class, "stDistance", PolyGeometry.class, PolyGeometry.class ), // on Points diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index 68be6ff3db..99419cd134 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -166,6 +166,57 @@ public void transformFunctions() throws SQLException { } + @Test + public void spatialRelationsFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // check that geo are withing the distance + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_WithinDistance(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('POINT (9.289382 48.741588)', 4326), 135)" ), + ImmutableList.of( + new Object[]{ true } + ) ); + // check that geo are disjoint + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Disjoint(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 4326))" ), + ImmutableList.of( + new Object[]{ true } + ) ); + // check that point touches the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Touches(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[]{ false } + ) ); + // check that line intersects the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Intersects(ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 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[]{ true } + ) ); + // check that line crosses the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Crosses(ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 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[]{ false } + ) ); + // check that point is within the polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Within(ST_GeoFromText('POINT (9.3 48)', 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[]{ true } + ) ); + // check that point is relate with polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Relate(ST_GeoFromText('POINT (9.3 48)', 4326), ST_GeoFromText('POLYGON ((9.289382 48.741588, 10.289382 47.741588, 9.289382 47.741588, 9.289382 48.741588))', 4326), 'T********')" ), + ImmutableList.of( + new Object[]{ true } + ) ); + } + } + } + @Test public void distanceFunctions() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 09c764744f..492cf984ab 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2511,6 +2511,7 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec FunctionCategory.GEOMETRY ) ); // Common properties + register( OperatorName.ST_ISSIMPLE, new SqlFunction( @@ -2633,7 +2634,120 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec register( OperatorName.ST_BUFFER, new SqlStBuffer() ); + // Spatial relationships + + register( + OperatorName.ST_WITHINDISTANCE, + new SqlFunction( + "ST_WITHINDISTANCE", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY_NUMERIC, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_DISJOINT, + new SqlFunction( + "ST_DISJOINT", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_TOUCHES, + new SqlFunction( + "ST_TOUCHES", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_INTERSECTS, + new SqlFunction( + "ST_INTERSECTS", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_CROSSES, + new SqlFunction( + "ST_CROSSES", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_WITHIN, + new SqlFunction( + "ST_WITHIN", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_CONTAINS, + new SqlFunction( + "ST_CONTAINS", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_OVERLAPS, + new SqlFunction( + "ST_OVERLAPS", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_COVERS, + new SqlFunction( + "ST_COVERS", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_COVEREDBY, + new SqlFunction( + "ST_COVEREDBY", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_RELATE, + new SqlFunction( + "ST_RELATE", + Kind.GEO, + ReturnTypes.BOOLEAN, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY_STRING, + FunctionCategory.GEOMETRY ) ); + // Yield metric values + register( OperatorName.ST_DISTANCE, new SqlFunction( From 6eb20498b8c686cd1e711562332ed8a8ad79eb49 Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Sat, 11 Nov 2023 15:28:07 +0100 Subject: [PATCH 16/16] GIS #462: added set functions; fixed problem with SRID not beeing assigned --- .../db/algebra/enumerable/RexImpTable.java | 5 ++ .../db/algebra/operators/OperatorName.java | 22 +++++++++ .../polypheny/db/functions/GeoFunctions.java | 48 ++++++++++++++++++ .../db/type/entity/spatial/PolyGeometry.java | 49 ++++++++++++------- .../spatial/PolyGeometryCollection.java | 24 +++++++-- .../type/entity/spatial/PolyLineString.java | 20 ++++++-- .../type/entity/spatial/PolyLinearRing.java | 14 ++++++ .../entity/spatial/PolyMultiLineString.java | 14 ++++++ .../type/entity/spatial/PolyMultiPoint.java | 14 ++++++ .../type/entity/spatial/PolyMultiPolygon.java | 14 ++++++ .../db/type/entity/spatial/PolyPoint.java | 14 ++++++ .../db/type/entity/spatial/PolyPolygon.java | 18 ++++++- .../org/polypheny/db/util/BuiltInMethod.java | 5 ++ .../db/sql/fun/GeoFunctionsTest.java | 33 +++++++++++++ .../polypheny/db/sql/SqlLanguagePlugin.java | 43 ++++++++++++++++ 15 files changed, 309 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java index 1d9626669c..38b0e30cc9 100644 --- a/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java +++ b/core/src/main/java/org/polypheny/db/algebra/enumerable/RexImpTable.java @@ -399,6 +399,11 @@ public Expression implement( RexToLixTranslator translator, RexCall call, ListST_Intersection operator function: compute the intersection of two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_INTERSECTION( Function.class ), + + /** + * The ST_Union operator function: compute the union of two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_UNION( Function.class ), + + /** + * The ST_Difference operator function: compute the difference of two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_DIFFERENCE( Function.class ), + + /** + * The ST_SymDifference operator function: compute the symmetric difference of two {@link org.polypheny.db.type.entity.spatial.PolyGeometry} + */ + ST_SYMDIFFERENCE( Function.class ), + // Functions on Points /** diff --git a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java index d9e02c36ab..2717b3191b 100644 --- a/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java +++ b/core/src/main/java/org/polypheny/db/functions/GeoFunctions.java @@ -287,6 +287,54 @@ public static PolyNumber stDistance( PolyGeometry g1, PolyGeometry g2 ) { } } + /* + * Set operations + */ + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stIntersection( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + try { + return g1.intersection( g2 ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stUnion( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + try { + return g1.union( g2 ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stDifference( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + try { + return g1.difference( g2 ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + + + @SuppressWarnings("UnusedDeclaration") + public static PolyGeometry stSymDifference( PolyGeometry g1, PolyGeometry g2 ) { + restrictToSrid( g1, g2 ); + try { + return g1.symDifference( g2 ); + } catch ( GeometryTopologicalException e ) { + throw toUnchecked( e ); + } + } + /* * Geometry Specific Functions diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java index 50c4a41c78..046b272a98 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometry.java @@ -119,6 +119,14 @@ public PolyGeometry( Geometry geometry ) { } + public PolyGeometry( Geometry geometry, int srid ) { + super( PolyType.GEOMETRY ); + this.jtsGeometry = geometry; + this.SRID = srid; + this.geometryType = getPolyGeometryType(); + } + + protected PolyGeometry( PolyType type ) { super( type ); } @@ -139,6 +147,11 @@ public static PolyGeometry of( Geometry geometry ) { } + public static PolyGeometry of( Geometry geometry, int srid ) { + return new PolyGeometry( geometry, srid ); + } + + private void init( String wkt, int srid ) throws InvalidGeometryException { this.SRID = srid; WKTReader reader = new WKTReader(); @@ -192,7 +205,7 @@ public boolean isPoint() { @NotNull public PolyPoint asPoint() { if ( isPoint() ) { - return PolyPoint.of( jtsGeometry ); + return PolyPoint.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyPoint.class ); } @@ -206,7 +219,7 @@ public boolean isLineString() { @NotNull public PolyLineString asLineString() { if ( isLineString() ) { - return PolyLineString.of( jtsGeometry ); + return PolyLineString.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyLineString.class ); } @@ -220,7 +233,7 @@ public boolean isLinearRing() { @NotNull public PolyLinearRing asLinearRing() { if ( isLinearRing() ) { - return PolyLinearRing.of( jtsGeometry ); + return PolyLinearRing.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyLinearRing.class ); } @@ -234,7 +247,7 @@ public boolean isPolygon() { @NotNull public PolyPolygon asPolygon() { if ( isPolygon() ) { - return PolyPolygon.of( jtsGeometry ); + return PolyPolygon.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyPolygon.class ); } @@ -248,7 +261,7 @@ public boolean isGeometryCollection() { @NotNull public PolyGeometryCollection asGeometryCollection() { if ( isGeometryCollection() ) { - return PolyGeometryCollection.of( jtsGeometry ); + return PolyGeometryCollection.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyGeometryCollection.class ); } @@ -337,7 +350,7 @@ public PolyGeometry getEnvelope() { * @return the set of the combinatorial boundary {@link PolyGeometry} that define the limit of this {@link PolyGeometry} */ public PolyGeometry getBoundary() { - return PolyGeometry.of( jtsGeometry.getBoundary() ); + return PolyGeometry.of( jtsGeometry.getBoundary(), getSRID() ); } @@ -355,7 +368,7 @@ public int getBoundaryDimension() { * @return the minimum area {@link PolyGeometry} containing all points in this {@link PolyGeometry} */ public PolyGeometry convexHull() { - return PolyGeometry.of( jtsGeometry.convexHull() ); + return PolyGeometry.of( jtsGeometry.convexHull(), getSRID() ); } @@ -371,7 +384,7 @@ public PolyGeometry convexHull() { * @return {@link PolyPoint} that is the centroid of this {@link PolyGeometry} */ public PolyPoint getCentroid() { - return PolyPoint.of( jtsGeometry.getCentroid() ); + return PolyPoint.of( jtsGeometry.getCentroid(), getSRID() ); } @@ -385,7 +398,7 @@ public PolyPoint getCentroid() { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance ) { - return PolyGeometry.of( jtsGeometry.buffer( distance ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance ), getSRID() ); } @@ -401,7 +414,7 @@ public PolyGeometry buffer( double distance ) { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance, int quadrantSegments ) { - return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments ), getSRID() ); } @@ -418,7 +431,7 @@ public PolyGeometry buffer( double distance, int quadrantSegments ) { * @return a {@link PolyPolygon} that represent buffer region around this {@link PolyGeometry} */ public PolyGeometry buffer( double distance, int quadrantSegments, BufferCapStyle endCapStyle ) { - return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments, endCapStyle.code ) ); + return PolyGeometry.of( jtsGeometry.buffer( distance, quadrantSegments, endCapStyle.code ), getSRID() ); } @@ -426,7 +439,7 @@ public PolyGeometry buffer( double distance, int quadrantSegments, BufferCapStyl * @return new {@link PolyGeometry} with coordinates in a reverse order. */ public PolyGeometry reverse() { - return PolyGeometry.of( jtsGeometry.reverse() ); + return PolyGeometry.of( jtsGeometry.reverse(), getSRID() ); } /* @@ -446,7 +459,7 @@ public PolyGeometry reverse() { * @return true if {@link PolyGeometry} is withing the given distance */ public boolean isWithinDistance( @NotNull PolyGeometry g, double distance ) throws GeometryTopologicalException { - if (this.SRID == NO_SRID) { + if ( this.SRID == NO_SRID ) { // Euclidean distance return jtsGeometry.isWithinDistance( g.getJtsGeometry(), distance ); } @@ -617,7 +630,7 @@ public boolean relate( @NotNull PolyGeometry g, @NotNull String intersectionPatt * @return the distance in meters if spheroid, otherwise in Cartesian coordinate units */ public double distance( @NotNull PolyGeometry g ) throws GeometryTopologicalException { - if (this.SRID == NO_SRID) { + if ( this.SRID == NO_SRID ) { // Euclidean distance return jtsGeometry.distance( g.getJtsGeometry() ); } @@ -642,7 +655,7 @@ public double distance( @NotNull PolyGeometry g ) throws GeometryTopologicalExce */ public PolyGeometry intersection( @NotNull PolyGeometry g ) throws GeometryTopologicalException { try { - return PolyGeometry.of( jtsGeometry.intersection( g.getJtsGeometry() ) ); + return PolyGeometry.of( jtsGeometry.intersection( g.getJtsGeometry() ), getSRID() ); } catch ( TopologyException | IllegalArgumentException e ) { // TopologyException: robustness error occurs // IllegalArgumentException: non-empty heterogeneous GeometryCollection as an input @@ -663,7 +676,7 @@ public PolyGeometry intersection( @NotNull PolyGeometry g ) throws GeometryTopol */ public PolyGeometry union( @NotNull PolyGeometry g ) throws GeometryTopologicalException { try { - return PolyGeometry.of( jtsGeometry.union( g.getJtsGeometry() ) ); + return PolyGeometry.of( jtsGeometry.union( g.getJtsGeometry() ), getSRID() ); } catch ( TopologyException | IllegalArgumentException e ) { // TopologyException: robustness error occurs // IllegalArgumentException: non-empty GeometryCollection as an input @@ -683,7 +696,7 @@ public PolyGeometry union( @NotNull PolyGeometry g ) throws GeometryTopologicalE */ public PolyGeometry difference( @NotNull PolyGeometry g ) throws GeometryTopologicalException { try { - return PolyGeometry.of( jtsGeometry.difference( g.getJtsGeometry() ) ); + return PolyGeometry.of( jtsGeometry.difference( g.getJtsGeometry() ), getSRID() ); } catch ( TopologyException | IllegalArgumentException e ) { // TopologyException: robustness error occurs // IllegalArgumentException: non-empty GeometryCollection as an input @@ -705,7 +718,7 @@ public PolyGeometry difference( @NotNull PolyGeometry g ) throws GeometryTopolog */ public PolyGeometry symDifference( @NotNull PolyGeometry g ) throws GeometryTopologicalException { try { - return PolyGeometry.of( jtsGeometry.symDifference( g.getJtsGeometry() ) ); + return PolyGeometry.of( jtsGeometry.symDifference( g.getJtsGeometry() ), getSRID() ); } catch ( TopologyException | IllegalArgumentException e ) { // TopologyException: robustness error occurs // IllegalArgumentException: non-empty GeometryCollection as an input diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java index 5a2f251710..d10974bb09 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyGeometryCollection.java @@ -57,6 +57,15 @@ public PolyGeometryCollection( Geometry geometry ) { } + public PolyGeometryCollection( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = getPolyGeometryType(); + this.jtsGeometry = geometry; + this.jtsGeometryCollection = (GeometryCollection) jtsGeometry; + this.SRID = srid; + } + + protected PolyGeometryCollection( PolyType type ) { super( type ); this.geometryType = getPolyGeometryType(); @@ -68,6 +77,11 @@ public static PolyGeometryCollection of( Geometry geometry ) { } + public static PolyGeometryCollection of( Geometry geometry, int srid ) { + return new PolyGeometryCollection( geometry, srid ); + } + + public boolean isMultiPoint() { return geometryType.equals( PolyGeometryType.MULTIPOINT ); } @@ -76,7 +90,7 @@ public boolean isMultiPoint() { @NotNull public PolyMultiPoint asMultiPoint() { if ( isMultiPoint() ) { - return PolyMultiPoint.of( jtsGeometry ); + return PolyMultiPoint.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyMultiPoint.class ); } @@ -90,7 +104,7 @@ public boolean isMultiLineString() { @NotNull public PolyMultiLineString asMultiLineString() { if ( isMultiLineString() ) { - return PolyMultiLineString.of( jtsGeometry ); + return PolyMultiLineString.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyMultiLineString.class ); } @@ -104,7 +118,7 @@ public boolean isMultiPolygon() { @NotNull public PolyMultiPolygon asMultiPolygon() { if ( isMultiPolygon() ) { - return PolyMultiPolygon.of( jtsGeometry ); + return PolyMultiPolygon.of( jtsGeometry, getSRID() ); } throw cannotParse( this, PolyMultiPolygon.class ); } @@ -125,7 +139,7 @@ public int getNumGeometries() { * @return the nth {@link PolyGeometry} in the collection. */ public PolyGeometry getGeometryN( int n ) { - return PolyGeometry.of( jtsGeometryCollection.getGeometryN( n ) ); + return PolyGeometry.of( jtsGeometryCollection.getGeometryN( n ), getSRID() ); } @@ -135,7 +149,7 @@ public PolyGeometry getGeometryN( int n ) { * @return the union set of all {@link PolyGeometry} in the collection. */ public PolyGeometry union() { - return PolyGeometry.of( jtsGeometryCollection.union() ); + return PolyGeometry.of( jtsGeometryCollection.union(), getSRID() ); } } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java index a67af10ef8..a0b889f4b8 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLineString.java @@ -61,6 +61,15 @@ protected PolyLineString( Geometry geometry ) { } + protected PolyLineString( Geometry geometry, int srid ) { + super( PolyType.GEOMETRY ); + this.geometryType = PolyGeometryType.LINESTRING; + this.jtsGeometry = geometry; + this.jtsLineString = (LineString) jtsGeometry; + this.SRID = srid; + } + + protected PolyLineString( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.LINESTRING; @@ -72,6 +81,11 @@ public static PolyLineString of( Geometry geometry ) { } + public static PolyLineString of( Geometry geometry, int srid ) { + return new PolyLineString( geometry, srid ); + } + + /** * Test whether {@link PolyLineString} is closed: line starts and ends at the same point. * @@ -110,7 +124,7 @@ public boolean isCoordinate( PolyPoint point ) { * @return the nth point in the sequence. */ public PolyPoint getPoint( int n ) { - return PolyPoint.of( jtsLineString.getPointN( n ) ); + return PolyPoint.of( jtsLineString.getPointN( n ), getSRID() ); } @@ -118,7 +132,7 @@ public PolyPoint getPoint( int n ) { * @return the first (start) point in the sequence */ public PolyPoint getStartPoint() { - return PolyPoint.of( jtsLineString.getStartPoint() ); + return PolyPoint.of( jtsLineString.getStartPoint(), getSRID() ); } @@ -126,7 +140,7 @@ public PolyPoint getStartPoint() { * @return the last (end) point in the sequence */ public PolyPoint getEndPoint() { - return PolyPoint.of( jtsLineString.getEndPoint() ); + return PolyPoint.of( jtsLineString.getEndPoint(), getSRID() ); } } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java index 331b604dff..240a7100b5 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyLinearRing.java @@ -55,6 +55,15 @@ protected PolyLinearRing( Geometry geometry ) { } + protected PolyLinearRing( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = PolyGeometryType.LINEARRING; + this.jtsGeometry = geometry; + this.jtsLinearRing = (LinearRing) jtsGeometry; + this.SRID = srid; + } + + protected PolyLinearRing( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.LINEARRING; @@ -65,4 +74,9 @@ public static PolyLinearRing of( Geometry geometry ) { return new PolyLinearRing( geometry ); } + + public static PolyLinearRing of( Geometry geometry, int srid ) { + return new PolyLinearRing( geometry, srid ); + } + } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java index 93f5272531..75fb2cc6b8 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiLineString.java @@ -54,6 +54,15 @@ public PolyMultiLineString( Geometry geometry ) { } + public PolyMultiLineString( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTILINESTRING; + this.jtsGeometry = geometry; + this.jtMultiLineString = (MultiLineString) jtsGeometry; + this.SRID = srid; + } + + protected PolyMultiLineString( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.MULTILINESTRING; @@ -65,6 +74,11 @@ public static PolyMultiLineString of( Geometry geometry ) { } + public static PolyMultiLineString of( Geometry geometry, int srid ) { + return new PolyMultiLineString( geometry, srid ); + } + + /** * Test whether {@link PolyMultiLineString} is closed: line starts and ends at the same point. * diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java index 4c2b819038..c27e786ed0 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPoint.java @@ -56,6 +56,15 @@ public PolyMultiPoint( Geometry geometry ) { } + public PolyMultiPoint( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTIPOINT; + this.jtsGeometry = geometry; + this.jtsMultiPoint = (MultiPoint) jtsGeometry; + this.SRID = srid; + } + + protected PolyMultiPoint( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.MULTIPOINT; @@ -66,4 +75,9 @@ public static PolyMultiPoint of( Geometry geometry ) { return new PolyMultiPoint( geometry ); } + + public static PolyMultiPoint of( Geometry geometry, int srid ) { + return new PolyMultiPoint( geometry, srid ); + } + } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java index 1ea6d9a1d4..4a28005c01 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyMultiPolygon.java @@ -57,6 +57,15 @@ public PolyMultiPolygon( Geometry geometry ) { } + public PolyMultiPolygon( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = PolyGeometryType.MULTIPOLYGON; + this.jtsGeometry = geometry; + this.jtsMultiPolygon = (MultiPolygon) jtsGeometry; + this.SRID = srid; + } + + protected PolyMultiPolygon( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.MULTIPOLYGON; @@ -67,4 +76,9 @@ public static PolyMultiPolygon of( Geometry geometry ) { return new PolyMultiPolygon( geometry ); } + + public static PolyMultiPolygon of( Geometry geometry, int srid ) { + return new PolyMultiPolygon( geometry, srid ); + } + } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java index 127121fa33..3843859aa0 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPoint.java @@ -57,6 +57,15 @@ protected PolyPoint( Geometry geometry ) { } + protected PolyPoint( Geometry geometry, int srid ) { + super( PolyType.GEOMETRY ); + this.geometryType = PolyGeometryType.POINT; + this.jtsGeometry = geometry; + this.jtsPoint = (Point) jtsGeometry; + this.SRID = srid; + } + + protected PolyPoint( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.POINT; @@ -68,6 +77,11 @@ public static PolyPoint of( Geometry geometry ) { } + public static PolyPoint of( Geometry geometry, int srid ) { + return new PolyPoint( geometry, srid ); + } + + public double getX() { return jtsPoint.getX(); } diff --git a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java index 87f84b94a6..13c0239c81 100644 --- a/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java +++ b/core/src/main/java/org/polypheny/db/type/entity/spatial/PolyPolygon.java @@ -61,6 +61,15 @@ public PolyPolygon( Geometry geometry ) { } + public PolyPolygon( Geometry geometry, int srid ) { + super( geometry ); + this.geometryType = PolyGeometryType.POLYGON; + this.jtsGeometry = geometry; + this.jtsPolygon = (Polygon) jtsGeometry; + this.SRID = srid; + } + + protected PolyPolygon( PolyType type ) { super( type ); this.geometryType = PolyGeometryType.POLYGON; @@ -72,13 +81,18 @@ public static PolyPolygon of( Geometry geometry ) { } + public static PolyPolygon of( Geometry geometry, int srid ) { + return new PolyPolygon( geometry, srid ); + } + + public boolean isRectangle() { return jtsPolygon.isRectangle(); } public PolyLinearRing getExteriorRing() { - return PolyLinearRing.of( jtsPolygon.getExteriorRing() ); + return PolyLinearRing.of( jtsPolygon.getExteriorRing(), getSRID() ); } @@ -88,7 +102,7 @@ public int getNumInteriorRing() { public PolyLinearRing getInteriorRingN( int n ) { - return PolyLinearRing.of( jtsPolygon.getInteriorRingN( n ) ); + return PolyLinearRing.of( jtsPolygon.getInteriorRingN( n ), getSRID() ); } } diff --git a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java index 3ea5eeb3a8..e715e0a26f 100644 --- a/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java +++ b/core/src/main/java/org/polypheny/db/util/BuiltInMethod.java @@ -477,6 +477,11 @@ public enum BuiltInMethod { ST_RELATE( GeoFunctions.class, "stRelate", PolyGeometry.class, PolyGeometry.class, PolyString.class ), // Yield metric values ST_DISTANCE( GeoFunctions.class, "stDistance", PolyGeometry.class, PolyGeometry.class ), + // Set operations + ST_INTERSECTION( GeoFunctions.class, "stIntersection", PolyGeometry.class, PolyGeometry.class ), + ST_UNION( GeoFunctions.class, "stUnion", PolyGeometry.class, PolyGeometry.class ), + ST_DIFFERENCE( GeoFunctions.class, "stDifference", PolyGeometry.class, PolyGeometry.class ), + ST_SYMDIFFERENCE( GeoFunctions.class, "stSymDifference", PolyGeometry.class, PolyGeometry.class ), // on Points ST_X( GeoFunctions.class, "stX", PolyGeometry.class ), ST_Y( GeoFunctions.class, "stY", PolyGeometry.class ), diff --git a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java index 99419cd134..b59a73a866 100644 --- a/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java +++ b/dbms/src/test/java/org/polypheny/db/sql/fun/GeoFunctionsTest.java @@ -244,6 +244,39 @@ public void distanceFunctions() throws SQLException { } } + @Test + public void setOperationsFunctions() throws SQLException { + try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { + Connection connection = polyphenyDbConnection.getConnection(); + try ( Statement statement = connection.createStatement() ) { + // calculate the intersection of two points + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Intersection(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('POINT (9.289382 48.741588)', 4326))" ), + ImmutableList.of( + new Object[]{ "SRID=4326;POINT EMPTY" } // empty + ) ); + // calculate the union of two points + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Union(ST_GeoFromText('POINT (7.852923 47.998949)', 4326), ST_GeoFromText('POINT (9.289382 48.741588)', 4326))" ), + ImmutableList.of( + new Object[]{ "SRID=4326;MULTIPOINT ((7.852923 47.998949), (9.289382 48.741588))" } + ) ); + // calculate the difference of linestring and polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_Difference(ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 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[]{ "SRID=4326;LINESTRING (10.289382 47.741588, 12.289382 45.741588)" } + ) ); + // calculate the symmetrical difference of linestring and polygon + TestHelper.checkResultSet( + statement.executeQuery( "SELECT ST_SymDifference(ST_GeoFromText('LINESTRING (9.289382 48.741588, 10.289382 47.741588, 12.289382 45.741588)', 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[]{ "SRID=4326;GEOMETRYCOLLECTION (LINESTRING (10.289382 47.741588, 12.289382 45.741588), POLYGON ((9.289382 48.741588, 10.289382 47.741588, 9.289382 47.741588, 9.289382 48.741588)))" } + ) ); + } + } + } + @Test public void pointFunctions() throws SQLException { try ( TestHelper.JdbcConnection polyphenyDbConnection = new TestHelper.JdbcConnection( true ) ) { diff --git a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java index 492cf984ab..5e9b26397b 100644 --- a/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java +++ b/plugins/sql-language/src/main/java/org/polypheny/db/sql/SqlLanguagePlugin.java @@ -2758,6 +2758,49 @@ public void unparse( SqlWriter writer, SqlCall call, int leftPrec, int rightPrec OperandTypes.GEOMETRY_GEOMETRY, FunctionCategory.GEOMETRY ) ); + // Set operations + + register( + OperatorName.ST_INTERSECTION, + new SqlFunction( + "ST_INTERSECTION", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_UNION, + new SqlFunction( + "ST_UNION", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_DIFFERENCE, + new SqlFunction( + "ST_DIFFERENCE", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + register( + OperatorName.ST_SYMDIFFERENCE, + new SqlFunction( + "ST_SYMDIFFERENCE", + Kind.GEO, + ReturnTypes.GEOMETRY, + InferTypes.GEOMETRY, + OperandTypes.GEOMETRY_GEOMETRY, + FunctionCategory.GEOMETRY ) ); + + // on Points register( OperatorName.ST_X,