TrackSegment.java
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 outthrows IOException {
407         IO.writes(_points, WayPoint::write, out);
408         IO.writeNullable(_extensions, IO::write, out);
409     }
410 
411     static TrackSegment read(final DataInput inthrows 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 }