io.jenetics.jpx.Track Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jpx Show documentation
Show all versions of jpx Show documentation
JPX - Java GPX (GPS) Library
/*
* Java GPX Library (jpx-1.1.2).
* Copyright (c) 2017-2017 Franz Wilhelmstötter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author:
* Franz Wilhelmstötter ([email protected])
*/
package io.jenetics.jpx;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static io.jenetics.jpx.Lists.copy;
import static io.jenetics.jpx.Lists.immutable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
/**
* Represents a GPX track - an ordered list of points describing a path.
*
* Creating a Track object with one track-segment and 3 track-points:
*
{@code
* final Track track = Track.builder()
* .name("Track 1")
* .description("Mountain bike tour.")
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
* .build();
* }
*
* @author Franz Wilhelmstötter
* @version 1.0
* @since 1.0
*/
public final class Track implements Iterable, Serializable {
private static final long serialVersionUID = 1L;
private final String _name;
private final String _comment;
private final String _description;
private final String _source;
private final List _links;
private final UInt _number;
private final String _type;
private final List _segments;
/**
* Create a new {@code Track} with the given parameters.
*
* @param name the GPS name of the track
* @param comment the GPS comment for the track
* @param description user description of the track
* @param source the source of data. Included to give user some idea of
* reliability and accuracy of data.
* @param links the links to external information about track
* @param number the GPS track number
* @param type the type (classification) of track
* @param segments the track-segments holds a list of track-points which are
* logically connected in order. To represent a single GPS track
* where GPS reception was lost, or the GPS receiver was turned off,
* start a new track-segment for each continuous span of track data.
*/
private Track(
final String name,
final String comment,
final String description,
final String source,
final List links,
final UInt number,
final String type,
final List segments
) {
_name = name;
_comment = comment;
_description = description;
_source = source;
_links = immutable(links);
_number = number;
_type = type;
_segments = immutable(segments);
}
/**
* Return the track name.
*
* @return the track name
*/
public Optional getName() {
return Optional.ofNullable(_name);
}
/**
* Return the GPS comment of the track.
*
* @return the GPS comment of the track
*/
public Optional getComment() {
return Optional.ofNullable(_comment);
}
/**
* Return the text description of the track.
*
* @return the text description of the track
*/
public Optional getDescription() {
return Optional.ofNullable(_description);
}
/**
* Return the source of data. Included to give user some idea of reliability
* and accuracy of data.
*
* @return the source of data
*/
public Optional getSource() {
return Optional.ofNullable(_source);
}
/**
* Return the links to external information about the track.
*
* @return the links to external information about the track
*/
public List getLinks() {
return _links;
}
/**
* Return the GPS track number.
*
* @return the GPS track number
*/
public Optional getNumber() {
return Optional.ofNullable(_number);
}
/**
* Return the type (classification) of the track.
*
* @return the type (classification) of the track
*/
public Optional getType() {
return Optional.ofNullable(_type);
}
/**
* Return the sequence of route points.
*
* @return the sequence of route points
*/
public List getSegments() {
return _segments;
}
/**
* Return a stream of {@link TrackSegment} objects this track contains.
*
* @return a stream of {@link TrackSegment} objects this track contains
*/
public Stream segments() {
return _segments.stream();
}
@Override
public Iterator iterator() {
return _segments.iterator();
}
/**
* Convert the immutable track object into a mutable
* builder initialized with the current track values.
*
* @since 1.1
*
* @return a new track builder initialized with the values of {@code this}
* track
*/
public Builder toBuilder() {
return builder()
.name(_name)
.cmt(_comment)
.desc(_description)
.src(_source)
.links(_links)
.number(_number)
.segments(_segments);
}
/**
* Return {@code true} if all track properties are {@code null} or empty.
*
* @return {@code true} if all track properties are {@code null} or empty
*/
public boolean isEmpty() {
return _name == null &&
_comment == null &&
_description == null &&
_source == null &&
_links.isEmpty() &&
_number == null &&
(_segments.isEmpty() ||
_segments.stream().allMatch(TrackSegment::isEmpty));
}
/**
* Return {@code true} if not all track properties are {@code null} or empty.
*
* @since 1.1
*
* @return {@code true} if not all track properties are {@code null} or empty
*/
public boolean nonEmpty() {
return !isEmpty();
}
@Override
public int hashCode() {
int hash = 31;
hash += 17*Objects.hashCode(_name) + 37;
hash += 17*Objects.hashCode(_comment) + 37;
hash += 17*Objects.hashCode(_description) + 37;
hash += 17*Objects.hashCode(_source) + 37;
hash += 17*_links.stream().mapToInt(Objects::hashCode).sum() + 37;
hash += 17*Objects.hashCode(_number) + 37;
hash += 17*Objects.hashCode(_segments) + 37;
return hash;
}
@Override
public boolean equals(final Object obj) {
return obj instanceof Track &&
Objects.equals(((Track)obj)._name, _name) &&
Objects.equals(((Track)obj)._comment, _comment) &&
Objects.equals(((Track)obj)._description, _description) &&
Objects.equals(((Track)obj)._source, _source) &&
((Track)obj)._links.size() == _links.size() &&
((Track)obj)._links.containsAll(_links) &&
Objects.equals(((Track)obj)._number, _number) &&
Objects.equals(((Track)obj)._segments, _segments);
}
@Override
public String toString() {
return format("Track[name=%s, segments=%s]", _name, _segments);
}
/**
* Builder class for creating immutable {@code Track} objects.
*
* Creating a Track object with one track-segment and 3 track-points:
*
{@code
* final Track track = Track.builder()
* .name("Track 1")
* .description("Mountain bike tour.")
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
* .build();
* }
*/
public static final class Builder implements Filter {
private String _name;
private String _comment;
private String _description;
private String _source;
private final List _links = new ArrayList<>();
private UInt _number;
private String _type;
private final List _segments = new ArrayList<>();
private Builder() {
}
/**
* Set the name of the track.
*
* @param name the track name
* @return {@code this} {@code Builder} for method chaining
*/
public Builder name(final String name) {
_name = name;
return this;
}
/**
* Return the current name value.
*
* @since 1.1
*
* @return the current name value
*/
public Optional name() {
return Optional.ofNullable(_name);
}
/**
* Set the comment of the track.
*
* @param comment the track comment
* @return {@code this} {@code Builder} for method chaining
*/
public Builder cmt(final String comment) {
_comment = comment;
return this;
}
public Optional cmt() {
return Optional.ofNullable(_comment);
}
/**
* Set the description of the track.
*
* @param description the track description
* @return {@code this} {@code Builder} for method chaining
*/
public Builder desc(final String description) {
_description = description;
return this;
}
/**
* Return the current description value.
*
* @since 1.1
*
* @return the current description value
*/
public Optional desc() {
return Optional.ofNullable(_description);
}
/**
* Set the source of the track.
*
* @param source the track source
* @return {@code this} {@code Builder} for method chaining
*/
public Builder src(final String source) {
_source = source;
return this;
}
/**
* Return the current source value.
*
* @since 1.1
*
* @return the current source value
*/
public Optional src() {
return Optional.ofNullable(_source);
}
/**
* Set the track links. The link list may be {@code null}.
*
* @param links the track links
* @return {@code this} {@code Builder} for method chaining
* @throws NullPointerException if one of the links in the list is
* {@code null}
*/
public Builder links(final List links) {
copy(links, _links);
return this;
}
/**
* Add the given {@code link} to the track
*
* @param link the link to add to the track
* @return {@code this} {@code Builder} for method chaining
*/
public Builder addLink(final Link link) {
_links.add(requireNonNull(link));
return this;
}
/**
* Add the given {@code link} to the track
*
* @param href the link to add to the track
* @return {@code this} {@code Builder} for method chaining
* @throws NullPointerException if the given {@code href} is {@code null}
* @throws IllegalArgumentException if the given {@code href} is not a
* valid URL
*/
public Builder addLink(final String href) {
return addLink(Link.of(href));
}
/**
* Return the current links. The returned link list is mutable.
*
* @since 1.1
*
* @return the current links
*/
public List links() {
return new NonNullList<>(_links);
}
/**
* Set the track number.
*
* @param number the track number
* @return {@code this} {@code Builder} for method chaining
*/
public Builder number(final UInt number) {
_number = number;
return this;
}
/**
* Set the track number.
*
* @param number the track number
* @return {@code this} {@code Builder} for method chaining
* @throws IllegalArgumentException if the given {@code value} is smaller
* than zero
*/
public Builder number(final int number) {
_number = UInt.of(number);
return this;
}
/**
* Return the current number value.
*
* @since 1.1
*
* @return the current number value
*/
public Optional number() {
return Optional.ofNullable(_number);
}
/**
* Set the track type.
*
* @param type the track type
* @return {@code this} {@code Builder} for method chaining
*/
public Builder type(final String type) {
_type = type;
return this;
}
/**
* Return the current type value.
*
* @since 1.1
*
* @return the current type value
*/
public Optional type() {
return Optional.ofNullable(_type);
}
/**
* Set the track segments of the track. The list may be {@code null}.
*
* @param segments the track segments
* @return {@code this} {@code Builder} for method chaining
* @throws NullPointerException if one of the segments in the list is
* {@code null}
*/
public Builder segments(final List segments) {
copy(segments, _segments);
return this;
}
/**
* Add a track segment to the track.
*
* @param segment the track segment added to the track
* @return {@code this} {@code Builder} for method chaining
* @throws NullPointerException if the given argument is {@code null}
*/
public Builder addSegment(final TrackSegment segment) {
_segments.add(requireNonNull(segment));
return this;
}
/**
* Add a track segment to the track, via the given builder.
* {@code
* final Track track = Track.builder()
* .name("Track 1")
* .description("Mountain bike tour.")
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(48.2081743).lon(16.3738189).ele(162))))
* .addSegment(segment -> segment
* .addPoint(p -> p.lat(46.2081743).lon(16.3738189).ele(160))
* .addPoint(p -> p.lat(47.2081743).lon(16.3738189).ele(161))
* .addPoint(p -> p.lat(49.2081743).lon(16.3738189).ele(162))))
* .build();
* }
*
* @param segment the track segment
* @return {@code this} {@code Builder} for method chaining
* @throws NullPointerException if the given argument is {@code null}
*/
public Builder addSegment(final Consumer segment) {
final TrackSegment.Builder builder = TrackSegment.builder();
segment.accept(builder);
return addSegment(builder.build());
}
/**
* Return the current track segments. The returned segment list is
* mutable.
*
* @since 1.1
*
* @return the current track segments
*/
public List segments() {
return new NonNullList<>(_segments);
}
@Override
public Builder filter(final Predicate super TrackSegment> predicate) {
segments(
_segments.stream()
.filter(predicate)
.collect(Collectors.toList())
);
return this;
}
@Override
public Builder map(
final Function super TrackSegment, ? extends TrackSegment> mapper
) {
segments(
_segments.stream()
.map(mapper)
.collect(Collectors.toList())
);
return this;
}
@Override
public Builder flatMap(
final Function<
? super TrackSegment,
? extends List> mapper
) {
segments(
_segments.stream()
.flatMap(segment -> mapper.apply(segment).stream())
.collect(Collectors.toList())
);
return this;
}
@Override
public Builder listMap(
final Function<
? super List,
? extends List> mapper
) {
segments(mapper.apply(_segments));
return this;
}
/**
* Create a new GPX track from the current builder state.
*
* @return a new GPX track from the current builder state
*/
@Override
public Track build() {
return new Track(
_name,
_comment,
_description,
_source,
_links,
_number,
_type,
_segments
);
}
}
public static Builder builder() {
return new Builder();
}
/* *************************************************************************
* Static object creation methods
* ************************************************************************/
/**
* Create a new {@code Track} with the given parameters.
*
* @param name the GPS name of the track
* @param comment the GPS comment for the track
* @param description user description of the track
* @param source the source of data. Included to give user some idea of
* reliability and accuracy of data.
* @param links the links to external information about track
* @param number the GPS track number
* @param type the type (classification) of track
* @param segments the track-segments holds a list of track-points which are
* logically connected in order. To represent a single GPS track
* where GPS reception was lost, or the GPS receiver was turned off,
* start a new track-segment for each continuous span of track data.
* @return a new {@code Track} with the given parameters
* @throws NullPointerException if the {@code links} or the {@code segments}
* sequence is {@code null}
*/
public static Track of(
final String name,
final String comment,
final String description,
final String source,
final List links,
final UInt number,
final String type,
final List segments
) {
return new Track(
name,
comment,
description,
source,
links,
number,
type,
segments
);
}
/* *************************************************************************
* XML stream object serialization
* ************************************************************************/
/**
* Writes this {@code Link} object to the given XML stream {@code writer}.
*
* @param writer the XML data sink
* @throws XMLStreamException if an error occurs
*/
void write(final XMLStreamWriter writer) throws XMLStreamException {
final XMLWriter xml = new XMLWriter(writer);
xml.write("trk",
xml.elem("name", _name),
xml.elem("cmt", _comment),
xml.elem("desc", _description),
xml.elem("src", _source),
xml.elems(_links, Link::write),
xml.elem("number", _number),
xml.elem("type", _type),
xml.elems(_segments, TrackSegment::write)
);
}
@SuppressWarnings("unchecked")
static XMLReader