001 /*
002 * Java GPX Library (jpx-3.1.0).
003 * Copyright (c) 2016-2023 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 * Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020 package io.jenetics.jpx;
021
022 import static java.lang.String.format;
023 import static java.util.Objects.requireNonNull;
024 import static io.jenetics.jpx.Lists.copyOf;
025 import static io.jenetics.jpx.Lists.copyTo;
026 import static io.jenetics.jpx.XMLWriter.elem;
027
028 import java.io.DataInput;
029 import java.io.DataOutput;
030 import java.io.IOException;
031 import java.io.InvalidObjectException;
032 import java.io.ObjectInputStream;
033 import java.io.Serial;
034 import java.io.Serializable;
035 import java.util.ArrayList;
036 import java.util.Iterator;
037 import java.util.List;
038 import java.util.Objects;
039 import java.util.Optional;
040 import java.util.function.Consumer;
041 import java.util.function.Function;
042 import java.util.function.Predicate;
043 import java.util.stream.Stream;
044
045 import org.w3c.dom.Document;
046
047 import io.jenetics.jpx.GPX.Version;
048
049 /**
050 * A Track Segment holds a list of Track Points which are logically connected in
051 * order. To represent a single GPS track where GPS reception was lost, or the
052 * GPS receiver was turned off, start a new Track Segment for each continuous
053 * span of track data.
054 *
055 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
056 * @version 1.5
057 * @since 1.0
058 */
059 public final class TrackSegment implements Iterable<WayPoint>, Serializable {
060
061 @Serial
062 private static final long serialVersionUID = 2L;
063
064 private final List<WayPoint> _points;
065 private final Document _extensions;
066
067 /**
068 * Create a new track-segment with the given points.
069 *
070 * @param points the points of the track-segment
071 */
072 private TrackSegment(final List<WayPoint> points, final Document extensions) {
073 _points = copyOf(points);
074 _extensions = extensions;
075 }
076
077 /**
078 * Return the track-points of this segment.
079 *
080 * @return the track-points of this segment
081 */
082 public List<WayPoint> getPoints() {
083 return _points;
084 }
085
086 /**
087 * Return a stream of {@link WayPoint} objects this track-segments contains.
088 *
089 * @return a stream of {@link WayPoint} objects this track-segment contains
090 */
091 public Stream<WayPoint> points() {
092 return _points.stream();
093 }
094
095 @Override
096 public Iterator<WayPoint> iterator() {
097 return _points.iterator();
098 }
099
100
101 /**
102 * Return the (cloned) extensions document. The root element of the returned
103 * document has the name {@code extensions}.
104 * <pre>{@code
105 * <extensions>
106 * ...
107 * </extensions>
108 * }</pre>
109 *
110 * @since 1.5
111 *
112 * @return the extensions document
113 * @throws org.w3c.dom.DOMException if the document could not be cloned,
114 * because of an erroneous XML configuration
115 */
116 public Optional<Document> getExtensions() {
117 return Optional.ofNullable(_extensions).map(XML::clone);
118 }
119
120 /**
121 * Convert the <em>immutable</em> track-segment object into a
122 * <em>mutable</em> builder initialized with the current track-segment
123 * values.
124 *
125 * @since 1.1
126 *
127 * @return a new track-segment builder initialized with the values of
128 * {@code this} track-segment
129 */
130 public Builder toBuilder() {
131 return builder()
132 .points(_points)
133 .extensions(_extensions);
134 }
135
136 /**
137 * Return {@code true} if {@code this} track-segment doesn't contain any
138 * track-point.
139 *
140 * @return {@code true} if {@code this} track-segment is empty, {@code false}
141 * otherwise
142 */
143 public boolean isEmpty() {
144 return _points.isEmpty();
145 }
146
147 /**
148 * Return {@code true} if {@code this} track-segment contains at least one
149 * track-point.
150 *
151 * @since 1.1
152 *
153 * @return {@code true} if {@code this} track-segment is not empty,
154 * {@code false} otherwise
155 */
156 public boolean nonEmpty() {
157 return !isEmpty();
158 }
159
160 @Override
161 public int hashCode() {
162 return Objects.hashCode(_points);
163 }
164
165 @Override
166 public boolean equals(final Object obj) {
167 return obj == this ||
168 obj instanceof TrackSegment &&
169 Objects.equals(((TrackSegment)obj)._points, _points);
170 }
171
172 @Override
173 public String toString() {
174 return format("TrackSegment[points=%s]", _points.size());
175 }
176
177 /**
178 * Builder class for creating immutable {@code TrackSegment} objects.
179 * <p>
180 * Creating a {@code TrackSegment} object with 3 track-points:
181 * <pre>{@code
182 * final TrackSegment segment = TrackSegment.builder()
183 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
184 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
185 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
186 * .build();
187 * }</pre>
188 */
189 public static final class Builder implements Filter<WayPoint, TrackSegment> {
190 private final List<WayPoint> _points = new ArrayList<>();
191 private Document _extensions;
192
193 private Builder() {
194 }
195
196 /**
197 * Set the way-points fo the track segment. The list of way-points may
198 * be {@code null}.
199 *
200 * @param points the track-segment points
201 * @return {@code this} {@code Builder} for method chaining
202 * @throws NullPointerException if one of the way-points in the list is
203 * {@code null}
204 */
205 public Builder points(final List<WayPoint> points) {
206 copyTo(points, _points);
207 return this;
208 }
209
210 /**
211 * Add a way-point to the track-segment.
212 *
213 * @param point the segment way-point
214 * @return {@code this} {@code Builder} for method chaining
215 * @throws NullPointerException if the given {@code href} is {@code null}
216 */
217 public Builder addPoint(final WayPoint point) {
218 _points.add(requireNonNull(point));
219 return this;
220 }
221
222 /**
223 * Add a way-point to the track-segment, via the given way-point builder.
224 * <p>
225 * Creating a {@code TrackSegment} object with 3 track-points:
226 * <pre>{@code
227 * final TrackSegment segment = TrackSegment.builder()
228 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
229 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
230 * .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
231 * .build();
232 * }</pre>
233 *
234 * @param point the segment way-point builder
235 * @return {@code this} {@code Builder} for method chaining
236 * @throws NullPointerException if the given {@code href} is {@code null}
237 */
238 public Builder addPoint(final Consumer<? super WayPoint.Builder> point) {
239 final WayPoint.Builder builder = WayPoint.builder();
240 point.accept(builder);
241 return addPoint(builder.build());
242 }
243
244 /**
245 * Return the current way-points. The returned list is mutable.
246 *
247 * @since 1.1
248 *
249 * @return the current, mutable way-point list
250 */
251 public List<WayPoint> points() {
252 return new NonNullList<>(_points);
253 }
254
255 /**
256 * Sets the extensions object, which may be {@code null}. The root
257 * element of the extensions document must be {@code extensions}.
258 * <pre>{@code
259 * <extensions>
260 * ...
261 * </extensions>
262 * }</pre>
263 *
264 * @since 1.5
265 *
266 * @param extensions the document
267 * @return {@code this} {@code Builder} for method chaining
268 * @throws IllegalArgumentException if the root element is not the
269 * an {@code extensions} node
270 */
271 public Builder extensions(final Document extensions) {
272 _extensions = XML.checkExtensions(extensions);
273 return this;
274 }
275
276 /**
277 * Return the current extensions
278 *
279 * @since 1.5
280 *
281 * @return the extensions document
282 */
283 public Optional<Document> extensions() {
284 return Optional.ofNullable(_extensions);
285 }
286
287 @Override
288 public Builder filter(final Predicate<? super WayPoint> predicate) {
289 points(_points.stream().filter(predicate).toList());
290 return this;
291 }
292
293 @Override
294 public Builder map(
295 final Function<? super WayPoint, ? extends WayPoint> mapper
296 ) {
297 points(
298 _points.stream()
299 .map(mapper)
300 .map(WayPoint.class::cast)
301 .toList()
302 );
303 return this;
304 }
305
306 @Override
307 public Builder flatMap(
308 final Function<
309 ? super WayPoint,
310 ? extends List<WayPoint>> mapper
311 ) {
312 points(
313 _points.stream()
314 .flatMap(wp -> mapper.apply(wp).stream())
315 .toList()
316 );
317 return this;
318 }
319
320 @Override
321 public Builder listMap(
322 final Function<
323 ? super List<WayPoint>,
324 ? extends List<WayPoint>> mapper
325 ) {
326 points(mapper.apply(_points));
327 return this;
328 }
329
330 /**
331 * Create a new track-segment from the current builder state.
332 *
333 * @return a new track-segment from the current builder state
334 */
335 @Override
336 public TrackSegment build() {
337 return of(_points, _extensions);
338 }
339
340 }
341
342 /**
343 * Create a new track-segment builder.
344 *
345 * @return a new track-segment builder
346 */
347 public static Builder builder() {
348 return new Builder();
349 }
350
351
352 /* *************************************************************************
353 * Static object creation methods
354 * ************************************************************************/
355
356 /**
357 * Create a new track-segment with the given points.
358 *
359 * @since 1.5
360 *
361 * @param points the points of the track-segment
362 * @param extensions the extensions document
363 * @return a new track-segment with the given points
364 * @throws NullPointerException if the given {@code points} sequence is
365 * {@code null}
366 */
367 public static TrackSegment of(
368 final List<WayPoint> points,
369 final Document extensions
370 ) {
371 return new TrackSegment(
372 points,
373 XML.extensions(XML.clone(extensions))
374 );
375 }
376
377 /**
378 * Create a new track-segment with the given points.
379 *
380 * @param points the points of the track-segment
381 * @return a new track-segment with the given points
382 * @throws NullPointerException if the given {@code points} sequence is
383 * {@code null}
384 */
385 public static TrackSegment of(final List<WayPoint> points) {
386 return of(points, null);
387 }
388
389
390 /* *************************************************************************
391 * Java object serialization
392 * ************************************************************************/
393
394 @Serial
395 private Object writeReplace() {
396 return new SerialProxy(SerialProxy.TRACK_SEGMENT, this);
397 }
398
399 @Serial
400 private void readObject(final ObjectInputStream stream)
401 throws InvalidObjectException
402 {
403 throw new InvalidObjectException("Serialization proxy required.");
404 }
405
406 void write(final DataOutput out) throws IOException {
407 IO.writes(_points, WayPoint::write, out);
408 IO.writeNullable(_extensions, IO::write, out);
409 }
410
411 static TrackSegment read(final DataInput in) throws IOException {
412 return new TrackSegment(
413 IO.reads(WayPoint::read, in),
414 IO.readNullable(IO::readDoc, in)
415 );
416 }
417
418
419 /* *************************************************************************
420 * XML stream object serialization
421 * ************************************************************************/
422
423 static XMLWriter<TrackSegment> xmlWriter(
424 final Version version,
425 final Function<? super Number, String> formatter
426 ) {
427 return elem("trkseg",
428 XMLWriter
429 .elems(WayPoint.xmlWriter(version,"trkpt", formatter))
430 .map(ts -> ts._points),
431 XMLWriter.doc("extensions").map(gpx -> gpx._extensions)
432 );
433 }
434
435 @SuppressWarnings("unchecked")
436 static XMLReader<TrackSegment> xmlReader(
437 final Version version,
438 final Function<? super String, Length> lengthParser
439 ) {
440 return XMLReader.elem(a -> new TrackSegment(
441 (List<WayPoint>)a[0],
442 XML.extensions((Document)a[1])
443 ),
444 "trkseg",
445 XMLReader.elems(WayPoint.xmlReader(version,"trkpt", lengthParser)),
446 XMLReader.doc("extensions")
447 );
448 }
449
450
451
452 }
|