// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.elevation.gpx; import java.util.ArrayList; import java.util.List; import org.openstreetmap.josm.data.gpx.GpxData; import org.openstreetmap.josm.data.gpx.GpxRoute; import org.openstreetmap.josm.data.gpx.GpxTrack; import org.openstreetmap.josm.data.gpx.GpxTrackSegment; import org.openstreetmap.josm.data.gpx.IWithAttributes; import org.openstreetmap.josm.data.gpx.WayPoint; import org.openstreetmap.josm.plugins.elevation.IElevationModel; import org.openstreetmap.josm.plugins.elevation.IElevationModelListener; import org.openstreetmap.josm.plugins.elevation.IElevationProfile; import org.openstreetmap.josm.tools.CheckParameterUtil; /** * Represents the top-level part of the elevation model. The elevation model * breaks done into the tracks/routes of a GPX file. * * @author Oliver Wieland <oliver.wieland@online.de> * @see IElevationModelTrackListener */ public class ElevationModel implements IGpxVisitor, IElevationModel { // private int sliceSize; private int trackCounter; private final GpxData gpxData; private final String name; private final WayPointMap profiles = new WayPointMap(); private final List<IElevationModelListener> listeners = new ArrayList<>(); private final List<WayPoint> buffer = new ArrayList<>(); private int currentProfileIndex = 0; private ElevationProfile curProfile = null; /** * Instantiates a new elevation model. */ public ElevationModel() { this("", null); } /** * Instantiates a new elevation model. * * @param name the name of the model * @param data the GPX data */ public ElevationModel(String name, GpxData data) { gpxData = data; this.name = name; GpxIterator.visit(data, this); } /** * Gets the GPX data instance used by this model. */ public GpxData getGpxData() { return gpxData; } /** * @return the tracks */ protected WayPointMap getTracks() { return profiles; } /** * Fires the 'model changed' event to all listeners. */ protected void fireModelChanged() { for (IElevationModelListener listener : listeners) { if (profiles != null && profiles.size() > 0) listener.elevationProfileChanged(getCurrentProfile()); } } @Override public void addModelListener(IElevationModelListener listener) { this.listeners.add(listener); } @Override public void removeModelListener(IElevationModelListener listener) { this.listeners.remove(listener); } @Override public void removeAllListeners() { this.listeners.clear(); } @Override public List<IElevationProfile> getProfiles() { return profiles; } @Override public IElevationProfile getCurrentProfile() { if (currentProfileIndex < 0 || currentProfileIndex >= profileCount()) return null; return profiles.get(currentProfileIndex); } @Override public void setCurrentProfile(IElevationProfile newProfile) { CheckParameterUtil.ensureParameterNotNull(newProfile); if (!profiles.contains(newProfile)) { profiles.add(newProfile); } setCurrentProfile(profiles.indexOf(newProfile)); } @Override public void setCurrentProfile(int index) { if (index < 0 || index >= profileCount()) throw new RuntimeException("Invalid arg for setCurrentProfile: " + index + ", value must be 0.." + profileCount()); currentProfileIndex = index; fireModelChanged(); } @Override public int profileCount() { return profiles != null ? profiles.size() : 0; } // Visitor stuff starts here... @Override public void beginWayPoints() { // we ignore single way points (elevation profile is quite meaningless...) } @Override public void endWayPoints() { // we ignore single way points (elevation profile is quite meaningless...) } @Override public void visitWayPoint(WayPoint wp) { // we ignore single way points (elevation profile is quite meaningless...) } @Override public void beginTrack(GpxTrack track) { createProfile(track); } @Override public void endTrack(GpxTrack track) { if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile"); curProfile.setDistance(track.length()); commitProfile(); } @Override public void beginTrackSegment(GpxTrack track, GpxTrackSegment segment) { // Nothing to do here for now } @Override public void endTrackSegment(GpxTrack track, GpxTrackSegment segment) { // Nothing to do here for now } @Override public void visitTrackPoint(WayPoint wp, GpxTrack track, GpxTrackSegment segment) { processWayPoint(wp); } @Override public void beginRoute(GpxRoute route) { createProfile(route); } @Override public void endRoute(GpxRoute route) { if (curProfile == null) throw new RuntimeException("Internal error: No elevation profile"); // a GpxRoute has no 'length' property curProfile.setDistance(0); commitProfile(); } @Override public void visitRoutePoint(WayPoint wp, GpxRoute route) { processWayPoint(wp); } /** * Creates a new profile. * * @param trackOrRoute the track or route */ private void createProfile(IWithAttributes trackOrRoute) { // check GPX data String trackName = (String) trackOrRoute.get("name"); if (trackName == null) { trackName = (String) trackOrRoute.get(GpxData.META_NAME); if (trackName == null) { // no name given, build artificial one trackName = name + "." + trackCounter; } } curProfile = new ElevationProfile(trackName); } /** * Adds a track or route to the internal track list. * * @param trackName the track name */ private void commitProfile() { if (buffer.size() > 0) { // assign way points to profile... curProfile.setWayPoints(buffer); // ... and add to profile list profiles.add(curProfile); buffer.clear(); } } /** * Adds the given way point to the current buffer. * * @param wp the wp */ private void processWayPoint(WayPoint wp) { if (wp == null) { throw new RuntimeException("WPT must not be null!"); } buffer.add(wp); } }