import tim.prune.gui.SidebarController;
import tim.prune.gui.UndoManager;
import tim.prune.gui.Viewport;
+import tim.prune.gui.colour.ColourerCaretaker;
+import tim.prune.gui.colour.PointColourer;
import tim.prune.load.FileLoader;
import tim.prune.load.JpegLoader;
import tim.prune.load.MediaLinkInfo;
private JpegLoader _jpegLoader = null;
private FileSaver _fileSaver = null;
private UndoStack _undoStack = null;
+ private ColourerCaretaker _colCaretaker = null;
private boolean _mangleTimestampsConfirmed = false;
private Viewport _viewport = null;
private ArrayList<File> _dataFiles = null;
_track = new Track();
_trackInfo = new TrackInfo(_track);
FunctionLibrary.initialise(this);
+ _colCaretaker = new ColourerCaretaker(this);
+ UpdateMessageBroker.addSubscriber(_colCaretaker);
+ _colCaretaker.setColourer(Config.getPointColourer());
}
return _undoStack;
}
+ /**
+ * Update the system's point colourer using the one in the Config
+ */
+ public void updatePointColourer()
+ {
+ if (_colCaretaker != null) {
+ _colCaretaker.setColourer(Config.getPointColourer());
+ }
+ }
+
+ /**
+ * @return colourer object, or null
+ */
+ public PointColourer getPointColourer()
+ {
+ if (_colCaretaker == null) {return null;}
+ return _colCaretaker.getColourer();
+ }
/**
* Show the specified tip if appropriate
int audioIndex = _trackInfo.getAudioList().getAudioIndex(currentPoint.getAudio());
DataPoint nextTrackPoint = _trackInfo.getTrack().getNextTrackPoint(pointIndex + 1);
// Construct Undo object
- UndoOperation undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
+ UndoDeletePoint undo = new UndoDeletePoint(pointIndex, currentPoint, photoIndex,
audioIndex, nextTrackPoint != null && nextTrackPoint.getSegmentStart());
+ undo.setAtBoundaryOfSelectedRange(pointIndex == _trackInfo.getSelection().getStart() ||
+ pointIndex == _trackInfo.getSelection().getEnd());
// call track to delete point
if (_trackInfo.deletePoint())
{
* @param inPoint point to add
*/
public void createPoint(DataPoint inPoint)
+ {
+ createPoint(inPoint, true);
+ }
+
+ /**
+ * Create a new point at the end of the track
+ * @param inPoint point to add
+ * @param inNewSegment true for a single point, false for a continuation
+ */
+ public void createPoint(DataPoint inPoint, boolean inNewSegment)
{
// create undo object
UndoCreatePoint undo = new UndoCreatePoint();
_undoStack.add(undo);
// add point to track
- inPoint.setSegmentStart(true);
+ inPoint.setSegmentStart(inNewSegment);
_track.appendPoints(new DataPoint[] {inPoint});
// ensure track's field list contains point's fields
_track.extendFieldList(inPoint.getFieldList());
import tim.prune.function.charts.Charter;
import tim.prune.function.compress.CompressTrackFunction;
import tim.prune.function.compress.MarkPointsInRectangleFunction;
+import tim.prune.function.deletebydate.DeleteByDateFunction;
import tim.prune.function.distance.DistanceFunction;
import tim.prune.function.edit.PointNameEditor;
import tim.prune.function.estimate.EstimateTime;
public static GenericFunction FUNCTION_IMPORTBABEL = null;
public static GenericFunction FUNCTION_SAVECONFIG = null;
public static GenericFunction FUNCTION_EDIT_WAYPOINT_NAME = null;
- public static RearrangeWaypointsFunction FUNCTION_REARRANGE_WAYPOINTS = null;
+ public static GenericFunction FUNCTION_REARRANGE_WAYPOINTS = null;
+ public static GenericFunction FUNCTION_SELECT_SEGMENT = null;
public static GenericFunction FUNCTION_SPLIT_SEGMENTS = null;
public static GenericFunction FUNCTION_SEW_SEGMENTS = null;
public static GenericFunction FUNCTION_REARRANGE_PHOTOS = null;
public static GenericFunction FUNCTION_DELETE_RANGE = null;
public static GenericFunction FUNCTION_CROP_TRACK = null;
public static GenericFunction FUNCTION_MARK_IN_RECTANGLE = null;
- public static GenericFunction FUNCTION_INTERPOLATE = null;
+ public static GenericFunction FUNCTION_DELETE_BY_DATE = null;
+ public static SingleNumericParameterFunction FUNCTION_INTERPOLATE = null;
public static GenericFunction FUNCTION_LOOKUP_SRTM = null;
public static GenericFunction FUNCTION_DOWNLOAD_SRTM = null;
public static GenericFunction FUNCTION_LOOKUP_WIKIPEDIA = null;
public static GenericFunction FUNCTION_SET_DISK_CACHE = null;
public static GenericFunction FUNCTION_SET_PATHS = null;
public static GenericFunction FUNCTION_SET_COLOURS = null;
- public static GenericFunction FUNCTION_SET_LINE_WIDTH = null;
+ public static SingleNumericParameterFunction FUNCTION_SET_LINE_WIDTH = null;
public static GenericFunction FUNCTION_SET_LANGUAGE = null;
+ public static SingleNumericParameterFunction FUNCTION_SET_ALTITUDE_TOLERANCE = null;
public static GenericFunction FUNCTION_HELP = null;
public static GenericFunction FUNCTION_SHOW_KEYS = null;
public static GenericFunction FUNCTION_ABOUT = null;
FUNCTION_SAVECONFIG = new SaveConfig(inApp);
FUNCTION_EDIT_WAYPOINT_NAME = new PointNameEditor(inApp);
FUNCTION_REARRANGE_WAYPOINTS = new RearrangeWaypointsFunction(inApp);
+ FUNCTION_SELECT_SEGMENT = new SelectSegmentFunction(inApp);
FUNCTION_SPLIT_SEGMENTS = new SplitSegmentsFunction(inApp);
FUNCTION_SEW_SEGMENTS = new SewTrackSegmentsFunction(inApp);
FUNCTION_REARRANGE_PHOTOS = new RearrangePhotosFunction(inApp);
FUNCTION_DELETE_RANGE = new DeleteSelectedRangeFunction(inApp);
FUNCTION_CROP_TRACK = new CropToSelection(inApp);
FUNCTION_MARK_IN_RECTANGLE = new MarkPointsInRectangleFunction(inApp);
+ FUNCTION_DELETE_BY_DATE = new DeleteByDateFunction(inApp);
FUNCTION_INTERPOLATE = new InterpolateFunction(inApp);
FUNCTION_LOOKUP_SRTM = new LookupSrtmFunction(inApp);
FUNCTION_DOWNLOAD_SRTM = new DownloadSrtmFunction(inApp);
FUNCTION_SET_COLOURS = new SetColours(inApp);
FUNCTION_SET_LINE_WIDTH = new SetLineWidth(inApp);
FUNCTION_SET_LANGUAGE = new SetLanguage(inApp);
+ FUNCTION_SET_ALTITUDE_TOLERANCE = new SetAltitudeTolerance(inApp);
FUNCTION_HELP = new HelpScreen(inApp);
FUNCTION_SHOW_KEYS = new ShowKeysScreen(inApp);
FUNCTION_ABOUT = new AboutScreen(inApp);
import java.awt.event.WindowAdapter;
import java.awt.BorderLayout;
import java.awt.Component;
+import java.awt.Image;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileNotFoundException;
public class GpsPrune
{
/** Version number of application, used in about screen and for version check */
- public static final String VERSION_NUMBER = "16.3";
+ public static final String VERSION_NUMBER = "17";
/** Build number, just used for about screen */
- public static final String BUILD_NUMBER = "303c";
+ public static final String BUILD_NUMBER = "320";
/** Static reference to App object */
private static App APP = null;
// Avoid automatically shutting down if window closed
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
- // set icon
- try {
- frame.setIconImage(IconManager.getImageIcon(IconManager.WINDOW_ICON).getImage());
+ // set window icons of different resolutions (1.6+)
+ try
+ {
+ ArrayList<Image> icons = new ArrayList<Image>();
+ String[] resolutions = {"_16", "_20", "_32", "_64", "_128"};
+ for (String r : resolutions) {
+ icons.add(IconManager.getImageIcon(IconManager.WINDOW_ICON + r + ".png").getImage());
+ }
+ Class<?> d = java.awt.Window.class;
+ // This is the same as frame.setIconImages(icons) but is compilable also for java1.5 where this isn't available
+ d.getDeclaredMethod("setIconImages", new Class[]{java.util.List.class}).invoke(frame, icons);
+ }
+ catch (Exception e)
+ {
+ // setting a list of icon images didn't work, so try with just one image instead
+ try {
+ frame.setIconImage(IconManager.getImageIcon(IconManager.WINDOW_ICON + "_16.png").getImage());
+ }
+ catch (Exception e2) {}
}
- catch (Exception e) {} // ignore
// Set up drag-and-drop handler to accept dropped files
frame.setTransferHandler(new FileDropHandler(APP));
*/
public abstract class UpdateMessageBroker
{
- private static final int MAXIMUM_NUMBER_SUBSCRIBERS = 6;
+ private static final int MAXIMUM_NUMBER_SUBSCRIBERS = 7;
/** Array of all subscribers */
private static DataSubscriber[] _subscribers = new DataSubscriber[MAXIMUM_NUMBER_SUBSCRIBERS];
/** Counter of the number of subscribers added so far */
import tim.prune.data.RecentFileList;
import tim.prune.data.UnitSet;
import tim.prune.data.UnitSetLibrary;
+import tim.prune.gui.colour.ColourerFactory;
+import tim.prune.gui.colour.PointColourer;
import tim.prune.gui.map.MapSourceLibrary;
private static Properties _configValues = null;
/** Colour scheme object is also part of config */
private static ColourScheme _colourScheme = new ColourScheme();
+ /** Point colourer object, if any */
+ private static PointColourer _pointColourer = null;
/** Recently-used file list */
private static RecentFileList _recentFiles = new RecentFileList();
/** Current unit set */
public static final String KEY_EXIFTOOL_PATH = "prune.exiftoolpath";
/** Key for colour scheme */
public static final String KEY_COLOUR_SCHEME = "prune.colourscheme";
+ /** Key for point colourer */
+ public static final String KEY_POINT_COLOURER = "prune.pointcolourer";
/** Key for line width used for drawing */
public static final String KEY_LINE_WIDTH = "prune.linewidth";
/** Key for kml track colour */
public static final String KEY_ESTIMATION_PARAMS = "prune.estimationparams";
/** Key for 3D exaggeration factor */
public static final String KEY_HEIGHT_EXAGGERATION = "prune.heightexaggeration";
+ /** Key for terrain grid size */
+ public static final String KEY_TERRAIN_GRID_SIZE = "prune.terraingridsize";
+ /** Key for altitude tolerance */
+ public static final String KEY_ALTITUDE_TOLERANCE = "prune.altitudetolerance";
/** Initialise the default properties */
// Save all properties from file
_configValues.putAll(props);
_colourScheme.loadFromHex(_configValues.getProperty(KEY_COLOUR_SCHEME));
+ _pointColourer = ColourerFactory.createColourer(_configValues.getProperty(KEY_POINT_COLOURER));
_recentFiles = new RecentFileList(_configValues.getProperty(KEY_RECENT_FILES));
_unitSet = UnitSetLibrary.getUnitSet(_configValues.getProperty(KEY_UNITSET_KEY));
// Adjust map source index if necessary
props.put(KEY_AUTOSAVE_SETTINGS, "0"); // autosave false by default
props.put(KEY_UNITSET_KEY, "unitset.kilometres"); // metric by default
props.put(KEY_HEIGHT_EXAGGERATION, "100"); // 100%, no exaggeration
+ props.put(KEY_TERRAIN_GRID_SIZE, "50");
+ props.put(KEY_ALTITUDE_TOLERANCE, "0"); // 0, all exact as before
return props;
}
return _colourScheme;
}
+ /**
+ * @return the current point colourer, if any
+ */
+ public static PointColourer getPointColourer()
+ {
+ return _pointColourer;
+ }
+
/**
* @return list of recently used files
*/
setConfigString(KEY_COLOUR_SCHEME, _colourScheme.toString());
}
+ /**
+ * Update the point colourer from the given colourer
+ * @param inColourer point colourer object, or null
+ */
+ public static void updatePointColourer(PointColourer inColourer)
+ {
+ _pointColourer = inColourer;
+ setConfigString(KEY_POINT_COLOURER, ColourerFactory.PointColourerToString(_pointColourer));
+ }
+
/**
* @return the current unit set
*/
return _unitSet;
}
+ /**
+ * @param inIndex index of unit set to select
+ */
public static void selectUnitSet(int inIndex)
{
_unitSet = UnitSetLibrary.getUnitSet(inIndex);
package tim.prune.data;
+import tim.prune.config.Config;
+
/**
* Represents a range of altitudes, taking units into account.
* Values assumed to be >= 0.
{
/** Range of altitudes in metres */
private IntegerRange _range = new IntegerRange();
- /** Empty flag */
- private boolean _empty;
+ /** Flag for whether previous value exists or not */
+ private boolean _gotPreviousValue;
/** Previous metric value */
- private int _prevValue;
+ private int _previousValue;
/** Total climb in metres */
- private double _climb;
+ private int _climb;
/** Total descent in metres */
- private double _descent;
+ private int _descent;
+ /** Flags for whether minimum or maximum has been found */
+ private boolean _gotPreviousMinimum = false, _gotPreviousMaximum = false;
+ /** Integer values of previous minimum and maximum, if any */
+ private int _previousExtreme = 0;
/**
public void clear()
{
_range.clear();
- _climb = 0.0;
- _descent = 0.0;
- _empty = true;
- _prevValue = 0;
+ _climb = _descent = 0;
+ _gotPreviousValue = false;
+ _previousValue = 0;
+ _gotPreviousMinimum = _gotPreviousMaximum = false;
+ _previousExtreme = 0;
}
*/
public void addValue(Altitude inAltitude)
{
+ final int wiggleLimit = Config.getConfigInt(Config.KEY_ALTITUDE_TOLERANCE) / 100;
+
if (inAltitude != null && inAltitude.isValid())
{
int altValue = (int) inAltitude.getMetricValue();
_range.addValue(altValue);
// Compare with previous value if any
- if (!_empty)
+ if (_gotPreviousValue)
+ {
+ if (altValue != _previousValue)
+ {
+ // Got an altitude value which is different from the previous one
+ final boolean locallyUp = (altValue > _previousValue);
+ final boolean overallUp = _gotPreviousMinimum && _previousValue > _previousExtreme;
+ final boolean overallDn = _gotPreviousMaximum && _previousValue < _previousExtreme;
+ final boolean moreThanWiggle = Math.abs(altValue - _previousValue) > wiggleLimit;
+ // Do we know whether we're going up or down yet?
+ if (!_gotPreviousMinimum && !_gotPreviousMaximum)
+ {
+ // we don't know whether we're going up or down yet - check limit
+ if (moreThanWiggle)
+ {
+ if (locallyUp) {_gotPreviousMinimum = true;}
+ else {_gotPreviousMaximum = true;}
+ _previousExtreme = _previousValue;
+ _previousValue = altValue;
+ _gotPreviousValue = true;
+ }
+ }
+ else if (overallUp)
+ {
+ if (locallyUp) {
+ // we're still going up - do nothing
+ _previousValue = altValue;
+ }
+ else if (moreThanWiggle)
+ {
+ // we're going up but have dropped over a maximum
+ // Add the climb from _previousExtreme up to _previousValue
+ _climb += (_previousValue - _previousExtreme);
+ _previousExtreme = _previousValue;
+ _gotPreviousMinimum = false; _gotPreviousMaximum = true;
+ _previousValue = altValue;
+ _gotPreviousValue = true;
+ }
+ }
+ else if (overallDn)
+ {
+ if (locallyUp) {
+ if (moreThanWiggle)
+ {
+ // we're going down but have climbed up from a minimum
+ // Add the descent from _previousExtreme down to _previousValue
+ _descent += (_previousExtreme - _previousValue);
+ _previousExtreme = _previousValue;
+ _gotPreviousMinimum = true; _gotPreviousMaximum = false;
+ _previousValue = altValue;
+ _gotPreviousValue = true;
+ }
+ }
+ else {
+ // we're still going down - do nothing
+ _previousValue = altValue;
+ _gotPreviousValue = true;
+ }
+ }
+ // TODO: Behaviour when WIGGLE_LIMIT == 0 should be same as before, all differences cumulated
+ }
+ }
+ else
{
- if (altValue > _prevValue)
- _climb += (altValue - _prevValue);
- else
- _descent += (_prevValue - altValue);
+ // we haven't got a previous value at all, so it's the start of a new segment
+ _previousValue = altValue;
+ _gotPreviousValue = true;
}
- _prevValue = altValue;
- _empty = false;
+
+// if (!_empty)
+// {
+// if (altValue > _previousValue)
+// _climb += (altValue - _previousValue);
+// else
+// _descent += (_previousValue - altValue);
+// }
+// _previousValue = altValue;
+// _empty = false;
}
}
*/
public void ignoreValue(Altitude inAltitude)
{
- // If we set the empty flag to true, that has the same effect as restarting a segment
- _empty = true;
- addValue(inAltitude);
+ // Process the previous value, if any, to update climb/descent as that's the end of the previous segment
+ if (_gotPreviousValue && _gotPreviousMinimum && _previousValue > _previousExtreme) {
+ _climb += (_previousValue - _previousExtreme);
+ }
+ else if (_gotPreviousValue && _gotPreviousMaximum && _previousValue < _previousExtreme) {
+ _descent += (_previousExtreme - _previousValue);
+ }
+ // Eliminate the counting values to start the new segment
+ _gotPreviousMinimum = _gotPreviousMaximum = false;
+ _gotPreviousValue = false;
+ // Now process this value if there is one
+ if (inAltitude != null && inAltitude.isValid())
+ {
+ final int altValue = (int) inAltitude.getMetricValue();
+ _range.addValue(altValue);
+ _previousValue = altValue;
+ _gotPreviousValue = true;
+ }
}
/**
*/
public int getClimb(Unit inUnit)
{
- return (int) (_climb * inUnit.getMultFactorFromStd());
+ // May need to add climb from last segment
+ int lastSegmentClimb = 0;
+ if (_gotPreviousValue && _gotPreviousMinimum && _previousValue > _previousExtreme) {
+ lastSegmentClimb = _previousValue - _previousExtreme;
+ }
+ return (int) ((_climb + lastSegmentClimb) * inUnit.getMultFactorFromStd());
}
/**
*/
public int getDescent(Unit inUnit)
{
- return (int) (_descent * inUnit.getMultFactorFromStd());
+ // May need to add descent from last segment
+ int lastSegmentDescent = 0;
+ if (_gotPreviousValue && _gotPreviousMaximum && _previousValue < _previousExtreme) {
+ lastSegmentDescent = _previousExtreme - _previousValue;
+ }
+ return (int) ((_descent + lastSegmentDescent) * inUnit.getMultFactorFromStd());
}
/**
*/
public double getMetricHeightDiff()
{
- return _climb - _descent;
+ return getClimb(UnitSetLibrary.UNITS_METRES) - getDescent(UnitSetLibrary.UNITS_METRES);
}
}
{
int i = inIndex + 1;
DataPoint point = null;
- while ((point=inTrack.getPoint(i)) != null && !point.getSegmentStart()) {
+ while ((point=inTrack.getPoint(i)) != null && (point.isWaypoint() || !point.getSegmentStart())) {
i++;
}
return Math.min(i, inTrack.getNumPoints()-1);
{
int i = inIndex - 1;
DataPoint point = null;
- while ((point=inTrack.getPoint(i)) != null && !point.getSegmentStart()) {
+ while ((point=inTrack.getPoint(i)) != null && (point.isWaypoint() || !point.getSegmentStart())) {
i--;
}
- return Math.max(i, 0);
+ // Have we gone past the beginning of the track?
+ i = Math.max(i, 0);
+ // count forwards past the waypoints if necessary
+ while ((point=inTrack.getPoint(i)) != null && point.isWaypoint()) {
+ i++;
+ }
+ return i;
+ }
+
+ /**
+ * Find the index of the last track point in the current segment
+ * @param inTrack track object
+ * @param inIndex current index
+ * @return index of next segment end
+ */
+ public static int getNextSegmentEnd(Track inTrack, int inIndex)
+ {
+ // First, go to start of following segment, or the end of the track
+ int i = getNextSegmentStart(inTrack, inIndex);
+ // If it's the next segment, subtract one
+ DataPoint point = inTrack.getPoint(i);
+ if (point == null || point.getSegmentStart())
+ {
+ i--;
+ }
+ // Now we may be on a waypoint, so count back to get the last track point
+ while ((point=inTrack.getPoint(i)) != null && point.isWaypoint()) {
+ i--;
+ }
+ return Math.min(i, inTrack.getNumPoints()-1);
+ }
+
+
+ /**
+ * @param inTrack track object
+ * @return true if there is at least one waypoint with a timestamp
+ */
+ public static boolean haveWaypointsGotTimestamps(Track inTrack)
+ {
+ if (inTrack != null)
+ {
+ for (int i=0; i<inTrack.getNumPoints(); i++)
+ {
+ DataPoint p = inTrack.getPoint(i);
+ if (p != null && p.isWaypoint() && p.hasTimestamp())
+ {
+ return true;
+ }
+ }
+ }
+ return false;
}
}
// parse fields according to number found
_degrees = (int) fields[0];
_asDouble = _degrees;
- _originalFormat = hasCardinal?FORMAT_DEG:FORMAT_DEG_WITHOUT_CARDINAL;
+ _originalFormat = hasCardinal ? FORMAT_DEG : FORMAT_DEG_WITHOUT_CARDINAL;
_fracDenom = 10;
if (numFields == 2)
{
_asDouble = 1.0 * _degrees + (_minutes / 60.0);
}
}
+ // Check for exponential degrees like 1.3E-6
+ else if (numFields == 3 && !otherDelims[1] && otherDelims[2] && isJustNumber(inString))
+ {
+ _originalFormat = FORMAT_DEG;
+ _asDouble = Math.abs(Double.parseDouble(inString)); // must succeed if isJustNumber has given true
+ // now we can ignore the fields and just use this double
+ _degrees = (int) _asDouble;
+ double numMins = (_asDouble - _degrees) * 60.0;
+ _minutes = (int) numMins;
+ double numSecs = (numMins - _minutes) * 60.0;
+ _seconds = (int) numSecs;
+ _fracs = (int) ((numSecs - _seconds) * 10);
+ }
// Differentiate between d-m.f and d-m-s using . or ,
else if (numFields == 3 && !otherDelims[2])
{
/**
* Get the cardinal from the given character
- * @param inFirstChar first character from file
- * @param inLastChar last character from file
+ * @param inFirstChar first character from string
+ * @param inLastChar last character from string
*/
- protected int getCardinal(char inFirstChar, char inLastChar)
+ private int getCardinal(char inFirstChar, char inLastChar)
{
// Try leading character first
int cardinal = getCardinal(inFirstChar);
return _sources.get(inIndex);
}
+ /**
+ * Get the SourceInfo object (if any) for the given point
+ * @param inPoint point object
+ * @return SourceInfo object if there is one, otherwise null
+ */
+ public SourceInfo getSourceForPoint(DataPoint inPoint)
+ {
+ for (SourceInfo source : _sources) {
+ if (source.getIndex(inPoint) >= 0) {
+ return source;
+ }
+ }
+ return null;
+ }
+
/**
* Clone contents of file info
*/
--- /dev/null
+package tim.prune.data;
+
+/**
+ * Abstract class to hold static calculation functions
+ * for gradient (like glide slope)
+ */
+public abstract class GradientCalculator
+{
+ /**
+ * Calculate the gradient value of the track at the specified index
+ * @param inTrack track object
+ * @param inIndex index of point to calculate gradient for
+ * @param inValue object in which to place result of calculation
+ */
+ public static void calculateGradient(Track inTrack, int inIndex, SpeedValue inValue)
+ {
+ inValue.setInvalid();
+ if (inTrack == null || inIndex < 0 || inValue == null)
+ {
+ System.err.println("Cannot calculate gradient for index " + inIndex);
+ return;
+ }
+
+ // If no altitude or it's a waypoint then no gradient either
+ DataPoint point = inTrack.getPoint(inIndex);
+ if (point == null || !point.hasAltitude() || point.isWaypoint()) {
+ return;
+ }
+
+ // If the point has horizontal and vertical speeds already then just use those
+ if (point.hasHSpeed() && point.hasVSpeed()) {
+ inValue.setValue(point.getVSpeed().getValueInMetresPerSec() / point.getHSpeed().getValueInMetresPerSec());
+ }
+ else if (!point.getSegmentStart())
+ {
+ // Use the previous track point and the next track point
+ DataPoint p = inTrack.getPreviousTrackPoint(inIndex-1);
+ DataPoint q = inTrack.getNextTrackPoint(inIndex+1);
+ if (p != null && q != null && !q.getSegmentStart()
+ && p.hasAltitude() && q.hasAltitude())
+ {
+ final double horizRads = DataPoint.calculateRadiansBetween(p, point) +
+ DataPoint.calculateRadiansBetween(point, q);
+ final double horizDist = Distance.convertRadiansToDistance(horizRads, UnitSetLibrary.UNITS_METRES);
+ final double heightDiff = q.getAltitude().getMetricValue() - p.getAltitude().getMetricValue();
+ // Get gradient in radians
+ final double gradient = Math.atan2(heightDiff, horizDist);
+ inValue.setValue(gradient);
+ }
+ }
+ // otherwise, just leave value as invalid
+ }
+}
public int getIndex(DataPoint inPoint)
{
int idx = -1;
- for (int i=0; i<_points.length && (idx < 0); i++) {
- if (_points[i] == inPoint) {idx = i;}
+ for (int i=0; i<_points.length; i++)
+ {
+ if (_points[i] == inPoint) {
+ idx = i;
+ break;
+ }
}
if (idx == -1) {return idx;} // point not found
if (_pointIndices == null) {return idx;} // All points loaded
*/
public static void calculateSpeed(Track inTrack, int inIndex, SpeedValue inValue)
{
- if (inTrack == null || inIndex < 0 || inValue == null) {
+ inValue.setInvalid();
+ if (inTrack == null || inIndex < 0 || inValue == null)
+ {
System.err.println("Cannot calculate speed for index " + inIndex);
return;
}
- inValue.setInvalid();
DataPoint point = inTrack.getPoint(inIndex);
if (point == null) {return;}
private boolean _valid = false;
private long _milliseconds = 0L;
private String _text = null;
- private String _timeText = null;
- private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateTimeInstance();
+ private static final DateFormat DEFAULT_DATETIME_FORMAT = DateFormat.getDateTimeInstance();
+ private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
private static final DateFormat DEFAULT_TIME_FORMAT = DateFormat.getTimeInstance();
+ private static boolean MillisAddedToTimeFormat = false;
private static final DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ private static final DateFormat ISO_8601_FORMAT_WITH_MILLIS = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
private static final DateFormat ISO_8601_FORMAT_NOZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private static DateFormat[] ALL_DATE_FORMATS = null;
private static Calendar CALENDAR = null;
private static long TWENTY_YEARS_IN_SECS = 0L;
private static final long GARTRIP_OFFSET = 631065600L;
- /** Specifies original timestamp format */
- public static final int FORMAT_ORIGINAL = 0;
- /** Specifies locale-dependent timestamp format */
- public static final int FORMAT_LOCALE = 1;
- /** Specifies ISO 8601 timestamp format */
- public static final int FORMAT_ISO_8601 = 2;
+ /** Possible formats for parsing and displaying timestamps */
+ public enum Format
+ {
+ ORIGINAL,
+ LOCALE,
+ ISO8601
+ }
/** Identifier for the parsing strategy to use */
private enum ParseType
FIXED_FORMAT4,
FIXED_FORMAT5,
FIXED_FORMAT6,
+ FIXED_FORMAT7,
GENERAL_STRING
}
/** Array of parse types to loop through (first one is changed to last successful type) */
private static ParseType[] ALL_PARSE_TYPES = {ParseType.NONE, ParseType.ISO8601_FRACTIONAL, ParseType.LONG,
ParseType.FIXED_FORMAT0, ParseType.FIXED_FORMAT1, ParseType.FIXED_FORMAT2, ParseType.FIXED_FORMAT3,
- ParseType.FIXED_FORMAT4, ParseType.FIXED_FORMAT5, ParseType.FIXED_FORMAT6, ParseType.GENERAL_STRING};
+ ParseType.FIXED_FORMAT4, ParseType.FIXED_FORMAT5, ParseType.FIXED_FORMAT6, ParseType.FIXED_FORMAT7,
+ ParseType.GENERAL_STRING};
// Static block to initialise offsets
static
TWENTY_YEARS_IN_SECS = (MSECS_SINCE_1970 - MSECS_SINCE_1990) / 1000L;
// Set timezone for output
ISO_8601_FORMAT.setTimeZone(gmtZone);
- DEFAULT_DATE_FORMAT.setTimeZone(gmtZone);
+ ISO_8601_FORMAT_WITH_MILLIS.setTimeZone(gmtZone);
+ DEFAULT_DATETIME_FORMAT.setTimeZone(gmtZone);
// Date formats
ALL_DATE_FORMATS = new DateFormat[] {
- DEFAULT_DATE_FORMAT,
+ DEFAULT_DATETIME_FORMAT,
new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"),
new SimpleDateFormat("HH:mm:ss dd MMM yyyy"),
new SimpleDateFormat("dd MMM yyyy HH:mm:ss"),
+ new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss"),
new SimpleDateFormat("yyyy MMM dd HH:mm:ss"),
ISO_8601_FORMAT, ISO_8601_FORMAT_NOZ
};
+ for (DateFormat df : ALL_DATE_FORMATS) {
+ df.setLenient(false);
+ }
}
public Timestamp(String inString)
{
_valid = false;
+ _text = null;
if (inString != null && !inString.equals(""))
{
// Try each of the parse types in turn
{
ALL_PARSE_TYPES[0] = type;
_valid = true;
+ _text = inString;
return;
}
}
case FIXED_FORMAT4: return parseString(inString, ALL_DATE_FORMATS[4]);
case FIXED_FORMAT5: return parseString(inString, ALL_DATE_FORMATS[5]);
case FIXED_FORMAT6: return parseString(inString, ALL_DATE_FORMATS[6]);
+ case FIXED_FORMAT7: return parseString(inString, ALL_DATE_FORMATS[7]);
case GENERAL_STRING:
if (inString.length() == 19)
*/
private boolean parseString(String inString, DateFormat inDateFormat)
{
- inDateFormat.setLenient(false);
ParsePosition pPos = new ParsePosition(0);
Date date = inDateFormat.parse(inString, pPos);
if (date != null && inString.length() == pPos.getIndex()) // require use of _all_ the string, not just the beginning
return _valid;
}
+ /**
+ * @return true if the timestamp has non-zero milliseconds
+ */
+ public boolean hasMilliseconds()
+ {
+ return isValid() && (_milliseconds % 1000L) > 0;
+ }
/**
* @param inOther other Timestamp
* @return true if this one is at least a second after the other
*/
public String getText()
{
- return getText(FORMAT_LOCALE);
+ return getText(Format.LOCALE);
}
/**
* @param inFormat format of timestamp
* @return Description of timestamp in required format
*/
- public String getText(int inFormat)
+ public String getText(Format inFormat)
{
if (!_valid) {return "";}
- if (inFormat == FORMAT_ISO_8601) {
- return format(ISO_8601_FORMAT);
- }
- if (_text == null) {
- _text = format(DEFAULT_DATE_FORMAT);
+ switch (inFormat)
+ {
+ case ORIGINAL:
+ if (_text != null) {return _text;}
+ // otherwise fallthrough to default
+ //$FALL-THROUGH$
+ case LOCALE:
+ return format(DEFAULT_DATETIME_FORMAT);
+ case ISO8601:
+ return format(hasMilliseconds() ? ISO_8601_FORMAT_WITH_MILLIS : ISO_8601_FORMAT);
}
return _text;
}
+ /**
+ * @return date part of timestamp in locale-specific format
+ */
+ public String getDateText()
+ {
+ if (!_valid) return "";
+ return format(DEFAULT_DATE_FORMAT);
+ }
+
/**
* @return Description of time part of timestamp in locale-specific format
*/
public String getTimeText()
{
- if (_timeText == null)
+ if (!_valid) return "";
+ // Maybe we should add milliseconds to this format?
+ if (hasMilliseconds() && !MillisAddedToTimeFormat)
{
- if (_valid) {
- _timeText = format(DEFAULT_TIME_FORMAT);
+ try
+ {
+ SimpleDateFormat sdf = (SimpleDateFormat) DEFAULT_TIME_FORMAT;
+ String pattern = sdf.toPattern();
+ if (pattern.indexOf("ss") > 0 && pattern.indexOf("SS") < 0)
+ {
+ sdf.applyPattern(pattern.replaceFirst("s+", "$0.SSS"));
+ MillisAddedToTimeFormat = true;
+ }
}
- else _timeText = "";
+ catch (ClassCastException cce) {}
}
- return _timeText;
+ return format(DEFAULT_TIME_FORMAT);
}
/**
_dataPoints[pointIndex] = point;
pointIndex++;
}
+ else
+ {
+ // TODO: Maybe report this somehow?
+ // System.out.println("point is not valid!");
+ }
}
_numPoints = pointIndex;
// Set first track point to be start of segment
}
- /**
- * Collect all waypoints to the start or end of the track
- * @param inAtStart true to collect at start, false for end
- * @return true if successful, false if no change
- */
- public boolean collectWaypoints(boolean inAtStart)
- {
- // Check for mixed data, numbers of waypoints & nons
- int numWaypoints = 0, numNonWaypoints = 0;
- boolean wayAfterNon = false, nonAfterWay = false;
- DataPoint[] waypoints = new DataPoint[_numPoints];
- DataPoint[] nonWaypoints = new DataPoint[_numPoints];
- DataPoint point = null;
- for (int i=0; i<_numPoints; i++)
- {
- point = _dataPoints[i];
- if (point.isWaypoint())
- {
- waypoints[numWaypoints] = point;
- numWaypoints++;
- wayAfterNon |= (numNonWaypoints > 0);
- }
- else
- {
- nonWaypoints[numNonWaypoints] = point;
- numNonWaypoints++;
- nonAfterWay |= (numWaypoints > 0);
- }
- }
- // Exit if the data is already in the specified order
- if (numWaypoints == 0 || numNonWaypoints == 0
- || (inAtStart && !wayAfterNon && nonAfterWay)
- || (!inAtStart && wayAfterNon && !nonAfterWay))
- {
- return false;
- }
-
- // Copy the arrays back into _dataPoints in the specified order
- if (inAtStart)
- {
- System.arraycopy(waypoints, 0, _dataPoints, 0, numWaypoints);
- System.arraycopy(nonWaypoints, 0, _dataPoints, numWaypoints, numNonWaypoints);
- }
- else
- {
- System.arraycopy(nonWaypoints, 0, _dataPoints, 0, numNonWaypoints);
- System.arraycopy(waypoints, 0, _dataPoints, numNonWaypoints, numWaypoints);
- }
- // needs to be scaled again
- _scaled = false;
- UpdateMessageBroker.informSubscribers();
- return true;
- }
-
-
/**
* Interleave all waypoints by each nearest track point
* @return true if successful, false if no change
-package tim.prune.function;
+package tim.prune.data.sort;
import java.util.Comparator;
import tim.prune.data.DataPoint;
+
/**
* Class for comparing photos to sort them by name or timestamp
*/
public class PhotoComparer implements Comparator<DataPoint>
{
- public enum SortMode {
- SORTBY_NAME, SORTBY_TIME
- };
-
/** Sort mode */
- private SortMode _sortMode = SortMode.SORTBY_NAME;
+ private SortMode _sortMode;
+
/**
* Constructor
if (!inP2.hasTimestamp()) return -1;
if (!inP1.hasTimestamp()) return 1;
// Compare the timestamps
- long secDiff = inP1.getPhoto().getTimestamp().getSecondsSince(inP2.getPhoto().getTimestamp());
+ long secDiff = inP1.getPhoto().getTimestamp().getMillisecondsSince(inP2.getPhoto().getTimestamp());
return (secDiff<0?-1:(secDiff==0?0:1));
}
--- /dev/null
+package tim.prune.data.sort;
+
+/**
+ * Enumeration for possible sort modes
+ */
+public enum SortMode
+{
+ DONT_SORT,
+ SORTBY_NAME,
+ SORTBY_TIME
+}
--- /dev/null
+package tim.prune.data.sort;
+
+import java.util.Comparator;
+
+import tim.prune.data.DataPoint;
+
+
+/**
+ * Class for comparing waypoints to sort them by name or timestamp
+ */
+public class WaypointComparer implements Comparator<DataPoint>
+{
+ /** Sort mode */
+ private SortMode _sortMode;
+
+
+ /**
+ * Constructor
+ * @param inMode sort mode
+ */
+ public WaypointComparer(SortMode inMode)
+ {
+ _sortMode = inMode;
+ }
+
+ /**
+ * Main compare method
+ */
+ public int compare(DataPoint inP1, DataPoint inP2)
+ {
+ if (inP2 == null || !inP2.isWaypoint()) return -1; // all nulls at end
+ if (inP1 == null || !inP1.isWaypoint()) return 1;
+
+ // Sort by time, if requested
+ int result = 0;
+ if (_sortMode == SortMode.SORTBY_TIME) {
+ result = compareTimes(inP1, inP2);
+ }
+ // check names if names requested or if times didn't work
+ if (result == 0) {
+ result = inP1.getWaypointName().compareTo(inP2.getWaypointName());
+ }
+ // names and times equal, try longitude
+ if (result == 0) {
+ result = inP1.getLongitude().getDouble() > inP2.getLongitude().getDouble() ? 1 : -1;
+ }
+ // and latitude
+ if (result == 0) {
+ result = inP1.getLatitude().getDouble() > inP2.getLatitude().getDouble() ? 1 : -1;
+ }
+ return result;
+ }
+
+ /**
+ * Compare the timestamps of the two waypoints
+ * @param inP1 first point
+ * @param inP2 second point
+ * @return compare value (-1,0,1)
+ */
+ private int compareTimes(DataPoint inP1, DataPoint inP2)
+ {
+ // Points might not have timestamps
+ if (inP1.hasTimestamp() && !inP2.hasTimestamp()) return 1;
+ if (!inP1.hasTimestamp() && inP2.hasTimestamp()) return -1;
+ if (inP1.hasTimestamp() && inP2.hasTimestamp())
+ {
+ // Compare the timestamps
+ long secDiff = inP1.getTimestamp().getMillisecondsSince(inP2.getTimestamp());
+ return (secDiff<0?-1:(secDiff==0?0:1));
+ }
+ // neither has a timestamp
+ return 0;
+ }
+}
--- /dev/null
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.gui.WholeNumberField;
+
+/**
+ * First step of functions which just require a single numeric
+ * parameter in order to run
+ */
+public class ChooseSingleParameter extends GenericFunction
+{
+ /** Parent function which needs this parameter */
+ private SingleNumericParameterFunction _parent = null;
+ /** dialog */
+ private JDialog _dialog = null;
+ /** label which might need to be changed */
+ private JLabel _descLabel = null;
+ /** entry field */
+ private WholeNumberField _numberField = null;
+ /** ok button */
+ private JButton _okButton = null;
+
+
+ /** Constructor */
+ public ChooseSingleParameter(App inApp, SingleNumericParameterFunction inFunction)
+ {
+ super(inApp);
+ _parent = inFunction;
+ }
+
+ @Override
+ public String getNameKey() {
+ return _parent.getNameKey();
+ }
+
+ @Override
+ public void begin()
+ {
+ // Make dialog window
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(_parent.getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ // refresh and show the dialog
+ _descLabel.setText(I18nManager.getText(_parent.getDescriptionKey()));
+ int param = _parent.getCurrentParamValue();
+ if (param > 0) {
+ _numberField.setValue(param);
+ }
+ else {
+ _numberField.setText("");
+ }
+ _dialog.setVisible(true);
+ enableOkButton();
+ }
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private JPanel makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+ // label
+ _descLabel = new JLabel(I18nManager.getText(_parent.getDescriptionKey()));
+ dialogPanel.add(_descLabel, BorderLayout.NORTH);
+ // Centre panel with number entry field
+ JPanel centrePanel = new JPanel();
+ centrePanel.setLayout(new BorderLayout(8, 8));
+ _numberField = new WholeNumberField(4);
+ centrePanel.add(_numberField, BorderLayout.NORTH);
+ dialogPanel.add(centrePanel, BorderLayout.CENTER);
+
+ // Listener to enable/disable ok button
+ _numberField.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ enableOkButton();
+ }
+ });
+ _numberField.addKeyListener(new KeyAdapter() {
+ public void keyReleased(KeyEvent inE)
+ {
+ int eCode = inE.getKeyCode();
+ if (eCode == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ else if (eCode == KeyEvent.VK_ENTER) {finish();}
+ super.keyReleased(inE);
+ }
+ });
+
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ _okButton = new JButton(I18nManager.getText("button.ok"));
+ ActionListener okListener = new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ finish();
+ }
+ };
+ _okButton.addActionListener(okListener);
+ _okButton.setEnabled(false);
+ buttonPanel.add(_okButton);
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 15));
+ return dialogPanel;
+ }
+
+
+ /**
+ * Enable or disable the OK button as appropriate
+ */
+ private void enableOkButton()
+ {
+ _okButton.setEnabled(_numberField.getValue() >= _parent.getMinAllowedValue()
+ && _numberField.getValue() <= _parent.getMaxAllowedValue());
+ }
+
+ /**
+ * The OK button (or Enter) has been pressed
+ */
+ private void finish()
+ {
+ if (_numberField.getValue() >= _parent.getMinAllowedValue()
+ && _numberField.getValue() <= _parent.getMaxAllowedValue())
+ {
+ _parent.completeFunction(_numberField.getValue());
+ _dialog.dispose();
+ }
+ }
+}
// Set status label according to error or "none found", leave blank if ok
if (_errorMessage == null && _trackListModel.isEmpty()) {
- _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
+ _errorMessage = I18nManager.getText("dialog.wikipedia.nonefound");
}
_statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
}
import javax.swing.JOptionPane;
import tim.prune.App;
-import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Track;
/**
* Function to interpolate between the points in a range
*/
-public class InterpolateFunction extends GenericFunction
+public class InterpolateFunction extends SingleNumericParameterFunction
{
/**
* Constructor
* @param inApp app object
*/
public InterpolateFunction(App inApp) {
- super(inApp);
+ super(inApp, 1, 1000);
}
/** @return name key */
return "function.interpolate";
}
+ /** @return description key for input parameter */
+ public String getDescriptionKey() {
+ return "dialog.interpolate.parameter.text";
+ }
+
+ /** @return current (or default) parameter value */
+ public int getCurrentParamValue() {
+ return 0;
+ }
+
/**
* Perform the operation
*/
public void begin()
+ {
+ // not needed, we just use the completeFunction method instead
+ }
+
+ /**
+ * Complete the function after the input parameter has been chosen
+ */
+ public void completeFunction(int inParam)
{
// Firstly, work out whether the selected range only contains waypoints or not
final int startIndex = _app.getTrackInfo().getSelection().getStart();
betweenWaypoints = true;
}
- // Get number of points to add
- Object numPointsStr = JOptionPane.showInputDialog(_parentFrame,
- I18nManager.getText("dialog.interpolate.parameter.text"),
- I18nManager.getText(getNameKey()),
- JOptionPane.QUESTION_MESSAGE, null, null, "");
- if (numPointsStr == null) {return;}
- int numToAdd = parseNumber(numPointsStr);
- if (numToAdd <= 0 || numToAdd > 1000)
- {
- _app.showErrorMessage(getNameKey(), "error.interpolate.invalidparameter");
- return;
- }
-
if (startIndex < 0 || endIndex < 0 || endIndex <= startIndex) {
return;
}
// construct new point array with the interpolated points
+ final int numToAdd = inParam;
final Track track = _app.getTrackInfo().getTrack();
final int maxToAdd = (endIndex-startIndex) * numToAdd;
final int extendedSize = track.getNumPoints() + maxToAdd;
}
return false;
}
-
- /**
- * Helper method to parse an Object into an integer
- * @param inObject object, eg from dialog
- * @return int value given
- */
- private static int parseNumber(Object inObject)
- {
- int num = 0;
- if (inObject != null)
- {
- try
- {
- num = Integer.parseInt(inObject.toString());
- }
- catch (NumberFormatException nfe)
- {}
- }
- return num;
- }
}
--- /dev/null
+package tim.prune.function;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+import tim.prune.data.sort.SortMode;
+
+/**
+ * Abstract superclass for the functions which rearrange points,
+ * such as waypoints or photo points
+ */
+public abstract class RearrangeFunction extends GenericFunction
+{
+ /** Function dialog */
+ private JDialog _dialog = null;
+ /** Radio buttons for start/end/nearest */
+ private JRadioButton[] _positionRadios = null;
+ /** Radio buttons for sorting */
+ private JRadioButton[] _sortRadios = null;
+ /** Is the "nearest" option available? */
+ private boolean _nearestAvailable = false;
+
+
+ /** Enumeration for rearrange commands */
+ protected enum Rearrange
+ {
+ /** Rearrange all waypoints to start */
+ TO_START,
+ /** Rearrange all waypoints to end */
+ TO_END,
+ /** Rearrange each waypoint to nearest track point */
+ TO_NEAREST
+ }
+
+
+ /**
+ * Constructor
+ * @param inApp app object
+ * @param isNearestAvailable true if nearest option is visible
+ */
+ public RearrangeFunction(App inApp, boolean isNearestAvailable)
+ {
+ super(inApp);
+ _nearestAvailable = isNearestAvailable;
+ }
+
+ /**
+ * Begin the function by showing the dialog
+ */
+ public void begin()
+ {
+ // Make dialog window
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ // If sorting by time isn't available, then disable radio button
+ _sortRadios[2].setEnabled(isSortByTimeAllowed());
+ // Show dialog
+ _dialog.setVisible(true);
+ }
+
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private JPanel makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout());
+ JLabel descLabel = new JLabel(I18nManager.getText(getDescriptionKey()));
+ descLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ dialogPanel.add(descLabel, BorderLayout.NORTH);
+ // Radios for position (start / end / nearest)
+ _positionRadios = new JRadioButton[3];
+ final String[] posNames = {"tostart", "toend", "tonearest"};
+ ButtonGroup posGroup = new ButtonGroup();
+ JPanel posPanel = new JPanel();
+ posPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ for (int i=0; i<posNames.length; i++)
+ {
+ _positionRadios[i] = new JRadioButton(I18nManager.getText("dialog.rearrange." + posNames[i]));
+ posGroup.add(_positionRadios[i]);
+ posPanel.add(_positionRadios[i]);
+ }
+ _positionRadios[0].setSelected(true);
+ _positionRadios[2].setVisible(_nearestAvailable);
+
+ // Radios for sort (none / filename / time)
+ _sortRadios = new JRadioButton[3];
+ final String[] sortNames = {"nosort", getSortNameKey(), "sortbytime"};
+ ButtonGroup sortGroup = new ButtonGroup();
+ JPanel sortPanel = new JPanel();
+ sortPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
+ for (int i=0; i<3; i++)
+ {
+ _sortRadios[i] = new JRadioButton(I18nManager.getText("dialog.rearrange." + sortNames[i]));
+ sortGroup.add(_sortRadios[i]);
+ sortPanel.add(_sortRadios[i]);
+ }
+ _sortRadios[0].setSelected(true);
+ // Use listener to disable all sort options if nearest type chosen, re-enable otherwise
+ ActionListener rearrListener = new ActionListener() {
+ public void actionPerformed(ActionEvent arg0) {
+ final boolean sortAvailable = !_positionRadios[2].isSelected();
+ for (int i=0; i<_sortRadios.length; i++) {
+ _sortRadios[i].setEnabled(sortAvailable);
+ }
+ }
+ };
+ for (int i=0; i<_positionRadios.length; i++) {
+ _positionRadios[i].addActionListener(rearrListener);
+ }
+ // add to middle of dialog
+ JPanel centrePanel = new JPanel();
+ centrePanel.setLayout(new BoxLayout(centrePanel, BoxLayout.Y_AXIS));
+ centrePanel.add(posPanel);
+ centrePanel.add(sortPanel);
+ dialogPanel.add(centrePanel, BorderLayout.CENTER);
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ JButton okButton = new JButton(I18nManager.getText("button.ok"));
+ okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ finish();
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(okButton);
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ _dialog.dispose();
+ }
+ });
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+ /**
+ * @return true if sorting by time is allowed, false otherwise
+ */
+ protected boolean isSortByTimeAllowed()
+ {
+ return true;
+ }
+
+ /**
+ * @return the selected rearrange option
+ */
+ protected Rearrange getRearrangeOption()
+ {
+ if (_positionRadios[0].isSelected()) {
+ return Rearrange.TO_START;
+ }
+ if (_positionRadios[1].isSelected()) {
+ return Rearrange.TO_END;
+ }
+ return Rearrange.TO_NEAREST;
+ }
+
+ /**
+ * @return the selected sort mode
+ */
+ protected SortMode getSortMode()
+ {
+ if (_sortRadios[0].isSelected()) {
+ return SortMode.DONT_SORT;
+ }
+ if (_sortRadios[1].isSelected()) {
+ return SortMode.SORTBY_NAME;
+ }
+ return SortMode.SORTBY_TIME;
+ }
+
+ /** @return key for description */
+ protected abstract String getDescriptionKey();
+
+ /** @return partial key for the sort by name radio */
+ protected abstract String getSortNameKey();
+
+ /**
+ * Perform the rearrange
+ */
+ protected abstract void finish();
+}
package tim.prune.function;
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
import java.util.Arrays;
-
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.JButton;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-
import tim.prune.App;
-import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.data.DataPoint;
import tim.prune.data.Track;
+import tim.prune.data.sort.PhotoComparer;
+import tim.prune.data.sort.SortMode;
import tim.prune.undo.UndoRearrangePhotos;
/**
* Class to provide the function for rearranging photo points
*/
-public class RearrangePhotosFunction extends GenericFunction
+public class RearrangePhotosFunction extends RearrangeFunction
{
- /** Function dialog */
- private JDialog _dialog = null;
- /** Radio buttons for start/end */
- private JRadioButton[] _positionRadios = null;
- /** Radio buttons for sorting */
- private JRadioButton[] _sortRadios = null;
-
-
/**
* Constructor
* @param inApp app object
*/
public RearrangePhotosFunction(App inApp)
{
- super(inApp);
+ super(inApp, false);
}
- /** Begin the rearrange */
- public void begin()
- {
- // Make dialog window
- if (_dialog == null)
- {
- _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
- _dialog.setLocationRelativeTo(_parentFrame);
- _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
- _dialog.getContentPane().add(makeDialogComponents());
- _dialog.pack();
- }
- // Reset dialog and show
- _dialog.setVisible(true);
- }
-
- /** Get the name key (not needed) */
+ /** Get the name key */
public String getNameKey() {
return "function.rearrangephotos";
}
+ /** Get the description key */
+ public String getDescriptionKey() {
+ return "dialog.rearrangephotos.desc";
+ }
- /**
- * Create dialog components
- * @return Panel containing all gui elements in dialog
- */
- private JPanel makeDialogComponents()
- {
- JPanel dialogPanel = new JPanel();
- dialogPanel.setLayout(new BorderLayout());
- dialogPanel.add(new JLabel(I18nManager.getText("dialog.rearrangephotos.desc")), BorderLayout.NORTH);
- // Radios for position (start / end)
- _positionRadios = new JRadioButton[2];
- final String[] posNames = {"tostart", "toend"};
- ButtonGroup posGroup = new ButtonGroup();
- JPanel posPanel = new JPanel();
- posPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
- for (int i=0; i<2; i++)
- {
- _positionRadios[i] = new JRadioButton(I18nManager.getText("dialog.rearrangephotos." + posNames[i]));
- posGroup.add(_positionRadios[i]);
- posPanel.add(_positionRadios[i]);
- }
- _positionRadios[0].setSelected(true);
- // Radios for sort (none / filename / time)
- _sortRadios = new JRadioButton[3];
- final String[] sortNames = {"nosort", "sortbyfilename", "sortbytime"};
- ButtonGroup sortGroup = new ButtonGroup();
- JPanel sortPanel = new JPanel();
- sortPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
- for (int i=0; i<3; i++)
- {
- _sortRadios[i] = new JRadioButton(I18nManager.getText("dialog.rearrangephotos." + sortNames[i]));
- sortGroup.add(_sortRadios[i]);
- sortPanel.add(_sortRadios[i]);
- }
- _sortRadios[0].setSelected(true);
- // add to middle of dialog
- JPanel centrePanel = new JPanel();
- centrePanel.setLayout(new BoxLayout(centrePanel, BoxLayout.Y_AXIS));
- centrePanel.add(posPanel);
- centrePanel.add(sortPanel);
- dialogPanel.add(centrePanel, BorderLayout.CENTER);
- // button panel at bottom
- JPanel buttonPanel = new JPanel();
- buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
- JButton okButton = new JButton(I18nManager.getText("button.ok"));
- okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- finish();
- _dialog.dispose();
- }
- });
- buttonPanel.add(okButton);
- JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- _dialog.dispose();
- }
- });
- buttonPanel.add(cancelButton);
- dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
- return dialogPanel;
+ /** Sort by filename key */
+ protected String getSortNameKey() {
+ return "sortbyfilename";
}
/**
* Perform the rearrange
*/
- private void finish()
+ protected void finish()
{
Track track = _app.getTrackInfo().getTrack();
UndoRearrangePhotos undo = new UndoRearrangePhotos(track);
for (int i=0; i<numPoints; i++)
{
DataPoint point = track.getPoint(i);
- if (point.getPhoto() != null) {
+ if (point.getPhoto() != null)
+ {
photos[numPhotos] = point;
numPhotos++;
}
- else {
+ else
+ {
nonPhotos[numNonPhotos] = point;
numNonPhotos++;
}
}
- // Sort photos if necessary
- if (!_sortRadios[0].isSelected() && numPhotos > 1) {
- sortPhotos(photos, _sortRadios[1].isSelected());
- }
- // Put the non-photo points and photo points together
- DataPoint[] neworder = new DataPoint[numPoints];
- if (_positionRadios[0].isSelected()) {
- // photos at front
- System.arraycopy(photos, 0, neworder, 0, numPhotos);
- System.arraycopy(nonPhotos, 0, neworder, numPhotos, numNonPhotos);
- }
- else {
- // photos at end
- System.arraycopy(nonPhotos, 0, neworder, 0, numNonPhotos);
- System.arraycopy(photos, 0, neworder, numNonPhotos, numPhotos);
+ boolean pointsChanged = false;
+ if (numPhotos > 0)
+ {
+ Rearrange rearrangeOption = getRearrangeOption();
+ SortMode sortOption = getSortMode();
+ // Sort photos if necessary
+ if (sortOption != SortMode.DONT_SORT && numPhotos > 1) {
+ sortPhotos(photos, sortOption);
+ }
+ // Put the non-photo points and photo points together
+ DataPoint[] neworder = new DataPoint[numPoints];
+ if (rearrangeOption == Rearrange.TO_START)
+ {
+ // photos at front
+ System.arraycopy(photos, 0, neworder, 0, numPhotos);
+ System.arraycopy(nonPhotos, 0, neworder, numPhotos, numNonPhotos);
+ }
+ else
+ {
+ // photos at end
+ System.arraycopy(nonPhotos, 0, neworder, 0, numNonPhotos);
+ System.arraycopy(photos, 0, neworder, numNonPhotos, numPhotos);
+ }
+
+ // Give track the new point order
+ pointsChanged = track.replaceContents(neworder);
}
- // Give track the new point order
- if (track.replaceContents(neworder))
+ // did anything change?
+ if (pointsChanged)
{
_app.getTrackInfo().getSelection().clearAll();
_app.completeFunction(undo, I18nManager.getText("confirm.rearrangephotos"));
/**
* Sort the given photo list either by filename or by time
* @param inPhotos array of DataPoint objects to sort
- * @param inSortByFile true to sort by filename, false to sort by timestamp
+ * @param inSortOrder sort order
* @return sorted array
*/
- private static void sortPhotos(DataPoint[] inPhotos, boolean inSortByFile)
+ private static void sortPhotos(DataPoint[] inPhotos, SortMode inSortMode)
{
- PhotoComparer comparer = new PhotoComparer(inSortByFile ? PhotoComparer.SortMode.SORTBY_NAME : PhotoComparer.SortMode.SORTBY_TIME);
+ PhotoComparer comparer = new PhotoComparer(inSortMode);
Arrays.sort(inPhotos, comparer);
}
}
package tim.prune.function;
+import java.util.Arrays;
+
import javax.swing.JOptionPane;
import tim.prune.App;
-import tim.prune.GenericFunction;
import tim.prune.I18nManager;
+import tim.prune.data.Checker;
+import tim.prune.data.DataPoint;
import tim.prune.data.Track;
+import tim.prune.data.sort.SortMode;
+import tim.prune.data.sort.WaypointComparer;
import tim.prune.undo.UndoRearrangeWaypoints;
/**
* Class to provide the function for rearranging waypoints
*/
-public class RearrangeWaypointsFunction extends GenericFunction
+public class RearrangeWaypointsFunction extends RearrangeFunction
{
- /** Enumeration for rearrange commands */
- public enum Rearrange
- {
- /** Rearrange all waypoints to start */
- TO_START,
- /** Rearrange all waypoints to end */
- TO_END,
- /** Rearrange each waypoint to nearest track point */
- TO_NEAREST
- }
-
/**
* Constructor
* @param inApp app object
*/
public RearrangeWaypointsFunction(App inApp)
{
- super(inApp);
+ super(inApp, true);
+ }
+
+ /** Get the name key */
+ public String getNameKey() {
+ return "function.rearrangewaypoints";
}
- /** Begin the rearrange (not needed) */
- public void begin() {
+ /** Get whether sorting by time is allowed or not */
+ protected boolean isSortByTimeAllowed() {
+ return Checker.haveWaypointsGotTimestamps(_app.getTrackInfo().getTrack());
}
- /** Get the name key (not needed) */
- public String getNameKey() {
- return null;
+ /** Get the description key */
+ public String getDescriptionKey() {
+ return "dialog.rearrangewaypoints.desc";
+ }
+
+ /** Sort by name key */
+ protected String getSortNameKey() {
+ return "sortbyname";
}
/**
- * Rearrange the waypoints into track order
- * @param inFunction nearest point, all to end or all to start
+ * Perform the rearrange and sort according to the radio buttons
*/
- public void rearrangeWaypoints(Rearrange inFunction)
+ protected void finish()
{
Track track = _app.getTrackInfo().getTrack();
+ // Figure out what is required from the radio buttons
+ Rearrange rearrangeOption = getRearrangeOption();
+ SortMode sortOption = getSortMode();
+
UndoRearrangeWaypoints undo = new UndoRearrangeWaypoints(track);
boolean success = false;
- if (inFunction == Rearrange.TO_START || inFunction == Rearrange.TO_END)
+ if (rearrangeOption == Rearrange.TO_START || rearrangeOption == Rearrange.TO_END)
{
// Collect the waypoints to the start or end of the track
- success = track.collectWaypoints(inFunction == Rearrange.TO_START);
+ success = collectWaypoints(rearrangeOption, sortOption);
}
else
{
}
}
+
+ /**
+ * Do the collection and sorting of the waypoints
+ * @param inRearrangeOption beginning or end
+ * @param inSortOption optional sort criterion
+ * @return true on success
+ */
+ private boolean collectWaypoints(Rearrange inRearrangeOption, SortMode inSortOption)
+ {
+ // Check for mixed data, numbers of waypoints & nons
+ int numWaypoints = 0, numNonWaypoints = 0;
+ boolean wayAfterNon = false, nonAfterWay = false;
+ Track track = _app.getTrackInfo().getTrack();
+ final int numPoints = track.getNumPoints();
+ DataPoint[] waypoints = new DataPoint[numPoints];
+ DataPoint[] nonWaypoints = new DataPoint[numPoints];
+ DataPoint point = null;
+ for (int i=0; i<numPoints; i++)
+ {
+ point = track.getPoint(i);
+ if (point.isWaypoint())
+ {
+ waypoints[numWaypoints] = point;
+ numWaypoints++;
+ wayAfterNon |= (numNonWaypoints > 0);
+ }
+ else
+ {
+ nonWaypoints[numNonWaypoints] = point;
+ numNonWaypoints++;
+ nonAfterWay |= (numWaypoints > 0);
+ }
+ }
+
+ // Exit if the data is already in the specified order
+ final boolean wpsToStart = (inRearrangeOption == Rearrange.TO_START);
+ final boolean doSort = (inSortOption != SortMode.DONT_SORT);
+ if (numWaypoints == 0 || numNonWaypoints == 0
+ || (wpsToStart && !wayAfterNon && nonAfterWay && !doSort)
+ || (!wpsToStart && wayAfterNon && !nonAfterWay && !doSort)
+ || inRearrangeOption == Rearrange.TO_NEAREST)
+ {
+ return false;
+ }
+ // Note: it could still be that the rearrange and sort has no effect, but we don't know yet
+ // Make a copy of the waypoints array first so we can compare it with after the sort
+ DataPoint[] origWaypoints = new DataPoint[numPoints];
+ System.arraycopy(waypoints, 0, origWaypoints, 0, numPoints);
+
+ if (doSort && numWaypoints > 1)
+ {
+ // Sort the waypoints array
+ WaypointComparer comparer = new WaypointComparer(inSortOption);
+ Arrays.sort(waypoints, comparer);
+ final boolean sortDidNothing = areArraysSame(origWaypoints, waypoints);
+ if (sortDidNothing && (numNonWaypoints == 0
+ || (wpsToStart && !wayAfterNon && nonAfterWay)
+ || (!wpsToStart && wayAfterNon && !nonAfterWay)))
+ {
+ return false;
+ }
+ }
+
+ // Copy the arrays into an array in the specified order
+ DataPoint[] neworder = new DataPoint[numPoints];
+ if (wpsToStart)
+ {
+ System.arraycopy(waypoints, 0, neworder, 0, numWaypoints);
+ System.arraycopy(nonWaypoints, 0, neworder, numWaypoints, numNonWaypoints);
+ }
+ else
+ {
+ System.arraycopy(nonWaypoints, 0, neworder, 0, numNonWaypoints);
+ System.arraycopy(waypoints, 0, neworder, numNonWaypoints, numWaypoints);
+ }
+ // Give track the new point order
+ return track.replaceContents(neworder);
+ }
+
+ /**
+ * Compare two arrays of DataPoints and see if they're identical or not
+ * @param inOriginal original array of points
+ * @param inSorted array of points after sorting
+ * @return true if the two arrays have the same points in the same order
+ */
+ private static boolean areArraysSame(DataPoint[] inOriginal, DataPoint[] inSorted)
+ {
+ if (inOriginal == null && inSorted == null) return true; // both null
+ if (inOriginal == null || inSorted == null) return false; // only one of them null
+ if (inOriginal.length != inSorted.length) return false;
+ // Loop over all points
+ for (int i=0; i<inOriginal.length; i++)
+ {
+ DataPoint origPoint = inOriginal[i];
+ DataPoint sortedPoint = inSorted[i];
+ if ((origPoint != null || sortedPoint != null)
+ && (origPoint != sortedPoint))
+ {
+ return false; // points different
+ }
+ }
+ // Must be all the same
+ return true;
+ }
}
else
{
// point is attached, so need to confirm point deletion
+ final int pointIndex = _app.getTrackInfo().getTrack().getPointIndex(currentAudio.getDataPoint());
undoAction = new UndoDeleteAudio(currentAudio, _app.getTrackInfo().getSelection().getCurrentAudioIndex(),
- currentAudio.getDataPoint(), _app.getTrackInfo().getTrack().getPointIndex(currentAudio.getDataPoint()));
+ currentAudio.getDataPoint(), pointIndex);
+ undoAction.setAtBoundaryOfSelectedRange(pointIndex == _app.getTrackInfo().getSelection().getStart() ||
+ pointIndex == _app.getTrackInfo().getSelection().getEnd());
int response = JOptionPane.showConfirmDialog(_app.getFrame(),
I18nManager.getText("dialog.deleteaudio.deletepoint"),
I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION);
else
{
// point is attached, so need to confirm point deletion
+ final int pointIndex = _app.getTrackInfo().getTrack().getPointIndex(currentPhoto.getDataPoint());
undoAction = new UndoDeletePhoto(currentPhoto, _app.getTrackInfo().getSelection().getCurrentPhotoIndex(),
- currentPhoto.getDataPoint(), _app.getTrackInfo().getTrack().getPointIndex(currentPhoto.getDataPoint()));
+ currentPhoto.getDataPoint(), pointIndex);
+ undoAction.setAtBoundaryOfSelectedRange(pointIndex == _app.getTrackInfo().getSelection().getStart() ||
+ pointIndex == _app.getTrackInfo().getSelection().getEnd());
int response = JOptionPane.showConfirmDialog(_app.getFrame(),
I18nManager.getText("dialog.deletephoto.deletepoint"),
I18nManager.getText("dialog.deletephoto.title"),
*/
private void saveConfig(File inSaveFile)
{
+ // TODO: Check for null inSaveFile, then just call finish() ?
FileOutputStream outStream = null;
try
{
// Set status label according to error or "none found", leave blank if ok
if (_errorMessage == null && _trackListModel.isEmpty()) {
- _errorMessage = I18nManager.getText("dialog.gpsies.nonefound");
+ _errorMessage = I18nManager.getText("dialog.wikipedia.nonefound");
}
_statusLabel.setText(_errorMessage == null ? "" : _errorMessage);
}
--- /dev/null
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.data.Checker;
+import tim.prune.data.DataPoint;
+
+/**
+ * Function to allow the selection of which tracks to load from the file / stream
+ */
+public class SelectSegmentFunction extends GenericFunction
+{
+
+ /**
+ * Constructor
+ * @param inApp app object to use for load
+ */
+ public SelectSegmentFunction(App inApp)
+ {
+ super(inApp);
+ }
+
+ /**
+ * Start the function
+ */
+ public void begin()
+ {
+ // If no point selected, or a waypoint is selected, then do nothing
+ DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
+ if (currPoint != null && !currPoint.isWaypoint())
+ {
+ // Find indexes of segment start and end
+ final int currIndex = _app.getTrackInfo().getSelection().getCurrentPointIndex();
+ final int startIndex = Checker.getPreviousSegmentStart(_app.getTrackInfo().getTrack(), currIndex+1);
+ final int endIndex = Checker.getNextSegmentEnd(_app.getTrackInfo().getTrack(), currIndex);
+ // Select this range if there is one
+ if (endIndex > startIndex) {
+ _app.getTrackInfo().getSelection().selectRange(startIndex, endIndex);
+ }
+ }
+ }
+
+ /** @return name key */
+ public String getNameKey() {
+ return "function.selectsegment";
+ }
+}
--- /dev/null
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.config.Config;
+import tim.prune.data.Unit;
+
+/**
+ * Function to set the tolerance for the altitude range calculations
+ */
+public class SetAltitudeTolerance extends SingleNumericParameterFunction
+{
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public SetAltitudeTolerance(App inApp) {
+ super(inApp, 0, 100);
+ }
+
+ /** @return name key */
+ public String getNameKey() {
+ return "function.setaltitudetolerance";
+ }
+
+ /**
+ * @return description key
+ */
+ public String getDescriptionKey()
+ {
+ // Two different keys for feet and metres
+ final boolean isMetres = Config.getUnitSet().getAltitudeUnit().isStandard();
+ return "dialog.setaltitudetolerance.text." + (isMetres ? "metres" : "feet");
+ }
+
+ /**
+ * @return the current value to display
+ */
+ public int getCurrentParamValue()
+ {
+ int configVal = Config.getConfigInt(Config.KEY_ALTITUDE_TOLERANCE);
+ // Convert this to feet if necessary
+ Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ if (altUnit.isStandard()) {
+ return configVal / 100;
+ }
+ return (int) (configVal * altUnit.getMultFactorFromStd() / 100.0);
+ }
+
+ /**
+ * Run function
+ */
+ public void begin()
+ {
+ // Not required, because this function is started from a ChooseSingleParameter function
+ // and goes directly to the completeFunction method.
+ }
+
+ /**
+ * Complete the function using the given tolerance parameter
+ */
+ public void completeFunction(int inTolerance)
+ {
+ // Convert back from feet into metres again
+ Unit altUnit = Config.getUnitSet().getAltitudeUnit();
+ int configVal = inTolerance * 100;
+ if (!altUnit.isStandard()) {
+ configVal = (int) (inTolerance * 100.0 / altUnit.getMultFactorFromStd());
+ }
+ Config.setConfigInt(Config.KEY_ALTITUDE_TOLERANCE, configVal);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+}
package tim.prune.function;
import java.awt.BorderLayout;
-import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.ColourScheme;
import tim.prune.config.Config;
-import tim.prune.gui.ColourChooser;
-import tim.prune.gui.ColourPatch;
+import tim.prune.gui.colour.ColourChooser;
+import tim.prune.gui.colour.ColourPatch;
+import tim.prune.gui.colour.ColourerSelectorPanel;
+import tim.prune.gui.colour.PatchListener;
+import tim.prune.gui.colour.PointColourer;
/**
* Class to show the popup window for setting the colours
private JButton _okButton = null;
/** Array of 8 colour patches */
private ColourPatch[] _patches = null;
+ /** colourer selection panel */
+ private ColourerSelectorPanel _colourerSelector = null;
/** Single colour chooser */
private ColourChooser _colourChooser = null;
ColourScheme.IDX_TEXT, ColourScheme.IDX_LINES
};
- /**
- * Inner class to react to patch clicks
- */
- class PatchListener extends MouseAdapter
- {
- /** Associated patch */
- private ColourPatch _patch = null;
- /** Constructor */
- public PatchListener(ColourPatch inPatch) {
- _patch = inPatch;
- }
- /** React to mouse clicks */
- public void mouseClicked(MouseEvent e)
- {
- _colourChooser.showDialog(_patch.getBackground());
- Color colour = _colourChooser.getChosenColour();
- if (colour != null) _patch.setColour(colour);
- }
- }
/**
* Constructor
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout(0, 10));
- JLabel intro = new JLabel(I18nManager.getText("dialog.setcolours.intro"));
- intro.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0));
- mainPanel.add(intro, BorderLayout.NORTH);
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.setcolours.intro"));
+ introLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0));
+ mainPanel.add(introLabel, BorderLayout.NORTH);
+
+ // Panel in centre, to hold both the patch panel and the colourer panel (and maybe introLabel too?)
JPanel centralPanel = new JPanel();
- centralPanel.setLayout(new GridLayout());
+ centralPanel.setLayout(new BoxLayout(centralPanel, BoxLayout.Y_AXIS));
+
+ // Make panel for 8 colour patches
+ JPanel patchPanel = new JPanel();
+ patchPanel.setLayout(new GridLayout());
_patches = new ColourPatch[8];
ColourScheme scheme = Config.getColourScheme();
// Top label and patch
colPanel.add(new JLabel(I18nManager.getText("dialog.setcolours." + LABEL_KEYS[i*2])));
patch = new ColourPatch(scheme.getColour(INDICES[i*2]));
- patch.addMouseListener(new PatchListener(patch));
+ patch.addMouseListener(new PatchListener(patch, _colourChooser));
colPanel.add(patch);
_patches[i*2] = patch;
// separator
// Bottom label and patch
colPanel.add(new JLabel(I18nManager.getText("dialog.setcolours." + LABEL_KEYS[i*2+1])));
patch = new ColourPatch(scheme.getColour(INDICES[i*2+1]));
- patch.addMouseListener(new PatchListener(patch));
+ patch.addMouseListener(new PatchListener(patch, _colourChooser));
colPanel.add(patch);
_patches[i*2+1] = patch;
// Add column to panel
colPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
- centralPanel.add(colPanel);
+ patchPanel.add(colPanel);
}
+ patchPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ centralPanel.add(patchPanel);
+ centralPanel.add(Box.createVerticalStrut(15));
+
+ // now the colourer selector
+ _colourerSelector = new ColourerSelectorPanel(_colourChooser);
+ _colourerSelector.setAlignmentX(Component.LEFT_ALIGNMENT);
+ centralPanel.add(_colourerSelector);
+ // add the central panel to the main one
mainPanel.add(centralPanel, BorderLayout.CENTER);
// Buttons at the bottom
{
_dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()));
_dialog.setLocationRelativeTo(_parentFrame);
+ _colourChooser = new ColourChooser(_dialog);
_dialog.getContentPane().add(makeContents());
_dialog.pack();
- _colourChooser = new ColourChooser(_dialog);
}
// Reset colours to current ones
ColourScheme scheme = Config.getColourScheme();
for (int i=0; i<8; i++) {
_patches[i].setColour(scheme.getColour(INDICES[i]));
}
+ PointColourer colourer = Config.getPointColourer();
+ _colourerSelector.init(colourer, scheme.getColour(ColourScheme.IDX_POINT));
_dialog.setVisible(true);
_okButton.requestFocus();
}
scheme.setColour(INDICES[i], _patches[i].getBackground());
}
Config.updateColourScheme();
+ PointColourer colourer = _colourerSelector.getSelectedColourer();
+ Config.updatePointColourer(colourer);
+ _app.updatePointColourer();
UpdateMessageBroker.informSubscribers();
}
}
package tim.prune.function;
-import javax.swing.JOptionPane;
-
import tim.prune.App;
import tim.prune.DataSubscriber;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
-public class SetLineWidth extends GenericFunction
+/**
+ * Function to set the width with which lines are drawn
+ */
+public class SetLineWidth extends SingleNumericParameterFunction
{
/**
* @param inApp App object
*/
public SetLineWidth(App inApp) {
- super(inApp);
+ super(inApp, 1, 4);
}
/** @return name key */
return "function.setlinewidth";
}
+ /** @return description key */
+ public String getDescriptionKey() {
+ return "dialog.setlinewidth.text";
+ }
+
+ /** @return the current value to display */
+ public int getCurrentParamValue() {
+ return Config.getConfigInt(Config.KEY_LINE_WIDTH);
+ }
/**
* Run function
*/
public void begin()
{
- int currLineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
- if (currLineWidth < 1 || currLineWidth > 4) {
- currLineWidth = 2;
- }
- Object lineWidthStr = JOptionPane.showInputDialog(_app.getFrame(),
- I18nManager.getText("dialog.setlinewidth.text"),
- I18nManager.getText(getNameKey()),
- JOptionPane.QUESTION_MESSAGE, null, null, "" + currLineWidth);
- if (lineWidthStr != null)
+ // Not required, because this function is started from a ChooseSingleParameter function
+ // and goes directly to the completeFunction method.
+ }
+
+ /**
+ * Complete the function using the given line width parameter
+ */
+ public void completeFunction(int inLineWidth)
+ {
+ final int currLineWidth = Config.getConfigInt(Config.KEY_LINE_WIDTH);
+ if (inLineWidth >= 1 && inLineWidth <= 4 && inLineWidth != currLineWidth)
{
- int lineWidth = 2;
- try {
- lineWidth = Integer.parseInt(lineWidthStr.toString());
- if (lineWidth >= 1 && lineWidth <= 4 && lineWidth != currLineWidth)
- {
- Config.setConfigInt(Config.KEY_LINE_WIDTH, lineWidth);
- UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
- }
- }
- catch (NumberFormatException nfe) {};
+ Config.setConfigInt(Config.KEY_LINE_WIDTH, inLineWidth);
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
}
}
}
*/
private void finish()
{
- // Store exaggeration factor in config
+ // Store exaggeration factor and grid size in config
Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_exaggField.getValue() * 100));
+ int terrainGridSize = _terrainPanel.getGridSize();
+ if (terrainGridSize < 20) {terrainGridSize = 20;}
+ Config.setConfigInt(Config.KEY_TERRAIN_GRID_SIZE, terrainGridSize);
+
ThreeDWindow window = WindowFactory.getWindow(_parentFrame);
if (window != null)
{
--- /dev/null
+package tim.prune.function;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+
+/**
+ * Abstract superclass of Functions which just take a
+ * single numeric parameter
+ */
+public abstract class SingleNumericParameterFunction extends GenericFunction
+{
+ /** Minimum and maximum allowed values */
+ protected int _minAllowedValue, _maxAllowedValue;
+
+ /** Constructor */
+ public SingleNumericParameterFunction(App inApp, int inMinValue, int inMaxValue)
+ {
+ super(inApp);
+ _minAllowedValue = inMinValue;
+ _maxAllowedValue = inMaxValue;
+ }
+
+ /** Get the current value for display in the dialog */
+ public abstract int getCurrentParamValue();
+
+ /** Get the key for the description label */
+ public abstract String getDescriptionKey();
+
+ /** Callback to trigger the rest of the function once the parameter has been chosen */
+ public abstract void completeFunction(int inParam);
+
+ /** @return minimum allowed value */
+ public int getMinAllowedValue() {return _minAllowedValue;}
+ /** @return maximum allowed value */
+ public int getMaxAllowedValue() {return _maxAllowedValue;}
+}
+ getSvgValue(_svgHeightField, DEFAULT_SVG_HEIGHT) + "\n");
writer.write("set out '" + svgFile.getAbsolutePath() + "'\n");
}
+ else {
+ // For screen output, gnuplot should use the default terminal (windows or x11 or wxt or something)
+ }
if (numCharts > 1) {
writer.write("set multiplot layout " + numCharts + ",1\n");
}
break;
}
// Make a temporary data file for the output (one per subchart)
- File tempFile = File.createTempFile("prunedata", null);
+ File tempFile = File.createTempFile("gpsprunedata", null);
tempFile.deleteOnExit();
// write out values for x and y to temporary file
FileWriter tempFileWriter = null;
import javax.swing.JPanel;
import tim.prune.App;
-import tim.prune.GenericFunction;
import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.data.DataPoint;
/**
* Class to provide the function for track compression
*/
-public class CompressTrackFunction extends GenericFunction
+public class CompressTrackFunction extends MarkAndDeleteFunction
{
private Track _track = null;
private JDialog _dialog = null;
private JButton _okButton = null;
private CompressionAlgorithm[] _algorithms = null;
private SummaryLabel _summaryLabel = null;
- /** flag to remember whether the automatic deletion has been set to always */
- private boolean _automaticallyDelete = false;
/**
// Show confirmation dialog with OK button (not status bar message)
if (numMarked > 0)
{
- // Allow calling of delete function with one click
- final String[] buttonTexts = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
- I18nManager.getText("button.always")};
- int answer = _automaticallyDelete ? JOptionPane.YES_OPTION :
- JOptionPane.showOptionDialog(_parentFrame,
- I18nManager.getTextWithNumber("dialog.compress.confirm", numMarked),
- I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]);
- if (answer == JOptionPane.CANCEL_OPTION) {_automaticallyDelete = true;} // "always" is third option
- if (_automaticallyDelete || answer == JOptionPane.YES_OPTION)
- {
- new Thread(new Runnable() {
- public void run() {
- _app.finishCompressTrack();
- }
- }).start();
- }
+ optionallyDeleteMarkedPoints(numMarked);
}
else
{
--- /dev/null
+package tim.prune.function.compress;
+
+import javax.swing.JOptionPane;
+
+import tim.prune.App;
+import tim.prune.GenericFunction;
+import tim.prune.I18nManager;
+
+/**
+ * Superclass of those functions which mark points for deletion
+ * (and optionally delete them automatically)
+ */
+public abstract class MarkAndDeleteFunction extends GenericFunction
+{
+ /** flag to remember whether the automatic deletion has been set to always */
+ private boolean _automaticallyDelete = false;
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public MarkAndDeleteFunction(App inApp)
+ {
+ super(inApp);
+ }
+
+ /**
+ * optionally delete the marked points
+ */
+ protected void optionallyDeleteMarkedPoints(int inNumMarked)
+ {
+ // Allow calling of delete function with one click
+ final String[] buttonTexts = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
+ I18nManager.getText("button.always")};
+ int answer = _automaticallyDelete ? JOptionPane.YES_OPTION :
+ JOptionPane.showOptionDialog(_parentFrame,
+ I18nManager.getTextWithNumber("dialog.compress.confirm", inNumMarked),
+ I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]);
+ if (answer == JOptionPane.CANCEL_OPTION) {_automaticallyDelete = true;} // "always" is third option
+ if (_automaticallyDelete || answer == JOptionPane.YES_OPTION)
+ {
+ new Thread(new Runnable() {
+ public void run() {
+ _app.finishCompressTrack();
+ }
+ }).start();
+ }
+ }
+}
package tim.prune.function.compress;
-import javax.swing.JOptionPane;
-
import tim.prune.App;
-import tim.prune.GenericFunction;
-import tim.prune.I18nManager;
import tim.prune.UpdateMessageBroker;
import tim.prune.data.DataPoint;
/**
* Function to mark all the points in the selected rectangle
*/
-public class MarkPointsInRectangleFunction extends GenericFunction
+public class MarkPointsInRectangleFunction extends MarkAndDeleteFunction
{
/** Minimum and maximum latitude values of rectangle */
private double _minLat = 0.0, _maxLat = 0.0;
/** Minimum and maximum longitude values of rectangle */
private double _minLon = 0.0, _maxLon = 0.0;
- /** flag to remember whether the automatic deletion has been set to always */
- private boolean _automaticallyDelete = false;
/**
final double pointLat = point.getLatitude().getDouble();
final boolean insideRect = (pointLon >= _minLon && pointLon <= _maxLon
&& pointLat >= _minLat && pointLat <= _maxLat);
- // If so, then mark it
+ // Mark it accordingly (also resetting points outside the rect to false)
point.setMarkedForDeletion(insideRect);
if (insideRect) {
numMarked++;
// Confirm message showing how many marked
if (numMarked > 0)
{
- // Allow calling of delete function with one click
- final String[] buttonTexts = {I18nManager.getText("button.yes"), I18nManager.getText("button.no"),
- I18nManager.getText("button.always")};
- int answer = _automaticallyDelete ? JOptionPane.YES_OPTION :
- JOptionPane.showOptionDialog(_parentFrame,
- I18nManager.getTextWithNumber("dialog.compress.confirm", numMarked),
- I18nManager.getText(getNameKey()), JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]);
- if (answer == JOptionPane.CANCEL_OPTION) {_automaticallyDelete = true;} // "always" is third option
- if (_automaticallyDelete || answer == JOptionPane.YES_OPTION)
- {
- new Thread(new Runnable() {
- public void run() {
- _app.finishCompressTrack();
- }
- }).start();
- }
+ optionallyDeleteMarkedPoints(numMarked);
}
}
}
--- /dev/null
+package tim.prune.function.deletebydate;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Class to hold the information about a date,
+ * including how many points correspond to the date
+ * and whether it has been selected for deletion or not
+ */
+public class DateInfo implements Comparable<DateInfo>
+{
+ /** Date, or null for no date - used for earlier/later comparison */
+ private Date _date = null;
+ /** String representation of date, for equality comparison */
+ private String _dateString = null;
+ /** Number of points with this date */
+ private int _numPoints = 0;
+ /** Flag for deletion or retention */
+ private boolean _toDelete = false;
+
+ // Doesn't really matter what format is used here, as long as dates are different
+ private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
+
+ /**
+ * Constructor
+ * @param inDate date object from timestamp
+ */
+ public DateInfo(Date inDate)
+ {
+ _date = inDate;
+ if (_date == null) {
+ _dateString = "";
+ }
+ else {
+ _dateString = DEFAULT_DATE_FORMAT.format(_date);
+ }
+ _numPoints = 0;
+ _toDelete = false;
+ }
+
+ /**
+ * @return true if this info is for dateless points (points without timestamp)
+ */
+ public boolean isDateless() {
+ return (_date == null);
+ }
+
+ /**
+ * @return date object, or null
+ */
+ public Date getDate() {
+ return _date;
+ }
+
+ /**
+ * Compare with a given Date object to see if they represent the same date
+ * @param inDate date to compare
+ * @return true if they're the same date
+ */
+ public boolean isSameDate(Date inDate)
+ {
+ if (inDate == null) {
+ return (_date == null);
+ }
+ else if (_dateString == null) {
+ return false;
+ }
+ String otherDateString = DEFAULT_DATE_FORMAT.format(inDate);
+ return _dateString.equals(otherDateString);
+ }
+
+ /**
+ * Increment the point count
+ */
+ public void incrementCount() {
+ _numPoints++;
+ }
+
+ /**
+ * @return point count
+ */
+ public int getPointCount() {
+ return _numPoints;
+ }
+
+ /**
+ * @param inFlag true to delete, false to keep
+ */
+ public void setDeleteFlag(boolean inFlag) {
+ _toDelete = inFlag;
+ }
+
+ /**
+ * @return true to delete, false to keep
+ */
+ public boolean getDeleteFlag() {
+ return _toDelete;
+ }
+
+ /**
+ * Compare with another DateInfo object for sorting
+ */
+ public int compareTo(DateInfo inOther)
+ {
+ // Dateless goes first
+ if (_date == null || _dateString == null) {return -1;}
+ if (inOther._date == null || inOther._dateString == null) {return 1;}
+ // Just compare dates
+ return _date.compareTo(inOther._date);
+ }
+}
--- /dev/null
+package tim.prune.function.deletebydate;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * List of date info objects for use by the table model
+ */
+public class DateInfoList
+{
+ /** list of info about points according to date */
+ private List<DateInfo> _infoList = new ArrayList<DateInfo>();
+ /** previously used dateinfo object to reduce list searching */
+ private DateInfo _previousInfo = null;
+ /** true if the list has been sorted, false otherwise */
+ private boolean _hasBeenSorted = false;
+
+
+ /**
+ * Add a point to the corresponding dateinfo
+ * @param inDate date of current point, or null if no timestamp
+ */
+ public void addPoint(Date inDate)
+ {
+ if (_previousInfo != null && _previousInfo.isSameDate(inDate))
+ {
+ // found it
+ _previousInfo.incrementCount();
+ }
+ else
+ {
+ // loop through list, seeing if date already present
+ boolean foundDate = false;
+ for (DateInfo info : _infoList)
+ {
+ if (info.isSameDate(inDate))
+ {
+ info.incrementCount();
+ _previousInfo = info;
+ foundDate = true;
+ break;
+ }
+ }
+ // create new info if necessary
+ if (!foundDate)
+ {
+ _previousInfo = new DateInfo(inDate);
+ _previousInfo.incrementCount();
+ _infoList.add(_previousInfo);
+ _hasBeenSorted = false;
+ }
+ }
+ }
+
+ /**
+ * Clear the whole list
+ */
+ public void clearAll()
+ {
+ _infoList.clear();
+ _previousInfo = null;
+ _hasBeenSorted = true;
+ }
+
+ /**
+ * not used, can be removed
+ * @return true if any points without dates were found
+ */
+ public boolean hasDatelessPoints()
+ {
+ if (_infoList.isEmpty()) {return false;}
+ sort();
+ DateInfo firstInfo = _infoList.get(0);
+ return (firstInfo != null && firstInfo.isDateless() && firstInfo.getPointCount() > 0);
+ }
+
+ /**
+ * @return number of entries in the list, including dateless points
+ */
+ public int getNumEntries()
+ {
+ return _infoList.size();
+ }
+
+ /**
+ * @return the total number of points found, which should match the track size
+ */
+ public int getTotalNumPoints()
+ {
+ int total = 0;
+ for (DateInfo info : _infoList) {
+ total += info.getPointCount();
+ }
+ return total;
+ }
+
+ /**
+ * Sort the info list by ascending timestamps
+ */
+ private void sort()
+ {
+ if (!_hasBeenSorted)
+ {
+ Collections.sort(_infoList);
+ _hasBeenSorted = true;
+ }
+ }
+
+ /**
+ * Get the DateInfo object at the given index
+ * @param inIndex index in (sorted) list
+ * @return corresponding object (may throw exception if out of range)
+ */
+ public DateInfo getDateInfo(int inIndex)
+ {
+ sort();
+ return _infoList.get(inIndex);
+ }
+}
--- /dev/null
+package tim.prune.function.deletebydate;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.Date;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+import tim.prune.I18nManager;
+import tim.prune.UpdateMessageBroker;
+import tim.prune.data.DataPoint;
+import tim.prune.function.compress.MarkAndDeleteFunction;
+
+/**
+ * Function to select a date or dates,
+ * and delete the corresponding points
+ */
+public class DeleteByDateFunction extends MarkAndDeleteFunction
+{
+ /** dialog for selecting dates */
+ private JDialog _dialog = null;
+ /** Ok button */
+ private JButton _okButton = null;
+ /** date info list */
+ private DateInfoList _infoList = new DateInfoList();
+
+
+ /**
+ * Constructor
+ * @param inApp App object
+ */
+ public DeleteByDateFunction(App inApp)
+ {
+ super(inApp);
+ }
+
+ @Override
+ public String getNameKey() {
+ return "function.deletebydate";
+ }
+
+ @Override
+ public void begin()
+ {
+ // Make a list of which dates are present in the track
+ _infoList.clearAll();
+ final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+ for (int i=0; i<numPoints; i++)
+ {
+ DataPoint point = _app.getTrackInfo().getTrack().getPoint(i);
+ if (point != null)
+ {
+ if (point.hasTimestamp()) {
+ _infoList.addPoint(point.getTimestamp().getCalendar().getTime());
+ }
+ else {
+ _infoList.addPoint(null); // no timestamp available
+ }
+ }
+ }
+// System.out.println("Debug: info list has dateless points? " + (_infoList.hasDatelessPoints() ? "yes":"no"));
+// System.out.println("Debug: info list has " + _infoList.getNumEntries() + " different entries");
+// System.out.println("Debug: info list has " + _infoList.getTotalNumPoints() + " total points");
+// final boolean checkOk = (_infoList.getTotalNumPoints() == numPoints);
+// System.out.println("Debug: which " + (checkOk?"IS":"ISN'T!") + " the same as track: " + numPoints);
+
+ // Loop over entries for debug
+// if (!checkOk)
+// {
+// for (int i=0; i<_infoList.getNumEntries(); i++)
+// {
+// DateInfo info = _infoList.getDateInfo(i);
+// System.out.println(" " + i + " (" + info.getPointCount() + " points) - " +
+// (info.isDateless() ? "no date" : "date"));
+// }
+// }
+
+ // Complain if there is only one entry in the list - this means all points are on the same day
+ if (_infoList.getNumEntries() < 2)
+ {
+ _app.showErrorMessage(getNameKey(), "dialog.deletebydate.onlyonedate");
+ }
+ else
+ {
+ // Create and build dialog if necessary
+ if (_dialog == null)
+ {
+ _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
+ _dialog.setLocationRelativeTo(_parentFrame);
+ _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ _dialog.getContentPane().add(makeDialogComponents());
+ _dialog.pack();
+ }
+ // Show dialog
+ _dialog.setVisible(true);
+ }
+ }
+
+ /**
+ * Create dialog components
+ * @return Panel containing all gui elements in dialog
+ */
+ private Component makeDialogComponents()
+ {
+ JPanel dialogPanel = new JPanel();
+ dialogPanel.setLayout(new BorderLayout(5, 5));
+ // Label at top
+ JLabel topLabel = new JLabel(I18nManager.getText("dialog.deletebydate.intro"));
+ topLabel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ dialogPanel.add(topLabel, BorderLayout.NORTH);
+
+ // close window if escape pressed
+ KeyAdapter escListener = new KeyAdapter() {
+ public void keyReleased(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ _dialog.dispose();
+ }
+ }
+ };
+
+ JTable infoTable = new JTable(new DeletionTableModel(_infoList));
+ JScrollPane pane = new JScrollPane(infoTable);
+ pane.setPreferredSize(new Dimension(300, 80));
+ pane.setBorder(BorderFactory.createEmptyBorder(2, 50, 2, 50));
+ dialogPanel.add(pane, BorderLayout.CENTER);
+
+ // button panel at bottom
+ JPanel buttonPanel = new JPanel();
+ buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ // OK button
+ _okButton = new JButton(I18nManager.getText("button.ok"));
+ _okButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ performDelete();
+ }
+ });
+ buttonPanel.add(_okButton);
+ _okButton.addKeyListener(escListener);
+ // Cancel button
+ JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
+ cancelButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ _dialog.dispose();
+ }
+ });
+ cancelButton.addKeyListener(new KeyAdapter() {
+ public void keyPressed(KeyEvent inE) {
+ if (inE.getKeyCode() == KeyEvent.VK_ESCAPE) {_dialog.dispose();}
+ }
+ });
+ buttonPanel.add(cancelButton);
+ dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
+ return dialogPanel;
+ }
+
+ /**
+ * Do the actual point deletion according to the
+ * selected rows in the table
+ */
+ private void performDelete()
+ {
+ int numMarked = 0;
+ final int numPoints = _app.getTrackInfo().getTrack().getNumPoints();
+ final int numDates = _infoList.getNumEntries();
+ // Loop over all points to mark each one for deletion or not
+ for (int p=0; p<numPoints; p++)
+ {
+ DataPoint point = _app.getTrackInfo().getTrack().getPoint(p);
+ if (point != null)
+ {
+ Date date = (point.hasTimestamp() ? point.getTimestamp().getCalendar().getTime() : null);
+ boolean pointMarked = false;
+ // Try to match each of the date info objects in the list
+ for (int d=0; d<numDates; d++)
+ {
+ DateInfo info = _infoList.getDateInfo(d);
+ if ( (info.isDateless() && date == null) // matches dateless
+ || (!info.isDateless() && date != null && info.isSameDate(date)))
+ {
+ pointMarked = info.getDeleteFlag();
+ break;
+ }
+ }
+ point.setMarkedForDeletion(pointMarked);
+ if (pointMarked) {
+ numMarked++;
+ }
+ }
+ }
+ // Now points have been marked, we can ask user to delete them (or delete automatically)
+ if (numMarked > 0) {
+ optionallyDeleteMarkedPoints(numMarked);
+ }
+ else {
+ // Do nothing //System.out.println("Nothing selected to delete!");
+ // delete flags might have been reset, so refresh display
+ UpdateMessageBroker.informSubscribers(DataSubscriber.SELECTION_CHANGED);
+ }
+ _dialog.dispose();
+ }
+}
--- /dev/null
+package tim.prune.function.deletebydate;
+
+import java.text.DateFormat;
+import javax.swing.table.AbstractTableModel;
+import tim.prune.I18nManager;
+
+/**
+ * Table model for selecting which dates to delete
+ */
+public class DeletionTableModel extends AbstractTableModel
+{
+ /** info list, one for each row of table */
+ private DateInfoList _infoList = null;
+
+ /** Formatter, determining how dates appear in the table */
+ private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
+ /** Column heading for date */
+ private static final String COLUMN_HEADING_DATE = I18nManager.getText("fieldname.date");
+ /** Column heading for number of points */
+ private static final String COLUMN_HEADING_NUMPOINTS = I18nManager.getText("details.track.points");
+ /** Column heading for keep */
+ private static final String COLUMN_HEADING_KEEP = I18nManager.getText("dialog.deletebydate.column.keep");
+ /** Column heading for delete */
+ private static final String COLUMN_HEADING_DELETE = I18nManager.getText("dialog.deletebydate.column.delete");
+
+
+ /**
+ * Constructor
+ * @param inList date info list from function
+ */
+ public DeletionTableModel(DateInfoList inList)
+ {
+ _infoList = inList;
+ }
+
+ /**
+ * @return column count
+ */
+ public int getColumnCount()
+ {
+ return 4; // always fixed (date, numpoints, keep, delete)
+ }
+
+ /**
+ * @return row count
+ */
+ public int getRowCount()
+ {
+ if (_infoList == null) {return 0;} // shouldn't happen
+ return _infoList.getNumEntries();
+ }
+
+ /**
+ * Get the name of the column
+ * @param inColNum column number
+ * @return column name
+ */
+ public String getColumnName(int inColNum)
+ {
+ if (inColNum == 0) return COLUMN_HEADING_DATE;
+ else if (inColNum == 1) return COLUMN_HEADING_NUMPOINTS;
+ else if (inColNum == 2) return COLUMN_HEADING_KEEP;
+ else if (inColNum == 3) return COLUMN_HEADING_DELETE;
+ return "unknown column!";
+ }
+
+ /**
+ * Get the class of objects in the given column
+ * @see javax.swing.table.AbstractTableModel#getColumnClass(int)
+ */
+ public Class<?> getColumnClass(int inColumnIndex)
+ {
+ if (inColumnIndex == 1) {return Integer.class;}
+ if (inColumnIndex > 1) {return Boolean.class;}
+ return super.getColumnClass(inColumnIndex);
+ }
+
+ /**
+ * Get whether the given cell is editable
+ * @see javax.swing.table.AbstractTableModel#isCellEditable(int, int)
+ */
+ public boolean isCellEditable(int inRowIndex, int inColumnIndex)
+ {
+ return (inColumnIndex > 1);
+ }
+
+ /**
+ * Set the value at the given table cell
+ * @see javax.swing.table.AbstractTableModel#setValueAt(java.lang.Object, int, int)
+ */
+ public void setValueAt(Object inValue, int inRowIndex, int inColumnIndex)
+ {
+ // can only edit the keep and delete columns
+ final boolean isKeep = (inColumnIndex == 2);
+ final boolean isDelete = (inColumnIndex == 3);
+ // ignore all events for other columns
+ if (isKeep || isDelete)
+ {
+ try {
+ boolean setFlag = ((Boolean) inValue).booleanValue();
+ if (setFlag)
+ {
+ _infoList.getDateInfo(inRowIndex).setDeleteFlag(isDelete);
+ // make sure the other cell (keep or delete) on the same row is updated too
+ fireTableCellUpdated(inRowIndex, 5 - inColumnIndex);
+ }
+ }
+ catch (ClassCastException cce) {}
+ }
+ }
+
+ /**
+ * @return cell contents at the given row, column inded
+ */
+ public Object getValueAt(int inRowIndex, int inColIndex)
+ {
+ try {
+ DateInfo info = _infoList.getDateInfo(inRowIndex);
+ if (info != null)
+ {
+ switch (inColIndex)
+ {
+ case 0: // date
+ if (info.isDateless()) {
+ return I18nManager.getText("dialog.deletebydate.nodate");
+ }
+ return DEFAULT_DATE_FORMAT.format(info.getDate());
+ case 1: // number of points
+ return info.getPointCount();
+ case 2: // keep
+ return !info.getDeleteFlag();
+ case 3: // delete
+ return info.getDeleteFlag();
+ }
+ }
+ }
+ catch (IndexOutOfBoundsException obe) {} // ignore, fallthrough
+ return null;
+ }
+}
private static final String _toColLabel = I18nManager.getText("dialog.distances.column.to");
/** Column heading (depends on metric/imperial settings) */
private String _distanceLabel = null;
+ /** Previous distance units */
+ private Unit _previousDistUnit = null;
/**
* @return column count
Unit distUnit = Config.getUnitSet().getDistanceUnit();
_distanceLabel = I18nManager.getText("fieldname.distance") + " (" +
I18nManager.getText(distUnit.getShortnameKey()) + ")";
+ final boolean distUnitsChanged = (distUnit != _previousDistUnit);
+ _previousDistUnit = distUnit;
+
// Initialize array of distances
int numRows = getRowCount();
if (_distances == null || _distances.length != numRows) {
_distances[i] = Distance.convertRadiansToDistance(rads);
}
}
- // Let table know that it has to refresh data (and might as well refresh column headings too)
- fireTableStructureChanged();
+ // Let table know that it has to refresh data, and maybe the whole table too
+ if (distUnitsChanged) {
+ fireTableStructureChanged();
+ }
+ else {
+ fireTableDataChanged();
+ }
}
}
import tim.prune.data.Field;
import tim.prune.data.Photo;
import tim.prune.data.Selection;
+import tim.prune.data.SourceInfo;
import tim.prune.data.SpeedCalculator;
import tim.prune.data.SpeedValue;
import tim.prune.data.TrackInfo;
private JLabel _indexLabel = null;
private JLabel _latLabel = null, _longLabel = null;
private JLabel _altLabel = null;
- private JLabel _timeLabel = null;
+ private JLabel _ptDateLabel = null, _ptTimeLabel = null;
private JLabel _descLabel = null;
private JLabel _speedLabel = null, _vSpeedLabel = null;
private JLabel _nameLabel = null, _typeLabel = null;
+ private JLabel _filenameLabel = null;
// Range details
private JLabel _rangeLabel = null;
private static final String LABEL_POINT_LATITUDE = I18nManager.getText("fieldname.latitude") + ": ";
private static final String LABEL_POINT_LONGITUDE = I18nManager.getText("fieldname.longitude") + ": ";
private static final String LABEL_POINT_ALTITUDE = I18nManager.getText("fieldname.altitude") + ": ";
- private static final String LABEL_POINT_TIMESTAMP = I18nManager.getText("fieldname.timestamp") + ": ";
+ private static final String LABEL_POINT_DATE = I18nManager.getText("fieldname.date") + ": ";
+ private static final String LABEL_POINT_TIME = I18nManager.getText("fieldname.timestamp") + ": ";
private static final String LABEL_POINT_WAYPOINTNAME = I18nManager.getText("fieldname.waypointname") + ": ";
private static final String LABEL_POINT_WAYPOINTTYPE = I18nManager.getText("fieldname.waypointtype") + ": ";
private static final String LABEL_POINT_DESCRIPTION = I18nManager.getText("fieldname.description") + ": ";
private static final String LABEL_POINT_SPEED = I18nManager.getText("fieldname.speed") + ": ";
private static final String LABEL_POINT_VERTSPEED = I18nManager.getText("fieldname.verticalspeed") + ": ";
+ private static final String LABEL_POINT_FILENAME = I18nManager.getText("details.track.file") + ": ";
private static final String LABEL_RANGE_SELECTED = I18nManager.getText("details.range.selected") + ": ";
private static final String LABEL_RANGE_DURATION = I18nManager.getText("fieldname.duration") + ": ";
private static final String LABEL_RANGE_DISTANCE = I18nManager.getText("fieldname.distance") + ": ";
pointDetailsPanel.add(_longLabel);
_altLabel = new JLabel("");
pointDetailsPanel.add(_altLabel);
- _timeLabel = new JLabel("");
- _timeLabel.setMinimumSize(new Dimension(120, 10));
- pointDetailsPanel.add(_timeLabel);
+ _ptDateLabel = new JLabel("");
+ _ptDateLabel.setMinimumSize(new Dimension(120, 10));
+ pointDetailsPanel.add(_ptDateLabel);
+ _ptTimeLabel = new JLabel("");
+ _ptTimeLabel.setMinimumSize(new Dimension(120, 10));
+ pointDetailsPanel.add(_ptTimeLabel);
_descLabel = new JLabel("");
pointDetailsPanel.add(_descLabel);
_speedLabel = new JLabel("");
pointDetailsPanel.add(_nameLabel);
_typeLabel = new JLabel("");
pointDetailsPanel.add(_typeLabel);
+ _filenameLabel = new JLabel("");
+ pointDetailsPanel.add(_filenameLabel);
pointDetailsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
// range details panel
_latLabel.setText("");
_longLabel.setText("");
_altLabel.setText("");
- _timeLabel.setText("");
+ _ptDateLabel.setText("");
+ _ptTimeLabel.setText("");
_descLabel.setText("");
_nameLabel.setText("");
_typeLabel.setText("");
_speedLabel.setText("");
_vSpeedLabel.setText("");
+ _filenameLabel.setText("");
}
else
{
I18nManager.getText(altUnit.getShortnameKey()))
: "");
if (currentPoint.hasTimestamp()) {
- _timeLabel.setText(LABEL_POINT_TIMESTAMP + currentPoint.getTimestamp().getText());
- _timeLabel.setToolTipText(currentPoint.getTimestamp().getText());
+ _ptDateLabel.setText(LABEL_POINT_DATE + currentPoint.getTimestamp().getDateText());
+ _ptTimeLabel.setText(LABEL_POINT_TIME + currentPoint.getTimestamp().getTimeText());
}
else {
- _timeLabel.setText("");
- _timeLabel.setToolTipText("");
+ _ptDateLabel.setText("");
+ _ptTimeLabel.setText("");
}
// Maybe the point has a description?
String pointDesc = currentPoint.getFieldValue(Field.DESCRIPTION);
_typeLabel.setText(LABEL_POINT_WAYPOINTTYPE + type);
}
else _typeLabel.setText("");
+
+ // File to which point belongs
+ final int numFiles = _trackInfo.getFileInfo().getNumFiles();
+ String filename = null;
+ if (numFiles > 1)
+ {
+ final SourceInfo info = _trackInfo.getFileInfo().getSourceForPoint(currentPoint);
+ if (info != null) {
+ filename = info.getName();
+ }
+ }
+ if (filename != null) {
+ _filenameLabel.setText(LABEL_POINT_FILENAME + filename);
+ _filenameLabel.setToolTipText(filename);
+ }
+ else {
+ _filenameLabel.setText("");
+ _filenameLabel.setToolTipText("");
+ }
}
// Update range details
String shortPath = shortenPath(fullPath);
_photoPathLabel.setText(fullPath == null ? "" : LABEL_FULL_PATH + shortPath);
_photoPathLabel.setToolTipText(currentPhoto.getFullPath());
- _photoTimestampLabel.setText(currentPhoto.hasTimestamp()?(LABEL_POINT_TIMESTAMP + currentPhoto.getTimestamp().getText()):"");
+ _photoTimestampLabel.setText(currentPhoto.hasTimestamp()?(LABEL_POINT_TIME + currentPhoto.getTimestamp().getText()):"");
_photoConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
+ (currentPhoto.getCurrentStatus() == Photo.Status.NOT_CONNECTED ?
I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes")));
String shortPath = shortenPath(fullPath);
_audioPathLabel.setText(fullPath == null ? "" : LABEL_FULL_PATH + shortPath);
_audioPathLabel.setToolTipText(fullPath == null ? "" : fullPath);
- _audioTimestampLabel.setText(currentAudio.hasTimestamp()?(LABEL_POINT_TIMESTAMP + currentAudio.getTimestamp().getText()):"");
+ _audioTimestampLabel.setText(currentAudio.hasTimestamp()?(LABEL_POINT_TIME + currentAudio.getTimestamp().getText()):"");
int audioLength = currentAudio.getLengthInSeconds();
_audioLengthLabel.setText(audioLength < 0?"":LABEL_RANGE_DURATION + DisplayUtils.buildDurationString(audioLength));
_audioConnectedLabel.setText(I18nManager.getText("details.media.connected") + ": "
{
final int DECIMAL_PLACES = 7;
if (inCoord == null) return "";
+ String result = inCoord;
final int dotPos = Math.max(inCoord.lastIndexOf('.'), inCoord.lastIndexOf(','));
- if (dotPos >= 0) {
+ if (dotPos >= 0)
+ {
final int chopPos = dotPos + DECIMAL_PLACES;
- if (chopPos < (inCoord.length()-1)) {
- return inCoord.substring(0, chopPos);
+ if (chopPos < (inCoord.length()-1))
+ {
+ result = inCoord.substring(0, chopPos);
+ // Maybe there's an exponential in there too which needs to be appended
+ int expPos = inCoord.toUpperCase().indexOf("E", chopPos);
+ if (expPos > 0 && expPos < (inCoord.length()-1))
+ {
+ result += inCoord.substring(expPos);
+ }
}
}
- return inCoord;
+ return result;
}
/**
{
/** Icon for window */
- public static final String WINDOW_ICON = "window_icon.png";
+ public static final String WINDOW_ICON = "window_icon";
/** Icon for scalebar button on main map display */
public static final String SCALEBAR_BUTTON = "scalebar.gif";
import tim.prune.UpdateMessageBroker;
import tim.prune.config.Config;
import tim.prune.data.AudioClip;
+import tim.prune.data.DataPoint;
import tim.prune.data.Field;
import tim.prune.data.Photo;
import tim.prune.data.RecentFile;
import tim.prune.data.Selection;
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
-import tim.prune.function.RearrangeWaypointsFunction.Rearrange;
+import tim.prune.function.ChooseSingleParameter;
import tim.prune.function.browser.UrlGenerator;
/**
private JMenuItem _compressItem = null;
private JMenuItem _markRectangleItem = null;
private JMenuItem _deleteMarkedPointsItem = null;
+ private JMenuItem _deleteByDateItem = null;
private JMenuItem _interpolateItem = null;
private JMenuItem _averageItem = null;
private JMenuItem _selectAllItem = null;
private JMenuItem _selectNoneItem = null;
+ private JMenuItem _selectSegmentItem = null;
private JMenuItem _selectStartItem = null;
private JMenuItem _selectEndItem = null;
private JMenuItem _findWaypointItem = null;
private JMenuItem _addTimeOffsetItem = null;
private JMenuItem _addAltitudeOffsetItem = null;
private JMenuItem _mergeSegmentsItem = null;
- private JMenu _rearrangeMenu = null;
+ private JMenuItem _rearrangeWaypointsItem = null;
private JMenuItem _splitSegmentsItem = null;
private JMenuItem _sewSegmentsItem = null;
private JMenuItem _cutAndMoveItem = null;
});
_deleteMarkedPointsItem.setEnabled(false);
trackMenu.add(_deleteMarkedPointsItem);
+ _deleteByDateItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_BY_DATE, false);
+ trackMenu.add(_deleteByDateItem);
trackMenu.addSeparator();
// Rearrange waypoints
- _rearrangeMenu = new JMenu(I18nManager.getText("menu.track.rearrange"));
- _rearrangeMenu.setEnabled(false);
- JMenuItem rearrangeStartItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.start"));
- rearrangeStartItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_START);
- }
- });
- rearrangeStartItem.setEnabled(true);
- _rearrangeMenu.add(rearrangeStartItem);
- JMenuItem rearrangeEndItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.end"));
- rearrangeEndItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_END);
- }
- });
- rearrangeEndItem.setEnabled(true);
- _rearrangeMenu.add(rearrangeEndItem);
- JMenuItem rearrangeNearestItem = new JMenuItem(I18nManager.getText("menu.track.rearrange.nearest"));
- rearrangeNearestItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS.rearrangeWaypoints(Rearrange.TO_NEAREST);
- }
- });
- rearrangeNearestItem.setEnabled(true);
- _rearrangeMenu.add(rearrangeNearestItem);
- trackMenu.add(_rearrangeMenu);
+ _rearrangeWaypointsItem = makeMenuItem(FunctionLibrary.FUNCTION_REARRANGE_WAYPOINTS, false);
+ trackMenu.add(_rearrangeWaypointsItem);
// Split track segments
_splitSegmentsItem = makeMenuItem(FunctionLibrary.FUNCTION_SPLIT_SEGMENTS, false);
trackMenu.add(_splitSegmentsItem);
}
});
rangeMenu.add(_selectNoneItem);
+ _selectSegmentItem = makeMenuItem(FunctionLibrary.FUNCTION_SELECT_SEGMENT);
+ rangeMenu.add(_selectSegmentItem);
rangeMenu.addSeparator();
_selectStartItem = new JMenuItem(I18nManager.getText("menu.range.start"));
_selectStartItem.setEnabled(false);
_deleteFieldValuesItem = makeMenuItem(FunctionLibrary.FUNCTION_DELETE_FIELD_VALUES, false);
rangeMenu.add(_deleteFieldValuesItem);
rangeMenu.addSeparator();
- _interpolateItem = makeMenuItem(FunctionLibrary.FUNCTION_INTERPOLATE, false);
+ _interpolateItem = makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_INTERPOLATE), false);
rangeMenu.add(_interpolateItem);
_averageItem = new JMenuItem(I18nManager.getText("menu.range.average"));
_averageItem.addActionListener(new ActionListener() {
// Set colours
settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_COLOURS));
// Set line width used for drawing
- settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LINE_WIDTH));
+ settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_LINE_WIDTH)));
// Set language
settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SET_LANGUAGE));
+ // Set altitude tolerance
+ settingsMenu.add(makeMenuItem(new ChooseSingleParameter(_app, FunctionLibrary.FUNCTION_SET_ALTITUDE_TOLERANCE)));
settingsMenu.addSeparator();
// Save configuration
settingsMenu.add(makeMenuItem(FunctionLibrary.FUNCTION_SAVECONFIG));
_compressItem.setEnabled(hasData);
_markRectangleItem.setEnabled(hasData);
_deleteMarkedPointsItem.setEnabled(hasData && _track.hasMarkedPoints());
- _rearrangeMenu.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
+ _rearrangeWaypointsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.hasWaypoints());
_splitSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
_sewSegmentsItem.setEnabled(hasData && _track.hasTrackPoints() && _track.getNumPoints() > 3);
_selectAllItem.setEnabled(hasData);
_findWaypointItem.setEnabled(hasData && _track.hasWaypoints());
// have we got a cache?
_downloadSrtmItem.setEnabled(hasData && Config.getConfigString(Config.KEY_DISK_CACHE) != null);
+ // have we got any timestamps?
+ _deleteByDateItem.setEnabled(hasData && _track.hasData(Field.TIMESTAMP));
// is undo available?
boolean hasUndo = !_app.getUndoStack().isEmpty();
_undoButton.setEnabled(hasUndo);
_clearUndoItem.setEnabled(hasUndo);
// is there a current point?
- boolean hasPoint = (hasData && _selection.getCurrentPointIndex() >= 0);
+ DataPoint currPoint = _app.getTrackInfo().getCurrentPoint();
+ boolean hasPoint = (currPoint != null);
_editPointItem.setEnabled(hasPoint);
_editPointButton.setEnabled(hasPoint);
_editWaypointNameItem.setEnabled(hasPoint);
_selectEndItem.setEnabled(hasPoint);
_selectEndButton.setEnabled(hasPoint);
_duplicatePointItem.setEnabled(hasPoint);
+ // is it a waypoint?
+ _selectSegmentItem.setEnabled(hasPoint && !currPoint.isWaypoint());
// are there any photos?
boolean anyPhotos = _app.getTrackInfo().getPhotoList().getNumPhotos() > 0;
_saveExifItem.setEnabled(anyPhotos && _app.getTrackInfo().getPhotoList().hasMediaWithFile());
_selectNoAudioItem.setEnabled(hasAudio);
_removeAudioItem.setEnabled(hasAudio);
_connectAudioItem.setEnabled(hasAudio && hasPoint && currentAudio.getDataPoint() == null);
- _disconnectAudioItem.setEnabled(hasAudio && _app.getTrackInfo().getCurrentAudio().getDataPoint() != null);
+ _disconnectAudioItem.setEnabled(hasAudio && currentAudio.getDataPoint() != null);
_correlateAudiosItem.setEnabled(anyAudios && hasData);
// is there a current range?
boolean hasRange = (hasData && _selection.hasRangeSelected());
for (int i=0; i<numRecentFiles; i++)
{
JMenuItem item = _recentFileMenu.getItem(i);
- item.setText(rfl.getFile(i)==null?"":rfl.getFile(i).getFile().getName());
- item.setToolTipText(rfl.getFile(i)==null?null:rfl.getFile(i).getFile().getAbsolutePath());
+ RecentFile rf = rfl.getFile(i);
+ item.setText(rf==null?"":rf.getFile().getName());
+ item.setToolTipText(rf==null?null:rf.getFile().getAbsolutePath());
}
}
else
import javax.swing.JPanel;
import tim.prune.I18nManager;
+import tim.prune.config.Config;
import tim.prune.threedee.TerrainDefinition;
/**
JLabel label = new JLabel(I18nManager.getText("dialog.3d.terraingridsize") + ": ");
add(label);
_gridSizeField = new WholeNumberField(4);
- _gridSizeField.setValue(50); // default grid size
+ _gridSizeField.setValue(Config.getConfigInt(Config.KEY_TERRAIN_GRID_SIZE)); // default grid size
_gridSizeField.setMaximumSize(new Dimension(100, 50));
_gridSizeField.setEnabled(false);
add(_gridSizeField);
package tim.prune.gui;
import javax.swing.JTextField;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
{
super(inMaxDigits);
setDocument(new WholeNumberDocument(inMaxDigits));
+ getDocument().addDocumentListener(new DocumentListener() {
+ public void removeUpdate(DocumentEvent arg0) {fireActionPerformed();}
+ public void insertUpdate(DocumentEvent arg0) {fireActionPerformed();}
+ public void changedUpdate(DocumentEvent arg0) {fireActionPerformed();}
+ });
}
/**
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Colourer based on altitude values
+ */
+public class AltitudeColourer extends ContinuousPointColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public AltitudeColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ final int numPoints = track == null ? 0 : track.getNumPoints();
+ DataPoint point = null;
+
+ // Figure out altitude range
+ double minAltitude = 0.0;
+ double maxAltitude = 0.0;
+ boolean altFound = false;
+ for (int i=0; i<numPoints; i++)
+ {
+ point = track.getPoint(i);
+ if (point != null && point.hasAltitude())
+ {
+ double altValue = point.getAltitude().getMetricValue();
+ if (altValue < minAltitude || !altFound) {minAltitude = altValue;}
+ if (altValue > maxAltitude || !altFound) {maxAltitude = altValue;}
+ altFound = true;
+ }
+ }
+
+ if ((maxAltitude - minAltitude) < 1.0)
+ {
+ // not enough altitude range, set all to null
+ init(0);
+ }
+ else
+ {
+ // initialise the array to the right size
+ init(numPoints);
+ // loop over track points to calculate colours
+ for (int i=0; i<numPoints; i++)
+ {
+ point = track.getPoint(i);
+ if (point != null && point.hasAltitude() && !point.isWaypoint())
+ {
+ double altValue = point.getAltitude().getMetricValue();
+ double fraction = (altValue - minAltitude) / (maxAltitude - minAltitude);
+ setColour(i, mixColour((float) fraction));
+ }
+ else setColour(i, null);
+ }
+ }
+ }
+}
-package tim.prune.gui;
+package tim.prune.gui.colour;
import java.awt.BorderLayout;
import java.awt.Color;
-package tim.prune.gui;
+package tim.prune.gui.colour;
import java.awt.Color;
import java.awt.Dimension;
{
/**
* Constructor
+ * @param inColour starting colour
*/
public ColourPatch(Color inColour)
{
--- /dev/null
+package tim.prune.gui.colour;
+
+import tim.prune.App;
+import tim.prune.DataSubscriber;
+
+/**
+ * Caretaker of the current PointColourer, responsible for listening
+ * to data changes and updating the colourer
+ */
+public class ColourerCaretaker implements DataSubscriber
+{
+ /** App object for getting the track */
+ private App _app = null;
+ /** PointColourer object for passing details to */
+ private PointColourer _colourer = null;
+
+ /**
+ * Constructor
+ * @param inApp app object to use
+ */
+ public ColourerCaretaker(App inApp)
+ {
+ _app = inApp;
+ }
+
+ /**
+ * @param inColourer current colourer object
+ */
+ public void setColourer(PointColourer inColourer)
+ {
+ _colourer = inColourer;
+ dataUpdated(ALL);
+ }
+
+ /**
+ * @return point colourer, or null
+ */
+ public PointColourer getColourer()
+ {
+ return _colourer;
+ }
+
+ /**
+ * Data has been updated
+ */
+ public void dataUpdated(byte inUpdateType)
+ {
+ if ((inUpdateType &
+ (DataSubscriber.DATA_ADDED_OR_REMOVED | DataSubscriber.DATA_EDITED)) > 0
+ && _colourer != null)
+ {
+ _colourer.calculateColours(_app.getTrackInfo());
+ }
+ }
+
+ /** Don't care about status */
+ public void actionCompleted(String inMessage) {}
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.config.ColourUtils;
+
+/**
+ * Factory for the creation of PointColourer objects
+ */
+public abstract class ColourerFactory
+{
+ /** Enumeration of colourer types */
+ public enum ColourerId
+ {
+ NONE,
+ BY_FILE,
+ BY_SEGMENT,
+ BY_ALTITUDE,
+ BY_SPEED,
+ BY_VSPEED,
+ BY_GRADIENT,
+ BY_DATE
+ }
+
+ /**
+ * Does the specified colourer need a field for maximum number of colours?
+ * @param inId id of colourer
+ * @return true if max colours required, false otherwise
+ */
+ public static boolean isMaxColoursRequired(ColourerId inId)
+ {
+ switch (inId)
+ {
+ case NONE: return false;
+ case BY_FILE: return FileColourer.isMaxColoursRequired();
+ case BY_SEGMENT: return SegmentColourer.isMaxColoursRequired();
+ case BY_ALTITUDE: return AltitudeColourer.isMaxColoursRequired();
+ case BY_SPEED: return SpeedColourer.isMaxColoursRequired();
+ case BY_VSPEED: return VertSpeedColourer.isMaxColoursRequired();
+ case BY_GRADIENT: return GradientColourer.isMaxColoursRequired();
+ case BY_DATE: return DateColourer.isMaxColoursRequired();
+ }
+ return false;
+ }
+
+ /**
+ * Does the specified colourer need fields for start and end colours?
+ * @param inId id of colourer
+ * @return true if colours required, false otherwise
+ */
+ public static boolean areColoursRequired(ColourerId inId)
+ {
+ // all of them except NONE need start and end colours
+ return inId != ColourerId.NONE;
+ }
+
+ /**
+ * @param inDesc Single character used as a code (in Config string)
+ * @return associated ColourerId
+ */
+ private static ColourerId getColourerId(char inDesc)
+ {
+ switch (inDesc)
+ {
+ case 'f': return ColourerId.BY_FILE;
+ case 's': return ColourerId.BY_SEGMENT;
+ case 'a': return ColourerId.BY_ALTITUDE;
+ case 'p': return ColourerId.BY_SPEED;
+ case 'v': return ColourerId.BY_VSPEED;
+ case 'g': return ColourerId.BY_GRADIENT;
+ case 'd': return ColourerId.BY_DATE;
+ }
+ return ColourerId.NONE;
+ }
+
+ /**
+ * Create a new PointColourer object given the parameters
+ * @param inId id of colourer to create
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ * @param inMaxColours maximum number of colours
+ * @return PointColourer object, or null
+ */
+ public static PointColourer createColourer(ColourerId inId, Color inStartColour, Color inEndColour, String inMaxColours)
+ {
+ try
+ {
+ switch (inId)
+ {
+ case NONE: return null;
+ case BY_FILE: return new FileColourer(inStartColour, inEndColour, Integer.parseInt(inMaxColours));
+ case BY_SEGMENT: return new SegmentColourer(inStartColour, inEndColour, Integer.parseInt(inMaxColours));
+ case BY_ALTITUDE: return new AltitudeColourer(inStartColour, inEndColour);
+ case BY_SPEED: return new SpeedColourer(inStartColour, inEndColour);
+ case BY_VSPEED: return new VertSpeedColourer(inStartColour, inEndColour);
+ case BY_GRADIENT: return new GradientColourer(inStartColour, inEndColour);
+ case BY_DATE: return new DateColourer(inStartColour, inEndColour, Integer.parseInt(inMaxColours));
+ }
+ }
+ catch (NumberFormatException nfe) {} // drop out to return null
+ return null;
+ }
+
+ /**
+ * Create a PointColourer object from the given description
+ * @param inString string from config
+ * @return PointColourer object, or null if string was invalid
+ */
+ public static PointColourer createColourer(String inString)
+ {
+ try
+ {
+ String[] comps = inString.split(";");
+ if (comps.length == 4)
+ {
+ ColourerId colourerType = getColourerId(comps[0].charAt(0));
+ Color startColour = ColourUtils.colourFromHex(comps[1]);
+ Color endColour = ColourUtils.colourFromHex(comps[2]);
+ String maxColours = comps[3];
+ return createColourer(colourerType, startColour, endColour, maxColours);
+ }
+ }
+ catch (NullPointerException npe) {}
+ catch (NumberFormatException nfe) {}
+ return null;
+ }
+
+ /**
+ * Convert the given PointColourer object into a string for the config
+ * @param inColourer PointColourer object
+ * @return string describing object (for later re-creation) or null
+ */
+ public static String PointColourerToString(PointColourer inColourer)
+ {
+ if (inColourer != null)
+ {
+ final String startColour = ColourUtils.makeHexCode(inColourer.getStartColour());
+ final String endColour = ColourUtils.makeHexCode(inColourer.getEndColour());
+ final int maxColours = inColourer.getMaxColours();
+ if (inColourer instanceof FileColourer) {
+ return "f;" + startColour + ";" + endColour + ";" + maxColours;
+ }
+ else if (inColourer instanceof SegmentColourer) {
+ return "s;" + startColour + ";" + endColour + ";" + maxColours;
+ }
+ else if (inColourer instanceof AltitudeColourer) {
+ return "a;" + startColour + ";" + endColour + ";0";
+ }
+ else if (inColourer instanceof SpeedColourer) {
+ return "p;" + startColour + ";" + endColour + ";0";
+ }
+ else if (inColourer instanceof VertSpeedColourer) {
+ return "v;" + startColour + ";" + endColour + ";0";
+ }
+ else if (inColourer instanceof GradientColourer) {
+ return "g;" + startColour + ";" + endColour + ";0";
+ }
+ else if (inColourer instanceof DateColourer) {
+ return "d;" + startColour + ";" + endColour + ";" + maxColours;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the colourer-specific end of the description key for translation
+ * @param inId id of colourer
+ * @return end of description key for combobox text
+ */
+ public static String getDescriptionKey(ColourerId inId)
+ {
+ switch (inId)
+ {
+ case NONE: return "none";
+ case BY_FILE: return "byfile";
+ case BY_SEGMENT: return "bysegment";
+ case BY_ALTITUDE: return "byaltitude";
+ case BY_SPEED: return "byspeed";
+ case BY_VSPEED: return "byvertspeed";
+ case BY_GRADIENT: return "bygradient";
+ case BY_DATE: return "bydate";
+ }
+ return null;
+ }
+
+ /**
+ * Get the id of the given colourer, according to its class
+ * @param inColourer point colourer object, or null
+ * @return id, for example for selection in dropdown
+ */
+ public static ColourerId getId(PointColourer inColourer)
+ {
+ if (inColourer != null)
+ {
+ if (inColourer instanceof FileColourer) {return ColourerId.BY_FILE;}
+ if (inColourer instanceof SegmentColourer) {return ColourerId.BY_SEGMENT;}
+ if (inColourer instanceof AltitudeColourer) {return ColourerId.BY_ALTITUDE;}
+ if (inColourer instanceof SpeedColourer) {return ColourerId.BY_SPEED;}
+ if (inColourer instanceof VertSpeedColourer) {return ColourerId.BY_VSPEED;}
+ if (inColourer instanceof GradientColourer) {return ColourerId.BY_GRADIENT;}
+ if (inColourer instanceof DateColourer) {return ColourerId.BY_DATE;}
+ }
+ return ColourerId.NONE;
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.border.EtchedBorder;
+
+import tim.prune.I18nManager;
+import tim.prune.gui.GuiGridLayout;
+import tim.prune.gui.colour.ColourerFactory.ColourerId;
+
+/**
+ * Class to provide a gui panel for selecting a colourer
+ * including which colourer, the start/end colours and
+ * optionally the max number of colours
+ */
+public class ColourerSelectorPanel extends JPanel
+{
+ /** Combo box for selecting the type of colourer */
+ private JComboBox<String> _typeCombo = null;
+ /** Array of type ids as stored in combo box */
+ private ColourerId[] _typeIds = null;
+ /** Panel object holding the colour patches */
+ private JPanel _patchPanel = null;
+ /** Array of colour patches for start and end */
+ private ColourPatch[] _startEndPatches = null;
+ /** Panel holding the max colours selection */
+ JPanel _maxColoursPanel = null;
+ private JComboBox<String> _maxColoursCombo = null;
+
+ /** Array of label keys for the 2 patches */
+ private static final String[] LABEL_KEYS = {"start", "end"};
+
+
+ /**
+ * Constructor
+ * @param inColourChooser colour chooser to use (needs reference to parent dialog)
+ */
+ public ColourerSelectorPanel(ColourChooser inColourChooser)
+ {
+ _typeIds = new ColourerId[] {ColourerId.NONE, ColourerId.BY_FILE,
+ ColourerId.BY_SEGMENT, ColourerId.BY_DATE, ColourerId.BY_ALTITUDE,
+ ColourerId.BY_SPEED, ColourerId.BY_VSPEED, ColourerId.BY_GRADIENT};
+ makeGuiComponents(inColourChooser);
+ }
+
+
+ /**
+ * Create all the gui components and lay them out in the panel
+ * @param inColourChooser colour chooser to use
+ */
+ private void makeGuiComponents(ColourChooser inColourChooser)
+ {
+ // Etched border and vertical layout
+ setBorder(BorderFactory.createCompoundBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(4, 4, 4, 4))
+ );
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+ // Label at the top
+ JLabel introLabel = new JLabel(I18nManager.getText("dialog.colourer.intro"));
+ introLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(introLabel);
+
+ // Combo box for selecting which colourer to use
+ JPanel typePanel = new JPanel();
+ GuiGridLayout grid = new GuiGridLayout(typePanel);
+ final String keyPrefix = "dialog.colourer.type.";
+ String[] colourerTypes = new String[_typeIds.length];
+ for (int i=0; i<colourerTypes.length; i++)
+ {
+ colourerTypes[i] = I18nManager.getText(keyPrefix +
+ ColourerFactory.getDescriptionKey(_typeIds[i]));
+ }
+ _typeCombo = new JComboBox<String>(colourerTypes);
+ _typeCombo.setAlignmentX(Component.LEFT_ALIGNMENT);
+ _typeCombo.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e)
+ {
+ onColourerTypeChanged();
+ }
+ });
+ // Add to the panel
+ grid.add(new JLabel(I18nManager.getText("dialog.colourer.type")));
+ grid.add(_typeCombo);
+ typePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(typePanel);
+
+ // Make panel for colour patches
+ _patchPanel = new JPanel();
+ _patchPanel.setLayout(new GridLayout());
+ _startEndPatches = new ColourPatch[2];
+
+ // Blank column
+ JPanel blankColumn = new JPanel();
+ ColourPatch blankPatch = new ColourPatch(Color.BLACK);
+ blankPatch.setVisible(false);
+ blankColumn.add(blankPatch);
+ _patchPanel.add(blankColumn);
+
+ // Loop over two columns of patches
+ for (int i=0; i<2; i++)
+ {
+ JPanel colPanel = new JPanel();
+ colPanel.setLayout(new BoxLayout(colPanel, BoxLayout.Y_AXIS));
+ // Top label and patch
+ colPanel.add(new JLabel(I18nManager.getText("dialog.colourer." + LABEL_KEYS[i])));
+ ColourPatch patch = new ColourPatch(Color.BLUE); // will be set by init() method shortly
+ patch.addMouseListener(new PatchListener(patch, inColourChooser));
+ colPanel.add(patch);
+ _startEndPatches[i] = patch;
+
+ // Add column to panel
+ colPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+ _patchPanel.add(colPanel);
+ }
+
+ // Blank column
+ blankColumn = new JPanel();
+ blankPatch = new ColourPatch(Color.BLACK);
+ blankPatch.setVisible(false);
+ blankColumn.add(blankPatch);
+ _patchPanel.add(blankColumn);
+
+ _patchPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(_patchPanel);
+
+ // Combo box for selecting max colours
+ _maxColoursPanel = new JPanel();
+ grid = new GuiGridLayout(_maxColoursPanel);
+ grid.add(new JLabel(I18nManager.getText("dialog.colourer.maxcolours")));
+ String[] colourOptions = new String[] {"2", "3", "5", "10", "15"};
+ _maxColoursCombo = new JComboBox<String>(colourOptions);
+ grid.add(_maxColoursCombo);
+ _maxColoursPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
+ add(_maxColoursPanel);
+ }
+
+ /**
+ * Init the colours from the colourer (if possible) or the default colour
+ * @param inColourer current colourer object, or null
+ * @param inDefaultColour current colour for points
+ */
+ public void init(PointColourer inColourer, Color inDefaultColour)
+ {
+ Color startColour = null, endColour = null;
+ if (inColourer != null)
+ {
+ selectColourerType(ColourerFactory.getId(inColourer));
+ startColour = inColourer.getStartColour();
+ endColour = inColourer.getEndColour();
+ _maxColoursCombo.setSelectedItem("" + inColourer.getMaxColours());
+ }
+ else
+ {
+ // no colourer, so default to 5 colours maximum
+ _maxColoursCombo.setSelectedIndex(2);
+ }
+ if (startColour == null) {startColour = inDefaultColour;}
+ if (endColour == null) {endColour = makeDefaultEndColour(inDefaultColour);}
+ if (startColour != null) {_startEndPatches[0].setBackground(startColour);}
+ if (endColour != null) {_startEndPatches[1].setBackground(endColour);}
+ onColourerTypeChanged(); // make sure gui is updated
+ }
+
+ /**
+ * Make a default end colour if there isn't one already defined
+ * @param inStartColour start colour
+ * @return end colour, with the hue shifted by a third from the start
+ */
+ private static Color makeDefaultEndColour(Color inStartColour)
+ {
+ float[] defaultHSB = Color.RGBtoHSB(inStartColour.getRed(), inStartColour.getGreen(), inStartColour.getBlue(), null);
+ // add 120 degrees to the hue
+ defaultHSB[0] += (1.0f/3f);
+ return Color.getHSBColor(defaultHSB[0], defaultHSB[1], defaultHSB[2]);
+ }
+
+ /**
+ * React to the colourer type being changed
+ * by showing / hiding gui elements
+ */
+ private void onColourerTypeChanged()
+ {
+ final ColourerId id = _typeIds[_typeCombo.getSelectedIndex()];
+ // Set visibility of controls according to whether they're needed for the selected type
+ _patchPanel.setVisible(ColourerFactory.areColoursRequired(id));
+ _maxColoursPanel.setVisible(ColourerFactory.isMaxColoursRequired(id));
+ }
+
+ /**
+ * @return the selected colourer object, or null
+ */
+ public PointColourer getSelectedColourer()
+ {
+ final ColourerId id = _typeIds[_typeCombo.getSelectedIndex()];
+ return ColourerFactory.createColourer(id, _startEndPatches[0].getBackground(),
+ _startEndPatches[1].getBackground(), _maxColoursCombo.getSelectedItem().toString());
+ }
+
+ /**
+ * Select the appropriate item in the dropdown
+ * @param inId id of colourer to choose
+ */
+ private void selectColourerType(ColourerId inId)
+ {
+ int selIndex = -1;
+ for (int i=0; i<_typeIds.length; i++)
+ {
+ if (_typeIds[i] == inId) {
+ selIndex = i;
+ break;
+ }
+ }
+ if (selIndex < 0) {
+ System.err.println("Id " + inId + " not found in _typeIds!");
+ }
+ else {
+ _typeCombo.setSelectedIndex(selIndex);
+ }
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+public abstract class ContinuousPointColourer extends PointColourer
+{
+ /** array of colours to use */
+ private Color[] _colours = null;
+
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public ContinuousPointColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ /** Continuous colourers don't need a maximum count */
+ public static boolean isMaxColoursRequired() {
+ return false;
+ }
+
+ /**
+ * Initialise the array to the right size
+ * @param inNumPoints number of points in the track
+ */
+ protected void init(int inNumPoints)
+ {
+ if (_colours == null || _colours.length != inNumPoints)
+ {
+ // Array needs to be created or resized
+ if (inNumPoints > 0) {
+ _colours = new Color[inNumPoints];
+ }
+ else {
+ _colours = null;
+ }
+ }
+ }
+
+ /**
+ * Set the colour at the given index
+ * @param inPointIndex point index
+ * @param inColour colour to use, or null
+ */
+ protected void setColour(int inPointIndex, Color inColour)
+ {
+ if (_colours != null && _colours.length > inPointIndex && inPointIndex >= 0)
+ {
+ _colours[inPointIndex] = inColour;
+ }
+ }
+
+ /**
+ * Get the colour for the given point index
+ * @param inPointIndex index of point in track
+ * @return colour object
+ */
+ public Color getColour(int inPointIndex)
+ {
+ Color colour = null;
+ if (_colours != null && _colours.length > inPointIndex && inPointIndex >= 0)
+ {
+ colour = _colours[inPointIndex];
+ }
+ if (colour == null) {
+ // not found, use default
+ colour = super.getDefaultColour();
+ }
+ return colour;
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.TimeZone;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.Timestamp;
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Point colourer giving a different colour to each date
+ * Uses the system timezone so may give funny results for
+ * data from other timezones (eg far-away holidays)
+ */
+public class DateColourer extends DiscretePointColourer
+{
+ // Doesn't really matter what format is used here, as long as dates are different
+ private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateInstance();
+
+ /**
+ * Constructor
+ * @param inStartColour start colour of scale
+ * @param inEndColour end colour of scale
+ * @param inWrapLength number of unique colours before wrap
+ */
+ public DateColourer(Color inStartColour, Color inEndColour, int inWrapLength)
+ {
+ super(inStartColour, inEndColour, inWrapLength);
+ }
+
+ /**
+ * Calculate the colours for each of the points in the given track
+ * @param inTrackInfo track info object
+ */
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ // initialise the array to the right size
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ final int numPoints = track == null ? 0 : track.getNumPoints();
+ init(numPoints);
+ // Make a hashmap of the already-used dates
+ HashMap<String, Integer> usedDates = new HashMap<String, Integer>(20);
+ // Also store the previous one, because they're probably consecutive
+ String prevDate = null;
+ int prevIndex = -1;
+
+ // loop over track points
+ int dayIndex = -1;
+ for (int i=0; i<numPoints; i++)
+ {
+ DataPoint p = track.getPoint(i);
+ if (p != null && !p.isWaypoint())
+ {
+ dayIndex = 0; // default index 0 will be used if no date found
+ String date = getDate(p.getTimestamp());
+ if (date != null)
+ {
+ // Check if it's the previous one
+ if (prevDate != null && date.equals(prevDate)) {
+ dayIndex = prevIndex;
+ }
+ else
+ {
+ // Look up in the hashmap to see if it's been used before
+ Integer foundIndex = usedDates.get(date);
+ if (foundIndex == null)
+ {
+ // not been used before, so add it
+ dayIndex = usedDates.size() + 1;
+ usedDates.put(date, dayIndex);
+ }
+ else
+ {
+ // found it
+ dayIndex = foundIndex;
+ }
+ // Remember what we've got for the next point
+ prevDate = date;
+ prevIndex = dayIndex;
+ }
+ }
+ // if date is null (no timestamp or invalid) then dayIndex remains 0
+ setColour(i, dayIndex);
+ }
+ }
+ // generate the colours needed
+ generateDiscreteColours(usedDates.size() + 1);
+ }
+
+
+ /**
+ * Find which date (in the system timezone) the given timestamp falls on
+ * @param inTimestamp timestamp
+ * @return String containing description of date, or null
+ */
+ private static String getDate(Timestamp inTimestamp)
+ {
+ if (inTimestamp == null || !inTimestamp.isValid()) {
+ return null;
+ }
+ Calendar cal = inTimestamp.getCalendar();
+ // use system time zone
+ cal.setTimeZone(TimeZone.getDefault());
+ return DEFAULT_DATE_FORMAT.format(cal.getTime());
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+/**
+ * Abstract class to do the discrete colouring of points,
+ * using start and end colours and a wrapping index
+ */
+public abstract class DiscretePointColourer extends PointColourer
+{
+ /** array of discrete colours to use */
+ private Color[] _discreteColours = null;
+ /** array of colour indexes */
+ private int[] _colourIndexes = null;
+
+
+ /**
+ * Constructor
+ * @param inStartColour start colour of scale
+ * @param inEndColour end colour of scale
+ * @param inMaxColours number of unique colours before wrap
+ */
+ public DiscretePointColourer(Color inStartColour, Color inEndColour, int inMaxColours)
+ {
+ super(inStartColour, inEndColour, inMaxColours);
+ }
+
+ /** max number of colours is required here */
+ public static boolean isMaxColoursRequired() {
+ return true;
+ }
+
+ /**
+ * Initialise the array to the right size
+ * @param inNumPoints number of points in the track
+ */
+ protected void init(int inNumPoints)
+ {
+ if (_colourIndexes == null || _colourIndexes.length != inNumPoints)
+ {
+ // Array needs to be created or resized
+ if (inNumPoints > 0) {
+ _colourIndexes = new int[inNumPoints];
+ }
+ else {
+ _colourIndexes = null;
+ }
+ }
+ }
+
+ /**
+ * Set the colour at the given index
+ * @param inPointIndex point index
+ * @param inColourIndex index of colour to use
+ */
+ protected void setColour(int inPointIndex, int inColourIndex)
+ {
+ if (_colourIndexes != null && _colourIndexes.length > inPointIndex && inPointIndex >= 0)
+ {
+ _colourIndexes[inPointIndex] = inColourIndex;
+ }
+ }
+
+ /**
+ * Get the colour for the given point index
+ * @param inPointIndex index of point in track
+ * @return colour object
+ */
+ public Color getColour(int inPointIndex)
+ {
+ if (_colourIndexes != null && _colourIndexes.length > inPointIndex && inPointIndex >= 0 && getMaxColours() > 0)
+ {
+ int colourIndex = _colourIndexes[inPointIndex] % getMaxColours();
+ if (colourIndex >= 0 && _discreteColours != null && colourIndex < _discreteColours.length) {
+ return _discreteColours[colourIndex];
+ }
+ }
+ // not found, use default
+ return super.getDefaultColour();
+ }
+
+ /**
+ * Generate the set of discrete colours to use
+ * @param inNumCategories number of different categories found in the data
+ */
+ protected void generateDiscreteColours(int inNumCategories)
+ {
+ int maxColours = getMaxColours();
+ if (maxColours <= 1) {maxColours = 2;}
+ if (inNumCategories < 1) {inNumCategories = 1;}
+ else if (inNumCategories > maxColours) {inNumCategories = maxColours;}
+
+ // Use this number of categories to generate the colours
+ _discreteColours = new Color[inNumCategories];
+ for (int i=0; i<inNumCategories; i++) {
+ _discreteColours[i] = mixColour(i, inNumCategories);
+ }
+ }
+
+ /**
+ * Mix the given colours together by interpolating H,S,B values
+ * @param inIndex index from 0 to inWrap-1
+ * @param inWrap wrap length
+ * @return mixed colour
+ */
+ private Color mixColour(int inIndex, int inWrap)
+ {
+ float fraction = inWrap < 2 ? 0.0f : (float) inIndex / (float) (inWrap - 1);
+ return mixColour(fraction);
+ }
+
+ /**
+ * @param inIndex specified colour index
+ * @return precalculated colour at the given index
+ */
+ protected Color getDiscreteColour(int inIndex)
+ {
+ if (_discreteColours == null || inIndex < 0 || getMaxColours() <= 1) {
+ return getDefaultColour();
+ }
+ return _discreteColours[inIndex % getMaxColours()];
+ }
+
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+import java.util.ArrayList;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.FileInfo;
+import tim.prune.data.SourceInfo;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Colours points according to which file (or source) they came from
+ */
+public class FileColourer extends DiscretePointColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour of scale
+ * @param inEndColour end colour of scale
+ * @param inWrapLength number of unique colours before wrap
+ */
+ public FileColourer(Color inStartColour, Color inEndColour, int inWrapLength)
+ {
+ super(inStartColour, inEndColour, inWrapLength);
+ }
+
+ /**
+ * Calculate the colours for each of the points in the given track
+ * @param inTrack track object
+ */
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ // initialise the array to the right size
+ final int numPoints = inTrackInfo == null ? 0 : inTrackInfo.getTrack().getNumPoints();
+ init(numPoints);
+
+ // loop over track points
+ FileInfo fInfo = inTrackInfo.getFileInfo();
+ ArrayList<SourceInfo> sourceList = new ArrayList<SourceInfo>();
+ for (int i=0; i<numPoints; i++)
+ {
+ DataPoint p = inTrackInfo.getTrack().getPoint(i);
+ if (p != null && !p.isWaypoint())
+ {
+ SourceInfo sInfo = fInfo.getSourceForPoint(p);
+ // Is this info object already in the list?
+ int foundIndex = -1;
+ int sIndex = 0;
+ for (SourceInfo si : sourceList) {
+ if (si == sInfo) {
+ foundIndex = sIndex;
+ break;
+ }
+ sIndex++;
+ }
+ // Add source info to list
+ if (foundIndex < 0)
+ {
+ sourceList.add(sInfo);
+ foundIndex = sourceList.size()-1;
+ }
+ // use this foundIndex to find the colour
+ setColour(i, foundIndex);
+ }
+ }
+ // generate the colours needed
+ generateDiscreteColours(sourceList.size());
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.profile.GradientData;
+
+/**
+ * Colourer based on gradient or glide slope values
+ */
+public class GradientColourer extends ProfileDataColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public GradientColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ // Calculate gradient value for each point
+ GradientData data = new GradientData(track);
+ calculateColours(track, data);
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+
+/**
+ * Listener class to react to patch clicks
+ */
+public class PatchListener extends MouseAdapter
+{
+ /** Associated patch */
+ private ColourPatch _patch = null;
+ /** Colour chooser object, shared between listeners */
+ private ColourChooser _colourChooser = null;
+
+ /**
+ * Constructor
+ * @param inPatch patch object to listen to
+ * @param inChooser colour chooser to use for selection
+ */
+ public PatchListener(ColourPatch inPatch, ColourChooser inChooser)
+ {
+ _patch = inPatch;
+ _colourChooser = inChooser;
+ }
+
+ /** React to mouse clicks */
+ public void mouseClicked(MouseEvent e)
+ {
+ _colourChooser.showDialog(_patch.getBackground());
+ Color colour = _colourChooser.getChosenColour();
+ if (colour != null) _patch.setColour(colour);
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.TrackInfo;
+
+/**
+ * Abstract class to do the colouring of points,
+ * that is holding a colour for each track point
+ * in the current track
+ */
+public abstract class PointColourer
+{
+ /** default colour */
+ private Color _defaultColour = Color.BLUE;
+ /** start and end colours */
+ private Color _startColour = null, _endColour = null;
+ /** max number of unique colours before wrapping */
+ private int _maxColours = 1;
+
+
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ * @param inMaxColours max number of colours
+ */
+ public PointColourer(Color inStartColour, Color inEndColour, int inMaxColours)
+ {
+ _startColour = inStartColour;
+ _endColour = inEndColour;
+ _maxColours = inMaxColours;
+ }
+
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public PointColourer(Color inStartColour, Color inEndColour)
+ {
+ this(inStartColour, inEndColour, -1);
+ }
+
+ /**
+ * Calculate the colours for each of the points in the given track
+ * @param inTrackInfo track info object
+ */
+ public abstract void calculateColours(TrackInfo inTrackInfo);
+
+ /**
+ * Get the colour for the given point index
+ * @param inPointIndex index of point in track
+ * @return colour object
+ */
+ public Color getColour(int inPointIndex)
+ {
+ return _defaultColour;
+ }
+
+ /**
+ * @param inColor default colour to use
+ */
+ protected void setDefaultColour(Color inColour)
+ {
+ if (inColour != null) {
+ _defaultColour = inColour;
+ }
+ }
+
+ /**
+ * @return default colour
+ */
+ protected Color getDefaultColour() {
+ return _defaultColour;
+ }
+
+ /**
+ * @return start colour
+ */
+ protected Color getStartColour() {
+ return _startColour;
+ }
+
+ /**
+ * @return end colour
+ */
+ protected Color getEndColour() {
+ return _endColour;
+ }
+
+ /**
+ * @return maximum number of colours, or -1
+ */
+ protected int getMaxColours() {
+ return _maxColours;
+ }
+
+ /**
+ * Mix the given colours together using HSB values instead of interpolating RGB
+ * @param inFraction between 0.0 (start) and 1.0 (end)
+ * @return mixed colour
+ */
+ protected Color mixColour(float inFraction)
+ {
+ if (_startColour == null && _endColour == null) return getDefaultColour();
+ if (_startColour == null) return _endColour;
+ if (_endColour == null || inFraction < 0.0 || inFraction > 1.0) return _startColour;
+
+ // Convert both colours to hsb, and interpolate
+ float[] startHSB = Color.RGBtoHSB(_startColour.getRed(), _startColour.getGreen(), _startColour.getBlue(), null);
+ float[] endHSB = Color.RGBtoHSB(_endColour.getRed(), _endColour.getGreen(), _endColour.getBlue(), null);
+ // Note that if end hue is less than start hue, hue will go backwards rather than forwards with wrap around 0
+
+ return Color.getHSBColor(startHSB[0] + (endHSB[0]-startHSB[0]) * inFraction,
+ startHSB[1] + (endHSB[1]-startHSB[1]) * inFraction,
+ startHSB[2] + (endHSB[2]-startHSB[2]) * inFraction);
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.config.Config;
+import tim.prune.data.Track;
+import tim.prune.gui.profile.ProfileData;
+
+/**
+ * Colourer based on speed values
+ */
+public abstract class ProfileDataColourer extends ContinuousPointColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public ProfileDataColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ /**
+ * Calculate the colours according to the track and the profile data
+ */
+ public void calculateColours(Track inTrack, ProfileData inData)
+ {
+ final int numPoints = inTrack == null ? 0 : inTrack.getNumPoints();
+
+ // Calculate values for each point
+ inData.init(Config.getUnitSet());
+ // Figure out speed range
+ double minValue = inData.getMinValue();
+ double maxValue = inData.getMaxValue();
+ if (!inData.hasData() || (maxValue - minValue) < 0.1)
+ {
+ // not enough value range, set all to null
+ init(0);
+ }
+ else
+ {
+ // initialise the array to the right size
+ init(numPoints);
+ // loop over track points to calculate colours
+ for (int i=0; i<numPoints; i++)
+ {
+ if (inData.hasData(i))
+ {
+ double fraction = (inData.getData(i) - minValue) / (maxValue - minValue);
+ setColour(i, mixColour((float) fraction));
+ }
+ else setColour(i, null);
+ }
+ }
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.DataPoint;
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+
+/**
+ * Point colourer using the segment indices
+ */
+public class SegmentColourer extends DiscretePointColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour of scale
+ * @param inEndColour end colour of scale
+ * @param inWrapLength number of unique colours before wrap
+ */
+ public SegmentColourer(Color inStartColour, Color inEndColour, int inWrapLength)
+ {
+ super(inStartColour, inEndColour, inWrapLength);
+ }
+
+ /**
+ * Calculate the colours for each of the points in the given track
+ * @param inTrackInfo track info object
+ */
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ // initialise the array to the right size
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ final int numPoints = track == null ? 0 : track.getNumPoints();
+ init(numPoints);
+ // loop over track points
+ int c = -1; // first track point will increment this to 0
+ for (int i=0; i<numPoints; i++)
+ {
+ DataPoint p = track.getPoint(i);
+ if (p != null && !p.isWaypoint())
+ {
+ if (p.getSegmentStart()) {
+ c++;
+ }
+ setColour(i, c);
+ }
+ }
+ // generate the colours needed
+ generateDiscreteColours(c+1);
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.profile.SpeedData;
+
+/**
+ * Colourer based on speed values
+ */
+public class SpeedColourer extends ProfileDataColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public SpeedColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ // Calculate speed value for each point
+ SpeedData data = new SpeedData(track);
+ calculateColours(track, data);
+ }
+}
--- /dev/null
+package tim.prune.gui.colour;
+
+import java.awt.Color;
+
+import tim.prune.data.Track;
+import tim.prune.data.TrackInfo;
+import tim.prune.gui.profile.VerticalSpeedData;
+
+/**
+ * Colourer based on vertical speed values
+ */
+public class VertSpeedColourer extends ProfileDataColourer
+{
+ /**
+ * Constructor
+ * @param inStartColour start colour
+ * @param inEndColour end colour
+ */
+ public VertSpeedColourer(Color inStartColour, Color inEndColour)
+ {
+ super(inStartColour, inEndColour);
+ }
+
+ @Override
+ public void calculateColours(TrackInfo inTrackInfo)
+ {
+ Track track = inTrackInfo == null ? null : inTrackInfo.getTrack();
+ // Calculate speed value for each point
+ VerticalSpeedData data = new VerticalSpeedData(track);
+ calculateColours(track, data);
+ }
+}
package tim.prune.gui.map;
+import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import tim.prune.function.edit.FieldEdit;
import tim.prune.function.edit.FieldEditList;
import tim.prune.gui.IconManager;
+import tim.prune.gui.colour.PointColourer;
import tim.prune.tips.TipManager;
/**
private MapTileManager _tileManager = new MapTileManager(this);
/** Image to display */
private BufferedImage _mapImage = null;
+ /** Second image for drawing track (only needed for alpha blending) */
+ private BufferedImage _trackImage = null;
/** Slider for transparency */
private JSlider _transparencySlider = null;
/** Checkbox for scale bar */
else if (px > (getWidth()-PAN_DISTANCE)) {
panX = AUTOPAN_DISTANCE + px - getWidth();
}
- if (py < PAN_DISTANCE) {
+ if (py < (2*PAN_DISTANCE)) {
panY = py - AUTOPAN_DISTANCE;
}
if (py > (getHeight()-PAN_DISTANCE)) {
}
}
- // Paint the track points on top
- int pointsPainted = 1;
- try
+ // Work out track opacity according to slider
+ final float[] opacities = {1.0f, 0.75f, 0.5f, 0.3f, 0.15f, 0.0f};
+ float trackOpacity = 1.0f;
+ if (_transparencySlider.getValue() < 0) {
+ trackOpacity = opacities[-1 - _transparencySlider.getValue()];
+ }
+
+ if (trackOpacity > 0.0f)
{
- pointsPainted = paintPoints(g);
+ // Paint the track points on top
+ int pointsPainted = 1;
+ try
+ {
+ if (trackOpacity > 0.9f)
+ {
+ // Track is fully opaque, just draw it directly
+ pointsPainted = paintPoints(g);
+ _trackImage = null;
+ }
+ else
+ {
+ // Track is partly transparent, so use a separate BufferedImage
+ if (_trackImage == null || _trackImage.getWidth() != getWidth() || _trackImage.getHeight() != getHeight())
+ {
+ _trackImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+ }
+ // Clear to transparent
+ Graphics2D gTrack = _trackImage.createGraphics();
+ gTrack.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
+ gTrack.fillRect(0, 0, getWidth(), getHeight());
+ gTrack.setPaintMode();
+ // Draw the track onto this separate image
+ pointsPainted = paintPoints(gTrack);
+ ((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, trackOpacity));
+ g.drawImage(_trackImage, 0, 0, null);
+ }
+ }
+ catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
+ catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
+
+ // Zoom to fit if no points found
+ if (pointsPainted <= 0 && _checkBounds)
+ {
+ zoomToFit();
+ _recalculate = true;
+ repaint();
+ }
}
- catch (NullPointerException npe) {} // ignore, probably due to data being changed during drawing
- catch (ArrayIndexOutOfBoundsException obe) {} // also ignore
// free g
g.dispose();
- // Zoom to fit if no points found
- if (pointsPainted <= 0 && _checkBounds) {
- zoomToFit();
- _recalculate = true;
- repaint();
- }
_checkBounds = false;
// enable / disable transparency slider
_transparencySlider.setEnabled(showMap);
{
// Set up colours
final ColourScheme cs = Config.getColourScheme();
- final int[] opacities = {255, 190, 130, 80, 40, 0};
- int opacity = 255;
- if (_transparencySlider.getValue() < 0)
- opacity = opacities[-1 - _transparencySlider.getValue()];
- final Color pointColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_POINT), opacity);
- final Color rangeColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_SELECTION), opacity);
- final Color currentColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_PRIMARY), opacity);
- final Color secondColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_SECONDARY), opacity);
- final Color textColour = makeTransparentColour(cs.getColour(ColourScheme.IDX_TEXT), opacity);
+ final Color pointColour = cs.getColour(ColourScheme.IDX_POINT);
+ final Color rangeColour = cs.getColour(ColourScheme.IDX_SELECTION);
+ final Color currentColour = cs.getColour(ColourScheme.IDX_PRIMARY);
+ final Color secondColour = cs.getColour(ColourScheme.IDX_SECONDARY);
+ final Color textColour = cs.getColour(ColourScheme.IDX_TEXT);
+ final PointColourer pointColourer = _app.getPointColourer();
final int winWidth = getWidth();
final int winHeight = getHeight();
currPointVisible = px >= 0 && px < winWidth && py >= 0 && py < winHeight;
isWaypoint = _track.getPoint(i).isWaypoint();
anyWaypoints = anyWaypoints || isWaypoint;
- if (currPointVisible)
+ if (!isWaypoint)
{
- if (!isWaypoint)
+ if (currPointVisible || prevPointVisible)
{
- // Draw rectangle for track point
+ // For track points, work out which colour to use
if (_track.getPoint(i).getDeleteFlag()) {
inG.setColor(currentColour);
}
- else {
+ else if (pointColourer != null)
+ { // use the point colourer if there is one
+ Color trackColour = pointColourer.getColour(i);
+ inG.setColor(trackColour);
+ }
+ else
+ {
inG.setColor(pointColour);
}
- inG.drawRect(px-2, py-2, 3, 3);
- pointsPainted++;
+
+ // Draw rectangle for track point if it's visible
+ if (currPointVisible)
+ {
+ inG.drawRect(px-2, py-2, 3, 3);
+ pointsPainted++;
+ }
}
- }
- if (!isWaypoint)
- {
+
// Connect track points if either of them are visible
- if (connectPoints && (currPointVisible || prevPointVisible)
- && !(prevX == -1 && prevY == -1)
+ if (connectPoints && !(prevX == -1 && prevY == -1)
&& !_track.getPoint(i).getSegmentStart())
{
inG.drawLine(prevX, prevY, px, py);
}
}
- /**
- * Make a semi-transparent colour for drawing with
- * @param inColour base colour (fully opaque)
- * @param inOpacity opacity where 0=invisible and 255=full
- * @return new colour object
- */
- private static Color makeTransparentColour(Color inColour, int inOpacity)
- {
- if (inOpacity > 240) return inColour;
- return new Color(inColour.getRed(), inColour.getGreen(), inColour.getBlue(), inOpacity);
- }
-
/**
* Inform that tiles have been updated and the map can be repainted
* @param inIsOk true if data loaded ok, false for error
else if (_drawMode == MODE_DRAW_POINTS_CONT)
{
DataPoint point = createPointFromClick(inE.getX(), inE.getY());
- _app.createPoint(point);
- point.setSegmentStart(false);
+ _app.createPoint(point, false); // not a new segment
}
}
else if (inE.getClickCount() == 2)
// check prefix
try {
new URL(urlstr.replace('[', 'w').replace(']', 'w'));
+ // There's a warning that this URL object isn't used, but it's enough to know the parse
+ // was successful without an exception being thrown.
}
catch (MalformedURLException e)
{
--- /dev/null
+package tim.prune.gui.profile;
+
+import tim.prune.I18nManager;
+import tim.prune.data.GradientCalculator;
+import tim.prune.data.SpeedValue;
+import tim.prune.data.Track;
+import tim.prune.data.UnitSet;
+
+/**
+ * Class to provide a source of gradient data for the profile chart
+ * or for the point colourer
+ */
+public class GradientData extends ProfileData
+{
+ /**
+ * Constructor
+ * @param inTrack track object
+ */
+ public GradientData(Track inTrack) {
+ super(inTrack);
+ }
+
+ /**
+ * Get the data and populate the instance arrays
+ */
+ public void init(UnitSet inUnitSet)
+ {
+ setUnitSet(inUnitSet);
+ initArrays();
+ _hasData = false;
+ _minValue = _maxValue = 0.0;
+ SpeedValue speed = new SpeedValue();
+ if (_track != null)
+ {
+ for (int i=0; i<_track.getNumPoints(); i++)
+ {
+ // Get the gradient either from the speed values or from the distances and altitudes
+ GradientCalculator.calculateGradient(_track, i, speed);
+ if (speed.isValid())
+ {
+ double speedValue = speed.getValue();
+ _pointValues[i] = speedValue;
+ if (speedValue < _minValue || !_hasData) {_minValue = speedValue;}
+ if (speedValue > _maxValue || !_hasData) {_maxValue = speedValue;}
+ _hasData = true;
+ }
+ _pointHasData[i] = speed.isValid();
+ }
+ }
+ }
+
+ /**
+ * @return text description
+ */
+ public String getLabel()
+ {
+ return I18nManager.getText("fieldname.gradient");
+ }
+
+ /**
+ * @return key for message when no altitudes present
+ */
+ public String getNoDataKey() {
+ return "display.noaltitudes";
+ }
+}
{
double speedValue = speed.getValue();
_pointValues[i] = speedValue;
- if (speedValue < _minValue || _minValue == 0.0) {_minValue = speedValue;}
- if (speedValue > _maxValue) {_maxValue = speedValue;}
+ if (speedValue < _minValue || !_hasData) {_minValue = speedValue;}
+ if (speedValue > _maxValue || !_hasData) {_maxValue = speedValue;}
_hasData = true;
}
_pointHasData[i] = speed.isValid();
// Store the value and maintain max and min values
double speedValue = speed.getValue();
_pointValues[i] = speedValue;
- if (speedValue < _minValue || _minValue == 0.0) {_minValue = speedValue;}
- if (speedValue > _maxValue) {_maxValue = speedValue;}
+ if (speedValue < _minValue || !_hasData) {_minValue = speedValue;}
+ if (speedValue > _maxValue || !_hasData) {_maxValue = speedValue;}
_hasData = true;
}
_pointHasData[i] = speed.isValid();
menu.track.undo=Herroep
menu.track.clearundo=Herroep Lys Skoonmaak
menu.track.deletemarked=Gemerkde Punt Uitvee
-menu.track.rearrange=Herrangskik bakens
-menu.track.rearrange.start=Bakens na begin van l\u00eaer
-menu.track.rearrange.end=Bakens na einde van l\u00eaer
-menu.track.rearrange.nearest=Beweeg elk na naaste spoor punt
+function.rearrangewaypoints=Herrangskik bakens
menu.range=Reeks
menu.range.all=Selekteer Alles
menu.range.none=Selekteer Niks
dialog.correlate.timestamp.beginning=Begin
dialog.correlate.timestamp.middle=Middel
dialog.correlate.timestamp.end=Einde
-dialog.rearrangephotos.tostart=Beweeg na begin
-dialog.rearrangephotos.toend=Beweeg na einde
-dialog.rearrangephotos.nosort=Nie sorteer
-dialog.rearrangephotos.sortbyfilename=Sorteer volgens leernaam
-dialog.rearrangephotos.sortbytime=Sorteer volgens tyd
+dialog.rearrange.tostart=Beweeg na begin
+dialog.rearrange.toend=Beweeg na einde
+dialog.rearrange.tonearest=Beweeg elk na naaste spoor punt
+dialog.rearrange.nosort=Nie sorteer
+dialog.rearrange.sortbyfilename=Sorteer volgens leernaam
+dialog.rearrange.sortbyname=Sorteer volgens naam
+dialog.rearrange.sortbytime=Sorteer volgens tyd
dialog.compress.closepoints.title=Naby punt verwydering
dialog.compress.closepoints.paramdesc=Span faktor
dialog.compress.wackypoints.title=Gekkige punt verwydering
menu.track.clearundo=Vypr\u00e1zdnit pam\u011b\u0165 undo
menu.track.markrectangle=Ozna\u010dit body v obd\u00e9ln\u00edku
menu.track.deletemarked=Smazat ozna\u010den\u00e9 body
-menu.track.rearrange=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
-menu.track.rearrange.start=V\u0161e na po\u010d\u00e1tek
-menu.track.rearrange.end=V\u0161e na konec
-menu.track.rearrange.nearest=Zarovnat body na stopu
+function.rearrangewaypoints=P\u0159euspo\u0159\u00e1dat z\u00e1jmov\u00e9 body
menu.range=Rozmez\u00ed
menu.range.all=Vybrat v\u0161e
menu.range.none=Zru\u0161it v\u00fdb\u011br
dialog.correlate.select.audioname=N\u00e1zev audionahr\u00e1vky
dialog.correlate.select.audiolater=Audio pozd\u011bj\u0161\u00ed
dialog.rearrangephotos.desc=Vyberte um\u00edst\u011bn\u00ed a uspo\u0159\u00e1d\u00e1n\u00ed bod\u016f fotografi\u00ed
-dialog.rearrangephotos.tostart=P\u0159en\u00e9st na za\u010d\u00e1tek
-dialog.rearrangephotos.toend=P\u0159en\u00e9st na konec
-dialog.rearrangephotos.nosort=Neuspo\u0159\u00e1d\u00e1vat
-dialog.rearrangephotos.sortbyfilename=Uspo\u0159\u00e1dat dle n\u00e1zvu souboru
-dialog.rearrangephotos.sortbytime=Uspo\u0159\u00e1dat dle \u010dasu
+dialog.rearrange.tostart=P\u0159en\u00e9st na za\u010d\u00e1tek
+dialog.rearrange.toend=P\u0159en\u00e9st na konec
+dialog.rearrange.tonearest=Zarovnat body na stopu
+dialog.rearrange.nosort=Neuspo\u0159\u00e1d\u00e1vat
+dialog.rearrange.sortbyfilename=Uspo\u0159\u00e1dat dle n\u00e1zvu souboru
+dialog.rearrange.sortbyname=Uspo\u0159\u00e1dat dle n\u00e1zvu
+dialog.rearrange.sortbytime=Uspo\u0159\u00e1dat dle \u010dasu
dialog.compress.closepoints.title=Odstran\u011bn\u00ed bl\u00edzk\u00fdch bod\u016f
dialog.compress.closepoints.paramdesc=Koeficient bl\u00edzkosti
dialog.compress.wackypoints.title=Odstran\u011bn\u00ed ust\u0159elen\u00fdch bod\u016f
error.cache.notthere=Nepoda\u0159ilo se nal\u00e9zt adres\u00e1\u0159 s cache map.
error.cache.empty=Adres\u00e1\u0159 s cache map je pr\u00e1zdn\u00fd.
error.cache.cannotdelete=Nelze smazat soubory map.
-error.interpolate.invalidparameter=Po\u010det bod\u016f mus\u00ed b\u00fdt mezi 1 a 1000
error.learnestimationparams.failed=Na z\u00e1klad\u011b t\u00e9to stopy nelze vypo\u010d\u00edtat parametr.\nZkuste jin\u00e9 stopy.
error.tracksplit.nosplit=Stopu nen\u00ed mo\u017en\u00e9 rozd\u011blit
error.downloadsrtm.nocache=Nepoda\u0159ilo se ulo\u017eit soubory.\nPros\u00edm ov\u011b\u0159te diskovou cache.
menu.track.undo=Fortryd
menu.track.clearundo=Nulstil fortrydelsesliste
menu.track.deletemarked=Slet markerede punkter
-menu.track.rearrange=Omorganis\u00e9r waypoints
-menu.track.rearrange.nearest=Hvert waypoint til n\u00e6rmeste nabo
+function.rearrangewaypoints=Omorganis\u00e9r waypoints
+dialog.rearrange.tonearest=Hvert waypoint til n\u00e6rmeste nabo
menu.range=Omr\u00e5de
menu.range.all=V\u00e6lg alle
menu.range.none=Ingen valgt
menu.track.clearundo=Liste der letzten \u00c4nderungen l\u00f6schen
menu.track.markrectangle=Punkte im Viereck markieren
menu.track.deletemarked=Markierte Punkte l\u00f6schen
-menu.track.rearrange=Wegpunkte neu anordnen
-menu.track.rearrange.start=Alle Wegpunkte zum Anfang
-menu.track.rearrange.end=Alle Wegpunkte ans Ende
-menu.track.rearrange.nearest=Jeden Wegpunkt zum n\u00e4chsten Trackpunkt verschieben
+function.rearrangewaypoints=Wegpunkte neu anordnen
menu.range=Bereich
menu.range.all=Alles markieren
menu.range.none=Nichts markieren
function.exportpov=POV exportieren
function.exportsvg=SVG exportieren
function.exportimage=Bild exportieren
-function.editwaypointname=Name des Punkts bearbeiten
+function.editwaypointname=Namen des Punkts bearbeiten
function.compress=Track komprimieren
function.deleterange=Bereich l\u00f6schen
function.croptrack=Track zuschneiden
function.interpolate=Punkte interpolieren
+function.deletebydate=Punkte nach Datum l\u00f6schen
function.addtimeoffset=Zeitverschiebung aufrechnen
function.addaltitudeoffset=H\u00f6henverschiebung aufrechnen
function.convertnamestotimes=Namen der Wegpunkte in Zeitstempel umwandeln
function.learnestimationparams=Zeitparameter erlernen
function.setmapbg=Karte Hintergrund setzen
function.setpaths=Programmpfade setzen
+function.selectsegment=Aktuellen Abschnitt markieren
function.splitsegments=In Trackabschnitte schneiden
function.sewsegments=Trackabschnitte zusammenf\u00fcgen
function.getgpsies=Tracks bei GPSies.com herunterladen
function.diskcache=Karten auf Festplatte speichern
function.managetilecache=Kartenkacheln verwalten
function.getweatherforecast=Wettervorhersage herunterladen
+function.setaltitudetolerance=Altitudtoleranz einstellen
# Dialogs
dialog.exit.confirm.title=GpsPrune beenden
dialog.gpsies.activity.skating=Inline-Skating
dialog.wikipedia.column.name=Artikelname
dialog.wikipedia.column.distance=Entfernung
+dialog.wikipedia.nonefound=Keine Punkte gefunden
dialog.correlate.notimestamps=Die Punkte enthalten keine Zeitangaben, deshalb k\u00f6nnen die Fotos nicht zugeordnet werden.
dialog.correlate.nouncorrelatedphotos=Alle Fotos sind schon zugeordnet.\nWollen Sie trotzdem fortfahren?
dialog.correlate.nouncorrelatedaudios=Alle Audiodateien sind schon zugeordnet.\nWollen Sie trotzdem fortfahren?
dialog.correlate.audioselect.intro=W\u00e4hlen Sie eines dieser Audiodateien aus, um die Zeitdifferenz zu berechnen
dialog.correlate.select.audioname=Audio Name
dialog.correlate.select.audiolater=Audio sp\u00e4ter
+dialog.rearrangewaypoints.desc=Setzen Sie das Ziel und die Reihenfolge der Wegpunkte
dialog.rearrangephotos.desc=Setzen Sie das Ziel und die Reihenfolge der Fotopunkte
-dialog.rearrangephotos.tostart=Am Anfang
-dialog.rearrangephotos.toend=Am Ende
-dialog.rearrangephotos.nosort=Nicht sortieren
-dialog.rearrangephotos.sortbyfilename=per Dateiname sortieren
-dialog.rearrangephotos.sortbytime=per Zeitstempel sortieren
+dialog.rearrange.tostart=Zum Anfang
+dialog.rearrange.toend=Zum Ende
+dialog.rearrange.tonearest=Zum n\u00e4chsten Trackpunkt
+dialog.rearrange.nosort=Nicht sortieren
+dialog.rearrange.sortbyfilename=nach Dateinamen sortieren
+dialog.rearrange.sortbyname=nach Namen sortieren
+dialog.rearrange.sortbytime=nach Zeitstempel sortieren
dialog.compress.closepoints.title=Nahegelegene Punkte entfernen
dialog.compress.closepoints.paramdesc=Span-Faktor
dialog.compress.wackypoints.title=Ungew\u00f6hnliche Punkte entfernen
dialog.checkversion.releasedate2=ver\u00f6ffentlicht worden.
dialog.checkversion.download=Um die neue Version herunterzuladen, gehen Sie zu http://gpsprune.activityworkshop.net/download.html.
dialog.keys.intro=Anstelle der Maus k\u00f6nnen Sie folgende Tastenkombinationen nutzen
-dialog.keys.keylist=<table><tr><td>Pfeil Tasten</td><td>Karte verschieben</td></tr><tr><td>Strg + Links-, Rechts-Pfeil</td><td>Vorherigen oder n\u00e4chsten Punkt markieren</td></tr><tr><td>Strg + Auf-, Abw\u00e4rts-Pfeil</td><td>Ein- oder Auszoomen</td></tr><tr><td>Strg + Bild auf, ab</td><td>Vorheriges oder n\u00e4chstes Segment markieren</td></tr><tr><td>Strg + Pos1, Ende</td><td>Ersten oder letzten Punkt markieren</td></tr><tr><td>Entf</td><td>Aktuellen Punkt entfernen</td></tr></table>
+dialog.keys.keylist=<table><tr><td>Pfeil Tasten</td><td>Karte verschieben</td></tr><tr><td>Strg + Links-, Rechts-Pfeil</td><td>Vorherigen oder n\u00e4chsten Punkt markieren</td></tr><tr><td>Strg + Auf-, Abw\u00e4rts-Pfeil</td><td>Ein- oder Auszoomen</td></tr><tr><td>Strg + Bild auf, ab</td><td>Zum vorherigen oder n\u00e4chsten Abschnitt</td></tr><tr><td>Strg + Pos1, Ende</td><td>Ersten oder letzten Punkt markieren</td></tr><tr><td>Entf</td><td>Aktuellen Punkt entfernen</td></tr></table>
dialog.keys.normalmodifier=Strg
dialog.keys.macmodifier=Kommando
dialog.saveconfig.desc=Die folgende Einstellungen k\u00f6nnen gespeichert werden:
dialog.colourchooser.red=Rot
dialog.colourchooser.green=Gr\u00fcn
dialog.colourchooser.blue=Blau
+dialog.colourer.intro=F\u00e4rb die Trackpunkte unterschiedlich ein
+dialog.colourer.type=Unterschiedliche Farben
+dialog.colourer.type.none=Keine
+dialog.colourer.type.byfile=Nach Datei
+dialog.colourer.type.bysegment=Nach Abschnitt
+dialog.colourer.type.byaltitude=Nach H\u00f6henangaben
+dialog.colourer.type.byspeed=Nach Geschwindigkeit
+dialog.colourer.type.byvertspeed=Nach vertikalen Geschwindigkeit
+dialog.colourer.type.bygradient=Nach Gef\u00e4lle
+dialog.colourer.type.bydate=Nach Datum
+dialog.colourer.start=Anfangsfarbe
+dialog.colourer.end=Zielfarbe
+dialog.colourer.maxcolours=Maximal Anzahl Farben
dialog.setlanguage.firstintro=Sie k\u00f6nnen entweder eine von den mitgelieferten Sprachen<p>oder eine Text-Datei ausw\u00e4hlen.
dialog.setlanguage.secondintro=Sie m\u00fcssen Ihre Einstellungen speichern und dann<p>GpsPrune neu starten, um die Sprache zu \u00e4ndern.
dialog.setlanguage.language=Sprache
dialog.weather.temp=Temp
dialog.weather.humidity=R.L.
dialog.weather.creditnotice=Diese Daten wurden von openweathermap.org zur Verf\u00fcgung gestellt. Die Webseite hat mehr Information.
+dialog.deletebydate.onlyonedate=Die Punkte fallen alle am gleichen Tag.
+dialog.deletebydate.intro=F\u00fcr jeden gefundenen Datum, k\u00f6nnen Sie die Punkte behalten oder l\u00f6schen.
+dialog.deletebydate.nodate=Ohne Zeitangabe
+dialog.deletebydate.column.keep=Behalten
+dialog.deletebydate.column.delete=L\u00f6schen
+dialog.setaltitudetolerance.text.metres=Mindestabweichung (Meter) f\u00fcr H\u00f6henunterschiede
+dialog.setaltitudetolerance.text.feet=Mindestabweichung (Feet) f\u00fcr H\u00f6henunterschiede
# 3d window
dialog.3d.title=GpsPrune-3D-Ansicht
fieldname.altitude=H\u00f6he
fieldname.timestamp=Zeitstempel
fieldname.time=Zeit
+fieldname.date=Datum
fieldname.waypointname=Name
fieldname.waypointtype=Typ
-fieldname.newsegment=Segment
+fieldname.newsegment=Abschnitt
fieldname.custom=Custom
fieldname.prefix=Feld
fieldname.distance=L\u00e4nge
error.cache.notthere=Der Ordner wurde nicht gefunden
error.cache.empty=Der Ordner ist leer
error.cache.cannotdelete=Es konnte keine Kacheln gel\u00f6scht werden
-error.interpolate.invalidparameter=Die Anzahl der Punkte muss zwischen 1 und 1000 liegen
error.learnestimationparams.failed=Mit diesem Track k\u00f6nnen die Parameter nicht berechnet werden.\nVersuchen Sie mit mehreren Tracks.
error.tracksplit.nosplit=Der Track konnte nicht aufgesplittet werden.
error.downloadsrtm.nocache=Die Dateien konnten nicht gespeichert werden.\nBitte pr\u00fcfen Sie den Kartenordner nach.
menu.track.clearundo=Undo-Liste l\u00f6sche
menu.track.markrectangle=P\u00fcnkte inem Viereck markiere
menu.track.deletemarked=Markierte P\u00fcnkte l\u00f6sche
-menu.track.rearrange=Waypoints reorganisiere
-menu.track.rearrange.start=Alli zum Aafang
-menu.track.rearrange.end=Alli zum \u00c4nde
-menu.track.rearrange.nearest=Jede zum n\u00f6chsti Trackpunkt
+function.rearrangewaypoints=Waypoints reorganisiere
menu.range=Beriich
menu.range.all=Alles selektiere
menu.range.none=N\u00fc\u00fct selektiere
function.exportpov=POV exportier\u00e4
function.exportsvg=SVG exportier\u00e4
function.exportimage=Bild exportier\u00e4
-function.editwaypointname=Waypoint Name editiere
+function.editwaypointname=Waypoint Namen editiere
function.compress=Track komprimier\u00e4
function.deleterange=Beriich l\u00f6sche
function.croptrack=Track zuschniide
+function.deletebydate=P\u00fcnkte na Datum l\u00f6sche
function.addtimeoffset=Ziitverschiebig zutue
function.addaltitudeoffset=H\u00f6chiverschiebig zutue
function.findwaypoint=Waypoint suech\u00e4
function.estimatetime=Ziit absch\u00e4tze
function.learnestimationparams=Ziitparameter erlerne
function.setmapbg=Karte Hintegrund setz\u00e4
+function.selectsegment=Aktuelli Segm\u00e4nt selektiere
function.splitsegments=In Tracksegm\u00e4nte schniide
function.sewsegments=Tracksegm\u00e4nte z\u00e4mef\u00fcge
function.getgpsies=Gpsies Tracks hol\u00e4
function.diskcache=Karten uufem Disk speichere
function.managetilecache=Kartebildli verwolte
function.getweatherforecast=W\u00e4tterprognose abalade
+function.setaltitudetolerance=H\u00f6chitoleranz iistelle
# Dialogs
dialog.exit.confirm.title=GpsPrune be\u00e4nde
dialog.openoptions.deliminfo.fields=F\u00e4ldere
dialog.openoptions.deliminfo.norecords=Kei Rekords
dialog.openoptions.altitudeunits=H\u00f6chi Masseiheite
-dialog.openoptions.speedunits=Masseiheite f\u00fcr Geschwindigkeite
-dialog.openoptions.vertspeedunits=Masseiheite f\u00fcr vertikale Geschwindigkeite
+dialog.openoptions.speedunits=Masseiheite f\u00fcr Gschwindikeite
+dialog.openoptions.vertspeedunits=Masseiheite f\u00fcr vertikale Gschwindikeite
dialog.openoptions.vspeed.positiveup=Positive bed\u00fc\u00fctet uufe
dialog.openoptions.vspeed.positivedown=Positive bed\u00fc\u00fctet abe
dialog.open.contentsdoubled=Dieses File h\u00e4t zwei Kopien von j\u00e4dem Punkt,\neimol als Waypoint und eimol als Trackpunkt.
dialog.gpsies.activity.skating=Inline-Skate
dialog.wikipedia.column.name=Artikelname
dialog.wikipedia.column.distance=Entf\u00e4rnig
+dialog.wikipedia.nonefound=Kei Wiki-Iitr\u00e4ge gfunde
dialog.correlate.notimestamps=Es h\u00e4t kei Ziitst\u00e4mpel inem Track inn\u00e4, so s'isch n\u00f6d m\u00f6glech die F\u00f6telis zu korrelier\u00e4.
dialog.correlate.nouncorrelatedphotos=Alle F\u00f6telis sin scho korreliert.\nWend Sie trotzdem fortsetz\u00e4?
dialog.correlate.nouncorrelatedaudios=Alle Audios sin scho korreliert.\nWend Sie trotzdem fortsetz\u00e4?
dialog.correlate.audioselect.intro=W\u00e4hlet Sie eini vo deren Audios uus, um die Ziitdiffer\u00e4nz zu ber\u00e4chn\u00e4
dialog.correlate.select.audioname=Audio Name
dialog.correlate.select.audiolater=Audio sp\u00f6ter
-dialog.rearrangephotos.desc=Bitte Ziel und Reihefolge von d P\u00fcnkte setze
-dialog.rearrangephotos.tostart=zum Aafang
-dialog.rearrangephotos.toend=zum \u00c4nde
-dialog.rearrangephotos.nosort=N\u00f6d sortiere
-dialog.rearrangephotos.sortbyfilename=per Filename sortiere
-dialog.rearrangephotos.sortbytime=per Ziit sortiere
+dialog.rearrangewaypoints.desc=Bitte Ziel und Reihefolge von d Waypoints setze
+dialog.rearrangephotos.desc=Bitte Ziel und Reihefolge von d F\u00f6telip\u00fcnkte setze
+dialog.rearrange.tostart=zum Aafang
+dialog.rearrange.toend=zum \u00c4nde
+dialog.rearrange.tonearest=zum n\u00f6chsti Trackpunkt
+dialog.rearrange.nosort=N\u00f6d sortiere
+dialog.rearrange.sortbyfilename=nachem Filename sortiere
+dialog.rearrange.sortbyname=nachem Name sortiere
+dialog.rearrange.sortbytime=nachdr Ziit sortiere
dialog.compress.duplicates.title=Duplikate entf\u00e4rn\u00e4
dialog.compress.closepoints.title=N\u00f6chigl\u00e4geni P\u00fcnkte entf\u00e4rn\u00e4
dialog.compress.closepoints.paramdesc=Span Faktor
dialog.colourchooser.red=Rot
dialog.colourchooser.green=Gr\u00fcen
dialog.colourchooser.blue=Blau
+dialog.colourer.intro=F\u00e4rb die Trackp\u00fcnkte unterschiedlich ii
+dialog.colourer.type=Unterschiedliche Farbe
+dialog.colourer.type.none=Kei
+dialog.colourer.type.byfile=Nach Datei
+dialog.colourer.type.bysegment=Nach Segm\u00e4nt
+dialog.colourer.type.byaltitude=Nach H\u00f6chi
+dialog.colourer.type.byspeed=Nach Gschwindikeit
+dialog.colourer.type.byvertspeed=Nach uf/ab Gschwindikeit
+dialog.colourer.type.bygradient=Nach Gef\u00e4lle
+dialog.colourer.type.bydate=Nach Datum
+dialog.colourer.start=Aafangsfarb
+dialog.colourer.end=Zielfarb
+dialog.colourer.maxcolours=Maximal Anzahl Farbe
dialog.setlanguage.firstintro=Sie k\u00f6nnet entweder eini vo den iigebouti Sproche<p>oder ne Text-Datei uusw\u00e4hle.
dialog.setlanguage.secondintro=Sie m\u00fcnt Ihri Iistellige speichere und dann<p>GpsPrune wieder neustarte um die Sproch z'\u00e4ndere.
dialog.setlanguage.language=Sproch
dialog.weather.temp=Temp
dialog.weather.humidity=R.L.
dialog.weather.creditnotice=Diese Date sin vo openweathermap.org zur Verf\u00fcegig gestellt worde. Uf d Websiite h\u00e4ts no meh Infos.
+dialog.deletebydate.onlyonedate=Die P\u00fcnkte sin alli vom gliichen Tag.
+dialog.deletebydate.intro=F\u00fcr jeden Tag, k\u00f6nnet Sie d P\u00fcnkte behalte oder l\u00f6sche.
+dialog.deletebydate.nodate=Ohni Ziitaagab
+dialog.deletebydate.column.keep=Behalte
+dialog.deletebydate.column.delete=L\u00f6sche
+dialog.setaltitudetolerance.text.metres=Mindeschtabweichig (Meter) f\u00fcr H\u00f6chiunterschied
+dialog.setaltitudetolerance.text.feet=Mindeschtabweichig (Feet) f\u00fcr H\u00f6chinunterschied
# 3d window
dialog.3d.title=GpsPrune Dr\u00fc\u00fc-d Aasicht
tip.learntimeparams=Wenn Sie Track -> Ziitparameter erlerne mit Ihren Tracks benutze\ndann werdet d ber\u00e4chneti Werte gnauer.
tip.downloadsrtm=Sie k\u00f6nnet d Funktion beschleunige indem Sie\nOnline -> SRTM Files abalade uufrufe\num d Date lokal z'speichere.
tip.usesrtmfor3d=Dere Track h\u00e4t kei H\u00f6chiinformation.\nSie k\u00f6nnet d SRTM Funktione verw\u00e4nde, um\nH\u00f6chiwerte abz'sch\u00e4tze.
-tip.manuallycorrelateone=Mit mindeschtens einem verbundenen Elem\u00e4nt kann die Ziitdiffer\u00e4nz automatisch ber\u00e4chnet werd\u00e4.
+tip.manuallycorrelateone=Mit mindeschtens einem verbundenen Elem\u00e4nt chann die Ziitdiffer\u00e4nz automatisch ber\u00e4chnet werd\u00e4.
# Buttons
button.ok=OK
fieldname.altitude=H\u00f6chi
fieldname.timestamp=Ziitst\u00e4mpel
fieldname.time=Ziit
+fieldname.date=Tag
fieldname.waypointname=Name
fieldname.waypointtype=Typ
fieldname.newsegment=Segm\u00e4nt
error.cache.notthere=D Ordner isch n\u00f6d gfunde worde
error.cache.empty=D Ordner h\u00e4t n\u00fc\u00fct drinne
error.cache.cannotdelete=Es sin kei Kachle gl\u00f6scht worde
-error.interpolate.invalidparameter=D'Aazahl P\u00fcnkt muess zw\u00fcschet 1 und 1000 sii
error.learnestimationparams.failed=Mit dere Track k\u00f6nnet die Parameter n\u00f6d br\u00e4chnet werde.\nVersuechet Sie mit mehreri Tracks.
error.tracksplit.nosplit=Es isch n\u00f6d m\u00f6glech gsi, den Track uufz'schniide.
error.downloadsrtm.nocache=Die Files k\u00f6nnet n\u00f6d gspeicheret werde.\nBitte pr\u00fcefet Sie den Kartenordner na.
menu.track.clearundo=Clear undo list
menu.track.markrectangle=Mark points in rectangle
menu.track.deletemarked=Delete marked points
-menu.track.rearrange=Rearrange waypoints
-menu.track.rearrange.start=All to start of file
-menu.track.rearrange.end=All to end of file
-menu.track.rearrange.nearest=Each to nearest track point
menu.range=Range
menu.range.all=Select all
menu.range.none=Select none
function.deleterange=Delete range
function.croptrack=Crop track
function.interpolate=Interpolate points
+function.deletebydate=Delete points by date
function.addtimeoffset=Add time offset
function.addaltitudeoffset=Add altitude offset
function.findwaypoint=Find waypoint
+function.rearrangewaypoints=Rearrange waypoints
function.convertnamestotimes=Convert waypoint names to times
function.deletefieldvalues=Delete field values
function.pastecoordinates=Enter new coordinates
function.fullrangedetails=Full range details
function.estimatetime=Estimate time
function.learnestimationparams=Learn time estimation parameters
+function.selectsegment=Select current segment
function.splitsegments=Split track into segments
function.sewsegments=Sew track segments together
function.getgpsies=Get Gpsies tracks
function.diskcache=Save maps to disk
function.managetilecache=Manage tile cache
function.getweatherforecast=Get weather forecast
+function.setaltitudetolerance=Set altitude tolerance
# Dialogs
dialog.exit.confirm.title=Exit GpsPrune
dialog.save.table.hasdata=Has data
dialog.save.table.save=Save
dialog.save.headerrow=Output header row
-dialog.save.coordinateunits=Coordinate units
+dialog.save.coordinateunits=Coordinate format
dialog.save.altitudeunits=Altitude units
dialog.save.timestampformat=Timestamp format
dialog.save.overwrite.title=File already exists
dialog.gpsies.activity.skating=Skating
dialog.wikipedia.column.name=Article name
dialog.wikipedia.column.distance=Distance
+dialog.wikipedia.nonefound=No wikipedia entries found
dialog.correlate.notimestamps=There are no timestamps in the data points, so there is nothing to correlate with the photos.
dialog.correlate.nouncorrelatedphotos=There are no uncorrelated photos.\nAre you sure you want to continue?
dialog.correlate.nouncorrelatedaudios=There are no uncorrelated audios.\nAre you sure you want to continue?
dialog.correlate.audioselect.intro=Select one of these correlated audios to use as the time offset
dialog.correlate.select.audioname=Audio name
dialog.correlate.select.audiolater=Audio later
+dialog.rearrangewaypoints.desc=Select the destination and sort order of the waypoints
dialog.rearrangephotos.desc=Select the destination and sort order of the photo points
-dialog.rearrangephotos.tostart=Move to start
-dialog.rearrangephotos.toend=Move to end
-dialog.rearrangephotos.nosort=Don't sort
-dialog.rearrangephotos.sortbyfilename=Sort by filename
-dialog.rearrangephotos.sortbytime=Sort by time
+dialog.rearrange.tostart=Move to start
+dialog.rearrange.toend=Move to end
+dialog.rearrange.tonearest=Each to nearest track point
+dialog.rearrange.nosort=Don't sort
+dialog.rearrange.sortbyfilename=Sort by filename
+dialog.rearrange.sortbyname=Sort by name
+dialog.rearrange.sortbytime=Sort by time
dialog.compress.duplicates.title=Duplicate removal
dialog.compress.closepoints.title=Nearby point removal
dialog.compress.closepoints.paramdesc=Span factor
dialog.colourchooser.red=Red
dialog.colourchooser.green=Green
dialog.colourchooser.blue=Blue
+dialog.colourer.intro=A point colourer can give track points different colours
+dialog.colourer.type=Colourer type
+dialog.colourer.type.none=None
+dialog.colourer.type.byfile=By file
+dialog.colourer.type.bysegment=By segment
+dialog.colourer.type.byaltitude=By altitude
+dialog.colourer.type.byspeed=By speed
+dialog.colourer.type.byvertspeed=By vertical speed
+dialog.colourer.type.bygradient=By gradient
+dialog.colourer.type.bydate=By date
+dialog.colourer.start=Start colour
+dialog.colourer.end=End colour
+dialog.colourer.maxcolours=Maximum number of colours
dialog.setlanguage.firstintro=You can either select one of the included languages,<p>or select a text file to use instead.
dialog.setlanguage.secondintro=You need to save your settings and then<p>restart GpsPrune to change the language.
dialog.setlanguage.language=Language
dialog.weather.temp=Temp
dialog.weather.humidity=Humidity
dialog.weather.creditnotice=This data is made available by openweathermap.org. Their website has more details.
+dialog.deletebydate.onlyonedate=The points were all recorded on the same date.
+dialog.deletebydate.intro=For each date in the track, you can choose to delete or keep the points
+dialog.deletebydate.nodate=No timestamp
+dialog.deletebydate.column.keep=Keep
+dialog.deletebydate.column.delete=Delete
+dialog.setaltitudetolerance.text.metres=Limit (in metres) below which small climbs and descents will be ignored
+dialog.setaltitudetolerance.text.feet=Limit (in feet) below which small climbs and descents will be ignored
# 3d window
dialog.3d.title=GpsPrune Three-d view
fieldname.altitude=Altitude
fieldname.timestamp=Time
fieldname.time=Time
+fieldname.date=Date
fieldname.waypointname=Name
fieldname.waypointtype=Type
fieldname.newsegment=Segment
error.cache.notthere=The tile cache directory was not found
error.cache.empty=The tile cache directory is empty
error.cache.cannotdelete=No tiles could be deleted
-error.interpolate.invalidparameter=The number of points must be between 1 and 1000
error.learnestimationparams.failed=Cannot learn the parameters from this track.\nTry loading more tracks.
error.tracksplit.nosplit=The track could not be split
error.downloadsrtm.nocache=The files could not be saved.\nPlease check the disk cache.
# Dialogs
dialog.exportkml.trackcolour=Track color
dialog.saveconfig.prune.languagecode=Language code (EN_US)
+dialog.saveconfig.prune.colourscheme=Color scheme
+dialog.saveconfig.prune.kmltrackcolour=KML track color
dialog.setcolours.intro=Click on a color patch to change the color
dialog.colourchooser.title=Choose color
+dialog.colourer.intro=A point colorer can give track points different colors
+dialog.colourer.type=Colorer type
+dialog.colourer.start=Start color
+dialog.colourer.end=End color
+dialog.colourer.maxcolours=Maximum number of colors
+dialog.setaltitudetolerance.text.metres=Limit (in meters) below which small climbs and descents will be ignored
# Measurement units
units.metres=Meters
menu.track.clearundo=Despejar la lista de deshacer
menu.track.markrectangle=Marcar puntos dentro de un rect\u00e1ngulo
menu.track.deletemarked=Eliminar puntos marcados
-menu.track.rearrange=Reorganizar waypoints
-menu.track.rearrange.start=Volver al comienzo
-menu.track.rearrange.end=Ir al final
-menu.track.rearrange.nearest=Ir al m\u00e1s pr\u00f3ximo
+function.rearrangewaypoints=Reorganizar waypoints
menu.range=Rango
menu.range.all=Seleccionar todo
menu.range.none=No seleccionar nada
function.deleterange=Eliminar rango
function.croptrack=Truncar track
function.interpolate=Interpolar puntos
+function.deletebydate=Eliminar puntos de acuerdo a la fecha
function.addtimeoffset=A\u00f1adir compensar tiempo
function.addaltitudeoffset=A\u00f1adir compensar altitud
function.convertnamestotimes=Convertir los nombres de los "waypoints" a tiempo
function.estimatetime=Estimar duraci\u00f3n
function.setmapbg=Configurar fondo de mapa
function.setpaths=Configurar rutas del programas
+function.selectsegment=Seleccionar segmento actual
function.splitsegments=Segmentar el track
function.sewsegments=Ensamblar los segmentos
function.getgpsies=Bajar ruta de Gpsies
dialog.gpsies.activity.skating=Patinaje
dialog.wikipedia.column.name=Nombre del art\u00edculo
dialog.wikipedia.column.distance=Distancia
+dialog.wikipedia.nonefound=No se encontraron puntos
dialog.correlate.notimestamps=No hay informaci\u00f3n de tiempo para los puntos, as\u00ed que no hay nada que correlacionar con las fotos.
dialog.correlate.nouncorrelatedphotos=No hay fotos no correlacionadas.\n\u00bfEst\u00e1 seguro de que desea continuar?
dialog.correlate.nouncorrelatedaudios=No hay audios no correlacionadas.\n\u00bfEst\u00e1 seguro de que desea continuar?
dialog.correlate.select.audioname=Nombre del audio
dialog.correlate.select.audiolater=Audio m\u00e1s adelante
dialog.rearrangephotos.desc=Seleccionar el destino y sortear el orden de los puntos de las fotos
-dialog.rearrangephotos.tostart=Mover al comienzo
-dialog.rearrangephotos.toend=Mover al final
-dialog.rearrangephotos.nosort=No sortear
-dialog.rearrangephotos.sortbyfilename=Sortear por nombre del archivo
-dialog.rearrangephotos.sortbytime=Sortear por tiempo
+dialog.rearrange.tostart=Mover al comienzo
+dialog.rearrange.toend=Mover al final
+dialog.rearrange.tonearest=Mover al punto m\u00e1s pr\u00f3ximo
+dialog.rearrange.nosort=No sortear
+dialog.rearrange.sortbyfilename=Sortear por nombre del archivo
+dialog.rearrange.sortbyname=Sortear por nombre
+dialog.rearrange.sortbytime=Sortear por tiempo
dialog.compress.closepoints.title=remover puntos cercanos
dialog.compress.closepoints.paramdesc=Factor de extensi\u00f3n
dialog.compress.wackypoints.title=Eliminar puntos an\u00f3malos
dialog.colourchooser.red=Rojo
dialog.colourchooser.green=Verde
dialog.colourchooser.blue=Azul
+dialog.colourer.start=Color de inicio
+dialog.colourer.end=Color final
dialog.setlanguage.firstintro=Puede usted seleccionar algunos de los lenguajes incluidos,<p>o puede en lugar de esto seleccionar un archivo de texto
dialog.setlanguage.secondintro=Usted necesita guardar sus preferencias y luego<p>reiniciar GpsPrune para cambiar el lenguaje
dialog.setlanguage.language=Lenguaje
dialog.weather.wind=Viento
dialog.weather.temp=Temp
dialog.weather.humidity=Humedad
+dialog.deletebydate.nodate=Sin marcas de tiempo
+dialog.deletebydate.column.keep=Mantener
+dialog.deletebydate.column.delete=Eliminar
# 3d window
dialog.3d.title=GpsPrune vista 3-D
fieldname.altitude=Altitud
fieldname.timestamp=Informaci\u00f3n de tiempo
fieldname.time=Tiempo
+fieldname.date=Data
fieldname.waypointname=Nombre
fieldname.waypointtype=Tipo
fieldname.newsegment=Segmento
error.cache.notthere=No se encontr\u00f3 la carpeta del cache de recuadros
error.cache.empty=La carpeta del cache de recuadros esta vac\u00edo
error.cache.cannotdelete=No se pudieron borrar recuadros
-error.interpolate.invalidparameter=El n\u00famero de puntos necesita ser entre 1 y 1000
error.tracksplit.nosplit=Imposible segmentar el track
error.downloadsrtm.nocache=Imposible guardar los archivos.\nPor favor, compruebe el cache.
error.sewsegments.nothingdone=Imposible ensamblar los segmentos.\nEl track tiene ahora %d segmentos.
menu.track.undo=\u0628\u0627\u0637\u0644 \u06a9\u0631\u062f\u0646 \u06a9\u0627\u0631 \u0642\u0628\u0644\u064a
menu.track.clearundo=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0644\u064a\u0633\u062a \u06a9\u0627\u0631\u0647\u0627\u06cc \u0627\u0646\u062c\u0627\u0645 \u0634\u062f\u0647
menu.track.deletemarked=\u067e\u0627\u06a9 \u06a9\u0631\u062f\u0646 \u0646\u0642\u0627\u0637 \u0627\u0646\u062a\u062e\u0627\u0628 \u0634\u062f\u0647
-menu.track.rearrange=\u0628\u0627\u0632 \u0686\u064a\u0646\u06cc \u0646\u0642\u0627\u0637 \u0645\u0633\u064a\u0631
-menu.track.rearrange.start=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0628\u062a\u062f\u0627\u06cc \u0645\u0633\u064a\u0631
-menu.track.rearrange.end=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0646\u062a\u0647\u0627\u06cc \u0645\u0633\u064a\u0631
-menu.track.rearrange.nearest=\u0647\u0631 \u064a\u06a9 \u0628\u0647 \u0646\u0632\u062f\u064a\u06a9 \u0646\u0642\u0637\u0647 \u0645\u0633\u064a\u0631
+function.rearrangewaypoints=\u0628\u0627\u0632 \u0686\u064a\u0646\u06cc \u0646\u0642\u0627\u0637 \u0645\u0633\u064a\u0631
+dialog.rearrange.tostart=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0628\u062a\u062f\u0627\u06cc \u0645\u0633\u064a\u0631
+dialog.rearrange.toend=\u0647\u0645\u0647 \u0646\u0642\u0627\u0637 \u0628\u0647 \u0627\u0646\u062a\u0647\u0627\u06cc \u0645\u0633\u064a\u0631
+dialog.rearrange.tonearest=\u0647\u0631 \u064a\u06a9 \u0628\u0647 \u0646\u0632\u062f\u064a\u06a9 \u0646\u0642\u0637\u0647 \u0645\u0633\u064a\u0631
menu.range=\u0686\u064a\u0646\u0634
menu.range.all=\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u0645\u0647 \u0646\u0642\u0627\u0637
menu.range.none=\u0627\u0646\u062a\u062e\u0627\u0628 \u0647\u064a\u0686 \u064a\u06a9 \u0627\u0632 \u0646\u0642\u0627\u0637
menu.track.clearundo=Purger la liste d'annulation
menu.track.markrectangle=S\u00e9lectionner les points dans un rectangle
menu.track.deletemarked=Supprimer les points marqu\u00e9s
-menu.track.rearrange=R\u00e9arranger les waypoints
-menu.track.rearrange.start=Tous au d\u00e9but du fichier
-menu.track.rearrange.end=Tous \u00e0 la fin du fichier
-menu.track.rearrange.nearest=Chacun au point de trace le plus proche
+function.rearrangewaypoints=R\u00e9arranger les waypoints
menu.range=\u00c9tendue
menu.range.all=Tout s\u00e9lectionner
menu.range.none=Rien s\u00e9lectionner
function.distances=Distances
function.fullrangedetails=Montrer tous les d\u00e9tails
function.estimatetime=Temps estim\u00e9
+function.learnestimationparams=Apprendre les param\u00e8tres pour l'estimation
function.setmapbg=D\u00e9finir le fond de carte
function.setpaths=D\u00e9finir les chemins des programmes
function.splitsegments=S\u00e9pare les segments
dialog.gpsies.activity.skating=Skating
dialog.wikipedia.column.name=Nom de l'article
dialog.wikipedia.column.distance=Distance
+dialog.wikipedia.nonefound=Aucune points trouv\u00e9e
dialog.correlate.notimestamps=Les points n'ont pas d'indication de temps, il n'est pas possible de les corr\u00e9ler.
dialog.correlate.nouncorrelatedphotos=Il n'y a pas de photos non-corr\u00e9l\u00e9es.\nVoulez-vous continuer ?
dialog.correlate.nouncorrelatedaudios=Il n'y a pas d'audios non-corr\u00e9l\u00e9s.\nVoulez-vous continuer ?
dialog.correlate.select.audioname=Nom du fichier audio
dialog.correlate.select.audiolater=Audio apr\u00e8s
dialog.rearrangephotos.desc=Choisissez la destination et l\u2019ordre des points des photos
-dialog.rearrangephotos.tostart=Aller au d\u00e9but
-dialog.rearrangephotos.toend=Aller \u00e0 la fin
-dialog.rearrangephotos.nosort=Ne pas trier
-dialog.rearrangephotos.sortbyfilename=Trier par nom de fichier
-dialog.rearrangephotos.sortbytime=Trier par horodatage
+dialog.rearrange.tostart=Au d\u00e9but
+dialog.rearrange.toend=\u00e0 la fin
+dialog.rearrange.tonearest=Au point de trace le plus proche
+dialog.rearrange.nosort=Ne pas trier
+dialog.rearrange.sortbyfilename=Trier par nom de fichier
+dialog.rearrange.sortbyname=Trier par nom
+dialog.rearrange.sortbytime=Trier par horodatage
dialog.compress.closepoints.title=Suppression des points voisins
dialog.compress.closepoints.paramdesc=Taille du voisinage
dialog.compress.wackypoints.title=Suppression des points anormaux
dialog.weather.temp=Temp
dialog.weather.humidity=Humidit\u00e9
dialog.weather.creditnotice=Ces donn\u00e9es sont fournies par openweathermap.org. Consultez la page pour plus de d\u00e9tails.
+dialog.deletebydate.column.keep=Conserver
+dialog.deletebydate.column.delete=Supprimer
# 3d window
dialog.3d.title=Vue 3D de GpsPrune
error.cache.notthere=Le dossier du cache n'a pas \u00e9t\u00e9 trouv\u00e9
error.cache.empty=Le dossier du cache est vide
error.cache.cannotdelete=Effacement des dalles impossible
-error.interpolate.invalidparameter=Le nombre de points doit \u00eatre compris entre 1 et 1000
error.tracksplit.nosplit=Impossible de s\u00e9parer les segments
menu.track.clearundo=Visszavon\u00e1si lista t\u00f6rl\u00e9se
menu.track.markrectangle=N\u00e9gyzeten bel\u00fcli pontok megjel\u00f6l\u00e9se
menu.track.deletemarked=Jel\u00f6lt pontok t\u00f6rl\u00e9se
-menu.track.rearrange=\u00datpontok \u00fajrarendez\u00e9se
-menu.track.rearrange.start=\u00d6sszes a f\u00e1jl elej\u00e9re
-menu.track.rearrange.end=\u00d6sszes a f\u00e1jl v\u00e9g\u00e9re
-menu.track.rearrange.nearest=Egyenk\u00e9nt a legk\u00f6zelebbi nyomponthoz
+function.rearrangewaypoints=\u00datpontok \u00fajrarendez\u00e9se
+dialog.rearrange.tonearest=Egyenk\u00e9nt a legk\u00f6zelebbi nyomponthoz
menu.range=Tartom\u00e1ny
menu.range.all=Mindet kijel\u00f6l
menu.range.none=Kijel\u00f6l\u00e9s megsz\u00fcntet\u00e9se
dialog.correlate.select.audioname=Hang neve
dialog.correlate.select.audiolater=Hang k\u00e9s\u0151bb
dialog.rearrangephotos.desc=V\u00e1lassza ki a f\u00e9nyk\u00e9ppontok c\u00e9lj\u00e1t \u00e9s rendez\u00e9si sorrendj\u00e9t
-dialog.rearrangephotos.tostart=Mozgat\u00e1s a kezdet\u00e9hez
-dialog.rearrangephotos.toend=Mozgat\u00e1s a v\u00e9g\u00e9hez
-dialog.rearrangephotos.nosort=Ne rendezze
-dialog.rearrangephotos.sortbyfilename=Rendez\u00e9s f\u00e1jln\u00e9v szerint
-dialog.rearrangephotos.sortbytime=Rendez\u00e9s id\u0151 szerint
+dialog.rearrange.tostart=Mozgat\u00e1s a kezdet\u00e9hez
+dialog.rearrange.toend=Mozgat\u00e1s a v\u00e9g\u00e9hez
+dialog.rearrange.nosort=Ne rendezze
+dialog.rearrange.sortbyfilename=Rendez\u00e9s f\u00e1jln\u00e9v szerint
+dialog.rearrange.sortbytime=Rendez\u00e9s id\u0151 szerint
dialog.compress.closepoints.title=K\u00f6zeli pontok elt\u00e1vol\u00edt\u00e1sa
dialog.compress.closepoints.paramdesc=Hat\u00f3t\u00e1vols\u00e1g
dialog.compress.wackypoints.title=Kisz\u00e1m\u00edthatatlan pontok elt\u00e1vol\u00edt\u00e1sa
error.cache.notthere=A csempegyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra nem tal\u00e1lhat\u00f3
error.cache.empty=A csempegyors\u00edt\u00f3t\u00e1r k\u00f6nyvt\u00e1ra \u00fcres
error.cache.cannotdelete=Nincs t\u00f6r\u00f6lhet\u0151 csempe
-error.interpolate.invalidparameter=A pontok sz\u00e1ma 1 \u00e9s 1000 k\u00f6z\u00f6tt kell legyen
error.learnestimationparams.failed=Nem lehet param\u00e9tereket tanulni ebb\u0151l a nyomvonalb\u00f3l.\nT\u00f6lts be t\u00f6bb nyomvonalat.
error.tracksplit.nosplit=A nyomvonal nem elv\u00e1ghat\u00f3
error.downloadsrtm.nocache=A f\u00e1jlokat nem siker\u00fclt meneni.\nK\u00e9rlek, ellen\u0151rizd a gyors\u00edt\u00f3t\u00e1rat.
menu.track.clearundo=Cancella lista annulla
menu.track.markrectangle=Segnare i punti nel rettangolo
menu.track.deletemarked=Cancella punti marcati
-menu.track.rearrange=Riorganizza waypoint
-menu.track.rearrange.start=Tutti all'inizio del file
-menu.track.rearrange.end=Tutti alla fine del file
-menu.track.rearrange.nearest=Sul punto pi\u00f9 vicino
+function.rearrangewaypoints=Riorganizza waypoint
menu.range=Serie
menu.range.all=Seleziona tutto
menu.range.none=Deseleziona tutto
function.deleterange=Cancella la serie
function.croptrack=Cima la traccia
function.interpolate=Interpola i punti
+function.deletebydate=Cancella punti secondo la data
function.addtimeoffset=Aggiungi uno scarto temporale
function.addaltitudeoffset=Aggiungi uno scarto di altitudine
function.convertnamestotimes=Converti nomi dei waypoint in orari
function.learnestimationparams=Apprendi parametri di stima
function.setmapbg=Configura sfondo mappa
function.setpaths=Configura percorsi programmi
+function.selectsegment=Seleziona segmento corrente
function.splitsegments=Dividi traccia in segmenti
function.sewsegments=Riorganizza segmenti insieme
function.getgpsies=Ottieni tracce da Gpsies
function.diskcache=Salva mappe su disco
function.managetilecache=Gestione del cache di tasselli
function.getweatherforecast=Ottieni previsioni del tempo
+function.setaltitudetolerance=Configura tolleranza di altitudini
# Dialogs
dialog.exit.confirm.title=Esci da GpsPrune
dialog.gpsies.activity.skating=Pattinaggio
dialog.wikipedia.column.name=Titolo articolo
dialog.wikipedia.column.distance=Distanza
+dialog.wikipedia.nonefound=Nessuna punti trovata
dialog.correlate.notimestamps=Non ci sono informazioni temporali tra i dati dei punti, non c'\u00e8 niente per collegarli con le foto.
dialog.correlate.nouncorrelatedphotos=Non ci sono foto non correlate.\nSei sicuro di voler continuare?
dialog.correlate.nouncorrelatedaudios=Non ci sono audio non correlati. \nSei sicuro di voler continuare?
dialog.correlate.select.audioname=Nome ripresa audio
dialog.correlate.select.audiolater=Ripresa audio successiva
dialog.rearrangephotos.desc=Seleziona la destinazione e l'ordine dei punti foto
-dialog.rearrangephotos.tostart=Sposta all'inizio
-dialog.rearrangephotos.toend=Sposta alla fine
-dialog.rearrangephotos.nosort=Non mettere in ordine
-dialog.rearrangephotos.sortbyfilename=Metti in ordine di nome del file
-dialog.rearrangephotos.sortbytime=Metti in ordine di tempo
+dialog.rearrangephotos.desc=Seleziona la destinazione e l'ordine dei waypoint
+dialog.rearrange.tostart=Sposta all'inizio
+dialog.rearrange.toend=Sposta alla fine
+dialog.rearrange.tonearest=Sul punto pi\u00f9 vicino
+dialog.rearrange.nosort=Non mettere in ordine
+dialog.rearrange.sortbyfilename=Metti in ordine di nome del file
+dialog.rearrange.sortbyname=Metti in ordine di nome
+dialog.rearrange.sortbytime=Metti in ordine di tempo
dialog.compress.closepoints.title=Cancella punti vicini
dialog.compress.closepoints.paramdesc=Fattore vicinanza
dialog.compress.wackypoints.title=Cancella punti strani
dialog.checkversion.releasedate2=.
dialog.checkversion.download=Per scaricare la nuova versione vai a http://gpsprune.activityworkshop.net/download.html.
dialog.keys.intro=Puoi utilizzare i seguenti tast di scelta rapida al posto del mouse
-dialog.keys.keylist=<table><tr><td>Tasti freccia</td><td>Muovi mappa destra, sinistra, su, giu'</td></tr><tr><td>Ctrl + freccia destra, sinistra</td><td>Selezione punto successivo o precedente</td></tr><tr><td>Ctrl + freccia su, giu'</td><td>Zoom in o out</td></tr><tr><td>Del</td><td>Cancella punto attuale</td></tr></table>
+dialog.keys.keylist=<table><tr><td>Tasti freccia</td><td>Muovi mappa destra, sinistra, su, giu'</td></tr><tr><td>Ctrl + freccia destra, sinistra</td><td>Selezione punto successivo o precedente</td></tr><tr><td>Ctrl + freccia su, giu'</td><td>Zoom in o out</td></tr><tr><td>Ctrl + pagina su, giu'</td><td>Segmento successivo o precedente</tr><tr><td>Ctrl + Home, End</td><td>Punto primo o ultimo</td></tr><tr><td>Del</td><td>Cancella punto attuale</td></tr></table>
dialog.keys.normalmodifier=Ctrl
dialog.keys.macmodifier=Comando
dialog.saveconfig.desc=Le configurazioni seguenti possono essere salvati in un file di configurazione:
dialog.colourchooser.red=Rosso
dialog.colourchooser.green=Verde
dialog.colourchooser.blue=Blu
+dialog.colourer.type.byfile=Per file
+dialog.colourer.type.bysegment=Per segmento
+dialog.colourer.type.byaltitude=Per altitud
+dialog.colourer.type.byspeed=Per velocit\u00e0
+dialog.colourer.type.byvertspeed=Per velocit\u00e0 verticale
+dialog.colourer.type.bygradient=Per gradiente
+dialog.colourer.type.bydate=Per data
+dialog.colourer.start=Colore iniziale
+dialog.colourer.end=Colore finale
+dialog.colourer.maxcolours=Numero massimo di colori
dialog.setlanguage.firstintro=Puoi selezionare una delle lingue incluse,<p>oppure selezionare un file di testo.
dialog.setlanguage.secondintro=Devi salvare le impostazioni e<p> riavviare GpsPrune per cambiare la lingua.
dialog.setlanguage.language=Lingua
dialog.weather.temp=Temp
dialog.weather.humidity=Umidit\u00e0
dialog.weather.creditnotice=Queste informazioni sono rese disponibili da openweathermap.org. Il loro sito web contiene ulteriori dettagli.
+dialog.deletebydate.nodate=Senza dati temporali
+dialog.deletebydate.column.keep=Tieni
+dialog.deletebydate.column.delete=Cancella
# 3d window
dialog.3d.title=Visione GpsPrune in 3D
fieldname.altitude=Altitudine
fieldname.timestamp=Dati temporali
fieldname.time=Tempo
+fieldname.date=Data
fieldname.waypointname=Nome
fieldname.waypointtype=Tipo
fieldname.newsegment=Segmento
error.cache.notthere=Directory del cache di tasselli non trovato
error.cache.empty=Directory del cache di tasselli \u00e8 vuoto
error.cache.cannotdelete=Impossibile cancellare tasselli
-error.interpolate.invalidparameter=Il numero di punti deve essere tra 1 e 1000
error.learnestimationparams.failed=Non \u00e8 possibile apprendere i parametri da questa traccia.\nProva a caricare pi\u00f9 tracce.
error.tracksplit.nosplit=La traccia non pu\u00f2 essere divisa
error.downloadsrtm.nocache=Non \u00e8 stato possibile salvare i file.\nControlla la cache del disco.
menu.track.clearundo=\u30a2\u30f3\u30c9\u30a5\u30ea\u30b9\u30c8\u3092\u7a7a\u306b\u3059\u308b
menu.track.markrectangle=\u56db\u89d2\u306e\u4e2d\u306b\u5370\u3092\u3064\u3051\u308b
menu.track.deletemarked=\u5370\u306e\u4ed8\u3044\u305f\u70b9\u3092\u524a\u9664
-menu.track.rearrange=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u3092\u4e26\u3079\u66ff\u3048
-menu.track.rearrange.start=\u5168\u3066\u3092\u30d5\u30a1\u30a4\u30eb\u306e\u59cb\u70b9\u306b
-menu.track.rearrange.end=\u5168\u3066\u3092\u30d5\u30a1\u30a4\u30eb\u306e\u7d42\u70b9\u306b
-menu.track.rearrange.nearest=\u305d\u308c\u305e\u308c\u3092\u6700\u8fd1\u306e\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8\u306b
+function.rearrangewaypoints=\u30a6\u30a7\u30a4\u30dd\u30a4\u30f3\u30c8\u3092\u4e26\u3079\u66ff\u3048
menu.range=\u7bc4\u56f2(R)
menu.range.all=\u5168\u3066\u9078\u629e
menu.range.none=\u9078\u629e\u89e3\u9664
dialog.correlate.timestamp.end=\u7d42\u70b9
dialog.correlate.select.audioname=\u30aa\u30fc\u30c7\u30a3\u30aa\u540d
dialog.rearrangephotos.desc=\u5411\u304b\u3046\u5148\u3092\u9078\u629e\u3057\u3066\u3001\u5199\u771f\u306e\u70b9\u3092\u4e26\u3079\u76f4\u3059\u3002
-dialog.rearrangephotos.tostart=\u79fb\u52d5\u958b\u59cb
-dialog.rearrangephotos.toend=\u79fb\u52d5\u7d42\u4e86
-dialog.rearrangephotos.nosort=\u4e26\u3079\u66ff\u3048\u306a\u3044
-dialog.rearrangephotos.sortbyfilename=\u30d5\u30a1\u30a4\u30eb\u540d\u3067\u4e26\u3079\u66ff\u3048
-dialog.rearrangephotos.sortbytime=\u6642\u9593\u3067\u4e26\u3079\u66ff\u3048
+dialog.rearrange.tostart=\u79fb\u52d5\u958b\u59cb
+dialog.rearrange.toend=\u79fb\u52d5\u7d42\u4e86
+dialog.rearrange.tonearest=\u305d\u308c\u305e\u308c\u3092\u6700\u8fd1\u306e\u30c8\u30e9\u30c3\u30af\u30dd\u30a4\u30f3\u30c8\u306b
+dialog.rearrange.nosort=\u4e26\u3079\u66ff\u3048\u306a\u3044
+dialog.rearrange.sortbyfilename=\u30d5\u30a1\u30a4\u30eb\u540d\u3067\u4e26\u3079\u66ff\u3048
+dialog.rearrange.sortbytime=\u6642\u9593\u3067\u4e26\u3079\u66ff\u3048
dialog.compress.closepoints.title=\u8fd1\u508d\u70b9\u3092\u524a\u9664
dialog.compress.closepoints.paramdesc=\u8fd1\u508d\u4fc2\u6570
dialog.compress.wackypoints.title=\u304a\u304b\u3057\u306a\u70b9\u306e\u524a\u9664
menu.track.undo=\uc2e4\ud589\ucde8\uc18c
menu.track.clearundo=\uc2e4\ud589\ucde8\uc18c \ubaa9\ub85d \uc0ad\uc81c
menu.track.deletemarked=\ud45c\uc2dc\ub41c \uc9c0\uc810 \uc9c0\uc6b0\uae30
-menu.track.rearrange=\uacbd\uc720\uc9c0 \uc7ac\uc815\ub82c
-menu.track.rearrange.start=\uc2dc\uc791 \uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
-menu.track.rearrange.end=\ub05d \uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
-menu.track.rearrange.nearest=\uac00\uae4c\uc6b4 \ud2b8\ub799\uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
+function.rearrangewaypoints=\uacbd\uc720\uc9c0 \uc7ac\uc815\ub82c
menu.range=\uc5f0\uacb0\uc120
menu.range.all=\ubaa8\ub450 \uc120\ud0dd
menu.range.none=\uc120\ud0dd\ud558\uc9c0 \uc54a\uae30
dialog.correlate.select.audioname=\uc18c\ub9ac \uc774\ub984
dialog.correlate.select.audiolater=\uc18c\ub9ac \ud6c4\uc5d0
dialog.rearrangephotos.desc=\uc0ac\uc9c4 \uc9c0\uc810\ub4e4\uc744 \uc5b4\ub5bb\uac8c \uc815\ub82c\ud560 \uac74\uc9c0 \uc120\ud0dd\ud574\uc8fc\uc138\uc694.
-dialog.rearrangephotos.tostart=\uc2dc\uc791\uc73c\ub85c
-dialog.rearrangephotos.toend=\ub05d\uc73c\ub85c
-dialog.rearrangephotos.nosort=\uc815\ub82c\ud558\uc9c0 \uc54a\uae30
-dialog.rearrangephotos.sortbyfilename=\ud30c\uc77c \uc774\ub984\uc73c\ub85c \uc815\ub82c
-dialog.rearrangephotos.sortbytime=\uc2dc\uac04\uc73c\ub85c \uc815\ub82c
+dialog.rearrange.tostart=\uc2dc\uc791\uc73c\ub85c
+dialog.rearrange.toend=\ub05d\uc73c\ub85c
+dialog.rearrange.tonearest=\uac00\uae4c\uc6b4 \ud2b8\ub799\uc9c0\uc810\uc73c\ub85c \uc774\ub3d9
+dialog.rearrange.nosort=\uc815\ub82c\ud558\uc9c0 \uc54a\uae30
+dialog.rearrange.sortbyfilename=\ud30c\uc77c \uc774\ub984\uc73c\ub85c \uc815\ub82c
+dialog.rearrange.sortbytime=\uc2dc\uac04\uc73c\ub85c \uc815\ub82c
dialog.deletemarked.nonefound=\uc9c0\uc810 \ub370\uc774\ud130\uac00 \uc81c\uac70\ub420 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.
dialog.compress.closepoints.title=\uc8fc\ubcc0 \ud3ec\uc778\ud2b8 \uc81c\uac70
dialog.compress.closepoints.paramdesc=\ud655\uc7a5 \uacc4\uc218
menu.track.clearundo=Ongedaan-maken lijst wissen
menu.track.markrectangle=Makeer alle punten in een vierkant
menu.track.deletemarked=Verwijderen gemarkeerde punten
-menu.track.rearrange=Rangschikken waypoints
-menu.track.rearrange.start=Alles naar begin route
-menu.track.rearrange.end=Alles naar einde route
-menu.track.rearrange.nearest=Alles naar dichtstbijzijnde routepunt
menu.range=Reeks
menu.range.all=Selecteer alles
menu.range.none=Selecteer geen
function.deleterange=Verwijder reeks
function.croptrack=Route bijknippen
function.interpolate=Interpoleer punten
+function.deletebydate=Verwijder punten op datum
function.addtimeoffset=Tijdsverschil toevoegen
function.addaltitudeoffset=Hoogteverschil toevoegen
+function.rearrangewaypoints=Rangschikken waypoints
function.convertnamestotimes=Converteer waypointnamen naar tijden
function.deletefieldvalues=Verwijder veldwaarden
function.findwaypoint=Zoek waypoint
function.learnestimationparams=Parameters voor geschatte tijd
function.setmapbg=Instellen kaart achtergrond
function.setpaths=Instellen programmapaden
+function.selectsegment=Selecteer huidige segment
function.splitsegments=Splits route in segmenten
function.sewsegments=Voeg segmenten samen
function.getgpsies=Routes van Gpsies ophalen
function.diskcache=Kaart opslaan op schijf
function.managetilecache=Beheer tegelcache
function.getweatherforecast=Ophalen weersvoorspelling
+function.setaltitudetolerance=Instellen hoogtetolerantie
# Dialogs
dialog.exit.confirm.title=GpsPrune afsluiten
dialog.gpsies.activity.skating=Skating
dialog.wikipedia.column.name=Artikelnaam
dialog.wikipedia.column.distance=Afstand
+dialog.wikipedia.nonefound=Geen punten gevonden
dialog.correlate.notimestamps=Er zit geen tijdinformatie in de punten, dus kunnen ze niet aan foto's gekoppeld worden.
dialog.correlate.nouncorrelatedphotos=Er zijn geen ongekoppelde foto's.\nWeet u zeker dat u wilt doorgaan?
dialog.correlate.nouncorrelatedaudios=Er zijn geen ongekoppelde geluidsbestanden.\nWeet u zeker dat u wilt doorgaan?
dialog.correlate.timestamp.middle=Midden
dialog.correlate.timestamp.end=Einde
dialog.correlate.audioselect.intro=Gebruk \u00e9\u00e9n van deze gecorreleerde audiobestanden als tijdsveschil
-dialog.correlate.select.audioname=Naam audiobestsnd
+dialog.correlate.select.audioname=Naam audiobestand
dialog.correlate.select.audiolater=Audio later
+dialog.rearrangewaypoints.desc=Selecteer doel en sorteervolgorde van de waypoints
dialog.rearrangephotos.desc=Selecteer doel en sorteervolgorde van de foto punten
-dialog.rearrangephotos.tostart=Naar begin
-dialog.rearrangephotos.toend=Naar einde
-dialog.rearrangephotos.nosort=Niet sorteren
-dialog.rearrangephotos.sortbyfilename=Sorteren op bestandsnaam
-dialog.rearrangephotos.sortbytime=Sorteren op tijd
+dialog.rearrange.tostart=Naar begin
+dialog.rearrange.toend=Naar einde
+dialog.rearrange.tonearest=Naar dichtstbijzijnde routepunt
+dialog.rearrange.nosort=Niet sorteren
+dialog.rearrange.sortbyfilename=Sorteren op bestandsnaam
+dialog.rearrange.sortbyname=Sorteren op naam
+dialog.rearrange.sortbytime=Sorteren op tijd
dialog.compress.closepoints.title=Verwijder nabijliggende punten
dialog.compress.closepoints.paramdesc=Bereik
dialog.compress.wackypoints.title=Vreemde punten verwijderen
dialog.colourchooser.red=Rood
dialog.colourchooser.green=Groen
dialog.colourchooser.blue=Blauw
+dialog.colourer.intro=Een puntinkleuring geeft routepunten verschillende kleuren
+dialog.colourer.type=Type inkleuring
+dialog.colourer.type.none=Geen
+dialog.colourer.type.byfile=Op bestand
+dialog.colourer.type.bysegment=Op segment
+dialog.colourer.type.byaltitude=Op hoogte
+dialog.colourer.type.byspeed=Op snelheid
+dialog.colourer.type.byvertspeed=Op verticale snelheid
+dialog.colourer.type.bygradient=Op stijgingspercentage
+dialog.colourer.type.bydate=Op datum
+dialog.colourer.start=Beginkleur
+dialog.colourer.end=Eindkleur
+dialog.colourer.maxcolours=Maximum aantal kleuren
dialog.setlanguage.firstintro=U kunt kiezen uit \u00e9\u00e9n van de meegeleverde talen,<p>of selecteer een tekstbestand.
dialog.setlanguage.secondintro=U dient uw instellingen op te slaan en<p>GpsPrune te herstarten om de taal te kunnen wijzigen
dialog.setlanguage.language=Taal
dialog.weather.day.friday=Vrijdag
dialog.weather.day.saturday=Zaterdag
dialog.weather.day.sunday=Zondag
+dialog.weather.wind=Wind
+dialog.weather.temp=Temp
dialog.weather.humidity=Luchtvocht.
dialog.weather.creditnotice=Deze gegevens worden beschikbaar gesteld door openweathermap.org. Hun website heeft meer details.
+dialog.deletebydate.onlyonedate=Alle punten werden op dezelfde datum opgenomen.
+dialog.deletebydate.intro=Je kan voor iedere datum in de route kiezen of je de punten wilt verwijderen of behouden
+dialog.deletebydate.nodate=Geen tijden
+dialog.deletebydate.column.keep=Behouden
+dialog.deletebydate.column.delete=Verwijderen
+dialog.setaltitudetolerance.text.metres=Grens (in meters) waaronder kleine klimmen en afdalingen worden genegeerd
+dialog.setaltitudetolerance.text.feet=Grens (in feet) waaronder kleine klimmen en afdalingen worden genegeerd
# 3d window
dialog.3d.title=GpsPrune in 3D
fieldname.altitude=Hoogte
fieldname.timestamp=Tijd
fieldname.time=Tijd
+fieldname.date=Datum
fieldname.waypointname=Naam
fieldname.waypointtype=Type
fieldname.newsegment=Segment
units.deg=Graden
units.iso8601=ISO 8601
units.degreescelsius=Celsius
+units.degreescelsius.short=\u00baC
units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00baF
# How to combine conditions, such as filters
logic.and=en
error.cache.notthere=De tegelcache map niet gevonden
error.cache.empty=De tegelcache map is leeg
error.cache.cannotdelete=Er konden geen tegels verwijderd worden
-error.interpolate.invalidparameter=Aantal punten moet tussen 1 en 1000 liggen
error.learnestimationparams.failed=Kan geen parameters bepalen van deze route.\nProbeer meer routes te laden.
error.tracksplit.nosplit=Deze route kon niet opgedeeld worden
error.downloadsrtm.nocache=De bestanden konden niet worden opgeslagen.\nControleer de schijfcache.
menu.file.recentfiles=Ostatnio u\u017cywane
menu.file.save=Zapisz
menu.file.exit=Zako\u0144cz
+menu.online=Online
menu.track=\u015acie\u017cka
menu.track.undo=Cofnij
menu.track.clearundo=Wyczy\u015b\u0107 list\u0119 zmian
menu.track.markrectangle=Zaznaczenie prostok\u0105tne
menu.track.deletemarked=Usu\u0144 zaznaczone punkty
-menu.track.rearrange=Zmie\u0144 kolejno\u015b\u0107 punkt\u00f3w po\u015brednich
-menu.track.rearrange.start=Wszystkie na pocz\u0105tek \u015bcie\u017cki
-menu.track.rearrange.end=Wszystkie na koniec \u015bcie\u017cki
-menu.track.rearrange.nearest=Do najbli\u017cszego punktu
menu.range=Zakres
menu.range.all=Zaznacz wszystko
menu.range.none=Usu\u0144 zaznaczenie
menu.range.end=Zaznacz koniec zakresu
menu.range.average=U\u015brednij zaznaczenie
menu.range.reverse=Odwr\u00f3\u0107 zakres
-menu.range.mergetracksegments=Po\u0142\u0105cz fragmenty \u015bcie\u017cek
+menu.range.mergetracksegments=Scal fragmenty \u015bcie\u017cek
menu.range.cutandmove=Wytnij i przesu\u0144 zaznaczenie
menu.point=Punkt
menu.point.editpoint=Edytuj punkt
# Alt keys for menus
altkey.menu.file=P
+altkey.menu.online=O
altkey.menu.track=C
altkey.menu.range=Z
altkey.menu.point=U
function.deleterange=Usu\u0144 zakres
function.croptrack=Przytnij \u015bcie\u017ck\u0119
function.interpolate=Wstaw pomi\u0119dzy punkty
+function.deletebydate=Usu\u0144 punkty wed\u0142ug daty
function.addtimeoffset=Dodaj przesuni\u0119cie czasu
function.addaltitudeoffset=Dodaj przesuni\u0119cie wysoko\u015bci
+function.rearrangewaypoints=Zmie\u0144 kolejno\u015b\u0107 punkt\u00f3w po\u015brednich
function.convertnamestotimes=Zamie\u0144 nazwy punkt\u00f3w na czas
function.deletefieldvalues=Usu\u0144 warto\u015bci
function.findwaypoint=Znajd\u017a punkt po\u015bredni
function.learnestimationparams=Skoryguj wsp\u00f3\u0142czynniki szacowania czasu
function.setmapbg=Wybierz map\u0119 t\u0142a
function.setpaths=Ustaw \u015bcie\u017cki do program\u00f3w
+function.selectsegment=Wybierz bie\u017c\u0105cy fragment
+function.splitsegments=Podziel \u015bcie\u017ck\u0119 na fragmenty
+function.sewsegments=Po\u0142\u0105cz fragmenty
function.getgpsies=Pobierz \u015bcie\u017cki z Gpsies
function.uploadgpsies=Wy\u015blij \u015bcie\u017cki do Gpsies
function.lookupsrtm=Pobierz wysoko\u015bci z SRTM
function.saveconfig=Zapisz ustawienia
function.diskcache=Zapisz mapy na dysk
function.managetilecache=Zarz\u0105dzaj keszem p\u0142ytek
-function.getweatherforecast=Pobierz prognoza pogody
+function.getweatherforecast=Pobierz prognoz\u0119 pogody
+function.setaltitudetolerance=Ustaw tolerancj\u0119 wysoko\u015bci
# Dialogs
dialog.exit.confirm.title=Zako\u0144cz GpsPrune
dialog.exportpov.ballsandsticks=Kule i pa\u0142ki
dialog.exportpov.tubesandwalls=Rurki i \u015bciany
dialog.3d.warningtracksize=Ta \u015bcie\u017cka ma bardzo wiele punkt\u00f3w, kt\u00f3rych Java3D mo\u017ce nie wy\u015bwietli\u0107.\nCzy chcesz kontynuowa\u0107?
+dialog.3d.useterrain=Poka\u017c teren
+dialog.3d.terraingridsize=Rozmiar siatki
dialog.exportpov.baseimage=Obraz podk\u0142adu
dialog.exportpov.cannotmakebaseimage=Nie mo\u017cna zapisa\u0107 obrazu podk\u0142adu
dialog.baseimage.title=Obrazu podk\u0142adu
dialog.gpsies.activity.skating=Wrotki/rolki
dialog.wikipedia.column.name=Tytu\u0142 artyku\u0142u
dialog.wikipedia.column.distance=Odleg\u0142o\u015b\u0107
+dialog.wikipedia.nonefound=Brak wpis\u00f3w w wikipedii
dialog.correlate.notimestamps=Punkty nie maj\u0105 znacznik\u00f3w czasu, nie mo\u017cna ich powi\u0105za\u0107 ze zdj\u0119ciami.
dialog.correlate.nouncorrelatedphotos=Nie ma nie powi\u0105zanych zdj\u0119\u0107.\nCzy na pewno chcesz kontynuowa\u0107?
dialog.correlate.nouncorrelatedaudios=Nie ma nie powi\u0105zanych plik\u00f3w audio.\nCzy na pewno chcesz kontynuowa\u0107?
dialog.correlate.audioselect.intro=Wybierz jeden z powi\u0105zanych plik\u00f3w audio i u\u017cyj go jako wzorca do przesuni\u0119cia czasu
dialog.correlate.select.audioname=nazwa pliku audio
dialog.correlate.select.audiolater=p\u00f3\u017aniejszy plik audio
+dialog.rearrangewaypoints.desc=Wybierz przeznaczenie i porz\u0105dek sortowania punkt\u00f3w po\u015brednich
dialog.rearrangephotos.desc=Wybierz przeznaczenie i porz\u0105dek sortowania punkt\u00f3w ze zdj\u0119ciami
-dialog.rearrangephotos.tostart=Przesu\u0144 na pocz\u0105tek
-dialog.rearrangephotos.toend=Przesu\u0144 na koniec
-dialog.rearrangephotos.nosort=Nie sortuj
-dialog.rearrangephotos.sortbyfilename=Sortuj po nazwie pliku
-dialog.rearrangephotos.sortbytime=Sortuj wed\u0142ug czasu
+dialog.rearrange.tostart=Przesu\u0144 na pocz\u0105tek
+dialog.rearrange.toend=Przesu\u0144 na koniec
+dialog.rearrange.tonearest=Do najbli\u017cszego punktu
+dialog.rearrange.nosort=Nie sortuj
+dialog.rearrange.sortbyfilename=Sortuj po nazwie pliku
+dialog.rearrange.sortbyname=Sortuj po nazwie
+dialog.rearrange.sortbytime=Sortuj wed\u0142ug czasu
dialog.compress.closepoints.title=Usuwanie bliskich sobie punkt\u00f3w
dialog.compress.closepoints.paramdesc=Wsp\u00f3\u0142czynnik rozpi\u0119to\u015bci
dialog.compress.wackypoints.title=Usuwanie dziwacznych punkt\u00f3w
dialog.colourchooser.red=Czerwony
dialog.colourchooser.green=Zielony
dialog.colourchooser.blue=Niebieski
+dialog.colourer.intro=Koloryzer punkt\u00f3w, zmienia kolor punkt\u00f3w \u015bcie\u017cki
+dialog.colourer.type=Tryb koloryzera
+dialog.colourer.type.none=\u017baden
+dialog.colourer.type.byfile=wed\u0142ug pliku
+dialog.colourer.type.bysegment=wed\u0142ug segmentu
+dialog.colourer.type.byaltitude=wed\u0142ug wysoko\u015bci
+dialog.colourer.type.byspeed=wed\u0142ug pr\u0119dko\u015bci
+dialog.colourer.type.byvertspeed=wed\u0142ug pr\u0119dko\u015bci pionowej
+dialog.colourer.type.bygradient=wed\u0142ug nachylenia
+dialog.colourer.type.bydate=wed\u0142ug daty
+dialog.colourer.start=Kolor pocz\u0105tkowy
+dialog.colourer.end=Kolor ko\u0144cowy
+dialog.colourer.maxcolours=Maksymalna liczba kolor\u00f3w
dialog.setlanguage.firstintro=Mo\u017cesz wybra\u0107 jeden z do\u0142\u0105czonych j\u0119zyk\u00f3w<p>Albo wybra\u0107 wybrany przez siebie plik tekstowy.
dialog.setlanguage.secondintro=B\u0119dziesz musia\u0142 zapisa\u0107 ustawienia<p>i zrestartowa\u0107 GpsPrune by zmieni\u0107 j\u0119zyk.
dialog.setlanguage.language=J\u0119zyk
dialog.setlinewidth.text=Wprowad\u017a grubo\u015b\u0107 linii do rysowania \u015bcie\u017cek
dialog.downloadosm.desc=Potwierd\u017a \u015bci\u0105gni\u0119cie danych dla tego obszaru z OSM:
dialog.searchwikipedianames.search=Szukaj
+dialog.weather.location=Pozycja
+dialog.weather.update=Prognoza zaktualizowana
+dialog.weather.sunrise=Wsch\u00f3d s\u0142o\u0144ca
+dialog.weather.sunset=Zach\u00f3d s\u0142o\u0144ca
+dialog.weather.temperatureunits=Jednostki temperatury
+dialog.weather.currentforecast=Bie\u017c\u0105ca pogoda
+dialog.weather.dailyforecast=Prognoza dobowa
+dialog.weather.3hourlyforecast=Prognoza na trzy godziny
dialog.weather.day.now=Aktualny
dialog.weather.day.today=Dzisiaj
dialog.weather.day.tomorrow=Jutro
dialog.weather.day.monday=Poniedzia\u0142ek
dialog.weather.day.tuesday=Wtorek
-dialog.weather.day.wednesday=\u015Aroda
+dialog.weather.day.wednesday=\u015aroda
dialog.weather.day.thursday=Czwartek
dialog.weather.day.friday=Pi\u0105tek
dialog.weather.day.saturday=Sobota
dialog.weather.day.sunday=Niedziela
+dialog.weather.wind=Wiatr
+dialog.weather.temp=Temp
+dialog.weather.humidity=Wilgotno\u015b\u0107
+dialog.weather.creditnotice=Dane na podstawie openweathermap.org. Wi\u0119cej informacji na ich stronie.
+dialog.deletebydate.onlyonedate=Te punkty zosta\u0142y zarejestrowane w tym samym czasie.
+dialog.deletebydate.intro=Dla ka\u017cdej daty w \u015bcie\u017cce, mo\u017cesz wybra\u0107 czy usun\u0105\u0107 czy zostawi\u0107 punkty
+dialog.deletebydate.nodate=Brak znacznika czasu.
+dialog.deletebydate.column.keep=Zostaw
+dialog.deletebydate.column.delete=Usu\u0144
+dialog.setaltitudetolerance.text.metres=Limit (w metrach) poni\u017cej kt\u00f3rego, ma\u0142e spadki wzniosy b\u0119d\u0105 ignorowane
+dialog.setaltitudetolerance.text.feet=Limit (w stopach) poni\u017cej kt\u00f3rego, ma\u0142e spadki wzniosy b\u0119d\u0105 ignorowane
# 3d window
dialog.3d.title=GpsPrune widok tr\u00f3jwymiarowy
confirm.addaltitudeoffset=Dodano przesuni\u0119cie wysoko\u015bci
confirm.rearrangewaypoints=Przestawiono punkty po\u015brednie
confirm.rearrangephotos=Zmieniono kolejno\u015b\u0107 zdj\u0119\u0107
+confirm.splitsegments=
+confirm.sewsegments=Po\u0142\u0105czono %d fragmenty/\u00f3w
confirm.cutandmove=Przesuni\u0119to zaznaczenie
confirm.interpolate=Dodano punkty
confirm.convertnamestotimes=Zmieniono nazwy punkt\u00f3w po\u015brednich
confirm.rotatephoto=obr\u00f3cono zdj\u0119cie
confirm.running=Przetwarzam dane ...
confirm.lookupsrtm=Znaleziono %d warto\u015bci wysoko\u015bci
+confirm.downloadsrtm=Pobrano %d plik\u00f3w do kesza
+confirm.downloadsrtm.1=Pobrano %d plik do kesza
+confirm.downloadsrtm.none=Nie pobrano \u017cadnych plik\u00f3w, wszystkie by\u0142y ju\u017c w keszu
confirm.deletefieldvalues=Warto\u015bci p\u00f3l usuni\u0119to
confirm.audioload=dodano pliki audio
confirm.correlateaudios.single=audio zosta\u0142o po\u0142\u0105czone
confirm.correlateaudios.multi=audio zosta\u0142y po\u0142\u0105czone
-# Tips
+# Tips, shown just once when appropriate
tip.title=Porada
+tip.useamapcache=Konfiguruj\u0105c kesz dyskowy (Ustawienia -> Zapisz mapy na dysk)\nprzyspieszasz wy\u015bwietlanie i ograniczasz ruch sieciowy
+tip.learntimeparams=Resultat b\u0119dzie dok\u0142adniejszy je\u015bli u\u017cyjesz
+tip.downloadsrtm=Mo\u017cesz przyspieszy\u0107 operacj\u0119 wywo\u0142uj\u0105c polecenie
+tip.usesrtmfor3d=\u015acie\u017cka nie zawiera danych o wysoko\u015bciach\nMo\u017cesz u\u017cy\u0107 funkcji SRTM by pobrac przybli\u017cone dane\no wysko\u015bciach w trybie widoku 3D.
tip.manuallycorrelateone=Gdy powi\u0105\u017cesz r\u0119cznie przynajmniej jedno zdj\u0119cie, r\u00f3\u017cnica czasowa zostanie policzona automatycznie.
# Buttons
fieldname.altitude=Wysoko\u015b\u0107
fieldname.timestamp=Czas
fieldname.time=Czas
+fieldname.date=Data
fieldname.waypointname=Nazwa
fieldname.waypointtype=Typ
fieldname.newsegment=Odcinek
undo.deletemarked=usu\u0144 punkty
undo.insert=wstaw punkty
undo.reverse=odwr\u00f3\u0107 zakres
-undo.mergetracksegments=po\u0142\u0105cz fragmenty \u015bcie\u017cki
+undo.mergetracksegments=scal fragmenty \u015bcie\u017cki
+undo.splitsegments=podziel \u015bcie\u017ck\u0119 na fragmenty
+undo.sewsegments=po\u0142\u0105cz fragmenty \u015bcie\u017cki
undo.addtimeoffset=dodaj przesuni\u0119cie czasowe
undo.addaltitudeoffset=dodaj przeuni\u0119cie wysoko\u015bci
undo.rearrangewaypoints=przestaw punkty po\u015brednie
error.cache.notthere=Nie znaleziono katalogu kesza
error.cache.empty=Katalog kesza jest pusty
error.cache.cannotdelete=\u017badne p\u0142ytki nie mog\u0142y zosta\u0107 usuni\u0119te
-error.interpolate.invalidparameter=Ilo\u015b\u0107 punkt\u00f3w musi zawiera\u0107 si\u0119 w zakresie od 1 do 1000
error.learnestimationparams.failed=Oszacowanie wsp\u00f3\u0142czynnik\u00f3w dla danej scie\u017cki nie powiod\u0142o si\u0119.\nSpr\u00f3buj za\u0142adowa\u0107 wi\u0119cej \u015bcie\u017cek.
+error.tracksplit.nosplit=Nie mo\u017cna podzieli\u0107 \u015bcie\u017cki
+error.downloadsrtm.nocache=Nie mo\u017cna zapisa\u0107 plik\u00f3w\nSprawd\u017a ustawienia kesza
+error.sewsegments.nothingdone=Nie mo\u017cna po\u0142\u0105czy\u0107 fragment\u00f3w\nW \u015bcie\u017cce jest teraz %d fragment\u00f3w.
menu.track.clearundo=Limpar lista de desfazer
menu.track.markrectangle=Marcar pontos no ret\u00e2ngulo
menu.track.deletemarked=Remover pontos marcados
-menu.track.rearrange=Rearrumar pontos
-menu.track.rearrange.start=Tudo para o in\u00edcio do arquivo
-menu.track.rearrange.end=Tudo para o fim do arquivo
-menu.track.rearrange.nearest=Cada um para o ponto da rota mais pr\u00f3ximo
+function.rearrangewaypoints=Rearrumar pontos
menu.range=Intervalo
menu.range.all=Selecionar tudo
menu.range.none=Desmarcar todas as sele\u00e7\u00f5es
function.deleterange=Remover intervalo
function.croptrack=Cortar rota
function.interpolate=Interpolar pontos
+function.deletebydate=Remover pontos de acordo com a data
function.addtimeoffset=Adicionar diferen\u00e7a de tempo
function.addaltitudeoffset=Adicionar diferen\u00e7a de altitude
function.convertnamestotimes=Converter nomes dos pontos para tempos
function.learnestimationparams=Aprender os par\u00e2metros para estimativa de tempo
function.setmapbg=Definir como fundo do mapa
function.setpaths=Definir caminhos do programa
+function.selectsegment=Selecionar segmento atual
function.splitsegments=Dividir rota em segmentos
function.sewsegments=Reunir segmentos em rota
function.getgpsies=Obter rotas Gpsies
dialog.correlate.select.audioname=Nome do \u00e1udio
dialog.correlate.select.audiolater=\u00c1udio posterior
dialog.rearrangephotos.desc=Selecione o destino e a ordena\u00e7\u00e3o dos pontos das fotos
-dialog.rearrangephotos.tostart=Mover para o in\u00edcio
-dialog.rearrangephotos.toend=Mover para o fim
-dialog.rearrangephotos.nosort=N\u00e3o ordenar
-dialog.rearrangephotos.sortbyfilename=Ordenar pelo nome do arquivo
-dialog.rearrangephotos.sortbytime=Ordenar pela hora
+dialog.rearrange.tostart=Mover para o in\u00edcio
+dialog.rearrange.toend=Mover para o fim
+dialog.rearrange.tonearest=Cada um para o ponto da rota mais pr\u00f3ximo
+dialog.rearrange.nosort=N\u00e3o ordenar
+dialog.rearrange.sortbyfilename=Ordenar pelo nome do arquivo
+dialog.rearrange.sortbyname=Ordenar pelo nome
+dialog.rearrange.sortbytime=Ordenar pela hora
dialog.compress.closepoints.title=Remo\u00e7\u00e3o de ponto pr\u00f3ximo
dialog.compress.closepoints.paramdesc=Fator de deslocamento
dialog.compress.wackypoints.title=Remo\u00e7\u00e3o de ponto exc\u00eantrica
error.cache.notthere=A paste de cache de fundos n\u00e3o foi encontrada
error.cache.empty=A pasta de cache de fundos est\u00e1 vazia
error.cache.cannotdelete=Nenhum fundo pode ser removido
-error.interpolate.invalidparameter=O n\u00famero de pontos deve estar entre 1 e 1000
error.learnestimationparams.failed=N\u00e3o foi poss\u00edvel aprender par\u00e2metros desta rota.\nTente baixar mais rotas.
error.tracksplit.nosplit=A rota n\u00e3o pode ser dividida.
error.downloadsrtm.nocache=Os arquivos n\u00e3o puderam ser salvos.\nPor favor, verifique a cache do disco.
menu.file.addphotos=Adaug\u0103 foto
menu.file.recentfiles=Fi\u015fiere recente
menu.file.save=Salvare
-menu.file.exit=Iesire
+menu.file.exit=Ie\u015fire
+menu.online=Internet
menu.track=Traseu
menu.track.undo=Anulare
menu.track.clearundo=\u015etergere lista de anulari
menu.track.deletemarked=\u015etergere puncte marcate
-menu.track.rearrange=Rearanjare waypoint
-menu.track.rearrange.start=Toate la inceputul fi\u015fierului
-menu.track.rearrange.end=Toate la sfarsitul fi\u015fierului
-menu.track.rearrange.nearest=Fiecare la punctul cel mai apropiat al traseului
menu.range=Interval
menu.range.all=Selectare toate
menu.range.none=Nu selecta niciun punct
menu.range.start=Seteaza inceputul selectiei
menu.range.end=Seteaza sfarsitul selectiei
-menu.range.average=Mediere selectie
-menu.range.reverse=Inversare selectie
+menu.range.average=Mediere selec\u0163ie
+menu.range.reverse=Inversare selec\u0163ie
menu.range.mergetracksegments=Unire segmente traseu
-menu.range.cutandmove=Taiere si mutare selectie
+menu.range.cutandmove=Taiere si mutare selec\u0163ie
menu.point=Punct
menu.point.editpoint=Editare punct
menu.point.deletepoint=\u015etergere punct
menu.view.browser.yahoo=Harti Yahoo
menu.view.browser.bing=Harti Bing
menu.settings=Set\u0103ri
+menu.settings.onlinemode=\u00cencarc\u0103 h\u0103r\u021bi
menu.help=Ajutor
# Popup menu for map
menu.map.zoomin=Apropie
# Alt keys for menus
altkey.menu.file=F
+altkey.menu.online=N
altkey.menu.track=T
altkey.menu.range=I
altkey.menu.point=P
function.interpolate=Interpolare
function.addtimeoffset=Adaug\u0103 decalaj timp
function.addaltitudeoffset=Adaug\u0103 decalaj altitudine
+function.rearrangewaypoints=Rearanjare waypoint
function.findwaypoint=Gasire waypoint
function.charts=Grafice
function.show3d=Vizualizare arborescenta
function.distances=Distan\u0163e
function.fullrangedetails=Informa\u0163ie complet
-function.loadaudio=Adaug\u0103 audio
-function.setmapbg=Fundal
+function.getgpsies=\u00cencarc\u0103 trassee Gpsies
+function.uploadgpsies=Trimite date spre Gpsies
+function.downloadsrtm=\u00cencarc\u0103 date SRTM
+function.estimatetime=Estimare durat\u0103
+function.setmapbg=Seteaza harta
+function.selectsegment=Selectare segment curent
function.setcolours=Selectare culorile
function.setlanguage=Selectare limba
function.connecttopoint=Conecteaza la punct
function.disconnectfrompoint=Deconecteaza de la punct
function.removephoto=Elimina foto
function.correlatephotos=Corelare fotografii
+function.rearrangephotos=Rearanjare fotografii
+function.rotatephotoleft=Roti foto la st\u00e2nga
+function.rotatephotoright=Roti foto la dreapta
+function.photopopup=Arat\u0103 foto
+function.loadaudio=Adaug\u0103 audio
+function.removeaudio=Elimina audio
+function.playaudio=Redare audio
function.help=Ajutor
function.showkeys=Arat\u0103 tastele scurt\u0103turi
function.about=Despre GpsPrune
function.checkversion=Verific\u0103 pentru o versiune noua
function.saveconfig=Salvare set\u0103ri
+function.diskcache=Salvare harti
function.getweatherforecast=Prognoz\u0103 meteo
# Dialogs
dialog.openappend.text=Adauga la datele deja incarcate?
dialog.deletepoint.title=\u015eterge Punct
dialog.deletepoint.deletephoto=\u015eterg fotografiile atasate acestui punct?
-dialog.deletephoto.title=\u015eterge Foto
+dialog.deletephoto.title=\u015eterge foto
dialog.deletephoto.deletepoint=\u015eterg punct atasat acestei fotografii?
+dialog.deleteaudio.deletepoint=\u015eterg punct atasat acestei audio?
dialog.openoptions.title=Optiuni deschidere
dialog.load.table.field=Cimp
dialog.load.table.datatype=Tip data
dialog.openoptions.deliminfo.records=inregistrari, cu
dialog.openoptions.deliminfo.fields=cimpuri
dialog.openoptions.deliminfo.norecords=Nu sunt inregistrari
+dialog.openoptions.altitudeunits=Unit\u0103\u0163i de altitudini
+dialog.openoptions.speedunits=Unit\u0103\u0163i de viteza
+dialog.openoptions.vertspeedunits=Unit\u0103\u0163i de viteza vertical\u0103
dialog.selecttracks.noname=F\u0103r\u0103 nume
dialog.jpegload.subdirectories=Include subdirectori
dialog.jpegload.loadjpegswithoutcoords=Include fotografii fara coordonate
dialog.jpegload.loadjpegsoutsidearea=Include fotografii din afara zonei curente
-dialog.jpegload.progress.title=Incarcare fotografii
+dialog.jpegload.progress.title=\u00cenc\u0103rcare fotografii
dialog.jpegload.progress=Va rog sa asteptati, caut fotografiile
dialog.gpsload.nogpsbabel=Nu gasesc programul gpsbabel. Continui ?
dialog.gpsload.device=Nume dispozitiv
dialog.gpsload.format=Format
-dialog.gpsload.getwaypoints=Incarcare waypoints
+dialog.gpsload.getwaypoints=\u00cencarc\u0103 waypoints
+dialog.gpsload.gettracks=\u00cencarc\u0103 trasee
+dialog.gpsload.save=Salvare fi\u015fier
+dialog.gpssend.sendwaypoints=Trimite waypoints
+dialog.gpssend.sendtracks=Trimite trasee
dialog.gpssend.trackname=Nume traseu
dialog.gpsbabel.filters=Filtre
-dialog.gpsbabel.filter.simplify=Simplifica
+dialog.addfilter.title=Adaug\u0103 filtru
+dialog.gpsbabel.filter.discard=Arunc\u0103
+dialog.gpsbabel.filter.simplify=Simplific\u0103
dialog.gpsbabel.filter.distance=Distan\u0163\u0103
+dialog.gpsbabel.filter.interpolate=Interpolare
+dialog.gpsbabel.filter.discard.intro=Arunc\u0103 puncte dac\u0103
+dialog.gpsbabel.filter.discard.hdop=Hdop >
+dialog.gpsbabel.filter.discard.vdop=Vdop >
dialog.gpsbabel.filter.discard.numsats=Num\u0103r de sateli\u0163i <
+dialog.gpsbabel.filter.simplify.maxpoints=Num\u0103r de puncte <
+dialog.gpsbabel.filter.simplify.length=diferen\u0163\u0103 de lungime
+dialog.gpsbabel.filter.distance.distance=Dac\u0103 distan\u0163\u0103 <
+dialog.gpsbabel.filter.distance.time=\u0219i diferen\u0163\u0103 de timp <
+dialog.gpsbabel.filter.interpolate.distance=Dac\u0103 distan\u0163\u0103 >
+dialog.gpsbabel.filter.interpolate.time=sau diferen\u0163\u0103 de timp >
dialog.saveoptions.title=Salvare fi\u015fier
dialog.save.table.field=Cimp
+dialog.save.table.hasdata=Date
dialog.save.table.save=Salvare
+dialog.save.coordinateunits=Format coordonate
+dialog.save.altitudeunits=Unit\u0103\u0163i de altitudini
+dialog.save.timestampformat=Format de timp
dialog.save.overwrite.title=Fi\u015fierul exist\u0103
dialog.save.overwrite.text=Fi\u015fierul exist\u0103. \u00cel suprascriu?
dialog.exportkml.text=Titlu
+dialog.exportkml.imagesize=Dimensiune imaginii
dialog.exportkml.trackcolour=Culoarea liniei
dialog.exportgpx.name=Nume
dialog.exportgpx.desc=Descriere
+dialog.exportgpx.encoding=Codare
dialog.exportgpx.encoding.system=Sistem
dialog.exportgpx.encoding.utf8=UTF-8
dialog.exportpov.font=Fontul
dialog.3d.terraingridsize=Dimensiune a grilei
dialog.exportpov.baseimage=Imagine cartografice
dialog.baseimage.title=Imagine cartografice
+dialog.baseimage.zoom=Nivel de zoom
+dialog.baseimage.incomplete=Imagine incomplet\u0103
dialog.baseimage.tiles=Tigla
-dialog.exportsvg.phi=Azimut \u03D5
-dialog.exportsvg.theta=\u00cenclina\u0163ie \u03B8
+dialog.exportsvg.phi=Azimut \u03d5
+dialog.exportsvg.theta=\u00cenclina\u0163ie \u03b8
+dialog.pointtype.track=Puncte de traseu
+dialog.pointtype.waypoint=Waypoints
+dialog.pointtype.photo=Puncte foto
+dialog.pointtype.audio=Puncte audio
+dialog.pointtype.selection=Doar interval
dialog.undo.title=Anulare
+dialog.pointedit.title=Editare punct
dialog.pointedit.intro=V\u0103 rog selecta\u0163i r\u00e2ndul care va fi editat
dialog.pointedit.table.field=Cimp
dialog.pointedit.table.value=Valoare
+dialog.pointnameedit.name=Nume de waypoint
dialog.pointnameedit.uppercase=Litere MARI
dialog.pointnameedit.lowercase=Litere mici
dialog.addtimeoffset.days=Zile
dialog.addtimeoffset.hours=Ore
dialog.addtimeoffset.minutes=Minute
dialog.findwaypoint.search=C\u0103utare
+dialog.saveexif.table.photoname=Nume
+dialog.saveexif.title=Salvare Exif
dialog.saveexif.table.status=Stare
dialog.saveexif.table.save=Salveaza
dialog.saveexif.photostatus.connected=Conectat
dialog.saveexif.overwrite=Suprascrie fi\u015fiere
dialog.charts.xaxis=Axa X
dialog.charts.yaxis=Axa Y
+dialog.charts.output=Rezultat
+dialog.charts.svgwidth=L\u0103\u021bime SVG
+dialog.charts.svgheight=\u00cen\u0103l\u021bime SVG
+dialog.distances.column.from=De punct
+dialog.distances.column.to=Spre punct
dialog.distances.currentpoint=Punct curent
+dialog.estimatetime.details=Detalii
+dialog.estimatetime.parameters=Parametrii
+dialog.estimatetime.parameters.timefor=Durata pentru
+dialog.estimatetime.results=Rezultate
+dialog.estimatetime.results.estimatedtime=Durata estimat\u0103
+dialog.estimatetime.results.actualtime=Durata (measured)
+dialog.learnestimationparams.averageerror=Eroare estimat
+dialog.learnestimationparams.combinedresults=Rezultate combinat
+dialog.learnestimationparams.weight.current=curente
+dialog.learnestimationparams.weight.calculated=calculate
+dialog.addmapsource.sourcename=Nume
dialog.addmapsource.noname=F\u0103r\u0103 nume
dialog.gpsies.column.name=Nume
dialog.gpsies.column.length=Lungime
dialog.gpsies.description=Descriere
dialog.gpsies.nodescription=F\u0103r\u0103 descriere
+dialog.gpsies.nonefound=Nu a fost g\u0103sit
+dialog.gpsies.username=Gpsies username
+dialog.gpsies.password=Gpsies parol\u0103
+dialog.gpsies.keepprivate=Traseu privat
+dialog.gpsies.activities=Activit\u0103\u0163i
dialog.wikipedia.column.name=Nume
dialog.wikipedia.column.distance=Distan\u0163\u0103
+dialog.wikipedia.nonefound=Nu a fost g\u0103sit
+dialog.correlate.select.photoname=Nume
+dialog.correlate.select.timediff=Diferenta de timp
dialog.correlate.options.offset.hours=ore,
dialog.correlate.options.offset.minutes=minute,
dialog.correlate.options.offset.seconds=secunde
+dialog.correlate.options.correlate=Corelare
+dialog.correlate.timestamp.beginning=\u00cenceptutul
+dialog.correlate.timestamp.middle=Mijlocul
+dialog.correlate.timestamp.end=Sf\u00e2r\u015fitul
+dialog.correlate.select.audioname=Nume
+dialog.rearrange.tostart=Toate la inceputul fi\u015fierului
+dialog.rearrange.toend=Toate la sfarsitul fi\u015fierului
+dialog.rearrange.tonearest=Fiecare la punctul cel mai apropiat al traseului
+dialog.rearrange.nosort=Nu sunt sortate
+dialog.rearrange.sortbyfilename=Sorta dup\u0103 nume de fi\u015fier
+dialog.rearrange.sortbyname=Sorta dup\u0103 nume
+dialog.rearrange.sortbytime=Sorta dup\u0103 timp
dialog.pastecoordinates.coords=Coordonate
dialog.about.version=Versiunea
+dialog.about.build=Construi
+dialog.about.languages=Limbi
dialog.about.systeminfo=Informa\u0163ii a sistemului
dialog.about.systeminfo.os=Sistem de operare
+dialog.about.systeminfo.java3d=Java3d instalat
+dialog.about.systeminfo.povray=Povray instalat
+dialog.about.systeminfo.exiftool=Exiftool instalat
+dialog.about.systeminfo.gpsbabel=Gpsbabel instalat
+dialog.about.systeminfo.gnuplot=Gnuplot instalat
dialog.about.systeminfo.exiflib=Bibliotec\u0103 Exif
dialog.about.systeminfo.exiflib.internal=Intern
dialog.about.systeminfo.exiflib.internal.failed=Intern (absent)
dialog.about.readme=Cite\u015fte-m\u0103
dialog.checkversion.releasedate1=Aceasta versiune noua a fost lansapa pe
dialog.checkversion.releasedate2=.
+dialog.saveconfig.prune.languagecode=Limb\u0103 (RO)
+dialog.saveconfig.prune.languagefile=Fi\u015fier de limba
+dialog.saveconfig.prune.gpsdevice=Dispozitiv GPS
+dialog.saveconfig.prune.gpsformat=Format GPS
dialog.setcolours.background=Fund
dialog.setcolours.lines=Linii
dialog.setcolours.primary=Primar
dialog.setcolours.secondary=Secundar
dialog.setcolours.point=Puncte
+dialog.setcolours.selection=Selec\u0163ie
dialog.setcolours.text=Text
+dialog.colourchooser.title=Selectare culoare
dialog.colourchooser.red=Ro\u0219u
dialog.colourchooser.green=Verde
dialog.colourchooser.blue=Albastru
+dialog.colourer.type.none=Nimic
+dialog.setlanguage.language=Limb\u0103
+dialog.setlanguage.languagefile=Fi\u015fier de limba
+dialog.diskcache.table.tiles=Tigla
+dialog.searchwikipedianames.search=C\u0103utare :
+dialog.weather.location=Loca\u0163ie
+dialog.weather.sunrise=R\u0103s\u0103rit
+dialog.weather.sunset=Apus de soare
+dialog.weather.currentforecast=Vremea curent\u0103
+dialog.weather.day.now=Vremea curent\u0103
dialog.weather.day.today=Ast\u0103zi
dialog.weather.day.tomorrow=M\u00e2ine
dialog.weather.day.monday=Luni
confirm.loadfile=Date incarcate din fi\u015fier
confirm.save.ok1=Salvat cu succes
confirm.save.ok2=puncte \u00een
+confirm.media.connect=foto/audio conectat
+confirm.photo.disconnect=foto deconectat
+confirm.audio.disconnect=audio deconectat
+confirm.media.removed=\u0219ters
+confirm.running=Executare ...
+confirm.downloadsrtm=S-au desc\u0103rcat %d fi\u015fiere
+confirm.downloadsrtm.1=S-au desc\u0103rcat %d fi\u015fier
# Tips
tip.title=Indiciu
button.selectall=Selecteaza tot
button.selectnone=Deselecteaza tot
button.load=Descarca
-button.upload=Inc\u0103rca
+button.upload=Trimite
button.guessfields=Ghici cimpuri
button.check=Verifica
button.delete=\u015etergere
details.trackdetails=Detalii traseul
details.track.points=Puncte
details.pointdetails=Detalii punctul
+details.index.selected=Punct
+details.index.of=de
+details.photofile=Fi\u015fier
details.rangedetails=Detalii intervalul
details.range.selected=Selectat
details.range.to=la
details.altitude.to=la
+details.range.climb=Urcare
+details.range.descent=Cobor\u00e2re
details.coordformat=Format coordonate
-details.distanceunits=Unitati de distan\u0163e
+details.distanceunits=Unit\u0103\u0163i de distan\u0163e
display.range.time.secs=s
display.range.time.mins=m
display.range.time.hours=o
display.range.time.days=z
details.range.avespeed=Viteza medie
details.range.maxspeed=Viteza maxim\u0103
+details.range.numsegments=Num\u0103r de segmente
+details.range.pace=Ritm
+details.range.gradient=Gradient
+details.lists.waypoints=Waypoints
details.lists.photos=Foto-uri
details.lists.audio=Audio
+details.photodetails=Detalii foto
+details.photo.loading=\u00cenc\u0103rcare
+details.photo.bearing=Direc\u0163ie
+details.media.connected=Conectat
details.audiodetails=Detalii audio
+details.audio.file=Fi\u015fier
# Field names
fieldname.latitude=Latitudine
fieldname.longitude=Longitudine
fieldname.altitude=Altitudine
+fieldname.timestamp=Timp
fieldname.time=Timp
+fieldname.date=Data
fieldname.waypointname=Nume
fieldname.waypointtype=Tip
fieldname.newsegment=Segment
units.kilometresperhour.short=km/o
units.miles=Mil\u0103
units.miles.short=mi
+units.milesperhour=mil\u0103 pe or\u0103
+units.milesperhour.short=mpo
units.nauticalmiles=Mil\u0103 marin\u0103
units.nauticalmiles.short=mm
units.nauticalmilesperhour.short=kn
+units.metrespersec=metri pe secund
+units.metrespersec.short=m/s
units.hours=ore
units.minutes=minute
units.seconds=secunde
+units.degminsec=Grad-min-sec
+units.degmin=Grad-min
+units.deg=Grad
+units.iso8601=ISO 8601
+units.degreescelsius=Celsius
+units.degreescelsius.short=\u00b0C
+units.degreesfahrenheit=Fahrenheit
+units.degreesfahrenheit.short=\u00b0F
# How to combine conditions, such as filters
-logic.and=\ufeff\u0219i
+logic.and=\u0219i
logic.or=sau
+# External urls
+wikipedia.lang=ro
+openweathermap.lang=ro
+
# Cardinals for 3d plots
cardinal.n=N
cardinal.s=S
cardinal.e=E
cardinal.w=V
-wikipedia.lang=ro
-openweathermap.lang=ro
+# Undo operations
+undo.load=\u00cencarc\u0103 date
+undo.loadphotos=\u00cencarc\u0103 fotografii
+undo.loadaudios=\u00cencarc\u0103 audio
+undo.editpoint=Editare punct
+undo.deletepoint=\u015eterge punct
+undo.removephoto=Elimina foto
+undo.removeaudio=Elimina audio
+undo.deleterange=\u015eterge interval
+undo.deletemarked=\u015eterge puncte
+undo.connect=conecteaza
+undo.disconnect=deconecteaza
+undo.rotatephoto=roti foto
+
+# Error messages
+error.function.notavailable.title=Func\u021bie indisponibil\u0103
\ No newline at end of file
menu.track.clearundo=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439
menu.track.markrectangle=\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u0442\u043e\u0447\u043a\u0438 \u0432 \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0435
menu.track.deletemarked=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043e\u0442\u043c\u0435\u0447\u0435\u043d\u043d\u044b\u0435 \u0442\u043e\u0447\u043a\u0438
-menu.track.rearrange=\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044b
-menu.track.rearrange.start=\u0412\u0441\u0435 \u0432 \u043d\u0430\u0447\u0430\u043b\u043e \u0444\u0430\u0439\u043b\u0430
-menu.track.rearrange.end=\u0412\u0441\u0435 \u0432 \u043a\u043e\u043d\u0435\u0446 \u0444\u0430\u0439\u043b\u0430
-menu.track.rearrange.nearest=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043a \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0439
+function.rearrangewaypoints=\u041f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u044b
menu.range=\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b
menu.range.all=\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435
menu.range.none=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u044b\u0431\u043e\u0440\u043a\u0443
dialog.correlate.select.audioname=\u0418\u043c\u044f \u0437\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438
dialog.correlate.select.audiolater=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u044c \u043f\u043e\u0437\u0434\u043d\u0435\u0435
dialog.rearrangephotos.desc=\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438 \u0444\u043e\u0442\u043e
-dialog.rearrangephotos.tostart=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e
-dialog.rearrangephotos.toend=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0435\u0446
-dialog.rearrangephotos.nosort=\u041d\u0435 \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
-dialog.rearrangephotos.sortbyfilename=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430
-dialog.rearrangephotos.sortbytime=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
+dialog.rearrange.tostart=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043d\u0430\u0447\u0430\u043b\u043e
+dialog.rearrange.toend=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u0432 \u043a\u043e\u043d\u0435\u0446
+dialog.rearrange.tonearest=\u041f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043a \u0431\u043b\u0438\u0436\u0430\u0439\u0448\u0435\u0439
+dialog.rearrange.nosort=\u041d\u0435 \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
+dialog.rearrange.sortbyfilename=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430
+dialog.rearrange.sortbytime=\u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438
dialog.compress.closepoints.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0441\u0431\u043b\u0438\u0436\u0435\u043d\u043d\u044b\u0445 \u0442\u043e\u0447\u0435\u043a
dialog.compress.closepoints.paramdesc=\u0420\u0430\u0437\u043c\u0430\u0445
dialog.compress.wackypoints.title=\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 "\u0448\u0430\u043b\u044c\u043d\u044b\u0445"(\u043d\u0435\u043e\u0431\u044b\u0447\u043d\u044b\u0445) \u0442\u043e\u0447\u0435\u043a
error.load.othererror=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0447\u0442\u0435\u043d\u0438\u0438 \u0444\u0430\u0439\u043b\u0430:
error.jpegload.dialogtitle=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0444\u043e\u0442\u043e
error.jpegload.nofilesfound=\u0424\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
-error.jpegload.nojpegsfound=JEPG-\u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
+error.jpegload.nojpegsfound=JPEG-\u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
error.jpegload.nogpsfound=\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 GPS-\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f
error.jpegload.exifreadfailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e. \u041d\u0435\u0442 Exif-\u200b\u200b\u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u200b\u200b\u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c\n\u0431\u0435\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0445 \u0438\u043b\u0438 \u0432\u043d\u0435\u0448\u043d\u0438\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a.
error.audioload.nofilesfound=\u0417\u0432\u0443\u043a\u043e\u0437\u0430\u043f\u0438\u0441\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b
error.cache.notthere=\u041f\u0430\u043f\u043a\u0430 \u043a\u044d\u0448\u0430 \u0441 \u0442\u0430\u0439\u043b\u0430\u043c\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430
error.cache.empty=\u041f\u0430\u043f\u043a\u0430 \u043a\u044d\u0448\u0430 \u0441 \u0442\u0430\u0439\u043b\u0430\u043c\u0438 \u043f\u0443\u0441\u0442\u0430
error.cache.cannotdelete=\u041d\u0435\u0442 \u0442\u0430\u0439\u043b\u043e\u0432, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f
-error.interpolate.invalidparameter=\u041d\u043e\u043c\u0435\u0440 \u0442\u043e\u0447\u043a\u0438 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u043e\u0442 1 \u0434\u043e 1000
error.tracksplit.nosplit=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u0442\u0440\u0435\u043a
menu.track.clearundo=Rensa \u00e5ngra-historik
menu.track.markrectangle=Markera punkter i rektangel
menu.track.deletemarked=Radera markerade punkter
-menu.track.rearrange=Ordna waypoints
-menu.track.rearrange.start=Alla till b\u00f6rjan av fil
-menu.track.rearrange.end=Alla till slut av fil
-menu.track.rearrange.nearest=Varje till n\u00e4rmaste sp\u00e5rpunkt
+function.rearrangewaypoints=Ordna waypoints
+dialog.rearrange.tostart=Alla till b\u00f6rjan av fil
+dialog.rearrange.toend=Alla till slut av fil
+dialog.rearrange.tonearest=Varje till n\u00e4rmaste sp\u00e5rpunkt
menu.range=Intervall
menu.range.all=V\u00e4lj alla
menu.range.none=V\u00e4lj ingen
menu.range.average=Se\u00e7me ortala
menu.range.reverse=S\u0131ra tersine \u00e7evir
menu.range.mergetracksegments=\u0130z par\u00e7alar\u0131 birle\u015ftir
-menu.track.rearrange=Yol noktalar\u0131 yeniden diz
-menu.track.rearrange.start=Hepsini dosyan\u0131n ba\u015f\u0131na
-menu.track.rearrange.end=Hepsini dosyan\u0131n sonuna
-menu.track.rearrange.nearest=En yak\u0131n iz noktaya
+function.rearrangewaypoints=Yol noktalar\u0131 yeniden diz
+dialog.rearrange.tostart=Hepsini dosyan\u0131n ba\u015f\u0131na
+dialog.rearrange.toend=Hepsini dosyan\u0131n sonuna
+dialog.rearrange.tonearest=En yak\u0131n iz noktaya
menu.range.cutandmove=Se\u00e7me kes ve ta\u015f\u0131
menu.range=S\u0131ra
menu.point=Nokta
menu.track.clearundo=\u041e\u0447\u0438\u0441\u0442\u0438\u0442\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0437\u043c\u0456\u043d
menu.track.markrectangle=\u041f\u043e\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0442\u043e\u0447\u043a\u0438 \u0443 \u043f\u0440\u044f\u043c\u043e\u043a\u0443\u0442\u043d\u0438\u043a\u0443
menu.track.deletemarked=\u0412\u0438\u043b\u0443\u0447\u0438\u0442\u0438 \u043f\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0456 \u0442\u043e\u0447\u043a\u0438
-menu.track.rearrange=\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0438
-menu.track.rearrange.start=\u0423\u0441\u0435 \u043d\u0430 \u043f\u043e\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0443
-menu.track.rearrange.end=\u0423\u0441\u0435 \u043d\u0430 \u043a\u0456\u043d\u0435\u0446\u044c \u0444\u0430\u0439\u043b\u0443
-menu.track.rearrange.nearest=\u041f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0434\u043e \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457
+function.rearrangewaypoints=\u041f\u0435\u0440\u0435\u0432\u0438\u0437\u043d\u0430\u0447\u0438\u0442\u0438 \u0456\u043d\u0442\u0435\u0440\u0432\u0430\u043b\u0438
+dialog.rearrange.tostart=\u0423\u0441\u0435 \u043d\u0430 \u043f\u043e\u0447\u0430\u0442\u043e\u043a \u0444\u0430\u0439\u043b\u0443
+dialog.rearrange.toend=\u0423\u0441\u0435 \u043d\u0430 \u043a\u0456\u043d\u0435\u0446\u044c \u0444\u0430\u0439\u043b\u0443
+dialog.rearrange.tonearest=\u041f\u0435\u0440\u0435\u043c\u0456\u0441\u0442\u0438\u0442\u0438 \u0434\u043e \u043d\u0430\u0439\u0431\u043b\u0438\u0436\u0447\u043e\u0457
menu.range=\u0406\u043d\u0442\u0435\u0440\u0432\u0430\u043b
menu.range.all=\u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0443\u0441\u0456
menu.range.none=\u0421\u043a\u0430\u0441\u0443\u0432\u0430\u0442\u0438 \u0432\u0438\u0431\u0456\u0440\u043a\u0443
menu.track.clearundo=\u6e05\u9664\u64a4\u9500\u6e05\u5355
menu.track.markrectangle=\u6807\u8bb0\u9009\u53d6\u533a\u57df\u5185\u7684\u70b9
menu.track.deletemarked=\u5220\u9664\u5df2\u6807\u8bb0\u8f68\u8ff9\u70b9
-menu.track.rearrange=\u91cd\u65b0\u6392\u5217\u822a\u70b9
-menu.track.rearrange.start=\u81f3\u8d77\u59cb\u4f4d\u7f6e
-menu.track.rearrange.end=\u81f3\u672b\u4f4d\u7f6e
-menu.track.rearrange.nearest=\u81f3\u6700\u8fd1\u8f68\u8ff9\u70b9
menu.range=\u822a\u6bb5
menu.range.all=\u5168\u9009
menu.range.none=\u64a4\u9500\u9009\u62e9
function.deleterange=\u5220\u9664\u8f68\u8ff9\u70b9\u6bb5
function.croptrack=\u4fee\u526a\u8f68\u8ff9
function.interpolate=\u91cd\u53e0\u8f68\u8ff9\u70b9
+function.deletebydate=\u6839\u636e\u65e5\u671f\u5220\u9664\u8f68\u8ff9\u70b9
function.addtimeoffset=\u52a0\u5165\u65f6\u95f4\u5dee
function.addaltitudeoffset=\u52a0\u5165\u9ad8\u5ea6\u504f\u79fb
+function.rearrangewaypoints=\u91cd\u65b0\u6392\u5217\u822a\u70b9
function.convertnamestotimes=\u822a\u70b9\u540d\u79f0\u8f6c\u4e3a\u65f6\u95f4
function.deletefieldvalues=\u5220\u9664\u5b57\u6bb5\u503c
function.findwaypoint=\u67e5\u627e\u822a\u70b9
function.learnestimationparams=\u4f7f\u7528\u5f53\u524d\u8f68\u8ff9\u53c2\u6570\u4f30\u8ba1\u65f6\u95f4
function.setmapbg=\u80cc\u666f\u5730\u56fe
function.setpaths=\u8bbe\u7f6e\u7a0b\u5e8f\u8def\u5f84
+function.selectsegment=\u9009\u4e2d\u5f53\u524d\u8f68\u8ff9\u6bb5
function.splitsegments=\u5206\u5272\u8f68\u8ff9
function.sewsegments=\u63a5\u5408\u8f68\u8ff9\u7247\u6bb5
function.getgpsies=\u83b7\u53d6Gpsies\u8f68\u8ff9
function.diskcache=\u4fdd\u5b58\u5730\u56fe
function.managetilecache=\u7ba1\u7406\u5730\u56fe\u533a\u57df\u6570\u636e\u7f13\u5b58
function.getweatherforecast=\u83b7\u53d6\u5929\u6c14\u9884\u62a5
+function.setaltitudetolerance=\u8bbe\u7f6e\u9ad8\u5ea6\u516c\u5dee
# Dialogs
dialog.exit.confirm.title=\u9000\u51fa
dialog.gpsies.activity.skating=\u6ed1\u51b0
dialog.wikipedia.column.name=\u6587\u7ae0\u9898\u76ee
dialog.wikipedia.column.distance=\u8ddd\u79bb
+dialog.wikipedia.nonefound=\u672a\u627e\u5230\u7ef4\u57fa\u767e\u79d1\u6761\u76ee
dialog.correlate.notimestamps=\u6570\u636e\u70b9\u4e2d\u65e0\u65f6\u95f4\u4fe1\u606f\uff0c\u7167\u7247\u65e0\u6cd5\u94fe\u63a5
dialog.correlate.nouncorrelatedphotos=\u6240\u6709\u7167\u7247\u5df2\u94fe\u63a5\uff0c\u7ee7\u7eed\uff1f
dialog.correlate.nouncorrelatedaudios=\u6240\u6709\u97f3\u9891\u5df2\u94fe\u63a5\uff0c\u7ee7\u7eed\uff1f
dialog.correlate.audioselect.intro=\u9009\u62e9\u4ee5\u4e0b\u58f0\u97f3\u6587\u4ef6\u4f5c\u4e3a\u65f6\u95f4\u504f\u5dee
dialog.correlate.select.audioname=\u58f0\u97f3\u6587\u4ef6\u540d\u5b57
dialog.correlate.select.audiolater=\u58f0\u97f3\u5ef6\u8fdf
+dialog.rearrangewaypoints.desc=\u9009\u62e9\u76ee\u7684\u5730\u5e76\u6392\u5217\u8def\u70b9
dialog.rearrangephotos.desc=\u9009\u62e9\u76ee\u7684\u5730\u53ca\u7167\u7247\u70b9\u6392\u5217\u987a\u5e8f
-dialog.rearrangephotos.tostart=\u79fb\u5230\u5f00\u59cb
-dialog.rearrangephotos.toend=\u79fb\u5230\u672b\u5c3e
-dialog.rearrangephotos.nosort=\u4e0d\u6392\u5e8f
-dialog.rearrangephotos.sortbyfilename=\u6309\u540d\u79f0\u6392\u5e8f
-dialog.rearrangephotos.sortbytime=\u6309\u65f6\u95f4\u6392\u5e8f
+dialog.rearrange.tostart=\u79fb\u5230\u5f00\u59cb
+dialog.rearrange.toend=\u79fb\u5230\u672b\u5c3e
+dialog.rearrange.tonearest=\u81f3\u6700\u8fd1\u8f68\u8ff9\u70b9
+dialog.rearrange.nosort=\u4e0d\u6392\u5e8f
+dialog.rearrange.sortbyfilename=\u6309\u6587\u4ef6\u540d\u6392\u5e8f
+dialog.rearrange.sortbyname=\u6309\u8def\u70b9\u540d\u6392\u5e8f
+dialog.rearrange.sortbytime=\u6309\u65f6\u95f4\u6392\u5e8f
dialog.compress.closepoints.title=\u90bb\u8fd1\u70b9\u5220\u9664
dialog.compress.closepoints.paramdesc=\u8303\u56f4\u7cfb\u6570
dialog.compress.wackypoints.title=\u5f02\u5e38\u70b9\u5220\u9664
dialog.colourchooser.red=\u7ea2
dialog.colourchooser.green=\u7eff
dialog.colourchooser.blue=\u84dd
+dialog.colourer.intro=\u53ef\u4ee5\u8d4b\u4e88\u8f68\u8ff9\u70b9\u4e0d\u540c\u7684\u989c\u8272
+dialog.colourer.type=\u7740\u8272\u6a21\u5f0f
+dialog.colourer.type.none=\u65e0
+dialog.colourer.type.byfile=\u6309\u6587\u4ef6
+dialog.colourer.type.bysegment=\u6309\u8f68\u8ff9\u6bb5
+dialog.colourer.type.byaltitude=\u6309\u9ad8\u5ea6
+dialog.colourer.type.byspeed=\u6309\u901f\u5ea6
+dialog.colourer.type.byvertspeed=\u6309\u5782\u76f4\u901f\u5ea6
+dialog.colourer.type.bygradient=\u6309\u5761\u5ea6
+dialog.colourer.type.bydate=\u6309\u65e5\u671f
+dialog.colourer.start=\u8d77\u59cb\u989c\u8272
+dialog.colourer.end=\u7ed3\u675f\u989c\u8272
+dialog.colourer.maxcolours=\u6700\u5927\u989c\u8272\u6570\u91cf
dialog.setlanguage.firstintro=\u4f60\u53ef\u4ee5\u9009\u62e9\u5df2\u6709\u8bed\u8a00,<p>\u6216\u9009\u62e9\u5916\u6302\u8bed\u8a00\u5305
dialog.setlanguage.secondintro=\u8bf7\u4fdd\u5b58\u8bbe\u7f6e<p>\u5e76\u91cd\u542fGpsPrune\u4f7f\u8bbe\u7f6e\u751f\u6548
dialog.setlanguage.language=\u8bed\u8a00
dialog.weather.day.friday=\u5468\u4e94
dialog.weather.day.saturday=\u5468\u516d
dialog.weather.day.sunday=\u5468\u65e5
+dialog.weather.wind=\u98ce\u529b
+dialog.weather.temp=\u6e29\u5ea6
+dialog.weather.humidity=\u6e7f\u5ea6
dialog.weather.creditnotice=\u5929\u6c14\u4fe1\u606f\u83b7\u53d6\u81eaopenweathermap.org\uff0c\u83b7\u53d6\u66f4\u591a\u5929\u6c14\u8be6\u60c5\u8bf7\u8bbf\u95ee\u7f51\u7ad9\u3002
+dialog.deletebydate.onlyonedate=\u6240\u6709\u8f68\u8ff9\u70b9\u90fd\u662f\u540c\u4e00\u5929\u7684
+dialog.deletebydate.intro=\u4f60\u53ef\u4ee5\u9009\u62e9\u4fdd\u7559\u6216\u5220\u9664\u67d0\u4e00\u5929\u7684\u8f68\u8ff9\u70b9
+dialog.deletebydate.nodate=\u6ca1\u6709\u65f6\u95f4\u6233
+dialog.deletebydate.column.keep=\u4fdd\u7559
+dialog.deletebydate.column.delete=\u5220\u9664
+dialog.setaltitudetolerance.text.metres=\u4e0d\u8d85\u8fc7\u6b64\u6570\u503c(\u7c73)\u7684\u9ad8\u5ea6\u53d8\u5316\u5c06\u88ab\u5ffd\u7565
+dialog.setaltitudetolerance.text.feet=
# 3d window
dialog.3d.title=GpsPrune 3D \u663e\u793a
fieldname.altitude=\u9ad8\u5ea6
fieldname.timestamp=\u65f6\u95f4
fieldname.time=\u65f6\u95f4
+fieldname.date=\u65e5\u671f
fieldname.waypointname=\u540d\u79f0
fieldname.waypointtype=\u7c7b\u578b
fieldname.newsegment=\u6bb5
error.cache.notthere=\u672a\u627e\u5230\u533a\u57df\u6570\u636e\u7f13\u5b58\u6587\u4ef6\u5939
error.cache.empty=\u533a\u57df\u6570\u636e\u6587\u4ef6\u5939\u7a7a
error.cache.cannotdelete=\u65e0\u53ef\u5220\u9664\u533a\u57df\u6570\u636e
-error.interpolate.invalidparameter=\u8f93\u5165\u70b9\u6570\u91cf\u5fc5\u987b\u57281\u52301000\u4e4b\u95f4
error.learnestimationparams.failed=\u65e0\u6cd5\u4ece\u6b64\u8f68\u8ff9\u5f97\u5230\u53c2\u6570\n \u5c1d\u8bd5\u8f7d\u5165\u66f4\u591a\u8f68\u8ff9
error.tracksplit.nosplit=\u6b64\u8f68\u8ff9\u65e0\u6cd5\u5206\u5272
error.downloadsrtm.nocache=\u6587\u4ef6\u65e0\u6cd5\u4fdd\u5b58\n\u8bf7\u68c0\u67e5\u78c1\u76d8\u7f13\u5b58
// Apply timestamp to photo and its point (if any)
photo.setTimestamp(timestamp);
if (photo.getDataPoint() != null) {
- photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.FORMAT_ISO_8601), false);
+ photo.getDataPoint().setFieldValue(Field.TIMESTAMP, timestamp.getText(Timestamp.Format.ISO8601), false);
}
return photo;
}
-GpsPrune version 16.3
-=====================
+GpsPrune version 17
+===================
GpsPrune is an application for viewing, editing and managing coordinate data from GPS systems,
including format conversion, charting and photo correlation.
-Full details can be found at http://activityworkshop.net/software/gpsprune/
+Full details can be found at http://gpsprune.activityworkshop.net/
GpsPrune is copyright 2006-2014 activityworkshop.net and distributed under the terms of the Gnu GPL version 2.
You may freely use the software, and may help others to freely use it too. For further information
=======
To run GpsPrune from the jar file, simply call it from a command prompt or shell:
- java -jar gpsprune_16.3.jar
+ java -jar gpsprune_17.jar
If the jar file is saved in a different directory, you will need to include the path.
Depending on your system settings, you may be able to click or double-click on the jar file
or other link can of course be made should you wish.
To specify a language other than the default, use an additional parameter, eg:
- java -jar gpsprune_16.3.jar --lang=DE
-
-
-New with version 16.3
-=====================
-The following fixes were added since version 16.2:
- - Fix for gpx caching of points which failed to load
- - Additional newlines / tabs in gpx export
- - API key for openweathermap.org
- - Improvements to 3d terrain reflections
- - Additional translations
-
-New with version 16.2
-=====================
-The following fixes were added since version 16.1:
- - Fix for Gpx-slicing UTF8 files
- - Conversion of sunrise/sunset times to local timezone
- - Removal of Cloudmade maps
- - Additional translations
+ java -jar gpsprune_17.jar --lang=DE
-New with version 16.1
-=====================
-The following fixes were added since version 16:
- - Caching of terrain information for three-dimensional views
- - Additional translations
- - Improved void filling by interpolation
- - Remembering file type of imported files
+New with version 17
+===================
+The following features were added since version 16:
+ - Colouring the track points according to various criteria (such as altitude,
+ speed, segment, file) in both the regular map view and the image export
+ - Marking points for deletion according to their date
+ - Select the current segment
+ - Adding an altitude tolerance to the climb and descent calculations
+ - Sorting waypoints by name or by timestamp
+
New with version 16
===================
The following features were added since version 15:
- Function to download and save SRTM tiles
New with version 15
-=====================
+===================
The following features were added since version 14:
- Extend povray output using map image on base plane
- Export an image of the map and track at a selected zoom level
- - Estimation of hiking times and learining of parameter values
+ - Estimation of hiking times and learning of parameter values
- Allow altitude / speed profile to show any arbitrary field
- Accept files dragged and dropped onto the GpsPrune window
- Take account of timezone if present in track timestamps
- Allow loading of speeds and vertical speeds from text files
New with version 14
-=====================
+===================
The following features were added since version 13:
- Dragging of existing points
- Creation of new points by dragging the halfway point between two points
New with version 11
===================
-
The following features were added since version 10:
- Option to select which of the named tracks to load out of a gpx file or gps
- Function to delete all values of a single field (eg all altitudes, all timestamps)
New with version 10
===================
-
The following features were added since version 9:
- Function to lookup altitudes using SRTM data from the Space Shuttle
- Choice between altitude profile and speed profile in main view
New with version 9
==================
-
The following features were added since version 8:
- Ability to paste coordinates (eg from wikipedia or geocaching sites) to create new points
- Configurable colour settings
New with version 8
==================
-
The following features were added since version 7:
- Loading of NMEA files (with suffix .nmea)
- Loading of nearby tracks from gpsies.com
New with version 7
==================
-
The following features were added since version 6:
- Loading of KMZ files and zipped GPX
- Improved compression functions with four configurable algorithms
New with version 6
==================
-
The following features were added since version 5:
- Map view using OpenStreetMap images is now integrated in the main window, with control for map transparency
- Pov export has new option to use sphere sweeps for better appearance
New with version 5
==================
-
-The following features were added since version 4.1:
+The following features were added since version 4:
- New map window in the View menu, showing points overlaid on OpenStreetMap images
- New function to launch a browser showing the area in either Google Maps or OpenStreetMap
- Handling of track segments, including loading, saving and exporting, and preservation during edits and undos
New with version 4
==================
-
The following features were added since version 3:
- Automatic correlation of photos with points based on timestamps
- Manual disconnection of photos from points
New with version 3
==================
-
The following features were added since version 2:
- Loading of GPX and KML files
- Loading of jpeg photos with or without coordinate data
New with version 2
==================
-
The following features were added since version 1:
- Display of data in 3d view using Java3D library
- Export of 3d model to POV format for rendering by povray
Features of version 1
=====================
-
The following features were included in version 1:
- Loading of text files, display in overhead and profile views
- Display of track details such as distances, speeds
private static final int[] FORMAT_COORDS = {Coordinate.FORMAT_NONE, Coordinate.FORMAT_DEG_MIN_SEC,
Coordinate.FORMAT_DEG_MIN, Coordinate.FORMAT_DEG};
private static final Unit[] UNIT_ALTS = {null, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_FEET};
- private static final int[] FORMAT_TIMES = {Timestamp.FORMAT_ORIGINAL, Timestamp.FORMAT_LOCALE, Timestamp.FORMAT_ISO_8601};
+ private static final Timestamp.Format[] FORMAT_TIMES = {Timestamp.Format.ORIGINAL, Timestamp.Format.LOCALE, Timestamp.Format.ISO8601};
/**
}
}
// Get timestamp format
- int timestampFormat = Timestamp.FORMAT_ORIGINAL;
+ Timestamp.Format timestampFormat = Timestamp.Format.ORIGINAL;
for (int i=0; i<_timestampUnitsRadios.length; i++)
{
if (_timestampUnitsRadios[i].isSelected()) {
* @param inTimestampFormat timestamp format
*/
private void saveField(StringBuffer inBuffer, DataPoint inPoint, Field inField,
- int inCoordFormat, Unit inAltitudeUnit, int inTimestampFormat)
+ int inCoordFormat, Unit inAltitudeUnit, Timestamp.Format inTimestampFormat)
{
// Output field according to type
if (inField == Field.LATITUDE)
{
if (inPoint.hasTimestamp())
{
- if (inTimestampFormat == Timestamp.FORMAT_ORIGINAL) {
- // output original string
- inBuffer.append(inPoint.getTimestamp().getText(Timestamp.FORMAT_ORIGINAL));
- }
- else {
- // format value accordingly
- inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
- }
+ // format value accordingly
+ inBuffer.append(inPoint.getTimestamp().getText(inTimestampFormat));
}
}
else
source = replaceGpxTags(source, "lat=\"", "\"", inPoint.getLatitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
source = replaceGpxTags(source, "lon=\"", "\"", inPoint.getLongitude().output(Coordinate.FORMAT_DECIMAL_FORCE_POINT));
source = replaceGpxTags(source, "<ele>", "</ele>", inPoint.getAltitude().getStringValue(UnitSetLibrary.UNITS_METRES));
- source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
+ source = replaceGpxTags(source, "<time>", "</time>", inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
if (inPoint.isWaypoint())
{
source = replaceGpxTags(source, "<name>", "</name>", inPoint.getWaypointName());
if (inPoint.hasTimestamp() && inTimestamps)
{
inWriter.write("\t\t<time>");
- inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
+ inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
inWriter.write("</time>\n");
}
// write waypoint name after elevation and time
if (inPoint.hasTimestamp() && inTimestamps)
{
inWriter.write("\t\t\t\t<time>");
- inWriter.write(inPoint.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
+ inWriter.write(inPoint.getTimestamp().getText(Timestamp.Format.ISO8601));
inWriter.write("</time>\n");
}
// photo, audio
import tim.prune.gui.BaseImageDefinitionPanel;
import tim.prune.gui.GuiGridLayout;
import tim.prune.gui.WholeNumberField;
+import tim.prune.gui.colour.PointColourer;
import tim.prune.gui.map.MapSource;
import tim.prune.gui.map.MapSourceLibrary;
import tim.prune.gui.map.MapUtils;
final int zoomFactor = 1 << _baseImagePanel.getImageDefinition().getZoom();
Graphics g = inImage.getImage().getGraphics();
// TODO: Set line width, style etc
- g.setColor(Config.getColourScheme().getColour(ColourScheme.IDX_POINT));
+ final PointColourer pointColourer = _app.getPointColourer();
+ final Color defaultPointColour = Config.getColourScheme().getColour(ColourScheme.IDX_POINT);
+ g.setColor(defaultPointColour);
- // Loop over points
+ // Loop to draw all track points
final Track track = _app.getTrackInfo().getTrack();
final int numPoints = track.getNumPoints();
int prevX = 0, prevY = 0;
DataPoint point = track.getPoint(i);
if (!point.isWaypoint())
{
+ // Determine what colour to use to draw the track point
+ if (pointColourer != null)
+ {
+ Color c = pointColourer.getColour(i);
+ g.setColor(c == null ? defaultPointColour : c);
+ }
double x = track.getX(i) - xRange.getMinimum();
double y = track.getY(i) - yRange.getMinimum();
// use zoom level to calculate pixel coords on image
prevX = px; prevY = py;
}
}
- // Draw waypoints
+
+ // Now the waypoints
final Color textColour = Config.getColourScheme().getColour(ColourScheme.IDX_TEXT);
g.setColor(textColour);
- // Loop over points
+ // Loop again to draw waypoints
for (int i=0; i<numPoints; i++)
{
DataPoint point = track.getPoint(i);
import tim.prune.data.Track;
import tim.prune.data.TrackInfo;
import tim.prune.data.UnitSetLibrary;
-import tim.prune.gui.ColourChooser;
-import tim.prune.gui.ColourPatch;
import tim.prune.gui.DialogCloser;
import tim.prune.gui.ImageUtils;
import tim.prune.gui.WholeNumberField;
+import tim.prune.gui.colour.ColourChooser;
+import tim.prune.gui.colour.ColourPatch;
import tim.prune.load.GenericFileFilter;
import tim.prune.save.xml.XmlUtils;
// Add timestamp (if any) to the list
whenList.append("<when>");
if (point.hasTimestamp()) {
- whenList.append(point.getTimestamp().getText(Timestamp.FORMAT_ISO_8601));
+ whenList.append(point.getTimestamp().getText(Timestamp.Format.ISO8601));
}
whenList.append("</when>\n");
// Add coordinates to the list
{
// file saved - store directory in config for later
Config.setConfigString(Config.KEY_TRACK_DIR, povFile.getParentFile().getAbsolutePath());
- // also store exaggeration
+ // also store exaggeration and grid size
Config.setConfigInt(Config.KEY_HEIGHT_EXAGGERATION, (int) (_altFactor * 100));
+ if (_terrainPanel.getUseTerrain() && _terrainPanel.getGridSize() > 20) {
+ Config.setConfigInt(Config.KEY_TERRAIN_GRID_SIZE, _terrainPanel.getGridSize());
+ }
}
else
{
*/
public abstract class UndoDeleteOperation implements UndoOperation
{
+ /** Flag to remember whether the deleted point was at the beginning or end of the selected range */
+ private boolean _isAtBoundaryOfSelectedRange = false;
+
+ /**
+ * @param inAtBoundary true if deleted point was at the beginning or end of the selected range
+ */
+ public void setAtBoundaryOfSelectedRange(boolean inAtBoundary)
+ {
+ _isAtBoundaryOfSelectedRange = inAtBoundary;
+ }
+
/**
* Modify the current point/range selection after the delete operation is undone
* @param inTrackInfo track info object
* @param inStartIndex start index of reinserted range
* @param inEndIndex end index of reinserted range
*/
- protected static void modifySelection(TrackInfo inTrackInfo, int inStartIndex, int inEndIndex)
+ protected void modifySelection(TrackInfo inTrackInfo, int inStartIndex, int inEndIndex)
{
final int numPointsInserted = inEndIndex - inStartIndex + 1;
// See if there is a currently selected point, if so does it need to be modified
// Same for currently selected range
int rangeStart = inTrackInfo.getSelection().getStart();
int rangeEnd = inTrackInfo.getSelection().getEnd();
- if (rangeEnd >= inStartIndex && rangeEnd > rangeStart)
+ // Was the deleted point at the start or end of the selected range?
+ final boolean wasAtStart = numPointsInserted == 1 && inStartIndex == rangeStart && _isAtBoundaryOfSelectedRange;
+ final boolean wasAtEnd = numPointsInserted == 1 && inStartIndex == (rangeEnd+1) && _isAtBoundaryOfSelectedRange;
+ if (rangeEnd >= inStartIndex && rangeEnd > rangeStart || wasAtStart || wasAtEnd)
{
rangeEnd += numPointsInserted;
if (rangeStart >= inStartIndex) {
rangeStart += numPointsInserted;
}
+ // Extend selection if the deleted point was at the start or end
+ if (wasAtStart) {
+ rangeStart--;
+ }
inTrackInfo.getSelection().selectRange(rangeStart, rangeEnd);
}
}