From da0b1f449260a0b4a94318006382a9039726ef3e Mon Sep 17 00:00:00 2001 From: activityworkshop Date: Sat, 14 Feb 2015 14:51:17 +0100 Subject: [PATCH] Version 4, January 2008 --- tim/prune/App.java | 205 +++++- tim/prune/DataSubscriber.java | 1 + tim/prune/GpsPruner.java | 7 +- tim/prune/I18nManager.java | 4 +- .../correlate/OptionsChangedListener.java | 98 +++ tim/prune/correlate/PhotoCorrelator.java | 661 ++++++++++++++++++ .../correlate/PhotoPreviewTableModel.java | 188 +++++ tim/prune/correlate/PhotoPreviewTableRow.java | 70 ++ .../correlate/PhotoSelectionTableModel.java | 96 +++ .../correlate/PhotoSelectionTableRow.java | 41 ++ tim/prune/correlate/PointPair.java | 135 ++++ tim/prune/correlate/TimeIndexPair.java | 45 ++ tim/prune/data/Altitude.java | 25 +- tim/prune/data/AltitudeRange.java | 2 +- tim/prune/data/Coordinate.java | 108 ++- tim/prune/data/DataPoint.java | 48 +- tim/prune/data/Distance.java | 30 +- tim/prune/data/Field.java | 8 + tim/prune/data/FieldList.java | 2 +- tim/prune/data/FieldType.java | 14 +- tim/prune/data/Latitude.java | 19 +- tim/prune/data/Longitude.java | 21 +- tim/prune/data/Photo.java | 18 +- tim/prune/data/Selection.java | 8 +- tim/prune/data/TimeDifference.java | 123 ++++ tim/prune/data/Timestamp.java | 99 ++- tim/prune/data/Track.java | 11 +- tim/prune/data/TrackInfo.java | 32 +- tim/prune/drew/jpeg/ExifReader.java | 69 +- tim/prune/drew/jpeg/JpegData.java | 49 +- tim/prune/drew/jpeg/JpegSegmentData.java | 1 - tim/prune/drew/jpeg/Rational.java | 3 +- tim/prune/edit/EditFieldsTableModel.java | 3 +- tim/prune/gui/AboutScreen.java | 23 +- tim/prune/gui/DetailsDisplay.java | 66 +- tim/prune/gui/MapChart.java | 115 +-- tim/prune/gui/MenuManager.java | 60 +- tim/prune/gui/PhotoThumbnail.java | 53 +- tim/prune/gui/ProfileChart.java | 1 + tim/prune/gui/SelectorDisplay.java | 21 +- tim/prune/gui/UndoManager.java | 6 +- tim/prune/lang/prune-texts.properties | 51 +- tim/prune/lang/prune-texts_de.properties | 51 +- tim/prune/lang/prune-texts_de_CH.properties | 77 +- tim/prune/lang/prune-texts_es.properties | 125 ++-- tim/prune/lang/prune-texts_fr.properties | 153 ++-- tim/prune/lang/prune-texts_pl.properties | 352 ++++++++++ tim/prune/load/DelimiterInfo.java | 17 +- tim/prune/load/FieldGuesser.java | 273 ++++++++ tim/prune/load/FieldSelectionTableModel.java | 51 +- tim/prune/load/FileCacher.java | 3 +- tim/prune/load/FileSplitter.java | 48 +- tim/prune/load/JpegLoader.java | 38 +- tim/prune/load/PhotoMeasurer.java | 60 -- tim/prune/load/PhotoSorter.java | 32 + tim/prune/load/TextFileLoader.java | 28 +- tim/prune/load/xml/GpxHandler.java | 10 +- tim/prune/load/xml/XmlFileLoader.java | 10 +- tim/prune/readme.txt | 33 +- tim/prune/save/ExifSaver.java | 1 + tim/prune/save/FieldInfo.java | 2 + tim/prune/save/FieldSelectionTableModel.java | 5 +- tim/prune/save/FileSaver.java | 123 +++- tim/prune/save/GpxExporter.java | 361 ++++++++++ tim/prune/save/KmlExporter.java | 84 ++- tim/prune/save/PhotoTableModel.java | 3 +- tim/prune/save/PovExporter.java | 2 +- tim/prune/save/UpDownToggler.java | 16 +- tim/prune/threedee/ThreeDWindow.java | 1 + tim/prune/undo/UndoConnectPhoto.java | 2 + tim/prune/undo/UndoCorrelatePhotos.java | 72 ++ tim/prune/undo/UndoDeleteRange.java | 3 +- tim/prune/undo/UndoDisconnectPhoto.java | 61 ++ tim/prune/undo/UndoLoad.java | 2 +- tim/prune/undo/UndoOperation.java | 1 + 75 files changed, 4189 insertions(+), 551 deletions(-) create mode 100644 tim/prune/correlate/OptionsChangedListener.java create mode 100644 tim/prune/correlate/PhotoCorrelator.java create mode 100644 tim/prune/correlate/PhotoPreviewTableModel.java create mode 100644 tim/prune/correlate/PhotoPreviewTableRow.java create mode 100644 tim/prune/correlate/PhotoSelectionTableModel.java create mode 100644 tim/prune/correlate/PhotoSelectionTableRow.java create mode 100644 tim/prune/correlate/PointPair.java create mode 100644 tim/prune/correlate/TimeIndexPair.java create mode 100644 tim/prune/data/TimeDifference.java create mode 100644 tim/prune/lang/prune-texts_pl.properties create mode 100644 tim/prune/load/FieldGuesser.java delete mode 100644 tim/prune/load/PhotoMeasurer.java create mode 100644 tim/prune/load/PhotoSorter.java create mode 100644 tim/prune/save/GpxExporter.java create mode 100644 tim/prune/undo/UndoCorrelatePhotos.java create mode 100644 tim/prune/undo/UndoDisconnectPhoto.java diff --git a/tim/prune/App.java b/tim/prune/App.java index e403722..18a2e79 100644 --- a/tim/prune/App.java +++ b/tim/prune/App.java @@ -1,12 +1,14 @@ package tim.prune; import java.util.EmptyStackException; -import java.util.List; +import java.util.Set; import java.util.Stack; import javax.swing.JFrame; import javax.swing.JOptionPane; +import tim.prune.correlate.PhotoCorrelator; +import tim.prune.correlate.PointPair; import tim.prune.data.DataPoint; import tim.prune.data.Field; import tim.prune.data.Photo; @@ -20,9 +22,9 @@ import tim.prune.gui.MenuManager; import tim.prune.gui.UndoManager; import tim.prune.load.FileLoader; import tim.prune.load.JpegLoader; -import tim.prune.load.PhotoMeasurer; import tim.prune.save.ExifSaver; import tim.prune.save.FileSaver; +import tim.prune.save.GpxExporter; import tim.prune.save.KmlExporter; import tim.prune.save.PovExporter; import tim.prune.threedee.ThreeDException; @@ -30,10 +32,12 @@ import tim.prune.threedee.ThreeDWindow; import tim.prune.threedee.WindowFactory; import tim.prune.undo.UndoCompress; import tim.prune.undo.UndoConnectPhoto; +import tim.prune.undo.UndoCorrelatePhotos; import tim.prune.undo.UndoDeleteDuplicates; import tim.prune.undo.UndoDeletePhoto; import tim.prune.undo.UndoDeletePoint; import tim.prune.undo.UndoDeleteRange; +import tim.prune.undo.UndoDisconnectPhoto; import tim.prune.undo.UndoEditPoint; import tim.prune.undo.UndoException; import tim.prune.undo.UndoInsert; @@ -57,7 +61,9 @@ public class App private MenuManager _menuManager = null; private FileLoader _fileLoader = null; private JpegLoader _jpegLoader = null; - private KmlExporter _exporter = null; + private FileSaver _fileSaver = null; + private KmlExporter _kmlExporter = null; + private GpxExporter _gpxExporter = null; private PovExporter _povExporter = null; private Stack _undoStack = null; private UpdateMessageBroker _broker = null; @@ -154,8 +160,10 @@ public class App } else { - FileSaver saver = new FileSaver(this, _frame, _track); - saver.showDialog(_fileLoader.getLastUsedDelimiter()); + if (_fileSaver == null) { + _fileSaver = new FileSaver(this, _frame, _track); + } + _fileSaver.showDialog(_fileLoader.getLastUsedDelimiter()); } } @@ -173,11 +181,33 @@ public class App else { // Invoke the export - if (_exporter == null) + if (_kmlExporter == null) { - _exporter = new KmlExporter(_frame, _trackInfo); + _kmlExporter = new KmlExporter(_frame, _trackInfo); } - _exporter.showDialog(); + _kmlExporter.showDialog(); + } + } + + + /** + * Export track data as Gpx + */ + public void exportGpx() + { + if (_track == null) + { + JOptionPane.showMessageDialog(_frame, I18nManager.getText("error.save.nodata"), + I18nManager.getText("error.save.dialogtitle"), JOptionPane.ERROR_MESSAGE); + } + else + { + // Invoke the export + if (_gpxExporter == null) + { + _gpxExporter = new GpxExporter(_frame, _trackInfo); + } + _gpxExporter.showDialog(); } } @@ -208,6 +238,7 @@ public class App * @param inX X component of unit vector * @param inY Y component of unit vector * @param inZ Z component of unit vector + * @param inAltitudeCap altitude cap */ private void exportPov(boolean inDefineSettings, double inX, double inY, double inZ, int inAltitudeCap) { @@ -277,7 +308,8 @@ public class App /** * Complete the point edit - * @param inEditList list of edits + * @param inEditList field values to edit + * @param inUndoList field values before edit */ public void completePointEdit(FieldEditList inEditList, FieldEditList inUndoList) { @@ -570,6 +602,7 @@ public class App /** * Rearrange the waypoints into track order + * @param inFunction nearest point, all to end or all to start */ public void rearrangeWaypoints(int inFunction) { @@ -647,6 +680,8 @@ public class App * Receive loaded data and optionally merge with current Track * @param inFieldArray array of fields * @param inDataArray array of data + * @param inAltFormat altitude format + * @param inFilename filename used */ public void informDataLoaded(Field[] inFieldArray, Object[][] inDataArray, int inAltFormat, String inFilename) { @@ -695,6 +730,7 @@ public class App _undoStack.add(new UndoLoad(_trackInfo, inDataArray.length, photos)); _lastSavePosition = _undoStack.size(); // TODO: Should be possible to reuse the Track object already loaded? + _trackInfo.selectPoint(null); _trackInfo.loadTrack(inFieldArray, inDataArray, inAltFormat); _trackInfo.getFileInfo().setFile(inFilename); if (photos != null) @@ -719,21 +755,19 @@ public class App /** * Accept a list of loaded photos - * @param inPhotoList List of Photo objects + * @param inPhotoSet Set of Photo objects */ - public void informPhotosLoaded(List inPhotoList) + public void informPhotosLoaded(Set inPhotoSet) { - if (inPhotoList != null && !inPhotoList.isEmpty()) + if (inPhotoSet != null && !inPhotoSet.isEmpty()) { - int[] numsAdded = _trackInfo.addPhotos(inPhotoList); + int[] numsAdded = _trackInfo.addPhotos(inPhotoSet); int numPhotosAdded = numsAdded[0]; int numPointsAdded = numsAdded[1]; if (numPhotosAdded > 0) { // Save numbers so load can be undone _undoStack.add(new UndoLoadPhotos(numPhotosAdded, numPointsAdded)); - // Trigger preloading of photo sizes in separate thread - new PhotoMeasurer(_trackInfo.getPhotoList()).measurePhotos(); } if (numPhotosAdded == 1) { @@ -768,7 +802,25 @@ public class App _undoStack.add(new UndoConnectPhoto(point, photo.getFile().getName())); photo.setDataPoint(point); point.setPhoto(photo); - //TODO: Confirm connect (maybe with status in photo panel?) + _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED); + } + } + + + /** + * Disconnect the current photo from its point + */ + public void disconnectPhotoFromPoint() + { + Photo photo = _trackInfo.getCurrentPhoto(); + if (photo != null && photo.getDataPoint() != null) + { + DataPoint point = photo.getDataPoint(); + _undoStack.add(new UndoDisconnectPhoto(point, photo.getFile().getName())); + // disconnect + photo.setDataPoint(null); + point.setPhoto(null); + _broker.informSubscribers(DataSubscriber.SELECTION_CHANGED); } } @@ -817,6 +869,117 @@ public class App } + /** + * Begin the photo correlation process by invoking dialog + */ + public void beginCorrelatePhotos() + { + PhotoCorrelator correlator = new PhotoCorrelator(this, _frame); + // TODO: Do we need to keep a reference to this object to reuse it later? + correlator.begin(); + } + + + /** + * Finish the photo correlation process + * @param inPointPairs array of PointPair objects describing operation + */ + public void finishCorrelatePhotos(PointPair[] inPointPairs) + { + // TODO: This method is too big for App, but where should it go? + if (inPointPairs != null && inPointPairs.length > 0) + { + // begin to construct undo information + UndoCorrelatePhotos undo = new UndoCorrelatePhotos(_trackInfo); + // loop over Photos + int arraySize = inPointPairs.length; + int i = 0, numPhotos = 0; + int numPointsToCreate = 0; + PointPair pair = null; + for (i=0; i 0) + { + // make new array for added points + DataPoint[] addedPoints = new DataPoint[numPointsToCreate]; + int pointNum = 0; + DataPoint pointToAdd = null; + for (i=0; i 0L) + { + // interpolate point + pointToAdd = DataPoint.interpolate(pair.getPointBefore(), pair.getPointAfter(), pair.getFraction()); + } + if (pointToAdd != null) + { + // link photo to point + pointToAdd.setPhoto(pair.getPhoto()); + pair.getPhoto().setDataPoint(pointToAdd); + // add to point array + addedPoints[pointNum] = pointToAdd; + pointNum++; + } + } + } + // expand track + _track.appendPoints(addedPoints); + } + // add undo information to stack + undo.setNumPhotosCorrelated(numPhotos); + _undoStack.add(undo); + // confirm correlation + JOptionPane.showMessageDialog(_frame, "" + numPhotos + " " + + (numPhotos==1?I18nManager.getText("dialog.correlate.confirmsingle.text"):I18nManager.getText("dialog.correlate.confirmmultiple.text")), + I18nManager.getText("dialog.correlate.title"), + JOptionPane.INFORMATION_MESSAGE); + // observers already informed by track update + } + } + + /** * Save the coordinates of photos in their exif data */ @@ -927,4 +1090,14 @@ public class App } return num; } + + /** + * Show a brief help message + */ + public void showHelp() + { + JOptionPane.showMessageDialog(_frame, I18nManager.getText("dialog.help.help"), + I18nManager.getText("menu.help"), + JOptionPane.INFORMATION_MESSAGE); + } } diff --git a/tim/prune/DataSubscriber.java b/tim/prune/DataSubscriber.java index 766ed53..d3fc123 100644 --- a/tim/prune/DataSubscriber.java +++ b/tim/prune/DataSubscriber.java @@ -16,6 +16,7 @@ public interface DataSubscriber /** * Inform clients that data has been updated + * @param inUpdateType type of update */ public void dataUpdated(byte inUpdateType); diff --git a/tim/prune/GpsPruner.java b/tim/prune/GpsPruner.java index 2511cf7..8a95f7f 100644 --- a/tim/prune/GpsPruner.java +++ b/tim/prune/GpsPruner.java @@ -18,12 +18,13 @@ import tim.prune.gui.SelectorDisplay; /** * Tool to visualize, edit and prune GPS data + * Please see the included readme.txt or http://activityworkshop.net */ public class GpsPruner { - // Final release of version 3 - public static final String VERSION_NUMBER = "3"; - public static final String BUILD_NUMBER = "074"; + // Final release of version 4 + public static final String VERSION_NUMBER = "4"; + public static final String BUILD_NUMBER = "089"; private static App APP = null; diff --git a/tim/prune/I18nManager.java b/tim/prune/I18nManager.java index c056fe2..5af9898 100644 --- a/tim/prune/I18nManager.java +++ b/tim/prune/I18nManager.java @@ -19,8 +19,8 @@ public abstract class I18nManager /** - * Initialize the library - * using the (optional) locale + * Initialize the library using the (optional) locale + * @param inLocale locale to use, or null for default */ public static void init(Locale inLocale) { diff --git a/tim/prune/correlate/OptionsChangedListener.java b/tim/prune/correlate/OptionsChangedListener.java new file mode 100644 index 0000000..70420f7 --- /dev/null +++ b/tim/prune/correlate/OptionsChangedListener.java @@ -0,0 +1,98 @@ +package tim.prune.correlate; + +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +/** + * Helper class to listen for changed options on the PhotoCorrelator + * Tightly coupled but only to ok button and preview function + */ +public class OptionsChangedListener implements KeyListener, ActionListener, ItemListener, Runnable +{ + /** Correlator object for callbacks */ + private PhotoCorrelator _correlator; + /** Thread counter */ + private int _threadCount = 0; + + /** Default delay time from change to preview trigger */ + private static final long PREVIEW_DELAY_TIME = 2500L; + + + /** + * Constructor + * @param inCorrelator correlator object for callbacks + */ + public OptionsChangedListener(PhotoCorrelator inCorrelator) + { + _correlator = inCorrelator; + } + + /** + * Respond to actions performed on control + * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) + */ + public void actionPerformed(ActionEvent inEvent) + { + optionsChanged(); + } + + /** + * Run method, called by separate thread(s) + * @see java.lang.Runnable#run() + */ + public void run() + { + // Wait for a certain time + try { + Thread.sleep(PREVIEW_DELAY_TIME); + } + catch (InterruptedException ie) {} + _threadCount--; + if (_threadCount == 0) { + // trigger preview (false means automatic) + _correlator.createPreview(false); + } + } + + /** + * Respond to key pressed event + * @param inEvent event + */ + public void keyPressed(KeyEvent inEvent) + { + optionsChanged(); + } + + /** Ignore key released events */ + public void keyReleased(KeyEvent inEvent) {} + + /** Ignore key typed events */ + public void keyTyped(KeyEvent e) {} + + /** + * Respond to item change events (eg dropdown) + * @see java.awt.event.ItemListener#itemStateChanged(java.awt.event.ItemEvent) + */ + public void itemStateChanged(ItemEvent inEvent) + { + if (inEvent.getStateChange() == ItemEvent.SELECTED) { + optionsChanged(); + } + } + + /** + * Trigger that an option has changed, whatever type + */ + private void optionsChanged() + { + // disable ok button + _correlator.disableOkButton(); + // start new thread to trigger preview + _threadCount++; + new Thread(this).start(); + } +} diff --git a/tim/prune/correlate/PhotoCorrelator.java b/tim/prune/correlate/PhotoCorrelator.java new file mode 100644 index 0000000..2223772 --- /dev/null +++ b/tim/prune/correlate/PhotoCorrelator.java @@ -0,0 +1,661 @@ +package tim.prune.correlate; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Calendar; +import java.util.Iterator; +import java.util.TreeSet; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; + +import tim.prune.App; +import tim.prune.I18nManager; +import tim.prune.data.DataPoint; +import tim.prune.data.Distance; +import tim.prune.data.Field; +import tim.prune.data.Photo; +import tim.prune.data.PhotoList; +import tim.prune.data.TimeDifference; +import tim.prune.data.Timestamp; +import tim.prune.data.Track; +import tim.prune.data.TrackInfo; + +/** + * Class to manage the automatic correlation of photos to points + * including the GUI stuff to control the correlation options + */ +public class PhotoCorrelator +{ + private App _app; + private JFrame _parentFrame; + private JDialog _dialog; + private JButton _nextButton = null, _backButton = null; + private JButton _okButton = null; + private JPanel _cards = null; + private JTable _photoSelectionTable = null; + private JLabel _tipLabel = null; + private JTextField _offsetHourBox = null, _offsetMinBox = null, _offsetSecBox = null; + private JRadioButton _photoLaterOption = null, _pointLaterOption = null; + private JRadioButton _timeLimitRadio = null, _distLimitRadio = null; + private JTextField _limitMinBox = null, _limitSecBox = null; + private JTextField _limitDistBox = null; + private JComboBox _distUnitsDropdown = null; + private JTable _previewTable = null; + private boolean _firstTabAvailable = false; + private boolean _previewEnabled = false; // flag required to enable preview function on second panel + + + /** + * Constructor + * @param inApp App object to report actions to + * @param inFrame parent frame for dialogs + */ + public PhotoCorrelator(App inApp, JFrame inFrame) + { + _app = inApp; + _parentFrame = inFrame; + _dialog = new JDialog(inFrame, I18nManager.getText("dialog.correlate.title"), true); + _dialog.setLocationRelativeTo(inFrame); + _dialog.getContentPane().add(makeDialogContents()); + _dialog.pack(); + } + + + /** + * Reset dialog and show it + */ + public void begin() + { + // Check whether track has timestamps, exit if not + if (!_app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP)) + { + JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.correlate.notimestamps"), + I18nManager.getText("dialog.correlate.title"), JOptionPane.INFORMATION_MESSAGE); + return; + } + // Check for any non-correlated photos, show warning continue/cancel + if (!trackHasUncorrelatedPhotos()) + { + Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")}; + if (JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.correlate.nouncorrelatedphotos"), + I18nManager.getText("dialog.correlate.title"), JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) + == JOptionPane.NO_OPTION) + { + return; + } + } + PhotoSelectionTableModel model = makePhotoSelectionTableModel(_app.getTrackInfo()); + _firstTabAvailable = model != null && model.getRowCount() > 0; + CardLayout cl = (CardLayout) _cards.getLayout(); + if (_firstTabAvailable) + { + cl.first(_cards); + _nextButton.setEnabled(true); + _backButton.setEnabled(false); + _tipLabel.setVisible(false); + _photoSelectionTable.setModel(model); + _previewEnabled = false; + for (int i=0; i 0.0 && correlatePhoto) { + final double angDistPair = DataPoint.calculateRadiansBetween(pair.getPointBefore(), pair.getPointAfter()); + //System.out.println("(dist between pair is " + angDistPair + ") which means " + // + Distance.convertRadiansToDistance(angDistPair, Distance.UNITS_METRES) + "m"); + double frac = pair.getFraction(); + if (frac > 0.5) {frac = 1 - frac;} + final double angDistPhoto = angDistPair * frac; + correlatePhoto = (angDistPhoto < angDistLimit); + } + // Don't select photos which are already correlated to the same point + if (pair.getSecondsBefore() == 0L && pair.getPointBefore().getPhoto() != null + && pair.getPointBefore().getPhoto().equals(photo)) { + correlatePhoto = false; + } + row.setCorrelateFlag(correlatePhoto); + model.addPhotoRow(row); + } + _previewTable.setModel(model); + // Set distance units + model.setDistanceUnits(getSelectedDistanceUnits()); + // Set column widths + _previewTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + final int[] colWidths = {150, 160, 100, 100, 50}; + for (int i=0; i _secondsBefore || _secondsBefore > 0L) + { + // point stamp is nearer to photo + _pointBefore = inPoint; + _secondsBefore = inSeconds; + } + } + // Check if point is closest point after + if (inSeconds >= 0) + { + // point stamp is after photo stamp + if (inSeconds < _secondsAfter || _secondsAfter < 0L) + { + // point stamp is nearer to photo + _pointAfter = inPoint; + _secondsAfter = inSeconds; + } + } + } + + + /** + * @return Photo object + */ + public Photo getPhoto() + { + return _photo; + } + + /** + * @return the closest point before the photo + */ + public DataPoint getPointBefore() + { + return _pointBefore; + } + + /** + * @return number of seconds between photo and subsequent point + */ + public long getSecondsBefore() + { + return _secondsBefore; + } + + /** + * @return the closest point after the photo + */ + public DataPoint getPointAfter() + { + return _pointAfter; + } + + /** + * @return number of seconds between previous point and photo + */ + public long getSecondsAfter() + { + return _secondsAfter; + } + + /** + * @return true if both points found + */ + public boolean isValid() + { + return getPointBefore() != null && getPointAfter() != null; + } + + /** + * @return the fraction of the distance along the interpolated line + */ + public double getFraction() + { + if (_secondsAfter == 0L) return 0.0; + return (-_secondsBefore * 1.0 / (-_secondsBefore + _secondsAfter)); + } + + /** + * @return the number of seconds to the nearest point + */ + public long getMinSeconds() + { + return Math.min(_secondsAfter, -_secondsBefore); + } + + /** + * @return angle from photo to nearest point in radians + */ + public double getMinRadians() + { + double totalRadians = DataPoint.calculateRadiansBetween(_pointBefore, _pointAfter); + double frac = getFraction(); + return totalRadians * Math.min(frac, 1-frac); + } +} diff --git a/tim/prune/correlate/TimeIndexPair.java b/tim/prune/correlate/TimeIndexPair.java new file mode 100644 index 0000000..50305d4 --- /dev/null +++ b/tim/prune/correlate/TimeIndexPair.java @@ -0,0 +1,45 @@ +package tim.prune.correlate; + +/** + * Simple class to hold a time and an index. + * Used in a TreeSet for calculating median time difference + */ +public class TimeIndexPair implements Comparable +{ + /** Time as long */ + private long _time = 0L; + /** Index as int */ + private int _index = 0; + + + /** + * Constructor + * @param inTime time as long + * @param inIndex index as int + */ + public TimeIndexPair(long inTime, int inIndex) + { + _time = inTime; + _index = inIndex; + } + + + /** + * @return the index + */ + public int getIndex() + { + return _index; + } + + + /** + * Compare two TimeIndexPair objects + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Object inOther) + { + TimeIndexPair other = (TimeIndexPair) inOther; + return (int) (_time - other._time); + } +} diff --git a/tim/prune/data/Altitude.java b/tim/prune/data/Altitude.java index 54a3f68..af247cf 100644 --- a/tim/prune/data/Altitude.java +++ b/tim/prune/data/Altitude.java @@ -17,7 +17,9 @@ public class Altitude /** - * Constructor + * Constructor using String + * @param inString string to parse + * @param inFormat format of altitude, either metres or feet */ public Altitude(String inString, int inFormat) { @@ -35,7 +37,9 @@ public class Altitude /** - * Constructor + * Constructor with int vaue + * @param inValue int value of altitude + * @param inFormat format of altitude, either metres or feet */ public Altitude(int inValue, int inFormat) { @@ -99,6 +103,19 @@ public class Altitude * @return Interpolated Altitude object */ public static Altitude interpolate(Altitude inStart, Altitude inEnd, int inIndex, int inNumSteps) + { + return interpolate(inStart, inEnd, 1.0 * (inIndex + 1) / (inNumSteps + 1)); + } + + + /** + * Interpolate a new Altitude object between the given ones + * @param inStart start altitude + * @param inEnd end altitude + * @param inFrac fraction of distance from first point + * @return Interpolated Altitude object + */ + public static Altitude interpolate(Altitude inStart, Altitude inEnd, double inFrac) { // Check if altitudes are valid if (inStart == null || inEnd == null || !inStart.isValid() || !inEnd.isValid()) @@ -107,8 +124,8 @@ public class Altitude int altFormat = inStart.getFormat(); int startValue = inStart.getValue(); int endValue = inEnd.getValue(altFormat); - int newValue = startValue - + (int) ((endValue - startValue) * 1.0 / (inNumSteps + 1) * (inIndex + 1)); + // interpolate between start and end + int newValue = startValue + (int) ((endValue - startValue) * inFrac); return new Altitude(newValue, altFormat); } } diff --git a/tim/prune/data/AltitudeRange.java b/tim/prune/data/AltitudeRange.java index 6c7e5d5..aff974d 100644 --- a/tim/prune/data/AltitudeRange.java +++ b/tim/prune/data/AltitudeRange.java @@ -22,7 +22,7 @@ public class AltitudeRange /** * Add a value to the range - * @param inValue value to add, only positive values considered + * @param inAltitude value to add, only positive values considered */ public void addValue(Altitude inAltitude) { diff --git a/tim/prune/data/Coordinate.java b/tim/prune/data/Coordinate.java index 8a81850..7a57268 100644 --- a/tim/prune/data/Coordinate.java +++ b/tim/prune/data/Coordinate.java @@ -6,6 +6,7 @@ package tim.prune.data; */ public abstract class Coordinate { + public static final int NO_CARDINAL = -1; public static final int NORTH = 0; public static final int EAST = 1; public static final int SOUTH = 2; @@ -27,6 +28,7 @@ public abstract class Coordinate private int _minutes = 0; private int _seconds = 0; private int _fracs = 0; + private int _fracDenom = 0; private String _originalString = null; private int _originalFormat = FORMAT_NONE; private double _asDouble = 0.0; @@ -47,16 +49,18 @@ public abstract class Coordinate } if (strLen > 1) { - // Check for leading character NSEW - _cardinal = getCardinal(inString.charAt(0)); + // Check for cardinal character either at beginning or end + _cardinal = getCardinal(inString.charAt(0), inString.charAt(strLen-1)); // count numeric fields - 1=d, 2=dm, 3=dm.m/dms, 4=dms.s int numFields = 0; boolean inNumeric = false; char currChar; - long[] fields = new long[4]; + long[] fields = new long[4]; // needs to be long for lengthy decimals long[] denoms = new long[4]; + String secondDelim = ""; try { + // Loop over characters in input string, populating fields array for (int i=0; i 0); @@ -86,6 +94,7 @@ public abstract class Coordinate // parse fields according to number found _degrees = (int) fields[0]; _originalFormat = FORMAT_DEG; + _fracDenom = 10; if (numFields == 2) { // String is just decimal degrees @@ -95,7 +104,8 @@ public abstract class Coordinate _seconds = (int) numSecs; _fracs = (int) ((numSecs - _seconds) * 10); } - else if (numFields == 3) + // Differentiate between d-m.f and d-m-s using . or , + else if (numFields == 3 && (secondDelim.equals(".") || secondDelim.equals(","))) { // String is degrees-minutes.fractions _originalFormat = FORMAT_DEG_MIN; @@ -104,28 +114,63 @@ public abstract class Coordinate _seconds = (int) numSecs; _fracs = (int) ((numSecs - _seconds) * 10); } - else if (numFields == 4) + else if (numFields == 4 || numFields == 3) { - _originalFormat = FORMAT_DEG_MIN_SEC; // String is degrees-minutes-seconds.fractions + _originalFormat = FORMAT_DEG_MIN_SEC; _minutes = (int) fields[1]; _seconds = (int) fields[2]; _fracs = (int) fields[3]; + _fracDenom = (int) denoms[3]; + if (_fracDenom < 1) {_fracDenom = 1;} } - _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (_seconds / 3600.0) + (_fracs / 36000.0); + _asDouble = 1.0 * _degrees + (_minutes / 60.0) + (_seconds / 3600.0) + (_fracs / 3600.0 / _fracDenom); if (_cardinal == WEST || _cardinal == SOUTH || inString.charAt(0) == '-') _asDouble = -_asDouble; + // validate fields + _valid = _valid && (_degrees <= getMaxDegrees() && _minutes < 60 && _seconds < 60 && _fracs < _fracDenom); } else _valid = false; } + /** + * Get the cardinal from the given character + * @param inFirstChar first character from file + * @param inLastChar last character from file + */ + protected int getCardinal(char inFirstChar, char inLastChar) + { + // Try leading character first + int cardinal = getCardinal(inFirstChar); + // if not there, try trailing character + if (cardinal == NO_CARDINAL) { + cardinal = getCardinal(inLastChar); + } + // use default from concrete subclass + if (cardinal == NO_CARDINAL) { + cardinal = getDefaultCardinal(); + } + return cardinal; + } + + /** * Get the cardinal from the given character * @param inChar character from file */ protected abstract int getCardinal(char inChar); + /** + * @return the default cardinal for the subclass + */ + protected abstract int getDefaultCardinal(); + + /** + * @return the maximum degree range for this coordinate + */ + protected abstract int getMaxDegrees(); + /** * Constructor @@ -143,6 +188,7 @@ public abstract class Coordinate double numSecs = (numMins - _minutes) * 60.0; _seconds = (int) numSecs; _fracs = (int) ((numSecs - _seconds) * 10); + _fracDenom = 10; // fixed for now // Make a string to display on screen _cardinal = inCardinal; _originalFormat = FORMAT_NONE; @@ -186,7 +232,6 @@ public abstract class Coordinate /** * Output the Coordinate in the given format - * @param inOriginalString the original String to use as default * @param inFormat format to use, eg FORMAT_DEG_MIN_SEC * @return String for output */ @@ -206,32 +251,33 @@ public abstract class Coordinate .append(threeDigitString(_degrees)).append('°') .append(twoDigitString(_minutes)).append('\'') .append(twoDigitString(_seconds)).append('.') - .append(_fracs); + .append(formatFraction(_fracs, _fracDenom)); answer = buffer.toString(); break; } case FORMAT_DEG_MIN: { answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°" - + (_minutes + _seconds / 60.0 + _fracs / 600.0) + "'"; + + (_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom) + "'"; break; } case FORMAT_DEG_WHOLE_MIN: { answer = "" + PRINTABLE_CARDINALS[_cardinal] + threeDigitString(_degrees) + "°" - + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 600.0 + 0.5) + "'"; + + (int) Math.floor(_minutes + _seconds / 60.0 + _fracs / 60.0 / _fracDenom + 0.5) + "'"; break; } case FORMAT_DEG: case FORMAT_DEG_WITHOUT_CARDINAL: { answer = (_asDouble<0.0?"-":"") - + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 36000.0); + + (_degrees + _minutes / 60.0 + _seconds / 3600.0 + _fracs / 3600.0 / _fracDenom); break; } case FORMAT_DEG_MIN_SEC_WITH_SPACES: { - answer = "" + _degrees + " " + _minutes + " " + _seconds + "." + _fracs; + // Note: cardinal not needed as this format is only for exif, which has cardinal separately + answer = "" + _degrees + " " + _minutes + " " + _seconds + "." + formatFraction(_fracs, _fracDenom); break; } case FORMAT_CARDINAL: @@ -244,6 +290,22 @@ public abstract class Coordinate return answer; } + /** + * Format the fraction part of seconds value + * @param inFrac fractional part eg 123 + * @param inDenom denominator of fraction eg 10000 + * @return String describing fraction, in this case 0123 + */ + private static final String formatFraction(int inFrac, int inDenom) + { + if (inDenom <= 1 || inFrac == 0) {return "" + inFrac;} + String denomString = "" + inDenom; + int reqdLen = denomString.length() - 1; + String result = denomString + inFrac; + int resultLen = result.length(); + return result.substring(resultLen - reqdLen); + } + /** * Format an integer to a two-digit String @@ -283,10 +345,24 @@ public abstract class Coordinate */ public static Coordinate interpolate(Coordinate inStart, Coordinate inEnd, int inIndex, int inNumPoints) + { + return interpolate(inStart, inEnd, 1.0 * (inIndex+1) / (inNumPoints + 1)); + } + + + /** + * Create a new Coordinate between two others + * @param inStart start coordinate + * @param inEnd end coordinate + * @param inFraction fraction from start to end + * @return new Coordinate object + */ + public static Coordinate interpolate(Coordinate inStart, Coordinate inEnd, + double inFraction) { double startValue = inStart.getDouble(); double endValue = inEnd.getDouble(); - double newValue = startValue + (endValue - startValue) * (inIndex+1) / (inNumPoints + 1); + double newValue = startValue + (endValue - startValue) * inFraction; Coordinate answer = inStart.makeNew(newValue, inStart._originalFormat); return answer; } @@ -303,9 +379,11 @@ public abstract class Coordinate /** * Create a String representation for debug + * @return String describing coordinate value */ public String toString() { - return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "." + _fracs + ") = " + _asDouble; + return "Coord: " + _cardinal + " (" + _degrees + ") (" + _minutes + ") (" + _seconds + "." + + formatFraction(_fracs, _fracDenom) + ") = " + _asDouble; } } diff --git a/tim/prune/data/DataPoint.java b/tim/prune/data/DataPoint.java index 46c4acd..5e864ce 100644 --- a/tim/prune/data/DataPoint.java +++ b/tim/prune/data/DataPoint.java @@ -138,30 +138,37 @@ public class DataPoint } + /** @return latitude */ public Coordinate getLatitude() { return _latitude; } + /** @return longitude */ public Coordinate getLongitude() { return _longitude; } + /** @return true if point has altitude */ public boolean hasAltitude() { return _altitude.isValid(); } + /** @return altitude */ public Altitude getAltitude() { return _altitude; } + /** @return true if point has timestamp */ public boolean hasTimestamp() { return _timestamp.isValid(); } + /** @return timestamp */ public Timestamp getTimestamp() { return _timestamp; } + /** @return waypoint name, if any */ public String getWaypointName() { return _waypointName; @@ -202,10 +209,7 @@ public class DataPoint { return !inOther.isWaypoint(); } - else - { - return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName)); - } + return (inOther._waypointName != null && inOther._waypointName.equals(_waypointName)); } @@ -246,21 +250,33 @@ public class DataPoint public DataPoint[] interpolate(DataPoint inEndPoint, int inNumPoints) { DataPoint[] range = new DataPoint[inNumPoints]; - Coordinate endLatitude = inEndPoint.getLatitude(); - Coordinate endLongitude = inEndPoint.getLongitude(); - Altitude endAltitude = inEndPoint.getAltitude(); - // Loop over points for (int i=0; i 0) + { + if (started) {buffer.append(", ");} + else {started = true;} + buffer.append(_minutes).append(' ').append(I18nManager.getText("display.range.time.mins")); + } + // seconds + if (_seconds > 0 || !started) + { + if (started) {buffer.append(", ");} + buffer.append(_seconds).append(' ').append(I18nManager.getText("display.range.time.secs")); + } + _description = buffer.toString(); + return _description; + } + +} diff --git a/tim/prune/data/Timestamp.java b/tim/prune/data/Timestamp.java index ddb3808..334bf49 100644 --- a/tim/prune/data/Timestamp.java +++ b/tim/prune/data/Timestamp.java @@ -15,8 +15,11 @@ public class Timestamp private boolean _valid = false; private long _seconds = 0L; private String _text = null; + private String _timeText = null; private static DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateTimeInstance(); + private static DateFormat DEFAULT_TIME_FORMAT = DateFormat.getTimeInstance(); + private static DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); private static DateFormat[] ALL_DATE_FORMATS = null; private static Calendar CALENDAR = null; private static long SECS_SINCE_1970 = 0L; @@ -24,9 +27,15 @@ public class Timestamp private static long MSECS_SINCE_1970 = 0L; private static long MSECS_SINCE_1990 = 0L; 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; + // Static block to initialise offsets static { @@ -43,13 +52,15 @@ public class Timestamp 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("yyyy MMM dd HH:mm:ss") + new SimpleDateFormat("yyyy MMM dd HH:mm:ss"), + ISO_8601_FORMAT }; } /** * Constructor + * @param inString String containing timestamp */ public Timestamp(String inString) { @@ -83,10 +94,10 @@ public class Timestamp _seconds = rawValue / 1000L + TWENTY_YEARS_IN_SECS; smallestDiff = diff3; } - // Lastly, check garmin offset + // Lastly, check gartrip offset if (diff4 < smallestDiff) { - // seconds since garmin offset + // seconds since gartrip offset _seconds = rawValue + GARTRIP_OFFSET; } _valid = true; @@ -137,8 +148,8 @@ public class Timestamp /** - * Constructor giving millis since 1970 - * @param inMillis + * Constructor giving millis + * @param inMillis milliseconds since 1970 */ public Timestamp(long inMillis) { @@ -167,20 +178,88 @@ public class Timestamp } + /** + * Add the given TimeDifference to this Timestamp + * @param inOffset TimeDifference to add + * @return new Timestamp object + */ + public Timestamp addOffset(TimeDifference inOffset) + { + return new Timestamp((_seconds + inOffset.getTotalSeconds()) * 1000L); + } + + + /** + * Subtract the given TimeDifference from this Timestamp + * @param inOffset TimeDifference to subtract + * @return new Timestamp object + */ + public Timestamp subtractOffset(TimeDifference inOffset) + { + return new Timestamp((_seconds - inOffset.getTotalSeconds()) * 1000L); + } + + /** * @return Description of timestamp in locale-specific format */ public String getText() { + return getText(FORMAT_LOCALE); + } + + /** + * @param inFormat format of timestamp + * @return Description of timestamp in required format + */ + public String getText(int inFormat) + { + if (inFormat == FORMAT_ISO_8601) { + return format(ISO_8601_FORMAT); + } if (_text == null) { - if (_valid) - { - CALENDAR.setTimeInMillis(_seconds * 1000L); - _text = DEFAULT_DATE_FORMAT.format(CALENDAR.getTime()); + if (_valid) { + _text = format(DEFAULT_DATE_FORMAT); } else _text = ""; } return _text; } + + /** + * @return Description of time part of timestamp in locale-specific format + */ + public String getTimeText() + { + if (_timeText == null) + { + if (_valid) { + _timeText = format(DEFAULT_TIME_FORMAT); + } + else _timeText = ""; + } + return _timeText; + } + + /** + * Utility method for formatting dates / times + * @param inFormat formatter object + * @return formatted String + */ + private String format(DateFormat inFormat) + { + CALENDAR.setTimeInMillis(_seconds * 1000L); + return inFormat.format(CALENDAR.getTime()); + } + + /** + * @return a Calendar object representing this timestamp + */ + public Calendar getCalendar() + { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(_seconds * 1000L); + return cal; + } } diff --git a/tim/prune/data/Track.java b/tim/prune/data/Track.java index c5a0f16..1188a7c 100644 --- a/tim/prune/data/Track.java +++ b/tim/prune/data/Track.java @@ -32,9 +32,8 @@ public class Track /** - * Constructor giving arrays of Fields and Objects - * @param inFieldArray field array - * @param inPointArray 2d array of field values + * Constructor for empty track + * @param inBroker message broker object */ public Track(UpdateMessageBroker inBroker) { @@ -182,7 +181,6 @@ public class Track System.arraycopy(newPointArray, 0, _dataPoints, 0, numCopied); _numPoints = _dataPoints.length; _scaled = false; - _broker.informSubscribers(); } return numDeleted; } @@ -212,6 +210,7 @@ public class Track /** * Delete the specified point + * @param inIndex point index * @return true if successful */ public boolean deletePoint(int inIndex) @@ -298,7 +297,6 @@ public class Track _dataPoints = newPointArray; _numPoints = _dataPoints.length; _scaled = false; - _broker.informSubscribers(); } return numDupes; } @@ -306,6 +304,8 @@ public class Track /** * Reverse the specified range of points + * @param inStart start index + * @param inEnd end index * @return true if successful, false otherwise */ public boolean reverseRange(int inStart, int inEnd) @@ -848,6 +848,7 @@ public class Track /** * Replace the track contents with the given point array * @param inContents array of DataPoint objects + * @return true on success */ public boolean replaceContents(DataPoint[] inContents) { diff --git a/tim/prune/data/TrackInfo.java b/tim/prune/data/TrackInfo.java index b987ea9..f8e3c84 100644 --- a/tim/prune/data/TrackInfo.java +++ b/tim/prune/data/TrackInfo.java @@ -1,7 +1,7 @@ package tim.prune.data; -import java.util.List; - +import java.util.Iterator; +import java.util.Set; import tim.prune.UpdateMessageBroker; /** @@ -100,23 +100,24 @@ public class TrackInfo /** - * Add a List of Photos - * @param inList List containing Photo objects + * Add a Set of Photos + * @param inSet Set containing Photo objects * @return array containing number of photos and number of points added */ - public int[] addPhotos(List inList) + public int[] addPhotos(Set inSet) { - // TODO: Should photos be sorted at load-time, either by filename or date? // Firstly count number of points and photos to add int numPhotosToAdd = 0; int numPointsToAdd = 0; - if (inList != null && !inList.isEmpty()) + Iterator iterator = null; + if (inSet != null && !inSet.isEmpty()) { - for (int i=0; i 0) + if (numDeleted > 0) { _selection.clearAll(); + _broker.informSubscribers(); + } return numDeleted; } @@ -261,8 +265,10 @@ public class TrackInfo public int deleteDuplicates() { int numDeleted = _track.deleteDuplicates(); - if (numDeleted > 0) + if (numDeleted > 0) { _selection.clearAll(); + _broker.informSubscribers(); + } return numDeleted; } diff --git a/tim/prune/drew/jpeg/ExifReader.java b/tim/prune/drew/jpeg/ExifReader.java index 4e58199..1637a9d 100644 --- a/tim/prune/drew/jpeg/ExifReader.java +++ b/tim/prune/drew/jpeg/ExifReader.java @@ -20,6 +20,11 @@ public class ExifReader */ private boolean _isMotorolaByteOrder; + /** Thumbnail offset */ + private int _thumbnailOffset = -1; + /** Thumbnail length */ + private int _thumbnailLength = -1; + /** * The number of bytes used per format descriptor. */ @@ -71,11 +76,18 @@ public class ExifReader public static final int TAG_GPS_TIMESTAMP = 0x0007; /** GPS date (atomic clock) GPSDateStamp 23 1d RATIONAL 3 */ public static final int TAG_GPS_DATESTAMP = 0x001d; + /** Exif timestamp */ + public static final int TAG_DATETIME_ORIGINAL = 0x9003; + /** Thumbnail offset */ + private static final int TAG_THUMBNAIL_OFFSET = 0x0201; + /** Thumbnail length */ + private static final int TAG_THUMBNAIL_LENGTH = 0x0202; + /** * Creates an ExifReader for a Jpeg file. * @param inFile File object to attempt to read from - * @throws JpegProcessingException on failure + * @throws JpegException on failure */ public ExifReader(File inFile) throws JpegException { @@ -238,11 +250,11 @@ public class ExifReader // Calculate the value as an offset for cases where the tag represents a directory final int subdirOffset = inTiffHeaderOffset + get32Bits(tagValueOffset); - // TODO: Also look for timestamp(s) in Exif for correlation - which directory? + // Look in both basic Exif tags (for timestamp, thumbnail) and Gps tags (for lat, long, altitude, timestamp) switch (tagType) { case TAG_EXIF_OFFSET: - // ignore + processDirectory(inMetadata, false, inDirectoryOffsets, subdirOffset, inTiffHeaderOffset); continue; case TAG_INTEROP_OFFSET: // ignore @@ -255,9 +267,14 @@ public class ExifReader continue; default: // not a known directory, so must just be a normal tag - // ignore if we're not in gps directory if (inIsGPS) + { processGpsTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode); + } + else + { + processExifTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode); + } break; } } @@ -336,16 +353,56 @@ public class ExifReader inMetadata.setAltitude(readRational(inTagValueOffset, inFormatCode, inComponentCount)); break; case TAG_GPS_TIMESTAMP: - inMetadata.setTimestamp(readRationalArray(inTagValueOffset, inFormatCode, inComponentCount)); + inMetadata.setGpsTimestamp(readRationalArray(inTagValueOffset, inFormatCode, inComponentCount)); break; case TAG_GPS_DATESTAMP: - inMetadata.setDatestamp(readRationalArray(inTagValueOffset, inFormatCode, inComponentCount)); + inMetadata.setGpsDatestamp(readRationalArray(inTagValueOffset, inFormatCode, inComponentCount)); break; default: // ignore all other tags } } + /** + * Process a general Exif tag + * @param inMetadata metadata holding extracted values + * @param inTagType tag type (eg latitude) + * @param inTagValueOffset start offset in data array + * @param inComponentCount component count for tag + * @param inFormatCode format code, eg byte + */ + private void processExifTag(JpegData inMetadata, int inTagType, int inTagValueOffset, + int inComponentCount, int inFormatCode) + { + // Only interested in original timestamp, thumbnail offset and thumbnail length + if (inTagType == TAG_DATETIME_ORIGINAL) + { + inMetadata.setOriginalTimestamp(readString(inTagValueOffset, inFormatCode, inComponentCount)); + } + else if (inTagType == TAG_THUMBNAIL_OFFSET) { + _thumbnailOffset = TIFF_HEADER_START_OFFSET + get16Bits(inTagValueOffset); + extractThumbnail(inMetadata); + } + else if (inTagType == TAG_THUMBNAIL_LENGTH) { + _thumbnailLength = get16Bits(inTagValueOffset); + extractThumbnail(inMetadata); + } + } + + /** + * Attempt to extract the thumbnail image + */ + private void extractThumbnail(JpegData inMetadata) + { + if (_thumbnailOffset > 0 && _thumbnailLength > 0 && inMetadata.getThumbnailImage() == null) + { + byte[] thumbnailBytes = new byte[_thumbnailLength]; + System.arraycopy(_data, _thumbnailOffset, thumbnailBytes, 0, _thumbnailLength); + inMetadata.setThumbnailImage(thumbnailBytes); + } + } + + /** * Calculate the tag value offset * @param inByteCount diff --git a/tim/prune/drew/jpeg/JpegData.java b/tim/prune/drew/jpeg/JpegData.java index 51995c9..3d16733 100644 --- a/tim/prune/drew/jpeg/JpegData.java +++ b/tim/prune/drew/jpeg/JpegData.java @@ -16,8 +16,10 @@ public class JpegData private Rational[] _latitude = null; private Rational[] _longitude = null; private Rational _altitude = null; - private Rational[] _timestamp = null; - private Rational[] _datestamp = null; + private Rational[] _gpsTimestamp = null; + private Rational[] _gpsDatestamp = null; + private String _originalTimestamp = null; + private byte[] _thumbnail = null; private ArrayList _errors = null; @@ -113,21 +115,30 @@ public class JpegData } /** - * Set the timestamp + * Set the Gps timestamp * @param inValues array of Rationals holding timestamp */ - public void setTimestamp(Rational[] inValues) + public void setGpsTimestamp(Rational[] inValues) { - _timestamp = inValues; + _gpsTimestamp = inValues; } /** - * Set the datestamp + * Set the Gps datestamp * @param inValues array of Rationals holding datestamp */ - public void setDatestamp(Rational[] inValues) + public void setGpsDatestamp(Rational[] inValues) { - _datestamp = inValues; + _gpsDatestamp = inValues; + } + + /** + * Set the original timestamp + * @param inStamp original timestamp of photo + */ + public void setOriginalTimestamp(String inStamp) + { + _originalTimestamp = inStamp; } /** @return latitude ref as char */ @@ -142,10 +153,24 @@ public class JpegData public byte getAltitudeRef() { return _altitudeRef; } /** @return altitude as Rational */ public Rational getAltitude() { return _altitude; } - /** @return timestamp as array of 3 Rationals */ - public Rational[] getTimestamp() { return _timestamp; } - /** @return timestamp as array of 3 Rationals */ - public Rational[] getDatestamp() { return _datestamp; } + /** @return Gps timestamp as array of 3 Rationals */ + public Rational[] getGpsTimestamp() { return _gpsTimestamp; } + /** @return Gps datestamp as array of 3 Rationals */ + public Rational[] getGpsDatestamp() { return _gpsDatestamp; } + /** @return original timestamp as string */ + public String getOriginalTimestamp() { return _originalTimestamp; } + + /** + * Set the thumbnail + * @param inBytes byte array containing thumbnail + */ + public void setThumbnailImage(byte[] inBytes) { + _thumbnail = inBytes; + } + /** @return thumbnail as byte array */ + public byte[] getThumbnailImage() { + return _thumbnail; + } /** * @return true if data looks valid, ie has at least lat and long diff --git a/tim/prune/drew/jpeg/JpegSegmentData.java b/tim/prune/drew/jpeg/JpegSegmentData.java index 6a0d6a4..23197f8 100644 --- a/tim/prune/drew/jpeg/JpegSegmentData.java +++ b/tim/prune/drew/jpeg/JpegSegmentData.java @@ -30,7 +30,6 @@ public class JpegSegmentData */ public void addSegment(byte inSegmentMarker, byte[] inSegmentBytes) { - // System.out.println("Adding segment: " + inSegmentMarker); List segmentList = getOrCreateSegmentList(inSegmentMarker); segmentList.add(inSegmentBytes); } diff --git a/tim/prune/drew/jpeg/Rational.java b/tim/prune/drew/jpeg/Rational.java index 78bd72b..e522935 100644 --- a/tim/prune/drew/jpeg/Rational.java +++ b/tim/prune/drew/jpeg/Rational.java @@ -62,7 +62,8 @@ public class Rational } /** - * Checks if this rational number is an Integer, either positive or negative. + * Checks if this rational number is an Integer, either positive or negative + * @return true if an integer */ public boolean isInteger() { diff --git a/tim/prune/edit/EditFieldsTableModel.java b/tim/prune/edit/EditFieldsTableModel.java index 575b0aa..fb4a86b 100644 --- a/tim/prune/edit/EditFieldsTableModel.java +++ b/tim/prune/edit/EditFieldsTableModel.java @@ -16,6 +16,7 @@ public class EditFieldsTableModel extends AbstractTableModel /** * Constructor giving list size + * @param inSize number of fields */ public EditFieldsTableModel(int inSize) { @@ -70,7 +71,7 @@ public class EditFieldsTableModel extends AbstractTableModel { return _fieldValues[inRowIndex]; } - return new Boolean(_valueChanged[inRowIndex]); + return Boolean.valueOf(_valueChanged[inRowIndex]); } diff --git a/tim/prune/gui/AboutScreen.java b/tim/prune/gui/AboutScreen.java index 1e4f4f5..605abf6 100644 --- a/tim/prune/gui/AboutScreen.java +++ b/tim/prune/gui/AboutScreen.java @@ -35,6 +35,7 @@ public class AboutScreen extends JDialog /** * Constructor + * @param inParent parent frame */ public AboutScreen(JFrame inParent) { @@ -147,29 +148,35 @@ public class AboutScreen extends JDialog new JLabel("Eclipse"), 1, 2); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "), + new JLabel(I18nManager.getText("dialog.about.credits.translators") + " : "), 0, 3); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel("Open Office, Gpsdrive, Babelfish, Leo"), + new JLabel("Ramon, Miguel, Inés, Piotr"), 1, 3); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel(I18nManager.getText("dialog.about.credits.devtools") + " : "), + new JLabel(I18nManager.getText("dialog.about.credits.translations") + " : "), 0, 4); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel("Mandriva Linux, Sun Java, Eclipse, Svn, Gimp"), + new JLabel("Open Office, Gpsdrive, Babelfish, Leo, Launchpad"), 1, 4); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel(I18nManager.getText("dialog.about.credits.othertools") + " : "), + new JLabel(I18nManager.getText("dialog.about.credits.devtools") + " : "), 0, 5); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel("Garble, Kate, Povray, Inkscape, Google Earth"), + new JLabel("Mandriva Linux, Sun Java, Eclipse, Svn, Gimp"), 1, 5); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel(I18nManager.getText("dialog.about.credits.thanks") + " : "), + new JLabel(I18nManager.getText("dialog.about.credits.othertools") + " : "), 0, 6); addToGridBagPanel(creditsPanel, gridBag, constraints, - new JLabel("Friends and loved ones, for encouragement and support"), + new JLabel("Garble, Kate, Povray, Exiftool, Inkscape, Google Earth"), 1, 6); + addToGridBagPanel(creditsPanel, gridBag, constraints, + new JLabel(I18nManager.getText("dialog.about.credits.thanks") + " : "), + 0, 7); + addToGridBagPanel(creditsPanel, gridBag, constraints, + new JLabel("Friends and loved ones, for encouragement and support"), + 1, 7); tabPane.add(I18nManager.getText("dialog.about.credits"), creditsPanel); // OK button at the bottom diff --git a/tim/prune/gui/DetailsDisplay.java b/tim/prune/gui/DetailsDisplay.java index 1c68626..aab8362 100644 --- a/tim/prune/gui/DetailsDisplay.java +++ b/tim/prune/gui/DetailsDisplay.java @@ -23,6 +23,7 @@ import tim.prune.data.DataPoint; import tim.prune.data.Distance; import tim.prune.data.IntegerRange; import tim.prune.data.Photo; +import tim.prune.data.PhotoStatus; import tim.prune.data.Selection; import tim.prune.data.TrackInfo; @@ -46,9 +47,11 @@ public class DetailsDisplay extends GenericDisplay // Photo details private JLabel _photoLabel = null; private PhotoThumbnail _photoThumbnail = null; + private JLabel _photoConnectedLabel = null; // Units - private JComboBox _unitsDropdown = null; + private JComboBox _coordFormatDropdown = null; + private JComboBox _distUnitsDropdown = null; // Formatter private NumberFormat _distanceFormatter = NumberFormat.getInstance(); @@ -139,6 +142,8 @@ public class DetailsDisplay extends GenericDisplay photoDetailsPanel.add(photoDetailsLabel); _photoLabel = new JLabel(I18nManager.getText("details.nophoto")); photoDetailsPanel.add(_photoLabel); + _photoConnectedLabel = new JLabel(""); + photoDetailsPanel.add(_photoConnectedLabel); _photoThumbnail = new PhotoThumbnail(); _photoThumbnail.setVisible(false); _photoThumbnail.setPreferredSize(new Dimension(100, 100)); @@ -154,28 +159,43 @@ public class DetailsDisplay extends GenericDisplay // add the main panel at the top add(mainPanel, BorderLayout.NORTH); - // Add units selection + // Add format, units selection JPanel lowerPanel = new JPanel(); lowerPanel.setLayout(new BoxLayout(lowerPanel, BoxLayout.Y_AXIS)); + JLabel coordFormatLabel = new JLabel(I18nManager.getText("details.coordformat") + ": "); + coordFormatLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + lowerPanel.add(coordFormatLabel); + String[] coordFormats = {I18nManager.getText("units.original"), I18nManager.getText("units.degminsec"), + I18nManager.getText("units.degmin"), I18nManager.getText("units.deg")}; + _coordFormatDropdown = new JComboBox(coordFormats); + _coordFormatDropdown.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + dataUpdated(DataSubscriber.UNITS_CHANGED); + } + }); + lowerPanel.add(_coordFormatDropdown); + _coordFormatDropdown.setAlignmentX(Component.LEFT_ALIGNMENT); JLabel unitsLabel = new JLabel(I18nManager.getText("details.distanceunits") + ": "); unitsLabel.setAlignmentX(Component.LEFT_ALIGNMENT); lowerPanel.add(unitsLabel); String[] distUnits = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.miles")}; - _unitsDropdown = new JComboBox(distUnits); - _unitsDropdown.addActionListener(new ActionListener() { + _distUnitsDropdown = new JComboBox(distUnits); + _distUnitsDropdown.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataUpdated(DataSubscriber.UNITS_CHANGED); } }); - lowerPanel.add(_unitsDropdown); - _unitsDropdown.setAlignmentX(Component.LEFT_ALIGNMENT); + lowerPanel.add(_distUnitsDropdown); + _distUnitsDropdown.setAlignmentX(Component.LEFT_ALIGNMENT); add(lowerPanel, BorderLayout.SOUTH); } /** * Notification that Track has been updated + * @param inUpdateType byte to specify what has been updated */ public void dataUpdated(byte inUpdateType) { @@ -197,8 +217,8 @@ public class DetailsDisplay extends GenericDisplay _indexLabel.setText(LABEL_POINT_SELECTED1 + (currentPointIndex+1) + " " + I18nManager.getText("details.index.of") + " " + _track.getNumPoints()); - _latLabel.setText(LABEL_POINT_LATITUDE + currentPoint.getLatitude().output(Coordinate.FORMAT_NONE)); - _longLabel.setText(LABEL_POINT_LONGITUDE + currentPoint.getLongitude().output(Coordinate.FORMAT_NONE)); + _latLabel.setText(makeCoordinateLabel(LABEL_POINT_LATITUDE, currentPoint.getLatitude(), _coordFormatDropdown.getSelectedIndex())); + _longLabel.setText(makeCoordinateLabel(LABEL_POINT_LONGITUDE, currentPoint.getLongitude(), _coordFormatDropdown.getSelectedIndex())); _altLabel.setText(LABEL_POINT_ALTITUDE + (currentPoint.hasAltitude()? (currentPoint.getAltitude().getValue() + getAltitudeUnitsLabel(currentPoint.getAltitude().getFormat())): @@ -229,7 +249,7 @@ public class DetailsDisplay extends GenericDisplay _rangeLabel.setText(LABEL_RANGE_SELECTED1 + (selection.getStart()+1) + " " + I18nManager.getText("details.range.to") + " " + (selection.getEnd()+1)); - if (_unitsDropdown.getSelectedIndex() == 0) + if (_distUnitsDropdown.getSelectedIndex() == 0) _distanceLabel.setText(LABEL_RANGE_DISTANCE + buildDistanceString( selection.getDistance(Distance.UNITS_KILOMETRES)) + " " + I18nManager.getText("units.kilometres.short")); @@ -264,12 +284,16 @@ public class DetailsDisplay extends GenericDisplay { // no photo, hide details _photoLabel.setText(I18nManager.getText("details.nophoto")); + _photoConnectedLabel.setText(""); _photoThumbnail.setVisible(false); } else { if (currentPhoto == null) {currentPhoto = currentPoint.getPhoto();} _photoLabel.setText(I18nManager.getText("details.photofile") + ": " + currentPhoto.getFile().getName()); + _photoConnectedLabel.setText(I18nManager.getText("details.photo.connected") + ": " + + (currentPhoto.getCurrentStatus() == PhotoStatus.NOT_CONNECTED ? + I18nManager.getText("dialog.about.no"):I18nManager.getText("dialog.about.yes"))); _photoThumbnail.setVisible(true); _photoThumbnail.setPhoto(currentPhoto); } @@ -293,6 +317,30 @@ public class DetailsDisplay extends GenericDisplay } + /** + * Construct an appropriate coordinate label using the selected format + * @param inPrefix prefix of label + * @param inCoordinate coordinate + * @param inFormat index of format selection dropdown + * @return language-sensitive string + */ + private static String makeCoordinateLabel(String inPrefix, Coordinate inCoordinate, int inFormat) + { + String coord = null; + switch (inFormat) { + case 1: // degminsec + coord = inCoordinate.output(Coordinate.FORMAT_DEG_MIN_SEC); break; + case 2: // degmin + coord = inCoordinate.output(Coordinate.FORMAT_DEG_MIN); break; + case 3: // degrees + coord = inCoordinate.output(Coordinate.FORMAT_DEG); break; + default: // just as it was + coord = inCoordinate.output(Coordinate.FORMAT_NONE); + } + return inPrefix + coord; + } + + /** * Build a String to describe a time duration * @param inNumSecs number of seconds diff --git a/tim/prune/gui/MapChart.java b/tim/prune/gui/MapChart.java index e705f84..a22cb18 100644 --- a/tim/prune/gui/MapChart.java +++ b/tim/prune/gui/MapChart.java @@ -49,6 +49,7 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis private BufferedImage _image = null; private JPopupMenu _popup = null; private JCheckBoxMenuItem _autoPanMenuItem = null; + private JCheckBoxMenuItem _connectPointsMenuItem = null; private int _numPoints = -1; private double _scale; private double _offsetX, _offsetY, _zoomScale; @@ -97,12 +98,13 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis /** * Override paint method to draw map + * @param inG graphics object */ - public void paint(Graphics g) + public void paint(Graphics inG) { if (_track == null) { - super.paint(g); + super.paint(inG); return; } @@ -161,14 +163,14 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis if (_image == null) {return;} // draw buffered image onto g - g.drawImage(_image, 0, 0, width, height, COLOR_BG, null); + inG.drawImage(_image, 0, 0, width, height, COLOR_BG, null); // draw selected range, if any if (_trackInfo.getSelection().hasRangeSelected() && !_zoomDragging) { int rangeStart = _trackInfo.getSelection().getStart(); int rangeEnd = _trackInfo.getSelection().getEnd(); - g.setColor(COLOR_CURR_RANGE); + inG.setColor(COLOR_CURR_RANGE); for (int i=rangeStart; i<=rangeEnd; i++) { x = width/2 + (int) ((_track.getX(i) - _offsetX) / _scale * _zoomScale); @@ -176,7 +178,7 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH) && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH) { - g.drawOval(x - 2, y - 2, 4, 4); + inG.drawRect(x - 2, y - 2, 4, 4); } } } @@ -184,47 +186,51 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis // Highlight selected point if (selectedPoint >= 0 && !_zoomDragging) { - g.setColor(COLOR_CROSSHAIRS); + inG.setColor(COLOR_CROSSHAIRS); x = width/2 + (int) ((_track.getX(selectedPoint) - _offsetX) / _scale * _zoomScale); y = height/2 - (int) ((_track.getY(selectedPoint) - _offsetY) / _scale * _zoomScale); if (x > BORDER_WIDTH && x < (width - BORDER_WIDTH) && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH) { // Draw cross-hairs for current point - g.drawLine(x, BORDER_WIDTH, x, height - BORDER_WIDTH); - g.drawLine(BORDER_WIDTH, y, width - BORDER_WIDTH, y); + inG.drawLine(x, BORDER_WIDTH, x, height - BORDER_WIDTH); + inG.drawLine(BORDER_WIDTH, y, width - BORDER_WIDTH, y); // Show selected point afterwards to make sure it's on top - g.drawOval(x - 2, y - 2, 4, 4); - g.drawOval(x - 3, y - 3, 6, 6); + inG.drawOval(x - 2, y - 2, 4, 4); + inG.drawOval(x - 3, y - 3, 6, 6); } } // Draw rectangle for dragging zoom area if (_zoomDragging) { - g.setColor(COLOR_CROSSHAIRS); - g.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragFromX, _zoomDragToY); - g.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragToX, _zoomDragFromY); - g.drawLine(_zoomDragToX, _zoomDragFromY, _zoomDragToX, _zoomDragToY); - g.drawLine(_zoomDragFromX, _zoomDragToY, _zoomDragToX, _zoomDragToY); + inG.setColor(COLOR_CROSSHAIRS); + inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragFromX, _zoomDragToY); + inG.drawLine(_zoomDragFromX, _zoomDragFromY, _zoomDragToX, _zoomDragFromY); + inG.drawLine(_zoomDragToX, _zoomDragFromY, _zoomDragToX, _zoomDragToY); + inG.drawLine(_zoomDragFromX, _zoomDragToY, _zoomDragToX, _zoomDragToY); } // Attempt to grab keyboard focus if possible - //this.requestFocus(); + //requestFocus(); (causes problems here) } /** - * Draw the map onto an offscreen image + * Plot the points onto an offscreen image + * which doesn't have to be redrawn when the selection changes */ private void createBackgroundImage() { int width = getWidth(); int height = getHeight(); int x, y; - // Make a new image and initialise it - _image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + int lastX = 0, lastY = 0; + // Initialise image + if (_image == null || _image.getWidth() != width || _image.getHeight() != height) { + _image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } Graphics bufferedG = _image.getGraphics(); super.paint(bufferedG); @@ -233,6 +239,7 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis bufferedG.setColor(COLOR_POINT); int halfWidth = width/2; int halfHeight = height/2; + boolean currPointTrackpoint = false, lastPointTrackpoint = false; for (int i=0; i BORDER_WIDTH && x < (width - BORDER_WIDTH) && y < (height - BORDER_WIDTH) && y > BORDER_WIDTH) { - bufferedG.drawOval(x - 2, y - 2, 4, 4); + // draw block for point (a bit faster than circles) + bufferedG.drawRect(x - 2, y - 2, 3, 3); + + // See whether to connect the point with previous one or not + currPointTrackpoint = !_track.getPoint(i).isWaypoint() && _track.getPoint(i).getPhoto() == null; + if (_connectPointsMenuItem.isSelected() && currPointTrackpoint && lastPointTrackpoint) + { + bufferedG.drawLine(lastX, lastY, x, y); + } + lastPointTrackpoint = currPointTrackpoint; + } + else { + lastPointTrackpoint = false; } + lastX = x; lastY = y; } // Loop again and show waypoints with names @@ -268,36 +288,34 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis int nameWidth = fm.stringWidth(waypointName); if (nameWidth < (width - 2 * BORDER_WIDTH)) { - double nameAngle = 0.3; - double nameRadius = 1.0; boolean drawnName = false; - while (!drawnName) + // Make arrays for coordinates right left up down + int[] nameXs = {x + 2, x - nameWidth - 2, x - nameWidth/2, x - nameWidth/2}; + int[] nameYs = {y + (nameHeight/2), y + (nameHeight/2), y - 2, y + nameHeight + 2}; + for (int extraSpace = 4; extraSpace < 13 && !drawnName; extraSpace+=2) { - int nameX = x + (int) (nameRadius * Math.cos(nameAngle)) - (nameWidth/2); - int nameY = y + (int) (nameRadius * Math.sin(nameAngle)) + (nameHeight/2); - if (nameX > BORDER_WIDTH && (nameX + nameWidth) < (width - BORDER_WIDTH) - && nameY < (height - BORDER_WIDTH) && (nameY - nameHeight) > BORDER_WIDTH) + // Shift arrays for coordinates right left up down + nameXs[0] += 2; nameXs[1] -= 2; + nameYs[2] -= 2; nameYs[3] += 2; + // Check each direction in turn right left up down + for (int a=0; a<4; a++) { - // name can fit in grid - does it overlap data points? - if (!overlapsPoints(nameX, nameY, nameWidth, nameHeight) || nameRadius > 50.0) + if (nameXs[a] > BORDER_WIDTH && (nameXs[a] + nameWidth) < (width - BORDER_WIDTH) + && nameYs[a] < (height - BORDER_WIDTH) && (nameYs[a] - nameHeight) > BORDER_WIDTH + && !overlapsPoints(nameXs[a], nameYs[a], nameWidth, nameHeight)) { - bufferedG.drawString(waypointName, nameX, nameY); + // Found a rectangle to fit - draw name here and quit + bufferedG.drawString(waypointName, nameXs[a], nameYs[a]); drawnName = true; - numWaypointNamesShown++; + break; } } - nameAngle += 0.08; - nameRadius += 0.2; - // wasn't room within the radius, so don't print name - if (nameRadius > 50.0) - { - drawnName = true; - } } } } } } + bufferedG.dispose(); } @@ -361,6 +379,16 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis }}); zoomFull.setEnabled(true); _popup.add(zoomFull); + _connectPointsMenuItem = new JCheckBoxMenuItem(I18nManager.getText("menu.map.connect")); + _connectPointsMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + // redraw map + dataUpdated(DataSubscriber.ALL); + } + }); + _connectPointsMenuItem.setSelected(false); + _popup.add(_connectPointsMenuItem); _autoPanMenuItem = new JCheckBoxMenuItem(I18nManager.getText("menu.map.autopan")); _autoPanMenuItem.setSelected(true); _popup.add(_autoPanMenuItem); @@ -421,24 +449,25 @@ public class MapChart extends GenericChart implements MouseWheelListener, KeyLis /** * React to click on map display + * @param inE mouse event */ - public void mouseClicked(MouseEvent e) + public void mouseClicked(MouseEvent inE) { this.requestFocus(); if (_track != null) { - int xClick = e.getX(); - int yClick = e.getY(); + int xClick = inE.getX(); + int yClick = inE.getY(); // Check click is within main area (not in border) if (xClick > BORDER_WIDTH && yClick > BORDER_WIDTH && xClick < (getWidth() - BORDER_WIDTH) && yClick < (getHeight() - BORDER_WIDTH)) { // Check left click or right click - if (e.isMetaDown()) + if (inE.isMetaDown()) { // Only show popup if track has data if (_track != null && _track.getNumPoints() > 0) - _popup.show(this, e.getX(), e.getY()); + _popup.show(this, xClick, yClick); } else { diff --git a/tim/prune/gui/MenuManager.java b/tim/prune/gui/MenuManager.java index 5a4b009..b97bb76 100644 --- a/tim/prune/gui/MenuManager.java +++ b/tim/prune/gui/MenuManager.java @@ -37,6 +37,7 @@ public class MenuManager implements DataSubscriber // Menu items which need enabling/disabling private JMenuItem _saveItem = null; private JMenuItem _exportKmlItem = null; + private JMenuItem _exportGpxItem = null; private JMenuItem _exportPovItem = null; private JMenuItem _undoItem = null; private JMenuItem _clearUndoItem = null; @@ -60,7 +61,8 @@ public class MenuManager implements DataSubscriber private JMenuItem _saveExifItem = null; private JMenuItem _connectPhotoItem = null; private JMenuItem _deletePhotoItem = null; - // TODO: Does Photo menu require disconnect option? + private JMenuItem _disconnectPhotoItem = null; + private JMenuItem _correlatePhotosItem = null; // ActionListeners for reuse by menu and toolbar private ActionListener _openFileAction = null; @@ -85,6 +87,7 @@ public class MenuManager implements DataSubscriber * Constructor * @param inParent parent object for dialogs * @param inApp application to call on menu actions + * @param inTrackInfo track info object */ public MenuManager(JFrame inParent, App inApp, TrackInfo inTrackInfo) { @@ -136,7 +139,7 @@ public class MenuManager implements DataSubscriber _saveItem.addActionListener(_saveAction); _saveItem.setEnabled(false); fileMenu.add(_saveItem); - // Export + // Export - Kml _exportKmlItem = new JMenuItem(I18nManager.getText("menu.file.exportkml")); _exportKmlItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -146,6 +149,17 @@ public class MenuManager implements DataSubscriber }); _exportKmlItem.setEnabled(false); fileMenu.add(_exportKmlItem); + // Gpx + _exportGpxItem = new JMenuItem(I18nManager.getText("menu.file.exportgpx")); + _exportGpxItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.exportGpx(); + } + }); + _exportGpxItem.setEnabled(false); + fileMenu.add(_exportGpxItem); + // Pov _exportPovItem = new JMenuItem(I18nManager.getText("menu.file.exportpov")); _exportPovItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -360,7 +374,18 @@ public class MenuManager implements DataSubscriber }; _connectPhotoItem.addActionListener(_connectPhotoAction); _connectPhotoItem.setEnabled(false); + photoMenu.addSeparator(); photoMenu.add(_connectPhotoItem); + // disconnect photo + _disconnectPhotoItem = new JMenuItem(I18nManager.getText("menu.photo.disconnect")); + _disconnectPhotoItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.disconnectPhotoFromPoint(); + } + }); + _disconnectPhotoItem.setEnabled(false); + photoMenu.add(_disconnectPhotoItem); _deletePhotoItem = new JMenuItem(I18nManager.getText("menu.photo.delete")); _deletePhotoItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -370,6 +395,17 @@ public class MenuManager implements DataSubscriber }); _deletePhotoItem.setEnabled(false); photoMenu.add(_deletePhotoItem); + photoMenu.addSeparator(); + // correlate all photos + _correlatePhotosItem = new JMenuItem(I18nManager.getText("menu.photo.correlate")); + _correlatePhotosItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.beginCorrelatePhotos(); + } + }); + _correlatePhotosItem.setEnabled(false); + photoMenu.add(_correlatePhotosItem); menubar.add(photoMenu); // Add 3d menu (whether java3d available or not) @@ -385,8 +421,16 @@ public class MenuManager implements DataSubscriber threeDMenu.add(_show3dItem); menubar.add(threeDMenu); - // Help menu for About + // Help menu JMenu helpMenu = new JMenu(I18nManager.getText("menu.help")); + JMenuItem helpItem = new JMenuItem(I18nManager.getText("menu.help")); + helpItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _app.showHelp(); + } + }); + helpMenu.add(helpItem); JMenuItem aboutItem = new JMenuItem(I18nManager.getText("menu.help.about")); aboutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -481,6 +525,7 @@ public class MenuManager implements DataSubscriber _saveItem.setEnabled(hasData); _saveButton.setEnabled(hasData); _exportKmlItem.setEnabled(hasData); + _exportGpxItem.setEnabled(hasData); _exportPovItem.setEnabled(hasData); _deleteDuplicatesItem.setEnabled(hasData); _compressItem.setEnabled(hasData); @@ -505,15 +550,18 @@ public class MenuManager implements DataSubscriber _selectEndItem.setEnabled(hasPoint); _selectEndButton.setEnabled(hasPoint); // are there any photos? - _saveExifItem.setEnabled(_photos != null && _photos.getNumPhotos() > 0); + boolean anyPhotos = _photos != null && _photos.getNumPhotos() > 0; + _saveExifItem.setEnabled(anyPhotos); // is there a current photo? - boolean hasPhoto = _photos != null && _photos.getNumPhotos() > 0 - && _selection.getCurrentPhotoIndex() >= 0; + boolean hasPhoto = anyPhotos && _selection.getCurrentPhotoIndex() >= 0; // connect is only available when current photo is not connected to current point boolean connectAvailable = hasPhoto && hasPoint && _track.getPoint(_selection.getCurrentPointIndex()).getPhoto() == null; _connectPhotoItem.setEnabled(connectAvailable); _connectPhotoButton.setEnabled(connectAvailable); + _disconnectPhotoItem.setEnabled(hasPhoto && _photos.getPhoto(_selection.getCurrentPhotoIndex()) != null + && _photos.getPhoto(_selection.getCurrentPhotoIndex()).getDataPoint() != null); + _correlatePhotosItem.setEnabled(anyPhotos && hasData); _deletePhotoItem.setEnabled(hasPhoto); // is there a current range? boolean hasRange = (hasData && _selection.hasRangeSelected()); diff --git a/tim/prune/gui/PhotoThumbnail.java b/tim/prune/gui/PhotoThumbnail.java index a8c94eb..b792a66 100644 --- a/tim/prune/gui/PhotoThumbnail.java +++ b/tim/prune/gui/PhotoThumbnail.java @@ -5,7 +5,6 @@ import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.image.BufferedImage; - import javax.swing.ImageIcon; import javax.swing.JPanel; @@ -92,29 +91,39 @@ public class PhotoThumbnail extends JPanel implements Runnable */ public void run() { - int picWidth = _photo.getWidth(); - int picHeight = _photo.getHeight(); - if (picWidth > -1 && picHeight > -1) + // Use exif thumbnail? + if (_photo.getExifThumbnail() != null) { + Image image = new ImageIcon(_photo.getExifThumbnail()).getImage(); + _thumbnail = ImageUtils.createScaledImage(image, image.getWidth(null), image.getHeight(null)); + image = null; + } + else { - int displayWidth = Math.min(getWidth(), getParent().getWidth()); - // System.out.println("width = " + getWidth() + ", " + getParent().getWidth() + " = " + displayWidth); - int displayHeight = Math.min(getHeight(), getParent().getHeight()); - // System.out.println("height = " + getHeight() + ", " + getParent().getHeight() + " = " + displayHeight); - - // calculate maximum thumbnail size - Dimension thumbSize = ImageUtils.getThumbnailSize(picWidth, picHeight, displayWidth, displayHeight); - // Work out if need to remake image - boolean needToRemake = (_thumbnail == null) - || _thumbnail.getWidth() != thumbSize.width || _thumbnail.getHeight() != thumbSize.height; - if (thumbSize.width > 0 && thumbSize.height > 0 && needToRemake) + // no exif thumbnail available, going to have to read whole thing + int picWidth = _photo.getWidth(); + int picHeight = _photo.getHeight(); + if (picWidth > -1 && picHeight > -1) { - // Make icon to load image into - Image image = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage(); - // save scaled, smoothed thumbnail for reuse - _thumbnail = ImageUtils.createScaledImage(image, thumbSize.width, thumbSize.height); - image = null; - // TODO: Calculate and set size of thumbnail here - // setPreferredSize(new Dimension(200, 200)); + int displayWidth = Math.min(getWidth(), getParent().getWidth()); + // System.out.println("width = " + getWidth() + ", " + getParent().getWidth() + " = " + displayWidth); + int displayHeight = Math.min(getHeight(), getParent().getHeight()); + // System.out.println("height = " + getHeight() + ", " + getParent().getHeight() + " = " + displayHeight); + + // calculate maximum thumbnail size + Dimension thumbSize = ImageUtils.getThumbnailSize(picWidth, picHeight, displayWidth, displayHeight); + // Work out if need to remake image + boolean needToRemake = (_thumbnail == null) + || _thumbnail.getWidth() != thumbSize.width || _thumbnail.getHeight() != thumbSize.height; + if (thumbSize.width > 0 && thumbSize.height > 0 && needToRemake) + { + // Make icon to load image into + Image image = new ImageIcon(_photo.getFile().getAbsolutePath()).getImage(); + // save scaled, smoothed thumbnail for reuse + _thumbnail = ImageUtils.createScaledImage(image, thumbSize.width, thumbSize.height); + image = null; + // TODO: Calculate and set size of thumbnail here + // setPreferredSize(new Dimension(200, 200)); + } } } _loadingImage = false; diff --git a/tim/prune/gui/ProfileChart.java b/tim/prune/gui/ProfileChart.java index 8bc2c05..22724b5 100644 --- a/tim/prune/gui/ProfileChart.java +++ b/tim/prune/gui/ProfileChart.java @@ -178,6 +178,7 @@ public class ProfileChart extends GenericChart /** * Method to inform map that data has changed + * @param inTrack track object */ public void dataUpdated(Track inTrack) { diff --git a/tim/prune/gui/SelectorDisplay.java b/tim/prune/gui/SelectorDisplay.java index 38e4165..203db45 100644 --- a/tim/prune/gui/SelectorDisplay.java +++ b/tim/prune/gui/SelectorDisplay.java @@ -4,6 +4,7 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; +import java.awt.GridLayout; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; @@ -92,7 +93,7 @@ public class SelectorDisplay extends GenericDisplay // Add panel for waypoints / photos JPanel listsPanel = new JPanel(); - listsPanel.setLayout(new BoxLayout(listsPanel, BoxLayout.Y_AXIS)); + listsPanel.setLayout(new GridLayout(0, 1)); listsPanel.setBorder(BorderFactory.createCompoundBorder( BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), BorderFactory.createEmptyBorder(3, 3, 3, 3)) ); @@ -104,8 +105,12 @@ public class SelectorDisplay extends GenericDisplay { if (!e.getValueIsAdjusting()) selectWaypoint(_waypointList.getSelectedIndex()); }}); - listsPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.waypoints"))); - listsPanel.add(new JScrollPane(_waypointList)); + JPanel waypointListPanel = new JPanel(); + waypointListPanel.setLayout(new BorderLayout()); + waypointListPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.waypoints")), BorderLayout.NORTH); + waypointListPanel.add(new JScrollPane(_waypointList), BorderLayout.CENTER); + listsPanel.add(waypointListPanel); + // photo list _photoListModel = new PhotoListModel(_trackInfo.getPhotoList()); _photoList = new JList(_photoListModel); _photoList.setVisibleRowCount(NUM_LIST_ENTRIES); @@ -114,8 +119,11 @@ public class SelectorDisplay extends GenericDisplay { if (!e.getValueIsAdjusting()) selectPhoto(_photoList.getSelectedIndex()); }}); - listsPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.photos"))); - listsPanel.add(new JScrollPane(_photoList)); + JPanel photoListPanel = new JPanel(); + photoListPanel.setLayout(new BorderLayout()); + photoListPanel.add(new JLabel(I18nManager.getText("details.waypointsphotos.photos")), BorderLayout.NORTH); + photoListPanel.add(new JScrollPane(_photoList), BorderLayout.CENTER); + listsPanel.add(photoListPanel); listsPanel.setAlignmentX(Component.LEFT_ALIGNMENT); // add the controls to the main panel @@ -123,10 +131,11 @@ public class SelectorDisplay extends GenericDisplay mainPanel.add(Box.createVerticalStrut(5)); mainPanel.add(_scroller); mainPanel.add(Box.createVerticalStrut(5)); - mainPanel.add(listsPanel); // add the main panel at the top add(mainPanel, BorderLayout.NORTH); + // and lists in the centre + add(listsPanel, BorderLayout.CENTER); // set preferred width to be small setPreferredSize(new Dimension(100, 100)); } diff --git a/tim/prune/gui/UndoManager.java b/tim/prune/gui/UndoManager.java index 5c3a992..46a1c8d 100644 --- a/tim/prune/gui/UndoManager.java +++ b/tim/prune/gui/UndoManager.java @@ -33,6 +33,8 @@ public class UndoManager /** * Constructor + * @param inApp App object + * @param inFrame parent frame */ public UndoManager(App inApp, JFrame inFrame) { @@ -67,7 +69,7 @@ public class UndoManager // Buttons JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); - JButton okButton = new JButton("OK"); + JButton okButton = new JButton(I18nManager.getText("button.ok")); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) @@ -77,7 +79,7 @@ public class UndoManager } }); buttonPanel.add(okButton); - JButton cancelButton = new JButton("Cancel"); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) diff --git a/tim/prune/lang/prune-texts.properties b/tim/prune/lang/prune-texts.properties index 5214018..b9d86eb 100644 --- a/tim/prune/lang/prune-texts.properties +++ b/tim/prune/lang/prune-texts.properties @@ -4,9 +4,10 @@ # Menu entries menu.file=File menu.file.open=Open -menu.file.addphotos=Add Photos +menu.file.addphotos=Add photos menu.file.save=Save menu.file.exportkml=Export KML +menu.file.exportgpx=Export GPX menu.file.exportpov=Export POV menu.file.exit=Exit menu.edit=Edit @@ -32,6 +33,8 @@ menu.select.end=Set range end menu.photo=Photo menu.photo.saveexif=Save to Exif menu.photo.connect=Connect to point +menu.photo.disconnect=Disconnect from point +menu.photo.correlate=Correlate all photos menu.photo.delete=Remove photo menu.3d=Three-D menu.3d.show3d=Show in Three-D @@ -41,6 +44,7 @@ menu.help.about=About Prune menu.map.zoomin=Zoom in menu.map.zoomout=Zoom out menu.map.zoomfull=Zoom to full scale +menu.map.connect=Connect track points menu.map.autopan=Autopan # Dialogs @@ -92,8 +96,8 @@ dialog.save.table.hasdata=Has data dialog.save.table.save=Save dialog.save.headerrow=Output header row dialog.save.coordinateunits=Coordinate units -dialog.save.units.original=Original dialog.save.altitudeunits=Altitude units +dialog.save.timestampformat=Timestamp format dialog.save.oktitle=File saved dialog.save.ok1=Successfully saved dialog.save.ok2=points to file @@ -101,9 +105,14 @@ dialog.save.overwrite.title=File already exists dialog.save.overwrite.text=This file already exists. Are you sure you want to overwrite the file? dialog.exportkml.title=Export KML dialog.exportkml.text=Title for the data +dialog.exportkml.altitude=Include altitudes (for aviation) dialog.exportkml.kmz=Compress to make kmz file dialog.exportkml.exportimages=Export image thumbnails to kmz dialog.exportkml.filetype=KML, KMZ files +dialog.exportgpx.title=Export GPX +dialog.exportgpx.name=Name +dialog.exportgpx.desc=Description +dialog.exportgpx.filetype=GPX files dialog.exportpov.title=Export POV dialog.exportpov.text=Please enter the parameters for the POV export dialog.exportpov.font=Font @@ -150,6 +159,32 @@ dialog.saveexif.photostatus.modified=Modified dialog.saveexif.overwrite=Overwrite files dialog.saveexif.ok1=Saved dialog.saveexif.ok2=photo files +dialog.correlate.title=Correlate photos +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.photoselect.intro=Select one of these correlated photos to use as the time offset +dialog.correlate.photoselect.photoname=Photo name +dialog.correlate.photoselect.timediff=Time difference +dialog.correlate.photoselect.photolater=Photo later +dialog.correlate.options.tip=Tip: By manually correlating at least one photo, the time offset can be calculated for you. +dialog.correlate.options.intro=Select the options for automatic correlation +dialog.correlate.options.offsetpanel=Time offset +dialog.correlate.options.offset=Offset +dialog.correlate.options.offset.hours=hours, +dialog.correlate.options.offset.minutes=minutes and +dialog.correlate.options.offset.seconds=seconds +dialog.correlate.options.photolater=Photo later than point +dialog.correlate.options.pointlater=Point later than photo +dialog.correlate.options.limitspanel=Correlation limits +dialog.correlate.options.notimelimit=No time limit +dialog.correlate.options.timelimit=Time limit +dialog.correlate.options.nodistancelimit=No distance limit +dialog.correlate.options.distancelimit=Distance limit +dialog.correlate.options.correlate=Correlate +dialog.correlate.alloutsiderange=All photos are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one photo. +dialog.correlate.confirmsingle.text=photo was correlated +dialog.correlate.confirmmultiple.text=photos were correlated +dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\nfor more information and user guides. dialog.about.title=About Prune dialog.about.version=Version dialog.about.build=Build @@ -169,6 +204,7 @@ dialog.about.credits=Credits dialog.about.credits.code=Prune code written by dialog.about.credits.exifcode=Exif code by dialog.about.credits.icons=Some icons taken from +dialog.about.credits.translators=Translators dialog.about.credits.translations=Translations helped by dialog.about.credits.devtools=Development tools dialog.about.credits.othertools=Other tools @@ -190,8 +226,6 @@ button.cancel=Cancel button.overwrite=Overwrite button.moveup=Move up button.movedown=Move down -button.deletepoint=Delete point -button.deleterange=Delete range button.showlines=Show lines button.edit=Edit button.exit=Exit @@ -203,6 +237,8 @@ button.yestoall=Yes to all button.notoall=No to all button.selectall=Select all button.selectnone=Select none +button.preview=Preview +button.guessfields=Guess fields # Display components display.nodata=No data loaded @@ -224,6 +260,7 @@ details.range.to=to details.altitude.to=to details.range.climb=Climb details.range.descent=Descent +details.coordformat=Coordinate format details.distanceunits=Distance units display.range.time.secs=s display.range.time.mins=m @@ -234,6 +271,7 @@ details.waypointsphotos.photos=Photos details.photodetails=Photo details details.nophoto=No photo selected details.photo.loading=Loading +details.photo.connected=Connected # Field names fieldname.latitude=Latitude @@ -249,6 +287,8 @@ fieldname.distance=Distance fieldname.duration=Duration # Measurement units +units.original=Original +units.default=Default units.metres=Metres units.metres.short=m units.feet=Feet @@ -260,6 +300,7 @@ units.miles.short=mi units.degminsec=Deg-min-sec units.degmin=Deg-min units.deg=Degrees +units.iso8601=ISO 8601 # Cardinals for 3d plots cardinal.n=N @@ -280,6 +321,8 @@ undo.deleteduplicates=delete duplicates undo.reverse=reverse range undo.rearrangewaypoints=rearrange waypoints undo.connectphoto=connect photo +undo.disconnectphoto=disconnect photo +undo.correlate=correlate photos # Error messages error.save.dialogtitle=Error saving data diff --git a/tim/prune/lang/prune-texts_de.properties b/tim/prune/lang/prune-texts_de.properties index 3a53f65..7c67508 100644 --- a/tim/prune/lang/prune-texts_de.properties +++ b/tim/prune/lang/prune-texts_de.properties @@ -7,6 +7,7 @@ menu.file.open= menu.file.addphotos=Fotos laden menu.file.save=Speichern menu.file.exportkml=KML exportieren +menu.file.exportgpx=GPX exportieren menu.file.exportpov=POV exportieren menu.file.exit=Beenden menu.edit=Bearbeiten @@ -32,6 +33,8 @@ menu.select.end=Stopp setzen menu.photo=Foto menu.photo.saveexif=Exif Daten speichern menu.photo.connect=Mit Punkt verbinden +menu.photo.disconnect=Vom Punkt trennen +menu.photo.correlate=Alle Fotos korrelieren menu.photo.delete=Foto entfernen menu.3d=Drei-D menu.3d.show3d=In drei-D zeigen @@ -41,6 +44,7 @@ menu.help.about= menu.map.zoomin=Einzoomen menu.map.zoomout=Auszoomen menu.map.zoomfull=Zoomen zum ganzes Bild +menu.map.connect=Trackpunkte mit Linie menu.map.autopan=Autopan # Dialogs @@ -92,8 +96,8 @@ dialog.save.table.hasdata=Hat Daten dialog.save.table.save=Speichern dialog.save.headerrow=Titel Zeile speichern dialog.save.coordinateunits=Koordinaten Maßeinheiten -dialog.save.units.original=Original dialog.save.altitudeunits=Höhe Maßeinheiten +dialog.save.timestampformat=Zeitstempelformat dialog.save.oktitle=Datei gespeichert dialog.save.ok1=Es wurden dialog.save.ok2=Punkte gespeichert nach @@ -101,9 +105,14 @@ dialog.save.overwrite.title=Datei existiert dialog.save.overwrite.text=Diese Datei existiert schon. Sind Sie sicher, Sie wollen die Datei überschreiben? dialog.exportkml.title=KML exportieren dialog.exportkml.text=Titel für die Daten +dialog.exportkml.altitude=Auch Höheninformation (für Luftfahrt) dialog.exportkml.kmz=Daten ins kmz Datei komprimieren dialog.exportkml.exportimages=Bilder ins kmz exportieren dialog.exportkml.filetype=KML, KMZ Dateien +dialog.exportgpx.title=GPX exportieren +dialog.exportgpx.name=Name +dialog.exportgpx.desc=Beschreibung +dialog.exportgpx.filetype=GPX Dateien dialog.exportpov.title=POV exportieren dialog.exportpov.text=Geben Sie die Parameter ein für das POV Export dialog.exportpov.font=Font @@ -140,7 +149,7 @@ dialog.pointnameedit.sentencecase=Gemischt geschrieben dialog.saveexif.title=Exif speichern dialog.saveexif.intro=Selektieren Sie die Fotos zu speichern dialog.saveexif.nothingtosave=Koordinaten sind nicht modifiziert, nichts zu speichern -dialog.saveexif.noexiftool=Kein exiftool Program gefunden. Trotzdem fortfahren? +dialog.saveexif.noexiftool=Kein exiftool Programm gefunden. Trotzdem fortfahren? dialog.saveexif.table.photoname=Foto Name dialog.saveexif.table.status=Status dialog.saveexif.table.save=Speichern @@ -150,6 +159,32 @@ dialog.saveexif.photostatus.modified=Modifiziert dialog.saveexif.overwrite=Dateien überschreiben dialog.saveexif.ok1=Es wurden dialog.saveexif.ok2=Foto Dateien geschrieben +dialog.correlate.title=Fotos korrelieren +dialog.correlate.notimestamps=Die Punkte haben keine Zeitinformation, deswegen ist es nicht möglich die Fotos zu korrelieren. +dialog.correlate.nouncorrelatedphotos=Alle Photos sind schon korreliert.\nWollen Sie trotzdem fortsetzen? +dialog.correlate.photoselect.intro=Selektieren Sie einen von diesen Fotos um die Differenz zu berechnen +dialog.correlate.photoselect.photoname=Foto Name +dialog.correlate.photoselect.timediff=Zeitdifferenz +dialog.correlate.photoselect.photolater=Foto später +dialog.correlate.options.tip=Tipp: Mit mindestens einem korrelierten Foto, die Zeitdifferenz kann automatisch berechnet werden. +dialog.correlate.options.intro=Wählen Sie die Optionen aus für die Korrelation +dialog.correlate.options.offsetpanel=Zeitunterschied +dialog.correlate.options.offset=Unterschied +dialog.correlate.options.offset.hours=Stunden, +dialog.correlate.options.offset.minutes=Minuten und +dialog.correlate.options.offset.seconds=Sekunden +dialog.correlate.options.photolater=Foto später als Punkt +dialog.correlate.options.pointlater=Punkt später als Foto +dialog.correlate.options.limitspanel=Korrelation Grenzen +dialog.correlate.options.notimelimit=Keine Zeitgrenzen +dialog.correlate.options.timelimit=Zeitgrenzen +dialog.correlate.options.nodistancelimit=Keine Distanzgrenzen +dialog.correlate.options.distancelimit=Distanzgrenzen +dialog.correlate.options.correlate=Korrelieren +dialog.correlate.alloutsiderange=Alle Fotos sind ausserhalb vom Track Zeitraum, so können nicht korreliert werden.\nVersuchen Sie mit einem anderen Offset oder verbinden Sie manuell mindestens ein Foto. +dialog.correlate.confirmsingle.text=Foto wurde korreliert +dialog.correlate.confirmmultiple.text=Fotos wurden korreliert +dialog.help.help=Bitte sehen Sie\n http://activityworkshop.net/software/prune/\nfür weitere Information und Benutzeranleitungen. dialog.about.title=Über Prune dialog.about.version=Version dialog.about.build=Build @@ -169,6 +204,7 @@ dialog.about.credits=Credits dialog.about.credits.code=Prune Code geschrieben von dialog.about.credits.exifcode=Exif Code von dialog.about.credits.icons=Einige Ikons von +dialog.about.credits.translators=Dolmetscher dialog.about.credits.translations=Übersetzungen mit Hilfe von dialog.about.credits.devtools=Entwicklungsprogrammen dialog.about.credits.othertools=Andere Programmen @@ -190,8 +226,6 @@ button.cancel=Abbrechen button.overwrite=Überschreiben button.moveup=Aufwärts moven button.movedown=Abwärts moven -button.deletepoint=Punkt löschen -button.deleterange=Spanne löschen button.showlines=Linien anzeigen button.edit=Bearbeiten button.exit=Beenden @@ -203,6 +237,8 @@ button.yestoall=Ja f button.notoall=Nein für alle button.selectall=Alle selektieren button.selectnone=Nichts selektieren +button.preview=Vorschauen +button.guessfields=Felder erraten # Display components display.nodata=Keine Daten geladen @@ -224,6 +260,7 @@ details.range.to=bis details.altitude.to=bis details.range.climb=Aufstieg details.range.descent=Abstieg +details.coordformat=Koordinatenformat details.distanceunits=Distanz Maßeinheiten display.range.time.secs=S display.range.time.mins=M @@ -234,6 +271,7 @@ details.waypointsphotos.photos=Fotos details.photodetails=Details vom Foto details.nophoto=Kein Foto selektiert details.photo.loading=Laden +details.photo.connected=Verbunden # Field names fieldname.latitude=Breitengrad @@ -249,6 +287,8 @@ fieldname.distance=L fieldname.duration=Zeitlänge # Measurement units +units.original=Original +units.default=Default units.metres=Meter units.metres.short=M units.feet=Füße @@ -260,6 +300,7 @@ units.miles.short=Mei units.degminsec=Grad-Min-Sek units.degmin=Grad-Min units.deg=Grad +units.iso8601=ISO 8601 # Cardinals for 3d plots cardinal.n=N @@ -280,6 +321,8 @@ undo.deleteduplicates=Duplikaten l undo.reverse=Spanne umdrehen undo.rearrangewaypoints=Waypoints reorganisieren undo.connectphoto=Foto verbinden +undo.disconnectphoto=Foto trennen +undo.correlate=Fotos korrelieren # Error messages error.save.dialogtitle=Fehler beim Speichern diff --git a/tim/prune/lang/prune-texts_de_CH.properties b/tim/prune/lang/prune-texts_de_CH.properties index 7b2013e..9ce937a 100644 --- a/tim/prune/lang/prune-texts_de_CH.properties +++ b/tim/prune/lang/prune-texts_de_CH.properties @@ -7,6 +7,7 @@ menu.file.open= menu.file.addphotos=Fötelis innätue menu.file.save=Speichere menu.file.exportkml=KML exportiere +menu.file.exportgpx=GPX exportiere menu.file.exportpov=POV exportiere menu.file.exit=Beände menu.edit=Editiere @@ -32,6 +33,8 @@ menu.select.end=Stopp setz menu.photo=Föteli menu.photo.saveexif=Exif Date speicherä menu.photo.connect=Mitem Punkt verbindä +menu.photo.disconnect=Vonem Punkt trännä +menu.photo.correlate=Alli Fötelis korrelierä menu.photo.delete=Föteli entfernä menu.3d=Drüü-D menu.3d.show3d=In drüü-D zeigä @@ -41,6 +44,7 @@ menu.help.about= menu.map.zoomin=Einzoome menu.map.zoomout=Uuszoome menu.map.zoomfull=Zoome zum ganzes Bild +menu.map.connect=Trackpünktli verbindä menu.map.autopan=Autopan # Dialogs @@ -92,26 +96,31 @@ dialog.save.table.hasdata=Het Date dialog.save.table.save=Speicherä dialog.save.headerrow=Titel Ziile speicherä dialog.save.coordinateunits=Koordinate Massiiheite -dialog.save.units.original=Original dialog.save.altitudeunits=Höchi Massiiheite -dialog.save.oktitle=File gespeichert worde +dialog.save.timestampformat=Ziitstämpelformat +dialog.save.oktitle=File gspeicheret worde dialog.save.ok1=Es sin dialog.save.ok2=Punkte gspeicheret worde na dialog.save.overwrite.title=s'File existiert scho dialog.save.overwrite.text=s'File existiert scho. Sind Sie sicher, Sie wend s'File überschriibe? dialog.exportkml.title=KML exportierä dialog.exportkml.text=Titel für die Date +dialog.exportkml.altitude=Au Höchiinformation (fürs Fliege) dialog.exportkml.kmz=Date ins kmz File komprimierä dialog.exportkml.exportimages=Bildli ins Kmz exportierä dialog.exportkml.filetype=KML, KMZ Dateie -dialog.exportpov.title=POV exportiere +dialog.exportgpx.title=GPX exportierä +dialog.exportgpx.name=Name +dialog.exportgpx.desc=Beschriibig +dialog.exportgpx.filetype=GPX Dateie +dialog.exportpov.title=POV exportierä dialog.exportpov.text=Gäbet Sie die Parameter ii fürs POV Export dialog.exportpov.font=Font dialog.exportpov.camerax=Kamera X dialog.exportpov.cameray=Kamera Y dialog.exportpov.cameraz=Kamera Z dialog.exportpov.filetype=POV Dateie -dialog.exportpov.warningtracksize=Dieser Track hät sehr viele Punkte, die Java3D villiicht nöd chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze? +dialog.exportpov.warningtracksize=Dieser Track hät mega viele Punkte, die Java3D villiicht nöd chann bearbeite.\nSind Sie sicher, Sie wend trotzdem fortsetze? dialog.confirmreversetrack.title=Umdrehig bestätige dialog.confirmreversetrack.text=Diese Daten enthalte Ziitstämpel Informatione, die bei dr Umkehrig usser Reihefolge erschiene würdi.\nSind Sie sicher, Sie wend diese Spanne umkehre? dialog.interpolate.title=Punkte interpoliere @@ -123,9 +132,9 @@ dialog.confirmundo.single.text=Operation r dialog.confirmundo.multiple.text=Operatione rückgängig gmacht worde. dialog.undo.none.title=Undo nöd möglich dialog.undo.none.text=Keini Operatione könne rückgängig gmacht werde. -dialog.clearundo.title=Undo-Liste lösche +dialog.clearundo.title=Undo-Liste löschä dialog.clearundo.text=Sind Sie sicher, Sie wend die Undo-Liste lösche?\nAlle Undo Infos werdet verlore gah! -dialog.pointedit.title=Punkt editiere +dialog.pointedit.title=Punkt editierä dialog.pointedit.text=Wählet Sie jäden Fäld uus zu editiere, und mitem 'Editiere' Chnopf den Wert ändere dialog.pointedit.table.field=Fäld dialog.pointedit.table.value=Wert @@ -150,6 +159,32 @@ dialog.saveexif.photostatus.modified=G dialog.saveexif.overwrite=Files überschriebä dialog.saveexif.ok1=Es sin dialog.saveexif.ok2=Fötelis gschriebe worde +dialog.correlate.title=Fötelis korrelierä +dialog.correlate.notimestamps=Es hät kei Ziitstämpel inem Track innä, so s'isch nöd möglech die Fötelis zu korrelierä. +dialog.correlate.nouncorrelatedphotos=Alle Fötelis sin scho korreliert.\nWend Sie trotzdem fortsetzä? +dialog.correlate.photoselect.intro=Wählet Sie eini vo deren Föteli uus um die Ziitdifferänz zu berächnä +dialog.correlate.photoselect.photoname=Föteli Name +dialog.correlate.photoselect.timediff=Ziitdifferänz +dialog.correlate.photoselect.photolater=Föteli spöter +dialog.correlate.options.tip=Tipp: Mit mindeschtens einem korrelierten Föteli, die Ziitdifferänz kann automatisch berächnet werdä. +dialog.correlate.options.intro=Wählet Sie die Optione uus für die Korrelierig +dialog.correlate.options.offsetpanel=Ziitunterschied +dialog.correlate.options.offset=Unterschied +dialog.correlate.options.offset.hours=Schtundä, +dialog.correlate.options.offset.minutes=Minutä und +dialog.correlate.options.offset.seconds=Sekundä +dialog.correlate.options.photolater=Föteli spöter alsem Punkt +dialog.correlate.options.pointlater=Punkt spöter alsem Föteli +dialog.correlate.options.limitspanel=Korrelation Gränzä +dialog.correlate.options.notimelimit=Kei Ziitgränzä +dialog.correlate.options.timelimit=Ziitgränzä +dialog.correlate.options.nodistancelimit=Kei Distanzgränzä +dialog.correlate.options.distancelimit=Distanzgränzä +dialog.correlate.options.correlate=Korrelierä +dialog.correlate.alloutsiderange=Alli Fötelis sin uusserhalb vonem Track Ziitruum, so chönne nöd korreliert werdä.\nVersuechet Sie mitenem anderen Offset oder verbindet Sie manuell mindeschtens eis Föteli. +dialog.correlate.confirmsingle.text=Föteli isch korreliert worde +dialog.correlate.confirmmultiple.text=Fötelis sin korreliert worde +dialog.help.help=Bitte lueg na\n http://activityworkshop.net/software/prune/\nfür wiitere Information und Benutzeraaleitige. dialog.about.title=Über Prune dialog.about.version=Version dialog.about.build=Build @@ -169,6 +204,7 @@ dialog.about.credits=Credits dialog.about.credits.code=Prune Code gschriebä vo dialog.about.credits.exifcode=Exif Code vo dialog.about.credits.icons=Einigi Ikons vo +dialog.about.credits.translators=Dolmätscher dialog.about.credits.translations=Übersetzige mit dr Hilfe vo dialog.about.credits.devtools=Entwicklungswärkzüüge dialog.about.credits.othertools=Anderi Wärkzüüge @@ -190,8 +226,6 @@ button.cancel=Abbr button.overwrite=Überschriibä button.moveup=Uufä schiebä button.movedown=Aba schiebä -button.deletepoint=Punkt löschä -button.deleterange=Spanne löschä button.showlines=Linie aazeigä button.edit=Editierä button.exit=Beändä @@ -203,6 +237,8 @@ button.yestoall=Ja f button.notoall=Nei für alli button.selectall=Alli selektierä button.selectnone=Nüüt selektierä +button.preview=Vorschauä +button.guessfields=Fälde erratä # Display components display.nodata=Kei Date glade worde @@ -224,7 +260,8 @@ details.range.to=bis details.altitude.to=bis details.range.climb=Uufstieg details.range.descent=Abstieg -details.distanceunits=Distanz Masseinheiten +details.coordformat=Koordinatenformat +details.distanceunits=Distanz Masseinheite display.range.time.secs=S display.range.time.mins=M display.range.time.hours=Std @@ -234,6 +271,7 @@ details.waypointsphotos.photos=F details.photodetails=Details vom Föteli details.nophoto=Kei föteli selektiert details.photo.loading=Ladä +details.photo.connected=Verbundä # Field names fieldname.latitude=Breitegrad @@ -249,6 +287,8 @@ fieldname.distance=L fieldname.duration=Ziitlängi # Measurement units +units.original=Original +units.default=Default units.metres=Meter units.metres.short=M units.feet=Fuess @@ -260,6 +300,7 @@ units.miles.short=Mei units.degminsec=Grad-Min-Sek units.degmin=Grad-Min units.deg=Grad +units.iso8601=ISO 8601 # Cardinals for 3d plots cardinal.n=N @@ -280,20 +321,22 @@ undo.deleteduplicates=Duplikaten l undo.reverse=Spanne umdrähie undo.rearrangewaypoints=Waypoints reorganisierä undo.connectphoto=Föteli verbindä +undo.disconnectphoto=Föteli trännä +undo.correlate=Fötelis korrelierä # Error messages -error.save.dialogtitle=Fehler bim Speichere +error.save.dialogtitle=Fähle bim Speichere error.save.nodata=Kei Date zum speichere error.save.failed=Speichere vom File fehlgschlage : error.saveexif.filenotfound=Föteli File nöd gfunde error.saveexif.cannotoverwrite1=Föteli File error.saveexif.cannotoverwrite2=isch nöd schriibbar. Speichere na einer Kopie? -error.load.dialogtitle=Fehler bim Lade +error.load.dialogtitle=Fähle bim Lade error.load.noread=File cha nöd glase werde -error.load.nopoints=Kei gültigi Information im Datei gfunde +error.load.nopoints=Kei gültigi Information inem File gfunde error.load.unknownxml=Unbekanntes xml Format: -error.load.othererror=Fehler bim Läse: -error.jpegload.dialogtitle=Fehler bim Lade von Fötelis +error.load.othererror=Fähle bim Läse: +error.jpegload.dialogtitle=Fähle bim Lade von Fötelis error.jpegload.nofilesfound=Kei Dateie gfunde error.jpegload.nojpegsfound=Kei Jpegs gfunde error.jpegload.noexiffound=Kei EXIF Information gfunde @@ -301,9 +344,9 @@ error.jpegload.nogpsfound=Kei GPS Information gfunde error.undofailed.title=Undo isch fehlgschlage worde error.undofailed.text=Operation kann nöd rückgängig gmacht werde error.function.noop.title=Funktion hät gar nüüt gmacht -error.rearrange.noop=Waypoints Reorganisiere hät kein Effäkt gha +error.rearrange.noop=Waypoints Reorganisierig hät kei Effäkt gha error.function.notimplemented=Sorry, d'Funktion isch nonig implementiert worde. error.function.notavailable.title=Funktion nöd verfüegbar error.function.nojava3d=Sorry, d'Funktion brucht d Java3d Library,\nvo Sun.com odr Blackdown.org erhältlech. -error.3d.title=Fähler mitm 3d Darstellig -error.3d=N Fähler isch mitm 3d Darstellig ufgtrete +error.3d.title=Fähler mitere 3d Darstellig +error.3d=N Fähler isch mitere 3d Darstellig ufgtrete diff --git a/tim/prune/lang/prune-texts_es.properties b/tim/prune/lang/prune-texts_es.properties index fcd83cf..f6746c7 100644 --- a/tim/prune/lang/prune-texts_es.properties +++ b/tim/prune/lang/prune-texts_es.properties @@ -7,6 +7,7 @@ menu.file.open=Abrir menu.file.addphotos=Cargar fotos menu.file.save=Guardar menu.file.exportkml=Exportar KML +menu.file.exportgpx=Exportar GPX menu.file.exportpov=Exportar POV menu.file.exit=Salir menu.edit=Editar @@ -26,12 +27,14 @@ menu.edit.rearrange.end=Ir al final menu.edit.rearrange.nearest=Ir al más próximo menu.select=Seleccionar menu.select.all=Seleccionar todo -menu.select.none=Seleccionar nada +menu.select.none=No seleccionar nada menu.select.start=Fijar comienzo menu.select.end=Fijar final menu.photo=Foto menu.photo.saveexif=Guardar Exif -menu.photo.connect=Connect con punto +menu.photo.connect=Conectar con punto +menu.photo.disconnect=Desconectar de punto +menu.photo.correlate=Correlacionar todas las fotos menu.photo.delete=Eliminar foto menu.3d=3-D menu.3d.show3d=Mostrar en 3-D @@ -41,7 +44,8 @@ menu.help.about=Acerca de Prune menu.map.zoomin=Ampliar zoom menu.map.zoomout=Reducir zoom menu.map.zoomfull=Mostrar todo -menu.map.autopan=Posicionar automático +menu.map.connect=Conectar puntos de track +menu.map.autopan=Posicionar automáticamente # Dialogs dialog.exit.confirm.title=Salir de Prune @@ -49,9 +53,9 @@ dialog.exit.confirm.text=Los datos han sido modificados. Desea salir de Prune? dialog.openappend.title=Agregar a datos existentes dialog.openappend.text=Agregar estos datos a los datos ya guardados? dialog.deletepoint.title=Borrar punto -dialog.deletepoint.deletephoto=Borrar foto tambien? +dialog.deletepoint.deletephoto=Borrar la foto tambien? dialog.deletephoto.title=Borrar foto -dialog.deletephoto.deletepoint=Borrar punto tambien? +dialog.deletephoto.deletepoint=Borrar el punto tambien? dialog.deleteduplicates.title=Borrar duplicados dialog.deleteduplicates.single.text=duplicado eliminado dialog.deleteduplicates.multi.text=duplicados eliminados @@ -73,7 +77,7 @@ dialog.delimiter.tab=Tabulador dialog.delimiter.space=Espacio dialog.delimiter.semicolon=Punto y coma ; dialog.delimiter.other=Otro -dialog.openoptions.deliminfo.records=datos, con +dialog.openoptions.deliminfo.records=datos, con dialog.openoptions.deliminfo.fields=campos dialog.openoptions.deliminfo.norecords=Ningun dato dialog.openoptions.tabledesc=Extraer archivo @@ -92,24 +96,29 @@ dialog.save.table.hasdata=Contiene datos dialog.save.table.save=Guardar dialog.save.headerrow=Título fila dialog.save.coordinateunits=Unidades de las coordenadas -dialog.save.units.original=Original dialog.save.altitudeunits=Unidades de las altitudes -dialog.save.oktitle=Guardando archivo +dialog.save.timestampformat=Format del tiempo +dialog.save.oktitle=Guardando archivo dialog.save.ok1=Guardando dialog.save.ok2=puntos al archivo dialog.save.overwrite.title=El archivo ya existe dialog.save.overwrite.text=El archivo ya existe, desea sobreescribirlo? dialog.exportkml.title=Exportar KML dialog.exportkml.text=Descripción para los datos +dialog.exportkml.altitude=Incluir altitudes (para aviación) dialog.exportkml.kmz=Comprimir al archivo kmz dialog.exportkml.exportimages=Exportar fotos al kmz dialog.exportkml.filetype=Archivos KML, KMZ +dialog.exportgpx.title=Exportar GPX +dialog.exportgpx.name=Nombre +dialog.exportgpx.desc=Descripción +dialog.exportgpx.filetype=Archivos GPX dialog.exportpov.title=Exportar POV dialog.exportpov.text=Introdzca los Parametros para exportar dialog.exportpov.font=Fuente -dialog.exportpov.camerax=Camera X -dialog.exportpov.cameray=Camera Y -dialog.exportpov.cameraz=Camera Z +dialog.exportpov.camerax=Cámara X +dialog.exportpov.cameray=Cámara Y +dialog.exportpov.cameraz=Cámara Z dialog.exportpov.filetype=Archivos POV dialog.exportpov.warningtracksize=Este track contiene un gran numero de puntos. Puede ser que Java3D no los pueda visualizar. Está seguro de que desea continuar? dialog.confirmreversetrack.title=Confirmar inversión @@ -134,22 +143,48 @@ dialog.pointedit.changevalue.text=Introduzca el nuevo valor de campo dialog.pointedit.changevalue.title=Editar campo dialog.pointnameedit.title=Editar nombre de waypoint dialog.pointnameedit.name=Nombre de waypoint -dialog.pointnameedit.uppercase=Maysculas -dialog.pointnameedit.lowercase=minsculas +dialog.pointnameedit.uppercase=Mayúsculas +dialog.pointnameedit.lowercase=minúsculas dialog.pointnameedit.sentencecase=Mezcla dialog.saveexif.title=Guardar Exif dialog.saveexif.intro=Seleccione fotos a guardar -dialog.saveexif.nothingtosave=Coordenadas no han modificados +dialog.saveexif.nothingtosave=Coordenadas no modificadas, nada que guardar dialog.saveexif.noexiftool=exiftool program no encontrado. Desea continuar? -dialog.saveexif.table.photoname=Nombre de foto -dialog.saveexif.table.status=Status +dialog.saveexif.table.photoname=Nombre de la foto +dialog.saveexif.table.status=Estado dialog.saveexif.table.save=Guardar -dialog.saveexif.photostatus.connected=Connected -dialog.saveexif.photostatus.disconnected=Disconnected -dialog.saveexif.photostatus.modified=Modificado +dialog.saveexif.photostatus.connected=Conectada +dialog.saveexif.photostatus.disconnected=Desconectada +dialog.saveexif.photostatus.modified=Modificada dialog.saveexif.overwrite=Sobreescribirlar archivos? dialog.saveexif.ok1=Guardando dialog.saveexif.ok2=fotos +dialog.correlate.title=Correlacionar fotos +dialog.correlate.notimestamps=No hay información de tiempo para los puntos, así que no hay nada que correlacionar con las fotos. +dialog.correlate.nouncorrelatedphotos=No hay fotos no correlacionadas.\nEstá seguro de que desea continuar? +dialog.correlate.photoselect.intro=Seleccione una de estas fotos correlacionadas para usar como margen de tiempo +dialog.correlate.photoselect.photoname=Nombre de la foto +dialog.correlate.photoselect.timediff=Diferencia de tiempo +dialog.correlate.photoselect.photolater=Foto más adelante +dialog.correlate.options.tip=Sugerencia: Correlacionando al menos una foto manualmente, el margen de tiempo se calcula automáticamente. +dialog.correlate.options.intro=Seleccionar las opciones para correlación automática +dialog.correlate.options.offsetpanel=Margen de tiempo +dialog.correlate.options.offset=Margen +dialog.correlate.options.offset.hours=horas, +dialog.correlate.options.offset.minutes=minutos y +dialog.correlate.options.offset.seconds=segundos +dialog.correlate.options.photolater=Foto después de punto +dialog.correlate.options.pointlater=Punto después de foto +dialog.correlate.options.limitspanel=Límites de correlación +dialog.correlate.options.notimelimit=Sin límite de tiempo +dialog.correlate.options.timelimit=Límite de tiempo +dialog.correlate.options.nodistancelimit=Sin límite de distancia +dialog.correlate.options.distancelimit=Límite de distancia +dialog.correlate.options.correlate=Correlacionar +dialog.correlate.alloutsiderange=Todas las fotos están fuera del margen horario del track, por lo que ninguna puede ser correlada.\nIntente cambiar el margen o correle manualmente al menos una foto. +dialog.correlate.confirmsingle.text=foto fue correlada +dialog.correlate.confirmmultiple.text=fotos fueron correladas +dialog.help.help=Por favor, ver\n http://activityworkshop.net/software/prune/\npara más información y guías del usuario. dialog.about.title=Acerca de Prune dialog.about.version=Versión dialog.about.build=Construir @@ -157,29 +192,30 @@ dialog.about.summarytext1=Prune es un programa para cargar, mostrar y editar dat dialog.about.summarytext2=Distribuido bajo el GNU GPL para uso libre y gratuito.
Se permite (y se anima) la copia, redistribución y modificación de acuerdo
a las condiciones incluidas en el archivo licence.txt. dialog.about.summarytext3=Por favor, ver http://activityworkshop.net/ para más información y guías del usuario. dialog.about.translatedby=Traducción al español realizada por activityworkshop y amigos muy amables! -dialog.about.systeminfo=Informacion del System -dialog.about.systeminfo.os=Operating System +dialog.about.systeminfo=Informacion del sistema +dialog.about.systeminfo.os=Sistema operativo dialog.about.systeminfo.java=Java Runtime -dialog.about.systeminfo.java3d=Java3d installed -dialog.about.systeminfo.povray=Povray installed -dialog.about.systeminfo.exiftool=Exiftool installed +dialog.about.systeminfo.java3d=Java3d instalado +dialog.about.systeminfo.povray=Povray instalado +dialog.about.systeminfo.exiftool=Exiftool instalado dialog.about.yes=Si dialog.about.no=No dialog.about.credits=Credits -dialog.about.credits.code=Prune code written by -dialog.about.credits.exifcode=Exif code by -dialog.about.credits.icons=Some icons taken from -dialog.about.credits.translations=Translations helped by -dialog.about.credits.devtools=Development tools -dialog.about.credits.othertools=Other tools -dialog.about.credits.thanks=Thanks to +dialog.about.credits.code=El código de Prune fue escrito por +dialog.about.credits.exifcode=El código Exif por +dialog.about.credits.icons=Algunos iconos se tomaron de +dialog.about.credits.translators=Traductores +dialog.about.credits.translations=Ayuda en la traducción +dialog.about.credits.devtools=Herramientas de desarrollo +dialog.about.credits.othertools=Otras herramientas +dialog.about.credits.thanks=Gracias a # 3d window dialog.3d.title=Prune vista 3-D dialog.3d.altitudecap=Escala de las altitudes -dialog.3dlines.title=Prune gridlines -dialog.3dlines.empty=Ninguna información coordenadas encontrada! -dialog.3dlines.intro=Información de gridlines +dialog.3dlines.title=Cuadrícula Prune +dialog.3dlines.empty=No hay ninguna cuadrícula! +dialog.3dlines.intro=Información de la cuadrícula # Buttons button.ok=Aceptar @@ -190,9 +226,7 @@ button.cancel=Cancelar button.overwrite=Sobreescribir button.moveup=Mover hacia arriba button.movedown=Mover hacia abajo -button.deletepoint=Eliminar punto -button.deleterange=Eliminar rango -button.showlines=Mostrar gridlines +button.showlines=Mostrar cuadrícula button.edit=Editar button.exit=Salir button.close=Cerrar @@ -203,6 +237,8 @@ button.yestoall=Si por todo button.notoall=No por todo button.selectall=Seleccionar todo button.selectnone=Seleccionar nada +button.preview=Previsualización +button.guessfields=Adivinar campos # Display components display.nodata=Ningún dato cargado @@ -224,6 +260,7 @@ details.range.to=hacia details.altitude.to=hacia details.range.climb=Ascenso details.range.descent=Descenso +details.coordformat=Formato de coordenadas details.distanceunits=Unidades de distancia display.range.time.secs=s display.range.time.mins=m @@ -232,8 +269,9 @@ display.range.time.days=d details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Fotos details.photodetails=Detalles del Foto -details.nophoto=Ningún foto seleccionado -details.photo.loading=Cargar +details.nophoto=Ninguna foto seleccionada +details.photo.loading=Cargando +details.photo.connected=Conectada # Field names fieldname.latitude=Latitud @@ -249,6 +287,8 @@ fieldname.distance=Distancia fieldname.duration=Duración # Measurement units +units.original=Original +units.default=Por defecto units.metres=Metros units.metres.short=m units.feet=Pies @@ -260,6 +300,7 @@ units.miles.short=mi units.degminsec=Gra-min-seg units.degmin=Gra-min units.deg=Grados +units.iso8601=ISO 8601 # Cardinals for 3d plots cardinal.n=N @@ -279,7 +320,9 @@ undo.insert=insertar puntos undo.deleteduplicates=eliminar duplicados undo.reverse=invertir rango undo.rearrangewaypoints=reordenar waypoints -undo.connectphoto=connectar foto +undo.connectphoto=conectar foto +undo.disconnectphoto=desconectar foto +undo.correlate=correlacionar fotos # Error messages error.save.dialogtitle=Fallo al guardar datos @@ -291,7 +334,7 @@ error.saveexif.cannotoverwrite2=. Guardar a una copia? error.load.dialogtitle=Fallo al cargar datos error.load.noread=No se puede leer el fichero error.load.nopoints=Ninguna información coordenadas encontrada -error.load.unknownxml=Unrecognised xml format: +error.load.unknownxml=Formato xml no reconocido: error.load.othererror=Fallo al cargar datos: error.jpegload.dialogtitle=Error cargando fotos error.jpegload.nofilesfound=Ningún archivo encontrado diff --git a/tim/prune/lang/prune-texts_fr.properties b/tim/prune/lang/prune-texts_fr.properties index 59e070b..adc2853 100644 --- a/tim/prune/lang/prune-texts_fr.properties +++ b/tim/prune/lang/prune-texts_fr.properties @@ -7,10 +7,11 @@ menu.file.open=Ouvrir menu.file.addphotos=Ouvrir photos menu.file.save=Enregistrer menu.file.exportkml=Exporter au KML +menu.file.exportgpx=Exporter au GPX menu.file.exportpov=Exporter au POV menu.file.exit=Quitter menu.edit=Édition -menu.edit.undo=Undo +menu.edit.undo=Annuler menu.edit.clearundo=Purger undo liste menu.edit.editpoint=Editer point menu.edit.editwaypointname=Editer nom du waypoint @@ -27,11 +28,13 @@ menu.edit.rearrange.nearest=Chaque menu.select=Sélectionner menu.select.all=Tous sélectionner menu.select.none=Rien sélectionner -menu.select.start=Set range start -menu.select.end=Set range end +menu.select.start=Set range début +menu.select.end=Set range fin menu.photo=Photo menu.photo.saveexif=Enregistrer à Exif -menu.photo.connect=Connect to point +menu.photo.connect=Relier au point +menu.photo.disconnect=Disconnect from point +menu.photo.correlate=Corréler tous les photos menu.photo.delete=Remove photo menu.3d=Trois-D menu.3d.show3d=Montrer en Trois-D @@ -41,31 +44,32 @@ menu.help.about= menu.map.zoomin=Zoom avant menu.map.zoomout=Zoom arrière menu.map.zoomfull=Zoom to full scale +menu.map.connect=Connect track points menu.map.autopan=Pan automatique # Dialogs dialog.exit.confirm.title=Terminer Prune -dialog.exit.confirm.text=Le data a été modifié. Souhaitez-vous terminer Prune sans enregistrement? -dialog.openappend.title=Append to existing data -dialog.openappend.text=Append this data to the data already loaded? -dialog.deletepoint.title=Delete Point -dialog.deletepoint.deletephoto=Delete photo attached to this point? -dialog.deletephoto.title=Delete Photo -dialog.deletephoto.deletepoint=Delete point attached to this photo? -dialog.deleteduplicates.title=Delete Duplicates -dialog.deleteduplicates.single.text=duplicate was deleted -dialog.deleteduplicates.multi.text=duplicates were deleted +dialog.exit.confirm.text=Les données ont été modifié. Souhaitez-vous terminer Prune sans enregistrement? +dialog.openappend.title=Append to existing données +dialog.openappend.text=Append this to the données already loaded? +dialog.deletepoint.title=Effacer point +dialog.deletepoint.deletephoto=Effacer photo attached to this point? +dialog.deletephoto.title=Effacer photo +dialog.deletephoto.deletepoint=Effacer point attached to this photo? +dialog.deleteduplicates.title=Effacer duplicates +dialog.deleteduplicates.single.text=duplicate a été effacé +dialog.deleteduplicates.multi.text=duplicates ont été effacés dialog.deleteduplicates.nonefound=No duplicates found -dialog.compresstrack.title=Compress Track +dialog.compresstrack.title=Comprimer track dialog.compresstrack.parameter.text=Parameter for compression (lower number = more compression) dialog.compresstrack.text=Track compressed -dialog.compresstrack.single.text=data point was removed -dialog.compresstrack.multi.text=data points were removed -dialog.compresstrack.nonefound=No data points could be removed -dialog.openoptions.title=Open options +dialog.compresstrack.single.text=point a été effacé +dialog.compresstrack.multi.text=points ont été effacés +dialog.compresstrack.nonefound=Pas de données ont été effacés +dialog.openoptions.title=Ouvrir options dialog.openoptions.filesnippet=Extract of fichier -dialog.load.table.field=Field -dialog.load.table.datatype=Data Type +dialog.load.table.field=Champ +dialog.load.table.datatype=Typ des données dialog.load.table.description=Description dialog.delimiter.label=Séparateur de texte dialog.delimiter.comma=Virgule , @@ -87,23 +91,28 @@ dialog.jpegload.photoadded=photo was added dialog.jpegload.photosadded=photos were added dialog.saveoptions.title=Save fichier dialog.save.fieldstosave=Fields to save -dialog.save.table.field=Field -dialog.save.table.hasdata=Has data +dialog.save.table.field=Champ +dialog.save.table.hasdata=A information dialog.save.table.save=Enregistrer dialog.save.headerrow=Output header row dialog.save.coordinateunits=Unités des coordonnées -dialog.save.units.original=Original dialog.save.altitudeunits=Unités de altitude +dialog.save.timestampformat=Format de timestamp dialog.save.oktitle=File saved dialog.save.ok1=Successfully saved dialog.save.ok2=points to fichier dialog.save.overwrite.title=File already exists dialog.save.overwrite.text=This fichier already exists. Are you sure you want to overwrite the fichier? dialog.exportkml.title=Exporter au KML -dialog.exportkml.text=Title for the data -dialog.exportkml.kmz=Compress to make kmz fichier -dialog.exportkml.exportimages=Export image thumbnails to kmz +dialog.exportkml.text=Titre pour le data +dialog.exportkml.altitude=Include altitudes (pour aviation) +dialog.exportkml.kmz=Comprimer à kmz fichier +dialog.exportkml.exportimages=Export image thumbnails à kmz dialog.exportkml.filetype=Classeur KML, KMZ +dialog.exportgpx.title=Exporter au GPX +dialog.exportgpx.name=Nom +dialog.exportgpx.desc=Légende +dialog.exportgpx.filetype=Classeur GPX dialog.exportpov.title=Exporter au POV dialog.exportpov.text=Please enter the parameters for the POV export dialog.exportpov.font=Police @@ -141,15 +150,41 @@ dialog.saveexif.title=Save Exif dialog.saveexif.intro=Select the photos to save using the checkboxes dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save dialog.saveexif.noexiftool=No exiftool program could be found. Continue? -dialog.saveexif.table.photoname=Photo name +dialog.saveexif.table.photoname=Nom de photo dialog.saveexif.table.status=Status -dialog.saveexif.table.save=Save +dialog.saveexif.table.save=Enregistrer dialog.saveexif.photostatus.connected=Connected dialog.saveexif.photostatus.disconnected=Disconnected dialog.saveexif.photostatus.modified=Modified dialog.saveexif.overwrite=Overwrite fichiers dialog.saveexif.ok1=Saved dialog.saveexif.ok2=photo fichiers +dialog.correlate.title=Correlate photos +dialog.correlate.notimestamps=Les points n'ont pas de timestamps, donc ce n'est pas possible de correler. +dialog.correlate.nouncorrelatedphotos=There are no uncorrelated photos.\nAre you sure you want to continue? +dialog.correlate.photoselect.intro=Select one of these correlated photos to use as the time offset +dialog.correlate.photoselect.photoname=Nom de photo +dialog.correlate.photoselect.timediff=Difference de temps +dialog.correlate.photoselect.photolater=Photo plus tard +dialog.correlate.options.tip=Tip: By manually correlating at least one photo, the time offset can be calculated for you. +dialog.correlate.options.intro=Select the options for automatic correlation +dialog.correlate.options.offsetpanel=Offset de temps +dialog.correlate.options.offset=Offset +dialog.correlate.options.offset.hours=heures, +dialog.correlate.options.offset.minutes=minutes et +dialog.correlate.options.offset.seconds=secondes +dialog.correlate.options.photolater=Photo later than point +dialog.correlate.options.pointlater=Point later than photo +dialog.correlate.options.limitspanel=Correlation limits +dialog.correlate.options.notimelimit=No time limit +dialog.correlate.options.timelimit=Time limit +dialog.correlate.options.nodistancelimit=No distance limit +dialog.correlate.options.distancelimit=Distance limit +dialog.correlate.options.correlate=Correlate +dialog.correlate.alloutsiderange=All photos are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one photo. +dialog.correlate.confirmsingle.text=photo was correlated +dialog.correlate.confirmmultiple.text=photos were correlated +dialog.help.help=Consultez la page\n http://activityworkshop.net/software/prune/\npour de plus détails et user guides. dialog.about.title=À propos de Prune dialog.about.version=Version dialog.about.build=Build @@ -160,19 +195,20 @@ dialog.about.translatedby=Texte en fran dialog.about.systeminfo=Info de Systeme dialog.about.systeminfo.os=Operating Systeme dialog.about.systeminfo.java=Java Runtime -dialog.about.systeminfo.java3d=Java3d installed -dialog.about.systeminfo.povray=Povray installed -dialog.about.systeminfo.exiftool=Exiftool installed +dialog.about.systeminfo.java3d=Java3d installé +dialog.about.systeminfo.povray=Povray installé +dialog.about.systeminfo.exiftool=Exiftool installé dialog.about.yes=Oui dialog.about.no=Non -dialog.about.credits=Credits -dialog.about.credits.code=Prune code written by -dialog.about.credits.exifcode=Exif code by +dialog.about.credits=Crédits +dialog.about.credits.code=Prune code écrit par +dialog.about.credits.exifcode=Exif code par dialog.about.credits.icons=Some icons taken from -dialog.about.credits.translations=Translations helped by -dialog.about.credits.devtools=Development tools -dialog.about.credits.othertools=Other tools -dialog.about.credits.thanks=Thanks to +dialog.about.credits.translators=Interprète +dialog.about.credits.translations=Traduction avec l'aide de +dialog.about.credits.devtools=Outils de développement +dialog.about.credits.othertools=Autre outils +dialog.about.credits.thanks=Merci à # 3d window dialog.3d.title=Vue Trois-d de Prune @@ -187,12 +223,10 @@ button.back=Retour button.next=Prochain button.finish=Fini button.cancel=Annuler -button.overwrite=Overwrite +button.overwrite=Écraser button.moveup=Move up button.movedown=Move down -button.deletepoint=Delete point -button.deleterange=Delete range -button.showlines=Show lines +button.showlines=Montrer lignes button.edit=Éditer button.exit=Terminer button.close=Fermer @@ -203,27 +237,30 @@ button.yestoall=Oui pour tous button.notoall=Non pour tous button.selectall=Sélecter tous button.selectnone=Sélecter rien +button.preview=Preview +button.guessfields=Guess fields # Display components -display.nodata=No data loaded +display.nodata=Pas de data loaded display.noaltitudes=Track data does not include altitudes -details.trackdetails=Track details -details.notrack=No track loaded +details.trackdetails=Détails de track +details.notrack=Pas de track loaded details.track.points=Points -details.track.file=File -details.track.numfiles=Number of fichiers -details.pointdetails=Point details +details.track.file=Fichier +details.track.numfiles=Nombre de fichiers +details.pointdetails=Détails de point details.index.selected=Index details.index.of=de -details.nopointselection=No point selected +details.nopointselection=Pas de point choisis details.photofile=Photo fichier -details.norangeselection=No range selected +details.norangeselection=No range choisis details.rangedetails=Range details -details.range.selected=Selected +details.range.selected=Choisis details.range.to=à details.altitude.to=à details.range.climb=Montée details.range.descent=Descente +details.coordformat=Coordinate format details.distanceunits=Unités de distance display.range.time.secs=s display.range.time.mins=m @@ -231,9 +268,10 @@ display.range.time.hours=h display.range.time.days=j details.waypointsphotos.waypoints=Waypoints details.waypointsphotos.photos=Photos -details.photodetails=Photo details -details.nophoto=Pas de Photo -details.photo.loading=Loading +details.photodetails=Détails de photo +details.nophoto=Pas de photo +details.photo.loading=Charger +details.photo.connected=Connected # Field names fieldname.latitude=Latitude @@ -249,6 +287,8 @@ fieldname.distance=Distance fieldname.duration=Durée # Measurement units +units.original=Original +units.default=Default units.metres=mètres units.metres.short=m units.feet=pieds @@ -260,6 +300,7 @@ units.miles.short=li units.degminsec=Deg-min-sec units.degmin=Deg-min units.deg=Degrés +units.iso8601=ISO 8601 # Cardinals for 3d plots cardinal.n=N @@ -280,6 +321,8 @@ undo.deleteduplicates=delete duplicates undo.reverse=reverse range undo.rearrangewaypoints=rearrange waypoints undo.connectphoto=connect photo +undo.disconnectphoto=disconnect photo +undo.correlate=correlate photos # Error messages error.save.dialogtitle=Error saving data diff --git a/tim/prune/lang/prune-texts_pl.properties b/tim/prune/lang/prune-texts_pl.properties new file mode 100644 index 0000000..7524363 --- /dev/null +++ b/tim/prune/lang/prune-texts_pl.properties @@ -0,0 +1,352 @@ +# Text entries for the Prune application +# Polish entries as extra + +# Menu entries +menu.file=Plik +menu.file.open=Otw\u00F3rz +menu.file.addphotos=Dodaj zdj\u0119cia +menu.file.save=Zapisz +menu.file.exportkml=Eksportuj jako KML +menu.file.exportgpx=Eksportuj jako GPX +menu.file.exportpov=Eksportuj jako POV +menu.file.exit=Zako\u0144cz +menu.edit=Edycja +menu.edit.undo=Cofnij +menu.edit.clearundo=Wyczy\u015B\u0107 list\u0119 zmian +menu.edit.editpoint=Edytuj punkt +menu.edit.editwaypointname=Zmie\u0144 nazw\u0119 punktu po\u015Bredniego +menu.edit.deletepoint=Usu\u0144 punkt +menu.edit.deleterange=Usu\u0144 zakres +menu.edit.deleteduplicates=Usu\u0144 duplikaty +menu.edit.compress=Skompresuj scie\u017Ck\u0119 +menu.edit.interpolate=Interpoluj punkty +menu.edit.reverse=Odwr\u00F3\u0107 zakres +menu.edit.rearrange=Zmie\u0144 kolejno\u015B\u0107 punkt\u00F3w po\u015Brednich +menu.edit.rearrange.start=Wszystkie na pocz\u0105tek \u015Bcie\u017Cki +menu.edit.rearrange.end=Wszystkie na koniec \u015Bcie\u017Cki +menu.edit.rearrange.nearest=Do najbli\u017Cszego punktu +menu.select=Zakres +menu.select.all=Zaznacz wszystko +menu.select.none=Usu\u0144 zaznaczenie +menu.select.start=Zaznacz pocz\u0105tek +menu.select.end=Zaznacz koniec +menu.photo=Zdj\u0119cie +menu.photo.saveexif=Zapisz Exif +menu.photo.connect=Przy\u0142\u0105cz do punktu +menu.photo.disconnect=Od\u0142\u0105cz od punktu +menu.photo.correlate=Skoreluj wszystkie zdj\u0119cia +menu.photo.delete=Usu\u0144 zdj\u0119cie +menu.3d=Operacje 3D +menu.3d.show3d=Poka\u017C model +menu.help=Pomoc +menu.help.about=Prune - Informacje +# Popup menu for map +menu.map.zoomin=Powi\u0119ksz +menu.map.zoomout=Zmniejsz +menu.map.zoomfull=Dostosuj powi\u0119kszenie +menu.map.connect=Connect track punkty +menu.map.autopan=Autopan + +# Dialogs +dialog.exit.confirm.title=Zako\u0144cz Prune +dialog.exit.confirm.text=Your data is not saved. Are you sure you want to exit? +dialog.openappend.title=Append to existing data +dialog.openappend.text=Append this data to the data already loaded? +dialog.deletepoint.title=Usu\u0144 punkt +dialog.deletepoint.deletephoto=Usu\u0144 zdj\u0119cie attached to this punkt? +dialog.deletephoto.title=Usu\u0144 zdj\u0119cie +dialog.deletephoto.deletepoint=Usu\u0144 punkt attached to this zdj\u0119cie? +dialog.deleteduplicates.title=Usu\u0144 Duplicates +dialog.deleteduplicates.single.text=duplicate was deleted +dialog.deleteduplicates.multi.text=duplicates were deleted +dialog.deleteduplicates.nonefound=Brak duplikaty found +dialog.compresstrack.title=Skompresuj scie\u017Ck\u0119 +dialog.compresstrack.parameter.text=Parameter for compression (lower number = more compression) +dialog.compresstrack.text=Track compressed +dialog.compresstrack.single.text=data punkt was removed +dialog.compresstrack.multi.text=data punkty were removed +dialog.compresstrack.nonefound=No data punkty could be removed +dialog.openoptions.title=Otw\u00F3rz opcje +dialog.openoptions.filesnippet=Extract of plik +dialog.load.table.field=Pole +dialog.load.table.datatype=Data Type +dialog.load.table.description=Opis +dialog.delimiter.label=Pole separator +dialog.delimiter.comma=Przecinek , +dialog.delimiter.tab=Tabulator +dialog.delimiter.space=Spacja +dialog.delimiter.semicolon=\u015Arednik ; +dialog.delimiter.other=Inne +dialog.openoptions.deliminfo.records=records, with +dialog.openoptions.deliminfo.fields=pola +dialog.openoptions.deliminfo.norecords=No records +dialog.openoptions.tabledesc=Extract of plik +dialog.openoptions.altitudeunits=Wysoko\u015B\u0107 units +dialog.jpegload.subdirectories=Include subdirectories +dialog.jpegload.loadjpegswithoutcoords=Include zdj\u0119cia without coordinates +dialog.jpegload.progress.title=Loading zdj\u0119cia +dialog.jpegload.progress=Please wait while the zdj\u0119cia are searched +dialog.jpegload.title=Loaded zdj\u0119cia +dialog.jpegload.photoadded=zdj\u0119cie was added +dialog.jpegload.photosadded=zdj\u0119cia were added +dialog.saveoptions.title=Zapisz plik +dialog.save.fieldstosave=Pola to save +dialog.save.table.field=Pole +dialog.save.table.hasdata=Has data +dialog.save.table.save=Zapisz +dialog.save.headerrow=Output header row +dialog.save.coordinateunits=Wsp\u00f3\u0142rz\u0119dne units +dialog.save.altitudeunits=Wysoko\u015B\u0107 units +dialog.save.timestampformat=Timestamp format +dialog.save.oktitle=Plik saved +dialog.save.ok1=Successfully saved +dialog.save.ok2=punkty to plik +dialog.save.overwrite.title=Plik ju\u017C istnieje +dialog.save.overwrite.text=This plik already exists. Are you sure you want to overwrite the plik? +dialog.exportkml.title=Eksportuj KML +dialog.exportkml.text=Tytu\u0142 for the data +dialog.exportkml.altitude=Include altitudes (for aviation) +dialog.exportkml.kmz=Compress to make kmz plik +dialog.exportkml.exportimages=Eksportuj image thumbnails to kmz +dialog.exportkml.filetype=KML, KMZ pliki +dialog.exportgpx.title=Eksportuj GPX +dialog.exportgpx.name=Nazwa +dialog.exportgpx.desc=Opis +dialog.exportgpx.filetype=GPX pliki +dialog.exportpov.title=Eksportuj POV +dialog.exportpov.text=Please enter the parameters for the POV export +dialog.exportpov.font=Czcionka +dialog.exportpov.camerax=Camera X +dialog.exportpov.cameray=Camera Y +dialog.exportpov.cameraz=Camera Z +dialog.exportpov.filetype=POV pliki +dialog.exportpov.warningtracksize=This track has a large number of punkty, which Java3D might not be able to display.\nCzy chcesz kontynuowa\u0107? +dialog.confirmreversetrack.title=Confirm reversal +dialog.confirmreversetrack.text=This track contains timestamp information, which will be out of sequence after a reversal.\nAre you sure you want to reverse this section? +dialog.interpolate.title=Interpoluj punkty +dialog.interpolate.parameter.text=Number of punkty to insert between selected punkty +dialog.undo.title=Cofnij action(s) +dialog.undo.pretext=Please select the action(s) to undo +dialog.confirmundo.title=Operation(s) undone +dialog.confirmundo.single.text=operation undone. +dialog.confirmundo.multiple.text=operations undone. +dialog.undo.none.title=Cannot undo +dialog.undo.none.text=No operations to undo! +dialog.clearundo.title=Wyczy\u015B\u0107 list\u0119 zmian +dialog.clearundo.text=Are you sure you want to clear the undo list?\nAll undo information will be lost! +dialog.pointedit.title=Edytuj punkt +dialog.pointedit.text=Select each field to edit and use the 'Edit' button to change the value +dialog.pointedit.table.field=Pole +dialog.pointedit.table.value=Value +dialog.pointedit.table.changed=Zmieniony +dialog.pointedit.changevalue.text=Enter the new value for this field +dialog.pointedit.changevalue.title=Edytuj field +dialog.pointnameedit.title=Zmie\u0144 nazw\u0119 punktu po\u015Bredniego +dialog.pointnameedit.name=Waypoint nazwa +dialog.pointnameedit.uppercase=UPPER case +dialog.pointnameedit.lowercase=lower case +dialog.pointnameedit.sentencecase=Sentence case +dialog.saveexif.title=Zapisz Exif +dialog.saveexif.intro=Select the zdj\u0119cia to save using the checkboxes +dialog.saveexif.nothingtosave=Coordinate data is unchanged, nothing to save +dialog.saveexif.noexiftool=No exiftool program could be found. Continue? +dialog.saveexif.table.photoname=Nazwa zdj\u0119cie +dialog.saveexif.table.status=Status +dialog.saveexif.table.save=Zapisz +dialog.saveexif.photostatus.connected=Connected +dialog.saveexif.photostatus.disconnected=Disconnected +dialog.saveexif.photostatus.modified=Modified +dialog.saveexif.overwrite=Overwrite pliki +dialog.saveexif.ok1=Saved +dialog.saveexif.ok2=zdj\u0119cia pliki +dialog.correlate.title=Skoreluj zdj\u0119cie +dialog.correlate.notimestamps=There are no timestamps in the data punkty, so there is nothing to correlate with the zdj\u0119cia. +dialog.correlate.nouncorrelatedphotos=There are no uncorrelated zdj\u0119cia.\nAre you sure you want to continue? +dialog.correlate.photoselect.intro=Select one of these correlated zdj\u0119cia to use as the time offset +dialog.correlate.photoselect.photoname=Nazwa zdj\u0119cie +dialog.correlate.photoselect.timediff=Time difference +dialog.correlate.photoselect.photolater=Zdj\u0119cie later +dialog.correlate.options.tip=Tip: By manually correlating at least one zdj\u0119cie, the time offset can be calculated for you. +dialog.correlate.options.intro=Select the options for automatic correlation +dialog.correlate.options.offsetpanel=Time offset +dialog.correlate.options.offset=Offset +dialog.correlate.options.offset.hours=hours, +dialog.correlate.options.offset.minutes=minuty i +dialog.correlate.options.offset.seconds=seconds +dialog.correlate.options.photolater=Zdj\u0119cie po punkt +dialog.correlate.options.pointlater=Punkt po zdj\u0119cie +dialog.correlate.options.limitspanel=Correlation limits +dialog.correlate.options.notimelimit=No time limit +dialog.correlate.options.timelimit=Time limit +dialog.correlate.options.nodistancelimit=No distance limit +dialog.correlate.options.distancelimit=Distance limit +dialog.correlate.options.correlate=Correlate +dialog.correlate.alloutsiderange=All zdj\u0119cia are outside the time range of the track, so none can be correlated.\nTry changing the offset or manually correlating at least one zdj\u0119cie. +dialog.correlate.confirmsingle.text=zdj\u0119cie was correlated +dialog.correlate.confirmmultiple.text=zdj\u0119cia were correlated +dialog.help.help=Please see\n http://activityworkshop.net/software/prune/\nfor more information and user guides. +dialog.about.title=Prune Informacje +dialog.about.version=Wersja +dialog.about.build=Build +dialog.about.summarytext1=Prune is a program for loading, displaying and editing data from GPS receivers. +dialog.about.summarytext2=It is released under the Gnu GPL for free, open, worldwide use and enhancement.
Copying, redistribution and modification are permitted and encouraged
according to the conditions in the included license.txt file. +dialog.about.summarytext3=Please see http://activityworkshop.net/ for more information and user guides. +dialog.about.translatedby=Tekst po polsku by Piotr. +dialog.about.systeminfo=System info +dialog.about.systeminfo.os=Operating System +dialog.about.systeminfo.java=Java Runtime +dialog.about.systeminfo.java3d=Java3d zainstalowana +dialog.about.systeminfo.povray=Povray zainstalowana +dialog.about.systeminfo.exiftool=Exiftool zainstalowana +dialog.about.yes=Tak +dialog.about.no=Nie +dialog.about.credits=Credits +dialog.about.credits.code=Prune code written by +dialog.about.credits.exifcode=Exif code by +dialog.about.credits.icons=Some icons taken from +dialog.about.credits.translators=Translators +dialog.about.credits.translations=T\u0142umaczenie helped by +dialog.about.credits.devtools=Development tools +dialog.about.credits.othertools=Other tools +dialog.about.credits.thanks=Dzi\u0119kuje to + +# 3d window +dialog.3d.title=Prune tr\u00f3jwymiarowa model +dialog.3d.altitudecap=Minimum altitude range +dialog.3dlines.title=Prune gridlines +dialog.3dlines.empty=No gridlines to display! +dialog.3dlines.intro=These are the gridlines for the three-d view + +# Buttons +button.ok=OK +button.back=Poprzedni +button.next=Nast\u0119pny +button.finish=Finish +button.cancel=Anuluj +button.overwrite=Overwrite +button.moveup=Do g\u00F3ry +button.movedown=Move down +button.showlines=Show lines +button.edit=Edycja +button.exit=Zako\u0144cz +button.close=Zamknij +button.continue=Continue +button.yes=Tak +button.no=Nie +button.yestoall=Tak to all +button.notoall=Nie to all +button.selectall=Select all +button.selectnone=Select none +button.preview=Preview +button.guessfields=Guess fields + +# Display components +display.nodata=No data loaded +display.noaltitudes=Track data does not include altitudes +details.trackdetails=Track szczeg\u00F3\u0142y +details.notrack=No track loaded +details.track.points=Punkty +details.track.file=Plik +details.track.numfiles=Number ze pliki +details.pointdetails=Punkt szczeg\u00F3\u0142y +details.index.selected=Index +details.index.of=of +details.nopointselection=No punkt selected +details.photofile=Plik zdj\u0119cie +details.norangeselection=No range selected +details.rangedetails=Range szczeg\u00F3\u0142y +details.range.selected=Selected +details.range.to=to +details.altitude.to=to +details.range.climb=Climb +details.range.descent=Descent +details.coordformat=Wsp\u00f3\u0142rz\u0119dne format +details.distanceunits=Distance units +display.range.time.secs=s +display.range.time.mins=m +display.range.time.hours=h +display.range.time.days=d +details.waypointsphotos.waypoints=Waypoints +details.waypointsphotos.photos=Zdj\u0119cia +details.photodetails=Zdj\u0119cie szczeg\u00F3\u0142y +details.nophoto=No zdj\u0119cie selected +details.photo.loading=Wczytywanie +details.photo.connected=Connected + +# Field names +fieldname.latitude=Szeroko\u015B\u0107 +fieldname.longitude=D\u0142ugo\u015B\u0107 +fieldname.altitude=Wysoko\u015B\u0107 +fieldname.timestamp=Timestamp +fieldname.waypointname=Nazwa +fieldname.waypointtype=Type +fieldname.newsegment=Segment +fieldname.custom=U\u017Cytkownika +fieldname.prefix=Pole +fieldname.distance=Distance +fieldname.duration=Duration + +# Measurement units +units.original=Oryginalny +units.default=Default +units.metres=Metres +units.metres.short=m +units.feet=Feet +units.feet.short=ft +units.kilometres=Kilometres +units.kilometres.short=km +units.miles=Miles +units.miles.short=mi +units.degminsec=Deg-min-sek +units.degmin=Deg-min +units.deg=Degrees +units.iso8601=ISO 8601 + +# Cardinals for 3d plots +cardinal.n=N +cardinal.s=S +cardinal.e=E +cardinal.w=W + +# Undo operations +undo.load=load data +undo.loadphotos=load zdj\u0119cia +undo.editpoint=edycja punkt +undo.deletepoint=usu\u0144 punkt +undo.deletephoto=remove zdj\u0119cie +undo.deleterange=usu\u0144 range +undo.compress=compress track +undo.insert=insert punkty +undo.deleteduplicates=usu\u0144 duplicates +undo.reverse=reverse range +undo.rearrangewaypoints=rearrange waypoints +undo.connectphoto=connect zdj\u0119cie +undo.disconnectphoto=disconnect zdj\u0119cie +undo.correlate=correlate zdj\u0119cia + +# Error messages +error.save.dialogtitle=Error saving data +error.save.nodata=No data to save +error.save.failed=Failed to save the data to plik: +error.saveexif.filenotfound=Failed to find zdj\u0119cie plik +error.saveexif.cannotoverwrite1=Zdj\u0119cie plik +error.saveexif.cannotoverwrite2=is read-only and can't be overwritten. Write to copy? +error.load.dialogtitle=B\u0142\u0105d loading data +error.load.noread=Cannot read plik +error.load.nopoints=No coordinate information found in the plik +error.load.unknownxml=Nieznany xml format: +error.load.othererror=B\u0142\u0105d reading plik: +error.jpegload.dialogtitle=B\u0142\u0105d loading zdj\u0119cia +error.jpegload.nofilesfound=No pliki found +error.jpegload.nojpegsfound=No jpeg pliki found +error.jpegload.noexiffound=No EXIF information found +error.jpegload.nogpsfound=No GPS information found +error.undofailed.title=Undo failed +error.undofailed.text=Failed to undo operation +error.function.noop.title=Function had no effect +error.rearrange.noop=Rearranging waypoints had no effect +error.function.notimplemented=Sorry, this function has not yet been implemented. +error.function.notavailable.title=Function not available +error.function.nojava3d=This function requires the Java3d library,\navailable from Sun.com or Blackdown.org. +error.3d.title=B\u0142\u0105d in 3d display +error.3d=A b\u0142\u0105d occurred with the 3d display diff --git a/tim/prune/load/DelimiterInfo.java b/tim/prune/load/DelimiterInfo.java index d882972..0bb6b73 100644 --- a/tim/prune/load/DelimiterInfo.java +++ b/tim/prune/load/DelimiterInfo.java @@ -21,41 +21,50 @@ public class DelimiterInfo _delimiter = inChar; } + /** @return the delimiter character */ public char getDelimiter() { return _delimiter; } + /** @return the max number of fields */ public int getMaxFields() { return _maxFields; } - public void updateMaxFields(int inNumields) + /** @param inNumFields number of fields */ + public void updateMaxFields(int inNumFields) { - if (inNumields > _maxFields) - _maxFields = inNumields; + if (inNumFields > _maxFields) + _maxFields = inNumFields; } - + /** @return the number of records */ public int getNumRecords() { return _numRecords; } + + /** Increment the number of records */ public void incrementNumRecords() { _numRecords++; } + /** @return the number of times this delimiter has won */ public int getNumWinningRecords() { return _numWinningRecords; } + + /** Increment the number of times this delimiter has won */ public void incrementNumWinningRecords() { _numWinningRecords++; } + /** @return String for debug */ public String toString() { return "(delim:" + _delimiter + " fields:" + _maxFields + ", records:" + _numRecords + ")"; diff --git a/tim/prune/load/FieldGuesser.java b/tim/prune/load/FieldGuesser.java new file mode 100644 index 0000000..032874a --- /dev/null +++ b/tim/prune/load/FieldGuesser.java @@ -0,0 +1,273 @@ +package tim.prune.load; + +import tim.prune.I18nManager; +import tim.prune.data.Field; +import tim.prune.data.Latitude; +import tim.prune.data.Longitude; +import tim.prune.data.Timestamp; + +/** + * Class to try to match data with field names, + * using a variety of guessing techniques + */ +public abstract class FieldGuesser +{ + /** + * Try to guess whether the given line is a header line or data + * @param inValues array of values from first non-blank line of file + * @return true if it looks like a header row, false if it looks like data + */ + private static boolean isHeaderRow(String[] inValues) + { + // Loop over values looking for a Latitude value + if (inValues != null) + { + for (int v=0; v 0 && intValue < 100000); + } + catch (NumberFormatException nfe) {} + return false; + } + } + + + /** + * Check whether the given String looks like a waypoint name + * @param inValue value from file + * @param inIsHeader true if this is a header line, false for data + * @return true if it could be a name + */ + private static boolean fieldLooksLikeName(String inValue, boolean inIsHeader) + { + if (inValue == null || inValue.equals("")) {return false;} + if (inIsHeader) + { + // This is a header line so look for english or local text + String upperValue = inValue.toUpperCase(); + return (upperValue.equals("NAME") + || upperValue.equals("LABEL") + || upperValue.equals(I18nManager.getText("fieldname.waypointname").toUpperCase())); + } + else + { + // Look for at least two letters in it + int numLetters = 0; + for (int i=0; i= 2; + } + } + + /** + * Check whether the given String looks like a timestamp + * @param inValue value from file + * @param inIsHeader true if this is a header line, false for data + * @return true if it could be a timestamp + */ + private static boolean fieldLooksLikeTimestamp(String inValue, boolean inIsHeader) + { + if (inValue == null || inValue.equals("")) {return false;} + if (inIsHeader) + { + String upperValue = inValue.toUpperCase(); + // This is a header line so look for english or local text + return (upperValue.equals("TIMESTAMP") + || upperValue.equals("TIME") + || upperValue.equals(I18nManager.getText("fieldname.timestamp").toUpperCase())); + } + else + { + // must be at least 7 characters long + if (inValue.length() < 7) {return false;} + Timestamp stamp = new Timestamp(inValue); + return stamp.isValid(); + } + } +} diff --git a/tim/prune/load/FieldSelectionTableModel.java b/tim/prune/load/FieldSelectionTableModel.java index 11b60d9..6814e2b 100644 --- a/tim/prune/load/FieldSelectionTableModel.java +++ b/tim/prune/load/FieldSelectionTableModel.java @@ -27,7 +27,7 @@ public class FieldSelectionTableModel extends AbstractTableModel /** - * Get the column count + * @return the column count */ public int getColumnCount() { @@ -36,7 +36,8 @@ public class FieldSelectionTableModel extends AbstractTableModel /** - * Get the name of the column + * @param inColNum column number + * @return name of the column */ public String getColumnName(int inColNum) { @@ -47,7 +48,7 @@ public class FieldSelectionTableModel extends AbstractTableModel /** - * Get the row count + * @return the row count */ public int getRowCount() { @@ -58,14 +59,16 @@ public class FieldSelectionTableModel extends AbstractTableModel /** - * Get the value of the specified cell + * @param inRowIndex row index + * @param inColumnIndex column index + * @return the value of the specified cell */ - public Object getValueAt(int rowIndex, int columnIndex) + public Object getValueAt(int inRowIndex, int inColumnIndex) { if (_fieldArray == null) return ""; - if (columnIndex == 0) return ("" + (rowIndex+1)); - Field field = _fieldArray[rowIndex]; - if (columnIndex == 1) + if (inColumnIndex == 0) return ("" + (inRowIndex+1)); + Field field = _fieldArray[inRowIndex]; + if (inColumnIndex == 1) { // Field name - take name from built-in fields if (field.isBuiltIn()) @@ -81,13 +84,16 @@ public class FieldSelectionTableModel extends AbstractTableModel /** * Make sure only second and third columns are editable + * @param inRowIndex row index + * @param inColumnIndex column index + * @return true if cell editable */ - public boolean isCellEditable(int rowIndex, int columnIndex) + public boolean isCellEditable(int inRowIndex, int inColumnIndex) { - if (columnIndex <= 1) - return (columnIndex == 1); + if (inColumnIndex <= 1) + return (inColumnIndex == 1); // Column is 2 so only edit non-builtin field names - Field field = _fieldArray[rowIndex]; + Field field = _fieldArray[inRowIndex]; return !field.isBuiltIn(); } @@ -109,24 +115,27 @@ public class FieldSelectionTableModel extends AbstractTableModel /** * React to edits to the table data + * @param inValue value to set + * @param inRowIndex row index + * @param inColumnIndex column index */ - public void setValueAt(Object aValue, int rowIndex, int columnIndex) + public void setValueAt(Object inValue, int inRowIndex, int inColumnIndex) { - super.setValueAt(aValue, rowIndex, columnIndex); - if (columnIndex == 1) + super.setValueAt(inValue, inRowIndex, inColumnIndex); + if (inColumnIndex == 1) { - Field field = _fieldArray[rowIndex]; - if (!field.getName().equals(aValue.toString())) + Field field = _fieldArray[inRowIndex]; + if (!field.getName().equals(inValue.toString())) { - manageFieldChange(rowIndex, aValue.toString()); + manageFieldChange(inRowIndex, inValue.toString()); } } - else if (columnIndex == 2) + else if (inColumnIndex == 2) { // change description if it's custom - Field field = _fieldArray[rowIndex]; + Field field = _fieldArray[inRowIndex]; if (!field.isBuiltIn()) - field.setName(aValue.toString()); + field.setName(inValue.toString()); } } diff --git a/tim/prune/load/FileCacher.java b/tim/prune/load/FileCacher.java index ad0f0ef..63c8b24 100644 --- a/tim/prune/load/FileCacher.java +++ b/tim/prune/load/FileCacher.java @@ -77,7 +77,8 @@ public class FileCacher /** * Get the top section of the file for preview - * @param inSize number of lines to extract + * @param inNumRows number of lines to extract + * @param inMaxWidth max length of Strings (longer ones will be chopped) * @return String array containing non-blank lines from the file */ public String[] getSnippet(int inNumRows, int inMaxWidth) diff --git a/tim/prune/load/FileSplitter.java b/tim/prune/load/FileSplitter.java index 216e027..39cd36c 100644 --- a/tim/prune/load/FileSplitter.java +++ b/tim/prune/load/FileSplitter.java @@ -1,8 +1,5 @@ package tim.prune.load; -import tim.prune.I18nManager; -import tim.prune.data.Field; - /** * Class responsible for splitting the file contents into an array * based on the selected delimiter character @@ -13,6 +10,8 @@ public class FileSplitter private int _numRows = 0; private int _numColumns = 0; private boolean[] _columnStates = null; + private String[] _firstFullRow = null; + /** * Constructor @@ -30,6 +29,7 @@ public class FileSplitter */ public String[][] splitFieldData(char inDelim) { + _firstFullRow = null; if (_cacher == null) return null; String[] contents = _cacher.getContents(); if (contents == null || contents.length == 0) return null; @@ -46,6 +46,7 @@ public class FileSplitter if (splitLine != null && splitLine.length > maxFields) { maxFields = splitLine.length; + _firstFullRow = splitLine; } } } @@ -100,6 +101,14 @@ public class FileSplitter return _numColumns; } + /** + * @return the fields in the first full row + */ + public String[] getFirstFullRow() + { + return _firstFullRow; + } + /** * Check if the specified column of the data is blank @@ -111,37 +120,4 @@ public class FileSplitter // Should probably trap out of range values return !_columnStates[inColumnNum]; } - - - /** - * @return a Field array to use as defaults for the data - */ - public Field[] makeDefaultFields() - { - Field[] fields = null; - if (_numColumns > 0) - { - fields = new Field[_numColumns]; - try - { - fields[0] = Field.LATITUDE; - fields[1] = Field.LONGITUDE; - fields[2] = Field.ALTITUDE; - fields[3] = Field.WAYPT_NAME; - fields[4] = Field.WAYPT_TYPE; - String customPrefix = I18nManager.getText("fieldname.prefix") + " "; - for (int i=5;; i++) - { - fields[i] = new Field(customPrefix + (i+1)); - } - } - catch (ArrayIndexOutOfBoundsException finished) - { - // Finished populating array - } - } - else - fields = new Field[0]; - return fields; - } } diff --git a/tim/prune/load/JpegLoader.java b/tim/prune/load/JpegLoader.java index e6917c1..a2453be 100644 --- a/tim/prune/load/JpegLoader.java +++ b/tim/prune/load/JpegLoader.java @@ -3,7 +3,7 @@ package tim.prune.load; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; -import java.util.ArrayList; +import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.BoxLayout; @@ -45,7 +45,7 @@ public class JpegLoader implements Runnable private JProgressBar _progressBar = null; private int[] _fileCounts = null; private boolean _cancelled = false; - private ArrayList _photos = null; + private TreeSet _photos = null; /** @@ -128,7 +128,7 @@ public class JpegLoader implements Runnable { // Initialise arrays, errors, summaries _fileCounts = new int[4]; // files, jpegs, exifs, gps - _photos = new ArrayList(); + _photos = new TreeSet(new PhotoSorter()); File[] files = _fileChooser.getSelectedFiles(); // Loop recursively over selected files/directories to count files int numFiles = countFileList(files, true, _subdirCheckbox.isSelected()); @@ -244,9 +244,9 @@ public class JpegLoader implements Runnable {_fileCounts[2]++;} // exif found if (jpegData.isValid()) { - if (jpegData.getDatestamp() != null && jpegData.getTimestamp() != null) + if (jpegData.getGpsDatestamp() != null && jpegData.getGpsTimestamp() != null) { - photo.setTimestamp(createTimestamp(jpegData.getDatestamp(), jpegData.getTimestamp())); + photo.setTimestamp(createTimestamp(jpegData.getGpsDatestamp(), jpegData.getGpsTimestamp())); } // Make DataPoint and attach to Photo DataPoint point = createDataPoint(jpegData); @@ -255,6 +255,12 @@ public class JpegLoader implements Runnable photo.setOriginalStatus(PhotoStatus.TAGGED); _fileCounts[3]++; } + // Use exif timestamp if gps timestamp not available + if (photo.getTimestamp() == null && jpegData.getOriginalTimestamp() != null) + { + photo.setTimestamp(createTimestamp(jpegData.getOriginalTimestamp())); + } + photo.setExifThumbnail(jpegData.getThumbnailImage()); } catch (JpegException jpe) { // don't list errors, just count them } @@ -367,6 +373,28 @@ public class JpegLoader implements Runnable } + /** + * Use the given String value to create a timestamp + * @param inStamp timestamp from exif + * @return Timestamp object corresponding to input + */ + private static Timestamp createTimestamp(String inStamp) + { + Timestamp stamp = null; + try + { + stamp = new Timestamp(Integer.parseInt(inStamp.substring(0, 4)), + Integer.parseInt(inStamp.substring(5, 7)), + Integer.parseInt(inStamp.substring(8, 10)), + Integer.parseInt(inStamp.substring(11, 13)), + Integer.parseInt(inStamp.substring(14, 16)), + Integer.parseInt(inStamp.substring(17))); + } + catch (NumberFormatException nfe) {} + return stamp; + } + + /** * Check whether to accept the given filename * @param inName name of file diff --git a/tim/prune/load/PhotoMeasurer.java b/tim/prune/load/PhotoMeasurer.java deleted file mode 100644 index 710508a..0000000 --- a/tim/prune/load/PhotoMeasurer.java +++ /dev/null @@ -1,60 +0,0 @@ -package tim.prune.load; - -import tim.prune.data.Photo; -import tim.prune.data.PhotoList; - -/** - * This class starts a new thread to preload image sizes - * TODO: # Cache small image thumbnails too? - */ -public class PhotoMeasurer implements Runnable -{ - /** PhotoList to loop through */ - private PhotoList _photoList = null; - - - /** - * Constructor - * @param inPhotoList photo list to loop through - */ - public PhotoMeasurer(PhotoList inPhotoList) - { - _photoList = inPhotoList; - } - - - /** - * Start off the process to measure the photo sizes - */ - public void measurePhotos() - { - // check if any photos in list - if (_photoList != null && _photoList.getNumPhotos() > 0) - { - // start new thread - new Thread(this).start(); - } - } - - - /** - * Run method called in new thread - */ - public void run() - { - try - { - // loop over all photos in list - for (int i=0; i<_photoList.getNumPhotos(); i++) - { - Photo photo = _photoList.getPhoto(i); - if (photo != null) - { - // call get size method which will calculate it if necessary - photo.getSize(); - } - } - } - catch (ArrayIndexOutOfBoundsException obe) {} // ignore, must have been changed by other thread - } -} diff --git a/tim/prune/load/PhotoSorter.java b/tim/prune/load/PhotoSorter.java new file mode 100644 index 0000000..7e65cac --- /dev/null +++ b/tim/prune/load/PhotoSorter.java @@ -0,0 +1,32 @@ +package tim.prune.load; + +import java.io.File; +import java.util.Comparator; + +import tim.prune.data.Photo; + +/** + * Class to sort photos by name + */ +public class PhotoSorter implements Comparator +{ + + /** + * Compare two photos + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Object o1, Object o2) + { + File file1 = ((Photo) o1).getFile(); + File file2 = ((Photo) o2).getFile(); + int nameComp = file1.getName().compareTo(file2.getName()); + if (nameComp == 0) + { + // names same, maybe in different directories + return file1.getAbsolutePath().compareTo(file2.getAbsolutePath()); + } + // names different + return nameComp; + } + +} diff --git a/tim/prune/load/TextFileLoader.java b/tim/prune/load/TextFileLoader.java index b692631..977845a 100644 --- a/tim/prune/load/TextFileLoader.java +++ b/tim/prune/load/TextFileLoader.java @@ -99,8 +99,8 @@ public class TextFileLoader /** - * Open the selected file and show the GUI dialog - * to select load options + * Open the selected file and show the GUI dialog to select load options + * @param inFile file to open */ public void openFile(File inFile) { @@ -364,7 +364,16 @@ public class TextFileLoader } }); innerPanel3.add(_moveDownButton); - innerPanel3.add(Box.createVerticalStrut(70)); + innerPanel3.add(Box.createVerticalStrut(60)); + JButton guessButton = new JButton(I18nManager.getText("button.guessfields")); + guessButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _lastSelectedFields = null; + prepareSecondPanel(); + } + }); + innerPanel3.add(guessButton); innerPanel2.add(innerPanel3, BorderLayout.EAST); secondCard.add(innerPanel2, BorderLayout.CENTER); @@ -458,11 +467,8 @@ public class TextFileLoader FileSplitter splitter = new FileSplitter(_fileCacher); // Check info makes sense - num fields > 0, num records > 0 // set "Finished" button to disabled if not ok - // TODO: Work out if there are header rows or not, save? - // Try to match header rows with fields - // Try to match data with fields // Add data to GUI elements - Object[][] tableData = splitter.splitFieldData(info.getDelimiter()); + String[][] tableData = splitter.splitFieldData(info.getDelimiter()); // possible to ignore blank columns here _currentDelimiter = info.getDelimiter(); _fileExtractTableModel.updateData(tableData); @@ -471,9 +477,15 @@ public class TextFileLoader // Check number of fields and use last ones if count matches Field[] startFieldArray = null; if (_lastSelectedFields != null && splitter.getNumColumns() == _lastSelectedFields.length) + { startFieldArray = _lastSelectedFields; + } else - startFieldArray = splitter.makeDefaultFields(); + { + // Take first full row of file and use it to guess fields + startFieldArray = FieldGuesser.guessFields(splitter.getFirstFullRow()); + } + _fieldTableModel.updateData(startFieldArray); _fieldTable.setModel(_fieldTableModel); // add dropdowns to second column diff --git a/tim/prune/load/xml/GpxHandler.java b/tim/prune/load/xml/GpxHandler.java index 962b495..771621b 100644 --- a/tim/prune/load/xml/GpxHandler.java +++ b/tim/prune/load/xml/GpxHandler.java @@ -13,6 +13,7 @@ import tim.prune.data.Field; */ public class GpxHandler extends XmlHandler { + private boolean _insideWaypoint = false; private boolean _insideName = false; private boolean _insideElevation = false; private boolean _insideTime = false; @@ -27,11 +28,12 @@ public class GpxHandler extends XmlHandler * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) */ public void startElement(String uri, String localName, String qName, - Attributes attributes) throws SAXException + Attributes attributes) throws SAXException { // Read the parameters for waypoints and track points if (qName.equalsIgnoreCase("wpt") || qName.equalsIgnoreCase("trkpt")) { + _insideWaypoint = qName.equalsIgnoreCase("wpt"); int numAttributes = attributes.getLength(); for (int i=0; i\n\n"); + // Name field + if (_nameField != null && _nameField.getText() != null && !_nameField.getText().equals("")) + { + inWriter.write("\t"); + inWriter.write(_nameField.getText()); + inWriter.write("\n"); + } + // Description field + inWriter.write("\t"); + if (_descriptionField != null && _descriptionField.getText() != null && !_descriptionField.getText().equals("")) + { + inWriter.write(_descriptionField.getText()); + } + else + { + inWriter.write("Export from Prune"); + } + inWriter.write("\n"); + + int i = 0; + DataPoint point = null; + boolean hasTrackpoints = false; + // Loop over waypoints + int numPoints = _track.getNumPoints(); + for (i=0; i\n"); + // Loop over track points + for (i=0; i\n"); + } + inWriter.write("\n"); + return numPoints; + } + + + /** + * Export the specified waypoint into the file + * @param inPoint waypoint to export + * @param inWriter writer object + * @throws IOException on write failure + */ + private void exportWaypoint(DataPoint inPoint, Writer inWriter) throws IOException + { + inWriter.write("\t\n"); + inWriter.write("\t\t"); + inWriter.write(inPoint.getWaypointName().trim()); + inWriter.write("\n"); + // altitude if available + if (inPoint.hasAltitude()) + { + inWriter.write("\t\t"); + inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + inWriter.write("\n"); + } + // timestamp if available (point might have altitude and then be turned into a waypoint) + if (inPoint.hasTimestamp()) + { + inWriter.write("\t\t\n"); + } + // TODO: Include waypt type in Gpx + inWriter.write("\t\n"); + } + + + /** + * Export the specified trackpoint into the file + * @param inPoint trackpoint to export + * @param inWriter writer object + */ + private void exportTrackpoint(DataPoint inPoint, Writer inWriter) throws IOException + { + inWriter.write("\t\t"); + // altitude + if (inPoint.hasAltitude()) + { + inWriter.write(""); + inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + inWriter.write(""); + } + // timestamp if available + if (inPoint.hasTimestamp()) + { + inWriter.write(""); + } + inWriter.write("\n"); + } +} diff --git a/tim/prune/save/KmlExporter.java b/tim/prune/save/KmlExporter.java index 11474a6..c091d0c 100644 --- a/tim/prune/save/KmlExporter.java +++ b/tim/prune/save/KmlExporter.java @@ -35,8 +35,10 @@ import javax.swing.SwingConstants; import javax.swing.filechooser.FileFilter; import tim.prune.I18nManager; +import tim.prune.data.Altitude; import tim.prune.data.Coordinate; import tim.prune.data.DataPoint; +import tim.prune.data.Field; import tim.prune.data.Track; import tim.prune.data.TrackInfo; import tim.prune.gui.ImageUtils; @@ -52,6 +54,7 @@ public class KmlExporter implements Runnable private Track _track = null; private JDialog _dialog = null; private JTextField _descriptionField = null; + private JCheckBox _altitudesCheckbox = null; private JCheckBox _kmzCheckbox = null; private JCheckBox _exportImagesCheckbox = null; private JProgressBar _progressBar = null; @@ -116,6 +119,10 @@ public class KmlExporter implements Runnable descPanel.add(_descriptionField); mainPanel.add(descPanel); dialogPanel.add(mainPanel, BorderLayout.CENTER); + // Checkbox for altitude export + _altitudesCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.altitude")); + _altitudesCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); + mainPanel.add(_altitudesCheckbox); // Checkboxes for kmz export and image export _kmzCheckbox = new JCheckBox(I18nManager.getText("dialog.exportkml.kmz")); _kmzCheckbox.setHorizontalTextPosition(SwingConstants.LEFT); @@ -166,6 +173,8 @@ public class KmlExporter implements Runnable */ private void enableCheckboxes() { + boolean hasAltitudes = _track.hasData(Field.ALTITUDE); + if (!hasAltitudes) {_altitudesCheckbox.setSelected(false);} boolean hasPhotos = _trackInfo.getPhotoList() != null && _trackInfo.getPhotoList().getNumPhotos() > 0; _exportImagesCheckbox.setSelected(hasPhotos && _kmzCheckbox.isSelected()); _exportImagesCheckbox.setEnabled(hasPhotos && _kmzCheckbox.isSelected()); @@ -339,6 +348,7 @@ public class KmlExporter implements Runnable } inWriter.write("\n"); + boolean exportAltitudes = _altitudesCheckbox.isSelected(); int i = 0; DataPoint point = null; boolean hasTrackpoints = false; @@ -352,7 +362,11 @@ public class KmlExporter implements Runnable // Make a blob for each waypoint if (point.isWaypoint()) { - exportWaypoint(point, inWriter); + exportWaypoint(point, inWriter, exportAltitudes); + } + else if (point.getPhoto() == null) + { + hasTrackpoints = true; } // Make a blob with description for each photo if (point.getPhoto() != null) @@ -363,11 +377,7 @@ public class KmlExporter implements Runnable writtenPhotoHeader = true; } photoNum++; - exportPhotoPoint(point, inWriter, inExportImages, photoNum); - } - else - { - hasTrackpoints = true; + exportPhotoPoint(point, inWriter, inExportImages, photoNum, exportAltitudes); } } // Make a line for the track, if there is one @@ -375,14 +385,19 @@ public class KmlExporter implements Runnable { inWriter.write("\t\n\t\ttrack\n\t\t\n\t\t\n\t\t\t"); + + "\t\t\t33cc0000\n" + + "\t\t\n\t\t\n"); + if (exportAltitudes) { + inWriter.write("\t\t\t1\n\t\t\tabsolute\n"); + } + inWriter.write("\t\t\t"); // Loop over track points for (i=0; i\n\t\t\n\t"); @@ -396,18 +411,30 @@ public class KmlExporter implements Runnable * Export the specified waypoint into the file * @param inPoint waypoint to export * @param inWriter writer object + * @param inExportAltitude true to include altitude * @throws IOException on write failure */ - private void exportWaypoint(DataPoint inPoint, Writer inWriter) throws IOException + private void exportWaypoint(DataPoint inPoint, Writer inWriter, boolean inExportAltitude) throws IOException { inWriter.write("\t\n\t\t"); inWriter.write(inPoint.getWaypointName().trim()); inWriter.write("\n"); - inWriter.write("\t\t\n\t\t\t"); + inWriter.write("\t\t\n"); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("\t\t\tabsolute\n"); + } + inWriter.write("\t\t\t"); inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - inWriter.write(",0\n\t\t\n\t\n"); + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n\t\t\n\t\n"); } @@ -417,9 +444,11 @@ public class KmlExporter implements Runnable * @param inWriter writer object * @param inImageLink flag to set whether to export image links or not * @param inImageNumber number of image for filename + * @param inExportAltitude true to include altitude * @throws IOException on write failure */ - private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, int inImageNumber) + private void exportPhotoPoint(DataPoint inPoint, Writer inWriter, boolean inImageLink, + int inImageNumber, boolean inExportAltitude) throws IOException { inWriter.write("\t\n\t\t"); @@ -436,11 +465,22 @@ public class KmlExporter implements Runnable + "
Caption for the photo
]]>"); } inWriter.write("#camera_icon\n"); - inWriter.write("\t\t\n\t\t\t"); + inWriter.write("\t\t\n"); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("\t\t\tabsolute\n"); + } + inWriter.write("\t\t\t"); inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - inWriter.write(",0\n\t\t\n\t
\n"); + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n\t\t\n\t\n"); } @@ -448,14 +488,22 @@ public class KmlExporter implements Runnable * Export the specified trackpoint into the file * @param inPoint trackpoint to export * @param inWriter writer object + * @param inExportAltitude true to include altitude */ - private void exportTrackpoint(DataPoint inPoint, Writer inWriter) throws IOException + private void exportTrackpoint(DataPoint inPoint, Writer inWriter, boolean inExportAltitude) throws IOException { inWriter.write(inPoint.getLongitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); inWriter.write(','); inWriter.write(inPoint.getLatitude().output(Coordinate.FORMAT_DEG_WITHOUT_CARDINAL)); - // Altitude not exported, locked to ground by Google Earth - inWriter.write(",0\n"); + // Altitude either absolute or locked to ground by Google Earth + inWriter.write(","); + if (inExportAltitude && inPoint.hasAltitude()) { + inWriter.write("" + inPoint.getAltitude().getValue(Altitude.FORMAT_METRES)); + } + else { + inWriter.write("0"); + } + inWriter.write("\n"); } diff --git a/tim/prune/save/PhotoTableModel.java b/tim/prune/save/PhotoTableModel.java index fd66590..9b616f2 100644 --- a/tim/prune/save/PhotoTableModel.java +++ b/tim/prune/save/PhotoTableModel.java @@ -15,6 +15,7 @@ public class PhotoTableModel extends AbstractTableModel /** * Constructor giving list size + * @param inSize number of photos */ public PhotoTableModel(int inSize) { @@ -75,7 +76,7 @@ public class PhotoTableModel extends AbstractTableModel { return _photos[inRowIndex].getStatus(); } - return new Boolean(_photos[inRowIndex].getSaveFlag()); + return Boolean.valueOf(_photos[inRowIndex].getSaveFlag()); } diff --git a/tim/prune/save/PovExporter.java b/tim/prune/save/PovExporter.java index 3696dd1..aacee11 100644 --- a/tim/prune/save/PovExporter.java +++ b/tim/prune/save/PovExporter.java @@ -185,7 +185,7 @@ public class PovExporter JPanel flowPanel = new JPanel(); flowPanel.add(centralPanel); - + // show lines button JButton showLinesButton = new JButton(I18nManager.getText("button.showlines")); showLinesButton.addActionListener(new ActionListener() { diff --git a/tim/prune/save/UpDownToggler.java b/tim/prune/save/UpDownToggler.java index 274bfe9..ffdc1d6 100644 --- a/tim/prune/save/UpDownToggler.java +++ b/tim/prune/save/UpDownToggler.java @@ -13,21 +13,27 @@ public class UpDownToggler implements ListSelectionListener { private JButton _upButton = null; private JButton _downButton = null; - private int _maxIndex = 0; + private int _maxIndex = 2; /** - * Constructor giving buttons and size + * Constructor giving buttons to enable/disable * @param inUpButton up button * @param inDownButton down button - * @param inListSize size of list */ - public UpDownToggler(JButton inUpButton, JButton inDownButton, int inListSize) + public UpDownToggler(JButton inUpButton, JButton inDownButton) { _upButton = inUpButton; _downButton = inDownButton; - _maxIndex = inListSize - 1; } + /** + * Set the list size + * @param inListSize number of items in list + */ + public void setListSize(int inListSize) + { + _maxIndex = inListSize - 1; + } /** * list selection has changed diff --git a/tim/prune/threedee/ThreeDWindow.java b/tim/prune/threedee/ThreeDWindow.java index 52aeba3..df72bc7 100644 --- a/tim/prune/threedee/ThreeDWindow.java +++ b/tim/prune/threedee/ThreeDWindow.java @@ -17,6 +17,7 @@ public interface ThreeDWindow /** * Show the window + * @throws ThreeDException when 3d classes not found */ public void show() throws ThreeDException; } diff --git a/tim/prune/undo/UndoConnectPhoto.java b/tim/prune/undo/UndoConnectPhoto.java index 99e6432..c5fc2f4 100644 --- a/tim/prune/undo/UndoConnectPhoto.java +++ b/tim/prune/undo/UndoConnectPhoto.java @@ -48,6 +48,8 @@ public class UndoConnectPhoto implements UndoOperation { _point.setPhoto(null); photo.setDataPoint(null); + // inform subscribers + inTrackInfo.triggerUpdate(); } else { diff --git a/tim/prune/undo/UndoCorrelatePhotos.java b/tim/prune/undo/UndoCorrelatePhotos.java new file mode 100644 index 0000000..3c25837 --- /dev/null +++ b/tim/prune/undo/UndoCorrelatePhotos.java @@ -0,0 +1,72 @@ +package tim.prune.undo; + +import tim.prune.I18nManager; +import tim.prune.data.DataPoint; +import tim.prune.data.Photo; +import tim.prune.data.TrackInfo; + +/** + * Operation to undo an auto-correlation of photos with points + */ +public class UndoCorrelatePhotos implements UndoOperation +{ + private DataPoint[] _contents = null; + private DataPoint[] _photoPoints = null; + private int _numPhotosCorrelated = -1; + + + /** + * Constructor + * @param inTrackInfo track information + */ + public UndoCorrelatePhotos(TrackInfo inTrackInfo) + { + // Copy track contents + _contents = inTrackInfo.getTrack().cloneContents(); + // Copy points associated with photos before correlation + int numPhotos = inTrackInfo.getPhotoList().getNumPhotos(); + _photoPoints = new DataPoint[numPhotos]; + for (int i=0; i