//********************************************************************** // //<copyright> // //BBN Technologies //10 Moulton Street //Cambridge, MA 02138 //(617) 873-8000 // //Copyright (C) BBNT Solutions LLC. All rights reserved. // //</copyright> //********************************************************************** // //$Source: ///cvs/darwars/ambush/aar/src/com/bbn/ambush/mission/MissionHandler.java,v //$ //$RCSfile: MissionHandler.java,v $ //$Revision: 1.10 $ //$Date: 2004/10/21 20:08:31 $ //$Author: dietrick $ // //********************************************************************** package com.bbn.openmap.layer.imageTile; import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.net.MalformedURLException; import java.util.Properties; import java.util.logging.Logger; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSlider; import com.bbn.openmap.Environment; import com.bbn.openmap.I18n; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.dataAccess.mapTile.MapTileFactory; import com.bbn.openmap.dataAccess.mapTile.MapTileRequester; import com.bbn.openmap.dataAccess.mapTile.ServerMapTileFactory; import com.bbn.openmap.dataAccess.mapTile.StandardMapTileFactory; import com.bbn.openmap.layer.OMGraphicHandlerLayer; import com.bbn.openmap.omGraphics.DrawingAttributes; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMText; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.ComponentFactory; import com.bbn.openmap.util.PropUtils; /** * A Layer that uses a MapTileFactory to display information (tiles) on the map. * Properties for this layer look like this: * * <pre> * * tiles.class=com.bbn.openmap.layer.imageTile.MapTileLayer * tiles.prettyName=TILES * tiles.tileFactory=com.bbn.openmap.dataAccess.mapTile.StandardMapTileFactory * tiles.rootDir=root_directory_of_tiles * #optional, .png is default * tiles.fileExt=.png * tiles.cacheSize=the number of mapTiles the factory should hold on to. The default is 100. * # transform for naming convention of tiles default is OSMMapTileCoordinateTransform, but it depends on the source of tiles. GDAL is TSMMapTileCoordinateTransform * tiles.mapTileTransform=com.bbn.openmap.dataAccess.mapTile.OSMMapTileCoordinateTransform, or com.bbn.openmap.dataAccess.mapTile.TMSMapTileCoordinateTransform * * </pre> * * You can use a server that provides image tiles: * * <pre> * * tiles.class=com.bbn.openmap.layer.imageTile.MapTileLayer * tiles.prettyName=TILES * tiles.tileFactory=com.bbn.openmap.dataAccess.mapTile.ServerMapTileFactory * tiles.rootDir=URL root directory of tiles * # a local location to cache tiles, to reduce load on server. * tiles.localCacheRootDir=/data/tiles/osmtiles * * # other properties are the same. * tiles.fileExt=.png * tiles.cacheSize=the number of mapTiles the factory should hold on to. The default is 100. * # transform for naming convention of tiles default is OSMMapTileCoordinateTransform, but it depends on the source of tiles. GDAL is TSMMapTileCoordinateTransform * mapTileTransform=com.bbn.openmap.dataAccess.mapTile.OSMMapTileCoordinateTransform, or com.bbn.openmap.dataAccess.mapTile.TMSMapTileCoordinateTransform * * </pre> * * The rootDir property can be defined as a pattern, with the zoom level z, x * tile coordinate and y tile coordinate set using {z}{x}{y} for however the * tiles are stored or retrieved: * * <pre> * rootDir=/data/tiles/{z}/{x}/{y}.png * * #or, for the ServerMapTileFactory: * rootDir=http://someserver.com/tileset/{z}/{x}/{y}.png * * </pre> * * In this case, the fileExt won't be used as the code will assume you are * setting that. * * To make things simpler, you can define a tiles.omp file that sits under the * tile root directory or at the top level of the jar file, and let it specify * the properties for the tile set. The properties in that file should be * unscoped: * * <pre> * * fileExt=.png * #for instance, for GDAL processed images you need this transform since tiles have difference reference coordinates * mapTileTransform=com.bbn.openmap.dataAccess.mapTile.TMSMapTileCoordinateTransform * #in jar file, should specify rootDir inside jar to tiles (don't need this for layers accessing local file system rootDirs, unless you want to specify z,x,y order differently): * rootDir=mytiles * * #optional * attribution=map data 2013 OpenStreetMap * * </pre> * * If you do this last configuration, all you need to define is rootDir (and * prettyName, class) property for layer, and then define all other props with * data. * * @author dietrick */ public class MapTileLayer extends OMGraphicHandlerLayer implements MapTileRequester { private static final long serialVersionUID = 1L; public static Logger logger = Logger.getLogger("com.bbn.openmap.layer.imageTile.TileLayer"); /** * Property that sets the class name of the MapTileFactory to use for this * layer. */ public final static String TILE_FACTORY_CLASS_PROPERTY = "tileFactory"; /** * Property to allow the MapTileFactory to call repaint on this layer as map * tiles become available. Default is false, enabling it will not allow this * layer to be used with an ImageServer (renderDataForProjection won't * work). */ public final static String INCREMENTAL_UPDATES_PROPERTY = "incrementalUpdates"; /** * A property to set if you want to force the layer to use tiles of a * certain zoom level. */ public final static String ZOOM_LEVEL_PROPERTY = "zoomLevel"; /** * A property to set for displaying attribution for the data used by the * layer. */ public final static String DATA_ATTRIBUTION_PROPERTY = "attribution"; /** * The MapTileFactory that knows how to fetch image files and create * OMRasters for them. */ protected MapTileFactory tileFactory; /** * Flag to allow this layer to set itself as a repaint callback object on * the tile factory. */ protected boolean incrementalUpdates = false; /** * The zoomLevel to use when requesting tiles from the MapTileFactory. Is -1 * for default, which lets the factory choose the zoom level based on the * current scale setting. You can choose 1-20 if you want to force the layer * to use something else. */ protected int zoomLevel = -1; /** * Attribution for the map data. If it exists, it will be displayed on the * lower left corner of the map. */ protected String attribution = null; /** * Rendering parameters for attribution string. */ protected DrawingAttributes attributionAttributes = DrawingAttributes.getDefaultClone(); public MapTileLayer() { setProjectionChangePolicy(new com.bbn.openmap.layer.policy.ListResetPCPolicy(this)); setTileFactory(new StandardMapTileFactory()); // We need to make this layer uninterruptable, because that messes with // the image file loading. setInterruptable(false); } public MapTileLayer(MapTileFactory tileFactory) { this(); this.tileFactory = tileFactory; } /** * OMGraphicHandlerLayer method, called with projection changes or whenever * else doPrepare() is called. Calls getTiles on the map tile factory. * * @return OMGraphicList that contains tiles to be displayed for the current * projection. */ public synchronized OMGraphicList prepare() { Projection projection = getProjection(); if (projection == null) { return null; } if (tileFactory != null) { return tileFactory.getTiles(projection, zoomLevel, new OMGraphicList()); } return null; } public void paint(java.awt.Graphics g) { super.paint(g); OMText attrib = getAttributionGraphic(); if (attrib != null) { attrib.render(g); } } /** * @return OMText for attribution text */ protected OMText getAttributionGraphic() { Projection proj = getProjection(); if (attribution != null && proj != null) { OMText attText = new OMText(10, proj.getHeight() - 10, attribution, OMText.JUSTIFY_LEFT); if (attributionAttributes != null) { attributionAttributes.setTo(attText); } attText.generate(proj); return attText; } return null; } public String getToolTipTextFor(OMGraphic omg) { return (String) omg.getAttribute(OMGraphic.TOOLTIP); } public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); prefix = PropUtils.getScopedPropertyPrefix(prefix); attribution = props.getProperty(prefix + DATA_ATTRIBUTION_PROPERTY, attribution); attributionAttributes.setProperties(prefix, props); String tileFactoryClassString = props.getProperty(prefix + TILE_FACTORY_CLASS_PROPERTY); if (tileFactoryClassString != null) { MapTileFactory itf = (MapTileFactory) ComponentFactory.create(tileFactoryClassString, prefix, props); if (itf != null) { setTileFactory(itf); } } else { // Let's see if we can figure out what kind of MapTileFactory is // needed based on rootDir String rootDirString = props.getProperty(prefix + StandardMapTileFactory.ROOT_DIR_PROPERTY); if (rootDirString != null) { try { // We build URL here to test if the rootDir location exists. // Comment out url to avoid dead store findbugs problem. /* URL url = */new java.net.URL(rootDirString); // If we get here, we have a protocol, looks remote, so we // should make sure the // ServerMapTileFactory is used. if (!(getTileFactory() instanceof ServerMapTileFactory)) { setTileFactory(new ServerMapTileFactory(rootDirString)); } } catch (MalformedURLException e) { // no protocol or something, use default // StandardMapTileFactory if (!(getTileFactory() instanceof StandardMapTileFactory)) { setTileFactory(new StandardMapTileFactory()); } } } } if (tileFactory instanceof PropertyConsumer) { ((PropertyConsumer) tileFactory).setProperties(prefix, props); } incrementalUpdates = PropUtils.booleanFromProperties(props, prefix + INCREMENTAL_UPDATES_PROPERTY, incrementalUpdates); setZoomLevel(PropUtils.intFromProperties(props, prefix + ZOOM_LEVEL_PROPERTY, zoomLevel)); } public Properties getProperties(Properties props) { props = super.getProperties(props); String prefix = PropUtils.getScopedPropertyPrefix(this); if (tileFactory != null) { props.put(prefix + TILE_FACTORY_CLASS_PROPERTY, tileFactory.getClass().getName()); if (tileFactory instanceof PropertyConsumer) { ((PropertyConsumer) tileFactory).getProperties(props); } } props.put(prefix + INCREMENTAL_UPDATES_PROPERTY, Boolean.toString(incrementalUpdates)); props.put(prefix + ZOOM_LEVEL_PROPERTY, Integer.toString(zoomLevel)); props.put(prefix + DATA_ATTRIBUTION_PROPERTY, PropUtils.unnull(attribution)); attributionAttributes.getProperties(props); return props; } public Properties getPropertyInfo(Properties props) { props = super.getPropertyInfo(props); PropUtils.setI18NPropertyInfo(i18n, props, this.getClass(), ZOOM_LEVEL_PROPERTY, "Zoom Level", "Force zoom level for queries (-1 is no forcing)", null); PropUtils.setI18NPropertyInfo(i18n, props, this.getClass(), DATA_ATTRIBUTION_PROPERTY, "Attribution", "Attribution for data source", null); if (tileFactory instanceof StandardMapTileFactory) { ((StandardMapTileFactory) tileFactory).getPropertyInfo(props); props.put(initPropertiesProperty, ((StandardMapTileFactory) tileFactory).getInitPropertiesOrder() + " " + ZOOM_LEVEL_PROPERTY + " " + DATA_ATTRIBUTION_PROPERTY); } else { props.put(initPropertiesProperty, StandardMapTileFactory.ROOT_DIR_PROPERTY + " " + StandardMapTileFactory.FILE_EXT_PROPERTY + " " + ZOOM_LEVEL_PROPERTY + " " + DATA_ATTRIBUTION_PROPERTY); } return props; } /** * Called when the layer has been turned off and the projection changes, * signifying that the layer can clean up. */ public void removed(Container cont) { MapTileFactory tileFactory = getTileFactory(); if (tileFactory != null) { tileFactory.reset(); } } public MapTileFactory getTileFactory() { return tileFactory; } public void setTileFactory(MapTileFactory tileFactory) { logger.fine("setting tile factory to: " + tileFactory.getClass().getName()); // This allows for general faster response, but causes the map to jump // around a little bit when used with the BufferedImageRenderPolicy and // when the projection changes occur rapidly, like when zooming and // panning several times in a second. The generation/positioning can't // keep up. It'll settle out, but it might be better to be slower and // less confusing to the user. tileFactory.setMapTileRequester(this); this.tileFactory = tileFactory; doPrepare(); } public boolean isIncrementalUpdates() { return incrementalUpdates; } public void setIncrementalUpdates(boolean incrementalUpdates) { this.incrementalUpdates = incrementalUpdates; } public int getZoomLevel() { return zoomLevel; } public void setZoomLevel(int zoomLevel) { this.zoomLevel = zoomLevel; } public java.awt.Component getGUI() { // Only allow delete cache button if the source of the tiles are from a // server. JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); if (getTileFactory() instanceof ServerMapTileFactory) { JPanel clearCachePanel = new JPanel(new BorderLayout()); clearCachePanel.add(new JPanel(), BorderLayout.WEST); clearCachePanel.add(new JPanel(), BorderLayout.EAST); JButton clearButton = new JButton(i18n.get(MapTileLayer.class, "clearCacheLabel", "Clear Tile Cache")); clearCachePanel.add(clearButton, BorderLayout.CENTER); clearButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { String query = i18n.get(MapTileLayer.class, "mapTileLayerDeleteCacheQuery", "Delete tiles on disk? Click OK to delete..."); int dialogResult = JOptionPane.showConfirmDialog(null, query, "Warning", JOptionPane.OK_CANCEL_OPTION); if (dialogResult == JOptionPane.OK_OPTION) { clearCache(); } } }); panel.add(clearCachePanel); } panel.add(getDefaultSettingsPanel(this.getClass(), getTransparency())); return panel; } /** * @return the attribution */ public String getAttribution() { return attribution; } /** * @param attribution the attribution to set */ public void setAttribution(String attribution) { this.attribution = attribution; } /** * @return the attributionAttributes */ public DrawingAttributes getAttributionAttributes() { return attributionAttributes; } /** * @param attributionAttributes the attributionAttributes to set */ public void setAttributionAttributes(DrawingAttributes attributionAttributes) { this.attributionAttributes = attributionAttributes; } /* * (non-Javadoc) * * @see com.bbn.openmap.dataAccess.mapTile.MapTileRequestor#shouldContinue() */ public boolean shouldContinue() { return !isInterruptable() || !isCancelled(); } /* * (non-Javadoc) * * @see com.bbn.openmap.dataAccess.mapTile.MapTileRequestor#listUpdated() */ public void listUpdated() { if (incrementalUpdates) { repaint(); } } /** * Clear the MapTileFactory cache. */ public void clearCache() { MapTileFactory mtf = getTileFactory(); if (mtf != null) { mtf.reset(); } } }