Java tutorial
/******************************************************************************* * Copyright (C) 2005, 2010 Wolfgang Schramm and Contributors * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA *******************************************************************************/ package net.tourbook.data; import static javax.persistence.CascadeType.ALL; import static javax.persistence.FetchType.EAGER; import java.awt.Point; import java.io.PrintStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.PostLoad; import javax.persistence.PostUpdate; import javax.persistence.Transient; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import net.tourbook.Messages; import net.tourbook.application.TourbookPlugin; import net.tourbook.chart.ChartLabel; import net.tourbook.database.FIELD_VALIDATION; import net.tourbook.database.TourDatabase; import net.tourbook.importdata.TourbookDevice; import net.tourbook.preferences.ITourbookPreferences; import net.tourbook.preferences.PrefPageComputedValues; import net.tourbook.srtm.ElevationSRTM3; import net.tourbook.srtm.GeoLat; import net.tourbook.srtm.GeoLon; import net.tourbook.srtm.NumberForm; import net.tourbook.tour.BreakTimeResult; import net.tourbook.tour.BreakTimeTool; import net.tourbook.ui.UI; import net.tourbook.ui.tourChart.ChartLayer2ndAltiSerie; import net.tourbook.ui.views.tourDataEditor.TourDataEditorView; import net.tourbook.util.Util; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.hibernate.annotations.Cascade; import org.joda.time.DateTime; /** * Tour data contains all data for a tour (except markers), an entity will be saved in the database */ @Entity @XmlType(name = "TourData") @XmlRootElement(name = "TourData") @XmlAccessorType(XmlAccessType.NONE) public class TourData implements Comparable<Object>, IXmlSerializable { public static final int DB_LENGTH_DEVICE_TOUR_TYPE = 2; public static final int DB_LENGTH_DEVICE_PLUGIN_ID = 255; public static final int DB_LENGTH_DEVICE_PLUGIN_NAME = 255; public static final int DB_LENGTH_DEVICE_MODE_NAME = 255; public static final int DB_LENGTH_DEVICE_FIRMWARE_VERSION = 255; public static final int DB_LENGTH_TOUR_TITLE = 255; public static final int DB_LENGTH_TOUR_DESCRIPTION = 4096; public static final int DB_LENGTH_TOUR_DESCRIPTION_V10 = 32000; public static final int DB_LENGTH_TOUR_START_PLACE = 255; public static final int DB_LENGTH_TOUR_END_PLACE = 255; public static final int DB_LENGTH_TOUR_IMPORT_FILE_PATH = 255; public static final int DB_LENGTH_WEATHER = 1000; public static final int DB_LENGTH_WEATHER_CLOUDS = 255; /** * */ public static final int MIN_TIMEINTERVAL_FOR_MAX_SPEED = 20; public static final float MAX_BIKE_SPEED = 120f; /** * Device Id for manually created tours */ public static final String DEVICE_ID_FOR_MANUAL_TOUR = "manual"; //$NON-NLS-1$ /** * Device id for csv files which behave like manually created tours, marker and timeslices are * disabled because they are not available, tour duration can be edited<br> * this is the id of the deviceDataReader */ public static final String DEVICE_ID_CSV_TOUR_DATA_READER = "net.tourbook.device.CSVTourDataReader"; //$NON-NLS-1$ /** * THIS IS NOT UNUSED !!!<br> * <br> * initialize SRTM */ @SuppressWarnings("unused") @Transient private static final NumberForm srtmNumberForm = new NumberForm(); @Transient private static final ElevationSRTM3 elevationSRTM3 = new ElevationSRTM3(); @Transient private static IPreferenceStore _prefStore = TourbookPlugin.getDefault().getPreferenceStore(); @Transient private final Calendar _calendar = GregorianCalendar.getInstance(); /** * Unique entity id which identifies the tour */ @Id private Long tourId; // ############################################# DATE ############################################# /** * year of tour start */ private short startYear; /** * mm (d) month of tour */ private short startMonth; /** * dd (d) day of tour */ private short startDay; /** * HH (d) hour of tour */ private short startHour; /** * MM (d) minute of tour */ private short startMinute; /** * altitude difference for the merged tour */ private int startSecond; // db-version 7 /** * THIS IS NOT UNUSED !!!<br> * <br> * week of the tour provided by {@link Calendar#get(int)} */ @SuppressWarnings("unused") private short startWeek; /** * THIS IS NOT UNUSED !!!<br> * <br> * this field can be read with sql statements <br> * year for startWeek */ @SuppressWarnings("unused") private short startWeekYear; // ############################################# TIME ############################################# /** * Total recording time in seconds */ @XmlElement private int tourRecordingTime; /** * Total driving/moving time in seconds */ @XmlElement private int tourDrivingTime; // ############################################# DISTANCE ############################################# /** * Total distance of the device at tour start (km) tttt (h). Distance for the tour is stored in * the field {@link #tourDistance} */ private int startDistance; /** * total distance of the tour in meters (metric system), this value is computed from the * distance data serie */ @XmlElement private int tourDistance; /** * A flag indicating that the distance of this series is defined by a distance sensor and not * from the GPS device.<br> * <br> * 0 == false <i>(default)</i> <br> * 1 == true */ private short isDistanceFromSensor = 0; // db-version 8 // ############################################# ALTITUDE ############################################# /** * aaaa (h) initial altitude (m) */ private short startAltitude; /** * altitude up (m) */ @XmlElement private int tourAltUp; /** * altitude down (m) */ @XmlElement private int tourAltDown; // ############################################# PULSE/WEIGHT/POWER ############################################# /** * pppp (h) initial pulse (bpm) */ private short startPulse; @XmlElement private int restPulse; // db-version 8 @XmlElement private Integer calories; // db-version 4 private float bikerWeight; // db-version 4 /** * A flag indicating that the pulse is from a sensor. This is the state of the device which is * not related to the availability of pulse data. Pulse data should be available but is not * checked.<br> * <br> * 0 == false, 1 == true */ private int isPulseSensorPresent = 0; // db-version 12 /** * A flag indicating that the power is from a sensor. This is the state of the device which is * not related to the availability of power data. Power data should be available but is not * checked.<br> * <br> * 0 == false, 1 == true */ private int isPowerSensorPresent = 0; // db-version 12 // ############################################# DEVICE TOUR TYPE ############################################# /** * tt (h) type of tour <br> * "2E" bike2 (CM414M) <br> * "3E" bike1 (CM414M) <br> * "81" jogging <br> * "91" ski <br> * "A1" bike<br> * "B1" ski-bike */ @Column(length = 2) private String deviceTourType; /** * Profile id which is defined by the device */ private short deviceMode; // db-version 3 /** * Visible name for the used profile which is defined in {@link #deviceMode}, e.g. Jogging, * Running, Bike1, Bike2... */ private String deviceModeName; // db-version 4 // ############################################# MAX VALUES ############################################# /** * maximum altitude in metric system */ @XmlElement private int maxAltitude; // db-version 4 @XmlElement private int maxPulse; // db-version 4 /** * maximum speed in metric system */ @XmlElement private float maxSpeed; // db-version 4 // ############################################# AVERAGE VALUES ############################################# @XmlElement private int avgPulse; // db-version 4 @XmlElement private int avgCadence; // db-version 4 private int avgTemperature; // db-version 4 private int weatherWindDir; // db-version 8 private int weatherWindSpd; // db-version 8 private String weatherClouds; // db-version 8 private String weather; // db-version 13 private float deviceAvgSpeed; // db-version 12 // ############################################# OTHER TOUR/DEVICE DATA ############################################# @XmlElement private String tourTitle; // db-version 4 @XmlElement private String tourDescription; // db-version 4 @XmlElement private String tourStartPlace; // db-version 4 @XmlElement private String tourEndPlace; // db-version 4 /** * Date/Time when tour data was created. This value is set to the tour start date before db * version 11, otherwise the value is set when the tour is saved the first time. */ private long dateTimeCreated; // db-version 11 /** * Date/Time when tour data was modified, default value is 0 */ private long dateTimeModified; // db-version 11 /** * file path for the imported tour */ private String tourImportFilePath; // db-version 6 /** * tolerance for the Douglas Peucker algorithm */ private short dpTolerance = 50; /** * Time difference in seconds between 2 time slices or <code>-1</code> for GPS devices when the * time slices has variable time duration */ private short deviceTimeInterval = -1; // db-version 3 /** * Scaling factor for the temperature data serie, e.g. when set to 10 the temperature data serie * is multiplied by 10, default scaling is <code>1</code> */ private int temperatureScale = 1; // db-version 13 /** * Firmware version of the device */ private String deviceFirmwareVersion; // db-version 12 // ############################################# MERGED DATA ############################################# /** * when a tour is merged with another tour, {@link #mergeSourceTourId} contains the tour id of * the tour which is merged into this tour */ private Long mergeSourceTourId; // db-version 7 /** * when a tour is merged into another tour, {@link #mergeTargetTourId} contains the tour id of * the tour into which this tour is merged */ private Long mergeTargetTourId; // db-version 7 /** * positive or negative time offset in seconds for the merged tour */ private int mergedTourTimeOffset; // db-version 7 /** * altitude difference for the merged tour */ private int mergedAltitudeOffset; // db-version 7 // ############################################# PLUGIN DATA ############################################# /** * Unique plugin id for the device data reader which created this tour, this id is defined in * plugin.xml * <p> * a better name would be <i>pluginId</i> */ private String devicePluginId; /** * Visible name for the used {@link TourbookDevice}, this name is defined in plugin.xml * <p> * a better name would be <i>pluginName</i> */ private String devicePluginName; // db-version 4 // ############################################# CONCONI TEST ############################################# /** * Deflection point in the conconi test, this value is the index for the data serie on the * x-axis */ private int conconiDeflection; // ############################################# UNUSED FIELDS ############################################# /** * ssss distance msw * <p> * is not used any more since 6.12.2006 but it's necessary then it's a field in the database */ @SuppressWarnings("unused") private int distance; @SuppressWarnings("unused") private int deviceDistance; @SuppressWarnings("unused") private int deviceTotalUp; @SuppressWarnings("unused") private int deviceTotalDown; @SuppressWarnings("unused") private long deviceTravelTime; @SuppressWarnings("unused") private int deviceWheel; @SuppressWarnings("unused") private int deviceWeight; /** * data series for time, speed, altitude,... */ @Basic(optional = false) private SerieData serieData; // ############################################# ASSOCIATED ENTITIES ############################################# /** * Tour marker */ @OneToMany(fetch = FetchType.EAGER, cascade = ALL, mappedBy = "tourData") @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) @XmlElementWrapper(name = "TourMarkers") @XmlElement(name = "TourMarker") private Set<TourMarker> tourMarkers = new HashSet<TourMarker>(); /** * Contains the tour way points */ @OneToMany(fetch = FetchType.EAGER, cascade = ALL, mappedBy = "tourData") @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) private final Set<TourWayPoint> tourWayPoints = new HashSet<TourWayPoint>(); /** * Reference tours */ @OneToMany(fetch = FetchType.EAGER, cascade = ALL, mappedBy = "tourData") @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) private final Set<TourReference> tourReferences = new HashSet<TourReference>(); /** * Tags */ @ManyToMany(fetch = EAGER) @JoinTable(inverseJoinColumns = @JoinColumn(name = "tourTag_tagId", referencedColumnName = "tagId")) private Set<TourTag> tourTags = new HashSet<TourTag>(); /** * Category of the tour, e.g. bike, mountainbike, jogging, inlinescating */ @ManyToOne private TourType tourType; /** * Person which created this tour or <code>null</code> when the tour is not saved in the * database */ @ManyToOne private TourPerson tourPerson; /** * plugin id for the device which was used for this tour Bike used for this tour */ @ManyToOne private TourBike tourBike; /* * tourCategory is currently (version 1.6) not used but is defined in older databases, it is * disabled because the field is not available in the database table */ // @ManyToMany(fetch = FetchType.LAZY, mappedBy = "tourData") // private Set<TourCategory> tourCategory = new HashSet<TourCategory>(); // ############################################# TRANSIENT DATA ############################################# /** * Contains time in seconds relativ to the tour start which is defined in: {@link #startYear}, * {@link #startMonth}, {@link #startDay}, {@link #startHour}, {@link #startMinute} and * {@link #startSecond}. * <p> * The array {@link #timeSerie} is <code>null</code> for a manually created tour, it is * <b>always</b> set when tour is from a device or imported file. */ @Transient public int[] timeSerie; /** * contains the absolute distance in m (metric system) */ @Transient public int[] distanceSerie; /** * contains the absolute distance in miles/1000 (imperial system) */ @Transient private int[] distanceSerieImperial; /** * contains the absolute altitude in m (metric system) */ @Transient public int[] altitudeSerie; /** * contains the absolute altitude in feet (imperial system) */ @Transient private int[] altitudeSerieImperial; /** * SRTM altitude values, when <code>null</code> srtm data have not yet been attached, when * <code>length()==0</code> data are invalid. */ @Transient private int[] srtmSerie; @Transient private int[] srtmSerieImperial; @Transient public int[] cadenceSerie; @Transient public int[] pulseSerie; /** * Is <code>true</code> when the time slice is a break */ @Transient public boolean[] breakTimeSerie; /** * contains the temperature in the metric measurement system */ @Transient public int[] temperatureSerie; /** * contains the temperature in the imperial measurement system */ @Transient private int[] temperatureSerieImperial; /** * contains speed in km/h multiplied by 10 * <p> * the metric speed serie is required when computing the power even if the current measurement * system is imperial */ @Transient private int[] speedSerie; @Transient private int[] speedSerieImperial; /** * Is <code>true</code> when the data in {@link #speedSerie} are from the device and not * computed. Speed data are normally available from an ergometer and not from a bike computer */ @Transient private boolean isSpeedSerieFromDevice = false; /** * pace in min/km */ @Transient private int[] paceSerieMinute; /** * pace in sec/km */ @Transient private int[] paceSerieSeconds; /** * pace in min/mile */ @Transient private int[] paceSerieMinuteImperial; /** * pace in sec/mile */ @Transient private int[] paceSerieSecondsImperial; @Transient private int[] powerSerie; /** * Is <code>true</code> when the data in {@link #powerSerie} are from the device and not * computed. Power data source can be an ergometer or a power sensor */ @Transient private boolean isPowerSerieFromDevice = false; @Transient private int[] altimeterSerie; @Transient private int[] altimeterSerieImperial; @Transient public int[] gradientSerie; /* * computed data series */ @Transient public int[] tourCompareSerie; /* * GPS data */ @Transient public double[] latitudeSerie; @Transient public double[] longitudeSerie; /** * contains the bounds of the tour in latitude/longitude */ @Transient public Rectangle gpsBounds; /** * Index of the segmented data in the data series */ @Transient public int[] segmentSerieIndex; /** * oooo (o) DD-record // offset */ @Transient public int offsetDDRecord; /* * data for the tour segments */ @Transient public int[] segmentSerieTimeTotal; @Transient public int[] segmentSerieRecordingTime; @Transient public int[] segmentSerieDrivingTime; @Transient public int[] segmentSerieBreakTime; @Transient public int[] segmentSerieDistanceDiff; @Transient public int[] segmentSerieDistanceTotal; @Transient public int[] segmentSerieAltitudeDiff; @Transient public int[] segmentSerieComputedAltitudeDiff; @Transient public float[] segmentSerieAltitudeUpH; @Transient public int[] segmentSerieAltitudeDownH; @Transient public float[] segmentSerieSpeed; @Transient public float[] segmentSeriePace; @Transient public float[] segmentSeriePaceDiff; @Transient public float[] segmentSeriePower; @Transient public float[] segmentSerieGradient; @Transient public float[] segmentSeriePulse; @Transient public float[] segmentSerieCadence; /** * contains the filename from which the data are imported, when set to <code>null</code> the * data it not imported they are from the database */ @Transient public String importRawDataFile; /** * Latitude for the center position in the map or {@link Double#MIN_VALUE} when the position is * not set */ @Transient public double mapCenterPositionLatitude = Double.MIN_VALUE; /** * Longitude for the center position in the map or {@link Double#MIN_VALUE} when the position is * not set */ @Transient public double mapCenterPositionLongitude = Double.MIN_VALUE; /** * Zoomlevel in the map */ @Transient public int mapZoomLevel; @Transient public double mapMinLatitude; @Transient public double mapMaxLatitude; @Transient public double mapMinLongitude; @Transient public double mapMaxLongitude; /** * caches the world positions for the tour lat/long values for each zoom level */ @Transient private final Map<Integer, Point[]> _tourWorldPosition = new HashMap<Integer, Point[]>(); /** * caches the world positions for the way point lat/long values for each zoom level */ @Transient private final HashMap<Integer, HashMap<Integer, Point>> _twpWorldPosition = new HashMap<Integer, HashMap<Integer, Point>>(); /** * when a tour was deleted and is still visible in the raw data view, resaving the tour or * finding the tour in the entity manager causes lots of trouble with hibernate, therefor this * tour cannot be saved again, it must be reloaded from the file system */ @Transient public boolean isTourDeleted = false; /** * 2nd data serie, this is used in the {@link ChartLayer2ndAltiSerie} to display the merged tour * or the adjusted altitude */ @Transient public int[] dataSerie2ndAlti; /** * altitude difference between this tour and the merge tour with metric measurement */ @Transient public int[] dataSerieDiffTo2ndAlti; /** * contains the altitude serie which is adjusted */ @Transient public int[] dataSerieAdjustedAlti; /** * contains special data points */ @Transient public SplineData splineDataPoints; /** * Contains a spline data serie */ @Transient public float[] dataSerieSpline; /** * when a tour is not saved, the tour id is not defined, therefore the tour data are provided * from the import view when tours are merged to display the merge layer */ @Transient private TourData _mergeSourceTourData; @Transient private DateTime _dateTimeCreated; @Transient private DateTime _dateTimeModified; /** * Tour start time */ @Transient private DateTime _dateTimeStart; /** * Tour markers which are sorted by serie index */ @Transient private ArrayList<TourMarker> _sortedMarkers; public TourData() { } /** * Removed data series when the sum of all values is 0 */ public void cleanupDataSeries() { int sumAltitude = 0; int sumCadence = 0; int sumDistance = 0; int sumPulse = 0; int sumTemperature = 0; int sumPower = 0; int sumSpeed = 0; // get first valid latitude/longitude if ((latitudeSerie != null) && (longitudeSerie != null)) { for (int timeIndex = 0; timeIndex < timeSerie.length; timeIndex++) { if ((latitudeSerie[timeIndex] != Double.MIN_VALUE) && (longitudeSerie[timeIndex] != Double.MIN_VALUE)) { mapMinLatitude = mapMaxLatitude = latitudeSerie[timeIndex] + 90; mapMinLongitude = mapMaxLongitude = longitudeSerie[timeIndex] + 180; break; } } } double lastValidLatitude = mapMinLatitude - 90; double lastValidLongitude = mapMinLongitude - 180; boolean isLatitudeValid = false; for (int serieIndex = 0; serieIndex < timeSerie.length; serieIndex++) { if (altitudeSerie != null) { sumAltitude += altitudeSerie[serieIndex]; } if (cadenceSerie != null) { sumCadence += cadenceSerie[serieIndex]; } if (distanceSerie != null) { sumDistance += distanceSerie[serieIndex]; } if (pulseSerie != null) { sumPulse += pulseSerie[serieIndex]; } if (temperatureSerie != null) { final int temp = temperatureSerie[serieIndex]; if (temp == Integer.MIN_VALUE) { // remove invalid values which are set temporaritly temperatureSerie[serieIndex] = 0; } else { sumTemperature += temp < 0 ? -temp : temp; } } if (powerSerie != null) { sumPower += powerSerie[serieIndex]; } if (speedSerie != null) { sumSpeed += speedSerie[serieIndex]; } if ((latitudeSerie != null) && (longitudeSerie != null)) { final double latitude = latitudeSerie[serieIndex]; final double longitude = longitudeSerie[serieIndex]; if ((latitude == Double.MIN_VALUE) || (longitude == Double.MIN_VALUE)) { latitudeSerie[serieIndex] = lastValidLatitude; longitudeSerie[serieIndex] = lastValidLongitude; } else { latitudeSerie[serieIndex] = lastValidLatitude = latitude; longitudeSerie[serieIndex] = lastValidLongitude = longitude; } // optimized performance for Math.min/max final double lastValidLatAdjusted = lastValidLatitude + 90; final double lastValidLonAdjusted = lastValidLongitude + 180; mapMinLatitude = mapMinLatitude < lastValidLatAdjusted ? mapMinLatitude : lastValidLatAdjusted; mapMaxLatitude = mapMaxLatitude > lastValidLatAdjusted ? mapMaxLatitude : lastValidLatAdjusted; mapMinLongitude = mapMinLongitude < lastValidLonAdjusted ? mapMinLongitude : lastValidLonAdjusted; mapMaxLongitude = mapMaxLongitude > lastValidLonAdjusted ? mapMaxLongitude : lastValidLonAdjusted; /* * check if latitude is not 0, there was a bug until version 1.3.0 where latitude * and longitude has been saved with 0 values */ if ((isLatitudeValid == false) && (lastValidLatitude != 0)) { isLatitudeValid = true; } } } mapMinLatitude -= 90; mapMaxLatitude -= 90; mapMinLongitude -= 180; mapMaxLongitude -= 180; /* * remove data series when the summary of the values is 0, for temperature this can be a * problem but for a longer tour the temperature varies */ if (sumAltitude == 0) { altitudeSerie = null; } if (sumCadence == 0) { cadenceSerie = null; } if (sumDistance == 0) { distanceSerie = null; } if (sumPulse == 0) { pulseSerie = null; } if (sumTemperature == 0) { temperatureSerie = null; } if (sumPower == 0) { powerSerie = null; } if (sumSpeed == 0) { speedSerie = null; } if (powerSerie != null) { isPowerSerieFromDevice = true; } if (speedSerie != null) { isSpeedSerieFromDevice = true; } if (isLatitudeValid == false) { latitudeSerie = null; longitudeSerie = null; } } /** * clear imperial altitude series so the next time when it's needed it will be recomputed */ public void clearAltitudeSeries() { altitudeSerieImperial = null; // // srtmSerie = null; // srtmSerieImperial = null; } /** * clear computed data series so the next time when they are needed they will be recomputed */ public void clearComputedSeries() { if (isSpeedSerieFromDevice == false) { speedSerie = null; } if (isPowerSerieFromDevice == false) { powerSerie = null; } paceSerieMinute = null; paceSerieSeconds = null; paceSerieMinuteImperial = null; paceSerieSecondsImperial = null; altimeterSerie = null; gradientSerie = null; breakTimeSerie = null; speedSerieImperial = null; paceSerieMinuteImperial = null; altimeterSerieImperial = null; altitudeSerieImperial = null; srtmSerie = null; srtmSerieImperial = null; } /** * clears the cached world positions, this is necessary when the data serie have been modified */ public void clearWorldPositions() { _tourWorldPosition.clear(); } /* * Set default sort method (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(final Object obj) { if (obj instanceof TourData) { final TourData otherTourData = (TourData) obj; return startYear < otherTourData.startYear ? -1 : startYear == otherTourData.startYear ? startMonth < otherTourData.startMonth ? -1 : startMonth == otherTourData.startMonth ? startDay < otherTourData.startDay ? -1 : startDay == otherTourData.startDay ? startHour < otherTourData.startHour ? -1 : startHour == otherTourData.startHour ? startMinute < otherTourData.startMinute ? -1 : startSecond == otherTourData.startSecond // ? 0 : 1 // : 1 // : 1 // : 1 // : 1; } return 0; } public void computeAltimeterGradientSerie() { // check if needed data are available if (timeSerie == null || timeSerie.length < 2 || distanceSerie == null || altitudeSerie == null) { return; } // optimization: don't recreate the data series when they are available if ((altimeterSerie != null) && (altimeterSerieImperial != null) && (gradientSerie != null)) { return; } if (deviceTimeInterval == -1) { computeAltimeterGradientSerieWithVariableInterval(); } else { computeAltimeterGradientSerieWithFixedInterval(); } } /** * Computes the data serie for altimeters with the internal algorithm for a fix time interval */ private void computeAltimeterGradientSerieWithFixedInterval() { final int serieLength = timeSerie.length; final int dataSerieAltimeter[] = new int[serieLength]; final int dataSerieGradient[] = new int[serieLength]; int adjustIndexLow; int adjustmentIndexHigh; // if (prefStore.getBoolean(ITourbookPreferences.GRAPH_PROPERTY_IS_VALUE_COMPUTING)) { // // // use custom settings to compute altimeter and gradient // // final int computeTimeSlice = prefStore.getInt(ITourbookPreferences.GRAPH_PROPERTY_CUSTOM_VALUE_TIMESLICE); // final int slices = computeTimeSlice / deviceTimeInterval; // // final int slice2 = slices / 2; // adjustmentIndexHigh = (1 >= slice2) ? 1 : slice2; // adjustIndexLow = slice2; // // // round up // if (adjustIndexLow + adjustmentIndexHigh < slices) { // adjustmentIndexHigh++; // } // // } else { // use internal algorithm to compute altimeter and gradient if (deviceTimeInterval <= 2) { adjustIndexLow = 15; adjustmentIndexHigh = 15; } else if (deviceTimeInterval <= 5) { adjustIndexLow = 4; adjustmentIndexHigh = 4; } else if (deviceTimeInterval <= 10) { adjustIndexLow = 2; adjustmentIndexHigh = 3; } else { adjustIndexLow = 1; adjustmentIndexHigh = 2; } // } /* * compute values */ for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) { /* * adjust index to the array size, this is optimized to NOT use Math.min or Math.max */ final int serieLengthLow = serieLength - 1; final int indexLowTemp = serieIndex - adjustIndexLow; final int indexLowTempMax = ((0 >= indexLowTemp) ? 0 : indexLowTemp); final int indexLow = ((indexLowTempMax <= serieLengthLow) ? indexLowTempMax : serieLengthLow); final int indexHighTemp = serieIndex + adjustmentIndexHigh; final int indexHighTempMin = ((indexHighTemp <= serieLengthLow) ? indexHighTemp : serieLengthLow); final int indexHigh = ((0 >= indexHighTempMin) ? 0 : indexHighTempMin); final int distanceDiff = distanceSerie[indexHigh] - distanceSerie[indexLow]; final int altitudeDiff = altitudeSerie[indexHigh] - altitudeSerie[indexLow]; final float timeDiff = deviceTimeInterval * (indexHigh - indexLow); // keep altimeter data dataSerieAltimeter[serieIndex] = (int) (3600F * altitudeDiff / timeDiff / UI.UNIT_VALUE_ALTITUDE); // keep gradient data dataSerieGradient[serieIndex] = distanceDiff == 0 ? 0 : altitudeDiff * 1000 / distanceDiff; } if (UI.UNIT_VALUE_ALTITUDE != 1) { // set imperial system altimeterSerieImperial = dataSerieAltimeter; } else { // set metric system altimeterSerie = dataSerieAltimeter; } gradientSerie = dataSerieGradient; } /** * Computes the data serie for gradient and altimeters for a variable time interval */ private void computeAltimeterGradientSerieWithVariableInterval() { final int[] checkSpeedSerie = getSpeedSerie(); final int serieLength = timeSerie.length; final int serieLengthLast = serieLength - 1; final int dataSerieAltimeter[] = new int[serieLength]; final int dataSerieGradient[] = new int[serieLength]; // get minimum time/distance differences final int minTimeDiff = _prefStore.getInt(ITourbookPreferences.APP_DATA_SPEED_MIN_TIMESLICE_VALUE); // if (isCustomProperty) { // // use custom settings to compute altimeter and gradient // minTimeDiff = prefStore.getInt(ITourbookPreferences.GRAPH_PROPERTY_CUSTOM_VALUE_TIMESLICE); // } else { // // use internal algorithm to compute altimeter and gradient // minTimeDiff = 16; // } final int minDistanceDiff = minTimeDiff; final boolean checkPosition = (latitudeSerie != null) && (longitudeSerie != null); for (int serieIndex = 1; serieIndex < serieLength; serieIndex++) { if (checkSpeedSerie[serieIndex] == 0) { // continue when no speed is available // dataSerieAltimeter[serieIndex] = 2000; continue; } final int sliceTimeDiff = timeSerie[serieIndex] - timeSerie[serieIndex - 1]; // check if a lat and long diff is available if (checkPosition && (serieIndex > 0) && (serieIndex < serieLengthLast - 1)) { if (sliceTimeDiff > 10) { if ((latitudeSerie[serieIndex] == latitudeSerie[serieIndex - 1]) && (longitudeSerie[serieIndex] == longitudeSerie[serieIndex - 1])) { // dataSerieAltimeter[serieIndex] = 100; continue; } if (distanceSerie[serieIndex] == distanceSerie[serieIndex - 1]) { // dataSerieAltimeter[serieIndex] = 120; continue; } if (altitudeSerie[serieIndex] == altitudeSerie[serieIndex - 1]) { // dataSerieAltimeter[serieIndex] = 130; continue; } } } final int serieIndexPrev = serieIndex - 1; // adjust index to the array size int lowIndex = ((0 >= serieIndexPrev) ? 0 : serieIndexPrev); int highIndex = ((serieIndex <= serieLengthLast) ? serieIndex : serieLengthLast); int timeDiff = timeSerie[highIndex] - timeSerie[lowIndex]; int distanceDiff = distanceSerie[highIndex] - distanceSerie[lowIndex]; int altitudeDiff = altitudeSerie[highIndex] - altitudeSerie[lowIndex]; boolean toggleIndex = true; while ((timeDiff < minTimeDiff) || (distanceDiff < minDistanceDiff)) { // toggle between low and high index if (toggleIndex) { lowIndex--; } else { highIndex++; } toggleIndex = !toggleIndex; // check array scope if ((lowIndex < 0) || (highIndex >= serieLength)) { break; } timeDiff = timeSerie[highIndex] - timeSerie[lowIndex]; distanceDiff = distanceSerie[highIndex] - distanceSerie[lowIndex]; altitudeDiff = altitudeSerie[highIndex] - altitudeSerie[lowIndex]; } highIndex = (highIndex <= serieLengthLast) ? highIndex : serieLengthLast; lowIndex = (lowIndex >= 0) ? lowIndex : 0; /* * check if a time difference is available between 2 time data, this can happen in gps * data that lat+long is available but no time */ boolean isTimeValid = true; int prevTime = timeSerie[lowIndex]; for (int timeIndex = lowIndex + 1; timeIndex <= highIndex; timeIndex++) { final int currentTime = timeSerie[timeIndex]; if (prevTime == currentTime) { isTimeValid = false; break; } prevTime = currentTime; } if (isTimeValid) { if (timeDiff > 50 /* && isCustomProperty == false */) { // dataSerieAltimeter[serieIndex] = 300; continue; } // check if lat and long diff is available if (checkPosition && (lowIndex > 0) && (highIndex < serieLengthLast - 1)) { if (sliceTimeDiff > 10) { if ((latitudeSerie[lowIndex] == latitudeSerie[lowIndex - 1]) && (longitudeSerie[lowIndex] == longitudeSerie[lowIndex - 1])) { // dataSerieAltimeter[serieIndex] = 210; continue; } if ((latitudeSerie[highIndex] == latitudeSerie[highIndex + 1]) && (longitudeSerie[highIndex] == longitudeSerie[highIndex + 1])) { // dataSerieAltimeter[serieIndex] = 220; continue; } } } // compute altimeter if (timeDiff > 0) { final int altimeter = (int) (3600f * altitudeDiff / timeDiff / UI.UNIT_VALUE_ALTITUDE); dataSerieAltimeter[serieIndex] = altimeter; } else { // dataSerieAltimeter[serieIndex] = -100; } // compute gradient if (distanceDiff > 0) { final int gradient = altitudeDiff * 1000 / distanceDiff; dataSerieGradient[serieIndex] = gradient; } else { // dataSerieAltimeter[serieIndex] = -200; } } else { // dataSerieAltimeter[serieIndex] = -300; } } if (UI.UNIT_VALUE_ALTITUDE != 1) { // set imperial system altimeterSerieImperial = dataSerieAltimeter; } else { // set metric system altimeterSerie = dataSerieAltimeter; } gradientSerie = dataSerieGradient; } /** * Computes and sets the altitude up/down values into {@link TourData} * * @return Returns <code>true</code> when altitude was computed otherwise <code>false</code> */ public boolean computeAltitudeUpDown() { final int prefMinAltitude = _prefStore.getInt(PrefPageComputedValues.STATE_COMPUTED_VALUE_MIN_ALTITUDE); final AltitudeUpDown altiUpDown = computeAltitudeUpDownInternal(null, prefMinAltitude); if (altiUpDown == null) { return false; } setTourAltUp(altiUpDown.altitudeUp); setTourAltDown(altiUpDown.altitudeDown); return true; } public AltitudeUpDown computeAltitudeUpDown(final ArrayList<AltitudeUpDownSegment> segmentSerieIndexParameter, final int minAltiDiff) { return computeAltitudeUpDownInternal(segmentSerieIndexParameter, minAltiDiff); } /** * compute altitude up/down since version 9.08 * * @param segmentSerie * segments are created for each gradient alternation when segmentSerie is not * <code>null</code> * @param altitudeMinDiff * @return Returns <code>null</code> when altitude up/down cannot be computed */ private AltitudeUpDown computeAltitudeUpDownInternal(final ArrayList<AltitudeUpDownSegment> segmentSerie, final int altitudeMinDiff) { // check if data are available if ((altitudeSerie == null) || (timeSerie == null) || (timeSerie.length < 2)) { return null; } final boolean isCreateSegments = segmentSerie != null; int prevAltitude = 0; int prevSegmentAltitude = 0; int prevAltiDiff = 0; int angleAltiUp = 0; int angleAltiDown = 0; int segmentAltitudeMin = 0; int segmentAltitudeMax = 0; int altitudeUpTotal = 0; int altitudeDownTotal = 0; final int serieLength = timeSerie.length; int currentSegmentSerieIndex = 0; for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) { final int altitude = altitudeSerie[serieIndex]; int altiDiff = 0; if (serieIndex == 0) { // first data point if (isCreateSegments) { // create start for the first segment segmentSerie.add(new AltitudeUpDownSegment(currentSegmentSerieIndex, 0)); } segmentAltitudeMin = altitude; segmentAltitudeMax = altitude; prevSegmentAltitude = altitude; } else if (serieIndex == serieLength - 1) { // last data point // check if last segment is set if (serieIndex != currentSegmentSerieIndex) { // create end point for the last segment int segmentMinMaxDiff = segmentAltitudeMax - segmentAltitudeMin; segmentMinMaxDiff = altitude > prevSegmentAltitude ? segmentMinMaxDiff : -segmentMinMaxDiff; if (isCreateSegments) { segmentSerie.add(new AltitudeUpDownSegment(serieIndex, segmentMinMaxDiff)); } if (segmentMinMaxDiff > 0) { altitudeUpTotal += segmentMinMaxDiff; } if (segmentMinMaxDiff < 0) { altitudeDownTotal += segmentMinMaxDiff; } } } else if (serieIndex > 0) { altiDiff = altitude - prevAltitude; if (altiDiff > 0) { // altitude is ascending /* * compares with equal 0 (== 0) to prevent initialization error, otherwise the * value is >0 or <0 */ if (prevAltiDiff >= 0) { // tour is ascending again angleAltiUp += altiDiff; segmentAltitudeMax = segmentAltitudeMax < altitude ? altitude : segmentAltitudeMax; } else if (prevAltiDiff < 0) { // angel changed, tour was descending and is now ascending if (angleAltiDown <= -altitudeMinDiff) { final int segmentAltiDiff = segmentAltitudeMin - segmentAltitudeMax; altitudeDownTotal += segmentAltiDiff; if (isCreateSegments) { // create segment point for the descending altitude currentSegmentSerieIndex = serieIndex - 1; segmentSerie.add(new AltitudeUpDownSegment(currentSegmentSerieIndex, // segmentAltiDiff)); } segmentAltitudeMin = prevAltitude; segmentAltitudeMax = prevAltitude + altiDiff; prevSegmentAltitude = prevAltitude; } angleAltiUp = altiDiff; angleAltiDown = 0; } } else if (altiDiff < 0) { // altitude is descending /* * compares to == 0 to prevent initialization error, otherwise the value is >0 * or <0 */ if (prevAltiDiff <= 0) { // tour is descending again angleAltiDown += altiDiff; segmentAltitudeMin = segmentAltitudeMin > altitude ? altitude : segmentAltitudeMin; } else if (prevAltiDiff > 0) { // angel changed, tour was ascending and is now descending if (angleAltiUp >= altitudeMinDiff) { final int segmentAltiDiff = segmentAltitudeMax - segmentAltitudeMin; altitudeUpTotal += segmentAltiDiff; // create segment if (isCreateSegments) { currentSegmentSerieIndex = serieIndex - 1; segmentSerie .add(new AltitudeUpDownSegment(currentSegmentSerieIndex, segmentAltiDiff)); } // initialize new segment segmentAltitudeMin = prevAltitude + altiDiff; segmentAltitudeMax = prevAltitude; prevSegmentAltitude = prevAltitude; } angleAltiUp = 0; angleAltiDown = altiDiff; } } } // prevent setting previous alti to 0 if (altiDiff != 0) { prevAltiDiff = altiDiff; } prevAltitude = altitude; } return new AltitudeUpDown(altitudeUpTotal, -altitudeDownTotal); } private void computeAvgCadence() { if (cadenceSerie == null) { return; } long cadenceSum = 0; int cadenceCount = 0; for (final int cadence : cadenceSerie) { if (cadence > 0) { cadenceCount++; cadenceSum += cadence; } } if (cadenceCount > 0) { avgCadence = (int) cadenceSum / cadenceCount; } } private void computeAvgPulse() { if ((pulseSerie == null) || (pulseSerie.length == 0) || (timeSerie == null) || (timeSerie.length == 0)) { return; } avgPulse = computeAvgPulseSegment(0, timeSerie.length - 1); } private int computeAvgPulseSegment(final int firstIndex, final int lastIndex) { // check if data are available if ((pulseSerie == null) || (pulseSerie.length == 0) || (timeSerie == null) || (timeSerie.length == 0)) { return 0; } // check for 1 point if (firstIndex == lastIndex) { return pulseSerie[firstIndex]; } // check for 2 points if (lastIndex - firstIndex == 1) { return (int) (((float) pulseSerie[firstIndex] + pulseSerie[lastIndex]) / 2 + 0.5f); } // at least 3 points are available int prevTime = timeSerie[firstIndex]; int currentTime = timeSerie[firstIndex]; int nextTime = timeSerie[firstIndex + 1]; float pulseSquare = 0; float timeSquare = 0; for (int serieIndex = firstIndex; serieIndex <= lastIndex; serieIndex++) { final float pulse = pulseSerie[serieIndex]; float timeDiffPrev = 0; float timeDiffNext = 0; if (serieIndex > firstIndex) { // prev is available timeDiffPrev = ((float) currentTime - prevTime) / 2; } if (serieIndex < lastIndex) { // next is available timeDiffNext = ((float) nextTime - currentTime) / 2; } if (pulse > 0) { pulseSquare += pulse * timeDiffPrev + pulse * timeDiffNext; timeSquare += timeDiffPrev + timeDiffNext; } if (serieIndex < lastIndex) { prevTime = currentTime; currentTime = nextTime; nextTime = timeSerie[serieIndex + 1]; } } return timeSquare == 0f ? 0 : (int) (pulseSquare / timeSquare + 0.5f); } private void computeAvgTemperature() { if (temperatureSerie == null) { return; } long temperatureSum = 0; int tempLength = temperatureSerie.length; for (final int temperature : temperatureSerie) { if (temperature == Integer.MIN_VALUE) { // ignore invalid values tempLength--; } else { temperatureSum += temperature; } } if (tempLength > 0) { avgTemperature = (int) temperatureSum / tempLength; } } private int computeBreakTimeVariable(final int startIndex, int endIndex, final BreakTimeTool btConfig) { endIndex = Math.min(endIndex, timeSerie.length - 1); int totalBreakTime = 0; if (breakTimeSerie != null) { // break time is already computed int prevTime = timeSerie[startIndex]; for (int serieIndex = startIndex + 1; serieIndex <= endIndex; serieIndex++) { final int currentTime = timeSerie[serieIndex]; final boolean isBreak = breakTimeSerie[serieIndex]; if (isBreak) { totalBreakTime += currentTime - prevTime; } prevTime = currentTime; } return totalBreakTime; } /* * compute break time according to the selected method */ BreakTimeResult breakTimeResult; if (btConfig.breakTimeMethod == BreakTimeTool.BREAK_TIME_METHOD_BY_TIME_DISTANCE) { breakTimeResult = BreakTimeTool.computeBreakTimeByTimeDistance(this, btConfig.breakShortestTime, btConfig.breakMaxDistance); breakTimeSerie = breakTimeResult.breakTimeSerie; return breakTimeResult.tourBreakTime; } else if (btConfig.breakTimeMethod == BreakTimeTool.BREAK_TIME_METHOD_BY_SLICE_SPEED) { breakTimeResult = BreakTimeTool.computeBreakTimeBySpeed(this, btConfig.breakTimeMethod, btConfig.breakMinSliceSpeed); breakTimeSerie = breakTimeResult.breakTimeSerie; return breakTimeResult.tourBreakTime; } else if (btConfig.breakTimeMethod == BreakTimeTool.BREAK_TIME_METHOD_BY_AVG_SPEED) { breakTimeResult = BreakTimeTool.computeBreakTimeBySpeed(this, btConfig.breakTimeMethod, btConfig.breakMinAvgSpeed); breakTimeSerie = breakTimeResult.breakTimeSerie; return breakTimeResult.tourBreakTime; } // this case should not occure !!! return totalBreakTime; } /** * compute maximum and average fields */ public void computeComputedValues() { computeMaxAltitude(); computeMaxPulse(); computeMaxSpeed(); computeAvgPulse(); computeAvgCadence(); computeAvgTemperature(); } private void computeMaxAltitude() { if (altitudeSerie == null) { return; } int maxAltitude = 0; for (final int altitude : altitudeSerie) { if (altitude > maxAltitude) { maxAltitude = altitude; } } this.maxAltitude = maxAltitude; } private void computeMaxPulse() { if (pulseSerie == null) { return; } int maxPulse = 0; for (final int pulse : pulseSerie) { if (pulse > maxPulse) { maxPulse = pulse; } } this.maxPulse = maxPulse; } private void computeMaxSpeed() { if (distanceSerie != null) { computeSpeedSerie(); } } /** * computes the speed data serie which can be retrieved with {@link TourData#getSpeedSerie()} */ public void computeSpeedSerie() { if ((speedSerie != null) && (speedSerieImperial != null) && (paceSerieMinute != null) && (paceSerieMinuteImperial != null)) { return; } if (isSpeedSerieFromDevice) { // speed is from the device computeSpeedSerieFromDevice(); } else { // speed is computed from distance and time if (deviceTimeInterval == -1) { computeSpeedSerieInternalWithVariableInterval(); } else { computeSpeedSerieInternalWithFixedInterval(); } } } /** * Computes the imperial speed data serie and max speed * * @return */ private void computeSpeedSerieFromDevice() { if (speedSerie == null) { return; } final int serieLength = speedSerie.length; speedSerieImperial = new int[serieLength]; for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) { /* * speed */ final int speedMetric = speedSerie[serieIndex]; speedSerieImperial[serieIndex] = ((int) (speedMetric / UI.UNIT_MILE)); maxSpeed = Math.max(maxSpeed, speedMetric); } maxSpeed /= 10; } /** * Computes the speed data serie with the internal algorithm for a fix time interval * * @return */ private void computeSpeedSerieInternalWithFixedInterval() { if (distanceSerie == null) { return; } final int serieLength = timeSerie.length; speedSerie = new int[serieLength]; speedSerieImperial = new int[serieLength]; paceSerieMinute = new int[serieLength]; paceSerieSeconds = new int[serieLength]; paceSerieMinuteImperial = new int[serieLength]; paceSerieSecondsImperial = new int[serieLength]; int lowIndexAdjustmentDefault = 0; int highIndexAdjustmentDefault = 0; if (deviceTimeInterval <= 2) { lowIndexAdjustmentDefault = 3; highIndexAdjustmentDefault = 3; } else if (deviceTimeInterval <= 5) { lowIndexAdjustmentDefault = 1; highIndexAdjustmentDefault = 1; } else if (deviceTimeInterval <= 10) { lowIndexAdjustmentDefault = 0; highIndexAdjustmentDefault = 1; } else { lowIndexAdjustmentDefault = 0; highIndexAdjustmentDefault = 1; } final int serieLengthLast = serieLength - 1; for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) { // adjust index to the array size final int serieIndexLow = serieIndex - lowIndexAdjustmentDefault; final int serieIndexLowMax = ((0 >= serieIndexLow) ? 0 : serieIndexLow); int distIndexLow = ((serieIndexLowMax <= serieLengthLast) ? serieIndexLowMax : serieLengthLast); final int serieIndexHigh = serieIndex + highIndexAdjustmentDefault; final int serieIndexHighMax = ((serieIndexHigh <= serieLengthLast) ? serieIndexHigh : serieLengthLast); int distIndexHigh = ((0 >= serieIndexHighMax) ? 0 : serieIndexHighMax); final int distanceDefault = distanceSerie[distIndexHigh] - distanceSerie[distIndexLow]; // adjust the accuracy for the distance int lowIndexAdjustment = lowIndexAdjustmentDefault; int highIndexAdjustment = highIndexAdjustmentDefault; if (distanceDefault < 30) { lowIndexAdjustment = lowIndexAdjustmentDefault + 3; highIndexAdjustment = highIndexAdjustmentDefault + 3; } else if (distanceDefault < 50) { lowIndexAdjustment = lowIndexAdjustmentDefault + 2; highIndexAdjustment = highIndexAdjustmentDefault + 2; } else if (distanceDefault < 100) { lowIndexAdjustment = lowIndexAdjustmentDefault + 1; highIndexAdjustment = highIndexAdjustmentDefault + 1; } // adjust index to the array size final int serieIndexLowAdjusted = serieIndex - lowIndexAdjustment; final int serieIndexLowAdjustedMax = ((0 >= serieIndexLowAdjusted) ? 0 : serieIndexLowAdjusted); distIndexLow = (serieIndexLowAdjustedMax <= serieLengthLast) ? serieIndexLowAdjustedMax : serieLengthLast; final int serieIndexHighAdjusted = serieIndex + highIndexAdjustment; final int serieIndexHighAdjustedMin = ((serieIndexHighAdjusted <= serieLengthLast) ? serieIndexHighAdjusted : serieLengthLast); distIndexHigh = (0 >= serieIndexHighAdjustedMin) ? 0 : serieIndexHighAdjustedMin; final int distDiff = distanceSerie[distIndexHigh] - distanceSerie[distIndexLow]; final float timeDiff = timeSerie[distIndexHigh] - timeSerie[distIndexLow]; /* * speed */ int speedMetric = 0; int speedImperial = 0; if (timeDiff != 0) { final float speed = (distDiff * 36F) / timeDiff; speedMetric = (int) (speed); speedImperial = (int) (speed / UI.UNIT_MILE); } speedSerie[serieIndex] = speedMetric; speedSerieImperial[serieIndex] = speedImperial; maxSpeed = Math.max(maxSpeed, speedMetric); /* * pace (computed with divisor 10) */ float paceMetricSeconds = 0; float paceImperialSeconds = 0; int paceMetricMinute = 0; int paceImperialMinute = 0; if ((speedMetric != 0) && (distDiff != 0)) { // final float pace = timeDiff * 166.66f / distDiff; // final float pace = 10 * (((float) timeDiff / 60) / ((float) distDiff / 1000)); // paceMetricSeconds = 10* timeDiff * 1000 / (float) distDiff; paceMetricSeconds = timeDiff * 10000 / distDiff; paceImperialSeconds = paceMetricSeconds * UI.UNIT_MILE; // paceMetricMinute = (int) ((paceMetricSeconds / 60)); paceMetricMinute = (int) ((paceMetricSeconds / 60)); paceImperialMinute = (int) ((paceImperialSeconds / 60)); } paceSerieMinute[serieIndex] = paceMetricMinute; paceSerieMinuteImperial[serieIndex] = paceImperialMinute; paceSerieSeconds[serieIndex] = (int) paceMetricSeconds / 10; paceSerieSecondsImperial[serieIndex] = (int) paceImperialSeconds / 10; } maxSpeed /= 10; } /** * compute the speed when the time serie has unequal time intervalls */ private void computeSpeedSerieInternalWithVariableInterval() { if (distanceSerie == null) { return; } final int minTimeDiff = _prefStore.getInt(ITourbookPreferences.APP_DATA_SPEED_MIN_TIMESLICE_VALUE); final int serieLength = timeSerie.length; final int lastSerieIndex = serieLength - 1; speedSerie = new int[serieLength]; speedSerieImperial = new int[serieLength]; paceSerieMinute = new int[serieLength]; paceSerieSeconds = new int[serieLength]; paceSerieMinuteImperial = new int[serieLength]; paceSerieSecondsImperial = new int[serieLength]; final boolean isCheckPosition = (latitudeSerie != null) && // (longitudeSerie != null) && (isDistanceFromSensor == 0); // --> distance is measured with the gps device and not from a sensor boolean isLatLongEqual = false; int equalStartIndex = 0; for (int serieIndex = 0; serieIndex < serieLength; serieIndex++) { final int prevSerieIndex = serieIndex - 1; // adjust index to the array size int lowIndex = ((0 >= prevSerieIndex) ? 0 : prevSerieIndex); int highIndex = ((serieIndex <= lastSerieIndex) ? serieIndex : lastSerieIndex); int timeDiff = timeSerie[highIndex] - timeSerie[lowIndex]; int distDiff = distanceSerie[highIndex] - distanceSerie[lowIndex]; // check if a lat and long diff is available if (isCheckPosition && (serieIndex > 0) && (serieIndex < lastSerieIndex - 1)) { if ((latitudeSerie[serieIndex] == latitudeSerie[prevSerieIndex]) && (longitudeSerie[serieIndex] == longitudeSerie[prevSerieIndex])) { if (isLatLongEqual == false) { equalStartIndex = prevSerieIndex; isLatLongEqual = true; } continue; } else if (isLatLongEqual) { /* * lat/long equality ended, compute distance for all datapoints which has the * same lat/long because this was not correctly computed from the device */ isLatLongEqual = false; final int equalTimeDiff = timeSerie[serieIndex] - timeSerie[equalStartIndex]; final int equalDistDiff = distanceSerie[serieIndex] - distanceSerie[equalStartIndex]; int speedMetric = 0; int speedImperial = 0; if ((equalTimeDiff > 20) && (equalDistDiff < 10)) { // speed must be greater than 1.8 km/h } else { for (int equalSerieIndex = equalStartIndex + 1; equalSerieIndex < serieIndex; equalSerieIndex++) { final int equalSegmentTimeDiff = timeSerie[equalSerieIndex] - timeSerie[equalSerieIndex - 1]; final int equalSegmentDistDiff = equalTimeDiff == 0 ? 0 : // (int) (((float) equalSegmentTimeDiff / equalTimeDiff) * equalDistDiff); distanceSerie[equalSerieIndex] = distanceSerie[equalSerieIndex - 1] + equalSegmentDistDiff; // compute speed for this segment if ((equalSegmentTimeDiff == 0) || (equalSegmentDistDiff == 0)) { speedMetric = 0; } else { speedMetric = (int) ((equalSegmentDistDiff * 36f) / equalSegmentTimeDiff); speedMetric = speedMetric < 0 ? 0 : speedMetric; speedImperial = (int) ((equalSegmentDistDiff * 36f) / (equalSegmentTimeDiff * UI.UNIT_MILE)); speedImperial = speedImperial < 0 ? 0 : speedImperial; } setSpeed(equalSerieIndex, speedMetric, speedImperial, equalSegmentTimeDiff, equalSegmentDistDiff); } } } } boolean swapIndexDirection = true; while (timeDiff < minTimeDiff) { // toggle between low and high index if (swapIndexDirection) { highIndex++; } else { lowIndex--; } swapIndexDirection = !swapIndexDirection; // check bounds if ((lowIndex < 0) || (highIndex >= serieLength)) { break; } timeDiff = timeSerie[highIndex] - timeSerie[lowIndex]; distDiff = distanceSerie[highIndex] - distanceSerie[lowIndex]; } /* * speed */ int speedMetric = 0; int speedImperial = 0; /* * check if a time difference is available between 2 time data, this can happen in gps * data that lat+long is available but no time */ highIndex = (highIndex <= lastSerieIndex) ? highIndex : lastSerieIndex; lowIndex = (lowIndex >= 0) ? lowIndex : 0; boolean isTimeValid = true; int prevTime = timeSerie[lowIndex]; for (int timeIndex = lowIndex + 1; timeIndex <= highIndex; timeIndex++) { final int currentTime = timeSerie[timeIndex]; if (prevTime == currentTime) { isTimeValid = false; break; } prevTime = currentTime; } if (isTimeValid && (serieIndex > 0) && (timeDiff != 0)) { // check if a lat and long diff is available if (isCheckPosition && (lowIndex > 0) && (highIndex < lastSerieIndex - 1)) { if ((latitudeSerie[lowIndex] == latitudeSerie[lowIndex - 1]) && (longitudeSerie[lowIndex] == longitudeSerie[lowIndex - 1])) { if (distDiff == 0) { continue; } } if ((longitudeSerie[highIndex] == longitudeSerie[highIndex + 1]) && (latitudeSerie[highIndex] == latitudeSerie[highIndex + 1])) { if (distDiff == 0) { continue; } } } if ((timeDiff > 20) && (distDiff < 10)) { // speed must be greater than 1.8 km/h speedMetric = 0; } else { speedMetric = (int) ((distDiff * 36f) / timeDiff); speedMetric = speedMetric < 0 ? 0 : speedMetric; speedImperial = (int) ((distDiff * 36f) / (timeDiff * UI.UNIT_MILE)); speedImperial = speedImperial < 0 ? 0 : speedImperial; } } setSpeed(serieIndex, speedMetric, speedImperial, timeDiff, distDiff); } maxSpeed /= 10; } /** * Computes the tour driving time in seconds, this is the tour recording time - tour break time. * This value is store in {@link #tourDrivingTime}. */ public void computeTourDrivingTime() { if (isManualTour()) { // manual tours do not have data series return; } if ((timeSerie == null) || (timeSerie.length == 0)) { tourDrivingTime = 0; } else { final int tourDrivingTimeRaw = timeSerie[timeSerie.length - 1] - getBreakTime(0, timeSerie.length); tourDrivingTime = Math.max(0, tourDrivingTimeRaw); } } private void createSRTMDataSerie() { BusyIndicator.showWhile(Display.getCurrent(), new Runnable() { @Override public void run() { int serieIndex = 0; short lastValidSRTM = 0; boolean isSRTMValid = false; final int serieLength = timeSerie.length; final int[] newSRTMSerie = new int[serieLength]; final int[] newSRTMSerieImperial = new int[serieLength]; for (final double latitude : latitudeSerie) { short srtmValue = elevationSRTM3.getElevation(new GeoLat(latitude), new GeoLon(longitudeSerie[serieIndex])); /* * set invalid values to the previous valid value */ if (srtmValue == Short.MIN_VALUE) { // invalid data srtmValue = lastValidSRTM; } else { // valid data are available isSRTMValid = true; lastValidSRTM = srtmValue; } // adjust wrong values if (srtmValue < -1000) { srtmValue = 0; } else if (srtmValue > 10000) { srtmValue = 10000; } newSRTMSerie[serieIndex] = srtmValue; newSRTMSerieImperial[serieIndex] = (int) (srtmValue / UI.UNIT_FOOT); serieIndex++; } if (isSRTMValid) { srtmSerie = newSRTMSerie; srtmSerieImperial = newSRTMSerieImperial; } else { // set state that srtm altitude is invalid srtmSerie = new int[0]; } } }); } /** * Convert {@link TimeData} into {@link TourData} this will be done after data are imported or * transfered. * <p> * The array {@link #timeSerie} is always created even when the time is not available. * * @param isCreateMarker * creates markers when <code>true</code> */ public void createTimeSeries(final ArrayList<TimeData> timeDataList, final boolean isCreateMarker) { final int serieLength = timeDataList.size(); if (serieLength == 0) { return; } final TimeData firstTimeDataItem = timeDataList.get(0); boolean isDistance = false; boolean isAltitude = false; boolean isPulse = false; boolean isCadence = false; boolean isTemperature = false; boolean isSpeed = false; boolean isPower = false; final boolean isAbsoluteData = firstTimeDataItem.absoluteTime != Long.MIN_VALUE; /* * a time serie is always available, except manually created tours */ timeSerie = new int[serieLength]; /* * create data series only when data are available */ if ((firstTimeDataItem.distance != Integer.MIN_VALUE) || isAbsoluteData) { distanceSerie = new int[serieLength]; isDistance = true; } /* * altitude serie */ if (isAbsoluteData) { if (firstTimeDataItem.absoluteAltitude == Integer.MIN_VALUE) { // search for first altitude value int firstAltitudeIndex = 0; for (final TimeData timeData : timeDataList) { if (timeData.absoluteAltitude != Integer.MIN_VALUE) { // altitude was found altitudeSerie = new int[serieLength]; isAltitude = true; // set altitude to the first available altitude value final int firstAltitudeValue = (int) (timeData.absoluteAltitude + 0.5); for (int valueIndex = 0; valueIndex < firstAltitudeIndex; valueIndex++) { altitudeSerie[valueIndex] = firstAltitudeValue; } break; } firstAltitudeIndex++; } } else { // altitude is available altitudeSerie = new int[serieLength]; isAltitude = true; } } else if (firstTimeDataItem.altitude != Integer.MIN_VALUE) { // altitude is available altitudeSerie = new int[serieLength]; isAltitude = true; } /* * pulse serie */ if (firstTimeDataItem.pulse == Integer.MIN_VALUE) { // search for first pulse value for (final TimeData timeData : timeDataList) { if (timeData.pulse != Integer.MIN_VALUE) { // pulse was found pulseSerie = new int[serieLength]; isPulse = true; break; } } } else { // pulse is available pulseSerie = new int[serieLength]; isPulse = true; } /* * cadence serie */ if (firstTimeDataItem.cadence == Integer.MIN_VALUE) { // search for first cadence value for (final TimeData timeData : timeDataList) { if (timeData.cadence != Integer.MIN_VALUE) { // cadence was found cadenceSerie = new int[serieLength]; isCadence = true; break; } } } else { // cadence is available cadenceSerie = new int[serieLength]; isCadence = true; } if (firstTimeDataItem.temperature != Integer.MIN_VALUE) { temperatureSerie = new int[serieLength]; isTemperature = true; } if (firstTimeDataItem.speed != Integer.MIN_VALUE) { speedSerie = new int[serieLength]; isSpeed = true; isSpeedSerieFromDevice = true; } if (firstTimeDataItem.power != Integer.MIN_VALUE) { powerSerie = new int[serieLength]; isPower = true; isPowerSerieFromDevice = true; } // check if GPS data are available boolean isGPS = false; if (firstTimeDataItem.latitude != Double.MIN_VALUE) { isGPS = true; } else { // check all data if lat/long is available for (final TimeData timeDataItem : timeDataList) { if (timeDataItem.latitude != Double.MIN_VALUE) { isGPS = true; break; } } } if (isGPS) { latitudeSerie = new double[serieLength]; longitudeSerie = new double[serieLength]; } int timeIndex = 0; long recordingTime = 0; // time in seconds relative to the tour start int altitudeAbsolute = 0; int distanceAbsolute = 0; if (isAbsoluteData) { /* * absolute data are available when data are from GPS devices */ long firstTime = 0; // index when altitude is available in the time data list int altitudeStartIndex = -1; int distanceDiff; int altitudeDiff; long lastValidTime = 0; /* * get first valid latitude */ double lastValidLatitude = firstTimeDataItem.latitude; double lastValidLongitude = firstTimeDataItem.longitude; // set initial min/max latitude/longitude if ((lastValidLatitude == Double.MIN_VALUE) || (lastValidLongitude == Double.MIN_VALUE)) { // find first valid latitude/longitude for (final TimeData timeSlice : timeDataList) { lastValidLatitude = timeSlice.latitude; lastValidLongitude = timeSlice.longitude; if ((lastValidLatitude != Double.MIN_VALUE) && (lastValidLongitude != Double.MIN_VALUE)) { mapMinLatitude = lastValidLatitude + 90; mapMaxLatitude = lastValidLatitude + 90; mapMinLongitude = lastValidLongitude + 180; mapMaxLongitude = lastValidLongitude + 180; break; } } } else { mapMinLatitude = lastValidLatitude + 90; mapMaxLatitude = lastValidLatitude + 90; mapMinLongitude = lastValidLongitude + 180; mapMaxLongitude = lastValidLongitude + 180; } // convert data from the tour format into interger[] arrays for (final TimeData timeData : timeDataList) { if ((altitudeStartIndex == -1) && isAltitude) { altitudeStartIndex = timeIndex; altitudeAbsolute = (int) (timeData.absoluteAltitude + 0.5); } final long absoluteTime = timeData.absoluteTime; if (timeIndex == 0) { // first trackpoint /* * time */ timeSerie[timeIndex] = 0; if (absoluteTime == Long.MIN_VALUE) { firstTime = 0; } else { firstTime = absoluteTime; } recordingTime = 0; lastValidTime = (int) firstTime; /* * distance */ final float tdDistance = timeData.absoluteDistance; if ((tdDistance == Float.MIN_VALUE) || (tdDistance >= Integer.MAX_VALUE)) { distanceDiff = 0; } else { distanceDiff = (int) tdDistance; } distanceSerie[timeIndex] = distanceAbsolute += distanceDiff < 0 ? 0 : distanceDiff; /* * altitude */ if (isAltitude) { altitudeSerie[timeIndex] = altitudeAbsolute; } } else { // 1..n trackpoint /* * time */ if (absoluteTime == Long.MIN_VALUE) { recordingTime = lastValidTime; } else { recordingTime = (absoluteTime - firstTime) / 1000; } timeSerie[timeIndex] = (int) (lastValidTime = recordingTime); /* * distance */ final float tdDistance = timeData.absoluteDistance; if ((tdDistance == Float.MIN_VALUE) || (tdDistance >= Integer.MAX_VALUE)) { // ensure to have correct data distanceDiff = 0; } else { /* * Math.round() cannot be used because the tour id contains the last * distance serie value, Math.round() creates another tour id */ distanceDiff = (int) tdDistance - distanceAbsolute; } distanceSerie[timeIndex] = distanceAbsolute += distanceDiff < 0 ? 0 : distanceDiff; /* * altitude */ if (isAltitude) { if (altitudeStartIndex == -1) { altitudeDiff = 0; } else { final float tdAltitude = timeData.absoluteAltitude; if ((tdAltitude == Float.MIN_VALUE) || (tdAltitude >= Integer.MAX_VALUE)) { altitudeDiff = 0; } else { altitudeDiff = (int) (tdAltitude - altitudeAbsolute); } } altitudeSerie[timeIndex] = altitudeAbsolute += altitudeDiff; } } /* * latitude & longitude */ final double latitude = timeData.latitude; final double longitude = timeData.longitude; if ((latitudeSerie != null) && (longitudeSerie != null)) { if ((latitude == Double.MIN_VALUE) || (longitude == Double.MIN_VALUE)) { latitudeSerie[timeIndex] = lastValidLatitude; longitudeSerie[timeIndex] = lastValidLongitude; } else { latitudeSerie[timeIndex] = lastValidLatitude = latitude; longitudeSerie[timeIndex] = lastValidLongitude = longitude; } final double lastValidLatitude90 = lastValidLatitude + 90; mapMinLatitude = Math.min(mapMinLatitude, lastValidLatitude90); mapMaxLatitude = Math.max(mapMaxLatitude, lastValidLatitude90); final double lastValidLongitude180 = lastValidLongitude + 180; mapMinLongitude = Math.min(mapMinLongitude, lastValidLongitude180); mapMaxLongitude = Math.max(mapMaxLongitude, lastValidLongitude180); } /* * pulse */ if (isPulse) { final int tdPulse = timeData.pulse; pulseSerie[timeIndex] = (tdPulse == Integer.MIN_VALUE) || (tdPulse == Integer.MAX_VALUE) ? 0 : tdPulse; } /* * cadence */ if (isCadence) { final int tdCadence = timeData.cadence; cadenceSerie[timeIndex] = (tdCadence == Integer.MIN_VALUE) || (tdCadence == Integer.MAX_VALUE) ? 0 : tdCadence; } /* * temperature */ if (isTemperature) { final int tdTemperature = timeData.temperature; // if (tdTemperature == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdTemperature is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } temperatureSerie[timeIndex] = tdTemperature == Integer.MIN_VALUE ? 0 : tdTemperature; } /* * marker */ if (isCreateMarker && (timeData.marker != 0)) { createTourMarker(timeData, timeIndex, recordingTime, distanceAbsolute); } // speed if (isSpeed) { final int tdSpeed = timeData.speed; speedSerie[timeIndex] = tdSpeed == Integer.MIN_VALUE ? 0 : tdSpeed; } timeIndex++; } mapMinLatitude -= 90; mapMaxLatitude -= 90; mapMinLongitude -= 180; mapMaxLongitude -= 180; } else { /* * relativ data is available, these data are from non GPS devices */ // convert data from the tour format into an interger[] for (final TimeData timeData : timeDataList) { final int tdTime = timeData.time; timeSerie[timeIndex] = (int) (recordingTime += tdTime == Integer.MIN_VALUE ? 0 : tdTime); if (isDistance) { final int tdDistance = timeData.distance; // if (tdDistance == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdDistance is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } distanceSerie[timeIndex] = distanceAbsolute += tdDistance == Integer.MIN_VALUE ? 0 : tdDistance; } if (isAltitude) { final int tdAltitude = timeData.altitude; // if (tdAltitude == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdAltitude is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } altitudeSerie[timeIndex] = altitudeAbsolute += tdAltitude == Integer.MIN_VALUE ? 0 : tdAltitude; } if (isPulse) { final int tdPulse = timeData.pulse; // if (tdPulse == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdPulse is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } pulseSerie[timeIndex] = tdPulse == Integer.MIN_VALUE ? 0 : tdPulse; } if (isTemperature) { final int tdTemperature = timeData.temperature; // if (tdTemperature == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdTemperature is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } temperatureSerie[timeIndex] = tdTemperature == Integer.MIN_VALUE ? 0 : tdTemperature; } if (isCadence) { final int tdCadence = timeData.cadence; // if (tdCadence == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdCadence is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } cadenceSerie[timeIndex] = tdCadence == Integer.MIN_VALUE ? 0 : tdCadence; } if (isPower) { final int tdPower = timeData.power; // if (tdPower == Integer.MIN_VALUE) { // System.out.println("tourId:" + tourId + " - tdPower is MIN_VALUE"); //$NON-NLS-1$ //$NON-NLS-1$ //$NON-NLS-2$ // } powerSerie[timeIndex] = tdPower == Integer.MIN_VALUE ? 0 : tdPower; } if (isSpeed) { final int tdSpeed = timeData.speed; speedSerie[timeIndex] = tdSpeed == Integer.MIN_VALUE ? 0 : tdSpeed; } if (isCreateMarker && (timeData.marker != 0)) { createTourMarker(timeData, timeIndex, recordingTime, distanceAbsolute); } timeIndex++; } } tourDistance = distanceAbsolute; tourRecordingTime = (int) recordingTime; cleanupDataSeries(); resetSortedMarkers(); } /** * Creates a unique tour id depending on the tour start time and current time */ public void createTourId() { final String uniqueKey = Long.toString(System.currentTimeMillis()); createTourId(uniqueKey.substring(uniqueKey.length() - 5, uniqueKey.length())); } /** * Creates the unique tour id from the tour date/time and the unique key * * @param uniqueKeySuffix * unique key to identify a tour * @return */ public Long createTourId(final String uniqueKeySuffix) { // final String uniqueKey = Integer.toString(Math.abs(getStartDistance())); String tourIdKey; try { /* * this is the default implementation to create a tour id, but on the 5.5.2007 a * NumberFormatException occured so the calculation for the tour id was adjusted */ tourIdKey = Short.toString(getStartYear()) + Short.toString(getStartMonth()) + Short.toString(getStartDay()) + Short.toString(getStartHour()) + Short.toString(getStartMinute()) // + uniqueKeySuffix; tourId = Long.valueOf(tourIdKey); } catch (final NumberFormatException e) { /* * the distance is shorted that the maximum of a Long datatype is not exceeded */ tourIdKey = Short.toString(getStartYear()) + Short.toString(getStartMonth()) + Short.toString(getStartDay()) + Short.toString(getStartHour()) + Short.toString(getStartMinute()) // + uniqueKeySuffix.substring(0, Math.min(5, uniqueKeySuffix.length())); tourId = Long.valueOf(tourIdKey); } return tourId; } /** * Creates a dummy tour id which should be replaced by setting the tour id with * {@link #createTourId()} or {@link #createTourId(String)} */ public void createTourIdDummy() { tourId = System.nanoTime(); } /** * Create a device marker at the current position * * @param timeData * @param serieIndex * @param recordingTime * @param distanceAbsolute */ private void createTourMarker(final TimeData timeData, final int serieIndex, final long recordingTime, final int distanceAbsolute) { // create a new marker final TourMarker tourMarker = new TourMarker(this, ChartLabel.MARKER_TYPE_DEVICE); tourMarker.setVisualPosition(ChartLabel.VISUAL_HORIZONTAL_ABOVE_GRAPH_CENTERED); tourMarker.setTime((int) (recordingTime + timeData.marker)); tourMarker.setDistance(distanceAbsolute); tourMarker.setSerieIndex(serieIndex); if (timeData.markerLabel == null) { tourMarker.setLabel(Messages.tour_data_label_device_marker); } else { tourMarker.setLabel(timeData.markerLabel); } tourMarkers.add(tourMarker); } /** * Create the tour segment list from the segment index array * * @param breakMinSpeedDiff * @param breakMaxDistance * @param breakMinTime * @param segmenterBreakDistance * @param breakMinSpeedDiff * in km/h * @param breakMinSpeed2 * @param breakDistance * @return */ public Object[] createTourSegments(final BreakTimeTool btConfig) { if ((segmentSerieIndex == null) || (segmentSerieIndex.length < 2)) { // at least two points are required to build a segment return new Object[0]; } final boolean isPulseSerie = (pulseSerie != null) && (pulseSerie.length > 0); final boolean isAltitudeSerie = (altitudeSerie != null) && (altitudeSerie.length > 0); final boolean isDistanceSerie = (distanceSerie != null) && (distanceSerie.length > 0); final int[] localPowerSerie = getPowerSerie(); final boolean isPowerSerie = (localPowerSerie != null) && (localPowerSerie.length > 0); final int segmentSerieLength = segmentSerieIndex.length; final ArrayList<TourSegment> tourSegments = new ArrayList<TourSegment>(segmentSerieLength); final int firstSerieIndex = segmentSerieIndex[0]; /* * get start values */ int timeStart = timeSerie[firstSerieIndex]; int altitudeStart = 0; if (isAltitudeSerie) { altitudeStart = altitudeSerie[firstSerieIndex]; } int distanceStart = 0; if (isDistanceSerie) { distanceStart = distanceSerie[firstSerieIndex]; } int timeTotal = 0; int distanceTotal = 0; int altitudeUpSummarizedBorder = 0; int altitudeUpSummarizedComputed = 0; int altitudeDownSummarizedBorder = 0; int altitudeDownSummarizedComputed = 0; final int tourPace = (int) (tourDistance == 0 ? 0 : (tourDrivingTime * 1000 / (tourDistance * UI.UNIT_VALUE_DISTANCE))); segmentSerieRecordingTime = new int[segmentSerieLength]; segmentSerieDrivingTime = new int[segmentSerieLength]; segmentSerieBreakTime = new int[segmentSerieLength]; segmentSerieTimeTotal = new int[segmentSerieLength]; segmentSerieDistanceDiff = new int[segmentSerieLength]; segmentSerieDistanceTotal = new int[segmentSerieLength]; segmentSerieAltitudeDiff = new int[segmentSerieLength]; segmentSerieAltitudeUpH = new float[segmentSerieLength]; segmentSerieAltitudeDownH = new int[segmentSerieLength]; segmentSerieSpeed = new float[segmentSerieLength]; segmentSeriePace = new float[segmentSerieLength]; segmentSeriePower = new float[segmentSerieLength]; segmentSerieGradient = new float[segmentSerieLength]; segmentSerieCadence = new float[segmentSerieLength]; segmentSeriePulse = new float[segmentSerieLength]; // compute values between start and end for (int segmentIndex = 1; segmentIndex < segmentSerieLength; segmentIndex++) { final int segmentStartIndex = segmentSerieIndex[segmentIndex - 1]; final int segmentEndIndex = segmentSerieIndex[segmentIndex]; final TourSegment segment = new TourSegment(); tourSegments.add(segment); segment.serieIndexStart = segmentStartIndex; segment.serieIndexEnd = segmentEndIndex; /* * time */ final int timeEnd = timeSerie[segmentEndIndex]; final int recordingTime = timeEnd - timeStart; final int segmentBreakTime = getSegmentBreakTime(segmentStartIndex, segmentEndIndex, btConfig); final float drivingTime = recordingTime - segmentBreakTime; segmentSerieRecordingTime[segmentIndex] = segment.recordingTime = recordingTime; segmentSerieDrivingTime[segmentIndex] = segment.drivingTime = (int) drivingTime; segmentSerieBreakTime[segmentIndex] = segment.breakTime = segmentBreakTime; segmentSerieTimeTotal[segmentIndex] = segment.timeTotal = timeTotal += recordingTime; float segmentDistance = 0.0f; /* * distance */ if (isDistanceSerie) { final int distanceEnd = distanceSerie[segmentEndIndex]; final int distanceDiff = distanceEnd - distanceStart; segmentSerieDistanceDiff[segmentIndex] = segment.distanceDiff = distanceDiff; segmentSerieDistanceTotal[segmentIndex] = segment.distanceTotal = distanceTotal += distanceDiff; // end point of current segment is the start of the next segment distanceStart = distanceEnd; segmentDistance = segment.distanceDiff; if (segmentDistance != 0.0) { // speed segmentSerieSpeed[segmentIndex] = segment.speed = drivingTime == 0.0f ? // 0.0f : segmentDistance / drivingTime * 3.6f / UI.UNIT_VALUE_DISTANCE; // pace final float segmentPace = (drivingTime * 1000 / (segmentDistance / UI.UNIT_VALUE_DISTANCE)); segment.pace = (int) segmentPace; segment.paceDiff = segment.pace - tourPace; segmentSeriePace[segmentIndex] = segmentPace; } } /* * altitude */ if (isAltitudeSerie) { final int altitudeEnd = altitudeSerie[segmentEndIndex]; final int altitudeDiff = altitudeEnd - altitudeStart; segmentSerieAltitudeDiff[segmentIndex] = segment.altitudeDiffSegmentBorder = altitudeDiff; if (altitudeDiff > 0) { segment.altitudeUpSummarizedBorder = altitudeUpSummarizedBorder += altitudeDiff; segment.altitudeDownSummarizedBorder = altitudeDownSummarizedBorder; } else { segment.altitudeUpSummarizedBorder = altitudeUpSummarizedBorder; segment.altitudeDownSummarizedBorder = altitudeDownSummarizedBorder += altitudeDiff; } if ((segmentSerieComputedAltitudeDiff != null) && (segmentIndex < segmentSerieComputedAltitudeDiff.length)) { final int segmentDiff = segmentSerieComputedAltitudeDiff[segmentIndex]; segment.altitudeDiffSegmentComputed = segmentDiff; if (segmentDiff > 0) { segment.altitudeUpSummarizedComputed = altitudeUpSummarizedComputed += segmentDiff; segment.altitudeDownSummarizedComputed = altitudeDownSummarizedComputed; } else { segment.altitudeUpSummarizedComputed = altitudeUpSummarizedComputed; segment.altitudeDownSummarizedComputed = altitudeDownSummarizedComputed += segmentDiff; } } int altitudeUpH = 0; int altitudeDownH = 0; int powerSum = 0; int altitude1 = altitudeSerie[segmentStartIndex]; // get computed values: altitude up/down, pulse and power for a segment for (int serieIndex = segmentStartIndex + 1; serieIndex <= segmentEndIndex; serieIndex++) { final int altitude2 = altitudeSerie[serieIndex]; final int altitude2Diff = altitude2 - altitude1; altitude1 = altitude2; altitudeUpH += altitude2Diff >= 0 ? altitude2Diff : 0; altitudeDownH += altitude2Diff < 0 ? altitude2Diff : 0; if (isPowerSerie) { powerSum += localPowerSerie[serieIndex]; } } segment.altitudeUpHour = altitudeUpH; segmentSerieAltitudeDownH[segmentIndex] = segment.altitudeDownHour = altitudeDownH; segmentSerieAltitudeUpH[segmentIndex] = recordingTime == 0 ? // 0 : (float) (altitudeUpH + altitudeDownH) / recordingTime * 3600 / UI.UNIT_VALUE_ALTITUDE; final int segmentIndexDiff = segmentEndIndex - segmentStartIndex; segmentSeriePower[segmentIndex] = segment.power = segmentIndexDiff == 0 ? 0 : powerSum / segmentIndexDiff; // end point of current segment is the start of the next segment altitudeStart = altitudeEnd; } if (isDistanceSerie && isAltitudeSerie && (segmentDistance != 0.0)) { // gradient segmentSerieGradient[segmentIndex] = segment.gradient = // (float) segment.altitudeDiffSegmentBorder * 100 / segmentDistance; } if (isPulseSerie) { final int segmentAvgPulse = computeAvgPulseSegment(segmentStartIndex, segmentEndIndex); segmentSeriePulse[segmentIndex] = segment.pulse = segmentAvgPulse; segment.pulseDiff = segmentAvgPulse - avgPulse; } else { // hide pulse in the view segment.pulseDiff = Integer.MIN_VALUE; } // end point of current segment is the start of the next segment timeStart = timeEnd; } return tourSegments.toArray(); } public void dumpData() { final PrintStream out = System.out; out.println("----------------------------------------------------"); //$NON-NLS-1$ out.println("TOUR DATA"); //$NON-NLS-1$ out.println("----------------------------------------------------"); //$NON-NLS-1$ // out.println("Typ: " + getDeviceTourType()); //$NON-NLS-1$ out.println("Date: " + getStartDay() + "." + getStartMonth() + "." + getStartYear()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ out.println("Time: " + getStartHour() + ":" + getStartMinute()); //$NON-NLS-1$ //$NON-NLS-2$ out.println("Total distance: " + getStartDistance()); //$NON-NLS-1$ // out.println("Distance: " + getDistance()); out.println("Altitude: " + getStartAltitude()); //$NON-NLS-1$ out.println("Pulse: " + getStartPulse()); //$NON-NLS-1$ out.println("Offset DD record: " + offsetDDRecord); //$NON-NLS-1$ } public void dumpTime() { final PrintStream out = System.out; out.print((getTourRecordingTime() / 3600) + ":" //$NON-NLS-1$ + ((getTourRecordingTime() % 3600) / 60) + ":" //$NON-NLS-1$ + ((getTourRecordingTime() % 3600) % 60) + " "); //$NON-NLS-1$ out.print(getTourDistance()); } public void dumpTourTotal() { final PrintStream out = System.out; out.println("Tour distance (m): " + getTourDistance()); //$NON-NLS-1$ out.println("Tour time: " //$NON-NLS-1$ + (getTourRecordingTime() / 3600) + ":" //$NON-NLS-1$ + ((getTourRecordingTime() % 3600) / 60) + ":" //$NON-NLS-1$ + (getTourRecordingTime() % 3600) % 60); out.println("Driving time: " //$NON-NLS-1$ + (getTourDrivingTime() / 3600) + ":" //$NON-NLS-1$ + ((getTourDrivingTime() % 3600) / 60) + ":" //$NON-NLS-1$ + (getTourDrivingTime() % 3600) % 60); out.println("Altitude up (m): " + getTourAltUp()); //$NON-NLS-1$ out.println("Altitude down (m): " + getTourAltDown()); //$NON-NLS-1$ } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj instanceof TourData) { return tourId.longValue() == ((TourData) obj).tourId.longValue(); } return false; } /** * @return Returns the metric or imperial altimeter serie depending on the active measurement */ public int[] getAltimeterSerie() { if (UI.UNIT_VALUE_ALTITUDE != 1) { // use imperial system if (altimeterSerieImperial == null) { computeAltimeterGradientSerie(); } return altimeterSerieImperial; } else { // use metric system if (altimeterSerie == null) { computeAltimeterGradientSerie(); } return altimeterSerie; } } /** * @return Returns the metric or imperial altitude serie depending on the active measurement or * <code>null</code> when altitude data serie is not available */ public int[] getAltitudeSerie() { if (altitudeSerie == null) { return null; } if (UI.UNIT_VALUE_ALTITUDE != 1) { // imperial system is used if (altitudeSerieImperial == null) { // compute imperial altitude altitudeSerieImperial = new int[altitudeSerie.length]; for (int valueIndex = 0; valueIndex < altitudeSerie.length; valueIndex++) { altitudeSerieImperial[valueIndex] = (int) (altitudeSerie[valueIndex] / UI.UNIT_VALUE_ALTITUDE); } } return altitudeSerieImperial; } else { return altitudeSerie; } } /** * @return the avgCadence */ public int getAvgCadence() { return avgCadence; } /** * @return the avgPulse */ public int getAvgPulse() { return avgPulse; } /** * @return Returns average temperature multiplied with {@link #temperatureScale} */ public int getAvgTemperature() { return avgTemperature; } /** * @return the bikerWeight */ public float getBikerWeight() { return bikerWeight; } /** * Computes the time between start index and end index when the speed is <code>0</code> * * @param startIndex * @param endIndex * @return Returns the break time in seconds */ public int getBreakTime(final int startIndex, final int endIndex) { if (distanceSerie == null) { // distance is required return 0; } final int minBreakTime = 20; if (deviceTimeInterval == -1) { // variable time slices return computeBreakTimeVariable(startIndex, endIndex, BreakTimeTool.getPrefValues()); } else { // fixed time slices final int ignoreTimeSlices = deviceTimeInterval == 0 ? // 0 : getBreakTimeSlices(distanceSerie, startIndex, endIndex, minBreakTime / deviceTimeInterval); return ignoreTimeSlices * deviceTimeInterval; } } /** * calculate the driving time, ignore the time when the distance is 0 within a time period which * is defined by <code>sliceMin</code> * * @param distanceValues * @param indexLeft * @param indexRight * @param sliceMin * @return Returns the number of slices which can be ignored */ private int getBreakTimeSlices(final int[] distanceValues, final int indexLeft, int indexRight, int sliceMin) { final int distanceLengthLast = distanceValues.length - 1; int ignoreTimeCounter = 0; int oldDistance = 0; sliceMin = (sliceMin >= 1) ? sliceMin : 1; indexRight = (indexRight <= distanceLengthLast) ? indexRight : distanceLengthLast; for (int valueIndex = indexLeft; valueIndex <= indexRight; valueIndex++) { if (distanceValues[valueIndex] == oldDistance) { ignoreTimeCounter++; } int oldIndex = valueIndex - sliceMin; if (oldIndex < 0) { oldIndex = 0; } oldDistance = distanceValues[oldIndex]; } return ignoreTimeCounter; } /** * @return the calories */ public int getCalories() { if (calories == null) { return 0; } return calories; } public int getConconiDeflection() { return conconiDeflection; } /** * @return Returns {@link DateTime} when the tour was created or <code>null</code> when * date/time is not available */ public DateTime getDateTimeCreated() { if (_dateTimeCreated != null || dateTimeCreated == 0) { return _dateTimeCreated; } _dateTimeCreated = Util.createDateTimeFromYMDhms(dateTimeCreated); return _dateTimeCreated; } /** * @return Returns {@link DateTime} when the tour was modified or <code>null</code> when * date/time is not available */ public DateTime getDateTimeModified() { if (_dateTimeModified != null || dateTimeModified == 0) { return _dateTimeModified; } _dateTimeModified = Util.createDateTimeFromYMDhms(dateTimeModified); return _dateTimeModified; } public float getDeviceAvgSpeed() { return deviceAvgSpeed; } public String getDeviceFirmwareVersion() { return deviceFirmwareVersion == null ? UI.EMPTY_STRING : deviceFirmwareVersion; } public String getDeviceId() { return devicePluginId; } public short getDeviceMode() { return deviceMode; } /** * This info is only displayed in viewer columns * * @return */ public String getDeviceModeName() { return deviceModeName; } /** * @return Returns device name which is displayed in the tour editor info tab */ public String getDeviceName() { if ((devicePluginId != null) && devicePluginId.equals(DEVICE_ID_FOR_MANUAL_TOUR)) { return Messages.tour_data_label_manually_created_tour; } else if ((devicePluginName == null) || (devicePluginName.length() == 0)) { return UI.EMPTY_STRING; } else { return devicePluginName; } } /** * @return Returns the time difference between 2 time slices or <code>-1</code> when the time * slices are unequally */ public short getDeviceTimeInterval() { return deviceTimeInterval; } // NOT USED 18.8.2010 // public long getDeviceTravelTime() { // return deviceTravelTime; // } // // public int getDeviceWeight() { // return deviceWeight; // } // // public int getDeviceWheel() { // return deviceWheel; // } // not used 5.10.2008 // public int getDeviceDistance() { // return deviceDistance; // } public String getDeviceTourType() { return deviceTourType; } /** * @return Returns the distance data serie for the current measurement system, this can be * metric or imperial */ public int[] getDistanceSerie() { if (distanceSerie == null) { return null; } int[] serie; final float unitValueDistance = UI.UNIT_VALUE_DISTANCE; if (unitValueDistance != 1) { // use imperial system if (distanceSerieImperial == null) { // compute imperial data distanceSerieImperial = new int[distanceSerie.length]; for (int valueIndex = 0; valueIndex < distanceSerie.length; valueIndex++) { distanceSerieImperial[valueIndex] = (int) (distanceSerie[valueIndex] / unitValueDistance); } } serie = distanceSerieImperial; } else { // use metric system serie = distanceSerie; } return serie; } public short getDpTolerance() { return dpTolerance; } /** * @return Returns the metric or imperial altimeter serie depending on the active measurement */ public int[] getGradientSerie() { if (gradientSerie == null) { computeAltimeterGradientSerie(); } return gradientSerie; } /** * @return the maxAltitude */ public int getMaxAltitude() { return maxAltitude; } // not used 5.10.2008 // public int getDeviceTotalDown() { // return deviceTotalDown; // } // public int getDeviceTotalUp() { // return deviceTotalUp; // } /** * @return the maxPulse */ public int getMaxPulse() { return maxPulse; } /** * @return the maxSpeed */ public float getMaxSpeed() { return maxSpeed; } public int getMergedAltitudeOffset() { return mergedAltitudeOffset; } public int getMergedTourTimeOffset() { return mergedTourTimeOffset; } public TourData getMergeSourceTourData() { return _mergeSourceTourData; } /** * @return tour id which is merged into this tour */ public Long getMergeSourceTourId() { return mergeSourceTourId; } /** * @return tour Id into which this tour is merged or <code>null</code> when this tour is not * merged into another tour */ public Long getMergeTargetTourId() { return mergeTargetTourId; } /** * @return Returns the distance serie from the metric system, the distance serie is * <b>always</b> saved in the database with the metric system */ public int[] getMetricDistanceSerie() { return distanceSerie; } public int[] getPaceSerie() { if (UI.UNIT_VALUE_DISTANCE == 1) { // use metric system if (paceSerieMinute == null) { computeSpeedSerie(); } return paceSerieMinute; } else { // use imperial system if (paceSerieMinuteImperial == null) { computeSpeedSerie(); } return paceSerieMinuteImperial; } } public int[] getPaceSerieSeconds() { if (UI.UNIT_VALUE_DISTANCE == 1) { // use metric system if (paceSerieMinute == null) { computeSpeedSerie(); } return paceSerieSeconds; } else { // use imperial system if (paceSerieMinuteImperial == null) { computeSpeedSerie(); } return paceSerieSecondsImperial; } } public int[] getPowerSerie() { if ((powerSerie != null) || isPowerSerieFromDevice) { return powerSerie; } if (speedSerie == null) { computeSpeedSerie(); } if (gradientSerie == null) { computeAltimeterGradientSerie(); } // check if required data series are available if ((speedSerie == null) || (gradientSerie == null)) { return null; } powerSerie = new int[timeSerie.length]; final int weightBody = 75; final int weightBike = 10; final int bodyHeight = 188; final float cR = 0.008f; // Rollreibungskoeffizient Asphalt final float cD = 0.8f;// Streomungskoeffizient final float p = 1.145f; // 20C / 400m // float p = 0.968f; // 10C / 2000m final float weightTotal = weightBody + weightBike; final float bsa = (float) (0.007184f * Math.pow(weightBody, 0.425) * Math.pow(bodyHeight, 0.725)); final float aP = bsa * 0.185f; final float fRoll = weightTotal * 9.81f * cR; final float fSlope = weightTotal * 9.81f; // * gradient/100 final float fAir = 0.5f * p * cD * aP;// * v2; // int joule = 0; // int prefTime = 0; for (int timeIndex = 0; timeIndex < timeSerie.length; timeIndex++) { final float speed = (float) speedSerie[timeIndex] / 36; // speed (m/s) /10 float gradient = (float) gradientSerie[timeIndex] / 1000; // gradient (%) /10 /100 // adjust computed errors // if (gradient < 0.04 && gradient > 0) { // gradient *= 0.5; //// gradient = 0; // } if (gradient < 0) { if (gradient < -0.02) { gradient *= 3; } else { gradient *= 1.5; } } final float fSlopeTotal = fSlope * gradient; final float fAirTotal = fAir * speed * speed; final float fTotal = fRoll + fAirTotal + fSlopeTotal; int pTotal = (int) (fTotal * speed); // if (pTotal > 600) { // pTotal = pTotal * 1; // } pTotal = pTotal < 0 ? 0 : pTotal; powerSerie[timeIndex] = pTotal; // final int currentTime = timeSerie[timeIndex]; // joule += pTotal * (currentTime - prefTime); // prefTime = currentTime; } return powerSerie; } public int getRestPulse() { return restPulse; } private int getSegmentBreakTime(final int startIndex, final int endIndex, final BreakTimeTool btConfig) { if (distanceSerie == null) { return 0; } final int minBreakTime = 20; if (deviceTimeInterval == -1) { // variable time slices return computeBreakTimeVariable(startIndex, endIndex, btConfig); } else { // fixed time slices final int ignoreTimeSlices = deviceTimeInterval == 0 ? // 0 : getBreakTimeSlices(distanceSerie, startIndex, endIndex, minBreakTime / deviceTimeInterval); return ignoreTimeSlices * deviceTimeInterval; } } public SerieData getSerieData() { return serieData; } /** * @return the speed data in the current measurement system, which is defined in * {@link UI#UNIT_VALUE_DISTANCE} */ public int[] getSpeedSerie() { if (isSpeedSerieFromDevice) { return getSpeedSerieInternal(); } if (distanceSerie == null) { return null; } return getSpeedSerieInternal(); } public int[] getSpeedSerieFromDevice() { if (isSpeedSerieFromDevice) { return speedSerie; } return null; } private int[] getSpeedSerieInternal() { computeSpeedSerie(); /* * when the speed series are not computed, the internal algorithm will be used to create the * speed data serie */ if (UI.UNIT_VALUE_DISTANCE == 1) { // use metric system return speedSerie; } else { // use imperial system return speedSerieImperial; } } /** * @return returns the speed data in the metric measurement system */ public int[] getSpeedSerieMetric() { computeSpeedSerie(); return speedSerie; } /** * @return Returns SRTM metric or imperial data serie depending on the active measurement or * <code>null</code> when SRTM data serie is not available */ public int[] getSRTMSerie() { if (latitudeSerie == null) { return null; } if (srtmSerie == null) { createSRTMDataSerie(); } if (srtmSerie.length == 0) { // SRTM data are invalid return null; } if (UI.UNIT_VALUE_ALTITUDE != 1) { // imperial system is used return srtmSerieImperial; } else { return srtmSerie; } } /** * @return Returned SRTM values: * <p> * metric <br> * imperial * <p> * or <code>null</code> when SRTM data serie is not available */ public int[][] getSRTMValues() { if (latitudeSerie == null) { return null; } if (srtmSerie == null) { createSRTMDataSerie(); } if (srtmSerie.length == 0) { // invalid SRTM values return null; } return new int[][] { srtmSerie, srtmSerieImperial }; } public short getStartAltitude() { return startAltitude; } /** * @return Returns date/time for the tour start */ public DateTime getStartDateTime() { if (_dateTimeStart == null) { _dateTimeStart = new DateTime(startYear, startMonth, startDay, startHour, startMinute, startSecond, 0); } return _dateTimeStart; } public short getStartDay() { return startDay; } public int getStartDistance() { return startDistance; } public short getStartHour() { return startHour; } public short getStartMinute() { return startMinute; } /** * @return Returns the month for the tour start in the range 1...12 */ public short getStartMonth() { return startMonth; } public short getStartPulse() { return startPulse; } public int getStartSecond() { return startSecond; } public short getStartYear() { return startYear; } public int getTemperatureScale() { return temperatureScale; } /** * @return Returns the temperature serie for the current measurement system or <code>null</code> * when temperature is not available */ public int[] getTemperatureSerie() { if (temperatureSerie == null) { return null; } int[] serie; final float unitValueTempterature = UI.UNIT_VALUE_TEMPERATURE; final float fahrenheitMulti = UI.UNIT_FAHRENHEIT_MULTI; final float fahrenheitAdd = UI.UNIT_FAHRENHEIT_ADD; if (unitValueTempterature != 1) { // use imperial system if (temperatureSerieImperial == null) { // compute imperial data temperatureSerieImperial = new int[temperatureSerie.length]; for (int valueIndex = 0; valueIndex < temperatureSerie.length; valueIndex++) { final float scaledTemperature = (float) temperatureSerie[valueIndex] / temperatureScale; temperatureSerieImperial[valueIndex] = (int) (((scaledTemperature) * fahrenheitMulti + fahrenheitAdd) * temperatureScale); } } serie = temperatureSerieImperial; } else { // use metric system serie = temperatureSerie; } return serie; } @XmlElement public String getTest() { return "jokl"; //$NON-NLS-1$ } public int getTourAltDown() { return tourAltDown; } public int getTourAltUp() { return tourAltUp; } public TourBike getTourBike() { return tourBike; } /** * @return the tourDescription */ public String getTourDescription() { return tourDescription == null ? UI.EMPTY_STRING : tourDescription; } /** * @return the tour distance in metric measurement system */ public int getTourDistance() { return tourDistance; } public int getTourDrivingTime() { return tourDrivingTime; } /** * @return the tourEndPlace */ public String getTourEndPlace() { return tourEndPlace == null ? UI.EMPTY_STRING : tourEndPlace; } /** * @return Returns the unique key in the database for this {@link TourData} entity */ public Long getTourId() { return tourId; } public String getTourImportFilePath() { if ((tourImportFilePath == null) || (tourImportFilePath.length() == 0)) { if (isManualTour()) { return UI.EMPTY_STRING; } else { return Messages.tour_data_label_feature_since_version_9_01; } } else { return tourImportFilePath; } } public String getTourImportFilePathRaw() { return tourImportFilePath; } /** * @return Returns a set with all {@link TourMarker} for the tour or an empty set when markers * are not available. */ public Set<TourMarker> getTourMarkers() { return tourMarkers; } public ArrayList<TourMarker> getTourMarkersSorted() { if (_sortedMarkers != null) { return _sortedMarkers; } // sort markers by serie index _sortedMarkers = new ArrayList<TourMarker>(tourMarkers); Collections.sort(_sortedMarkers, new Comparator<TourMarker>() { @Override public int compare(final TourMarker marker1, final TourMarker marker2) { return marker1.getSerieIndex() - marker2.getSerieIndex(); } }); return _sortedMarkers; } /** * @return Returns the person for which the tour is saved or <code>null</code> when the tour is * not saved in the database */ public TourPerson getTourPerson() { return tourPerson; } /** * @return Returns total recording time in seconds */ public int getTourRecordingTime() { return tourRecordingTime; } public Collection<TourReference> getTourReferences() { return tourReferences; } /** * @return the tourStartPlace */ public String getTourStartPlace() { return tourStartPlace == null ? UI.EMPTY_STRING : tourStartPlace; } /** * @return Returns the tags {@link #tourTags} which are defined for this tour */ public Set<TourTag> getTourTags() { return tourTags; } /** * @return Returns tour title or an empty string when title is not set */ public String getTourTitle() { return tourTitle == null ? UI.EMPTY_STRING : tourTitle; } /** * @return Returns the {@link TourType} for the tour or <code>null</code> when tour type is not * defined */ public TourType getTourType() { return tourType; } public Set<TourWayPoint> getTourWayPoints() { return tourWayPoints; } /** * @return Returns weather text or an empty string when weather text is not set. */ public String getWeather() { return weather == null ? UI.EMPTY_STRING : weather; } /** * @return Returns the {@link IWeather#WEATHER_ID_}... or <code>null</code> when weather is not * set. */ public String getWeatherClouds() { return weatherClouds; } /** * @return Returns the index for the cloud values in {@link IWeather#cloudIcon} and * {@link IWeather#cloudText} or 0 when the clouds are not defined */ public int getWeatherIndex() { int weatherCloudsIndex = -1; if (weatherClouds != null) { // binary search cannot be done because it requires sorting which we cannot... for (int cloudIndex = 0; cloudIndex < IWeather.cloudIcon.length; ++cloudIndex) { if (IWeather.cloudIcon[cloudIndex].equalsIgnoreCase(weatherClouds)) { weatherCloudsIndex = cloudIndex; break; } } } return weatherCloudsIndex < 0 ? 0 : weatherCloudsIndex; } public int getWeatherWindDir() { return weatherWindDir; } public int getWeatherWindSpeed() { return weatherWindSpd; } /** * @param zoomLevel * @param projectionId * @return Returns the world position for the suplied zoom level and projection id */ public Point[] getWorldPositionForTour(final String projectionId, final int zoomLevel) { return _tourWorldPosition.get(projectionId.hashCode() + zoomLevel); } /** * @param zoomLevel * @param projectionId * @return Returns the world position for way points */ public HashMap<Integer, Point> getWorldPositionForWayPoints(final String projectionId, final int zoomLevel) { return _twpWorldPosition.get(projectionId.hashCode() + zoomLevel); } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int result = 17; result = 37 * result + this.getStartYear(); result = 37 * result + this.getStartMonth(); result = 37 * result + this.getStartDay(); result = 37 * result + this.getStartHour(); result = 37 * result + this.getStartMinute(); result = 37 * result + this.getTourDistance(); result = 37 * result + this.getTourRecordingTime(); return result; } /** * @return Returns <code>true</code> when {@link TourData} contains refreence tours, otherwise * <code>false</code> */ public boolean isContainReferenceTour() { if (tourReferences == null) { return false; } else { return tourReferences.size() > 0; } } public boolean isDistanceSensorPresent() { return isDistanceFromSensor == 1; } /** * @return <code>true</code> when the tour is manually created and not imported from a file or * device */ public boolean isManualTour() { if (devicePluginId == null) { return false; } return devicePluginId.equals(DEVICE_ID_FOR_MANUAL_TOUR) || devicePluginId.equals(DEVICE_ID_CSV_TOUR_DATA_READER); } /** * This is the state of the device which is not related to the availability of power data. Power * data should be available but is not checked. * * @return Returns <code>true</code> when the device has a power sensor */ public boolean isPowerSensorPresent() { return isPowerSensorPresent == 1; } /** * @return Returns <code>true</code> when the data in {@link #powerSerie} is from a device and * not computed. Power data are normally available from an ergometer and not from a bike * computer */ public boolean isPowerSerieFromDevice() { return isPowerSerieFromDevice; } /** * This is the state of the device which is not related to the availability of pulse data. Pulse * data should be available but is not checked. * * @return Returns <code>true</code> when the device has a pulse sensor */ public boolean isPulseSensorPresent() { return isPulseSensorPresent == 1; } /** * @return Returns <code>true</code> when the data in {@link #speedSerie} are from the device * and not computed. Speed data are normally available from an ergometer and not from a * bike computer */ public boolean isSpeedSerieFromDevice() { return isSpeedSerieFromDevice; } /** * @return Returns <code>true</code> when SRTM data are available or when they can be available * but not yet computed. */ public boolean isSRTMAvailable() { if (latitudeSerie == null) { return false; } if (srtmSerie == null) { // srtm data can be available but are not yet computed return true; } if (srtmSerie.length == 0) { // SRTM data are invalid return false; } else { return true; } } public boolean isTourImportFilePathAvailable() { if (tourImportFilePath != null && tourImportFilePath.length() > 0) { return true; } return false; } /** * @return Returns <code>true</code> when the tour is saved in the database. */ public boolean isTourSaved() { return tourPerson != null; } /** * Checks if VARCHAR fields have the correct length * * @return Returns <code>true</code> when the data are valid and can be saved */ public boolean isValidForSave() { /* * check: tour title */ FIELD_VALIDATION fieldValidation = TourDatabase.isFieldValidForSave(tourTitle, DB_LENGTH_TOUR_TITLE, Messages.Db_Field_TourData_Title, false); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { tourTitle = tourTitle.substring(0, DB_LENGTH_TOUR_TITLE); } /* * check: tour description */ fieldValidation = TourDatabase.isFieldValidForSave(tourDescription, DB_LENGTH_TOUR_DESCRIPTION_V10, Messages.Db_Field_TourData_Description, false); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { tourDescription = tourDescription.substring(0, DB_LENGTH_TOUR_DESCRIPTION_V10); } /* * check: tour start location */ fieldValidation = TourDatabase.isFieldValidForSave(tourStartPlace, DB_LENGTH_TOUR_START_PLACE, Messages.Db_Field_TourData_StartPlace, false); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { tourStartPlace = tourStartPlace.substring(0, DB_LENGTH_TOUR_START_PLACE); } /* * check: tour end location */ fieldValidation = TourDatabase.isFieldValidForSave(tourEndPlace, DB_LENGTH_TOUR_END_PLACE, Messages.Db_Field_TourData_EndPlace, false); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { tourEndPlace = tourEndPlace.substring(0, DB_LENGTH_TOUR_END_PLACE); } /* * check: tour import file path */ fieldValidation = TourDatabase.isFieldValidForSave(tourImportFilePath, DB_LENGTH_TOUR_IMPORT_FILE_PATH, Messages.Db_Field_TourData_TourImportFilePath, true); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { tourImportFilePath = tourImportFilePath.substring(0, DB_LENGTH_TOUR_IMPORT_FILE_PATH); } /* * check: weather */ fieldValidation = TourDatabase.isFieldValidForSave(weather, DB_LENGTH_WEATHER, Messages.Db_Field_TourData_Weather, false); if (fieldValidation == FIELD_VALIDATION.IS_INVALID) { return false; } else if (fieldValidation == FIELD_VALIDATION.TRUNCATE) { weather = weather.substring(0, DB_LENGTH_WEATHER); } return true; } /** * Called after the object was loaded from the persistence store */ @PostLoad @PostUpdate public void onPostLoad() { timeSerie = serieData.timeSerie; // manually created tours have currently no time series if (timeSerie == null) { return; } altitudeSerie = serieData.altitudeSerie; cadenceSerie = serieData.cadenceSerie; distanceSerie = serieData.distanceSerie; pulseSerie = serieData.pulseSerie; temperatureSerie = serieData.temperatureSerie; powerSerie = serieData.powerSerie; speedSerie = serieData.speedSerie; latitudeSerie = serieData.latitude; longitudeSerie = serieData.longitude; /* * cleanup dataseries because dataseries has been saved before version 1.3.0 even when no * data are available */ cleanupDataSeries(); } /** * Called before this object gets persisted, copy data from the tourdata object into the object * which gets serialized */ /* * @PrePersist + @PreUpdate is currently disabled for EJB events because of bug * http://opensource.atlassian.com/projects/hibernate/browse/HHH-1921 2006-08-11 */ public void onPrePersist() { serieData = new SerieData(); serieData.altitudeSerie = altitudeSerie; serieData.cadenceSerie = cadenceSerie; serieData.distanceSerie = distanceSerie; serieData.pulseSerie = pulseSerie; serieData.temperatureSerie = temperatureSerie; serieData.timeSerie = timeSerie; /* * don't save computed data series */ if (isSpeedSerieFromDevice) { serieData.speedSerie = speedSerie; } if (isPowerSerieFromDevice) { serieData.powerSerie = powerSerie; } serieData.latitude = latitudeSerie; serieData.longitude = longitudeSerie; } public boolean replaceAltitudeWithSRTM() { if (getSRTMSerie() == null) { return false; } altitudeSerie = Arrays.copyOf(srtmSerie, srtmSerie.length); altitudeSerieImperial = Arrays.copyOf(srtmSerieImperial, srtmSerieImperial.length); // adjust computed altitude values computeAltitudeUpDown(); computeMaxAltitude(); return true; } /** * Reset sorted markers that they are resorted. */ private void resetSortedMarkers() { if (_sortedMarkers != null) { _sortedMarkers.clear(); _sortedMarkers = null; } } /** * @param avgCadence * the avgCadence to set */ public void setAvgCadence(final int avgCadence) { this.avgCadence = avgCadence; } /** * @param avgPulse * the avgPulse to set */ public void setAvgPulse(final int avgPulse) { this.avgPulse = avgPulse; } /** * @param avgTemperature * the avgTemperature to set */ public void setAvgTemperature(final int avgTemperature) { this.avgTemperature = avgTemperature; } /** * @param bikerWeight * the bikerWeight to set */ public void setBikerWeight(final float bikerWeight) { this.bikerWeight = bikerWeight; } /** * @param calories * the calories to set */ public void setCalories(final int calories) { this.calories = calories; } public void setConconiDeflection(final int conconiDeflection) { this.conconiDeflection = conconiDeflection; } public void setDateTimeCreated(final long dateTimeCreated) { this.dateTimeCreated = dateTimeCreated; } public void setDateTimeModified(final long dateTimeModified) { this.dateTimeModified = dateTimeModified; } public void setDeviceAvgSpeed(final float deviceAvgSpeed) { this.deviceAvgSpeed = deviceAvgSpeed; } public void setDeviceFirmwareVersion(final String deviceFirmwareVersion) { this.deviceFirmwareVersion = deviceFirmwareVersion; } public void setDeviceId(final String deviceId) { this.devicePluginId = deviceId; } public void setDeviceMode(final short deviceMode) { this.deviceMode = deviceMode; } public void setDeviceModeName(final String deviceModeName) { this.deviceModeName = deviceModeName; } public void setDeviceName(final String deviceName) { devicePluginName = deviceName; } /** * Time difference in seconds between 2 time slices when the interval is constant for the whole * tour or <code>-1</code> for GPS devices or ergometer when the time slice duration are not * equally * * @param deviceTimeInterval */ public void setDeviceTimeInterval(final short deviceTimeInterval) { this.deviceTimeInterval = deviceTimeInterval; } public void setDeviceTotalDown(final int deviceTotalDown) { this.deviceTotalDown = deviceTotalDown; } public void setDeviceTotalUp(final int deviceTotalUp) { this.deviceTotalUp = deviceTotalUp; } public void setDeviceTourType(final String tourType) { this.deviceTourType = tourType; } public void setDeviceTravelTime(final long deviceTravelTime) { // NOT USED 18.8.2010 // this.deviceTravelTime = deviceTravelTime; } public void setDeviceWeight(final int deviceWeight) { // NOT USED 18.8.2010 // this.deviceWeight = deviceWeight; } public void setDeviceWheel(final int deviceWheel) { // NOT USED 18.8.2010 // this.deviceWheel = deviceWheel; } public void setDpTolerance(final short dpTolerance) { this.dpTolerance = dpTolerance; } /** * Set state if the distance is from a sensor or not, default is <code>false</code> * * @param isFromSensor */ public void setIsDistanceFromSensor(final boolean isFromSensor) { this.isDistanceFromSensor = (short) (isFromSensor ? 1 : 0); } public void setMaxPulse(final int maxPulse) { this.maxPulse = maxPulse; } public void setMergedAltitudeOffset(final int altitudeDiff) { mergedAltitudeOffset = altitudeDiff; } public void setMergedTourTimeOffset(final int mergedTourTimeOffset) { this.mergedTourTimeOffset = mergedTourTimeOffset; } public void setMergeSourceTour(final TourData mergeSourceTour) { _mergeSourceTourData = mergeSourceTour; } public void setMergeSourceTourId(final Long mergeSourceTourId) { this.mergeSourceTourId = mergeSourceTourId; } public void setMergeTargetTourId(final Long mergeTargetTourId) { this.mergeTargetTourId = mergeTargetTourId; } /** * Sets the power data serie and set's a flag that the data serie is from a device * * @param powerSerie */ public void setPowerSerie(final int[] powerSerie) { this.powerSerie = powerSerie; this.isPowerSerieFromDevice = true; } public void setRestPulse(final int restPulse) { this.restPulse = restPulse; } private void setSpeed(final int serieIndex, final int speedMetric, final int speedImperial, final int timeDiff, final int distDiff) { speedSerie[serieIndex] = speedMetric; speedSerieImperial[serieIndex] = speedImperial; maxSpeed = Math.max(maxSpeed, speedMetric); /* * pace (computed with divisor 10) */ float paceMetricSeconds = 0; float paceImperialSeconds = 0; int paceMetricMinute = 0; int paceImperialMinute = 0; if ((speedMetric != 0) && (distDiff != 0)) { paceMetricSeconds = timeDiff * 10000 / (float) distDiff; paceImperialSeconds = paceMetricSeconds * UI.UNIT_MILE; paceMetricMinute = (int) ((paceMetricSeconds / 60)); paceImperialMinute = (int) ((paceImperialSeconds / 60)); } paceSerieMinute[serieIndex] = paceMetricMinute; paceSerieMinuteImperial[serieIndex] = paceImperialMinute; paceSerieSeconds[serieIndex] = (int) paceMetricSeconds / 10; paceSerieSecondsImperial[serieIndex] = (int) paceImperialSeconds / 10; } /** * Sets the speed data serie and set's a flag that the data serie is from a device * * @param speedSerie */ public void setSpeedSerie(final int[] speedSerie) { this.speedSerie = speedSerie; this.isSpeedSerieFromDevice = speedSerie != null; } public void setSRTMValues(final int[] srtm, final int[] srtmImperial) { srtmSerie = srtm; srtmSerieImperial = srtmImperial; } public void setStartAltitude(final short startAltitude) { this.startAltitude = startAltitude; } public void setStartDay(final short startDay) { _dateTimeStart = null; this.startDay = startDay; } /** * Odometer value, this is the distance which the device is accumulating * * @param startDistance */ public void setStartDistance(final int startDistance) { this.startDistance = startDistance; } public void setStartHour(final short startHour) { _dateTimeStart = null; this.startHour = startHour; } public void setStartMinute(final short startMinute) { _dateTimeStart = null; this.startMinute = startMinute; } /** * Sets the month for the tour start in the range 1...12 */ public void setStartMonth(final short startMonth) { _dateTimeStart = null; this.startMonth = startMonth; } public void setStartPulse(final short startPulse) { this.startPulse = startPulse; } public void setStartSecond(final int startSecond) { _dateTimeStart = null; this.startSecond = startSecond; } public void setStartYear(final short startYear) { _dateTimeStart = null; this.startYear = startYear; } public void setTemperatureScale(final int temperatureScale) { this.temperatureScale = temperatureScale; } public void setTourAltDown(final int tourAltDown) { this.tourAltDown = tourAltDown; } public void setTourAltUp(final int tourAltUp) { this.tourAltUp = tourAltUp; } public void setTourBike(final TourBike tourBike) { this.tourBike = tourBike; } /** * @param tourDescription * the tourDescription to set */ public void setTourDescription(final String tourDescription) { this.tourDescription = tourDescription; } public void setTourDistance(final int tourDistance) { this.tourDistance = tourDistance; } /** * Set total driving/moving time in seconds * * @param tourDrivingTime */ public void setTourDrivingTime(final int tourDrivingTime) { this.tourDrivingTime = tourDrivingTime; } /** * @param tourEndPlace * the tourEndPlace to set */ public void setTourEndPlace(final String tourEndPlace) { this.tourEndPlace = tourEndPlace; } /** * Sets the file path for the imported file, this is displayed in the {@link TourDataEditorView} * * @param tourImportFilePath */ public void setTourImportFilePath(final String tourImportFilePath) { this.tourImportFilePath = tourImportFilePath; } public void setTourMarkers(final Set<TourMarker> tourMarkers) { if (this.tourMarkers != null) { this.tourMarkers.clear(); } this.tourMarkers = tourMarkers; resetSortedMarkers(); } /** * Sets the {@link TourPerson} for the tour or <code>null</code> when the tour is not saved in * the database * * @param tourPerson */ public void setTourPerson(final TourPerson tourPerson) { this.tourPerson = tourPerson; } /** * Set total recording time in seconds * * @param tourRecordingTime */ public void setTourRecordingTime(final int tourRecordingTime) { this.tourRecordingTime = tourRecordingTime; } /** * @param tourStartPlace * the tourStartPlace to set */ public void setTourStartPlace(final String tourStartPlace) { this.tourStartPlace = tourStartPlace; } public void setTourTags(final Set<TourTag> tourTags) { this.tourTags = tourTags; } /** * @param tourTitle * the tourTitle to set */ public void setTourTitle(final String tourTitle) { this.tourTitle = tourTitle; } public void setTourType(final TourType tourType) { this.tourType = tourType; } public void setWayPoints(final ArrayList<TourWayPoint> wptList) { // remove old way points tourWayPoints.clear(); if ((wptList == null) || (wptList.size() == 0)) { return; } // set new way points for (final TourWayPoint tourWayPoint : wptList) { /** * !!!! <br> * way point must be cloned because the entity could be saved within different tour data * instances, otherwise hibernate exceptions occure <br> * this also sets the createId <br> * !!!! */ final TourWayPoint clonedWP = (TourWayPoint) tourWayPoint.clone(); clonedWP.setTourData(this); tourWayPoints.add(clonedWP); } } public void setWeather(final String weather) { this.weather = weather; } /** * Sets the weather id which is defined in {@link IWeather#WEATHER_ID_}... or <code>null</code> * when weather id is not defined * * @param weatherClouds */ public void setWeatherClouds(final String weatherClouds) { this.weatherClouds = weatherClouds; } public void setWeatherWindDir(final int weatherWindDir) { this.weatherWindDir = weatherWindDir; } public void setWeatherWindSpeed(final int weatherWindSpeed) { this.weatherWindSpd = weatherWindSpeed; } public void setWeek(final DateTime dt) { final int firstDayOfWeek = _prefStore.getInt(ITourbookPreferences.CALENDAR_WEEK_FIRST_DAY_OF_WEEK); final int minimalDaysInFirstWeek = _prefStore .getInt(ITourbookPreferences.CALENDAR_WEEK_MIN_DAYS_IN_FIRST_WEEK); _calendar.setFirstDayOfWeek(firstDayOfWeek); _calendar.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek); _calendar.set(dt.getYear(), dt.getMonthOfYear() - 1, dt.getDayOfMonth()); startWeek = (short) _calendar.get(Calendar.WEEK_OF_YEAR); startWeekYear = (short) Util.getYearForWeek(_calendar); } /** * @param year * @param month * month starts with 1 * @param tourDay */ public void setWeek(final short year, final short month, final short tourDay) { final int firstDayOfWeek = _prefStore.getInt(ITourbookPreferences.CALENDAR_WEEK_FIRST_DAY_OF_WEEK); final int minimalDaysInFirstWeek = _prefStore .getInt(ITourbookPreferences.CALENDAR_WEEK_MIN_DAYS_IN_FIRST_WEEK); _calendar.setFirstDayOfWeek(firstDayOfWeek); _calendar.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek); _calendar.set(year, month - 1, tourDay); startWeek = (short) _calendar.get(Calendar.WEEK_OF_YEAR); startWeekYear = (short) Util.getYearForWeek(_calendar); } /** * Set world positions which are cached * * @param worldPositions * @param zoomLevel * @param projectionId */ public void setWorldPixelForTour(final Point[] worldPositions, final int zoomLevel, final String projectionId) { _tourWorldPosition.put(projectionId.hashCode() + zoomLevel, worldPositions); } /** * Set world positions which are cached * * @param worldPositions * @param zoomLevel * @param projectionId */ public void setWorldPixelForWayPoints(final HashMap<Integer, Point> worldPositions, final int zoomLevel, final String projectionId) { _twpWorldPosition.put(projectionId.hashCode() + zoomLevel, worldPositions); } @Override public String toString() { return new StringBuilder()// .append("[TourData]") //$NON-NLS-1$ .append(" tourId:") //$NON-NLS-1$ .append(tourId).append(" object:") //$NON-NLS-1$ .append(super.toString()).append(" identityHashCode:") //$NON-NLS-1$ .append(System.identityHashCode(this)).toString(); } public String toStringWithHash() { final StringBuilder sb = new StringBuilder(); sb.append(" tourId:");//$NON-NLS-1$ sb.append(tourId); sb.append(" identityHashCode:");//$NON-NLS-1$ sb.append(System.identityHashCode(this)); return sb.toString(); } @Override public String toXml() { try { final JAXBContext context = JAXBContext.newInstance(this.getClass()); final Marshaller marshaller = context.createMarshaller(); final StringWriter sw = new StringWriter(); marshaller.marshal(this, sw); return sw.toString(); } catch (final JAXBException e) { e.printStackTrace(); } return null; } }