001    package org.openstreetmap.gui.jmapviewer;
002    
003    //License: GPL. Copyright 2008 by Jan Peter Stotz
004    
005    import java.awt.Color;
006    import java.awt.Dimension;
007    import java.awt.Font;
008    import java.awt.Graphics;
009    import java.awt.Image;
010    import java.awt.Insets;
011    import java.awt.Point;
012    import java.awt.Rectangle;
013    import java.awt.event.ActionEvent;
014    import java.awt.event.ActionListener;
015    import java.awt.event.MouseEvent;
016    import java.awt.font.TextAttribute;
017    import java.awt.geom.Rectangle2D;
018    import java.util.HashMap;
019    import java.util.LinkedList;
020    import java.util.List;
021    
022    import javax.swing.ImageIcon;
023    import javax.swing.JButton;
024    import javax.swing.JPanel;
025    import javax.swing.JSlider;
026    import javax.swing.event.ChangeEvent;
027    import javax.swing.event.ChangeListener;
028    import javax.swing.event.EventListenerList;
029    
030    import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent;
031    import org.openstreetmap.gui.jmapviewer.events.JMVCommandEvent.COMMAND;
032    import org.openstreetmap.gui.jmapviewer.interfaces.JMapViewerEventListener;
033    import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
034    import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
035    import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
036    import org.openstreetmap.gui.jmapviewer.interfaces.TileCache;
037    import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
038    import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener;
039    import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
040    import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
041    
042    /**
043     *
044     * Provides a simple panel that displays pre-rendered map tiles loaded from the
045     * OpenStreetMap project.
046     *
047     * @author Jan Peter Stotz
048     *
049     */
050    public class JMapViewer extends JPanel implements TileLoaderListener {
051    
052        private static final long serialVersionUID = 1L;
053    
054        /**
055         * Vectors for clock-wise tile painting
056         */
057        protected static final Point[] move = { new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1) };
058    
059        public static final int MAX_ZOOM = 22;
060        public static final int MIN_ZOOM = 0;
061    
062        protected List<MapMarker> mapMarkerList;
063        protected List<MapRectangle> mapRectangleList;
064        protected List<MapPolygon> mapPolygonList;
065    
066        protected boolean mapMarkersVisible;
067        protected boolean mapRectanglesVisible;
068        protected boolean mapPolygonsVisible;
069    
070        protected boolean tileGridVisible;
071    
072        protected TileController tileController;
073    
074        /**
075         * x- and y-position of the center of this map-panel on the world map
076         * denoted in screen pixel regarding the current zoom level.
077         */
078        protected Point center;
079    
080        /**
081         * Current zoom level
082         */
083        protected int zoom;
084    
085        protected JSlider zoomSlider;
086        protected JButton zoomInButton;
087        protected JButton zoomOutButton;
088    
089        private TileSource tileSource;
090    
091        protected AttributionSupport attribution = new AttributionSupport();
092    
093        /**
094         * Creates a standard {@link JMapViewer} instance that can be controlled via
095         * mouse: hold right mouse button for moving, double click left mouse button
096         * or use mouse wheel for zooming. Loaded tiles are stored the
097         * {@link MemoryTileCache} and the tile loader uses 4 parallel threads for
098         * retrieving the tiles.
099         */
100        public JMapViewer() {
101            this(new MemoryTileCache(), 4);
102            new DefaultMapController(this);
103        }
104    
105        public JMapViewer(TileCache tileCache, int downloadThreadCount) {
106            super();
107            tileSource = new OsmTileSource.Mapnik();
108            tileController = new TileController(tileSource, tileCache, this);
109            mapMarkerList = new LinkedList<MapMarker>();
110            mapPolygonList = new LinkedList<MapPolygon>();
111            mapRectangleList = new LinkedList<MapRectangle>();
112            mapMarkersVisible = true;
113            mapRectanglesVisible = true;
114            mapPolygonsVisible = true;
115            tileGridVisible = false;
116            setLayout(null);
117            initializeZoomSlider();
118            setMinimumSize(new Dimension(tileSource.getTileSize(), tileSource.getTileSize()));
119            setPreferredSize(new Dimension(400, 400));
120            setDisplayPositionByLatLon(50, 9, 3);
121            //setToolTipText("");
122        }
123    
124        @Override
125        public String getToolTipText(MouseEvent event) {
126            //        Point screenPoint = event.getLocationOnScreen();
127            //        Coordinate c = getPosition(screenPoint);
128            return super.getToolTipText(event);
129        }
130    
131        protected void initializeZoomSlider() {
132            zoomSlider = new JSlider(MIN_ZOOM, tileController.getTileSource().getMaxZoom());
133            zoomSlider.setOrientation(JSlider.VERTICAL);
134            zoomSlider.setBounds(10, 10, 30, 150);
135            zoomSlider.setOpaque(false);
136            zoomSlider.addChangeListener(new ChangeListener() {
137                public void stateChanged(ChangeEvent e) {
138                    setZoom(zoomSlider.getValue());
139                }
140            });
141            add(zoomSlider);
142            int size = 18;
143            try {
144                ImageIcon icon = new ImageIcon(getClass().getResource("images/plus.png"));
145                zoomInButton = new JButton(icon);
146            } catch (Exception e) {
147                zoomInButton = new JButton("+");
148                zoomInButton.setFont(new Font("sansserif", Font.BOLD, 9));
149                zoomInButton.setMargin(new Insets(0, 0, 0, 0));
150            }
151            zoomInButton.setBounds(4, 155, size, size);
152            zoomInButton.addActionListener(new ActionListener() {
153    
154                public void actionPerformed(ActionEvent e) {
155                    zoomIn();
156                }
157            });
158            add(zoomInButton);
159            try {
160                ImageIcon icon = new ImageIcon(getClass().getResource("images/minus.png"));
161                zoomOutButton = new JButton(icon);
162            } catch (Exception e) {
163                zoomOutButton = new JButton("-");
164                zoomOutButton.setFont(new Font("sansserif", Font.BOLD, 9));
165                zoomOutButton.setMargin(new Insets(0, 0, 0, 0));
166            }
167            zoomOutButton.setBounds(8 + size, 155, size, size);
168            zoomOutButton.addActionListener(new ActionListener() {
169    
170                public void actionPerformed(ActionEvent e) {
171                    zoomOut();
172                }
173            });
174            add(zoomOutButton);
175        }
176    
177        /**
178         * Changes the map pane so that it is centered on the specified coordinate
179         * at the given zoom level.
180         *
181         * @param lat
182         *            latitude of the specified coordinate
183         * @param lon
184         *            longitude of the specified coordinate
185         * @param zoom
186         *            {@link #MIN_ZOOM} <= zoom level <= {@link #MAX_ZOOM}
187         */
188        public void setDisplayPositionByLatLon(double lat, double lon, int zoom) {
189            setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), lat, lon, zoom);
190        }
191    
192        /**
193         * Changes the map pane so that the specified coordinate at the given zoom
194         * level is displayed on the map at the screen coordinate
195         * <code>mapPoint</code>.
196         *
197         * @param mapPoint
198         *            point on the map denoted in pixels where the coordinate should
199         *            be set
200         * @param lat
201         *            latitude of the specified coordinate
202         * @param lon
203         *            longitude of the specified coordinate
204         * @param zoom
205         *            {@link #MIN_ZOOM} <= zoom level <=
206         *            {@link TileSource#getMaxZoom()}
207         */
208        public void setDisplayPositionByLatLon(Point mapPoint, double lat, double lon, int zoom) {
209            int x = OsmMercator.LonToX(lon, zoom);
210            int y = OsmMercator.LatToY(lat, zoom);
211            setDisplayPosition(mapPoint, x, y, zoom);
212        }
213    
214        public void setDisplayPosition(int x, int y, int zoom) {
215            setDisplayPosition(new Point(getWidth() / 2, getHeight() / 2), x, y, zoom);
216        }
217    
218        public void setDisplayPosition(Point mapPoint, int x, int y, int zoom) {
219            if (zoom > tileController.getTileSource().getMaxZoom() || zoom < MIN_ZOOM)
220                return;
221    
222            // Get the plain tile number
223            Point p = new Point();
224            p.x = x - mapPoint.x + getWidth() / 2;
225            p.y = y - mapPoint.y + getHeight() / 2;
226            center = p;
227            setIgnoreRepaint(true);
228            try {
229                int oldZoom = this.zoom;
230                this.zoom = zoom;
231                if (oldZoom != zoom) {
232                    zoomChanged(oldZoom);
233                }
234                if (zoomSlider.getValue() != zoom) {
235                    zoomSlider.setValue(zoom);
236                }
237            } finally {
238                setIgnoreRepaint(false);
239                repaint();
240            }
241        }
242    
243        /**
244         * Sets the displayed map pane and zoom level so that all chosen map elements are
245         * visible.
246         */
247        public void setDisplayToFitMapElements(boolean markers, boolean rectangles, boolean polygons) {
248            int nbElemToCheck = 0;
249            if (markers && mapMarkerList != null)
250                nbElemToCheck += mapMarkerList.size();
251            if (rectangles && mapRectangleList != null)
252                nbElemToCheck += mapRectangleList.size();
253            if (polygons && mapPolygonList != null)
254                nbElemToCheck += mapPolygonList.size();
255            if (nbElemToCheck == 0)
256                return;
257    
258            int x_min = Integer.MAX_VALUE;
259            int y_min = Integer.MAX_VALUE;
260            int x_max = Integer.MIN_VALUE;
261            int y_max = Integer.MIN_VALUE;
262            int mapZoomMax = tileController.getTileSource().getMaxZoom();
263    
264            if (markers) {
265                for (MapMarker marker : mapMarkerList) {
266                    int x = OsmMercator.LonToX(marker.getLon(), mapZoomMax);
267                    int y = OsmMercator.LatToY(marker.getLat(), mapZoomMax);
268                    x_max = Math.max(x_max, x);
269                    y_max = Math.max(y_max, y);
270                    x_min = Math.min(x_min, x);
271                    y_min = Math.min(y_min, y);
272                }
273            }
274    
275            if (rectangles) {
276                for (MapRectangle rectangle : mapRectangleList) {
277                    x_max = Math.max(x_max, OsmMercator.LonToX(rectangle.getBottomRight().getLon(), mapZoomMax));
278                    y_max = Math.max(y_max, OsmMercator.LatToY(rectangle.getTopLeft().getLat(), mapZoomMax));
279                    x_min = Math.min(x_min, OsmMercator.LonToX(rectangle.getTopLeft().getLon(), mapZoomMax));
280                    y_min = Math.min(y_min, OsmMercator.LatToY(rectangle.getBottomRight().getLat(), mapZoomMax));
281                }
282            }
283    
284            if (polygons) {
285                for (MapPolygon polygon : mapPolygonList) {
286                    for (Coordinate c : polygon.getPoints()) {
287                        int x = OsmMercator.LonToX(c.getLon(), mapZoomMax);
288                        int y = OsmMercator.LatToY(c.getLat(), mapZoomMax);
289                        x_max = Math.max(x_max, x);
290                        y_max = Math.max(y_max, y);
291                        x_min = Math.min(x_min, x);
292                        y_min = Math.min(y_min, y);
293                    }
294                }
295            }
296    
297            int height = Math.max(0, getHeight());
298            int width = Math.max(0, getWidth());
299            int newZoom = mapZoomMax;
300            int x = x_max - x_min;
301            int y = y_max - y_min;
302            while (x > width || y > height) {
303                newZoom--;
304                x >>= 1;
305                y >>= 1;
306            }
307            x = x_min + (x_max - x_min) / 2;
308            y = y_min + (y_max - y_min) / 2;
309            int z = 1 << (mapZoomMax - newZoom);
310            x /= z;
311            y /= z;
312            setDisplayPosition(x, y, newZoom);
313        }
314    
315    
316        /**
317         * Sets the displayed map pane and zoom level so that all map markers are
318         * visible.
319         */
320        public void setDisplayToFitMapMarkers() {
321            setDisplayToFitMapElements(true, false, false);
322        }
323    
324        /**
325         * Sets the displayed map pane and zoom level so that all map rectangles are
326         * visible.
327         */
328        public void setDisplayToFitMapRectangles() {
329            setDisplayToFitMapElements(false, true, false);
330        }
331    
332        /**
333         * Sets the displayed map pane and zoom level so that all map polygons are
334         * visible.
335         */
336        public void setDisplayToFitMapPolygons() {
337            setDisplayToFitMapElements(false, false, true);
338        }
339    
340        /**
341         * @return the center
342         */
343        public Point getCenter() {
344            return center;
345        }
346    
347        /**
348         * @param center the center to set
349         */
350        public void setCenter(Point center) {
351            this.center = center;
352        }
353    
354        /**
355         * Calculates the latitude/longitude coordinate of the center of the
356         * currently displayed map area.
357         *
358         * @return latitude / longitude
359         */
360        public Coordinate getPosition() {
361            double lon = OsmMercator.XToLon(center.x, zoom);
362            double lat = OsmMercator.YToLat(center.y, zoom);
363            return new Coordinate(lat, lon);
364        }
365    
366        /**
367         * Converts the relative pixel coordinate (regarding the top left corner of
368         * the displayed map) into a latitude / longitude coordinate
369         *
370         * @param mapPoint
371         *            relative pixel coordinate regarding the top left corner of the
372         *            displayed map
373         * @return latitude / longitude
374         */
375        public Coordinate getPosition(Point mapPoint) {
376            return getPosition(mapPoint.x, mapPoint.y);
377        }
378    
379        /**
380         * Converts the relative pixel coordinate (regarding the top left corner of
381         * the displayed map) into a latitude / longitude coordinate
382         *
383         * @param mapPointX
384         * @param mapPointY
385         * @return latitude / longitude
386         */
387        public Coordinate getPosition(int mapPointX, int mapPointY) {
388            int x = center.x + mapPointX - getWidth() / 2;
389            int y = center.y + mapPointY - getHeight() / 2;
390            double lon = OsmMercator.XToLon(x, zoom);
391            double lat = OsmMercator.YToLat(y, zoom);
392            return new Coordinate(lat, lon);
393        }
394    
395        /**
396         * Calculates the position on the map of a given coordinate
397         *
398         * @param lat
399         * @param lon
400         * @param checkOutside
401         * @return point on the map or <code>null</code> if the point is not visible
402         *         and checkOutside set to <code>true</code>
403         */
404        public Point getMapPosition(double lat, double lon, boolean checkOutside) {
405            int x = OsmMercator.LonToX(lon, zoom);
406            int y = OsmMercator.LatToY(lat, zoom);
407            x -= center.x - getWidth() / 2;
408            y -= center.y - getHeight() / 2;
409            if (checkOutside) {
410                if (x < 0 || y < 0 || x > getWidth() || y > getHeight())
411                    return null;
412            }
413            return new Point(x, y);
414        }
415    
416        /**
417         * Calculates the position on the map of a given coordinate
418         *
419         * @param lat
420         * @param lon
421         * @return point on the map or <code>null</code> if the point is not visible
422         */
423        public Point getMapPosition(double lat, double lon) {
424            return getMapPosition(lat, lon, true);
425        }
426    
427        /**
428         * Calculates the position on the map of a given coordinate
429         *
430         * @param coord
431         * @return point on the map or <code>null</code> if the point is not visible
432         */
433        public Point getMapPosition(Coordinate coord) {
434            if (coord != null)
435                return getMapPosition(coord.getLat(), coord.getLon());
436            else
437                return null;
438        }
439    
440        /**
441         * Calculates the position on the map of a given coordinate
442         *
443         * @param coord
444         * @return point on the map or <code>null</code> if the point is not visible
445         *         and checkOutside set to <code>true</code>
446         */
447        public Point getMapPosition(Coordinate coord, boolean checkOutside) {
448            if (coord != null)
449                return getMapPosition(coord.getLat(), coord.getLon(), checkOutside);
450            else
451                return null;
452        }
453    
454        /**
455         * Gets the meter per pixel.
456         *
457         * @return the meter per pixel
458         * @author Jason Huntley
459         */
460        public double getMeterPerPixel() {
461            Point origin=new Point(5,5);
462            Point center=new Point(getWidth()/2, getHeight()/2);
463    
464            double pDistance=center.distance(origin);
465    
466            Coordinate originCoord=getPosition(origin);
467            Coordinate centerCoord=getPosition(center);
468    
469            double mDistance=OsmMercator.getDistance(originCoord.getLat(), originCoord.getLon(),
470                    centerCoord.getLat(), centerCoord.getLon());
471    
472            return mDistance/pDistance;
473        }
474    
475        @Override
476        protected void paintComponent(Graphics g) {
477            super.paintComponent(g);
478    
479            int iMove = 0;
480    
481            int tilesize = tileSource.getTileSize();
482            int tilex = center.x / tilesize;
483            int tiley = center.y / tilesize;
484            int off_x = (center.x % tilesize);
485            int off_y = (center.y % tilesize);
486    
487            int w2 = getWidth() / 2;
488            int h2 = getHeight() / 2;
489            int posx = w2 - off_x;
490            int posy = h2 - off_y;
491    
492            int diff_left = off_x;
493            int diff_right = tilesize - off_x;
494            int diff_top = off_y;
495            int diff_bottom = tilesize - off_y;
496    
497            boolean start_left = diff_left < diff_right;
498            boolean start_top = diff_top < diff_bottom;
499    
500            if (start_top) {
501                if (start_left) {
502                    iMove = 2;
503                } else {
504                    iMove = 3;
505                }
506            } else {
507                if (start_left) {
508                    iMove = 1;
509                } else {
510                    iMove = 0;
511                }
512            } // calculate the visibility borders
513            int x_min = -tilesize;
514            int y_min = -tilesize;
515            int x_max = getWidth();
516            int y_max = getHeight();
517    
518            // paint the tiles in a spiral, starting from center of the map
519            boolean painted = true;
520            int x = 0;
521            while (painted) {
522                painted = false;
523                for (int i = 0; i < 4; i++) {
524                    if (i % 2 == 0) {
525                        x++;
526                    }
527                    for (int j = 0; j < x; j++) {
528                        if (x_min <= posx && posx <= x_max && y_min <= posy && posy <= y_max) {
529                            // tile is visible
530                            Tile tile = tileController.getTile(tilex, tiley, zoom);
531                            if (tile != null) {
532                                painted = true;
533                                tile.paint(g, posx, posy);
534                                if (tileGridVisible) {
535                                    g.drawRect(posx, posy, tilesize, tilesize);
536                                }
537                            }
538                        }
539                        Point p = move[iMove];
540                        posx += p.x * tilesize;
541                        posy += p.y * tilesize;
542                        tilex += p.x;
543                        tiley += p.y;
544                    }
545                    iMove = (iMove + 1) % move.length;
546                }
547            }
548            // outer border of the map
549            int mapSize = tilesize << zoom;
550            g.drawRect(w2 - center.x, h2 - center.y, mapSize, mapSize);
551    
552            // g.drawString("Tiles in cache: " + tileCache.getTileCount(), 50, 20);
553    
554            if (mapPolygonsVisible && mapPolygonList != null) {
555                for (MapPolygon polygon : mapPolygonList) {
556                    paintPolygon(g, polygon);
557                }
558            }
559    
560            if (mapRectanglesVisible && mapRectangleList != null) {
561                for (MapRectangle rectangle : mapRectangleList) {
562                    paintRectangle(g, rectangle);
563                }
564            }
565    
566            if (mapMarkersVisible && mapMarkerList != null) {
567                for (MapMarker marker : mapMarkerList) {
568                    paintMarker(g, marker);
569                }
570            }
571    
572            attribution.paintAttribution(g, getWidth(), getHeight(), getPosition(0, 0), getPosition(getWidth(), getHeight()), zoom, this);
573        }
574    
575        /**
576         * Paint a single marker.
577         */
578        protected void paintMarker(Graphics g, MapMarker marker) {
579            Point p = getMapPosition(marker.getLat(), marker.getLon());
580            if (p != null) {
581                marker.paint(g, p);
582            }
583        }
584    
585        /**
586         * Paint a single rectangle.
587         */
588        protected void paintRectangle(Graphics g, MapRectangle rectangle) {
589            Coordinate topLeft = rectangle.getTopLeft();
590            Coordinate bottomRight = rectangle.getBottomRight();
591            if (topLeft != null && bottomRight != null) {
592                Point pTopLeft = getMapPosition(topLeft, false);
593                Point pBottomRight = getMapPosition(bottomRight, false);
594                if (pTopLeft != null && pBottomRight != null) {
595                    rectangle.paint(g, pTopLeft, pBottomRight);
596                }
597            }
598        }
599    
600        /**
601         * Paint a single polygon.
602         */
603        protected void paintPolygon(Graphics g, MapPolygon polygon) {
604            List<Coordinate> coords = polygon.getPoints();
605            if (coords != null && coords.size() >= 3) {
606                List<Point> points = new LinkedList<Point>();
607                for (Coordinate c : coords) {
608                    Point p = getMapPosition(c, false);
609                    if (p == null) {
610                        return;
611                    }
612                    points.add(p);
613                }
614                polygon.paint(g, points);
615            }
616        }
617    
618        /**
619         * Moves the visible map pane.
620         *
621         * @param x
622         *            horizontal movement in pixel.
623         * @param y
624         *            vertical movement in pixel
625         */
626        public void moveMap(int x, int y) {
627            center.x += x;
628            center.y += y;
629            repaint();
630            this.fireJMVEvent(new JMVCommandEvent(COMMAND.MOVE, this));
631        }
632    
633        /**
634         * @return the current zoom level
635         */
636        public int getZoom() {
637            return zoom;
638        }
639    
640        /**
641         * Increases the current zoom level by one
642         */
643        public void zoomIn() {
644            setZoom(zoom + 1);
645        }
646    
647        /**
648         * Increases the current zoom level by one
649         */
650        public void zoomIn(Point mapPoint) {
651            setZoom(zoom + 1, mapPoint);
652        }
653    
654        /**
655         * Decreases the current zoom level by one
656         */
657        public void zoomOut() {
658            setZoom(zoom - 1);
659        }
660    
661        /**
662         * Decreases the current zoom level by one
663         *
664         * @param mapPoint point to choose as center for new zoom level
665         */
666        public void zoomOut(Point mapPoint) {
667            setZoom(zoom - 1, mapPoint);
668        }
669    
670        /**
671         * Set the zoom level and center point for display
672         *
673         * @param zoom new zoom level
674         * @param mapPoint point to choose as center for new zoom level
675         */
676        public void setZoom(int zoom, Point mapPoint) {
677            if (zoom > tileController.getTileSource().getMaxZoom() || zoom < tileController.getTileSource().getMinZoom()
678                    || zoom == this.zoom)
679                return;
680            Coordinate zoomPos = getPosition(mapPoint);
681            tileController.cancelOutstandingJobs(); // Clearing outstanding load
682            // requests
683            setDisplayPositionByLatLon(mapPoint, zoomPos.getLat(), zoomPos.getLon(), zoom);
684    
685            this.fireJMVEvent(new JMVCommandEvent(COMMAND.ZOOM, this));
686        }
687    
688        /**
689         * Set the zoom level
690         *
691         * @param zoom new zoom level
692         */
693        public void setZoom(int zoom) {
694            setZoom(zoom, new Point(getWidth() / 2, getHeight() / 2));
695        }
696    
697        /**
698         * Every time the zoom level changes this method is called. Override it in
699         * derived implementations for adapting zoom dependent values. The new zoom
700         * level can be obtained via {@link #getZoom()}.
701         *
702         * @param oldZoom
703         *            the previous zoom level
704         */
705        protected void zoomChanged(int oldZoom) {
706            zoomSlider.setToolTipText("Zoom level " + zoom);
707            zoomInButton.setToolTipText("Zoom to level " + (zoom + 1));
708            zoomOutButton.setToolTipText("Zoom to level " + (zoom - 1));
709            zoomOutButton.setEnabled(zoom > tileController.getTileSource().getMinZoom());
710            zoomInButton.setEnabled(zoom < tileController.getTileSource().getMaxZoom());
711        }
712    
713        public boolean isTileGridVisible() {
714            return tileGridVisible;
715        }
716    
717        public void setTileGridVisible(boolean tileGridVisible) {
718            this.tileGridVisible = tileGridVisible;
719            repaint();
720        }
721    
722        public boolean getMapMarkersVisible() {
723            return mapMarkersVisible;
724        }
725    
726        /**
727         * Enables or disables painting of the {@link MapMarker}
728         *
729         * @param mapMarkersVisible
730         * @see #addMapMarker(MapMarker)
731         * @see #getMapMarkerList()
732         */
733        public void setMapMarkerVisible(boolean mapMarkersVisible) {
734            this.mapMarkersVisible = mapMarkersVisible;
735            repaint();
736        }
737    
738        public void setMapMarkerList(List<MapMarker> mapMarkerList) {
739            this.mapMarkerList = mapMarkerList;
740            repaint();
741        }
742    
743        public List<MapMarker> getMapMarkerList() {
744            return mapMarkerList;
745        }
746    
747        public void setMapRectangleList(List<MapRectangle> mapRectangleList) {
748            this.mapRectangleList = mapRectangleList;
749            repaint();
750        }
751    
752        public List<MapRectangle> getMapRectangleList() {
753            return mapRectangleList;
754        }
755    
756        public void setMapPolygonList(List<MapPolygon> mapPolygonList) {
757            this.mapPolygonList = mapPolygonList;
758            repaint();
759        }
760    
761        public List<MapPolygon> getMapPolygonList() {
762            return mapPolygonList;
763        }
764    
765        public void addMapMarker(MapMarker marker) {
766            mapMarkerList.add(marker);
767            repaint();
768        }
769    
770        public void removeMapMarker(MapMarker marker) {
771            mapMarkerList.remove(marker);
772            repaint();
773        }
774    
775        public void removeAllMapMarkers() {
776            mapMarkerList.clear();
777            repaint();
778        }
779    
780        public void addMapRectangle(MapRectangle rectangle) {
781            mapRectangleList.add(rectangle);
782            repaint();
783        }
784    
785        public void removeMapRectangle(MapRectangle rectangle) {
786            mapRectangleList.remove(rectangle);
787            repaint();
788        }
789    
790        public void removeAllMapRectangles() {
791            mapRectangleList.clear();
792            repaint();
793        }
794    
795        public void addMapPolygon(MapPolygon polygon) {
796            mapPolygonList.add(polygon);
797            repaint();
798        }
799    
800        public void removeMapPolygon(MapPolygon polygon) {
801            mapPolygonList.remove(polygon);
802            repaint();
803        }
804    
805        public void removeAllMapPolygons() {
806            mapPolygonList.clear();
807            repaint();
808        }
809    
810        public void setZoomContolsVisible(boolean visible) {
811            zoomSlider.setVisible(visible);
812            zoomInButton.setVisible(visible);
813            zoomOutButton.setVisible(visible);
814        }
815    
816        public boolean getZoomContolsVisible() {
817            return zoomSlider.isVisible();
818        }
819    
820        public void setTileSource(TileSource tileSource) {
821            if (tileSource.getMaxZoom() > MAX_ZOOM)
822                throw new RuntimeException("Maximum zoom level too high");
823            if (tileSource.getMinZoom() < MIN_ZOOM)
824                throw new RuntimeException("Minumim zoom level too low");
825            this.tileSource = tileSource;
826            tileController.setTileSource(tileSource);
827            zoomSlider.setMinimum(tileSource.getMinZoom());
828            zoomSlider.setMaximum(tileSource.getMaxZoom());
829            tileController.cancelOutstandingJobs();
830            if (zoom > tileSource.getMaxZoom()) {
831                setZoom(tileSource.getMaxZoom());
832            }
833    
834            attribution.initialize(tileSource);
835            repaint();
836        }
837    
838        public void tileLoadingFinished(Tile tile, boolean success) {
839            repaint();
840        }
841    
842        public boolean isMapRectanglesVisible() {
843            return mapRectanglesVisible;
844        }
845    
846        /**
847         * Enables or disables painting of the {@link MapRectangle}
848         *
849         * @param mapRectanglesVisible
850         * @see #addMapRectangle(MapRectangle)
851         * @see #getMapRectangleList()
852         */
853        public void setMapRectanglesVisible(boolean mapRectanglesVisible) {
854            this.mapRectanglesVisible = mapRectanglesVisible;
855            repaint();
856        }
857    
858        public boolean isMapPolygonsVisible() {
859            return mapPolygonsVisible;
860        }
861    
862        /**
863         * Enables or disables painting of the {@link MapPolygon}
864         *
865         * @param mapPolygonsVisible
866         * @see #addMapPolygon(MapPolygon)
867         * @see #getMapPolygonList()
868         */
869        public void setMapPolygonsVisible(boolean mapPolygonsVisible) {
870            this.mapPolygonsVisible = mapPolygonsVisible;
871            repaint();
872        }
873    
874        /**
875         * Return tile information caching class
876         * @see TileLoaderListener#getTileCache()
877         */
878        public TileCache getTileCache() {
879            return tileController.getTileCache();
880        }
881    
882        public void setTileLoader(TileLoader loader) {
883            tileController.setTileLoader(loader);
884        }
885    
886        protected EventListenerList listenerList = new EventListenerList();
887    
888        /**
889         * @param listener listener to set
890         */
891        public void addJMVListener(JMapViewerEventListener listener) {
892            listenerList.add(JMapViewerEventListener.class, listener);
893        }
894    
895        /**
896         * @param listener listener to remove
897         */
898        public void removeJMVListener(JMapViewerEventListener listener) {
899            listenerList.remove(JMapViewerEventListener.class, listener);
900        }
901    
902        /**
903         * Send an update to all objects registered with viewer
904         *
905         * @param event to dispatch
906         */
907        void fireJMVEvent(JMVCommandEvent evt) {
908            Object[] listeners = listenerList.getListenerList();
909            for (int i=0; i<listeners.length; i+=2) {
910                if (listeners[i]==JMapViewerEventListener.class) {
911                    ((JMapViewerEventListener)listeners[i+1]).processCommand(evt);
912                }
913            }
914        }
915    }