001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.gpx;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.List;
009import java.util.Map;
010
011import org.openstreetmap.josm.data.Bounds;
012
013/**
014 * Immutable GPX track.
015 * @since 2907
016 */
017public class ImmutableGpxTrack extends WithAttributes implements GpxTrack {
018
019    private final List<GpxTrackSegment> segments;
020    private final double length;
021    private final Bounds bounds;
022
023    /**
024     * Constructs a new {@code ImmutableGpxTrack}.
025     * @param trackSegs track segments
026     * @param attributes track attributes
027     */
028    public ImmutableGpxTrack(Collection<Collection<WayPoint>> trackSegs, Map<String, Object> attributes) {
029        List<GpxTrackSegment> newSegments = new ArrayList<>();
030        for (Collection<WayPoint> trackSeg: trackSegs) {
031            if (trackSeg != null && !trackSeg.isEmpty()) {
032                newSegments.add(new ImmutableGpxTrackSegment(trackSeg));
033            }
034        }
035        this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
036        this.segments = Collections.unmodifiableList(newSegments);
037        this.length = calculateLength();
038        this.bounds = calculateBounds();
039    }
040
041    /**
042     * Constructs a new {@code ImmutableGpxTrack} from {@code GpxTrackSegment} objects.
043     * @param segments The segments to build the track from.  Input is not deep-copied,
044     *                 which means the caller may reuse the same segments to build
045     *                 multiple ImmutableGpxTrack instances from.  This should not be
046     *                 a problem, since this object cannot modify {@code this.segments}.
047     * @param attributes Attributes for the GpxTrack, the input map is copied.
048     * @since 13210
049     */
050    public ImmutableGpxTrack(List<GpxTrackSegment> segments, Map<String, Object> attributes) {
051        this.attr = Collections.unmodifiableMap(new HashMap<>(attributes));
052        this.segments = Collections.unmodifiableList(segments);
053        this.length = calculateLength();
054        this.bounds = calculateBounds();
055    }
056
057    private double calculateLength() {
058        double result = 0.0; // in meters
059
060        for (GpxTrackSegment trkseg : segments) {
061            result += trkseg.length();
062        }
063        return result;
064    }
065
066    private Bounds calculateBounds() {
067        Bounds result = null;
068        for (GpxTrackSegment segment: segments) {
069            Bounds segBounds = segment.getBounds();
070            if (segBounds != null) {
071                if (result == null) {
072                    result = new Bounds(segBounds);
073                } else {
074                    result.extend(segBounds);
075                }
076            }
077        }
078        return result;
079    }
080
081    @Override
082    public Map<String, Object> getAttributes() {
083        return attr;
084    }
085
086    @Override
087    public Bounds getBounds() {
088        return bounds == null ? null : new Bounds(bounds);
089    }
090
091    @Override
092    public double length() {
093        return length;
094    }
095
096    @Override
097    public Collection<GpxTrackSegment> getSegments() {
098        return segments;
099    }
100
101    @Override
102    public int hashCode() {
103        return 31 * super.hashCode() + ((segments == null) ? 0 : segments.hashCode());
104    }
105
106    @Override
107    public boolean equals(Object obj) {
108        if (this == obj)
109            return true;
110        if (!super.equals(obj))
111            return false;
112        if (getClass() != obj.getClass())
113            return false;
114        ImmutableGpxTrack other = (ImmutableGpxTrack) obj;
115        if (segments == null) {
116            if (other.segments != null)
117                return false;
118        } else if (!segments.equals(other.segments))
119            return false;
120        return true;
121    }
122}