package com.nutiteq; import java.util.Enumeration; import java.util.Timer; import java.util.Vector; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.midlet.MIDlet; import com.mgmaps.cache.ScreenCache; import com.mgmaps.utils.Tools; import com.nutiteq.cache.Cache; import com.nutiteq.components.ImageBuffer; import com.nutiteq.components.KmlPlace; import com.nutiteq.components.Label; import com.nutiteq.components.Line; import com.nutiteq.components.MapPos; import com.nutiteq.components.MapTile; import com.nutiteq.components.OnMapElement; import com.nutiteq.components.Place; import com.nutiteq.components.PlaceInfo; import com.nutiteq.components.PlaceLabel; import com.nutiteq.components.Point; import com.nutiteq.components.Polygon; import com.nutiteq.components.Rectangle; import com.nutiteq.components.TileMapBounds; import com.nutiteq.components.WgsBoundingBox; import com.nutiteq.components.WgsPoint; import com.nutiteq.components.ZoomRange; import com.nutiteq.controls.ControlKeys; import com.nutiteq.controls.ControlKeysHandler; import com.nutiteq.controls.OnScreenZoomControls; import com.nutiteq.controls.UserDefinedKeysMapping; import com.nutiteq.core.MappingCore; import com.nutiteq.fs.FileSystem; import com.nutiteq.io.ResourceRequestor; import com.nutiteq.kml.KmlService; import com.nutiteq.kml.KmlServicesHandler; import com.nutiteq.license.License; import com.nutiteq.license.LicenseKeyCheck; import com.nutiteq.listeners.ErrorListener; import com.nutiteq.listeners.MapListener; import com.nutiteq.listeners.OnMapElementListener; import com.nutiteq.location.LocationSource; import com.nutiteq.log.Log; import com.nutiteq.maps.GeoMap; import com.nutiteq.maps.MapTileOverlay; import com.nutiteq.maps.MapTilesRequestor; import com.nutiteq.net.DownloadCounter; import com.nutiteq.net.DownloadHandler; import com.nutiteq.net.DownloadStreamOpener; import com.nutiteq.task.MapTileSearchTask; import com.nutiteq.task.Task; import com.nutiteq.task.TasksRunner; import com.nutiteq.task.TileOverlayRetriever; import com.nutiteq.ui.Cursor; import com.nutiteq.ui.DisplayUpdater; import com.nutiteq.ui.DownloadDisplay; import com.nutiteq.ui.ImageProcessor; import com.nutiteq.ui.Pannable; import com.nutiteq.ui.PanningStrategy; import com.nutiteq.ui.RepaintTimerTask; import com.nutiteq.ui.ZoomDelayTimerTask; import com.nutiteq.ui.ZoomIndicator; import com.nutiteq.ui.ZoomIndicatorCheckTask; import com.nutiteq.utils.Utils; /** * <p> * Main class for integration between implementing application and maps library * without any default values. * </p> * <p> * Best practice for map display handling would be using one instance trough * whole application life circle. This means calling <code>startMapping()</code> * and <code>stopMapping()</code> only once. * </p> * <p> * <strong>Required steps for basic functionality initialization:</strong> * <ul> * <li>set used map by calling {@link #setMap(GeoMap)}</li> * <li>for panning/zooming with keys define control keys by setting keys handler * with {@link #setControlKeysHandler(ControlKeysHandler)} or define individual * keys by calling {@link #defineControlKey(int, int)}</li> * <li>set {@link com.nutiteq.ui.PanningStrategy} if panning with keys will be * used</li> * <li>set {@link com.nutiteq.listeners.MapListener} for receiving map view * update notifications</li> * </ul> * </p> */ public class BasicMapComponent extends BaseMapComponent implements MapTilesRequestor, Pannable, DisplayUpdater, DownloadHandler { private static final int REPAINT_CALL_DELAY = 1000; private int displayWidth; private int displayHeight; private int displayCenterX; private int displayCenterY; private int displayX; private int displayY; private MapPos middlePoint; // tile display private int tileX; private int tileY; private int tileW; private int tileH; private static final int[][] MOVES = { { 0, -1 }, { 0, +1 }, { -1, 0 }, { +1, 0 } }; private static final int MOVE_UP = 0; private static final int MOVE_DOWN = 1; private static final int MOVE_LEFT = 2; private static final int MOVE_RIGHT = 3; /** * Pixel tolerance used for stylus phones to distinguish between screen click * and drag events */ public static final int STYLUS_CLICK_TOLERANCE = 5; /** * Pixel tolerance used for phones without stylus to distinguish between * screen click and drag events */ public static final int FINGER_CLICK_TOLERANCE = 10; private ControlKeysHandler controlKeysHandler; private Cursor cursor; private GeoMap displayedMap; private KmlServicesHandler kmlServicesHandler; private com.nutiteq.cache.Cache networkCache; private final TasksRunner taskRunner; private ScreenCache screenCache; private MapListener mapListener; private OnMapElementListener onMapElementListener; private ErrorListener errorListener; private ImageBuffer mapBuffer; private int mapBufferMoveX; private int mapBufferMoveY; private Image screenBuffer; private Graphics screenBufferGraphics; private Image zoomBuffer; private Graphics zoomBufferGraphics; private int zoomBufferX; private int zoomBufferY; private final Vector displayedElements = new Vector(); private OnMapElement centeredElement; private MapPos centeredClickMapPos; private int pointerX = -1; private int pointerY = -1; private int pointerXMove = 0; private int pointerYMove = 0; private boolean dragged; private OnScreenZoomControls onScreenZoomControls; private PanningStrategy panning; private final Vector neededTiles = new Vector(); private final Timer timer; private RepaintTimerTask repaintTask; private ZoomDelayTimerTask zoomDelay; private ZoomIndicator zoomLevelIndicator; //TODO jaanus : maybe can find a better way then tasks private ZoomIndicatorCheckTask indicatorCheck; private long lastRepaintCallTime; private License license = License.LICENSE_CHECKING; private boolean isMapComplete; private long lastZoomCall; private final Vector changedAreas = new Vector(); private LocationSource locationSource; private GeoMap[] tileSearchStrategy; private final LicenseKeyCheck licenseKeyCheck; private boolean paintingScreen; private TileMapBounds tileMapBounds; private boolean mappingStarted; private final WgsPoint startWgs; private final int startZoom; private DownloadCounter downloadCounter; private DownloadDisplay downloadDisplay; private boolean looseFocusOnDrag; private int touchTolerance = STYLUS_CLICK_TOLERANCE; private final Vector overlayQueue = new Vector(); /** * Constructor for map display object. Defines available paint area (width, * height), middle point for map display at start (defined in WGS84), and zoom * level at start. * * @param licenseKey * License key issued by Nutiteq LLC * @param vendor * Vendor name used by library for license check * @param appname * Application name used by library for license check * @param width * map view width * @param height * map view height * @param middlePoint * middle point at start (defined in WGS84) * @param zoom * zoom level at start */ public BasicMapComponent(final String licenseKey, final String vendor, final String appname, final int width, final int height, final WgsPoint middlePoint, final int zoom) { displayWidth = width; displayHeight = height; displayCenterX = width / 2; displayCenterY = height / 2; displayX = 0; displayY = 0; taskRunner = MappingCore.getInstance().getTasksRunner(); timer = new Timer(); startWgs = middlePoint; startZoom = zoom; licenseKeyCheck = new LicenseKeyCheck(this, licenseKey, appname, vendor); } private void createScreenCache() { screenCache = com.mgmaps.cache.ScreenCache.createScreenCache((2 + ((displayWidth - 2) / displayedMap.getTileSize())) * (2 + ((displayHeight - 2) / displayedMap.getTileSize()))); } /** * Enables processing of all map images. Applied to global cache, so works for all images, not just one map service * Set to null to disable it * @param processor specific processor. Try e.g. NightModeImageProcessor() */ public void setImageProcessor(final ImageProcessor processor){ com.mgmaps.cache.ScreenCache.getInstance().setImageProcessor(processor); } /** * Constructor for map display object. Defines available paint area (width, * height), middle point for map display at start (defined in WGS84), and zoom * level at start. * * @param licenseKey * License key issued by Nutiteq LLC * @param midlet * MIDlet class for the application. Used by library for reading * midlet name and vendor from jad for license verification. * @param width * map view width * @param height * map view height * @param middlePoint * middle point at start (defined in WGS84) * @param zoom * zoom level at start */ public BasicMapComponent(final String licenseKey, final MIDlet midlet, final int width, final int height, final WgsPoint middlePoint, final int zoom) { this(licenseKey, midlet.getAppProperty(LicenseKeyCheck.MIDLET_VENDOR_ATTRIBUTE), midlet .getAppProperty(LicenseKeyCheck.MIDLET_NAME_ATTRIBUTE), width, height, middlePoint, zoom); } /** * Initialize needed resources for mapping and start internal threads. This is * a required step for application */ public void startMapping() { if (mappingStarted) { return; } mappingStarted = true; initializeMiddlePoint(); mapBuffer = new ImageBuffer(2, displayWidth, displayHeight); screenBuffer = Image.createImage(displayWidth, displayHeight); screenBufferGraphics = screenBuffer.getGraphics(); zoomBuffer = Image.createImage(displayWidth, displayHeight); zoomBufferGraphics = zoomBuffer.getGraphics(); tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); if (networkCache != null) { networkCache.initialize(); taskRunner.setNetworkCache(networkCache); } if (downloadCounter != null) { taskRunner.setDownloadCounter(downloadCounter); } taskRunner.startWorker(); taskRunner.setLicenceKeyCheck(licenseKeyCheck); recalculateMapPosition(displayedElements); computeTilesToDisplay(); enqueueTiles(); //maybe map was not set before. initialize now. just in case :) setZoomLevelIndicator(zoomLevelIndicator); mapMoved(); // to force KML reading, if KML was added before startMapping() } protected void initializeMiddlePoint() { if (middlePoint == null) { middlePoint = displayedMap.wgsToMapPos(startWgs.toInternalWgs(), checkValidZoom(startZoom, displayedMap)); } } private void initializeKmlServiceshandler() { kmlServicesHandler = new KmlServicesHandler(this, taskRunner); } /** * Changes map view size * * @param width * new map view width * @param height * new map view height */ public void resize(final int width, final int height) { displayWidth = width; displayHeight = height; displayCenterX = width / 2; displayCenterY = height / 2; fullScreenUpdate(); createScreenCache(); // if mapping started if (mapBuffer != null) { mapBuffer.resize(width, height); zoomBuffer = Utils.resizeImageAndCopyPrevious(width, height, zoomBuffer); zoomBufferGraphics = zoomBuffer.getGraphics(); screenBuffer = Utils.resizeImageAndCopyPrevious(width, height, screenBuffer); screenBufferGraphics = screenBuffer.getGraphics(); computeTilesToDisplay(); enqueueTiles(); } } public void fullScreenUpdate() { changedAreas.setSize(0); changedAreas.addElement(getScreenAreaOnMap()); } private Rectangle getScreenAreaOnMap() { return new Rectangle(middlePoint.getX() - displayCenterX, middlePoint.getY() - displayCenterY, displayWidth, displayHeight); } /** * Return current view width * * @return view width */ public int getWidth() { return displayWidth; } /** * Return current view height * * @return view height */ public int getHeight() { return displayHeight; } /** * Define middle point location in WGS84 * * @param lon * degrees in wgs * @param lat * degrees in wgs * @param zoom * zoom level to be displayed */ public void setMiddlePoint(final double lon, final double lat, final int zoom) { setMiddlePoint(new WgsPoint(lon, lat), zoom); } /** * Define middle point location in WGS84 * * @param point * coordinates in WGS84 * @param zoom * zoom level to be displayed */ public void setMiddlePoint(final WgsPoint point, final int zoom) { if (point == null) { return; } final int newZoom = checkValidZoom(zoom, displayedMap); middlePoint = displayedMap.wgsToMapPos(point.toInternalWgs(), newZoom); tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); recalculateMapPosition(displayedElements); fullScreenUpdate(); computeTilesToDisplay(); enqueueTiles(); mapMoved(); repaint(); } /** * Define middle point in WGS84, without changing zoom level * * @param wgs * coordinates in WGS84 */ public void setMiddlePoint(final WgsPoint wgs) { moveMap(wgs); } private int checkValidZoom(final int zoom, final GeoMap map) { if (zoom >= map.getMinZoom() && zoom <= map.getMaxZoom()) { return zoom; } if (zoom > map.getMaxZoom()) { return map.getMaxZoom(); } return map.getMinZoom(); } /** * Paint the map component. As default view will be painted to 0, 0 on * graphics object. This can be changed with * {@link #setScreenPosition(int, int)} * * @param g * graphics object provided by implementing application */ public void paint(final Graphics g) { paintAt(g, displayX, displayY); } /** * Paint map view to give position on screen * * @param g * graphics object to paint on * @param paintX * screen position x * @param paintY * screen position y */ public void paintAt(final Graphics g, final int paintX, final int paintY) { if (g == null || screenBuffer == null || mapBuffer == null) { return; } if (!license.isValid()) { g.setClip(paintX, paintY, displayWidth, displayHeight); g.setColor(0xFFFFFFFF); g.fillRect(paintX, paintY, displayWidth, displayHeight); final Font font = Font.getDefaultFont(); final int textX = (displayWidth - font.stringWidth(license.getMessage())) / 2; final int textY = (displayHeight - font.getHeight()) / 2; g.setColor(0xFFFF0000); g.drawString(license.getMessage(), textX > 0 ? textX : 0, textY > 0 ? textY : 0, Graphics.TOP | Graphics.LEFT); return; } paintScreen(screenBufferGraphics); g.setClip(paintX, paintY, displayWidth, displayHeight); g.drawImage(screenBuffer, paintX, paintY, Graphics.TOP | Graphics.LEFT); } private void paintScreen(final Graphics g) { paintingScreen = true; g.setClip(0, 0, displayWidth, displayHeight); final Rectangle changed = paintMap(mapBuffer); OnMapElement nextCentered; if (cursor != null) { nextCentered = centeredPlaceCheck(displayedElements); handleActiveIconChange(centeredElement, nextCentered); } else { nextCentered = centeredElement; } paintPlaces(mapBuffer, displayedElements, changed, nextCentered); g.drawImage(mapBuffer.getFrontImage(), 0, 0, Graphics.TOP | Graphics.LEFT); //paint map copyright displayedMap.getCopyright().paint(g, displayWidth, displayHeight); //TODO jaanus : this should be removed after places go to map buffer g.setClip(0, 0, displayWidth, displayHeight); if (locationSource != null) { locationSource.getLocationMarker().paint(g, middlePoint, displayCenterX, displayCenterY); } if (cursor != null) { cursor.paint(g, displayWidth / 2, displayHeight / 2, displayWidth, displayHeight); } if (onScreenZoomControls != null) { onScreenZoomControls.paint(g, displayWidth, displayHeight); } if (zoomLevelIndicator != null && zoomLevelIndicator.isVisible()) { zoomLevelIndicator.paint(g, middlePoint.getZoom(), displayWidth, displayHeight); } if (downloadDisplay != null && downloadDisplay.isVisible()) { downloadDisplay.paint(g, displayWidth, displayHeight); } paintTitle(g, centeredElement); paintingScreen = false; } private void paintTitle(final Graphics g, final OnMapElement centered) { if (centered == null) { return; } final Label label = centered.getLabel(); if (label == null) { return; } g.setClip(0, 0, displayWidth, displayHeight); final int placeScreenX; final int placeScreenY; //place with point //place with poly/line //poly/line if (centered instanceof Place && ((Place) centered).getMapPosition() != null) { final Place cPlace = (Place) centered; placeScreenX = cPlace.getMapPosition().getX() - middlePoint.getX() + displayWidth / 2; placeScreenY = cPlace.getMapPosition().getY() - middlePoint.getY() + displayHeight / 2; } else if (cursor != null) { //on line/polygon put the label where cursor is final Point clickPoint = cursor.getPointOnDisplay(displayWidth, displayHeight); placeScreenX = clickPoint.getX(); placeScreenY = clickPoint.getY(); } else { placeScreenX = centeredClickMapPos.getX() - middlePoint.getX() + displayCenterX; placeScreenY = centeredClickMapPos.getY() - middlePoint.getY() + displayCenterY; } //TODO jaanus : resolve this hack if (label instanceof PlaceLabel) { ((PlaceLabel) label).setZoom(middlePoint.getZoom()); } label.paint(g, placeScreenX, placeScreenY, displayWidth, displayHeight); } private OnMapElement centeredPlaceCheck(final Vector places) { if (cursor == null || !mappingStarted || middlePoint == null) { return null; } OnMapElement centered = null; for (int i = 0; i < places.size(); i++) { final OnMapElement current = (OnMapElement) places.elementAt(i); final Point cursorOnScreen = cursor.getPointOnDisplay(displayWidth, displayHeight); final MapPos cursorOnMap = new MapPos(middlePoint.getX() - displayCenterX + cursorOnScreen.getX(), middlePoint.getY() - displayCenterY + cursorOnScreen.getY(), middlePoint.getZoom()); final boolean isCentered = current.isCentered(cursorOnMap); if (isCentered) { centered = current; } } return centered; } private void paintPlaces(final ImageBuffer buffer, final Vector places, final Rectangle changedArea, final OnMapElement centered) { if (!mappingStarted || middlePoint == null) { return; } final Graphics g = buffer.getFrontGraphics(); final Rectangle screenChange = Utils.areaToScreen(changedArea, middlePoint.getX() - displayCenterX, middlePoint.getY() - displayCenterY, displayWidth, displayHeight); for (int i = 0; i < places.size(); i++) { final OnMapElement current = (OnMapElement) places.elementAt(i); //is element is in changed area if (current.isVisible(changedArea.getX(), changedArea.getY(), changedArea.getWidth(), changedArea.getHeight(), middlePoint.getZoom())) { g.setClip(screenChange.getX(), screenChange.getY(), screenChange.getWidth(), screenChange .getHeight()); current.paint(g, middlePoint, displayCenterX, displayCenterY, changedArea); } } //TODO jaanus : take this one level up //paint centered on top of other elements if (centered != null && (centered instanceof Place)) { g.setClip(0, 0, displayWidth, displayHeight); centered.paint(g, middlePoint, displayCenterX, displayCenterY, changedArea); } if (cursor == null) { //without cursor nothing can be done here return; } if (centeredElement != null && centeredElement != centered) { if (onMapElementListener != null) { onMapElementListener.elementLeft(centeredElement); } } if (centeredElement != centered && centered != null) { if (onMapElementListener != null) { onMapElementListener.elementEntered(centered); } } centeredElement = centered; } //***** OPTIMIZATION data ****** // | Test runs | x | y | time // 2008.08.25 15:20 WTK | 10000 | 1 | 0 | 8071 // 2008.08.25 15:20 WTK | 10000 | 3 | 0 | 9949 // 2008.08.25 15:20 6500c | 10000 | 3 | 0 | 61851 // 2008.08.25 15:20 W960 | 10000 | 3 | 0 | 27763 (?) // 2008.08.25 15:20 N95 | 10000 | 3 | 0 | 20365 (?) // Map paint change // 2008.08.27 10:50 WTK | 10000 | 1 | 0 | 18071 // Three polygons added // 2008.08.27 11:00 WTK | 10000 | 1 | 0 | 23932 protected Rectangle paintMap(final ImageBuffer buffer) { final Graphics g = buffer.getBackGraphics(); g.setClip(0, 0, displayWidth, displayHeight); //view was moved since last paint if (mapBufferMoveX != 0) { changedAreas.addElement(new Rectangle(middlePoint.getX() + (mapBufferMoveX < 0 ? displayCenterX + mapBufferMoveX : -displayCenterX), middlePoint .getY() - displayCenterY, Math.abs(mapBufferMoveX) + 1, displayHeight)); } if (mapBufferMoveY != 0) { changedAreas.addElement(new Rectangle(middlePoint.getX() - displayCenterX, middlePoint.getY() + (mapBufferMoveY < 0 ? displayCenterY + mapBufferMoveY : -displayCenterY), displayWidth, Math.abs(mapBufferMoveY) + 1)); } //copy previous map image g.drawImage(buffer.getFrontImage(), mapBufferMoveX, mapBufferMoveY, Graphics.TOP | Graphics.LEFT); mapBufferMoveX = 0; mapBufferMoveY = 0; Rectangle[] changed = new Rectangle[changedAreas.size()]; try { //TODO jaanus : check this. for some reason thrown some times on android emulator changedAreas.copyInto(changed); } catch (final IndexOutOfBoundsException e) { Log.error("Copy again: " + e.getMessage()); changed = new Rectangle[0]; } changedAreas.setSize(0); final Rectangle change = calculateChangedArea(changed); final int changeScreenX = change.getX() - middlePoint.getX() + displayCenterX; final int changeScreenY = change.getY() - middlePoint.getY() + displayCenterY; g.setClip(changeScreenX, changeScreenY, change.getWidth(), change.getHeight()); g.setColor(0xFFFFFFFF); g.fillRect(changeScreenX, changeScreenY, change.getWidth(), change.getHeight()); final int tileSize = displayedMap.getTileSize(); // paint zoom buffer first if (Utils.rectanglesIntersect(zoomBufferX, zoomBufferY, displayWidth, displayHeight, change .getX(), change.getY(), change.getWidth(), change.getHeight())) { // TODO jaanus : when all tiles are present, no need for zoom buffer // paint g.drawImage(zoomBuffer, -(middlePoint.getX() - displayCenterX - zoomBufferX), -(middlePoint .getY() - displayCenterY - zoomBufferY), Graphics.TOP | Graphics.LEFT); } boolean complete = true; // print in order for (int i = 0; i < tileW; i++) { for (int j = 0; j < tileH; j++) { complete = paintTile(g, new MapTile(tileX + i * tileSize, tileY + j * tileSize, middlePoint .getZoom(), displayedMap, this), middlePoint, change) && complete; } } buffer.flip(); isMapComplete = complete; return change; } protected Rectangle calculateChangedArea(final Rectangle[] changed) { Rectangle result = new Rectangle(0, 0, 0, 0); for (int i = 0; i < changed.length; i++) { final Rectangle area = changed[i]; if (i == 0) { result = area; continue; } if (area.getWidth() == 0 && area.getHeight() == 0) { continue; } //add the change from this area to previous calculation result = Utils.mergeAreas(result, area); } return result; } /** * Paint a map tile. * * @param g * graphics object * @param mt * map tile to paint * @param centerCopy * copy of the current map center * @param change */ private boolean paintTile(final Graphics g, final MapTile mt, final MapPos centerCopy, final Rectangle change) { // search in screen cache int pos = screenCache.find(mt); if (pos < 0) { if (networkCache != null && networkCache.contains(mt.getIDString(), Cache.CACHE_LEVEL_MEMORY)) { final byte[] data = networkCache.get(mt.getIDString()); mt.setImagesData(new byte[][] { data }); pos = screenCache.add(mt, middlePoint, displayedMap, displayCenterX, displayCenterY, false); } } // if found in raw tiles, paint it if (pos >= 0 && Utils.rectanglesIntersect(mt.getX(), mt.getY(), displayedMap.getTileSize(), displayedMap .getTileSize(), change.getX(), change.getY(), change.getWidth(), change.getHeight())) { screenCache.paint(g, pos, centerCopy, displayCenterX, displayCenterY); } return pos >= 0; } /** * Get map center point value in lon/lat * * @return middle point on screen in WGS84 */ public WgsPoint getMiddlePoint() { if (displayedMap == null || middlePoint == null) { return null; } return displayedMap.mapPosToWgs(middlePoint).toWgsPoint(); } /** * Zoom in one level */ public void zoomIn() { if (middlePoint.getZoom() == displayedMap.getMaxZoom()) { return; } cleanMapBuffer(); middlePoint = displayedMap.zoom(middlePoint, 1); tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); createZoomBufferAndUpdateScreen(-1, true); } /** * Zoom out one level */ public void zoomOut() { if (middlePoint.getZoom() == displayedMap.getMinZoom()) { return; } cleanMapBuffer(); middlePoint = displayedMap.zoom(middlePoint, -1); tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); createZoomBufferAndUpdateScreen(1, true); } private void createZoomBufferAndUpdateScreen(final int scaleDown, final boolean needZoomDelay) { // kind of a hack for pointer events. if map is dragged and pointer is // released outside painted area (when map is not full screen) the pointer // location values are not reset. pointerX = -1; pointerY = -1; fullScreenUpdate(); final int absScaleDown = (scaleDown > 0) ? scaleDown : -scaleDown; final Image frontImage = mapBuffer.getFrontImage(); final int scaledWidth = (scaleDown > 0) ? (frontImage.getWidth() >> absScaleDown) : frontImage .getWidth(); final int scaledHeight = (scaleDown > 0) ? (frontImage.getHeight() >> absScaleDown) : frontImage.getHeight(); final Image scaled = (scaleDown > 0) ? Tools.scaleImage05(frontImage, absScaleDown) : Tools .scaleImage20(frontImage, absScaleDown); zoomBufferX = middlePoint.getX() - displayCenterX; zoomBufferY = middlePoint.getY() - displayCenterY; zoomBufferGraphics.setClip(0, 0, zoomBuffer.getWidth(), zoomBuffer.getHeight()); zoomBufferGraphics.setColor(0xFFFFFFFF); zoomBufferGraphics.fillRect(0, 0, zoomBuffer.getWidth(), zoomBuffer.getHeight()); if (scaledWidth > 0 && scaledHeight > 0) { zoomBufferGraphics.drawImage(scaled, (zoomBuffer.getWidth() - scaledWidth) / 2, (zoomBuffer .getHeight() - scaledHeight) / 2, Graphics.TOP | Graphics.LEFT); } computeTilesToDisplay(); if (zoomDelay == null && needZoomDelay) { zoomDelay = new ZoomDelayTimerTask(this, new MapTileSearchTask(this, tileSearchStrategy, taskRunner)); timer.schedule(zoomDelay, ZoomDelayTimerTask.ZOOM_DELAY_TIME); } enqueueTiles(); recalculateMapPosition(displayedElements); isMapComplete = onScreenTilesPresent(); if (needZoomDelay && zoomLevelIndicator != null && indicatorCheck == null) { lastZoomCall = System.currentTimeMillis(); zoomLevelIndicator.setVisible(true); timer.schedule(new ZoomIndicatorCheckTask(this), Math.max(zoomLevelIndicator.displayTime(), 100)); } if (locationSource != null) { locationSource.getLocationMarker().updatePosition(); } repaint(); mapMoved(); } private void cleanMapBuffer() { fullScreenUpdate(); paintMap(mapBuffer); } private void mapMoved() { if (mapListener != null) { mapListener.mapMoved(); } if (kmlServicesHandler != null && displayedMap != null && middlePoint != null) { kmlServicesHandler.mapMoved(getBoundingBox(), middlePoint.getZoom()); } } /** * Set map listener for receiving map related callback events from library * * @param mL * class implementing MapListener interface */ public void setMapListener(final MapListener mL) { mapListener = mL; } /** * Set listener for receiving events related to objects shown on map. * * @param listener */ public void setOnMapElementListener(final OnMapElementListener listener) { onMapElementListener = listener; } /** * Set listener for library errors * * @param errorListener * class implementing ErrorListener interface */ public void setErrorListener(final ErrorListener errorListener) { this.errorListener = errorListener; taskRunner.setErrorListener(errorListener); } /** * Set zoom controls to be displayed on screen and used for touch screen * zooming. * * @param zoomControls * zoom controls to be used */ public void setOnScreenZoomControls(final OnScreenZoomControls zoomControls) { onScreenZoomControls = zoomControls; } /* * Move view on map up */ private void moveUp(final boolean fromKeys) { moveMap(MOVE_UP, fromKeys); } /* * Move view on map down */ private void moveDown(final boolean fromKeys) { moveMap(MOVE_DOWN, fromKeys); } /* * Move view on map left */ private void moveLeft(final boolean fromKeys) { moveMap(MOVE_LEFT, fromKeys); } /* * Move view on map right */ private void moveRight(final boolean fromKeys) { moveMap(MOVE_RIGHT, fromKeys); } private void moveMap(final int moveDirection, final boolean fromKeys) { panning.startPanning(MOVES[moveDirection][0], MOVES[moveDirection][1], fromKeys); } /** * Move view on map by number of pixels * * @param panX * number of pixels to be moved left/right * @param panY * number of pixels to be moved up/down */ public void panMap(final int panX, final int panY) { if (paintingScreen) { return; } mapBufferMoveX -= panX; mapBufferMoveY -= panY; middlePoint.setX(middlePoint.getX() + panX); middlePoint.setY(middlePoint.getY() + panY); computeTilesToDisplay(); enqueueTiles(); repaint(); } /** * Handle key pressed event * * @param keyCode * key code forwarded by implementing application */ public void keyPressed(final int keyCode) { if (controlKeysHandler == null) { return; } final int actionCode = controlKeysHandler.getKeyActionCode(keyCode); if (actionCode == ControlKeys.MOVE_UP_KEY) { moveUp(true); } else if (actionCode == ControlKeys.MOVE_DOWN_KEY) { moveDown(true); } else if (actionCode == ControlKeys.MOVE_LEFT_KEY) { moveLeft(true); } else if (actionCode == ControlKeys.MOVE_RIGHT_KEY) { moveRight(true); } else if (actionCode == ControlKeys.ZOOM_IN_KEY) { zoomIn(); } else if (actionCode == ControlKeys.ZOOM_OUT_KEY) { zoomOut(); } else if (actionCode == ControlKeys.SELECT_KEY) { if (centeredElement != null) { if (onMapElementListener != null) { onMapElementListener.elementClicked(centeredElement); } } else if (cursor != null && mapListener != null) { final Point cursorOnDisplay = cursor.getPointOnDisplay(displayWidth, displayHeight); final MapPos cursorOnMap = new MapPos(middlePoint.getX() - displayCenterX + cursorOnDisplay.getX(), middlePoint.getY() - displayCenterY + cursorOnDisplay.getY(), middlePoint.getZoom()); mapListener.mapClicked(displayedMap.mapPosToWgs(cursorOnMap).toWgsPoint()); } } } /** * Handle key released event * * @param keyCode * key code forwarded by implementing application */ public void keyReleased(final int keyCode) { if (panning.isPanning()) { panning.stopPanning(); mapMoved(); } } /** * Handle key repeated event * * @param keyCode * key code forwarded by implementing application */ public void keyRepeated(final int keyCode) { panning.keyRepeated(keyCode); } /** * Handle pointer dragged event * * @param x * pixels dragged * @param y * pixels dragged */ public void pointerDragged(final int x, final int y) { final int componentX = x - displayX; final int componentY = y - displayY; pointerXMove += Math.abs(pointerX - x - displayX); pointerYMove += Math.abs(pointerY - y - displayY); if (centeredElement != null && centeredElement instanceof Place && ((Place) centeredElement).pointOnLabel(middlePoint, displayWidth, displayHeight, componentX, componentY)) { return; } if (pointerXMove + pointerYMove > touchTolerance && looseFocusOnDrag) { handleActiveIconChange(centeredElement, null); centeredElement = null; } // don't pan, when pointer is dragged on control button if (pointerXMove + pointerYMove >= touchTolerance && (onScreenZoomControls == null || (onScreenZoomControls != null && onScreenZoomControls .getControlAction(componentX, componentY) == -1))) { panMap(pointerX - componentX, pointerY - componentY); // Log.debug("panned in dragging"); } dragged = true; pointerX = componentX; pointerY = componentY; } /** * Handle pointer pressed event * * @param x * position on screen * @param y * position on screen */ public void pointerPressed(final int x, final int y) { pointerXMove = 0; pointerYMove = 0; final int componentX = x - displayX; final int componentY = y - displayY; int controlAction = -1; if (onScreenZoomControls != null && (controlAction = onScreenZoomControls.getControlAction(componentX, componentY)) != -1) { switch (controlAction) { } } pointerX = componentX; pointerY = componentY; } /** * Handle pointer released event * * @param x * position on screen * @param y * position on screen */ public void pointerReleased(final int x, final int y) { final int componentX = x - displayX; final int componentY = y - displayY; final int controlAction = getPossibleControlAction(componentX, componentY); if (controlAction != -1) { switch (controlAction) { case OnScreenZoomControls.CONTROL_ZOOM_IN: zoomIn(); break; case OnScreenZoomControls.CONTROL_ZOOM_OUT: zoomOut(); break; } } else if (dragged && (pointerXMove >= touchTolerance || pointerYMove >= touchTolerance)) { dragged = false; mapMoved(); // Log.debug("moved in released"); } else { dragged = false; handlePlaceOrMapClick(componentX, componentY, displayedElements); // Log.debug("click handled"); } pointerX = -1; pointerY = -1; } private int getPossibleControlAction(final int componentX, final int componentY) { if (onScreenZoomControls == null) { return -1; } if (centeredElement != null && centeredElement instanceof Place && ((Place) centeredElement).pointOnLabel(middlePoint, displayWidth, displayHeight, componentX, componentY)) { return -1; } return onScreenZoomControls.getControlAction(componentX, componentY); } private void handlePlaceOrMapClick(final int screenX, final int screenY, final Vector onMapElements) { final MapPos clickOnMap = new MapPos(middlePoint.getX() - displayCenterX + screenX, middlePoint .getY() - displayCenterY + screenY, middlePoint.getZoom()); if (centeredLabelClick(centeredElement, screenX, screenY)) { final boolean centeredIsPlace = centeredElement instanceof Place; if (onMapElementListener != null) { onMapElementListener.elementClicked(centeredElement); } if (centeredIsPlace) { ((Place) centeredElement).labelClicked(middlePoint, displayWidth, displayHeight, screenX, screenY); } repaint(); return; } OnMapElement centered = null; int distanceFromCenter = Integer.MAX_VALUE; for (int i = 0; i < onMapElements.size(); i++) { final OnMapElement current = (OnMapElement) onMapElements.elementAt(i); if (current.isCentered(clickOnMap) //&& current.distanceInPixels(clickOnMap) < distanceFromCenter ) { centered = current; distanceFromCenter = current.distanceInPixels(clickOnMap); } } final OnMapElement previousCentered = centeredElement; centeredElement = centered; centeredClickMapPos = clickOnMap; handleActiveIconChange(previousCentered, centered); if (onMapElementListener != null) { handlePossibleCenteredChangeNotification(previousCentered, centered); } if (centeredElement instanceof Place) { final Point viewUpdate = centeredElement == null ? null : ((Place) centeredElement) .getLabelViewUpdate(middlePoint, displayWidth, displayHeight); if (viewUpdate != null) { panMap(viewUpdate.getX(), viewUpdate.getY()); } } if (centered == null && mapListener != null) { mapListener.mapClicked(displayedMap.mapPosToWgs(clickOnMap).toWgsPoint()); } repaint(); } private void handleActiveIconChange(final OnMapElement previousCentered, final OnMapElement centered) { if (previousCentered != null && previousCentered instanceof Place) { final Place previous = (Place) previousCentered; changedAreas.addElement(previous.toMapArea(middlePoint.getZoom())); previous.setIsActive(false); } if (centered != null && centered instanceof Place) { final Place centeredPlace = (Place) centered; changedAreas.addElement(centeredPlace.toMapArea(middlePoint.getZoom())); centeredPlace.setIsActive(true); } } private boolean centeredLabelClick(final OnMapElement centeredPlace, final int clickX, final int clickY) { if (centeredPlace == null || !(centeredPlace instanceof Place)) { return false; } return ((Place) centeredPlace).pointOnLabel(middlePoint, displayWidth, displayHeight, clickX, clickY); } private void handlePossibleCenteredChangeNotification(final OnMapElement previousCentered, final OnMapElement centered) { if (previousCentered != null && previousCentered == centered) { if (onMapElementListener != null) { onMapElementListener.elementClicked(previousCentered); } } else if (previousCentered != null && previousCentered != centered) { if (onMapElementListener != null) { onMapElementListener.elementLeft(previousCentered); } } else if (previousCentered != centered && centered != null) { if (onMapElementListener != null) { onMapElementListener.elementEntered(centered); } } } /** * Set key code values for defined control keys. Using this method will set * the control keys handler to default implementation * {@link com.nutiteq.controls.UserDefinedKeysMapping} * * @param actionCode * internal code for action {@link com.nutiteq.controls.ControlKeys * ControlKeys} * @param keyCode * key code value for defined key */ public void defineControlKey(final int actionCode, final int keyCode) { //TODO jaanus : deprecate it and use setKeysHandler instead if (controlKeysHandler == null || !(controlKeysHandler instanceof UserDefinedKeysMapping)) { controlKeysHandler = new UserDefinedKeysMapping(); // ((UserDefinedKeysMapping) controlKeysHandler).defineKey(ControlKeys.ZOOM_OUT_KEY, // Canvas.KEY_STAR); // ((UserDefinedKeysMapping) controlKeysHandler).defineKey(ControlKeys.ZOOM_IN_KEY, // Canvas.KEY_POUND); } ((UserDefinedKeysMapping) controlKeysHandler).defineKey(actionCode, keyCode); } /** * Change control keys handler used for actions mapping. * * @param keysHandler * new keys mapping handler */ public void setControlKeysHandler(final ControlKeysHandler keysHandler) { controlKeysHandler = keysHandler; } private void computeTilesToDisplay() { if (middlePoint == null) { return; } final int tileSize = displayedMap.getTileSize(); // get top-left corner final int x = middlePoint.getX() - displayCenterX; final int y = middlePoint.getY() - displayCenterY; // compute -(x%SIZE) and -(y%SIZE) final int xx = (x >= 0) ? (x % tileSize) : (tileSize - (-x) % tileSize); final int yy = (y >= 0) ? (y % tileSize) : (tileSize - (-y) % tileSize); final int maxi = ((displayWidth + xx) / tileSize) + 1; final int maxj = ((displayHeight + yy) / tileSize) + 1; // corner of the top-left tile tileX = x; tileY = y; // number of tiles tileW = maxi; tileH = maxj; } /** * Enqueue map tiles to download, in "radial" order. */ private void enqueueTiles() { final int tileSize = displayedMap.getTileSize(); for (int k = 0; k <= (tileW >> 1) + (tileH >> 1); k++) { for (int i = Math.max(0, k - (tileH >> 1)); i <= (tileW >> 1); i++) { final int j = k - i; if (j < 0 || j > (tileH >> 1)) { continue; } final int i1 = (tileW >> 1) - i - 1; final int j1 = (tileH >> 1) - j - 1; final int i2 = (tileW >> 1) + i; final int j2 = (tileH >> 1) + j; if (i2 < tileW && j2 < tileH) { enqueueTile(new MapTile(tileX + i2 * tileSize, tileY + j2 * tileSize, middlePoint .getZoom(), displayedMap, this)); } if (i2 < tileW && j1 >= 0) { enqueueTile(new MapTile(tileX + i2 * tileSize, tileY + j1 * tileSize, middlePoint .getZoom(), displayedMap, this)); } if (i1 >= 0 && j2 < tileH) { enqueueTile(new MapTile(tileX + i1 * tileSize, tileY + j2 * tileSize, middlePoint .getZoom(), displayedMap, this)); } if (i1 >= 0 && j1 >= 0) { enqueueTile(new MapTile(tileX + i1 * tileSize, tileY + j1 * tileSize, middlePoint .getZoom(), displayedMap, this)); } } }// for k if (neededTiles.size() > 0 && zoomDelay == null) { taskRunner.enqueue(new MapTileSearchTask(this, tileSearchStrategy, taskRunner)); } } /** * Enqueue a tile. * * @param mt * map tile */ private void enqueueTile(final MapTile mt) { if (!tileMapBounds.isWithinBounds(mt.getX(), mt.getY())) { return; } // enqueue map tiles that are not already downloaded //TODO jaanus : check this synchronization synchronized (screenCache) { // enqueue only if not in offline mode and not already cached // search in screen cache first if ((screenCache.find(mt) > 0) || neededTiles.contains(mt)) { return; } if (networkCache != null && networkCache.contains(mt.getIDString(), Cache.CACHE_LEVEL_MEMORY)) { return; } neededTiles.addElement(mt); } } /** * Not part of public API */ public MapTile getRequiredTile() { return firstVisibleTile(neededTiles); } /** * Not part of public API */ public boolean requiresMoreTiles() { return neededTiles.size() > 0; } /** * Not part of public API */ public MapTile[] getAllRequiredTiles() { final Vector willBeDownloaded = new Vector(); for (int i = 0; i < neededTiles.size(); i++) { final MapTile tile = (MapTile) neededTiles.elementAt(i); if (tile.isVisible(middlePoint, displayedMap, displayCenterX, displayCenterY)) { willBeDownloaded.addElement(tile); } } neededTiles.setSize(0); if (willBeDownloaded.size() == 0) { return new MapTile[0]; } final MapTile[] downloaded = new MapTile[willBeDownloaded.size()]; willBeDownloaded.copyInto(downloaded); return downloaded; } private MapTile firstVisibleTile(final Vector tiles) { while (tiles.size() > 0) { final MapTile tile = (MapTile) tiles.elementAt(0); tiles.removeElement(tile); if (tile.isVisible(middlePoint, displayedMap, displayCenterX, displayCenterY) && screenCache.find(tile) < 0) { return tile; } } return null; } /** * Not in public API. Will be obfuscated. * * Notify when a tile was downloaded. * * @param mt * the tile downloaded */ public void tileRetrieved(final MapTile mt) { tileRetrieved(mt, false); } private void tileRetrieved(final MapTile mt, final boolean update) { if (mt.isVisible(middlePoint, displayedMap, displayCenterX, displayCenterY) && mt.tryAgain()) { //if image is missing (some download error), try again enqueueTile(mt); if (neededTiles.size() > 0) { taskRunner.enqueue(new MapTileSearchTask(this, tileSearchStrategy, taskRunner)); } return; } //TODO jaanus : check this synchronization synchronized (screenCache) { // add it to the screen cache if it is visible if (mt.isVisible(middlePoint, displayedMap, displayCenterX, displayCenterY)) { screenCache.add(mt, middlePoint, displayedMap, displayCenterX, displayCenterY, update); changedAreas.addElement(new Rectangle(mt.getX(), mt.getY(), displayedMap.getTileSize(), displayedMap.getTileSize())); isMapComplete = onScreenTilesPresent(); if (System.currentTimeMillis() - lastRepaintCallTime >= REPAINT_CALL_DELAY) { repaint(); } else if (repaintTask == null) { // if last repaint was called less then a second ago and no timer has // been started calculate the time from last repaint and schedule // timer based on that final long delay = REPAINT_CALL_DELAY - (System.currentTimeMillis() - lastRepaintCallTime); repaintTask = new RepaintTimerTask(this); timer.schedule(repaintTask, delay > 0 ? delay : 1); } } // Add overlays, if present final MapTileOverlay overlay = displayedMap.getTileOverlay(); if (overlay != null && !update) { overlayQueue.addElement(mt); } synchronized (overlayQueue) { if (isMapComplete && overlay != null && overlayQueue != null && !overlayQueue.isEmpty()) { final Enumeration e = overlayQueue.elements(); while (e.hasMoreElements()) { final MapTile overlayMt = (MapTile) e.nextElement(); enqueueDownload(new TileOverlayRetriever(overlayMt, overlay), Cache.CACHE_LEVEL_MEMORY); } overlayQueue.removeAllElements(); } } } } /** * Not part of public API */ public void updateTile(final MapTile mapTile) { tileRetrieved(mapTile, true); } private boolean onScreenTilesPresent() { boolean allPresent = true; final int tileSize = displayedMap.getTileSize(); final int zoom = middlePoint.getZoom(); for (int i = 0; i < tileW; i++) { for (int j = 0; j < tileH; j++) { final MapTile t = new MapTile(tileX + i * tileSize, tileY + j * tileSize, zoom, displayedMap, this); if (screenCache.find(t) >= 0) { continue; } allPresent = false; break; } } return allPresent; } public void repaint() { lastRepaintCallTime = System.currentTimeMillis(); if (mapListener != null) { mapListener.needRepaint(isMapComplete); } } /** * Remove a place from previously added places * * @param place * place to be removed */ public void removePlace(final Place place) { if (place == null) { return; } removePlaces(new Place[] { place }); } /** * Remove a line from previously added lines * * @param line * line to be removed */ public void removeLine(final Line line) { if (line == null) { return; } removeLines(new Line[] { line }); } /** * Remove lines from previously added lines * * @param lines * lines to be removed */ public void removeLines(final Line[] lines) { removeOnMapElements(lines); } /** * Remove given elements from map display * * @param elements * elements to be removed */ public void removeOnMapElements(final OnMapElement[] elements) { if (elements == null) { return; } boolean removed = false; for (int i = 0; i < elements.length; i++) { if (elements[i] == centeredElement) { centeredElement = null; } removed = displayedElements.removeElement(elements[i]) || removed; } fullScreenUpdate(); if (removed) { repaint(); } } private void removeAllKmlElements() { final boolean removed = !displayedElements.isEmpty(); displayedElements.removeAllElements(); fullScreenUpdate(); if (removed) { repaint(); } } /** * Remove places from previously added places * * @param places * places to be removed */ public void removePlaces(final Place[] places) { removeOnMapElements(places); } /** * Remove all places. */ public void removeAllPlaces() { //TODO jaanus : what should be done with internal places? at the moment the //display side is wiped, but data will remain in handler removeAllKmlElements(); } /** * Add new places to the map and remove all other places previously on the * map. * * @param places * places to be displayed on map */ public void replacePlaces(final Place[] places) { // we're using Vector for the places, we should just removeAll then add // because it's less expensive than calling contains() each time // avoid additional repaints //TODO jaanus : separate internal places handling? it is a mess at the moment displayedElements.removeAllElements(); addOnMapElements(places, true); } /** * Add a place to be displayed on map * * @param place * place to be displayed on map (if visible) */ public void addPlace(final Place place) { addOnMapElement(place); } /** * Add a place to be displayed on map first (Z-order) * * @param place * place to be displayed on map (if visible) */ public void addPlaceFirst(final Place place) { //TODO jaanus : this is so wrong. is it for "GPS"? if (place == null) { return; } final boolean calculatePlaceLocationAndRepaint = displayedMap != null && middlePoint != null; if (!displayedElements.contains(place)) { displayedElements.insertElementAt(place, 0); if (calculatePlaceLocationAndRepaint) { place.calculatePosition(displayedMap, middlePoint.getZoom()); } } if (!calculatePlaceLocationAndRepaint) { return; } //TODO jaanus : optimize this fullScreenUpdate(); //TODO jaanus : handler force update after kml place icon has been downloaded without this hack repaint(); } /** * Add places to be displayed on map * * @param places * places to be displayed on map (if visible) */ public void addPlaces(final Place[] places) { addPlaces(places, true); } /** * Not part of public API. * * Add place to map with possibility to skip repaint. * * @param places * places to be added * @param updateScreen * should screen be updated after places have been added */ public void addPlaces(final Place[] places, final boolean updateScreen) { addOnMapElements(places, updateScreen); } /** * Add line to be displayed on map * * @param line * line to be displayed */ public void addLine(final Line line) { addOnMapElement(line); } /** * Add lines to be displayed on map * * @param lines * lines to be displayed */ public void addLines(final Line[] lines) { addOnMapElements(lines, true); } /** * Add polygon to be displayed on map * * @param polygon * polygon to be displayed */ public void addPolygon(final Polygon polygon) { addOnMapElement(polygon); } /** * Add multiple polygons for display * * @param polygons * polygons to be added */ public void addPolygons(final Polygon[] polygons) { addOnMapElements(polygons, true); } /** * Remove polygon from map * * @param polygon * polygon to be removed */ public void removePolygon(final Polygon polygon) { removeOnMapElements(new OnMapElement[] { polygon }); } /** * Remove multiple polygons from map * * @param polygons * polygons to be removed */ public void removePolygons(final Polygon[] polygons) { removeOnMapElements(polygons); } /** * Add elements to be displayed on map * * @param elements * elements to be added for display */ public void addOnMapElements(final OnMapElement[] elements) { addOnMapElements(elements, true); } /** * Not part of public API * * @param elements * @param updateScreen */ public void addOnMapElements(final OnMapElement[] elements, final boolean updateScreen) { boolean added = false; final boolean calculatePlaceLocationAndRepaint = displayedMap != null && middlePoint != null; for (int i = 0; i < elements.length; i++) { //TODO jaanus : test removeElement/addElement speed if (!displayedElements.contains(elements[i])) { displayedElements.addElement(elements[i]); added = true; if (calculatePlaceLocationAndRepaint) { elements[i].calculatePosition(displayedMap, middlePoint.getZoom()); } } } if (!calculatePlaceLocationAndRepaint) { return; } //TODO jaanus : optimize this fullScreenUpdate(); //TODO jaanus : handler force update after kml place icon has been downloaded without this hack if (added || updateScreen) { repaint(); } } private void addOnMapElement(final OnMapElement element) { if (element == null) { return; } addOnMapElements(new OnMapElement[] { element }, true); } /** * Get bounding box for current map view. * * @return bounding box with WGS84 coordinates for current map views left * bottom and right top corners. */ public WgsBoundingBox getBoundingBox() { if (middlePoint == null || displayedMap == null) { return null; } //TODO jaanus : set it in one place if (tileMapBounds == null) { tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); } //TODO jaanus : make it clearer //these points are on tiles final MapPos mapMinPoint = tileMapBounds.getMinPoint(); final MapPos mapMaxPoint = tileMapBounds.getMaxPoint(); // left-bottom (SW) coordinate final int viewMinX = Math.max(middlePoint.getX() - displayCenterX, mapMinPoint.getX()); final int viewMinY = Math.min(middlePoint.getY() + displayCenterY, mapMaxPoint.getY()); final MapPos posMin = new MapPos(viewMinX, viewMinY, middlePoint.getZoom()); // right-top (NE) coordinate final int viewMaxX = Math.min(middlePoint.getX() + displayCenterX, mapMaxPoint.getX()); final int viewMaxY = Math.max(middlePoint.getY() - displayCenterY, mapMinPoint.getY()); final MapPos posMax = new MapPos(viewMaxX, viewMaxY, middlePoint.getZoom()); return new WgsBoundingBox(displayedMap.mapPosToWgs(posMin).toWgsPoint(), displayedMap .mapPosToWgs(posMax).toWgsPoint()); } /** * Set bounding box for the view. Finds the best zoom level for the bounding * box view. * * @param bBox * are to be displayed (in WGS84 coordinates) */ public void setBoundingBox(final WgsBoundingBox bBox) { if (bBox == null) { return; } WgsPoint newCenter = null; int zoomLevel = findZoomForBoundingBoxView(displayedMap, bBox); if (zoomLevel == -1) { //this means that could not fit the view on this maps zoom range. //set middlepoint and zoom to minimum newCenter = bBox.getBoundingBoxCenter(); zoomLevel = displayedMap.getMinZoom(); } else { final Point minPoint = bBox.getWgsMin().toInternalWgs(); final Point maxPoint = bBox.getWgsMax().toInternalWgs(); newCenter = new Point(minPoint.getX() + (maxPoint.getX() - minPoint.getX()) / 2, minPoint .getY() + (maxPoint.getY() - minPoint.getY()) / 2).toWgsPoint(); } tileMapBounds = displayedMap.getTileMapBounds(zoomLevel); setMiddlePoint(newCenter, zoomLevel); } private int findZoomForBoundingBoxView(final GeoMap usedMap, final WgsBoundingBox bBox) { if (bBox == null) { return -1; } int zoom = usedMap.getMaxZoom(); final Point minPoint = bBox.getWgsMin().toInternalWgs(); final Point maxPoint = bBox.getWgsMax().toInternalWgs(); while (true) { final MapPos posMin = usedMap.wgsToMapPos(minPoint, zoom); final MapPos posMax = usedMap.wgsToMapPos(maxPoint, zoom); //y values is top->down, so need to subtract max from min, to avoid Math.abs() :P if (posMax.getX() - posMin.getX() <= displayWidth && posMin.getY() - posMax.getY() <= displayHeight) { return zoom; } else { zoom--; if (zoom < usedMap.getMinZoom()) { return -1; } } } } private void recalculateMapPosition(final Vector places) { final int zoomLevel = middlePoint.getZoom(); for (int i = 0; i < places.size(); i++) { final OnMapElement element = (OnMapElement) places.elementAt(i); element.calculatePosition(displayedMap, zoomLevel); } } /** * Stop threads started by MapComponent. Called before application exit to * clean library resources. */ public void stopMapping() { if (!mappingStarted) { return; } mappingStarted = false; panning.quit(); if (locationSource != null) { locationSource.quit(); } if (networkCache != null) { networkCache.deinitialize(); } //TODO jaanus : check this MappingCore.clean(); //TODO jaanus : is this ever needed? mapBuffer.clean(); mapBuffer = null; System.gc(); } /** * Not in public API. Will be removed by obfuscation * * @param fromTimer */ public void repaint(final boolean fromTimer) { repaint(); if (fromTimer) { repaintTask = null; } } /** * Not in public API. Will be removed by obfuscation * * @param license */ public void setLicense(final License license) { this.license = license; if (license == License.LICENSE_NETWORK_ERROR) { // taskRunner.quit(); this.license = License.OFFLINE; } else if (!license.isValid()) { stopMapping(); if (errorListener != null) { errorListener.licenseError(license.getMessage()); } } Log.info("License: " + license); repaint(); } /** * Change used map * * @param newMap * new map to be displayed */ public void setMap(final GeoMap newMap) { setMap(newMap, true); } private void setMap(final GeoMap newMap, final boolean createStrategy) { final WgsBoundingBox currentView = getBoundingBox(); final WgsPoint middleWgs = getMiddlePoint(); displayedMap = newMap; if (createStrategy) { tileSearchStrategy = new GeoMap[] { displayedMap }; } if (screenCache == null) { createScreenCache(); } screenCache.resize((2 + ((displayWidth - 2) / displayedMap.getTileSize())) * (2 + ((displayHeight - 2) / displayedMap.getTileSize()))); neededTiles.setSize(0); //mapTilesRetriever.cancelAllRunning(); if (zoomBufferGraphics != null) { zoomBufferGraphics.setColor(0xFFFFFFFF); zoomBufferGraphics.fillRect(0, 0, displayWidth, displayHeight); } //initialize map size setZoomLevelIndicator(zoomLevelIndicator); //TODO jaanus : impove this //find optimal zoom final int viewZoom = findZoomForBoundingBoxView(displayedMap, currentView); Task task = displayedMap.getInitializationTask(); if (task != null) { taskRunner.enqueue(task); } setMiddlePoint(middleWgs, viewZoom == -1 ? displayedMap.getMinZoom() : viewZoom); } /** * Get zoom range for currently used map * * @return zoom range for map */ public ZoomRange getZoomRange() { return displayedMap.getZoomRange(); } /** * Add kml service to be handled (updated, parsed, painted) internally by * library. * * @param service * new service to be displayed on map */ public void addKmlService(final KmlService service) { if (kmlServicesHandler == null) { initializeKmlServiceshandler(); } kmlServicesHandler.addService(service); mapMoved(); } /** * Get list of used kml services * * @return kml services inserted by application */ public KmlService[] getKmlServices() { if (kmlServicesHandler == null) { return new KmlService[0]; } return kmlServicesHandler.getServices(); } /** * Remove previously added kml service * * @param service * service to be removed */ public void removeKmlService(final KmlService service) { if (kmlServicesHandler == null) { return; } kmlServicesHandler.removeService(service); } /** * Get additional info for place displayed by internally handled service. * * @param place * place associated with internal data * @return information object containing additional data for place */ public PlaceInfo getAdditionalInfo(final Place place) { PlaceInfo result = null; if (kmlServicesHandler != null) { result = kmlServicesHandler.getAdditionalInfo(place); } return result; } /** * Get KML places which were read by KML parser. * * @param KML * service TODO service for which KML places are needed * @return array of KmlPlace */ public KmlPlace[] getKmlPlaces(final KmlService service) { if (kmlServicesHandler != null && service != null) { return kmlServicesHandler.getKmlPlaces(service); } return null; } /** * Retrieve currently used map. * * @return currently displayed map */ public GeoMap getMap() { return displayedMap; } /** * Get internal log for library. * * @return internal log */ public String getLibraryLog() { return Log.getLog(); } /** * Not part of public API. Removed by obfuscator */ public void removeZoomDelay() { zoomDelay = null; } /** * Not part of public API. Removed by obfuscator. * * @return middle point location on pixal map */ public MapPos getInternalMiddlePoint() { return middlePoint; } /** * Set map size on screen. * * @param w * map width * @param h * map height */ public void setSize(final int w, final int h) { resize(w, h); } /** * Set position on screen for top-left corner. * * @param x * left * @param y * top */ public void setScreenPosition(final int x, final int y) { displayX = x; displayY = y; } /** * Set bounds on screen. * * @param x * left * @param y * top * @param w * width * @param h * height * @see BasicMapComponent#setScreenPosition(int,int) * @see BasicMapComponent#setSize(int,int) */ public void setScreenBounds(final int x, final int y, final int w, final int h) { setSize(w, h); setScreenPosition(x, y); } /** * Set the screen position for the center of the displayed map. * * @param x * screen position X * @param y * screen position Y */ public void setScreenCenter(final int x, final int y) { //TODO jaanus : ??? setScreenPosition(x - getWidth() / 2, y - getHeight() / 2); } /** * Get the screen position for the top-left corner. */ public int getScreenLeft() { return displayX; } /** * Get the screen position for the top-left corner. */ public int getScreenTop() { return displayY; } /** * Get map center point. Synonym for getMiddlePoint * * @return middle point on screen in WGS84 */ public WgsPoint getCenterPoint() { return getMiddlePoint(); } /** * Set tolerance for detecting click events instead of dragging on * touchscreen. Defaults to 5 pixels. * * @param pixels * needed to be moved for map dragging */ public void setTouchClickTolerance(final int pixels) { touchTolerance = pixels; } /** * * Move map without changing zoom. * * @param lon * new map center in WGS84 * @param lat * new map center in WGS84 */ public void moveMap(final double lon, final double lat) { moveMap(new WgsPoint(lon, lat)); } /** * Move map without changing zoom. * * @param point * new map center in WGS84 */ public void moveMap(final WgsPoint point) { if (point == null) { return; } final MapPos mp = displayedMap.wgsToMapPos(point.toInternalWgs(), middlePoint.getZoom()); panMap(mp.getX() - middlePoint.getX(), mp.getY() - middlePoint.getY()); mapMoved(); } /** * Set map zoom without changing position. * * @param newZoom * new zoom level */ public void setZoom(final int newZoom) { final int currentZoom = middlePoint.getZoom(); if (currentZoom == newZoom) { return; } cleanMapBuffer(); int dif = newZoom - currentZoom; if (currentZoom < newZoom && newZoom > displayedMap.getMaxZoom()) { dif = displayedMap.getMaxZoom() - currentZoom; } else if (newZoom < displayedMap.getMinZoom()) { dif = displayedMap.getMinZoom() - currentZoom; } middlePoint = displayedMap.zoom(middlePoint, dif); tileMapBounds = displayedMap.getTileMapBounds(middlePoint.getZoom()); createZoomBufferAndUpdateScreen(-dif, true); } /** * Set cursor used on screen for places selection, etc. * * @param newCursor * cursor implementation */ public void setCursor(final Cursor newCursor) { cursor = newCursor; } /** * Get the current zoom level. * * @return current zoom level */ public int getZoom() { return middlePoint.getZoom(); } /** * Get the current map listener. * * @return the current map listener */ public MapListener getMapListener() { return mapListener; } /** * Get current listener for objects on map * * @return elements listener */ public OnMapElementListener getOnMapElementListener() { return onMapElementListener; } /** * Get the current error listener. * * @return the current error listener */ public ErrorListener getErrorListener() { return errorListener; } /** * Change the implementation for download stream opening. As default a direct * connection without any additional headers is used. */ public void setDownloadStreamOpener(final DownloadStreamOpener opener) { taskRunner.setDownloadStreamOpener(opener); } /** * Not part of public API */ public void zoomLevelIndicatorCheck() { //TODO jaanus : check if null check is needed for obfuscator if (zoomLevelIndicator != null && System.currentTimeMillis() - lastZoomCall >= zoomLevelIndicator.displayTime()) { zoomLevelIndicator.setVisible(false); repaint(); } else { //schedule new task calculating the delay timer.schedule(new ZoomIndicatorCheckTask(this), Math.max(zoomLevelIndicator.displayTime(), 100) - (System.currentTimeMillis() - lastZoomCall)); } } /** * Set search strategy for map tile. * * @param searched * search strategy to be used */ public void setTileSearchStrategy(final GeoMap[] searched) { this.tileSearchStrategy = searched; setMap(searched[0], false); } /** * Set panning strategy for map component. This is required for map panning * with keys. * * @param panningStrategy * new panning strategy */ public void setPanningStrategy(final PanningStrategy panningStrategy) { if (panning != null) { panning.stopPanning(); panning.quit(); } panning = panningStrategy; panning.setMapComponent(this); panning.start(); } /** * Set location source with GPS marker to be displayed. Here the location * retrieving thread is also started. * * @param source * source to be used */ public void setLocationSource(final LocationSource source) { if (locationSource != null) { locationSource.quit(); } if (source == null) { return; } locationSource = source; locationSource.getLocationMarker().setMapComponent(this); locationSource.start(); } /** * Set cache for networking. Currently data is cached: * <ul> * <li>map tiles to rms level</li> * <li>kml icons to memory and rms</li> * </ul> * * @param cache */ public void setNetworkCache(final com.nutiteq.cache.Cache cache) { networkCache = cache; } /** * Not part of public API */ public MapPos getMapPosition(final WgsPoint wgsLocation) { return displayedMap.wgsToMapPos(wgsLocation.toInternalWgs(), middlePoint.getZoom()); } /** * Remove used location source */ public void removeLocationSource() { if (locationSource != null) { locationSource.quit(); locationSource = null; repaint(); } } /** * Enqueue download requestor to be handled by library. * * @param downloadable * resource to be downloaded * @param cacheLevel * at which cache levels should response be cached */ public void enqueueDownload(final ResourceRequestor downloadable, final int cacheLevel) { taskRunner.enqueueDownload(downloadable, cacheLevel); } /** * Set zoom indicator to be painted on display. This method will overwrite * default values set in * {@link com.nutiteq.MapComponent#showZoomLevelIndicator(boolean)}. * * @param zoomIndicator * zoom indicator to use */ public void setZoomLevelIndicator(final ZoomIndicator zoomIndicator) { zoomLevelIndicator = zoomIndicator; if (displayedMap == null || zoomLevelIndicator == null) { return; } zoomLevelIndicator.setZoomRange(displayedMap.getZoomRange()); } /** * Set used implementation for network traffic display on map. * * @param display * display used for info show */ public void setDownloadDisplay(final DownloadDisplay display) { downloadDisplay = display; downloadDisplay.setDownloadCounter(downloadCounter); downloadDisplay.setDisplayUpdater(this); } /** * Set download counter used for gathering information about network traffic * * @param counter * implementation used */ public void setDownloadCounter(final DownloadCounter counter) { downloadCounter = counter; if (downloadDisplay != null) { downloadDisplay.setDownloadCounter(downloadCounter); } } /** * On touch screen phones, if some object has been selected, should focus be * lost on map drag. * * @param looseFocus * should object focus be lost */ public void looseFocusOnDrag(final boolean looseFocus) { this.looseFocusOnDrag = looseFocus; } public void loosePlaceFocus() { if (centeredElement == null) { return; } handleActiveIconChange(centeredElement, null); centeredElement = null; } /** * Set focus on given place. * * @param focusOn * place to be focused on */ public void focusOnPlace(final Place focusOn) { if (focusOn == null) { loosePlaceFocus(); return; } if (focusOn == centeredElement) { return; } focusOn.calculatePosition(displayedMap, middlePoint.getZoom()); setMiddlePoint(focusOn.getWgs()); //copy/paste from handlePlaceOrMapClick handleActiveIconChange(centeredElement, focusOn); if (onMapElementListener != null) { handlePossibleCenteredChangeNotification(centeredElement, focusOn); } if (centeredElement instanceof Place) { final Point viewUpdate = centeredElement == null ? null : ((Place) centeredElement) .getLabelViewUpdate(middlePoint, displayWidth, displayHeight); if (viewUpdate != null) { panMap(viewUpdate.getX(), viewUpdate.getY()); } } centeredElement = focusOn; repaint(); } /** * Get places currently visible on map view. * * @return visible places */ public Place[] getVisiblePlaces() { final Vector visible = new Vector(); for (int i = 0; i < displayedElements.size(); i++) { final Object p = displayedElements.elementAt(i); if (!(p instanceof Place)) { continue; } final Place place = (Place) p; if (place.isVisible(middlePoint.getX() - displayCenterX, middlePoint.getY() - displayCenterY, displayWidth, displayHeight, middlePoint.getZoom())) { visible.addElement(place); } } final Place[] result = new Place[visible.size()]; visible.copyInto(result); return result; } /** * Set file system to be used for {@link com.nutiteq.maps.StoredMap} handling * * @param fs * platform dependent file system to be used */ public void setFileSystem(final FileSystem fs) { taskRunner.setFileSystem(fs); } public DownloadCounter getDownloadCounter() { return downloadCounter; } protected boolean hasMappingStarted() { return mappingStarted; } protected boolean isNetworkCacheSet() { return networkCache != null; } protected boolean isPanningStrategySet() { return panning != null; } protected boolean isMapSet() { return tileSearchStrategy != null; } protected boolean isDownloadCounterPresent() { return downloadCounter != null; } /** * Not part of public API * * @param task */ public void enqueue(final Task task) { taskRunner.enqueue(task); } /** * @return all KML Places from all loaded KML Services */ public KmlPlace[] getKmlPlaces() { if (kmlServicesHandler != null ) { return kmlServicesHandler.getKmlPlaces(); } return null; } /** * removes all KML Services added so far */ public void removeKmlService() { if (kmlServicesHandler == null) { return; } KmlService[] services = kmlServicesHandler.getServices(); for (int i = 0; i < services.length; i++) { kmlServicesHandler.removeService(services[i]); } } }