source: josm/trunk/src/org/openstreetmap/josm/gui/bbox/TileSelectionBBoxChooser.java@ 6248

Last change on this file since 6248 was 6248, checked in by Don-vip, 11 years ago

Rework console output:

  • new log level "error"
  • Replace nearly all calls to system.out and system.err to Main.(error|warn|info|debug)
  • Remove some unnecessary debug output
  • Some messages are modified (removal of "Info", "Warning", "Error" from the message itself -> notable i18n impact but limited to console error messages not seen by the majority of users, so that's ok)
  • Property svn:eol-style set to native
File size: 25.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.bbox;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTKeyStroke;
7import java.awt.BorderLayout;
8import java.awt.Color;
9import java.awt.FlowLayout;
10import java.awt.Graphics;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Insets;
14import java.awt.KeyboardFocusManager;
15import java.awt.Point;
16import java.awt.event.ActionEvent;
17import java.awt.event.ActionListener;
18import java.awt.event.FocusEvent;
19import java.awt.event.FocusListener;
20import java.awt.event.KeyEvent;
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
23import java.util.ArrayList;
24import java.util.HashSet;
25import java.util.List;
26import java.util.Set;
27import java.util.regex.Matcher;
28import java.util.regex.Pattern;
29
30import javax.swing.AbstractAction;
31import javax.swing.BorderFactory;
32import javax.swing.JButton;
33import javax.swing.JLabel;
34import javax.swing.JPanel;
35import javax.swing.JSpinner;
36import javax.swing.KeyStroke;
37import javax.swing.SpinnerNumberModel;
38import javax.swing.event.ChangeEvent;
39import javax.swing.event.ChangeListener;
40import javax.swing.text.JTextComponent;
41
42import org.openstreetmap.gui.jmapviewer.JMapViewer;
43import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
44import org.openstreetmap.gui.jmapviewer.OsmMercator;
45import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
46import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
47import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
48import org.openstreetmap.josm.data.Bounds;
49import org.openstreetmap.josm.data.Version;
50import org.openstreetmap.josm.data.coor.LatLon;
51import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
52import org.openstreetmap.josm.gui.widgets.HtmlPanel;
53import org.openstreetmap.josm.gui.widgets.JosmTextField;
54import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
55import org.openstreetmap.josm.tools.ImageProvider;
56
57/**
58 * TileSelectionBBoxChooser allows to select a bounding box (i.e. for downloading) based
59 * on OSM tile numbers.
60 *
61 * TileSelectionBBoxChooser can be embedded as component in a Swing container. Example:
62 * <pre>
63 * JFrame f = new JFrame(....);
64 * f.getContentPane().setLayout(new BorderLayout()));
65 * TileSelectionBBoxChooser chooser = new TileSelectionBBoxChooser();
66 * f.add(chooser, BorderLayout.CENTER);
67 * chooser.addPropertyChangeListener(new PropertyChangeListener() {
68 * public void propertyChange(PropertyChangeEvent evt) {
69 * // listen for BBOX events
70 * if (evt.getPropertyName().equals(BBoxChooser.BBOX_PROP)) {
71 * Main.info("new bbox based on OSM tiles selected: " + (Bounds)evt.getNewValue());
72 * }
73 * }
74 * });
75 *
76 * // init the chooser with a bounding box
77 * chooser.setBoundingBox(....);
78 *
79 * f.setVisible(true);
80 * </pre>
81 */
82public class TileSelectionBBoxChooser extends JPanel implements BBoxChooser{
83
84 /** the current bounding box */
85 private Bounds bbox;
86 /** the map viewer showing the selected bounding box */
87 private TileBoundsMapView mapViewer;
88 /** a panel for entering a bounding box given by a tile grid and a zoom level */
89 private TileGridInputPanel pnlTileGrid;
90 /** a panel for entering a bounding box given by the address of an individual OSM tile at
91 * a given zoom level
92 */
93 private TileAddressInputPanel pnlTileAddress;
94
95 /**
96 * builds the UI
97 */
98 protected void build() {
99 setLayout(new GridBagLayout());
100
101 GridBagConstraints gc = new GridBagConstraints();
102 gc.weightx = 0.5;
103 gc.fill = GridBagConstraints.HORIZONTAL;
104 gc.anchor = GridBagConstraints.NORTHWEST;
105 add(pnlTileGrid = new TileGridInputPanel(), gc);
106
107 gc.gridx = 1;
108 add(pnlTileAddress = new TileAddressInputPanel(), gc);
109
110 gc.gridx = 0;
111 gc.gridy = 1;
112 gc.gridwidth = 2;
113 gc.weightx = 1.0;
114 gc.weighty = 1.0;
115 gc.fill = GridBagConstraints.BOTH;
116 gc.insets = new Insets(2,2,2,2);
117 add(mapViewer = new TileBoundsMapView(), gc);
118 mapViewer.setFocusable(false);
119 mapViewer.setZoomContolsVisible(false);
120 mapViewer.setMapMarkerVisible(false);
121
122 pnlTileAddress.addPropertyChangeListener(pnlTileGrid);
123 pnlTileGrid.addPropertyChangeListener(new TileBoundsChangeListener());
124 }
125
126 /**
127 * Constructs a new {@code TileSelectionBBoxChooser}.
128 */
129 public TileSelectionBBoxChooser() {
130 build();
131 }
132
133 /**
134 * Replies the current bounding box. null, if no valid bounding box is currently selected.
135 *
136 */
137 @Override
138 public Bounds getBoundingBox() {
139 return bbox;
140 }
141
142 /**
143 * Sets the current bounding box.
144 *
145 * @param bbox the bounding box. null, if this widget isn't initialized with a bounding box
146 */
147 @Override
148 public void setBoundingBox(Bounds bbox) {
149 pnlTileGrid.initFromBoundingBox(bbox);
150 }
151
152 protected void refreshMapView() {
153 if (bbox == null) return;
154
155 // calc the screen coordinates for the new selection rectangle
156 MapMarkerDot xmin_ymin = new MapMarkerDot(bbox.getMinLat(), bbox.getMinLon());
157 MapMarkerDot xmax_ymax = new MapMarkerDot(bbox.getMaxLat(), bbox.getMaxLon());
158
159 List<MapMarker> marker = new ArrayList<MapMarker>(2);
160 marker.add(xmin_ymin);
161 marker.add(xmax_ymax);
162 mapViewer.setBoundingBox(bbox);
163 mapViewer.setMapMarkerList(marker);
164 mapViewer.setDisplayToFitMapMarkers();
165 mapViewer.zoomOut();
166 }
167
168 /**
169 * Computes the bounding box given a tile grid.
170 *
171 * @param tb the description of the tile grid
172 * @return the bounding box
173 */
174 protected Bounds convertTileBoundsToBoundingBox(TileBounds tb) {
175 LatLon min = getNorthWestLatLonOfTile(tb.min, tb.zoomLevel);
176 Point p = new Point(tb.max);
177 p.x++;
178 p.y++;
179 LatLon max = getNorthWestLatLonOfTile(p, tb.zoomLevel);
180 return new Bounds(max.lat(), min.lon(), min.lat(), max.lon());
181 }
182
183 /**
184 * Replies lat/lon of the north/west-corner of a tile at a specific zoom level
185 *
186 * @param tile the tile address (x,y)
187 * @param zoom the zoom level
188 * @return lat/lon of the north/west-corner of a tile at a specific zoom level
189 */
190 protected LatLon getNorthWestLatLonOfTile(Point tile, int zoom) {
191 double lon = tile.x / Math.pow(2.0, zoom) * 360.0 - 180;
192 double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI - (2.0 * Math.PI * tile.y) / Math.pow(2.0, zoom))));
193 return new LatLon(lat, lon);
194 }
195
196 /**
197 * Listens to changes in the selected tile bounds, refreshes the map view and emits
198 * property change events for {@link BBoxChooser#BBOX_PROP}
199 */
200 class TileBoundsChangeListener implements PropertyChangeListener {
201 @Override
202 public void propertyChange(PropertyChangeEvent evt) {
203 if (!evt.getPropertyName().equals(TileGridInputPanel.TILE_BOUNDS_PROP)) return;
204 TileBounds tb = (TileBounds)evt.getNewValue();
205 Bounds oldValue = TileSelectionBBoxChooser.this.bbox;
206 TileSelectionBBoxChooser.this.bbox = convertTileBoundsToBoundingBox(tb);
207 firePropertyChange(BBOX_PROP, oldValue, TileSelectionBBoxChooser.this.bbox);
208 refreshMapView();
209 }
210 }
211
212 /**
213 * A panel for describing a rectangular area of OSM tiles at a given zoom level.
214 *
215 * The panel emits PropertyChangeEvents for the property {@link TileGridInputPanel#TILE_BOUNDS_PROP}
216 * when the user successfully enters a valid tile grid specification.
217 *
218 */
219 static private class TileGridInputPanel extends JPanel implements PropertyChangeListener{
220 static public final String TILE_BOUNDS_PROP = TileGridInputPanel.class.getName() + ".tileBounds";
221
222 private JosmTextField tfMaxY;
223 private JosmTextField tfMinY;
224 private JosmTextField tfMaxX;
225 private JosmTextField tfMinX;
226 private TileCoordinateValidator valMaxY;
227 private TileCoordinateValidator valMinY;
228 private TileCoordinateValidator valMaxX;
229 private TileCoordinateValidator valMinX;
230 private JSpinner spZoomLevel;
231 private TileBoundsBuilder tileBoundsBuilder = new TileBoundsBuilder();
232 private boolean doFireTileBoundChanged = true;
233
234 protected JPanel buildTextPanel() {
235 JPanel pnl = new JPanel(new BorderLayout());
236 HtmlPanel msg = new HtmlPanel();
237 msg.setText(tr("<html>Please select a <strong>range of OSM tiles</strong> at a given zoom level.</html>"));
238 pnl.add(msg);
239 return pnl;
240 }
241
242 protected JPanel buildZoomLevelPanel() {
243 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
244 pnl.add(new JLabel(tr("Zoom level:")));
245 pnl.add(spZoomLevel = new JSpinner(new SpinnerNumberModel(0,0,18,1)));
246 spZoomLevel.addChangeListener(new ZomeLevelChangeHandler());
247 spZoomLevel.addChangeListener(tileBoundsBuilder);
248 return pnl;
249 }
250
251 protected JPanel buildTileGridInputPanel() {
252 JPanel pnl = new JPanel(new GridBagLayout());
253 pnl.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
254 GridBagConstraints gc = new GridBagConstraints();
255 gc.anchor = GridBagConstraints.NORTHWEST;
256 gc.insets = new Insets(0, 0, 2, 2);
257
258 gc.gridwidth = 2;
259 gc.gridx = 1;
260 gc.fill = GridBagConstraints.HORIZONTAL;
261 pnl.add(buildZoomLevelPanel(), gc);
262
263 gc.gridwidth = 1;
264 gc.gridy = 1;
265 gc.gridx = 1;
266 pnl.add(new JLabel(tr("from tile")), gc);
267
268 gc.gridx = 2;
269 pnl.add(new JLabel(tr("up to tile")), gc);
270
271 gc.gridx = 0;
272 gc.gridy = 2;
273 gc.weightx = 0.0;
274 pnl.add(new JLabel("X:"), gc);
275
276
277 gc.gridx = 1;
278 gc.weightx = 0.5;
279 pnl.add(tfMinX = new JosmTextField(), gc);
280 valMinX = new TileCoordinateValidator(tfMinX);
281 SelectAllOnFocusGainedDecorator.decorate(tfMinX);
282 tfMinX.addActionListener(tileBoundsBuilder);
283 tfMinX.addFocusListener(tileBoundsBuilder);
284
285 gc.gridx = 2;
286 gc.weightx = 0.5;
287 pnl.add(tfMaxX = new JosmTextField(), gc);
288 valMaxX = new TileCoordinateValidator(tfMaxX);
289 SelectAllOnFocusGainedDecorator.decorate(tfMaxX);
290 tfMaxX.addActionListener(tileBoundsBuilder);
291 tfMaxX.addFocusListener(tileBoundsBuilder);
292
293 gc.gridx = 0;
294 gc.gridy = 3;
295 gc.weightx = 0.0;
296 pnl.add(new JLabel("Y:"), gc);
297
298 gc.gridx = 1;
299 gc.weightx = 0.5;
300 pnl.add(tfMinY = new JosmTextField(), gc);
301 valMinY = new TileCoordinateValidator(tfMinY);
302 SelectAllOnFocusGainedDecorator.decorate(tfMinY);
303 tfMinY.addActionListener(tileBoundsBuilder);
304 tfMinY.addFocusListener(tileBoundsBuilder);
305
306 gc.gridx = 2;
307 gc.weightx = 0.5;
308 pnl.add(tfMaxY = new JosmTextField(), gc);
309 valMaxY = new TileCoordinateValidator(tfMaxY);
310 SelectAllOnFocusGainedDecorator.decorate(tfMaxY);
311 tfMaxY.addActionListener(tileBoundsBuilder);
312 tfMaxY.addFocusListener(tileBoundsBuilder);
313
314 gc.gridy = 4;
315 gc.gridx = 0;
316 gc.gridwidth = 3;
317 gc.weightx = 1.0;
318 gc.weighty = 1.0;
319 gc.fill = GridBagConstraints.BOTH;
320 pnl.add(new JPanel(), gc);
321 return pnl;
322 }
323
324 protected void build() {
325 setLayout(new BorderLayout());
326 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
327 add(buildTextPanel(), BorderLayout.NORTH);
328 add(buildTileGridInputPanel(), BorderLayout.CENTER);
329
330 Set<AWTKeyStroke> forwardKeys = new HashSet<AWTKeyStroke>(getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
331 forwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
332 setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,forwardKeys);
333 }
334
335 public TileGridInputPanel() {
336 build();
337 }
338
339 public void initFromBoundingBox(Bounds bbox) {
340 if (bbox == null)
341 return;
342 TileBounds tb = new TileBounds();
343 tb.zoomLevel = (Integer) spZoomLevel.getValue();
344 tb.min = new Point(
345 Math.max(0,lonToTileX(tb.zoomLevel, bbox.getMinLon())),
346 Math.max(0,latToTileY(tb.zoomLevel, bbox.getMaxLat() - 0.00001))
347 );
348 tb.max = new Point(
349 Math.max(0,lonToTileX(tb.zoomLevel, bbox.getMaxLon())),
350 Math.max(0,latToTileY(tb.zoomLevel, bbox.getMinLat() - 0.00001))
351 );
352 doFireTileBoundChanged = false;
353 setTileBounds(tb);
354 doFireTileBoundChanged = true;
355 }
356
357 public static int latToTileY(int zoom, double lat) {
358 if ((zoom < 3) || (zoom > 18)) return -1;
359 double l = lat / 180 * Math.PI;
360 double pf = Math.log(Math.tan(l) + (1/Math.cos(l)));
361 return (int) ((1<<(zoom-1)) * (Math.PI - pf) / Math.PI);
362 }
363
364 public static int lonToTileX(int zoom, double lon) {
365 if ((zoom < 3) || (zoom > 18)) return -1;
366 return (int) ((1<<(zoom-3)) * (lon + 180.0) / 45.0);
367 }
368
369 public void setTileBounds(TileBounds tileBounds) {
370 tfMinX.setText(Integer.toString(tileBounds.min.x));
371 tfMinY.setText(Integer.toString(tileBounds.min.y));
372 tfMaxX.setText(Integer.toString(tileBounds.max.x));
373 tfMaxY.setText(Integer.toString(tileBounds.max.y));
374 spZoomLevel.setValue(tileBounds.zoomLevel);
375 }
376
377 @Override
378 public void propertyChange(PropertyChangeEvent evt) {
379 if (evt.getPropertyName().equals(TileAddressInputPanel.TILE_BOUNDS_PROP)) {
380 TileBounds tb = (TileBounds)evt.getNewValue();
381 setTileBounds(tb);
382 fireTileBoundsChanged(tb);
383 }
384 }
385
386 protected void fireTileBoundsChanged(TileBounds tb) {
387 if (!doFireTileBoundChanged) return;
388 firePropertyChange(TILE_BOUNDS_PROP, null, tb);
389 }
390
391 class ZomeLevelChangeHandler implements ChangeListener {
392 @Override
393 public void stateChanged(ChangeEvent e) {
394 int zoomLevel = (Integer)spZoomLevel.getValue();
395 valMaxX.setZoomLevel(zoomLevel);
396 valMaxY.setZoomLevel(zoomLevel);
397 valMinX.setZoomLevel(zoomLevel);
398 valMinY.setZoomLevel(zoomLevel);
399 }
400 }
401
402 class TileBoundsBuilder implements ActionListener, FocusListener, ChangeListener {
403 protected void buildTileBounds() {
404 if (!valMaxX.isValid()) return;
405 if (!valMaxY.isValid()) return;
406 if (!valMinX.isValid()) return;
407 if (!valMinY.isValid()) return;
408 Point min = new Point(valMinX.getTileIndex(), valMinY.getTileIndex());
409 Point max = new Point(valMaxX.getTileIndex(), valMaxY.getTileIndex());
410 if (min.x > max.x) {
411
412 }
413 int zoomlevel = (Integer)spZoomLevel.getValue();
414 TileBounds tb = new TileBounds(min, max, zoomlevel);
415 fireTileBoundsChanged(tb);
416 }
417
418 @Override
419 public void focusGained(FocusEvent e) {/* irrelevant */}
420
421 @Override
422 public void focusLost(FocusEvent e) {
423 buildTileBounds();
424 }
425
426 @Override
427 public void actionPerformed(ActionEvent e) {
428 buildTileBounds();
429 }
430
431 @Override
432 public void stateChanged(ChangeEvent e) {
433 buildTileBounds();
434 }
435 }
436 }
437
438 /**
439 * A panel for entering the address of a single OSM tile at a given zoom level.
440 *
441 */
442 static private class TileAddressInputPanel extends JPanel {
443
444 static public final String TILE_BOUNDS_PROP = TileAddressInputPanel.class.getName() + ".tileBounds";
445
446 private JosmTextField tfTileAddress;
447 private TileAddressValidator valTileAddress;
448
449 protected JPanel buildTextPanel() {
450 JPanel pnl = new JPanel(new BorderLayout());
451 HtmlPanel msg = new HtmlPanel();
452 msg.setText(tr("<html>Alternatively you may enter a <strong>tile address</strong> for a single tile "
453 + "in the format <i>zoomlevel/x/y</i>, i.e. <i>15/256/223</i>. Tile addresses "
454 + "in the format <i>zoom,x,y</i> or <i>zoom;x;y</i> are valid too.</html>"));
455 pnl.add(msg);
456 return pnl;
457 }
458
459 protected JPanel buildTileAddressInputPanel() {
460 JPanel pnl = new JPanel(new GridBagLayout());
461 GridBagConstraints gc = new GridBagConstraints();
462 gc.anchor = GridBagConstraints.NORTHWEST;
463 gc.fill = GridBagConstraints.HORIZONTAL;
464 gc.weightx = 0.0;
465 gc.insets = new Insets(0,0,2,2);
466 pnl.add(new JLabel(tr("Tile address:")), gc);
467
468 gc.weightx = 1.0;
469 gc.gridx = 1;
470 pnl.add(tfTileAddress = new JosmTextField(), gc);
471 valTileAddress = new TileAddressValidator(tfTileAddress);
472 SelectAllOnFocusGainedDecorator.decorate(tfTileAddress);
473
474 gc.weightx = 0.0;
475 gc.gridx = 2;
476 ApplyTileAddressAction applyTileAddressAction = new ApplyTileAddressAction();
477 JButton btn = new JButton(applyTileAddressAction);
478 btn.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
479 pnl.add(btn, gc);
480 tfTileAddress.addActionListener(applyTileAddressAction);
481 return pnl;
482 }
483
484 protected void build() {
485 setLayout(new GridBagLayout());
486 GridBagConstraints gc = new GridBagConstraints();
487 gc.anchor = GridBagConstraints.NORTHWEST;
488 gc.fill = GridBagConstraints.HORIZONTAL;
489 gc.weightx = 1.0;
490 gc.insets = new Insets(0,0,5,0);
491 add(buildTextPanel(), gc);
492
493 gc.gridy = 1;
494 add(buildTileAddressInputPanel(), gc);
495
496 // filler - grab remaining space
497 gc.gridy = 2;
498 gc.fill = GridBagConstraints.BOTH;
499 gc.weighty = 1.0;
500 add(new JPanel(), gc);
501 }
502
503 public TileAddressInputPanel() {
504 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
505 build();
506 }
507
508 protected void fireTileBoundsChanged(TileBounds tb){
509 firePropertyChange(TILE_BOUNDS_PROP, null, tb);
510 }
511
512 class ApplyTileAddressAction extends AbstractAction {
513 public ApplyTileAddressAction() {
514 putValue(SMALL_ICON, ImageProvider.get("apply"));
515 putValue(SHORT_DESCRIPTION, tr("Apply the tile address"));
516 }
517
518 @Override
519 public void actionPerformed(ActionEvent e) {
520 TileBounds tb = valTileAddress.getTileBounds();
521 if (tb != null) {
522 fireTileBoundsChanged(tb);
523 }
524 }
525 }
526 }
527
528 /**
529 * Validates a tile address
530 */
531 static private class TileAddressValidator extends AbstractTextComponentValidator {
532
533 private TileBounds tileBounds = null;
534
535 public TileAddressValidator(JTextComponent tc) throws IllegalArgumentException {
536 super(tc);
537 }
538
539 @Override
540 public boolean isValid() {
541 String value = getComponent().getText().trim();
542 Matcher m = Pattern.compile("(\\d+)[^\\d]+(\\d+)[^\\d]+(\\d+)").matcher(value);
543 tileBounds = null;
544 if (!m.matches()) return false;
545 int zoom;
546 try {
547 zoom = Integer.parseInt(m.group(1));
548 } catch(NumberFormatException e){
549 return false;
550 }
551 if (zoom < 0 || zoom > 18) return false;
552
553 int x;
554 try {
555 x = Integer.parseInt(m.group(2));
556 } catch(NumberFormatException e){
557 return false;
558 }
559 if (x < 0 || x >= Math.pow(2, zoom)) return false;
560 int y;
561 try {
562 y = Integer.parseInt(m.group(3));
563 } catch(NumberFormatException e){
564 return false;
565 }
566 if (y < 0 || y >= Math.pow(2, zoom)) return false;
567
568 tileBounds = new TileBounds(new Point(x,y), new Point(x,y), zoom);
569 return true;
570 }
571
572 @Override
573 public void validate() {
574 if (isValid()) {
575 feedbackValid(tr("Please enter a tile address"));
576 } else {
577 feedbackInvalid(tr("The current value isn''t a valid tile address", getComponent().getText()));
578 }
579 }
580
581 public TileBounds getTileBounds() {
582 return tileBounds;
583 }
584 }
585
586 /**
587 * Validates the x- or y-coordinate of a tile at a given zoom level.
588 *
589 */
590 static private class TileCoordinateValidator extends AbstractTextComponentValidator {
591 private int zoomLevel;
592 private int tileIndex;
593
594 public TileCoordinateValidator(JTextComponent tc) throws IllegalArgumentException {
595 super(tc);
596 }
597
598 public void setZoomLevel(int zoomLevel) {
599 this.zoomLevel = zoomLevel;
600 validate();
601 }
602
603 @Override
604 public boolean isValid() {
605 String value = getComponent().getText().trim();
606 try {
607 if (value.isEmpty()) {
608 tileIndex = 0;
609 } else {
610 tileIndex = Integer.parseInt(value);
611 }
612 } catch(NumberFormatException e) {
613 return false;
614 }
615 if (tileIndex < 0 || tileIndex >= Math.pow(2, zoomLevel)) return false;
616
617 return true;
618 }
619
620 @Override
621 public void validate() {
622 if (isValid()) {
623 feedbackValid(tr("Please enter a tile index"));
624 } else {
625 feedbackInvalid(tr("The current value isn''t a valid tile index for the given zoom level", getComponent().getText()));
626 }
627 }
628
629 public int getTileIndex() {
630 return tileIndex;
631 }
632 }
633
634 /**
635 * Represents a rectangular area of tiles at a given zoom level.
636 *
637 */
638 static private class TileBounds {
639 public Point min;
640 public Point max;
641 public int zoomLevel;
642
643 public TileBounds() {
644 zoomLevel = 0;
645 min = new Point(0,0);
646 max = new Point(0,0);
647 }
648
649 public TileBounds(Point min, Point max, int zoomLevel) {
650 this.min = min;
651 this.max = max;
652 this.zoomLevel = zoomLevel;
653 }
654
655 @Override
656 public String toString() {
657 StringBuffer sb = new StringBuffer();
658 sb.append("min=").append(min.x).append(",").append(min.y).append(",");
659 sb.append("max=").append(max.x).append(",").append(max.y).append(",");
660 sb.append("zoom=").append(zoomLevel);
661 return sb.toString();
662 }
663 }
664
665 /**
666 * The map view used in this bounding box chooser
667 */
668 static private class TileBoundsMapView extends JMapViewer {
669 private Point min;
670 private Point max;
671
672 public TileBoundsMapView() {
673 setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
674 TileLoader loader = tileController.getTileLoader();
675 if (loader instanceof OsmTileLoader) {
676 ((OsmTileLoader)loader).headers.put("User-Agent", Version.getInstance().getFullAgentString());
677 }
678 }
679
680 public void setBoundingBox(Bounds bbox){
681 if (bbox == null) {
682 min = null;
683 max = null;
684 } else {
685 int y1 = OsmMercator.LatToY(bbox.getMinLat(), MAX_ZOOM);
686 int y2 = OsmMercator.LatToY(bbox.getMaxLat(), MAX_ZOOM);
687 int x1 = OsmMercator.LonToX(bbox.getMinLon(), MAX_ZOOM);
688 int x2 = OsmMercator.LonToX(bbox.getMaxLon(), MAX_ZOOM);
689
690 min = new Point(Math.min(x1, x2), Math.min(y1, y2));
691 max = new Point(Math.max(x1, x2), Math.max(y1, y2));
692 }
693 repaint();
694 }
695
696 protected Point getTopLeftCoordinates() {
697 return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
698 }
699
700 /**
701 * Draw the map.
702 */
703 @Override
704 public void paint(Graphics g) {
705 try {
706 super.paint(g);
707 if (min == null || max == null) return;
708 int zoomDiff = MAX_ZOOM - zoom;
709 Point tlc = getTopLeftCoordinates();
710 int x_min = (min.x >> zoomDiff) - tlc.x;
711 int y_min = (min.y >> zoomDiff) - tlc.y;
712 int x_max = (max.x >> zoomDiff) - tlc.x;
713 int y_max = (max.y >> zoomDiff) - tlc.y;
714
715 int w = x_max - x_min;
716 int h = y_max - y_min;
717 g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
718 g.fillRect(x_min, y_min, w, h);
719
720 g.setColor(Color.BLACK);
721 g.drawRect(x_min, y_min, w, h);
722 } catch (Exception e) {
723 e.printStackTrace();
724 }
725 }
726 }
727}
Note: See TracBrowser for help on using the repository browser.