source: josm/trunk/src/org/openstreetmap/josm/gui/layer/GpxLayer.java@ 1425

Last change on this file since 1425 was 1425, checked in by stoecker, 15 years ago

fixed #1642. patch by xeen

  • Property svn:eol-style set to native
File size: 54.7 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui.layer;
4
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Color;
10import java.awt.Component;
11import java.awt.Graphics;
12import java.awt.GridBagLayout;
13import java.awt.Point;
14import java.awt.event.ActionEvent;
15import java.awt.event.ActionListener;
16import java.awt.geom.Area;
17import java.awt.geom.Rectangle2D;
18import java.io.BufferedReader;
19import java.io.File;
20import java.io.FileInputStream;
21import java.io.FileOutputStream;
22import java.io.InputStreamReader;
23import java.net.URL;
24import java.net.URLConnection;
25import java.net.UnknownHostException;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Comparator;
30import java.util.LinkedList;
31import java.util.Date;
32import java.util.List;
33import java.text.DateFormat;
34import java.text.DecimalFormat;
35
36import javax.swing.AbstractAction;
37import javax.swing.Box;
38import javax.swing.ButtonGroup;
39import javax.swing.Icon;
40import javax.swing.JCheckBox;
41import javax.swing.JColorChooser;
42import javax.swing.JFileChooser;
43import javax.swing.JLabel;
44import javax.swing.JList;
45import javax.swing.JMenuItem;
46import javax.swing.JOptionPane;
47import javax.swing.JPanel;
48import javax.swing.JRadioButton;
49import javax.swing.JSeparator;
50import javax.swing.JTextField;
51import javax.swing.filechooser.FileFilter;
52
53import org.openstreetmap.josm.Main;
54import org.openstreetmap.josm.actions.RenameLayerAction;
55import org.openstreetmap.josm.actions.SaveAction;
56import org.openstreetmap.josm.actions.SaveAsAction;
57import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
58import org.openstreetmap.josm.data.coor.EastNorth;
59import org.openstreetmap.josm.data.coor.LatLon;
60import org.openstreetmap.josm.data.gpx.GpxData;
61import org.openstreetmap.josm.data.gpx.GpxRoute;
62import org.openstreetmap.josm.data.gpx.GpxTrack;
63import org.openstreetmap.josm.data.gpx.WayPoint;
64import org.openstreetmap.josm.data.osm.DataSet;
65import org.openstreetmap.josm.data.osm.Node;
66import org.openstreetmap.josm.data.osm.Way;
67import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
68import org.openstreetmap.josm.gui.MapView;
69import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
70import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
71import org.openstreetmap.josm.gui.download.DownloadDialog.DownloadTask;
72import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
73import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
74import org.openstreetmap.josm.io.GpxWriter;
75import org.openstreetmap.josm.io.MultiPartFormOutputStream;
76import org.openstreetmap.josm.tools.DontShowAgainInfo;
77import org.openstreetmap.josm.tools.GBC;
78import org.openstreetmap.josm.tools.ImageProvider;
79import org.openstreetmap.josm.tools.UrlLabel;
80
81public class GpxLayer extends Layer {
82 public GpxData data;
83 private final GpxLayer me;
84 protected static final double PHI = Math.toRadians(15);
85 private boolean computeCacheInSync;
86 private int computeCacheMaxLineLengthUsed;
87 private Color computeCacheColorUsed;
88 private colorModes computeCacheColored;
89 private int computeCacheColorTracksTune;
90
91 public GpxLayer(GpxData d) {
92 super((String) d.attr.get("name"));
93 data = d;
94 me = this;
95 computeCacheInSync = false;
96 }
97
98 public GpxLayer(GpxData d, String name) {
99 this(d);
100 this.name = name;
101 }
102
103 @Override public Icon getIcon() {
104 return ImageProvider.get("layer", "gpx_small");
105 }
106
107 @Override public Object getInfoComponent() {
108 return getToolTipText();
109 }
110
111 static public Color getColor(String name)
112 {
113 return Main.pref.getColor(marktr("gps point"), name != null ? "layer "+name : null, Color.gray);
114 }
115
116 @Override public Component[] getMenuEntries() {
117 JMenuItem line = new JMenuItem(tr("Customize line drawing"), ImageProvider.get("mapmode/addsegment"));
118 line.addActionListener(new ActionListener() {
119 public void actionPerformed(ActionEvent e) {
120 JRadioButton[] r = new JRadioButton[3];
121 r[0] = new JRadioButton(tr("Use global settings."));
122 r[1] = new JRadioButton(tr("Draw lines between points for this layer."));
123 r[2] = new JRadioButton(tr("Do not draw lines between points for this layer."));
124 ButtonGroup group = new ButtonGroup();
125 Box panel = Box.createVerticalBox();
126 for (JRadioButton b : r) {
127 group.add(b);
128 panel.add(b);
129 }
130 String propName = "draw.rawgps.lines.layer "+name;
131 if (Main.pref.hasKey(propName))
132 group.setSelected(r[Main.pref.getBoolean(propName) ? 1:2].getModel(), true);
133 else
134 group.setSelected(r[0].getModel(), true);
135 int answer = JOptionPane.showConfirmDialog(Main.parent, panel, tr("Select line drawing options"), JOptionPane.OK_CANCEL_OPTION);
136 if (answer == JOptionPane.CANCEL_OPTION)
137 return;
138 if (group.getSelection() == r[0].getModel())
139 Main.pref.put(propName, null);
140 else
141 Main.pref.put(propName, group.getSelection() == r[1].getModel());
142 Main.map.repaint();
143 }
144 });
145
146 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
147 color.putClientProperty("help", "Action/LayerCustomizeColor");
148 color.addActionListener(new ActionListener() {
149 public void actionPerformed(ActionEvent e) {
150 JColorChooser c = new JColorChooser(getColor(name));
151 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
152 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
153 JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
154 switch (answer) {
155 case 0:
156 Main.pref.putColor("layer "+name, c.getColor());
157 break;
158 case 1:
159 return;
160 case 2:
161 Main.pref.putColor("layer "+name, null);
162 break;
163 }
164 Main.map.repaint();
165 }
166 });
167
168 JMenuItem markersFromNamedTrackpoints = new JMenuItem(tr("Markers From Named Points"), ImageProvider.get("addmarkers"));
169 markersFromNamedTrackpoints.putClientProperty("help", "Action/MarkersFromNamedPoints");
170 markersFromNamedTrackpoints.addActionListener(new ActionListener() {
171 public void actionPerformed(ActionEvent e) {
172 GpxData namedTrackPoints = new GpxData();
173 for (GpxTrack track : data.tracks)
174 for (Collection<WayPoint> seg : track.trackSegs)
175 for (WayPoint point : seg)
176 if (point.attr.containsKey("name") || point.attr.containsKey("desc"))
177 namedTrackPoints.waypoints.add(point);
178
179 MarkerLayer ml = new MarkerLayer(namedTrackPoints, tr("Named Trackpoints from {0}", name), associatedFile, me);
180 if (ml.data.size() > 0) {
181 Main.main.addLayer(ml);
182 }
183 }
184 });
185
186 JMenuItem importAudio = new JMenuItem(tr("Import Audio"), ImageProvider.get("importaudio"));
187 importAudio.putClientProperty("help", "ImportAudio");
188 importAudio.addActionListener(new ActionListener() {
189 public void actionPerformed(ActionEvent e) {
190 String dir = Main.pref.get("markers.lastaudiodirectory");
191 JFileChooser fc = new JFileChooser(dir);
192 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
193 fc.setAcceptAllFileFilterUsed(false);
194 fc.setFileFilter(new FileFilter(){
195 @Override public boolean accept(File f) {
196 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
197 }
198 @Override public String getDescription() {
199 return tr("Wave Audio files (*.wav)");
200 }
201 });
202 fc.setMultiSelectionEnabled(true);
203 if(fc.showOpenDialog(Main.parent) == JFileChooser.APPROVE_OPTION) {
204 if (!fc.getCurrentDirectory().getAbsolutePath().equals(dir))
205 Main.pref.put("markers.lastaudiodirectory", fc.getCurrentDirectory().getAbsolutePath());
206
207 // FIXME: properly support multi-selection here.
208 // Calling importAudio several times just creates N maker layers, which
209 // is sub-optimal.
210 File sel[] = fc.getSelectedFiles();
211 if(sel != null)
212 for (int i = 0; i < sel.length; i++)
213 importAudio(sel[i]);
214
215 Main.map.repaint();
216 }
217 }
218 });
219
220 JMenuItem tagimage = new JMenuItem(tr("Import images"), ImageProvider.get("tagimages"));
221 tagimage.putClientProperty("help", "Action/ImportImages");
222 tagimage.addActionListener(new ActionListener() {
223 public void actionPerformed(ActionEvent e) {
224 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
225 fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
226 fc.setMultiSelectionEnabled(true);
227 fc.setAcceptAllFileFilterUsed(false);
228 fc.setFileFilter(new FileFilter() {
229 @Override public boolean accept(File f) {
230 return f.isDirectory() || f.getName().toLowerCase().endsWith(".jpg");
231 }
232 @Override public String getDescription() {
233 return tr("JPEG images (*.jpg)");
234 }
235 });
236 fc.showOpenDialog(Main.parent);
237 File[] sel = fc.getSelectedFiles();
238 if (sel == null || sel.length == 0)
239 return;
240 LinkedList<File> files = new LinkedList<File>();
241 addRecursiveFiles(files, sel);
242 Main.pref.put("tagimages.lastdirectory", fc.getCurrentDirectory().getPath());
243 GeoImageLayer.create(files, GpxLayer.this);
244 }
245
246 private void addRecursiveFiles(LinkedList<File> files, File[] sel) {
247 for (File f : sel) {
248 if (f.isDirectory())
249 addRecursiveFiles(files, f.listFiles());
250 else if (f.getName().toLowerCase().endsWith(".jpg"))
251 files.add(f);
252 }
253 }
254 });
255
256 if (Main.applet)
257 return new Component[] {
258 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
259 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
260 new JSeparator(),
261 color,
262 line,
263 new JMenuItem(new ConvertToDataLayerAction()),
264 new JSeparator(),
265 new JMenuItem(new RenameLayerAction(associatedFile, this)),
266 new JSeparator(),
267 new JMenuItem(new LayerListPopup.InfoAction(this))};
268 return new Component[] {
269 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
270 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
271 new JSeparator(),
272 new JMenuItem(new SaveAction(this)),
273 new JMenuItem(new SaveAsAction(this)),
274 // new JMenuItem(new UploadTraceAction()),
275 color,
276 line,
277 tagimage,
278 importAudio,
279 markersFromNamedTrackpoints,
280 new JMenuItem(new ConvertToDataLayerAction()),
281 new JMenuItem(new DownloadAlongTrackAction()),
282 new JSeparator(),
283 new JMenuItem(new RenameLayerAction(associatedFile, this)),
284 new JSeparator(),
285 new JMenuItem(new LayerListPopup.InfoAction(this))};
286 }
287
288 @Override public String getToolTipText() {
289 StringBuilder info = new StringBuilder().append("<html>");
290
291 info.append(trn("{0} track, ", "{0} tracks, ",
292 data.tracks.size(), data.tracks.size())).append(trn("{0} route, ", "{0} routes, ",
293 data.routes.size(), data.routes.size())).append(trn("{0} waypoint", "{0} waypoints",
294 data.waypoints.size(), data.waypoints.size())).append("<br>");
295
296 if (data.attr.containsKey("name"))
297 info.append(tr("Name: {0}", data.attr.get("name"))).append("<br>");
298
299 if (data.attr.containsKey("desc"))
300 info.append(tr("Description: {0}", data.attr.get("desc"))).append("<br>");
301
302 if(data.tracks.size() > 0){
303 boolean first = true;
304 WayPoint earliest = null, latest = null;
305
306 for(GpxTrack trk: data.tracks){
307 for(Collection<WayPoint> seg:trk.trackSegs){
308 for(WayPoint pnt:seg){
309 if(first){
310 latest = earliest = pnt;
311 first = false;
312 }else{
313 if(pnt.compareTo(earliest) < 0){
314 earliest = pnt;
315 }else{
316 latest = pnt;
317 }
318 }
319 }
320 }
321 }
322 if (earliest != null && latest != null) {
323 DateFormat df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
324 info.append(tr("Timespan: ") + df.format(new Date((long)(earliest.time * 1000))) + " - "
325 + df.format(new Date((long)(latest.time * 1000))));
326 int diff = (int)(latest.time - earliest.time);
327 info.append(" (" + (diff / 3600) + ":" + ((diff % 3600)/60) + ")");
328 info.append("<br>");
329 }
330 }
331 info.append(tr("Length: ") + new DecimalFormat("#0.00").format(data.length() / 1000) + "km");
332 info.append("<br>");
333
334 return info.append("</html>").toString();
335 }
336
337 @Override public boolean isMergable(Layer other) {
338 return other instanceof GpxLayer;
339 }
340
341 @Override public void mergeFrom(Layer from) {
342 data.mergeFrom(((GpxLayer)from).data);
343 computeCacheInSync = false;
344 }
345
346 private static Color[] colors = new Color[256];
347 static {
348 for (int i = 0; i < colors.length; i++) {
349 colors[i] = Color.getHSBColor(i/300.0f, 1, 1);
350 }
351 }
352
353 // lookup array to draw arrows without doing any math
354 private static int ll0 = 9;
355 private static int sl4 = 5;
356 private static int sl9 = 3;
357 private static int[][] dir = {
358 {+sl4,+ll0,+ll0,+sl4},
359 {-sl9,+ll0,+sl9,+ll0},
360 {-ll0,+sl4,-sl4,+ll0},
361 {-ll0,-sl9,-ll0,+sl9},
362 {-sl4,-ll0,-ll0,-sl4},
363 {+sl9,-ll0,-sl9,-ll0},
364 {+ll0,-sl4,+sl4,-ll0},
365 {+ll0,+sl9,+ll0,-sl9},
366 {+sl4,+ll0,+ll0,+sl4},
367 {-sl9,+ll0,+sl9,+ll0},
368 {-ll0,+sl4,-sl4,+ll0},
369 {-ll0,-sl9,-ll0,+sl9}
370 };
371
372 // the different color modes
373 enum colorModes { none, velocity, dilution }
374
375 @Override public void paint(Graphics g, MapView mv) {
376
377 /****************************************************************
378 ********** STEP 1 - GET CONFIG VALUES **************************
379 ****************************************************************/
380 // Long startTime = System.currentTimeMillis();
381 Color neutralColor = getColor(name);
382 // also draw lines between points belonging to different segments
383 boolean forceLines = Main.pref.getBoolean("draw.rawgps.lines.force");
384 // draw direction arrows on the lines
385 boolean direction = Main.pref.getBoolean("draw.rawgps.direction");
386 // don't draw lines if longer than x meters
387 int maxLineLength = Main.pref.getInteger("draw.rawgps.max-line-length", -1);
388 // draw line between points, global setting
389 boolean lines = Main.pref.getBoolean("draw.rawgps.lines");
390 String linesKey = "draw.rawgps.lines.layer "+name;
391 // draw lines, per-layer setting
392 if (Main.pref.hasKey(linesKey))
393 lines = Main.pref.getBoolean(linesKey);
394 // paint large dots for points
395 boolean large = Main.pref.getBoolean("draw.rawgps.large");
396 // color the lines
397 colorModes colored = colorModes.none;
398 try {
399 colored = colorModes.values()[Main.pref.getInteger("draw.rawgps.colors", 0)];
400 } catch(Exception e) { }
401 // paint direction arrow with alternate math. may be faster
402 boolean alternatedirection = Main.pref.getBoolean("draw.rawgps.alternatedirection");
403 // don't draw arrows nearer to each other than this
404 int delta = Main.pref.getInteger("draw.rawgps.min-arrow-distance", 0);
405 // allows to tweak line coloring for different speed levels.
406 int colorTracksTune = Main.pref.getInteger("draw.rawgps.colorTracksTune", 45);
407 /****************************************************************
408 ********** STEP 2a - CHECK CACHE VALIDITY **********************
409 ****************************************************************/
410 if (computeCacheInSync && ((computeCacheMaxLineLengthUsed != maxLineLength) ||
411 (!neutralColor.equals(computeCacheColorUsed)) ||
412 (computeCacheColored != colored) ||
413 (computeCacheColorTracksTune != colorTracksTune))) {
414// System.out.println("(re-)computing gpx line styles, reason: CCIS=" + computeCacheInSync + " CCMLLU=" + (computeCacheMaxLineLengthUsed != maxLineLength) + " CCCU=" + (!neutralColor.equals(computeCacheColorUsed)) + " CCC=" + (computeCacheColored != colored));
415 computeCacheMaxLineLengthUsed = maxLineLength;
416 computeCacheInSync = false;
417 computeCacheColorUsed = neutralColor;
418 computeCacheColored = colored;
419 computeCacheColorTracksTune = colorTracksTune;
420 }
421
422 /****************************************************************
423 ********** STEP 2b - RE-COMPUTE CACHE DATA *********************
424 ****************************************************************/
425 if (!computeCacheInSync) { // don't compute if the cache is good
426 WayPoint oldWp = null;
427 for (GpxTrack trk : data.tracks) {
428 if (!forceLines) { // don't draw lines between segments, unless forced to
429 oldWp = null;
430 }
431 for (Collection<WayPoint> segment : trk.trackSegs) {
432 for (WayPoint trkPnt : segment) {
433 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon())) {
434 continue;
435 }
436 trkPnt.customColoring = neutralColor;
437 if (oldWp != null) {
438 double dist = trkPnt.latlon.greatCircleDistance(oldWp.latlon);
439
440 switch(colored) {
441 case velocity:
442 double dtime = trkPnt.time - oldWp.time;
443 double vel = dist/dtime;
444 double velColor = vel/colorTracksTune*255;
445 // Bad case first
446 if (dtime <= 0 || vel < 0 || velColor > 255)
447 trkPnt.customColoring = colors[255];
448 else
449 trkPnt.customColoring = colors[(int) (velColor)];
450 break;
451
452 case dilution:
453 if(trkPnt.attr.get("hdop") != null) {
454 float hdop = ((Float)trkPnt.attr.get("hdop")).floatValue();
455 int hdoplvl = Math.round(hdop * 25);
456 // High hdop is bad, but high values in colors are green.
457 // Therefore inverse the logic
458 int hdopcolor = 255 - (hdoplvl > 255 ? 255 : hdoplvl);
459 trkPnt.customColoring = colors[hdopcolor];
460 }
461 break;
462 }
463
464 if (maxLineLength == -1 || dist <= maxLineLength) {
465 trkPnt.drawLine = true;
466 trkPnt.dir = (int)(Math.atan2(-trkPnt.eastNorth.north()+oldWp.eastNorth.north(), trkPnt.eastNorth.east()-oldWp.eastNorth.east()) / Math.PI * 4 + 3.5); // crude but works
467 } else {
468 trkPnt.drawLine = false;
469 }
470 } else { // make sure we reset outdated data
471 trkPnt.drawLine = false;
472 }
473 oldWp = trkPnt;
474 }
475 }
476 }
477 computeCacheInSync = true;
478 }
479
480 /****************************************************************
481 ********** STEP 3a - DRAW LINES ********************************
482 ****************************************************************/
483 if (lines) {
484 Point old = null;
485 for (GpxTrack trk : data.tracks) {
486 for (Collection<WayPoint> segment : trk.trackSegs) {
487 for (WayPoint trkPnt : segment) {
488 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
489 continue;
490 Point screen = mv.getPoint(trkPnt.eastNorth);
491 if (trkPnt.drawLine) {
492 // skip points that are on the same screenposition
493 if (old != null && ((old.x != screen.x) || (old.y != screen.y))) {
494 g.setColor(trkPnt.customColoring);
495 g.drawLine(old.x, old.y, screen.x, screen.y);
496 }
497 }
498 old = screen;
499 } // end for trkpnt
500 } // end for segment
501 } // end for trk
502 } // end if lines
503
504 /****************************************************************
505 ********** STEP 3b - DRAW NICE ARROWS **************************
506 ****************************************************************/
507 if (lines && direction && !alternatedirection) {
508 Point old = null;
509 Point oldA = null; // last arrow painted
510 for (GpxTrack trk : data.tracks) {
511 for (Collection<WayPoint> segment : trk.trackSegs) {
512 for (WayPoint trkPnt : segment) {
513 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
514 continue;
515 if (trkPnt.drawLine) {
516 Point screen = mv.getPoint(trkPnt.eastNorth);
517 // skip points that are on the same screenposition
518 if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
519 g.setColor(trkPnt.customColoring);
520 double t = Math.atan2(screen.y-old.y, screen.x-old.x) + Math.PI;
521 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t-PHI)), (int)(screen.y
522 + 10*Math.sin(t-PHI)));
523 g.drawLine(screen.x,screen.y, (int)(screen.x + 10*Math.cos(t+PHI)), (int)(screen.y
524 + 10*Math.sin(t+PHI)));
525 oldA = screen;
526 }
527 old = screen;
528 }
529 } // end for trkpnt
530 } // end for segment
531 } // end for trk
532 } // end if lines
533
534 /****************************************************************
535 ********** STEP 3c - DRAW FAST ARROWS **************************
536 ****************************************************************/
537 if (lines && direction && alternatedirection) {
538 Point old = null;
539 Point oldA = null; // last arrow painted
540 for (GpxTrack trk : data.tracks) {
541 for (Collection<WayPoint> segment : trk.trackSegs) {
542 for (WayPoint trkPnt : segment) {
543 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
544 continue;
545 if (trkPnt.drawLine) {
546 Point screen = mv.getPoint(trkPnt.eastNorth);
547 // skip points that are on the same screenposition
548 if (old != null && (oldA == null || screen.x < oldA.x-delta || screen.x > oldA.x+delta || screen.y < oldA.y-delta || screen.y > oldA.y+delta)) {
549 g.setColor(trkPnt.customColoring);
550 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][0], screen.y + dir[trkPnt.dir][1]);
551 g.drawLine(screen.x, screen.y, screen.x + dir[trkPnt.dir][2], screen.y + dir[trkPnt.dir][3]);
552 oldA = screen;
553 }
554 old = screen;
555 }
556 } // end for trkpnt
557 } // end for segment
558 } // end for trk
559 } // end if lines
560
561 /****************************************************************
562 ********** STEP 3d - DRAW LARGE POINTS *************************
563 ****************************************************************/
564 if (large) {
565 g.setColor(neutralColor);
566 for (GpxTrack trk : data.tracks) {
567 for (Collection<WayPoint> segment : trk.trackSegs) {
568 for (WayPoint trkPnt : segment) {
569 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
570 continue;
571 Point screen = mv.getPoint(trkPnt.eastNorth);
572 g.setColor(trkPnt.customColoring);
573 g.fillRect(screen.x-1, screen.y-1, 3, 3);
574 } // end for trkpnt
575 } // end for segment
576 } // end for trk
577 } // end if large
578
579 /****************************************************************
580 ********** STEP 3e - DRAW SMALL POINTS FOR LINES ***************
581 ****************************************************************/
582 if (!large && lines){
583 g.setColor(neutralColor);
584 for (GpxTrack trk : data.tracks) {
585 for (Collection<WayPoint> segment : trk.trackSegs) {
586 for (WayPoint trkPnt : segment) {
587 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
588 continue;
589 if (!trkPnt.drawLine) {
590 Point screen = mv.getPoint(trkPnt.eastNorth);
591 g.drawRect(screen.x, screen.y, 0, 0);
592 }
593 } // end for trkpnt
594 } // end for segment
595 } // end for trk
596 } // end if large
597
598 /****************************************************************
599 ********** STEP 3f - DRAW SMALL POINTS INSTEAD OF LINES ********
600 ****************************************************************/
601 if (!large && !lines){
602 g.setColor(neutralColor);
603 for (GpxTrack trk : data.tracks) {
604 for (Collection<WayPoint> segment : trk.trackSegs) {
605 for (WayPoint trkPnt : segment) {
606 if (Double.isNaN(trkPnt.latlon.lat()) || Double.isNaN(trkPnt.latlon.lon()))
607 continue;
608 Point screen = mv.getPoint(trkPnt.eastNorth);
609 g.setColor(trkPnt.customColoring);
610 g.drawRect(screen.x, screen.y, 0, 0);
611 } // end for trkpnt
612 } // end for segment
613 } // end for trk
614 } // end if large
615
616 //Long duration = System.currentTimeMillis() - startTime;
617 //System.out.println(duration);
618 } // end paint
619
620 @Override public void visitBoundingBox(BoundingXYVisitor v) {
621 for (WayPoint p : data.waypoints)
622 v.visit(p.eastNorth);
623
624 for (GpxRoute rte : data.routes) {
625 Collection<WayPoint> r = rte.routePoints;
626 for (WayPoint p : r) {
627 v.visit(p.eastNorth);
628 }
629 }
630
631 for (GpxTrack trk : data.tracks) {
632 for (Collection<WayPoint> seg : trk.trackSegs) {
633 for (WayPoint p : seg) {
634 v.visit(p.eastNorth);
635 }
636 }
637 }
638 }
639
640 public class UploadTraceAction extends AbstractAction {
641 public UploadTraceAction() {
642 super(tr("Upload this trace..."), ImageProvider.get("uploadtrace"));
643 }
644 public void actionPerformed(ActionEvent e) {
645 JPanel msg = new JPanel(new GridBagLayout());
646 msg.add(new JLabel(tr("<html>This functionality has been added only recently. Please<br>"+
647 "use with care and check if it works as expected.</html>")), GBC.eop());
648 ButtonGroup bg = new ButtonGroup();
649 JRadioButton c1 = null;
650 JRadioButton c2 = null;
651
652 //TODO
653 //check whether data comes from server
654 //check whether data changed sind last save/open
655
656 c1 = new JRadioButton(tr("Upload track filtered by JOSM"), true);
657 c2 = new JRadioButton(tr("Upload raw file: "), false);
658 c2.setEnabled(false);
659 c1.setEnabled(false);
660 bg.add(c1);
661 bg.add(c2);
662
663 msg.add(c1, GBC.eol());
664 msg.add(c2, GBC.eop());
665
666
667 JLabel description = new JLabel((String) data.attr.get("desc"));
668 JTextField tags = new JTextField();
669 tags.setText((String) data.attr.get("keywords"));
670 msg.add(new JLabel(tr("Description:")), GBC.std());
671 msg.add(description, GBC.eol().fill(GBC.HORIZONTAL));
672 msg.add(new JLabel(tr("Tags (keywords in GPX):")), GBC.std());
673 msg.add(tags, GBC.eol().fill(GBC.HORIZONTAL));
674 JCheckBox c3 = new JCheckBox("public");
675 msg.add(c3, GBC.eop());
676 msg.add(new JLabel("Please ensure that you don't upload your traces twice."), GBC.eop());
677
678 int answer = JOptionPane.showConfirmDialog(Main.parent, msg, tr("GPX-Upload"), JOptionPane.OK_CANCEL_OPTION);
679 if (answer == JOptionPane.OK_OPTION)
680 {
681 try {
682 String version = Main.pref.get("osm-server.version", "0.5");
683 URL url = new URL(Main.pref.get("osm-server.url") + "/" + version + "/gpx/create");
684
685 // create a boundary string
686 String boundary = MultiPartFormOutputStream.createBoundary();
687 URLConnection urlConn = MultiPartFormOutputStream.createConnection(url);
688 urlConn.setRequestProperty("Accept", "*/*");
689 urlConn.setRequestProperty("Content-Type", MultiPartFormOutputStream.getContentType(boundary));
690 // set some other request headers...
691 urlConn.setRequestProperty("Connection", "Keep-Alive");
692 urlConn.setRequestProperty("Cache-Control", "no-cache");
693 // no need to connect cuz getOutputStream() does it
694 MultiPartFormOutputStream out = new MultiPartFormOutputStream(urlConn.getOutputStream(), boundary);
695 out.writeField("description", description.getText());
696 out.writeField("tags", tags.getText());
697 out.writeField("public", (c3.getSelectedObjects() != null) ? "1" : "0");
698 // upload a file
699 // out.writeFile("gpx_file", "text/xml", associatedFile);
700 // can also write bytes directly
701 // out.writeFile("myFile", "text/plain", "C:\\test.txt",
702 // "This is some file text.".getBytes("ASCII"));
703 File tmp = File.createTempFile("josm", "tmp.gpx");
704 FileOutputStream outs = new FileOutputStream(tmp);
705 new GpxWriter(outs).write(data);
706 outs.close();
707 FileInputStream ins = new FileInputStream(tmp);
708 new GpxWriter(System.out).write(data);
709 out.writeFile("gpx_file", "text/xml", data.storageFile.getName(), ins);
710 out.close();
711 tmp.delete();
712 // read response from server
713 BufferedReader in = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
714 String line = "";
715 while((line = in.readLine()) != null) {
716 System.out.println(line);
717 }
718 in.close();
719
720 //TODO check response
721 /* int retCode = urlConn.getResponseCode();
722 System.out.println("got return: " + retCode);
723 String retMsg = urlConn.getResponseMessage();
724 urlConn.disconnect();
725 if (retCode != 200) {
726 // Look for a detailed error message from the server
727 if (urlConn.getHeaderField("Error") != null)
728 retMsg += "\n" + urlConn.getHeaderField("Error");
729
730 // Report our error
731 ByteArrayOutputStream o = new ByteArrayOutputStream();
732 System.out.println(new String(o.toByteArray(), "UTF-8").toString());
733 throw new RuntimeException(retCode+" "+retMsg);
734 }
735 */
736 } catch (UnknownHostException ex) {
737 throw new RuntimeException(tr("Unknown host")+": "+ex.getMessage(), ex);
738 } catch (Exception ex) {
739 //if (cancel)
740 // return; // assume cancel
741 if (ex instanceof RuntimeException)
742 throw (RuntimeException)ex;
743 throw new RuntimeException(ex.getMessage(), ex);
744 }
745 }
746 }
747 }
748
749 public class ConvertToDataLayerAction extends AbstractAction {
750 public ConvertToDataLayerAction() {
751 super(tr("Convert to data layer"), ImageProvider.get("converttoosm"));
752 }
753 public void actionPerformed(ActionEvent e) {
754 JPanel msg = new JPanel(new GridBagLayout());
755 msg.add(new JLabel(tr("<html>Upload of unprocessed GPS data as map data is considered harmful.<br>If you want to upload traces, look here:")), GBC.eol());
756 msg.add(new UrlLabel(tr("http://www.openstreetmap.org/traces")), GBC.eop());
757 if (!DontShowAgainInfo.show("convert_to_data", msg))
758 return;
759 DataSet ds = new DataSet();
760 for (GpxTrack trk : data.tracks) {
761 for (Collection<WayPoint> segment : trk.trackSegs) {
762 Way w = new Way();
763 for (WayPoint p : segment) {
764 Node n = new Node(p.latlon);
765 String timestr = p.getString("time");
766 if(timestr != null)
767 {
768 timestr = timestr.replace("Z","+00:00");
769 n.timestamp = timestr;
770 }
771 ds.nodes.add(n);
772 w.nodes.add(n);
773 }
774 ds.ways.add(w);
775 }
776 }
777 Main.main.addLayer(new OsmDataLayer(ds, tr("Converted from: {0}", GpxLayer.this.name), null));
778 Main.main.removeLayer(GpxLayer.this);
779 }
780 }
781
782 /**
783 * Action that issues a series of download requests to the API, following the GPX track.
784 *
785 * @author fred
786 */
787 public class DownloadAlongTrackAction extends AbstractAction {
788 public DownloadAlongTrackAction() {
789 super(tr("Download from OSM along this track"), ImageProvider.get("downloadalongtrack"));
790 }
791 public void actionPerformed(ActionEvent e) {
792 JPanel msg = new JPanel(new GridBagLayout());
793 Integer dist[] = {5000, 500, 50};
794 Integer area[] = {20, 10, 5, 1};
795
796 msg.add(new JLabel(tr("Download everything within:")), GBC.eol());
797 String s[] = new String[dist.length];
798 for(int i = 0; i < dist.length; ++i)
799 s[i] = tr("{0} meters", dist[i]);
800 JList buffer = new JList(s);
801 msg.add(buffer, GBC.eol());
802 msg.add(new JLabel(tr("Maximum area per request:")), GBC.eol());
803 s = new String[area.length];
804 for(int i = 0; i < area.length; ++i)
805 s[i] = tr("{0} sq km", area[i]);
806 JList maxRect = new JList(s);
807 msg.add(maxRect, GBC.eol());
808
809 if (JOptionPane.showConfirmDialog(Main.parent, msg,
810 tr("Download from OSM along this track"),
811 JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
812 return;
813 }
814
815 /*
816 * Find the average latitude for the data we're contemplating, so we can
817 * know how many metres per degree of longitude we have.
818 */
819 double latsum = 0;
820 int latcnt = 0;
821
822 for (GpxTrack trk : data.tracks) {
823 for (Collection<WayPoint> segment : trk.trackSegs) {
824 for (WayPoint p : segment) {
825 latsum += p.latlon.lat();
826 latcnt ++;
827 }
828 }
829 }
830
831 double avglat = latsum / latcnt;
832 double scale = Math.cos(Math.toRadians(avglat));
833
834 /*
835 * Compute buffer zone extents and maximum bounding box size. Note that the
836 * maximum we ever offer is a bbox area of 0.002, while the API theoretically
837 * supports 0.25, but as soon as you touch any built-up area, that kind of
838 * bounding box will download forever and then stop because it has more than
839 * 50k nodes.
840 */
841 Integer i = buffer.getSelectedIndex();
842 int buffer_dist = dist[i < 0 ? 0 : i];
843 double buffer_y = buffer_dist / 100000.0;
844 double buffer_x = buffer_y / scale;
845 i = maxRect.getSelectedIndex();
846 double max_area = area[i < 0 ? 0 : i] / 10000.0 / scale;
847 Area a = new Area();
848 Rectangle2D r = new Rectangle2D.Double();
849
850 /*
851 * Collect the combined area of all gpx points plus buffer zones around them.
852 * We ignore points that lie closer to the previous point than the given buffer
853 * size because otherwise this operation takes ages.
854 */
855 LatLon previous = null;
856 for (GpxTrack trk : data.tracks) {
857 for (Collection<WayPoint> segment : trk.trackSegs) {
858 for (WayPoint p : segment) {
859 if (previous == null || p.latlon.greatCircleDistance(previous) > buffer_dist) {
860 // we add a buffer around the point.
861 r.setRect(p.latlon.lon()-buffer_x, p.latlon.lat()-buffer_y, 2*buffer_x, 2*buffer_y);
862 a.add(new Area(r));
863 previous = p.latlon;
864 }
865 }
866 }
867 }
868
869 /*
870 * Area "a" now contains the hull that we would like to download data for.
871 * however we can only download rectangles, so the following is an attempt at
872 * finding a number of rectangles to download.
873 *
874 * The idea is simply: Start out with the full bounding box. If it is too large,
875 * then split it in half and repeat recursively for each half until you arrive
876 * at something small enough to download. The algorithm is improved
877 * by always using the intersection between the rectangle and the actual desired
878 * area. For example, if you have a track that goes like this:
879 * +----+
880 * | /|
881 * | / |
882 * | / |
883 * |/ |
884 * +----+
885 * then we would first look at downloading the whole rectangle (assume it's too big),
886 * after that we split it in half (upper and lower half), but we do *not* request the
887 * full upper and lower rectangle, only the part of the upper/lower rectangle that
888 * actually has something in it.
889 */
890
891 List<Rectangle2D> toDownload = new ArrayList<Rectangle2D>();
892
893 addToDownload(a, a.getBounds(), toDownload, max_area);
894
895 msg = new JPanel(new GridBagLayout());
896
897 msg.add(new JLabel(tr("<html>This action will require {0} individual<br>download requests. Do you wish<br>to continue?</html>",
898 toDownload.size())), GBC.eol());
899
900 if (JOptionPane.showConfirmDialog(Main.parent, msg,
901 tr("Download from OSM along this track"),
902 JOptionPane.OK_CANCEL_OPTION) == JOptionPane.CANCEL_OPTION) {
903 return;
904 }
905
906 // FIXME: DownloadTask's "please wait" dialog should display the number of
907 // downloads left, and "cancel" needs to be honoured. An error along the way
908 // should abort the whole process.
909 DownloadTask osmTask = new DownloadOsmTask();
910 for (Rectangle2D td : toDownload) {
911 osmTask.download(null, td.getMinY(), td.getMinX(), td.getMaxY(), td.getMaxX());
912 }
913 }
914 }
915
916 private static void addToDownload(Area a, Rectangle2D r, Collection<Rectangle2D> results, double max_area) {
917 Area tmp = new Area(r);
918 // intersect with sought-after area
919 tmp.intersect(a);
920 if (tmp.isEmpty()) return;
921 Rectangle2D bounds = tmp.getBounds2D();
922 if (bounds.getWidth() * bounds.getHeight() > max_area) {
923 // the rectangle gets too large; split it and make recursive call.
924 Rectangle2D r1;
925 Rectangle2D r2;
926 if (bounds.getWidth() > bounds.getHeight()) {
927 // rectangles that are wider than high are split into a left and right half,
928 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth()/2, bounds.getHeight());
929 r2 = new Rectangle2D.Double(bounds.getX()+bounds.getWidth()/2, bounds.getY(), bounds.getWidth()/2, bounds.getHeight());
930 } else {
931 // others into a top and bottom half.
932 r1 = new Rectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()/2);
933 r2 = new Rectangle2D.Double(bounds.getX(), bounds.getY()+bounds.getHeight()/2, bounds.getWidth(), bounds.getHeight()/2);
934 }
935 addToDownload(a, r1, results, max_area);
936 addToDownload(a, r2, results, max_area);
937 } else {
938 results.add(bounds);
939 }
940 }
941
942 /**
943 * Makes a new marker layer derived from this GpxLayer containing at least one
944 * audio marker which the given audio file is associated with.
945 * Markers are derived from the following
946 * (a) explict waypoints in the GPX layer, or
947 * (b) named trackpoints in the GPX layer, or
948 * (c) (in future) voice recognised markers in the sound recording
949 * (d) a single marker at the beginning of the track
950 * @param wavFile : the file to be associated with the markers in the new marker layer
951 */
952 private void importAudio(File wavFile) {
953 String uri = "file:".concat(wavFile.getAbsolutePath());
954 MarkerLayer ml = new MarkerLayer(new GpxData(), tr("Audio markers from {0}", name), associatedFile, me);
955
956 Collection<WayPoint> waypoints = new ArrayList<WayPoint>();
957 boolean timedMarkersOmitted = false;
958 boolean untimedMarkersOmitted = false;
959 double snapDistance = Main.pref.getDouble("marker.audiofromuntimedwaypoints.distance", 1.0e-3); /* about 25m */
960
961 // determine time of first point in track
962 double firstTime = -1.0;
963 if (data.tracks != null && ! data.tracks.isEmpty()) {
964 for (GpxTrack track : data.tracks) {
965 if (track.trackSegs == null) continue;
966 for (Collection<WayPoint> seg : track.trackSegs) {
967 for (WayPoint w : seg) {
968 firstTime = w.time;
969 break;
970 }
971 if (firstTime >= 0.0) break;
972 }
973 if (firstTime >= 0.0) break;
974 }
975 }
976 if (firstTime < 0.0) {
977 JOptionPane.showMessageDialog(Main.parent, tr("No GPX track available in layer to associate audio with."));
978 return;
979 }
980
981 // (a) try explicit timestamped waypoints - unless suppressed
982 if (Main.pref.getBoolean("marker.audiofromexplicitwaypoints", true) &&
983 data.waypoints != null && ! data.waypoints.isEmpty())
984 {
985 for (WayPoint w : data.waypoints) {
986 if (w.time > firstTime) {
987 waypoints.add(w);
988 } else if (w.time > 0.0) {
989 timedMarkersOmitted = true;
990 }
991 }
992 }
993
994 // (b) try explicit waypoints without timestamps - unless suppressed
995 if (Main.pref.getBoolean("marker.audiofromuntimedwaypoints", true) &&
996 data.waypoints != null && ! data.waypoints.isEmpty())
997 {
998 for (WayPoint w : data.waypoints) {
999 if (waypoints.contains(w)) { continue; }
1000 WayPoint wNear = nearestPointOnTrack(w.eastNorth, snapDistance);
1001 if (wNear != null) {
1002 WayPoint wc = new WayPoint(w.latlon);
1003 wc.time = wNear.time;
1004 if (w.attr.containsKey("name")) wc.attr.put("name", w.getString("name"));
1005 waypoints.add(wc);
1006 } else {
1007 untimedMarkersOmitted = true;
1008 }
1009 }
1010 }
1011
1012 // (c) use explicitly named track points, again unless suppressed
1013 if ((Main.pref.getBoolean("marker.audiofromnamedtrackpoints", false)) &&
1014 data.tracks != null && ! data.tracks.isEmpty())
1015 {
1016 for (GpxTrack track : data.tracks) {
1017 if (track.trackSegs == null) continue;
1018 for (Collection<WayPoint> seg : track.trackSegs) {
1019 for (WayPoint w : seg) {
1020 if (w.attr.containsKey("name") || w.attr.containsKey("desc")) {
1021 waypoints.add(w);
1022 }
1023 }
1024 }
1025 }
1026 }
1027
1028 // (d) analyse audio for spoken markers here, in due course
1029
1030 // (e) simply add a single marker at the start of the track
1031 if ((Main.pref.getBoolean("marker.audiofromstart") || waypoints.isEmpty()) &&
1032 data.tracks != null && ! data.tracks.isEmpty())
1033 {
1034 boolean gotOne = false;
1035 for (GpxTrack track : data.tracks) {
1036 if (track.trackSegs == null) continue;
1037 for (Collection<WayPoint> seg : track.trackSegs) {
1038 for (WayPoint w : seg) {
1039 WayPoint wStart = new WayPoint(w.latlon);
1040 wStart.attr.put("name", "start");
1041 wStart.time = w.time;
1042 waypoints.add(wStart);
1043 gotOne = true;
1044 break;
1045 }
1046 if (gotOne) break;
1047 }
1048 if (gotOne) break;
1049 }
1050 }
1051
1052 /* we must have got at least one waypoint now */
1053
1054 Collections.sort((ArrayList<WayPoint>) waypoints, new Comparator<WayPoint>() {
1055 public int compare(WayPoint a, WayPoint b) {
1056 return a.time <= b.time ? -1 : 1;
1057 }
1058 });
1059
1060 firstTime = -1.0; /* this time of the first waypoint, not first trackpoint */
1061 for (WayPoint w : waypoints) {
1062 if (firstTime < 0.0) firstTime = w.time;
1063 double offset = w.time - firstTime;
1064 String name;
1065 if (w.attr.containsKey("name"))
1066 name = w.getString("name");
1067 else if (w.attr.containsKey("desc"))
1068 name = w.getString("desc");
1069 else
1070 name = AudioMarker.inventName(offset);
1071 AudioMarker am = AudioMarker.create(w.latlon,
1072 name, uri, ml, w.time, offset);
1073 ml.data.add(am);
1074 }
1075 Main.main.addLayer(ml);
1076
1077 if (timedMarkersOmitted) {
1078 JOptionPane.showMessageDialog(Main.parent,
1079 tr("Some waypoints with timestamps from before the start of the track were omitted."));
1080 }
1081 if (untimedMarkersOmitted) {
1082 JOptionPane.showMessageDialog(Main.parent,
1083 tr("Some waypoints which were too far from the track to sensibly estimate their time were omitted."));
1084 }
1085 }
1086
1087 /**
1088 * Makes a WayPoint at the projection of point P onto the track providing P is
1089 * less than tolerance away from the track
1090
1091 * @param P : the point to determine the projection for
1092 * @param tolerance : must be no further than this from the track
1093 * @return the closest point on the track to P, which may be the
1094 * first or last point if off the end of a segment, or may be null if
1095 * nothing close enough
1096 */
1097 public WayPoint nearestPointOnTrack(EastNorth P, double tolerance) {
1098 /*
1099 * assume the coordinates of P are xp,yp, and those of a section of track
1100 * between two trackpoints are R=xr,yr and S=xs,ys. Let N be the projected point.
1101 *
1102 * The equation of RS is Ax + By + C = 0 where
1103 * A = ys - yr
1104 * B = xr - xs
1105 * C = - Axr - Byr
1106 *
1107 * Also, note that the distance RS^2 is A^2 + B^2
1108 *
1109 * If RS^2 == 0.0 ignore the degenerate section of track
1110 *
1111 * PN^2 = (Axp + Byp + C)^2 / RS^2
1112 * that is the distance from P to the line
1113 *
1114 * so if PN^2 is less than PNmin^2 (initialized to tolerance) we can reject
1115 * the line; otherwise...
1116 * determine if the projected poijnt lies within the bounds of the line:
1117 * PR^2 - PN^2 <= RS^2 and PS^2 - PN^2 <= RS^2
1118 *
1119 * where PR^2 = (xp - xr)^2 + (yp-yr)^2
1120 * and PS^2 = (xp - xs)^2 + (yp-ys)^2
1121 *
1122 * If so, calculate N as
1123 * xn = xr + (RN/RS) B
1124 * yn = y1 + (RN/RS) A
1125 *
1126 * where RN = sqrt(PR^2 - PN^2)
1127 */
1128
1129 double PNminsq = tolerance * tolerance;
1130 EastNorth bestEN = null;
1131 double bestTime = 0.0;
1132 double px = P.east();
1133 double py = P.north();
1134 double rx = 0.0, ry = 0.0, sx, sy, x, y;
1135 if (data.tracks == null) return null;
1136 for (GpxTrack track : data.tracks) {
1137 if (track.trackSegs == null) continue;
1138 for (Collection<WayPoint> seg : track.trackSegs) {
1139 WayPoint R = null;
1140 for (WayPoint S : seg) {
1141 if (R == null) {
1142 R = S;
1143 rx = R.eastNorth.east();
1144 ry = R.eastNorth.north();
1145 x = px - rx;
1146 y = py - ry;
1147 double PRsq = x * x + y * y;
1148 if (PRsq < PNminsq) {
1149 PNminsq = PRsq;
1150 bestEN = R.eastNorth;
1151 bestTime = R.time;
1152 }
1153 } else {
1154 sx = S.eastNorth.east();
1155 sy = S.eastNorth.north();
1156 double A = sy - ry;
1157 double B = rx - sx;
1158 double C = - A * rx - B * ry;
1159 double RSsq = A * A + B * B;
1160 if (RSsq == 0.0) continue;
1161 double PNsq = A * px + B * py + C;
1162 PNsq = PNsq * PNsq / RSsq;
1163 if (PNsq < PNminsq) {
1164 x = px - rx;
1165 y = py - ry;
1166 double PRsq = x * x + y * y;
1167 x = px - sx;
1168 y = py - sy;
1169 double PSsq = x * x + y * y;
1170 if (PRsq - PNsq <= RSsq && PSsq - PNsq <= RSsq) {
1171 double RNoverRS = Math.sqrt((PRsq - PNsq)/RSsq);
1172 double nx = rx - RNoverRS * B;
1173 double ny = ry + RNoverRS * A;
1174 bestEN = new EastNorth(nx, ny);
1175 bestTime = R.time + RNoverRS * (S.time - R.time);
1176 PNminsq = PNsq;
1177 }
1178 }
1179 R = S;
1180 rx = sx;
1181 ry = sy;
1182 }
1183 }
1184 if (R != null) {
1185 /* if there is only one point in the seg, it will do this twice, but no matter */
1186 rx = R.eastNorth.east();
1187 ry = R.eastNorth.north();
1188 x = px - rx;
1189 y = py - ry;
1190 double PRsq = x * x + y * y;
1191 if (PRsq < PNminsq) {
1192 PNminsq = PRsq;
1193 bestEN = R.eastNorth;
1194 bestTime = R.time;
1195 }
1196 }
1197 }
1198 }
1199 if (bestEN == null) return null;
1200 WayPoint best = new WayPoint(Main.proj.eastNorth2latlon(bestEN));
1201 best.time = bestTime;
1202 return best;
1203 }
1204}
Note: See TracBrowser for help on using the repository browser.