From 2d817d3a8a8c06d84693fa7e140de29ba8979d2e Mon Sep 17 00:00:00 2001 From: Danylo Kravchenko Date: Fri, 3 Nov 2023 15:19:47 +0100 Subject: [PATCH] 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 ))" ) + ); }