001/* 002 * GeoAPI - Java interfaces for OGC/ISO standards 003 * Copyright © 2024 Open Geospatial Consortium, Inc. 004 * http://www.geoapi.org 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.opengis.coordinate; 019 020import java.util.Optional; 021import java.util.Iterator; 022import java.util.Spliterator; 023import java.util.stream.Stream; 024import java.util.stream.StreamSupport; 025import java.nio.FloatBuffer; 026import java.nio.DoubleBuffer; 027import org.opengis.geometry.DirectPosition; 028import org.opengis.referencing.operation.MathTransform; 029import org.opengis.annotation.UML; 030 031import static org.opengis.annotation.Obligation.*; 032import static org.opengis.annotation.Specification.*; 033 034 035/** 036 * A collection of coordinate tuples referenced to the same coordinate reference system (<abbr>CRS</abbr>). 037 * If the <abbr>CRS</abbr> is dynamic, the {@code CoordinateSet} metadata contains also a coordinate epoch. 038 * Operations on the geometry of the tuples within the coordinate set are valid only if all 039 * tuples are referenced to the same coordinate epoch. 040 * 041 * <h2>Coordinate operations</h2> 042 * Coordinate sets referenced to one <abbr>CRS</abbr> may be referenced to another <abbr>CRS</abbr> through the 043 * application of a {@linkplain org.opengis.referencing.operation.CoordinateOperation coordinate operation}. 044 * If the <abbr>CRS</abbr> is dynamic, the {@code CoordinateSet} may be converted to another coordinate epoch 045 * through a point motion coordinate operation that includes time evolution. 046 * 047 * @author OGC Topic 2 (for abstract model and documentation) 048 * @author Martin Desruisseaux (Geomatys) 049 * @version 3.1 050 * 051 * @see org.opengis.referencing.operation.CoordinateOperation#transform(CoordinateSet) 052 * 053 * @since 3.1 054 */ 055@UML(identifier="CoordinateSet", specification=ISO_19111) 056public interface CoordinateSet extends Iterable<DirectPosition> { 057 /** 058 * Coordinate metadata to which this coordinate set is referenced. Coordinate metadata includes a 059 * {@linkplain org.opengis.referencing.crs.CoordinateReferenceSystem coordinate reference system} 060 * (<abbr>CRS</abbr>) and, if the <abbr>CRS</abbr> is dynamic, a coordinate epoch. 061 * 062 * @return coordinate metadata to which this coordinate set is referenced. 063 */ 064 @UML(identifier="coordinateMetadata", obligation=MANDATORY, specification=ISO_19111) 065 CoordinateMetadata getCoordinateMetadata(); 066 067 /** 068 * Returns the number of dimensions of coordinate tuples. This is determined by the 069 * {@linkplain CoordinateMetadata#getCoordinateReferenceSystem() coordinate reference system}. 070 * 071 * @return the number of dimensions of coordinate tuples. 072 * 073 * @departure easeOfUse 074 * This shortcut has been added because this is a frequently used information. 075 */ 076 default int getDimension() { 077 // All methods invoked below are for attributes declared as mandatory. Values shall not be null. 078 return getCoordinateMetadata().getCoordinateReferenceSystem().getCoordinateSystem().getDimension(); 079 } 080 081 /** 082 * Returns an iterator over the positions described by coordinate tuples. 083 * The positions shall be in a well-defined encounter order. 084 * For each element, the following constraints shall be met: 085 * 086 * <ul> 087 * <li>{@link DirectPosition#getDimension()} is equal to {@link #getDimension()}.</li> 088 * <li>{@link DirectPosition#getCoordinateReferenceSystem()} is null or equal to 089 * {@link CoordinateMetadata#getCoordinateReferenceSystem()}.</li> 090 * </ul> 091 * 092 * Invoking this method is equivalent to invoking {@code stream().iterator()}. 093 * 094 * @return a new iterator over the positions described by coordinate tuples. 095 */ 096 @Override 097 @UML(identifier="coordinateTuple", obligation=MANDATORY, specification=ISO_19111) 098 Iterator<DirectPosition> iterator(); 099 100 /** 101 * Returns a sequential stream of coordinate tuples. 102 * The positions shall be in a well-defined encounter order 103 * (i.e., positions are {@linkplain Spliterator#ORDERED ordered}). 104 * 105 * <h4>Default implementation</h4> 106 * If {@link #asDoubleBuffers()} or {@link #asFloatBuffers()} (in that preference order) returns 107 * a non-empty value, then the default method returns a stream of views over the buffers content. 108 * Otherwise, the default method returns a stream backed by {@link #iterator()}. 109 * 110 * @return a sequential stream of coordinate tuples. 111 * 112 * @departure integration 113 * Added for allowing developers to process coordinate tuples efficiently in Java environments. 114 * The use of Java streams makes parallel processing easier. 115 */ 116 default Stream<DirectPosition> stream() { 117 final Optional<Stream<DoubleBuffer>> asDoubleBuffers = asDoubleBuffers(); 118 if (asDoubleBuffers.isPresent()) { 119 final var src = asDoubleBuffers.get(); 120 final int dim = getDimension(); 121 final var crs = getCoordinateMetadata().getCoordinateReferenceSystem(); 122 return src.flatMap((buffer) -> StreamSupport.stream(new BufferToPoint.Doubles(crs, dim, buffer), src.isParallel())); 123 } 124 final Optional<Stream<FloatBuffer>> asFloatBuffers = asFloatBuffers(); 125 if (asFloatBuffers.isPresent()) { 126 final var src = asFloatBuffers.get(); 127 final int dim = getDimension(); 128 final var crs = getCoordinateMetadata().getCoordinateReferenceSystem(); 129 return src.flatMap((buffer) -> StreamSupport.stream(new BufferToPoint.Floats(crs, dim, buffer), src.isParallel())); 130 } 131 return StreamSupport.stream(spliterator(), false); 132 } 133 134 /** 135 * If the coordinates are packed in sequences of double-precision floating point values, 136 * returns views over those sequences. For example, if the number of dimensions is 3, 137 * then the coordinates can be packed in this order: 138 * 139 * (<var>x₀</var>,<var>y₀</var>,<var>z₀</var>, 140 * <var>x₁</var>,<var>y₁</var>,<var>z₁</var> …). 141 * 142 * <p>The coordinates may be packed in a single buffer, or may be partitioned in many buffers. 143 * For each buffer, the number of coordinate tuples is {@link DoubleBuffer#remaining()} / {@link #getDimension()}. 144 * Each buffer may have a different number of coordinate tuples. It shall be safe to modify the position, limit or 145 * mark of buffer instances (see example below). Each {@link DoubleBuffer} instance should be a <em>view</em> over 146 * an underlying coordinate array, those arrays should not be copied.</p> 147 * 148 * <h4>Examples</h4> 149 * For an implementation with all coordinates packed in a single array of {@code double} primitive values: 150 * 151 * {@snippet lang="java" : 152 * private final double[] coordinates = ...; 153 * 154 * public Optional<Stream<DoubleBuffer>> asDoubleBuffers() { 155 * return Optional.of(Stream.of(DoubleBuffer.wrap(coordinates))); 156 * } 157 * } 158 * 159 * For an implementation with all coordinates partitioned in an array of {@link DoubleBuffer} wrappers. 160 * Note the call to {@link DoubleBuffer#duplicate()} for protecting the internal buffers from changes: 161 * 162 * {@snippet lang="java" : 163 * private final DoubleBuffer[] buffers = ...; 164 * 165 * public Optional<Stream<DoubleBuffer>> asDoubleBuffers() { 166 * return Optional.of(Arrays.stream(buffers).map(DoubleBuffer::duplicate)); 167 * } 168 * } 169 * 170 * <h4><abbr>API</abbr> notes</h4> 171 * The use of {@link DoubleBuffer} makes possible to handle coordinate values not only from a Java array, 172 * but also from {@linkplain DoubleBuffer#slice() a sub-array}, from {@linkplain java.nio.MappedByteBuffer 173 * a region of a file} or from the {@link java.lang.foreign.MemorySegment#asByteBuffer() memory of a native 174 * application}. 175 * 176 * <p>Empty {@code Optional} and empty {@code Stream} are not synonymous. 177 * An empty optional means that this {@code CoordinateSet} is not backed by sequences of double-precision values, 178 * or that those values are not packed in the way described above, or are not accessible for any reason. 179 * An empty stream means that the coordinates are accessible by this method, but there is none 180 * (i.e., this {@code CoordinateSet} is empty).</p> 181 * 182 * <p>At most one of {@code asDoubleBuffers()} and {@link #asFloatBuffers()} should return a non-empty value, 183 * because a {@code CoordinateSet} may be backed by single- or double-precision floating point values, 184 * but usually not both in same time.</p> 185 * 186 * @departure integration 187 * Added for allowing developers to access coordinate tuples efficiently in Java environments, 188 * including the case where the coordinate values are in the memory of a native application. 189 * 190 * @return a view over the sequences of coordinates as double-precision floating point values. 191 * 192 * @see #stream() 193 * @see MathTransform#transform(DoubleBuffer, DoubleBuffer) 194 * @see MathTransform#transform(DoubleBuffer, FloatBuffer) 195 */ 196 default Optional<Stream<DoubleBuffer>> asDoubleBuffers() { 197 return Optional.empty(); 198 } 199 200 /** 201 * If the coordinates are packed in sequences of single-precision floating point values, returns views 202 * over those sequences. This method contract is the same as {@link #asDoubleBuffers()}, with only the 203 * {@code float} type instead of {@code double}. 204 * 205 * <p>At most one of {@code asFloatBuffers()} and {@link #asDoubleBuffers()} should return a non-empty value, 206 * because a {@code CoordinateSet} may be backed by single- or double-precision floating point values, 207 * but usually not both in same time.</p> 208 * 209 * @departure integration 210 * Added for allowing developers to access coordinate tuples efficiently in Java environments, 211 * including the case where the coordinate values are in the memory of a native application. 212 * 213 * @return a view over the sequences of coordinates as single-precision floating point values. 214 * 215 * @see #stream() 216 * @see MathTransform#transform(FloatBuffer, FloatBuffer) 217 * @see MathTransform#transform(FloatBuffer, DoubleBuffer) 218 */ 219 default Optional<Stream<FloatBuffer>> asFloatBuffers() { 220 return Optional.empty(); 221 } 222}