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.time.ZoneOffset.UTC;
023 import static java.util.stream.Collectors.groupingBy;
024
025 import java.time.LocalDate;
026 import java.util.List;
027 import java.util.Map;
028 import java.util.Map.Entry;
029 import java.util.stream.Stream;
030
031 /**
032 * Some commonly usable way-point filter methods.
033 *
034 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
035 * @version 1.1
036 * @since 1.1
037 */
038 public final class Filters {
039
040 private Filters() {
041 }
042
043 /**
044 * Merges the given segments into one segment containing all way-points. The
045 * order of the way-points is preserved.
046 * <pre>{@code
047 * final GPX merged = gpx.toBuilder()
048 * .trackFilter()
049 * .map(track -> track.toBuilder()
050 * .listMap(Filters::mergeSegments)
051 * .filter(TrackSegment::nonEmpty)
052 * .build())
053 * .build()
054 * .build();
055 * }</pre>
056 *
057 * @param segments the segment list to merge
058 * @return a list with one segment, containing all way-points of the original
059 * segments
060 * @throws NullPointerException if the given segment list is {@code null}
061 */
062 public static List<TrackSegment> mergeSegments(
063 final List<TrackSegment> segments
064 ) {
065 final List<WayPoint> points = segments.stream()
066 .flatMap(TrackSegment::points)
067 .toList();
068
069 return List.of(TrackSegment.of(points));
070 }
071
072 /**
073 * Merges the given tracks into one track containing all segments. The order
074 * of the segments is preserved.
075 * <pre>{@code
076 * final GPX merged = gpx.toBuilder()
077 * .trackFilter()
078 * .listMap(Filters::mergeTracks)
079 * .filter(Track::nonEmpty)
080 * .build())
081 * .build();
082 * }</pre>
083 *
084 * @param tracks the track list to merge
085 * @return a list with one track, containing all segments
086 * @throws NullPointerException if the given track list is {@code null}
087 */
088 public static List<Track> mergeTracks(final List<Track> tracks) {
089 final List<TrackSegment> segments = tracks.stream()
090 .flatMap(Track::segments)
091 .toList();
092
093 return tracks.isEmpty()
094 ? List.of()
095 : List.of(
096 tracks.get(0).toBuilder()
097 .segments(segments)
098 .build()
099 );
100 }
101
102 /**
103 * Merges all way-points of all segments of the given track list into one
104 * track with one segment, containing all way-points. The order of the
105 * way-points is preserved.
106 * <pre>{@code
107 * final GPX merged = gpx.toBuilder()
108 * .trackFilter()
109 * .listMap(Filters::fullyMergeTracks)
110 * .build())
111 * .build();
112 * }</pre>
113 *
114 *
115 * @param tracks the track list to merge
116 * @return a list with one track, containing one segment with all way-points
117 * @throws NullPointerException if the given track list is {@code null}
118 */
119 public static List<Track> fullyMergeTracks(final List<Track> tracks) {
120 final List<WayPoint> points = tracks.stream()
121 .flatMap(Track::segments)
122 .flatMap(TrackSegment::points)
123 .toList();
124
125 return tracks.isEmpty()
126 ? List.of()
127 : List.of(
128 tracks.get(0).toBuilder()
129 .segments(List.of(TrackSegment.of(points)))
130 .build()
131 );
132 }
133
134 /**
135 * Return a new {@code GPX} object with all <i>empty</i> elements (tracks,
136 * track-segments, routes and metadata) removed. This method can be used
137 * to clean up the GPX object before writing it to file.
138 * <pre>{@code
139 * final GPX gpx = ...;
140 * final GPX.write(Filters.nonEmptyGPX(gpx), "tracks.gpx", " ");
141 * }</pre>
142 *
143 * @param gpx the GPX object to clean up
144 * @return a new {@code GPX} object with all <i>empty</i> elements removed
145 * @throws NullPointerException if the given {@code gpx} object is
146 * {@code null}
147 */
148 public static GPX nonEmptyGPX(final GPX gpx) {
149 return gpx.toBuilder()
150 .routeFilter()
151 .listMap(Filters::nonEmptyRoutes)
152 .build()
153 .trackFilter()
154 .listMap(Filters::nonEmptyTracks)
155 .build()
156 .metadata(gpx.getMetadata()
157 .filter(Metadata::nonEmpty)
158 .orElse(null))
159 .build();
160 }
161
162 /**
163 * Return a new route list with all <i>empty</i> routes removed.
164 *
165 * @param routes the route list to clean up
166 * @return a new route list with all <i>empty</i> routes removed
167 * @throws NullPointerException if the given argument is {@code null}
168 */
169 public static List<Route> nonEmptyRoutes(final List<Route> routes) {
170 return routes.stream()
171 .filter(Route::nonEmpty)
172 .toList();
173 }
174
175 /**
176 * Return a new track list with all <i>empty</i> tracks removed.
177 *
178 * @param tracks the track list to clean up
179 * @return a new track list with all <i>empty</i> tracks removed
180 * @throws NullPointerException if the given argument is {@code null}
181 */
182 public static List<Track> nonEmptyTracks(final List<Track> tracks) {
183 return tracks.stream()
184 .map(track -> track.toBuilder()
185 .listMap(Filters::nonEmptySegments)
186 .build())
187 .filter(Track::nonEmpty)
188 .toList();
189 }
190
191 /**
192 * Return a new segment list with all <i>empty</i> segments removed.
193 *
194 * @param segments the segment list to clean up
195 * @return a new segment list with all <i>empty</i> segments removed
196 * @throws NullPointerException if the given argument is {@code null}
197 */
198 public static List<TrackSegment> nonEmptySegments(
199 final List<TrackSegment> segments
200 ) {
201 return segments.stream()
202 .filter(TrackSegment::nonEmpty)
203 .toList();
204 }
205
206 static List<Track> splitByDay(final Track track) {
207 return splitWayPointsByDay(
208 track.segments()
209 .flatMap(TrackSegment::points))
210 .stream()
211 .map(TrackSegment::of)
212 .map(segment -> Track.builder().addSegment(segment).build())
213 .toList();
214 }
215
216 private static List<List<WayPoint>> splitWayPointsByDay(
217 final Stream<WayPoint> points
218 ) {
219 final Map<LocalDate, List<WayPoint>> parts = points
220 .collect(groupingBy(wp -> wp.getTime()
221 .map(i -> LocalDate.ofInstant(i, UTC))
222 .orElse(LocalDate.MIN)));
223
224 return parts.entrySet().stream()
225 .sorted(Entry.comparingByKey())
226 .map(Entry::getValue)
227 .toList();
228 }
229
230 static List<TrackSegment> splitByDay(final TrackSegment segment) {
231 return splitWayPointsByDay(segment.points()).stream()
232 .map(TrackSegment::of)
233 .toList();
234 }
235
236 }
|