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}