001/*
002 * Java GPX Library (jpx-1.2.0).
003 * Copyright (c) 2017-2017 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 */
020package io.jenetics.jpx;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024import static io.jenetics.jpx.Lists.copy;
025import static io.jenetics.jpx.Lists.immutable;
026
027import java.io.Serializable;
028import java.util.ArrayList;
029import java.util.Iterator;
030import java.util.List;
031import java.util.Objects;
032import java.util.function.Consumer;
033import java.util.function.Function;
034import java.util.function.Predicate;
035import java.util.stream.Collectors;
036import java.util.stream.Stream;
037
038import javax.xml.stream.XMLStreamException;
039import javax.xml.stream.XMLStreamWriter;
040
041/**
042 * A Track Segment holds a list of Track Points which are logically connected in
043 * order. To represent a single GPS track where GPS reception was lost, or the
044 * GPS receiver was turned off, start a new Track Segment for each continuous
045 * span of track data.
046 *
047 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
048 * @version 1.1
049 * @since 1.0
050 */
051public final class TrackSegment implements Iterable<WayPoint>, Serializable {
052
053        private static final long serialVersionUID = 1L;
054
055        private final List<WayPoint> _points;
056
057        /**
058         * Create a new track-segment with the given points.
059         *
060         * @param points the points of the track-segment
061         */
062        private TrackSegment(final List<WayPoint> points) {
063                _points = immutable(points);
064        }
065
066        /**
067         * Return the track-points of this segment.
068         *
069         * @return the track-points of this segment
070         */
071        public List<WayPoint> getPoints() {
072                return _points;
073        }
074
075        /**
076         * Return a stream of {@link WayPoint} objects this track-segments contains.
077         *
078         * @return a stream of {@link WayPoint} objects this track-segment contains
079         */
080        public Stream<WayPoint> points() {
081                return _points.stream();
082        }
083
084        @Override
085        public Iterator<WayPoint> iterator() {
086                return _points.iterator();
087        }
088
089        /**
090         * Convert the <em>immutable</em> track-segment object into a
091         * <em>mutable</em> builder initialized with the current track-segment
092         * values.
093         *
094         * @since 1.1
095         *
096         * @return a new track-segment builder initialized with the values of
097         *        {@code this} track-segment
098         */
099        public Builder toBuilder() {
100                return builder().points(_points);
101        }
102
103        /**
104         * Return {@code true} if {@code this} track-segment doesn't contain any
105         * track-point.
106         *
107         * @return {@code true} if {@code this} track-segment is empty, {@code false}
108         *         otherwise
109         */
110        public boolean isEmpty() {
111                return _points.isEmpty();
112        }
113
114        /**
115         * Return {@code true} if {@code this} track-segment contains at least one
116         * track-point.
117         *
118         * @since 1.1
119         *
120         * @return {@code true} if {@code this} track-segment is not empty,
121         *         {@code false} otherwise
122         */
123        public boolean nonEmpty() {
124                return !isEmpty();
125        }
126
127        @Override
128        public int hashCode() {
129                return _points.hashCode();
130        }
131
132        @Override
133        public boolean equals(final Object obj) {
134                return obj instanceof TrackSegment &&
135                        ((TrackSegment)obj)._points.equals(_points);
136        }
137
138        @Override
139        public String toString() {
140                return format("TrackSegment[points=%s]", _points.size());
141        }
142
143        /**
144         * Builder class for creating immutable {@code TrackSegment} objects.
145         * <p>
146         * Creating a {@code TrackSegment} object with  3 track-points:
147         * <pre>{@code
148         * final TrackSegment segment = TrackSegment.builder()
149         *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
150         *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
151         *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
152         *     .build();
153         * }</pre>
154         */
155        public static final class Builder implements Filter<WayPoint, TrackSegment> {
156                private final List<WayPoint> _points = new ArrayList<>();
157
158                private Builder() {
159                }
160
161                /**
162                 * Set the way-points fo the track segment. The list of way-points may
163                 * be {@code null}.
164                 *
165                 * @param points the track-segment points
166                 * @return {@code this} {@code Builder} for method chaining
167                 * @throws NullPointerException if one of the way-points in the list is
168                 *         {@code null}
169                 */
170                public Builder points(final List<WayPoint> points) {
171                        copy(points, _points);
172                        return this;
173                }
174
175                /**
176                 * Add a way-point to the track-segment.
177                 *
178                 * @param point the segment way-point
179                 * @return {@code this} {@code Builder} for method chaining
180                 * @throws NullPointerException if the given {@code href} is {@code null}
181                 */
182                public Builder addPoint(final WayPoint point) {
183                        _points.add(requireNonNull(point));
184
185                        return this;
186                }
187
188                /**
189                 * Add a way-point to the track-segment, via the given way-point builder.
190                 * <p>
191                 * Creating a {@code TrackSegment} object with  3 track-points:
192                 * <pre>{@code
193                 * final TrackSegment segment = TrackSegment.builder()
194                 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
195                 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
196                 *     .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
197                 *     .build();
198                 * }</pre>
199                 *
200                 * @param point the segment way-point builder
201                 * @return {@code this} {@code Builder} for method chaining
202                 * @throws NullPointerException if the given {@code href} is {@code null}
203                 */
204                public Builder addPoint(final Consumer<WayPoint.Builder> point) {
205                        final WayPoint.Builder builder = WayPoint.builder();
206                        point.accept(builder);
207                        return addPoint(builder.build());
208                }
209
210                /**
211                 * Return the current way-points. The returned list is mutable.
212                 *
213                 * @since 1.1
214                 *
215                 * @return the current, mutable way-point list
216                 */
217                public List<WayPoint> points() {
218                        return new NonNullList<>(_points);
219                }
220
221                @Override
222                public Builder filter(final Predicate<? super WayPoint> predicate) {
223                        points(
224                                _points.stream()
225                                        .filter(predicate)
226                                        .collect(Collectors.toList())
227                        );
228
229                        return this;
230                }
231
232                @Override
233                public Builder map(
234                        final Function<? super WayPoint, ? extends WayPoint> mapper
235                ) {
236                        points(
237                                _points.stream()
238                                        .map(mapper)
239                                        .collect(Collectors.toList())
240                        );
241
242                        return this;
243                }
244
245                @Override
246                public Builder flatMap(
247                        final Function<
248                                ? super WayPoint,
249                                ? extends List<WayPoint>> mapper
250                ) {
251                        points(
252                                _points.stream()
253                                        .flatMap(wp -> mapper.apply(wp).stream())
254                                        .collect(Collectors.toList())
255                        );
256
257                        return this;
258                }
259
260                @Override
261                public Builder listMap(
262                        final Function<
263                                ? super List<WayPoint>,
264                                ? extends List<WayPoint>> mapper
265                ) {
266                        points(mapper.apply(_points));
267
268                        return this;
269                }
270
271                /**
272                 * Create a new track-segment from the current builder state.
273                 *
274                 * @return a new track-segment from the current builder state
275                 */
276                @Override
277                public TrackSegment build() {
278                        return new TrackSegment(_points);
279                }
280
281        }
282
283        /**
284         * Create a new track-segment builder.
285         *
286         * @return a new track-segment builder
287         */
288        public static Builder builder() {
289                return new Builder();
290        }
291
292
293        /* *************************************************************************
294         *  Static object creation methods
295         * ************************************************************************/
296
297        /**
298         * Create a new track-segment with the given points.
299         *
300         * @param points the points of the track-segment
301         * @return a new track-segment with the given points
302         * @throws NullPointerException if the given {@code points} sequence is
303         *        {@code null}
304         */
305        public static TrackSegment of(final List<WayPoint> points) {
306                return new TrackSegment(points);
307        }
308
309
310        /* *************************************************************************
311         *  XML stream object serialization
312         * ************************************************************************/
313
314        /**
315         * Writes this {@code Link} object to the given XML stream {@code writer}.
316         *
317         * @param writer the XML data sink
318         * @throws XMLStreamException if an error occurs
319         */
320        void write(final XMLStreamWriter writer) throws XMLStreamException {
321                final XMLWriter xml = new XMLWriter(writer);
322
323                xml.write("trkseg",
324                        xml.elems(_points, (p, w) -> p.write("trkpt", w))
325                );
326        }
327
328        @SuppressWarnings("unchecked")
329        static XMLReader<TrackSegment> reader() {
330                final XML.Function<Object[], TrackSegment> creator = a -> TrackSegment.of(
331                        (List<WayPoint>)a[0]
332                );
333
334                return XMLReader.of(creator, "trkseg",
335                        XMLReader.ofList(WayPoint.reader("trkpt"))
336                );
337        }
338
339}