This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch geoapi-4.0 in repository https://gitbox.apache.org/repos/asf/sis.git
commit 3337cb71bae057805ce04f831a34d3e6c10f65c4 Author: jsorel <[email protected]> AuthorDate: Mon Oct 19 11:50:04 2026 +0200 feat(Geometry): add draft topology interfaces --- .../org.apache.sis.geometry/main/module-info.java | 1 + .../org/apache/sis/geometries/package-info.java | 3 +- .../apache/sis/geometries/topology/Complex.java | 116 ++++++++++++ .../sis/geometries/topology/ComplexData.java | 45 +++++ .../sis/geometries/topology/DirectedEdge.java | 101 +++++++++++ .../sis/geometries/topology/DirectedFace.java | 97 +++++++++++ .../sis/geometries/topology/DirectedNode.java | 97 +++++++++++ .../sis/geometries/topology/DirectedSolid.java | 98 +++++++++++ .../sis/geometries/topology/DirectedTopo.java | 94 ++++++++++ .../org/apache/sis/geometries/topology/Edge.java | 87 +++++++++ .../apache/sis/geometries/topology/Expression.java | 194 +++++++++++++++++++++ .../sis/geometries/topology/ExpressionTerm.java | 69 ++++++++ .../org/apache/sis/geometries/topology/Face.java | 104 +++++++++++ .../org/apache/sis/geometries/topology/Node.java | 75 ++++++++ .../apache/sis/geometries/topology/Primitive.java | 140 +++++++++++++++ .../sis/geometries/topology/PrimitiveData.java | 34 ++++ .../org/apache/sis/geometries/topology/Solid.java | 87 +++++++++ .../apache/sis/geometries/topology/Topology.java | 167 ++++++++++++++++++ .../sis/geometries/topology/TopologyData.java | 72 ++++++++ .../sis/geometries/topology/package-info.java | 65 +++++++ 20 files changed, 1745 insertions(+), 1 deletion(-) diff --git a/incubator/src/org.apache.sis.geometry/main/module-info.java b/incubator/src/org.apache.sis.geometry/main/module-info.java index a7a0f82480..2a2df363ce 100644 --- a/incubator/src/org.apache.sis.geometry/main/module-info.java +++ b/incubator/src/org.apache.sis.geometry/main/module-info.java @@ -40,6 +40,7 @@ module org.apache.sis.geometry { exports org.apache.sis.geometries.solid; exports org.apache.sis.geometries.solid.polyhedron; exports org.apache.sis.geometries.spherical; + exports org.apache.sis.geometries.topology; exports org.apache.sis.gml; exports org.apache.sis.maths; exports org.apache.sis.scene; diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/package-info.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/package-info.java index 17330bc505..f7f41d86f9 100644 --- a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/package-info.java +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/package-info.java @@ -66,7 +66,8 @@ * Geometry.getMaximalComplex returns a Geometry until this interface exists. * * <h3>TODO : ISO 19107 Topology (Section 10)</h3> - * No interface or implementation yet. + * {@link org.apache.sis.geometries.topology} declares the interfaces, no implementation yet. + * The derived topological relations (Section 10.8) are not declared. * This work would be interesting to formalize delaunay classes. * * <h3>TODO : ISO 19107 Simplex (Section 11.1)</h3> diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Complex.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Complex.java new file mode 100644 index 0000000000..a90c35fcf1 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Complex.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.Set; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; +import org.apache.sis.geometries.Geometry; + + +/** + * A set of topological primitives, organized so that set operations on its elements perform the equivalent + * operations on the positions of its geometric realization. + * + * <p>A complex of dimension <var>n</var> contains primitives of every dimension up to <var>n</var>: + * a 2-dimensional complex contains faces, edges and nodes, while a 1-dimensional complex, or graph, + * contains only edges and nodes.</p> + * + * <p>The default construction of a topological complex is from a geometric collection, which then becomes + * its geometric realization. Only a collection of geometric primitives with mutually disjoint interiors + * gives a complex without error. Java interfaces have no constructor, so this is left to implementations.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.7.2 + */ +@UML(identifier="Complex", specification=ISO_19107) +public interface Complex extends Topology { + + /** + * The primitives of this complex. This association makes a complex a set of primitives, + * and the set operations of the {@linkplain #getSubComplexes() Contains} association agree with it. + * + * @return primitives of this complex, at least one. + * + * @see ISO 19107:2019 - 10.7.2.7 + */ + @UML(identifier="element", specification=ISO_19107) + Set<Primitive> getElements(); + + /** + * Returns {@code true} if this complex is not contained in any larger complex. + * + * <p>The default implementation tests whether this complex is its own + * {@linkplain #getMaximalComplex() maximal complex}.</p> + * + * @return whether this complex is maximal. + * + * @see ISO 19107:2019 - 10.7.2.4 + */ + @UML(identifier="isMaximal", specification=ISO_19107) + default boolean isMaximal() { + return equals(getMaximalComplex()); + } + + /** + * Returns {@code true} if this complex is topologically connected, which implies that its geometric + * realization is connected too. It is the case when every primitive of this complex is linked to every + * other one by a sequence of boundary, co-boundary and isolated-in associations whose intermediate + * primitives all belong to this complex. + * + * @return whether this complex is connected. + * + * @see ISO 19107:2019 - 10.7.2.5 + */ + @UML(identifier="isConnected", specification=ISO_19107) + boolean isConnected(); + + /** + * The complexes contained in this complex, as sets of primitives. + * + * @return complexes contained in this complex. + * + * @see ISO 19107:2019 - 10.7.2.6 + */ + @UML(identifier="subComplex", specification=ISO_19107) + Set<Complex> getSubComplexes(); + + /** + * The complexes containing this complex, as sets of primitives. + * + * @return complexes containing this complex. + * + * @see ISO 19107:2019 - 10.7.2.6 + */ + @UML(identifier="superComplex", specification=ISO_19107) + Set<Complex> getSuperComplexes(); + + /** + * The geometric complex this complex represents, or {@code null} if none. + * + * <p>Difference with ISO 19107: the standard returns a geometric complex, + * a type which does not exist yet in this module.</p> + * + * @return geometric realization of this complex, or {@code null} if none. + * + * @see ISO 19107:2019 - 10.7.2.8 + */ + @UML(identifier="geometry", specification=ISO_19107) + Geometry getGeometry(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ComplexData.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ComplexData.java new file mode 100644 index 0000000000..e70008e29b --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ComplexData.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * Data for building a topological {@link Complex} from a set of related primitive data, such that all + * the primitive interiors are disjoint and every boundary is represented by other geometries. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.7 + */ +@UML(identifier="ComplexData", specification=ISO_19107) +public interface ComplexData extends TopologyData { + + /** + * Data of the primitives of this complex. + * + * @return data of the primitives of this complex. + * + * @see ISO 19107:2019 - 10.1.7.2 + */ + @UML(identifier="primitive", specification=ISO_19107) + List<PrimitiveData> getPrimitives(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedEdge.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedEdge.java new file mode 100644 index 0000000000..9de767a3c9 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedEdge.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * An {@link Edge} with an orientation, used in the terms of an {@link Expression}. + * It is the topological equivalent of an oriented {@link org.apache.sis.geometries.Curve}, + * standing for the edge traversed in one direction or the other. + * + * <p>In a 2-dimensional planar complex, an oriented edge bounds at most one face, on its left. + * In 3 dimensions, or in a non-planar 2-dimensional complex, it may bound several faces.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.4.2 + */ +@UML(identifier="DirectedEdge", specification=ISO_19107) +public interface DirectedEdge extends DirectedTopo { + + /** + * Returns 1: an edge is a curve. + * + * @see ISO 19107:2019 - 10.4.1.5 + */ + @UML(identifier="dimension", specification=ISO_19107) + @Override + default int getDimension() { + return 1; + } + + /** + * The two oriented nodes bounding this oriented edge: the node where it ends is positive, + * and the node where it starts is negative. + * + * @return the two oriented nodes at the ends of this oriented edge. + * + * @see ISO 19107:2019 - 10.4.1.3 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedNode> getBoundary(); + + /** + * Oriented faces having this oriented edge on their boundary. + * See {@link Edge#getCoBoundary()} for the orientation and the order of the faces. + * + * @return oriented faces around this oriented edge. + * + * @see ISO 19107:2019 - 10.4.1.2 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedFace> getCoBoundary(); + + /** + * The same edge with the opposite orientation. + * + * @see ISO 19107:2019 - 10.1.4.3 + */ + @UML(identifier="negate", specification=ISO_19107) + @Override + DirectedEdge getNegate(); + + /** + * The edge of which this object is one of the two orientations. + * + * @see ISO 19107:2019 - 10.4.1.4 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + Edge getTopo(); + + /** + * The two oriented edges standing for the edge, the positive one first. + * + * @see ISO 19107:2019 - 10.4.1.4 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + List<DirectedEdge> getProxy(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedFace.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedFace.java new file mode 100644 index 0000000000..2359666209 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedFace.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * A {@link Face} with an orientation, used in the terms of an {@link Expression} and to define the boundary + * of a {@link Solid}. It is the topological equivalent of an oriented {@link org.apache.sis.geometries.Surface}. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.5.3 + */ +@UML(identifier="DirectedFace", specification=ISO_19107) +public interface DirectedFace extends DirectedTopo { + + /** + * Returns 2: a face is a surface. + * + * @see ISO 19107:2019 - 10.5.2.4 + */ + @UML(identifier="dimension", specification=ISO_19107) + @Override + default int getDimension() { + return 2; + } + + /** + * Oriented edges bounding this oriented face, positive when the face is on their left. + * See {@link Face#getBoundary()} for the organization in rings. + * + * @return oriented edges bounding this oriented face. + * + * @see ISO 19107:2019 - 10.5.2.1 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedEdge> getBoundary(); + + /** + * Oriented solids having this oriented face on their boundary, at most two. + * See {@link Face#getCoBoundary()} for the orientation of the solids. + * + * @return oriented solids on each side of this oriented face. + * + * @see ISO 19107:2019 - 10.5.2.2 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedSolid> getCoBoundary(); + + /** + * The same face with the opposite orientation. + * + * @see ISO 19107:2019 - 10.1.4.3 + */ + @UML(identifier="negate", specification=ISO_19107) + @Override + DirectedFace getNegate(); + + /** + * The face of which this object is one of the two orientations. + * + * @see ISO 19107:2019 - 10.5.2.3 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + Face getTopo(); + + /** + * The two oriented faces standing for the face, the positive one first. + * + * @see ISO 19107:2019 - 10.5.2.3 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + List<DirectedFace> getProxy(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedNode.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedNode.java new file mode 100644 index 0000000000..a67d87bcd9 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedNode.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * A {@link Node} with an orientation, used in the terms of an {@link Expression}. + * The end node of an edge appears positively in the boundary of that edge, and its start node negatively. + * There is no separate geometric equivalent, since a point has no orientation. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.3.3 + */ +@UML(identifier="DirectedNode", specification=ISO_19107) +public interface DirectedNode extends DirectedTopo { + + /** + * Returns 0: a node is a point. + * + * @see ISO 19107:2019 - 10.3.2.4 + */ + @UML(identifier="dimension", specification=ISO_19107) + @Override + default int getDimension() { + return 0; + } + + /** + * Returns an empty list: a node has no boundary, which is a zero expression. + * + * @see ISO 19107:2019 - 10.3.2.3 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + default List<? extends DirectedTopo> getBoundary() { + return List.of(); + } + + /** + * Oriented edges having this oriented node on their boundary. + * See {@link Node#getCoBoundary()} for the orientation of the edges. + * + * @return oriented edges starting or ending at this node. + * + * @see ISO 19107:2019 - 10.3.2.1 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedEdge> getCoBoundary(); + + /** + * The same node with the opposite orientation. + * + * @see ISO 19107:2019 - 10.1.4.3 + */ + @UML(identifier="negate", specification=ISO_19107) + @Override + DirectedNode getNegate(); + + /** + * The node of which this object is one of the two orientations. + * + * @see ISO 19107:2019 - 10.1.4.6 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + Node getTopo(); + + /** + * The two oriented nodes standing for the node, the positive one first. + * + * @see ISO 19107:2019 - 10.3.2.2 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + List<DirectedNode> getProxy(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedSolid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedSolid.java new file mode 100644 index 0000000000..d8738bd666 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedSolid.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * A {@link Solid} with an orientation, used in the terms of an {@link Expression}. + * There is no separate geometric equivalent, since a geometric solid is not orientable. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.6.2 + */ +@UML(identifier="DirectedSolid", specification=ISO_19107) +public interface DirectedSolid extends DirectedTopo { + + /** + * Returns 3: a solid is a volume. + * + * @see ISO 19107:2019 - 10.1.2.3 + */ + @UML(identifier="dimension", specification=ISO_19107) + @Override + default int getDimension() { + return 3; + } + + /** + * Oriented faces bounding this oriented solid, positive when the solid is below them. + * See {@link Solid#getBoundary()} for the organization in shells. + * + * @return oriented faces bounding this oriented solid. + * + * @see ISO 19107:2019 - 10.6.1.2 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedFace> getBoundary(); + + /** + * Returns an empty list: a solid has the highest dimension, so it is on the boundary of nothing. + * + * <p>Difference with ISO 19107: the standard returns {@code NULL}.</p> + * + * @see ISO 19107:2019 - 10.6.1.3 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + default List<? extends DirectedTopo> getCoBoundary() { + return List.of(); + } + + /** + * The same solid with the opposite orientation. + * + * @see ISO 19107:2019 - 10.1.4.3 + */ + @UML(identifier="negate", specification=ISO_19107) + @Override + DirectedSolid getNegate(); + + /** + * The solid of which this object is one of the two orientations. + * + * @see ISO 19107:2019 - 10.6.1.4 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + Solid getTopo(); + + /** + * The two oriented solids standing for the solid, the positive one first. + * + * @see ISO 19107:2019 - 10.6.1.4 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + List<DirectedSolid> getProxy(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedTopo.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedTopo.java new file mode 100644 index 0000000000..4ca63c44a8 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/DirectedTopo.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; +import org.apache.sis.geometries.Orientable; + + +/** + * A topological primitive with an orientation, the topological equivalent of {@link Orientable}. + * + * <p>Since a primitive has only two orientations whatever its dimension, each primitive has two oriented + * primitives. Each primitive type extends its oriented type, so that a primitive is its own positive + * orientation, as in the algebraic <var>x</var> = +<var>x</var>. A {@link Node} is a positive + * {@link DirectedNode}, an {@link Edge} a positive {@link DirectedEdge}, and so on.</p> + * + * <p>Oriented primitives of adjacent dimensions agree on the orientation: if a positive edge is on the + * boundary of a face, the positive face is on the co-boundary of that edge.</p> + * + * <p>Constraints:</p> + * <ul> + * <li>The {@linkplain #getBoundary() boundary} is the boundary of the {@linkplain #getTopo() primitive}, + * each term multiplied by the {@linkplain #getOrientationSign() orientation}. Likewise for the + * {@linkplain #getCoBoundary() co-boundary}.</li> + * <li>The {@linkplain #getNegate() opposite} has the same primitive and the other orientation.</li> + * </ul> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.4 + */ +@UML(identifier="DirectedTopo", specification=ISO_19107) +public interface DirectedTopo extends Primitive { + + /** + * Whether this oriented primitive agrees with the orientation of its {@linkplain #getTopo() primitive}. + * The default is {@link Orientable.Sign#POSITIVE}, which is always the case of a primitive itself. + * + * @return orientation of this object relative to its primitive. + * + * @see ISO 19107:2019 - 10.1.4.2 + */ + @UML(identifier="orientation", specification=ISO_19107) + default Orientable.Sign getOrientationSign() { + return Orientable.Sign.POSITIVE; + } + + /** + * The same primitive with the opposite orientation. + * + * @return this object with reversed orientation. + * + * @see ISO 19107:2019 - 10.1.4.3 + */ + @UML(identifier="negate", specification=ISO_19107) + DirectedTopo getNegate(); + + /** + * This oriented primitive as an expression of a single term, keeping its orientation. + * + * @return expression made of this oriented primitive. + * + * @see ISO 19107:2019 - 10.1.4.4 + */ + @UML(identifier="expression", specification=ISO_19107) + Expression getExpression(); + + /** + * The primitive of which this object is one of the two orientations. + * A primitive returns itself. + * + * @return primitive underlying this object. + * + * @see ISO 19107:2019 - 10.1.4.5 + */ + @UML(identifier="topo", specification=ISO_19107) + Primitive getTopo(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Edge.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Edge.java new file mode 100644 index 0000000000..f1d9b3ac43 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Edge.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * The 1-dimensional topological primitive, usually realized by a {@link org.apache.sis.geometries.Curve}. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.4.1 + */ +@UML(identifier="Edge", specification=ISO_19107) +public interface Edge extends DirectedEdge { + + /** + * The start and end nodes of this edge, as the expression <var>end</var> − <var>start</var>: + * the end node is a positive {@link DirectedNode} and the start node a negative one. + * When both nodes are the same, the two terms cancel out in the expression. + * + * @return the two oriented nodes at the ends of this edge. + * + * @see ISO 19107:2019 - 10.4.1.3 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedNode> getBoundary(); + + /** + * Oriented faces using this edge on their boundary: positive when the face uses this edge, + * negative when it uses its negative orientation. + * + * <p>The faces are listed in clockwise circular order, as seen from the end of the edge + * in a geometric realization of the maximal complex containing this edge.</p> + * + * <p>In a 2-dimensional planar complex there are at most two faces, exactly two in a full topology: + * the positive one on the left of the edge, and the negative one on its right.</p> + * + * @return oriented faces around this edge, in clockwise order seen from its end. + * + * @see ISO 19107:2019 - 10.4.1.2 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedFace> getCoBoundary(); + + /** + * Returns this edge, which is its own positive orientation. + * + * @see ISO 19107:2019 - 10.1.4.1 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + default Edge getTopo() { + return this; + } + + /** + * Returns this edge then its {@linkplain #getNegate() negative orientation}. + * + * @see ISO 19107:2019 - 10.4.1.4 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + default List<DirectedEdge> getProxy() { + return List.of(this, getNegate()); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Expression.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Expression.java new file mode 100644 index 0000000000..f340e46e76 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Expression.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.Set; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * A multivariate polynomial of degree 1 whose variables are oriented primitives, such as + * 2<var>e₁</var> − <var>e₂</var>. It is the basis of computational topology: geometric problems + * are translated into algebraic ones, solved there, and the solutions are translated back. + * + * <p>The order of the terms does not change the value of a polynomial, so an expression is + * a set of {@linkplain ExpressionTerm terms}, each one an integer coefficient and an oriented primitive. + * Expressions behave as vectors having the oriented primitives as basis vectors.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.8 + */ +@UML(identifier="Expression", specification=ISO_19107) +public interface Expression { + + /** + * The terms of this expression, in no particular order. Terms having the same underlying primitive + * are combined, and terms of coefficient zero are removed, so each primitive appears at most once. + * + * @return terms of this expression, empty for the zero expression. + * + * @see ISO 19107:2019 - 10.1.8.1 + */ + Collection<ExpressionTerm> getTerms(); + + /** + * Returns {@code true} if this expression has no term. + * This method is not in ISO 19107, which refers to the zero expression in several places. + * + * @return whether this expression is zero. + */ + default boolean isZero() { + return getTerms().isEmpty(); + } + + /** + * This expression with every term reversed: the unary minus of polynomials. + * + * @return the opposite of this expression. + * + * @see ISO 19107:2019 - 10.1.8.2 + */ + @UML(identifier="negate", specification=ISO_19107) + Expression getNegate(); + + /** + * This expression with each primitive replaced by its boundary, then simplified. + * The boundary is made of primitives of the next lower dimension, and is zero + * if all primitives of this expression are nodes. + * + * @return boundary of this expression. + * + * @see ISO 19107:2019 - 10.1.8.3 + */ + @UML(identifier="boundary", specification=ISO_19107) + Expression getBoundary(); + + /** + * This expression with each primitive replaced by its co-boundary, then simplified. + * The co-boundary is made of primitives of the next higher dimension, and is zero if the primitives + * of this expression have the dimension of their maximal complex. + * + * @return co-boundary of this expression. + * + * @see ISO 19107:2019 - 10.1.8.4 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + Expression getCoBoundary(); + + /** + * This expression as a set of primitives, for computing geometric operators: the primitives + * of the {@linkplain #asSet() oriented primitives set}. The support is a valid {@link Complex}, + * and is empty for the zero expression. + * + * <p>The default implementation returns the {@linkplain DirectedTopo#getTopo() primitive} + * of each element of {@link #asSet()}.</p> + * + * @return primitives of this expression and of their boundaries. + * + * @see ISO 19107:2019 - 10.1.8.5 + */ + @UML(identifier="support", specification=ISO_19107) + default Set<Primitive> getSupport() { + final Set<Primitive> support = new LinkedHashSet<>(); + for (final DirectedTopo element : asSet()) { + support.add(element.getTopo()); + } + return support; + } + + /** + * This expression as a set of oriented primitives, for computing geometric operators. The set contains + * the variable of each term, and every element of their boundaries recursively down to the nodes. + * It is empty for the zero expression. + * + * <p>The default implementation takes each variable with the sign of its coefficient, + * then adds the boundaries recursively.</p> + * + * @return oriented primitives of this expression and of their boundaries. + * + * @see ISO 19107:2019 - 10.1.8.6 + */ + @UML(identifier="asSet", specification=ISO_19107) + default Set<DirectedTopo> asSet() { + final Set<DirectedTopo> set = new LinkedHashSet<>(); + final Deque<DirectedTopo> pending = new ArrayDeque<>(); + for (final ExpressionTerm term : getTerms()) { + final DirectedTopo variable = term.getVariable(); + pending.add(term.getCoefficient() < 0 ? variable.getNegate() : variable); + } + DirectedTopo next; + while ((next = pending.poll()) != null) { + if (set.add(next)) { + pending.addAll(next.getBoundary()); + } + } + return set; + } + + /** + * The vector sum of this expression and the given one. Terms having the same underlying primitive + * are combined by adding their coefficients, and terms of coefficient zero are removed. + * + * @param other the expression to add to this expression. + * @return sum of the two expressions. + * + * @see ISO 19107:2019 - 10.1.8.7 + */ + @UML(identifier="plus", specification=ISO_19107) + Expression plus(Expression other); + + /** + * The vector difference of this expression and the given one. Terms having the same underlying primitive + * are combined by subtracting their coefficients, and terms of coefficient zero are removed. + * + * @param other the expression to subtract from this expression. + * @return difference of the two expressions. + * + * @see ISO 19107:2019 - 10.1.8.8 + */ + @UML(identifier="minus", specification=ISO_19107) + Expression minus(Expression other); + + /** + * Returns {@code true} if the given object is an expression equal to this one as a polynomial. + * The order of the terms does not matter. + * + * @param other the object to compare with this expression. + * @return whether the given object is an equal polynomial. + * + * @see ISO 19107:2019 - 10.1.8.9 + */ + @UML(identifier="equals", specification=ISO_19107) + @Override + boolean equals(Object other); + + /** + * Returns a hash code value consistent with {@link #equals(Object)}, + * therefore independent of the order of the terms. + * + * @return hash code value of this expression. + */ + @Override + int hashCode(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ExpressionTerm.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ExpressionTerm.java new file mode 100644 index 0000000000..4e31a3a115 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/ExpressionTerm.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.Collection; +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * A term of an {@link Expression}: an oriented primitive with an integer coefficient, such as + * 2<var>e₁</var>. A term is a monomial, so it is also an expression of a single term. + * Arithmetic on terms is the usual vector arithmetic. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.9 + */ +@UML(identifier="ExpressionTerm", specification=ISO_19107) +public interface ExpressionTerm extends Expression { + + /** + * The integer multiplying the {@linkplain #getVariable() variable}. The default is 1. + * + * @return coefficient of this term. + * + * @see ISO 19107:2019 - 10.1.9 + */ + @UML(identifier="coefficient", specification=ISO_19107) + default int getCoefficient() { + return 1; + } + + /** + * The oriented primitive of this term. + * + * @return variable of this term. + * + * @see ISO 19107:2019 - 10.1.9 + */ + @UML(identifier="variable", specification=ISO_19107) + DirectedTopo getVariable(); + + /** + * Returns this term alone, or nothing if its {@linkplain #getCoefficient() coefficient} is zero. + * + * @return this term, or an empty list for the zero expression. + */ + @Override + default Collection<ExpressionTerm> getTerms() { + return (getCoefficient() == 0) ? List.of() : List.of(this); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Face.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Face.java new file mode 100644 index 0000000000..b3753e065c --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Face.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * The 2-dimensional topological primitive, usually realized by a {@link org.apache.sis.geometries.Surface}. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.5.2 + */ +@UML(identifier="Face", specification=ISO_19107) +public interface Face extends DirectedFace { + + /** + * Oriented edges bounding this face: positive for the edges having this face on their left, + * negative for those having it on their right. The edges of every ring are included, + * those of the {@linkplain #getExteriorRing() exterior ring} first. + * + * @return oriented edges bounding this face, at least one. + * + * @see ISO 19107:2019 - 10.5.2.1 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedEdge> getBoundary(); + + /** + * The ring of the {@linkplain #getBoundary() boundary} which is the exterior of this face, + * as oriented edges in traversal order. + * + * <p>The same restriction applies as for the geometric boundary of a surface: on a closed surface such + * as a sphere, no ring can be told to be the exterior one, and this method returns an empty list.</p> + * + * @return oriented edges of the exterior ring, or an empty list if it cannot be determined. + * + * @see ISO 19107:2019 - 10.5.2.1 + */ + List<DirectedEdge> getExteriorRing(); + + /** + * The rings of the {@linkplain #getBoundary() boundary} other than the {@linkplain #getExteriorRing() exterior one}, + * each as oriented edges in traversal order. + * + * @return oriented edges of every interior ring, empty if none. + * + * @see ISO 19107:2019 - 10.5.2.1 + */ + List<List<DirectedEdge>> getInteriorRings(); + + /** + * Oriented solids using this face on their boundary: positive when the solid uses this face, + * negative when it uses its negative orientation. A face separates at most two solids. + * + * @return oriented solids on each side of this face, at most two. + * + * @see ISO 19107:2019 - 10.5.2.2 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedSolid> getCoBoundary(); + + /** + * Returns this face, which is its own positive orientation. + * + * @see ISO 19107:2019 - 10.1.4.1 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + default Face getTopo() { + return this; + } + + /** + * Returns this face then its {@linkplain #getNegate() negative orientation}. + * + * @see ISO 19107:2019 - 10.5.2.3 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + default List<DirectedFace> getProxy() { + return List.of(this, getNegate()); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Node.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Node.java new file mode 100644 index 0000000000..79b5df2df2 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Node.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * The 0-dimensional topological primitive, usually realized by a {@link org.apache.sis.geometries.Point}. + * + * <p>A node has no boundary. It may be isolated in a face while being the end of an edge, as long as that + * edge is not on the boundary of the face: the realization is a curve hanging in space and ending where it + * meets a surface.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.3.2 + */ +@UML(identifier="Node", specification=ISO_19107) +public interface Node extends DirectedNode { + + /** + * Oriented edges having this node on their boundary: positive for the edges entering + * this node (ending at it), and negative for the edges leaving it (starting at it). + * + * <p>In a 2-dimensional maximal complex, the edges can be listed as a clockwise circular sequence + * around the node in any geometric realization. In a 3-dimensional complex the order is arbitrary.</p> + * + * @return oriented edges starting or ending at this node. + * + * @see ISO 19107:2019 - 10.3.2.1 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + @Override + List<DirectedEdge> getCoBoundary(); + + /** + * Returns this node, which is its own positive orientation. + * + * @see ISO 19107:2019 - 10.1.4.1 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + default Node getTopo() { + return this; + } + + /** + * Returns this node then its {@linkplain #getNegate() negative orientation}. + * + * @see ISO 19107:2019 - 10.3.2.2 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + default List<DirectedNode> getProxy() { + return List.of(this, getNegate()); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Primitive.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Primitive.java new file mode 100644 index 0000000000..3c97f815ec --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Primitive.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import java.util.Set; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; +import org.apache.sis.geometries.Orientable; + + +/** + * A non-decomposed element of a topological {@link Complex}. + * + * <p>A topological primitive usually stands for a geometric primitive of the same dimension. When a + * geometric complex is the realization of a topological complex, the primitives of both are in a + * one-to-one correspondence which preserves the dimension.</p> + * + * <p>Unlike a geometric primitive, which may stand alone, a topological primitive is always part of at + * least one complex.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.3 + */ +@UML(identifier="Primitive", specification=ISO_19107) +public interface Primitive extends Topology { + + /** + * Returns this primitive alone: a primitive is its own interior. + * + * @return a set containing only this primitive. + * + * @see ISO 19107:2019 - 10.1.2.6 + */ + @UML(identifier="interior", specification=ISO_19107) + @Override + default Set<Primitive> getInterior() { + return Set.of(this); + } + + /** + * The geometric primitive this primitive represents in its maximal complex, or {@code null} if the + * maximal complex describes a logical structure without geometric realization. + * + * <p>Constraints:</p> + * <ul> + * <li>Either every primitive of a maximal complex has a geometry, or none has.</li> + * <li>A geometric primitive is associated with at most one primitive of any given complex, + * but may be associated with primitives of different complexes.</li> + * </ul> + * + * @return geometric realization of this primitive, or {@code null} if none. + * + * @see ISO 19107:2019 - 10.1.3.2 + */ + @UML(identifier="geometry", specification=ISO_19107) + org.apache.sis.geometries.Primitive getGeometry(); + + /** + * The complexes containing this primitive. They are all sub-complexes of the single + * maximal complex containing this primitive. + * + * @return complexes containing this primitive. + * + * @see ISO 19107:2019 - 10.1.3.3 + */ + @UML(identifier="complex", specification=ISO_19107) + Set<Complex> getComplexes(); + + /** + * Primitives isolated in this primitive: lying in its interior without any primitive of intermediate + * dimension between them. Examples are a node inside a face and not attached to the boundary of that face, + * or an edge inside a solid and not attached to its boundary faces. + * + * <p>Constraints:</p> + * <ul> + * <li>The dimension of every isolated primitive is lower than the dimension of this primitive minus one.</li> + * </ul> + * + * @return primitives isolated in this primitive, empty if none. + * + * @see ISO 19107:2019 - 10.1.3.4 + */ + @UML(identifier="isolated", specification=ISO_19107) + Set<Primitive> getIsolated(); + + /** + * The primitive in which this primitive is isolated, or {@code null} if none. + * A primitive without container lies on the boundary of another primitive, + * unless it has the highest dimension of its complex. + * + * @return primitive in which this primitive is isolated, or {@code null} if none. + * + * @see ISO 19107:2019 - 10.1.3.4 + */ + @UML(identifier="container", specification=ISO_19107) + Primitive getContainer(); + + /** + * The two oriented primitives standing for this primitive, + * the {@linkplain Orientable.Sign#POSITIVE positive} one first. + * + * @return the positive then the negative oriented primitive. + * + * @see ISO 19107:2019 - 10.1.4.5 + */ + @UML(identifier="proxy", specification=ISO_19107) + List<? extends DirectedTopo> getProxy(); + + /** + * Returns the oriented primitive standing for this primitive with the given orientation. + * + * <p>The default implementation returns the first or the second {@linkplain #getProxy() proxy}.</p> + * + * @param orientation orientation of the requested oriented primitive. + * @return the oriented primitive with the given orientation. + * + * @see ISO 19107:2019 - 10.1.3 + */ + @UML(identifier="asDirectedTopo", specification=ISO_19107) + default DirectedTopo asDirectedTopo(final Orientable.Sign orientation) { + return getProxy().get(orientation == Orientable.Sign.POSITIVE ? 0 : 1); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/PrimitiveData.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/PrimitiveData.java new file mode 100644 index 0000000000..0a644c2581 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/PrimitiveData.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * Data for building a topological {@link Primitive} from its underlying geometric primitive, such that all + * the primitive interiors are disjoint and every primitive boundary is represented by other geometries. + * All its information comes from {@link TopologyData}. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.6 + */ +@UML(identifier="PrimitiveData", specification=ISO_19107) +public interface PrimitiveData extends TopologyData { +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Solid.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Solid.java new file mode 100644 index 0000000000..7dd18bde79 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Solid.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * The 3-dimensional topological primitive, usually realized by a {@link org.apache.sis.geometries.Solid}. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.6.1 + */ +@UML(identifier="Solid", specification=ISO_19107) +public interface Solid extends DirectedSolid { + + /** + * Oriented faces bounding this solid: positive for the faces having this solid below them, + * negative for those having it above them. The faces of every shell are included, + * those of the {@linkplain #getExteriorShell() exterior shell} first. + * + * @return oriented faces bounding this solid, at least one. + * + * @see ISO 19107:2019 - 10.6.1.2 + */ + @UML(identifier="boundary", specification=ISO_19107) + @Override + List<DirectedFace> getBoundary(); + + /** + * The shell of the {@linkplain #getBoundary() boundary} which is the exterior of this solid. + * + * @return oriented faces of the exterior shell. + * + * @see ISO 19107:2019 - 10.6.1.2 + */ + List<DirectedFace> getExteriorShell(); + + /** + * The shells of the {@linkplain #getBoundary() boundary} bounding the voids of this solid. + * + * @return oriented faces of every interior shell, empty if none. + * + * @see ISO 19107:2019 - 10.6.1.2 + */ + List<List<DirectedFace>> getInteriorShells(); + + /** + * Returns this solid, which is its own positive orientation. + * + * @see ISO 19107:2019 - 10.1.4.1 + */ + @UML(identifier="topo", specification=ISO_19107) + @Override + default Solid getTopo() { + return this; + } + + /** + * Returns this solid then its {@linkplain #getNegate() negative orientation}. + * + * @see ISO 19107:2019 - 10.6.1.4 + */ + @UML(identifier="proxy", specification=ISO_19107) + @Override + default List<DirectedSolid> getProxy() { + return List.of(this, getNegate()); + } + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Topology.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Topology.java new file mode 100644 index 0000000000..5e6387d4e5 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/Topology.java @@ -0,0 +1,167 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; + + +/** + * Root type of the topological primitives and complexes. + * + * <p>Topology describes how objects are connected, independently of their coordinates. A topological + * complex turns geometric computations, such as containment, adjacency, boundaries or network tracing, + * into combinatorial ones. Its primitives usually have a geometric realization, but a complex may also + * describe a logical structure which has none.</p> + * + * <p>This type is not a {@link org.apache.sis.geometries.Geometry}. The topological boundary and the + * geometric boundary are two different operations, kept separate to avoid any confusion, even though + * they agree on any geometric realization of the topology.</p> + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.2 + */ +@UML(identifier="Topology", specification=ISO_19107) +public interface Topology { + + /** + * Returns {@code true} if this topology contains nothing. + * + * <p>Constraints:</p> + * <ul> + * <li>An empty topology has a {@linkplain #getDimension() dimension} of −1.</li> + * <li>An empty topology is realized only by empty geometries.</li> + * <li>Every other attribute and operation behaves consistently on an empty topology.</li> + * </ul> + * + * <p>An empty topology is meant as a temporary state, for example until a transaction completes.</p> + * + * @return whether this topology is empty. + * + * @see ISO 19107:2019 - 10.1.2.2 + */ + @UML(identifier="isEmpty", specification=ISO_19107) + boolean isEmpty(); + + /** + * Topological dimension of this object: 0 for nodes, 1 for edges, 2 for faces and 3 for solids. + * It depends only on the type of this object, and is −1 for an empty topology. + * + * @return topological dimension of this object. + * + * @see ISO 19107:2019 - 10.1.2.3 + */ + @UML(identifier="dimension", specification=ISO_19107) + int getDimension(); + + /** + * Oriented primitives of the next lower dimension which bound this object. + * + * <p>Constraints:</p> + * <ul> + * <li>The dimension of the boundary is the dimension of this object minus one.</li> + * <li>The orientations agree with the geometric realization of this object.</li> + * </ul> + * + * <p>This list is usually a valid {@link Expression}, except that a primitive may appear twice + * with opposite orientations, as a dangling edge which has the same face on both sides. Those two + * terms would cancel out in the expression.</p> + * + * @return boundary of this object, empty if none. + * + * @see ISO 19107:2019 - 10.1.2.4 + */ + @UML(identifier="boundary", specification=ISO_19107) + List<? extends DirectedTopo> getBoundary(); + + /** + * Oriented primitives of the next higher dimension having this object on their boundary. + * + * <p>This list is usually a valid {@link Expression}, except for an object lying on the boundary of a + * closed object, such as the node at both ends of a closed edge. That object appears twice, with + * opposite orientations, and would cancel out in the expression.</p> + * + * @return co-boundary of this object, empty if none. + * + * @see ISO 19107:2019 - 10.1.2.5 + */ + @UML(identifier="coBoundary", specification=ISO_19107) + List<? extends DirectedTopo> getCoBoundary(); + + /** + * Primitives forming the interior of this object within its {@linkplain #getMaximalComplex() maximal complex}. + * A primitive is its own interior. The interior of a complex is made of its elements which are not on its + * boundary. + * + * @return interior of this object. + * + * @see ISO 19107:2019 - 10.1.2.6 + */ + @UML(identifier="interior", specification=ISO_19107) + Set<Primitive> getInterior(); + + /** + * Primitives forming the exterior of this object within its {@linkplain #getMaximalComplex() maximal complex}: + * those which are neither in the interior nor on the boundary of this object. + * + * @return exterior of this object. + * + * @see ISO 19107:2019 - 10.1.2.7 + */ + @UML(identifier="exterior", specification=ISO_19107) + Set<Primitive> getExterior(); + + /** + * Union of the {@linkplain #getInterior() interior} and of the {@linkplain #getBoundary() boundary} of this object. + * + * <p>The default implementation adds the interior, then the primitives on the boundary, + * then those on their boundaries down to the nodes.</p> + * + * @return closure of this object. + * + * @see ISO 19107:2019 - 10.1.2.8 + */ + @UML(identifier="closure", specification=ISO_19107) + default Set<Primitive> getClosure() { + final Set<Primitive> closure = new LinkedHashSet<>(getInterior()); + final Deque<DirectedTopo> pending = new ArrayDeque<>(getBoundary()); + DirectedTopo next; + while ((next = pending.poll()) != null) { + if (closure.add(next.getTopo())) { + pending.addAll(next.getBoundary()); + } + } + return closure; + } + + /** + * The largest complex containing this object. Every object is contained in exactly one maximal complex. + * + * @return maximal complex containing this object. + * + * @see ISO 19107:2019 - 10.1.2.9 + */ + @UML(identifier="maximalComplex", specification=ISO_19107) + Complex getMaximalComplex(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/TopologyData.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/TopologyData.java new file mode 100644 index 0000000000..fd33f6a756 --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/TopologyData.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.geometries.topology; + +import java.util.List; +import static org.opengis.annotation.Specification.ISO_19107; +import org.opengis.annotation.UML; +import org.opengis.util.GenericName; +import org.apache.sis.geometries.Geometry; + + +/** + * Data for building a topology from its underlying geometry, such that all the geometry interiors + * are disjoint and every geometry boundary is represented by other geometries. + * + * @author Johann Sorel (Geomatys) + * + * @see ISO 19107:2019 - 10.1.5 + */ +@UML(identifier="TopologyData", specification=ISO_19107) +public interface TopologyData { + + /** + * Identifier by which other data of the same construction refer to this one. + * Its namespace component is unique within its maximal complex and its topological dimension. + * + * @return identifier of this data. + * + * @see ISO 19107:2019 - 10.1.5.2 + */ + @UML(identifier="id", specification=ISO_19107) + GenericName getId(); + + /** + * Data of the elements on the boundary of this element. + * + * @return data of the boundary elements, empty if none. + * + * @see ISO 19107:2019 - 10.1.5.3 + */ + @UML(identifier="boundaryData", specification=ISO_19107) + List<? extends TopologyData> getBoundaryData(); + + /** + * Geometry of the interior of this element. Its dimension usually matches the topological dimension, + * but not necessarily when the topology describes connections, as in a Poincaré dual graph. + * + * <p>Difference with ISO 19107: the standard type is {@code GeometryData}, + * which does not exist in this module.</p> + * + * @return geometry of the interior of this element. + * + * @see ISO 19107:2019 - 10.1.5.4 + */ + @UML(identifier="geometryData", specification=ISO_19107) + Geometry getGeometryData(); + +} diff --git a/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/package-info.java b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/package-info.java new file mode 100644 index 0000000000..66abfd699d --- /dev/null +++ b/incubator/src/org.apache.sis.geometry/main/org/apache/sis/geometries/topology/package-info.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Topology interfaces of ISO 19107:2019 section 10. + * + * <p>A topological complex describes how primitives are connected, independently of their coordinates, + * which turns geometric computations into combinatorial ones. Each primitive usually has a geometric + * realization of the same dimension in the {@link org.apache.sis.geometries} package.</p> + * + * <table class="sis"> + * <caption>Topological types</caption> + * <tr><th>Dimension</th> <th>Primitive</th> <th>Oriented primitive</th> <th>Usual realization</th></tr> + * <tr><td>0</td> <td>{@link org.apache.sis.geometries.topology.Node}</td> <td>{@link org.apache.sis.geometries.topology.DirectedNode}</td> <td>{@link org.apache.sis.geometries.Point}</td></tr> + * <tr><td>1</td> <td>{@link org.apache.sis.geometries.topology.Edge}</td> <td>{@link org.apache.sis.geometries.topology.DirectedEdge}</td> <td>{@link org.apache.sis.geometries.Curve}</td></tr> + * <tr><td>2</td> <td>{@link org.apache.sis.geometries.topology.Face}</td> <td>{@link org.apache.sis.geometries.topology.DirectedFace}</td> <td>{@link org.apache.sis.geometries.Surface}</td></tr> + * <tr><td>3</td> <td>{@link org.apache.sis.geometries.topology.Solid}</td> <td>{@link org.apache.sis.geometries.topology.DirectedSolid}</td> <td>{@link org.apache.sis.geometries.Solid}</td></tr> + * </table> + * + * <p>As in the standard, each primitive extends its oriented type, so a primitive is its own positive + * orientation. Oriented primitives are the variables of {@link org.apache.sis.geometries.topology.Expression}, + * the polynomials on which boundaries and co-boundaries are computed.</p> + * + * <h2>Differences with ISO 19107</h2> + * <ul> + * <li>Attributes are getter methods, such as {@code getBoundary()} for {@code boundary}.</li> + * <li>The orientation reuses {@link org.apache.sis.geometries.Orientable.Sign}, and is read by + * {@code getOrientationSign()} as for geometric orientable primitives.</li> + * <li>The two proxies of a primitive are listed with the positive one first.</li> + * <li>The rings of a face and the shells of a solid are read by {@code getExteriorRing()}, {@code getInteriorRings()}, + * {@code getExteriorShell()} and {@code getInteriorShells()}, instead of {@code FaceBoundary} and + * {@code SolidBoundary} types.</li> + * <li>The co-boundary of a solid is an empty list instead of {@code NULL}.</li> + * <li>The geometric complex realizing a topological complex, and the {@code GeometryData} of the construction + * data types, are a {@link org.apache.sis.geometries.Geometry}, since neither type exists in this module yet.</li> + * <li>The construction of a complex from a geometric collection is left to implementations, + * since Java interfaces have no constructor.</li> + * <li>{@code Expression} gives access to its terms, and tells whether it is zero.</li> + * </ul> + * + * <h2>Not covered</h2> + * <ul> + * <li>The derived topological relations of section 10.8: aggregate boundaries, Boolean, Egenhofer + * and full topological operators.</li> + * <li>The simplicial complexes of section 11.1.</li> + * <li>Editing operations, such as splitting an edge or a face, which the standard does not define.</li> + * </ul> + * + * @author Johann Sorel (Geomatys) + */ +package org.apache.sis.geometries.topology;
