package com.androidol; import java.util.ArrayList; import com.androidol.basetypes.Pixel; import com.androidol.basetypes.Size; import com.androidol.control.Control; import com.androidol.events.Event; import com.androidol.events.LayerEvents; import com.androidol.events.MapEvents; import com.androidol.layer.Grid; import com.androidol.layer.Layer; import com.androidol.layer.osm.Mapnik; import com.androidol.map.schema.MapSchema; import com.androidol.map.schema.OSMMapSchema; import com.androidol.map.schema.OSMTileMapSchema; import com.androidol.util.Util; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.GestureDetector.OnDoubleTapListener; import android.view.GestureDetector.OnGestureListener; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.OnHierarchyChangeListener; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.view.animation.Animation.AnimationListener; import android.widget.ZoomControls; public class Map extends ViewGroup { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== // protected String name = "androidol_map"; protected String title = "androidol_map"; private boolean initialized = false; // protected Size size; // size of the map, should fixed to phone screen size protected Coordinate center; protected int zoom; protected double resolution; protected double scale; protected String projection; protected String units; protected final boolean fractionalZoom = false; // TODO: support fractional zoom protected MapSchema schema; // protected int numZoomLevels; protected int maxZoomLevel; protected int minZoomLevel; protected Envelope maxExtent; protected Envelope minExtent; protected double maxResolution; protected double minResolution; protected double maxScale; protected double minScale; protected double[] scales; protected double[] resolutions; // protected ArrayList<Layer> layers; protected ArrayList<Control> controls; protected Layer baseLayer; // protected Coordinate layerContainerOrigin; protected final double layerContainerCanvasLeft = 0.0; // TODO: related to view padding protected final double layerContainerCanvasTop = 0.0; // TODO: related to view padding // protected Context context; protected MapEvents events = new MapEvents(); protected MapEventsHandler mapEventsHandler = new MapEventsHandler(); protected GestureDetector mapViewGestureDetector = new GestureDetector(new MapViewGestureListener()); protected ZoomControls zoomControls; protected Bitmap previousSnapShot = null; protected Paint paint = new Paint(); // =========================================================== // Constructors and methods inherited from ViewGroup // =========================================================== public Map(Context context) { super(context, null); this.context = context; // implement OnHierarchyChangeListener interface to track child view changes /* this.setOnHierarchyChangeListener( new OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) {} @Override public void onChildViewRemoved(View parent, View child) { } } ); */ } public Map(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; try { // TODO: initialize map with default configuration // like 'name' and 'title' etc. // TODO: initialize map from layout configuration over default settings initialize(); // initialize map itself } catch(Exception e) { // TODO: map initialization failure throw exception Util.printErrorMessage("...fail to initialize map..." + e.getMessage()); } // implement OnHierarchyChangeListener interface to track child view changes this.setOnHierarchyChangeListener( new OnHierarchyChangeListener() { @Override public void onChildViewAdded(View parent, View child) { //Util.printDebugMessage("...onChildViewAdded..."); } @Override public void onChildViewRemoved(View parent, View child) { //Util.printDebugMessage("...onChildViewRemoved..."); } } ); if(this.initialized == true) { // set this.initialized to true only when all layers from layout xml are added into map - // - will set it to true in first call of dispatchDraw() of map view group this.initialized = false; } // TODO: research on setDrawingCacheEnabled() // doesn't sound helpful much here //this.setDrawingCacheEnabled(true); } /** * Override generateDefaultLayoutParams() */ @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } /** * generateLayoutParams(AttributeSet attrs) * * Override this method such that child view of map (ViewGroup) doesn't have to specify - * - LayoutParams.FILL_PARENT and LayoutParams.FILL_PARENT * * Any child view that needs LayoutParams other than FILL_PARENT must call - * - super.addView(child, index, layoutParams) with a different layoutParams like below: * - super.addView(child, index, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { // always returns new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); return generateDefaultLayoutParams(); } /** * Override onMeasure(int widthMeasureSpec, int heightMeasureSpec) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //Util.printDebugMessage("...onMeasure() is called..."); // measure child views final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } // measure myself int maxHeight = getSuggestedMinimumWidth(); int maxWidth = getSuggestedMinimumHeight(); maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); /* int wSpecMode = MeasureSpec.getMode(widthMeasureSpec); int hSpecMode = MeasureSpec.getMode(heightMeasureSpec); int wSpecSize = MeasureSpec.getSize(widthMeasureSpec); int hSpecSize = MeasureSpec.getSize(heightMeasureSpec); */ int resolvedWidth = resolveSize(maxWidth, widthMeasureSpec); int resolvedHeight = resolveSize(maxHeight, heightMeasureSpec); setMeasuredDimension( resolvedWidth, resolvedHeight ); } /** * Override onLayout(boolean changed, int left, int top, int right, int bottom) */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //Util.printDebugMessage("...onLayout() is called..."); // size must be set in or after onLayout is called - // - getWidth() and getHeight() get the actual size this.size = new Size(this.getWidth(), this.getHeight()); // layout child views int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); final int childMeasuredWidth = child.getMeasuredWidth(); final int childMeasuredHeight = child.getMeasuredHeight(); child.layout( childLeft, childTop, childLeft + childMeasuredWidth, childTop + childMeasuredHeight ); } } } /** * addView(View child, int index, LayoutParams params) * * this is the addView() method being called by other addView in ViewGroup so - * - not necessary to override addView(View child), addView(View child, int index), or addView(View child, LayoutParams params) * @return */ @Override public void addView(View child, int index, LayoutParams params) { //Util.printDebugMessage("..." + ((Layer)child).getName() + "..."); //Util.printDebugMessage("...addView() is called..."); if(child instanceof Layer) { Layer layer = (Layer)child; // check if layer is already added for(int i=0, len=this.layers.size(); i<len; i++) { if(this.layers.get(i) == layer) { Util.printWarningMessage(" ...layer already in the map...skip adding it..."); return; } } if(index >= this.layers.size() || index < 0) { this.layers.add(layer); } else { this.layers.add(index, layer); } layer.setMap(this); super.addView(child, index, params); // follow code doesn't seem to be necessary // follow code is only useful when there is no base layer and a new base layer is added /* if(this.initialized == true) { // meaning layer being added after map is initialized (probably add layer on fly instead of in layout xml) if(layer.isBaseLayer()) { if(this.baseLayer == null) { // switch base layer if base layer is added this.setBaseLayer(layer); // set the first base layer we add as base layer } else { layer.setVisible(false); // can not have multiple base layers at the same time } } else { // draw overlay layer right after the layer is added layer.postInvalidate(); } } */ // if super.addView(child, index, params); is not called then - // - handlers in setOnHierarchyChangeListener won't be triggered } else { // initialize zoom controls if(child instanceof ZoomControls) { // deal with ZoomControls if(this.zoomControls != null) { Util.printWarningMessage("...map already has a zoomControls...can not add another..skip adding..."); return; } this.zoomControls = (ZoomControls)child; this.zoomControls.setZoomSpeed(150); // half second this.zoomControls.setOnZoomInClickListener(new OnClickListener() { public void onClick(View view) { Map.this.zoomIn(); } }); this.zoomControls.setOnZoomOutClickListener(new OnClickListener() { public void onClick(View view) { Map.this.zoomOut(); } }); /* * since I override generateLayoutParameters() to always use FILL_PARENT for width and height - * - so I have to switch WRAP_CONTENT here for zoom controls because otherwise it will masks the touch events - * - on map */ super.addView(child, index, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } else { // what else type of view could it be super.addView(child, index, params); } } } /** * removeView(View child) */ @Override public void removeView(View child) { if(child instanceof Layer) { Layer layer = (Layer)child; if(layer == this.baseLayer) { // by design...can not remove base layer...unless you set another new baselayer first Util.printWarningMessage("...skip removing...can not remove base layer...unless you set another new baselayer first..."); return; } else { this.layers.remove(layer); this.layers.trimToSize(); super.removeView(layer); // not necessary to call map.invalidate() because super.removeView() shall trigger that } } else { super.removeView(child); } } /** * */ @Override protected void dispatchDraw(Canvas canvas) { //Util.printDebugMessage("...dispatchDraw..."); //super.dispatchDraw(canvas); // the first time when map and layers are initialized from layout xml if(this.initialized == false) { for(int i=0; i<this.getChildCount(); i++) { if(this.getChildAt(i) instanceof Layer) { Layer layer = (Layer)this.getChildAt(i); if(layer.isBaseLayer()) { if(this.baseLayer == null) { this.setBaseLayer(layer); // set the first base layer we add as base layer } else { layer.setVisible(false); } } else { //layer.postInvalidate(); layer.invalidate(); } } } this.initialized = true; }/* else { // when map and layers are redrawn // redraw all overlay layers with current state if(this.baseLayer != null) { this.baseLayer.redraw(); } // draw layers based on their view order in view group for(int i=0; i<this.getChildCount(); i++) { if(getChildAt(i) instanceof Layer) { Layer layer = (Layer)getChildAt(i); if(layer!=null && layer.isBaseLayer()==false) { layer.redraw(); } } } // draw other controls like zoom controls if(this.zoomControls != null) { this.zoomControls.draw(canvas); } }*/ super.dispatchDraw(canvas); } /** * */ @Override public void onDraw(final Canvas canvas) { } // =========================================================== // Map API and private interfaces // =========================================================== private void initialize() { if(this.schema == null) { //this.schema = new OSMMapSchema(); this.schema = new OSMTileMapSchema(); } // everything comes from map schema this.numZoomLevels = this.schema.getNumZoomLevels(); // this.maxZoomLevel = this.schema.getMaxZoomLevel(); this.minZoomLevel = this.schema.getMinZoomLevel(); this.zoom = this.schema.getDefaultZoomLevel(); // initial zoom level is the minimum zoom level this.projection = this.schema.getProjection(); this.units = this.schema.getUnits(); this.center = this.schema.getDefaultCenter(); this.resolutions = this.schema.getResolutions(); // resolutions could be null or an empty array this.maxResolution = this.schema.getMaxResolution(); // this.minResolution = this.schema.getMinResolution(); // this.resolution = this.schema.getDefaultResolution(); if(this.resolution < 0) { this.resolution = Double.POSITIVE_INFINITY; } /* if(this.resolutions!=null && this.resolutions.length>0) { this.resolution = this.resolutions[this.zoom]; } else { if(this.maxResolution > 0) { this.resolution = this.maxResolution; } else if(this.minResolution > 0) { this.resolution = this.minResolution; } else { this.resolution = Double.POSITIVE_INFINITY; } } */ //this.scales = this.schema.getScales(); // //this.scale = this.scales[this.zoom]; // //this.maxScale = this.schema.getMaxScale(); // //this.minScale = this.schema.getMinScale(); // this.scales = null; // scales will be calculated based on resolutions and DPI this.scale = Double.NEGATIVE_INFINITY; // this.maxScale = Double.NEGATIVE_INFINITY; // this.minScale = Double.NEGATIVE_INFINITY; // this.maxExtent = this.schema.getDefaultMaxExtent(); // default max extent this.minExtent = this.schema.getDefaultMinExtent(); // default min extent // // size can not be determined at ViewGroup's constructor - // - because this.getWidth() and this.getHeight() return 0 here //this.size = new Size(this.getWidth(), this.getHeight()); // map view size is the size of parent viewgroup //this.fractionalZoom = false; // whether to support fractional zoom, no by default // register call-back handlers for different types of events this.events.registerAll(this.mapEventsHandler); // register double tap listeners this.mapViewGestureDetector.setOnDoubleTapListener(new MapViewDoubleTapListener()); // support double-tap event on map this.mapViewGestureDetector.setIsLongpressEnabled(true); this.layers = new ArrayList<Layer>(); // empty layer list initially this.baseLayer = null; // empty baselayer this.controls = new ArrayList<Control>(); // empty control list initially // TODO: register MOVE_START event with this.updateSize() // TODO: register all call-back in this.eventListeners } private void applyMapSchema(MapSchema newSchema) { if(schema != null) { this.numZoomLevels = newSchema.getNumZoomLevels(); // this.maxZoomLevel = newSchema.getMaxZoomLevel(); this.minZoomLevel = newSchema.getMinZoomLevel(); this.zoom = newSchema.getDefaultZoomLevel(); // initial zoom level is the minimum zoom level this.projection = newSchema.getProjection(); this.units = newSchema.getUnits(); this.center = newSchema.getDefaultCenter(); this.resolutions = newSchema.getResolutions(); // resolutions could be null or an empty array this.maxResolution = newSchema.getMaxResolution(); // this.minResolution = newSchema.getMinResolution(); // this.resolution = newSchema.getDefaultResolution(); if(this.resolution < 0) { this.resolution = Double.POSITIVE_INFINITY; } /* if(this.resolutions!=null && this.resolutions.length>0) { this.resolution = this.resolutions[this.zoom]; } else { if(this.maxResolution > 0) { this.resolution = this.maxResolution; } else if(this.minResolution > 0) { this.resolution = this.minResolution; } else { this.resolution = Double.POSITIVE_INFINITY; } } */ //this.scales = newSchema.getScales(); // //this.scale = this.scales[this.zoom]; // //this.maxScale = newSchema.getMaxScale(); // //this.minScale = newSchema.getMinScale(); // this.scales = null; // scales will be calculated based on resolutions and DPI this.scale = Double.NEGATIVE_INFINITY; // this.maxScale = Double.NEGATIVE_INFINITY; // this.minScale = Double.NEGATIVE_INFINITY; // this.maxExtent = newSchema.getDefaultMaxExtent(); // default max extent this.minExtent = newSchema.getDefaultMinExtent(); } } /** * API Method: destroy * destroy the map */ public void destroy() { //Util.printDebugMessage("@...Map.destroy() is called..."); if(this.layers != null) { // destroy layers for(int i=0; i<this.layers.size(); i++) { Layer layer = this.layers.get(i); if(layer != null) { // pass 'false' to destroy so that map wont try to set a new // baselayer after each baselayer is removed // TODO: uncomment this //layer.destroy(false); } } this.layers.clear(); //Util.printDebugMessage(" ...clean up layer list..."); } if(this.controls != null) { // destroy layers for(int i=0; i<this.controls.size(); i++) { Control control = this.controls.get(i); if(control != null) { control.destroy(); } } this.controls.clear(); //Util.printDebugMessage(" ...clean up control list..."); } // TODO: unregister all call-back handlers from this.events this.events.unregisterAll(this.mapEventsHandler); // TODO: unregister all other call-back handlers } /** * APIMethod: addLayer * * * @param layer {@link Layer} * @return index * return the index of layer after it is successfully added into map, if not or duplicated then return -1 */ public void addLayer(Layer layer) { //Util.printDebugMessage("@...Map.addLayer() is called..."); //Util.printDebugMessage(" ...add layer: " + layer.getName() + "..."); // always append newly added layer at the end of the layers queue - // - don't worry about it will cover zoom controls because this.dispatchDraw() is overrided such that - // - zoom controls is always drawn on top. addView(layer, getChildCount()-1); // trigger map event 'LAYER_ADDED' //this.events.triggerEvent(MapEvents.LAYER_ADDED, new Event(MapEvents.LAYER_ADDED, layer)); //return this.layers.indexOf(layer); } /** * setLayerZIndex(Layer layer, int zIdx) * * @param layer * @param zIdx */ public void setLayerZIndex(Layer layer, int zIdx) { //Util.printDebugMessage(" ...set z index for layer to: " + zIdx + "..."); if(this.layers.contains(layer) == false) { Util.printDebugMessage("...layer doesn't exist in map...skip setting z index..."); return; } else { // TODO: validate zIdx such that layer won't be under base-layer or above zoom controls if(layer != this.baseLayer) { int oldIdx = this.layers.indexOf(layer); if(oldIdx != zIdx) { this.removeView(layer); this.addView(layer, zIdx); } // TODO: also reset the index of layer view in map view group } else { Util.printDebugMessage("...base layer is always at bottom...can not change its z index..."); } // TODO: trigger LAYER_CHANGED event with layer and order } } /** * to redraw each layer in map */ public void redraw() { // TODO: to be implemented invalidate(); } /** * API Method: setBaseLayer * @param newBaseLayer * */ public void setBaseLayer(Layer newBaseLayer) { //Util.printDebugMessage("@...Map.setBaseLayer() is called..."); Envelope oldExtent = null; if(this.baseLayer != null) { oldExtent = this.baseLayer.getExtent(); } if(newBaseLayer != this.baseLayer) { if(this.layers.contains(newBaseLayer) == true) { // newBaseLayer must be added through this.addLayer() first if (this.baseLayer != null) { // make the old base layer invisible this.baseLayer.setVisible(false); } this.baseLayer = newBaseLayer; //Util.printDebugMessage(" ...set new base layer..."); this.baseLayer.setVisible(true); //Util.printDebugMessage(" ...make new base layer visible..."); Coordinate center = this.getCenter(); if(center != null) { Coordinate newCenter = (oldExtent!=null) ? oldExtent.centre() : center; int newZoom = (oldExtent!=null) ? this.getZoomForExtent(oldExtent) : this.getZoomForResolution(this.resolution); //Util.printDebugMessage(" ...center map after changing base layer..."); this.setCenter(newCenter, newZoom, false, false); } this.events.triggerEvent(MapEvents.LAYER_CHANGED, new Event(MapEvents.LAYER_CHANGED, this.baseLayer)); this.events.triggerEvent(MapEvents.BASELAYER_CHANGED, new Event(MapEvents.LAYER_CHANGED, this.baseLayer)); // TODO: shall I set isBaseLayer to true on newBaseLayer? } } } /** * APIMethod: addLayers * * @param layers */ public void addLayers(Layer[] layers) { // TODO: to be implemented } /** * removeLayer(Layer layer) * @param layer */ public void removeLayer(Layer layer) { removeLayer(layer, false); } /** * removeLayer(Layer layer, boolean setNewBaseLayer) * @param layer */ public void removeLayer(Layer layer, boolean setNewBaseLayer) { // TODO: to be implemented // must remove layer from map and also remove view from view group removeView(layer); //this.events.triggerEvent(MapEvents.LAYER_REMOVED, new Event(MapEvents.LAYER_REMOVED, layer)); } /** * APIMethod: resetLayersZIndex * Reset each layer's z-index based on layer's array index */ public void resetLayersZIndex() { // TODO: to be implemented } /** * API Method: getNumLayers * * @return * number of layers in the map */ public int getNumLayers() { return this.layers.size(); } /** * API Method: getLayerIndex * * @param layer * @return */ public int getLayerIndex(Layer layer) { // TODO: to be implemented return 0; } /** * API Method: setLayerIndex * Move the given layer to the specified (zero-based) index in the layer * list, changing its z-index in the map display. Use * map.getLayerIndex() to find out the current index of a layer. Note * that this cannot (or at least should not) be effectively used to * raise base layers above overlays. * * @param idx */ public void setLayerIndex(int idx) { // TODO: to be implemented } /** * API Method: raiseLayer * Change the index of the given layer by delta. If delta is positive, * the layer is moved up the map's layer stack; if delta is negative, * the layer is moved down. Again, note that this cannot (or at least * should not) be effectively used to raise base layers above overlays. */ public void raiseLayer(int delta) { // TODO: to be implemented } /** * getLayer(String id) * Get a layer based on its id * * @param id * @return */ public Layer getLayer(String id) { // TODO: to be implemented Util.printErrorMessage("...to be implemented..."); return null; } /** * getLayerByName(String name) * * @param name * @return */ public Layer getLayerByName(String name) { // TODO: to be implemented return null; } /** * getLayersByClass(String className) * * @param className * @return */ public Layer[] getLayersByClass(String className) { // TODO: to be implemented return null; } // =========================================================== // Controls operations // =========================================================== /** * API Method: addControl * * @param control * @param pixel */ public void addControl(Control control, Pixel pixel) { // TODO: to be implemented } /** * API Method: addControlToMap * * @param control * @param pixel */ public void addControlToMap(Control control, Pixel pixel) { // may merge into addControl // TODO: to be implemented } /** * API Method: getControl * * @param id * @return */ public Control getControl(String id) { // TODO: to be implemented return null; } /** * APIMethod: removeControl * Remove a control from the map. Removes the control both from the map * object's internal array of controls, as well as from the map's * viewPort (assuming the control was not added outsideViewport) * * @param control */ public void removeControl(Control control) { // TODO: to be implemented } /** * API Method: getControlsByClass * * @param className * @return */ public Control[] getControlsByClass(String className) { // TODO: to be implemented return null; } // =========================================================== // Extent, Size, Zoom, Scale and Resolution // =========================================================== /** * API Method: getSize * * @return {@link Size} * a Size object that represents the size, in pixels, * of the view into which OpenLayers has been loaded. * Note - A clone() of this locally cached variable is returned, * so as not to allow users to modify it. * */ public Size getSize() { Size size = null; if(this.size != null) { size = this.size.clone(); } return size; } /** * API Method: getCurrentSize * Get width & height directly from View in case dimension changed outside map * * @return {@link Size} */ public Size getCurrentSize() { return new Size(getWidth(), getHeight()); } /** * API Method: updateSize * This function should be called by any external code which dynamically * changes the size of the map view * */ public void updateSize() { //Util.printDebugMessage("@...Map.updateSize() is called..."); Size newSize = this.getCurrentSize(); Size oldSize = this.getSize(); if(oldSize == null) { this.size = oldSize = newSize; } if(newSize.equals(oldSize) == false) { this.size = newSize; for(int i=0, len=this.layers.size(); i<len; i++) { this.layers.get(i).onMapResize(); } if(this.baseLayer != null) { int zoom = this.getZoom(); //Util.printDebugMessage(" ...force zoom change and recenter map..."); this.zoom = -1; // set this.zoom to -1 to enforce the zoom change, so that setCenter redraw map correctly this.setCenter(this.getCenter(), zoom); } } } /** * API Method: calculateBounds * * @param center * @param resolution * * @return * extent based on current parent view size and resolution */ public Envelope calculateBounds(Coordinate center, double resolution) { //Util.printDebugMessage("@...Map.calculateBounds() is called..."); Envelope extent = null; if(center == null) { center = this.center; } if(resolution <= 0) { resolution = this.resolution; } if((center != null) && (resolution > 0)) { double x_span = this.size.getWidth() * resolution; double y_span = this.size.getHeight() * resolution; ////Util.printDebugMessage(" ...x span: " + x_span + "..."); ////Util.printDebugMessage(" ...y span: " + y_span + "..."); extent = new Envelope( center.x - x_span/2, center.x + x_span/2, center.y - y_span/2, center.y + y_span/2 ); //Util.printDebugMessage(" ...bounds calculated: " + extent.toString() + "..."); } return extent; } /** * API Method: calculateBounds * calculate extent bounds based on current resolution * * @return * extent bounds based on current resolution */ public Envelope calculateBounds() { return calculateBounds(null, -1); } /** * API Method: getExtent * * @return * current extent * */ public Envelope getExtent() { // implementation of this.baseLayer /* return this.calculateBounds(); */ Envelope extent = null; if(this.baseLayer != null) { extent = this.baseLayer.getExtent(); } else { extent = calculateBounds(); } return extent; } /** * API Method: getZoom * * @return * current zoom */ public int getZoom() { return zoom; } /** * API Method: getResolutionForZoom * * @param zoom * @return * resolution based on current zoom level */ public double getResolutionForZoom(int zoom) { // implementation of this.baseLayer /* zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1)); double res; if(this.fractionalZoom) { res = this.resolutions[Math.round(zoom)]; } else { res = this.resolutions[Math.round(zoom)]; } return res; */ double resolution = -1; if(this.baseLayer != null) { resolution = this.baseLayer.getResolutionForZoom(zoom); } else { int z = Math.max(0, Math.min(zoom, this.resolutions.length - 1)); if(this.isFractionalZoom()) { // TODO: deal with fractional zoom resolution = this.resolutions[Math.round(z)]; } else { resolution = this.resolutions[Math.round(z)]; } } return resolution; } /** * API Method: getResolution * * @return * current resolution */ public double getResolution() { // implementation of this.baseLayer /* int zoom = this.getZoom(); return this.getResolutionForZoom(zoom); */ double resolution = -1; if (this.baseLayer != null) { resolution = this.baseLayer.getResolution(); } else { resolution = getResolutionForZoom(this.zoom); } return resolution; } /** * API Method: getZoomForResolution * * @param resolution * @return * current zoom level based on resolution */ public int getZoomForResolution(double resolution) { // implementation in this.baseLayer /* int zoom; int i; for(i=1; i<this.resolutions.length; i++) { if(this.resolutions[i] < resolution) { break; } } zoom = i - 1; return zoom; */ int zoom = -1; if(this.baseLayer != null) { zoom = this.baseLayer.getZoomForResolution(resolution); } else { // TODO: deal with fractional zoom int i; for(i=1; i<this.resolutions.length; i++) { if(this.resolutions[i] < resolution) { break; } } zoom = i - 1; } return zoom; } /** * API Method: getZoomForExtent * * @param extent * @return * current zoom level based on extent */ public int getZoomForExtent(Envelope extent) { // implementation in this.baseLayer /* Size viewSize = this.getSize(); double idealResolution = Math.max(extent.getWidth()/viewSize.getWidth(), extent.getHeight()/viewSize.getHeight() ); return this.getZoomForResolution(idealResolution); */ int zoom = -1; if(this.baseLayer != null) { zoom = this.baseLayer.getZoomForExtent(extent); } else { Size viewSize = this.getSize(); double idealResolution = Math.max(extent.getWidth()/viewSize.getWidth(), extent.getHeight()/viewSize.getHeight()); return getZoomForResolution(idealResolution); } return zoom; } // =========================================================== // Center, and Pan operations // =========================================================== /** * API Method: getCenter * * @return {@link LonLat} * map center */ public Coordinate getCenter() { return this.center; } public void pan(int dx, int dy) { //Util.printDebugMessage("@...Map.pan() is called..."); Pixel centerPx = this.getViewPortPxFromCoordinate(this.getCenter()); Pixel newCenterPx = centerPx.add(dx, dy); Coordinate newCenter = this.getCoordinateFromViewPortPx(newCenterPx); //Util.printDebugMessage(" ...old screen center: " + centerPx.toString() + "...new screen center: " + newCenterPx.toString() + "..."); //Util.printDebugMessage(" ...old map center: " + this.getCenter().toString() + "...new map center: " + newCenter.toString() + "..."); // only call setCenter if there has been a change if(!newCenterPx.equals(centerPx)) { final boolean forceZoomChange = false; // zoom does not change when panning final boolean forceCenterChange = false; // //Util.printDebugMessage(" ...re-center map after pan..."); this.setCenter(newCenter, this.getZoom(), forceZoomChange, forceCenterChange); } else { //Util.printDebugMessage(" ...map center doesn't change...skip panning..."); } } /** * dragScreenDelta(int dx, int dy) * * @param dx * @param dy */ public void drag(int dx, int dy) { //Util.printDebugMessage("@...Map.drag() is called..."); Pixel centerPx = this.getViewPortPxFromCoordinate(this.getCenter()); Pixel newCenterPx = centerPx.add(-dx, -dy); Coordinate newCenter = this.getCoordinateFromViewPortPx(newCenterPx); //Util.printDebugMessage(" ...old screen center: " + centerPx.toString() + "...new screen center: " + newCenterPx.toString() + "..."); //Util.printDebugMessage(" ...old map center: " + this.getCenter().toString() + "...new map center: " + newCenter.toString() + "..."); // update map center this.center = new Coordinate(newCenter); if(this.baseLayer != null) { this.baseLayer.drag(dx, dy); //this.baseLayer.postInvalidate(); } for(int i=0; i<this.layers.size(); i++) { Layer layer = (Layer)this.layers.get(i); if(layer.isBaseLayer() == false) { boolean moveLayer = false; // the inRange property has changed. If the layer is // no longer in range, we turn it off right away. If // the layer is no longer out of range, the moveTo // call below will turn on the layer. boolean inRange = layer.calculateInRange(); if(layer.isInRange() != inRange) { layer.setInRange(inRange); moveLayer = true; Event event = new Event(MapEvents.LAYER_CHANGED); event.properties.put("data", layer); event.properties.put("incident", "visibility"); this.events.triggerEvent(MapEvents.LAYER_CHANGED, event); } else { moveLayer = (layer.isVisible() && layer.isInRange()); } if(moveLayer) { layer.drag(dx, dy); /* Event event = new Event(LayerEvents.MOVE_END); event.properties.put("data", layer); event.properties.put("incident", "zoom"); layer.events.triggerEvent(LayerEvents.MOVE_END, event); */ //Util.printDebugMessage("...postInvalidate overlay because of recentering map..."); //layer.postInvalidate(); //layer.invalidate(); } } } } /** * API Method: panTo * Allows user to pan to a new lonlat * If the new lonlat is in the current extent the map will slide smoothly * * @param lonlat */ public void panTo(Coordinate lonlat) { //TODO: to be implemented } /** * APIMethod: setCenter * * @param lonlat * @param zoom * @param dragging * @param forceZoomChange */ public void setCenter(Coordinate center, int zoom, boolean forceZoomChange, boolean forceCenterChange) { //Util.printDebugMessage("@...Map.setCenter() is called..."); if(this.center == null && !this.isValidCoordinate(center)) { center = this.maxExtent.centre(); // if invalid new center, reuse current center } // if map is restricted to certain extent, then map can not be re-center there // TODO: deal with restrictedExtent // check if zoom level has been changed boolean zoomChanged = forceZoomChange || ((this.isValidZoomLevel(zoom))&&(zoom!=this.zoom)); // check if map center has been changed boolean centerChanged = (this.isValidCoordinate(center))&&(!center.equals(this.center)); if(forceCenterChange == true) { centerChanged = true; } //Util.printDebugMessage(" ...is zoom changed: " + zoomChanged + "..."); //Util.printDebugMessage(" ...is center changed: " + centerChanged + "..."); if(zoomChanged || centerChanged) { //if(dragging == false) { this.events.triggerEvent(MapEvents.MOVE_START, new Event(MapEvents.MOVE_START, null)); //} if(centerChanged == true) { // never change this.layerContainerCanvasLeft and this.layerContainerCanvasTop // always draw layers from (0, 0) of parent android.View // never move the original point where layers are drawn in parent android.View this.center = new Coordinate(center); //Util.printDebugMessage(" ...center changed and new center is: " + this.center.toString() + "..."); } if((zoomChanged) || (this.layerContainerOrigin == null)) { this.layerContainerOrigin = new Coordinate(this.center); } if(zoomChanged) { this.zoom = zoom; this.resolution = this.getResolution(); //Util.printDebugMessage(" ...zoom changed and new zoom is: " + this.zoom + "..."); //Util.printDebugMessage(" ...resolution changed and new resolution is: " + this.resolution + "..."); } Envelope bounds = this.getExtent(); if(this.baseLayer != null) { //Util.printDebugMessage(" ...move baselayer to bounds: " + bounds.toString() + "..."); this.baseLayer.cancelLoadingThreads(); this.baseLayer.moveTo(bounds, zoomChanged, centerChanged); //Util.printDebugMessage("...postInvalidate baselayer because of recentering map..."); //this.baseLayer.postInvalidate(); // should I call postInvalidate() instead of invalidate()? this.baseLayer.invalidate(); } for(int i=0; i<this.layers.size(); i++) { Layer layer = (Layer)this.layers.get(i); if(layer.isBaseLayer() == false) { boolean moveLayer = false; // the inRange property has changed. If the layer is // no longer in range, we turn it off right away. If // the layer is no longer out of range, the moveTo // call below will turn on the layer. boolean inRange = layer.calculateInRange(); if(layer.isInRange() != inRange) { layer.setInRange(inRange); moveLayer = true; Event event = new Event(MapEvents.LAYER_CHANGED); event.properties.put("data", layer); event.properties.put("incident", "visibility"); this.events.triggerEvent(MapEvents.LAYER_CHANGED, event); } else { moveLayer = (layer.isVisible() && layer.isInRange()); } if(moveLayer) { //Util.printDebugMessage(" ...move layer to bounds: " + bounds.toString() + "..."); layer.cancelLoadingThreads(); layer.moveTo(bounds, zoomChanged, centerChanged); Event event = new Event(LayerEvents.MOVE_END); event.properties.put("data", layer); event.properties.put("incident", "zoom"); layer.events.triggerEvent(LayerEvents.MOVE_END, event); //Util.printDebugMessage("...postInvalidate overlay because of recentering map..."); //layer.postInvalidate(); layer.invalidate(); } } } if(zoomChanged) { //this.events.triggerEvent(MapEvents.ZOOM_END, new Event(MapEvents.ZOOM_END, null)); } } //this.events.triggerEvent(MapEvents.MOVE_END, new Event(MapEvents.MOVE_END, null)); } /** * API Method: setCenter * * @param lonlat * @param zoom * @param forceZoomChanged */ public void setCenter(Coordinate center, int zoom, boolean forceZoomChanged) { this.setCenter(center, zoom, forceZoomChanged, false); } /** * API Method: setCenter * * @param lonlat * @param zoom */ public void setCenter(Coordinate center, int zoom) { this.setCenter(center, zoom, false, false); } /** * API Method: setCenter * * @param lonlat */ public void setCenter(Coordinate center) { this.setCenter(center, this.getZoom(), false, false); } /** * createDrawSignature() * @return */ public String createDrawSignature() { int zoom = this.getZoom(); Coordinate center = this.getCenter(); //long time = System.currentTimeMillis(); final String signature = String.valueOf(zoom) + "_" + String.valueOf(center.x) + "_" + String.valueOf(center.y) + "_" + String.valueOf(center.z); //+ String.valueOf(time); return signature; } /** * private method: centerLayerContainer * * @param lonlat */ /* private void centerLayerContainer(LonLat lonlat) { Pixel originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin); Pixel newPx = this.getViewPortPxFromLonLat(lonlat); //Util.printDebugMessage(" ...old layer container: " + this.layerContainerCanvasLeft + "," + this.layerContainerCanvasTop + "..."); if((originPx != null) && (newPx != null)) { this.layerContainerOrigin = lonlat.clone(); this.layerContainerCanvasLeft = Math.round(originPx.getX() - newPx.getX()); this.layerContainerCanvasTop = Math.round(originPx.getY() - newPx.getY()); } //Util.printDebugMessage(" ...new layer container: " + this.layerContainerCanvasLeft + "," + this.layerContainerCanvasTop + "..."); } */ /** * API Method: isValidLonLat * * @param lonlat * @return * whether the lonlat is within the max extent of the map */ public boolean isValidCoordinate(Coordinate coord) { boolean valid = false; if(coord != null) { Envelope maxExtent = this.maxExtent; valid = maxExtent.contains(coord); } return valid; } /** * API Method: isValidZoomLevel * * @param zoomLevel * @return * whether the zoom is between the max and min zoom level */ public boolean isValidZoomLevel(int zoomLevel) { return ((zoomLevel >= 0) && (zoomLevel < this.numZoomLevels)); } // =========================================================== // Zooming operations // =========================================================== /** * API Method: zoomTo * * @param zoom */ public void zoomTo(int zoom) { if(this.isValidZoomLevel(zoom)) { this.setCenter(null, zoom); } } /** * API Method: zoomIn * */ public void zoomIn() { //this.zoomTo(this.getZoom() + 1); float dr = 2f; if(dr != 1) { ScaleAnimation animation = new ScaleAnimation(1f, dr, 1f, dr, getWidth()/2, getHeight()/2); animation.setDuration(500); animation.setAnimationListener( new AnimationListener() { public void onAnimationStart(Animation animation) {} public void onAnimationRepeat(Animation animation) {} public void onAnimationEnd(Animation animation) { zoomTo(getZoom() + 1); } }); startAnimation(animation); } } /** * API Method: zoomOut */ public void zoomOut() { //this.zoomTo(this.getZoom() - 1); float dr = 0.5f; if(dr != 1) { ScaleAnimation animation = new ScaleAnimation(1f, dr, 1f, dr, getWidth()/2, getHeight()/2); animation.setDuration(500); animation.setAnimationListener( new AnimationListener() { public void onAnimationStart(Animation animation) {} public void onAnimationRepeat(Animation animation) {} public void onAnimationEnd(Animation animation) { zoomTo(getZoom() - 1); } }); startAnimation(animation); } } /** * API Method: zoomToExtent * * @param bounds */ public void zoomToExtent(Envelope bounds) { this.setCenter(bounds.centre(), this.getZoomForExtent(bounds)); } /** * API Method: zoomToExtent */ public void zoomToMaxExtent() { this.zoomToExtent(this.maxExtent); } /** * API Method: zoomToScale * * @param scale */ public void zoomToScale(double scale) { double res = Util.getResolutionFromScale(scale, this.baseLayer.getUnits()); double w_deg = this.size.getWidth() * res; double h_deg = this.size.getHeight() * res; Coordinate center = this.center; Envelope extent = new Envelope( center.x - w_deg / 2, center.x + w_deg / 2, center.y - h_deg / 2, center.y + h_deg / 2); this.zoomToExtent(extent); } /** * API Method: getUnits * * @return * units of the map */ public String getUnits() { String units = null; if(this.baseLayer != null) { units = this.baseLayer.getUnits(); } else { units = this.units; } return units; } /** * API Method: getScale * * @return * calculate scale based on current resolution */ public double getScale() { double scale = -1; if (this.baseLayer != null) { double res = this.getResolution(); String units = this.baseLayer.getUnits(); scale = Util.getScaleFromResolution(res, units); } else { double res = this.getResolution(); String units = getUnits(); scale = Util.getScaleFromResolution(res, units); } return scale; } // =========================================================== // Convert between viewPort pixel and map coordinates // =========================================================== /** * API Method: getLonLatFromViewPortPx * Returns a map location from given a screen location. * * @param viewPortPx * @return */ public Coordinate getCoordinateFromViewPortPx(Pixel viewPortPx) { // implementation of this.baseLayer /* Coordinate coord = null; if(viewPortPx != null) { Size size = this.map.getSize(); Coordinate center = this.map.getCenter(); if(center != null) { double res = this.map.getResolution(); double dx = viewPortPx.getX() - (size.getWidth()/2); double dy = viewPortPx.getY() - (size.getHeight()/2); coord = new Coordinate(center.x+dx*res, center.y-dy*res); } } if(this.wrapDateLine == true) { // TODO: deal with wrapDateLine } return coord; */ Coordinate coord = null; if(this.baseLayer != null) { coord = this.baseLayer.getCoordinateFromViewPortPx(viewPortPx); } else { if(viewPortPx != null) { Size size = this.size; Coordinate center = this.center; if(center != null) { double res = this.getResolution(); double dx = viewPortPx.getX() - (size.getWidth()/2); double dy = viewPortPx.getY() - (size.getHeight()/2); coord = new Coordinate(center.x+dx*res, center.y-dy*res); } } return coord; } return coord; } /** * API Method: getViewPortPxFromLonLat * Returns a pixel location given a map location. This method will return * fractional pixel values. * * @param lonlat * @return */ public Pixel getViewPortPxFromCoordinate(Coordinate coord) { // implementation of this.baseLayer /* Pixel pixel = null; if(lonlat != null) { double res = this.resolution; Bounds extent = this.getExtent(); pixel = new Pixel( (1/res * (lonlat.getLon() - extent.getLeft())), (1/res * (extent.getTop() - lonlat.getLat())) ); } return pixel; */ Pixel px = null; if (this.baseLayer != null) { px = this.baseLayer.getViewPortPxFromCoordinate(coord); } return px; } /** * API Method: getLonLatFromPixel * * @param pixel * @return */ public Coordinate getCoordinateFromPixel(Pixel pixel) { return this.getCoordinateFromViewPortPx(pixel); } /** * API Method: getPixelFromLonLat * Returns a pixel location given a map location. The map location is * translated to an integer pixel location (in viewport pixel * coordinates) by the current base layer. * * @param lonlat * @return */ public Pixel getPixelFromCoordinate(Coordinate coord) { Pixel pixel = this.getViewPortPxFromCoordinate(coord); pixel.setX(Math.round(pixel.getX())); pixel.setY(Math.round(pixel.getY())); return pixel; } /** * API Method: getViewPortPxFromLayerPx * * @param layerPx * @return */ public Pixel getViewPortPxFromLayerPx(Pixel layerPx) { // this.layerContainerCanvasLeft and this.layerContainerCanvasTop are always set to 0.0 // so viewPortPx is always the same as layerPx Pixel viewPortPx = null; if(layerPx != null) { int dX = (int)(this.getLayerContainerCanvasLeft()); int dY = (int)(this.getLayerContainerCanvasTop()); viewPortPx = layerPx.add(dX, dY); } return viewPortPx; } /** * API Method: getLayerPxFromViewPortPx * * @param viewPortPx * @return */ public Pixel getLayerPxFromViewPortPx(Pixel viewPortPx) { // this.layerContainerCanvasLeft and this.layerContainerCanvasTop are always set to 0.0 // so viewPortPx is always the same as layerPx Pixel layerPx = null; if(viewPortPx != null) { int dX = -(int)(this.getLayerContainerCanvasLeft()); int dY = -(int)(this.getLayerContainerCanvasTop()); layerPx = viewPortPx.add(dX, dY); } return layerPx; } /** * API Method: getLonLatFromLayerPx * * @param pixel * @return */ public Coordinate getCoordinateFromLayerPx(Pixel pixel) { pixel = this.getViewPortPxFromLayerPx(pixel); return this.getCoordinateFromViewPortPx(pixel); } /** * API Method: getLayerPxFromLonLat * * @param lonlat * @return */ public Pixel getLayerPxFromCoordinate(Coordinate coord) { Pixel px = this.getViewPortPxFromCoordinate(coord); return this.getLayerPxFromViewPortPx(px); } // =========================================================== // Popups and Markers // =========================================================== public void removePopup() {}; // =========================================================== // UI events handling // =========================================================== private int touchXPrevious = 0; private int touchYPrevious = 0; /** * onTouchEvent(final MotionEvent event) */ @Override public boolean onTouchEvent(final MotionEvent event) { // TODO: call OnTouchEvent() on all overlays //this.mapViewGestureDetector.onTouchEvent(event); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: this.touchXPrevious = (int)event.getX(); this.touchYPrevious = (int)event.getY(); //Util.printDebugMessage("...touch down..."); return true; case MotionEvent.ACTION_MOVE: int dragDx = (int)event.getX() - this.touchXPrevious; int dragDy = (int)event.getY() - this.touchYPrevious; drag(dragDx, dragDy); this.touchXPrevious = this.touchXPrevious + dragDx; this.touchYPrevious = this.touchYPrevious + dragDy; return true; case MotionEvent.ACTION_UP: setCenter(getCenter(), getZoom(), false, true); this.touchXPrevious = 0; this.touchYPrevious = 0; /* Layer overlay = null; for(int i=0; i<getChildCount(); i++) { if((getChildAt(i) instanceof Layer)) { Layer layer = (Layer)getChildAt(i); if(layer.isBaseLayer() == false) { overlay = (Layer)this.getChildAt(i); break; } } } //Util.printDebugMessage("...change layer order..."); this.setLayerZIndex(overlay, getChildCount()-1); */ return true; } return super.onTouchEvent(event); } /** * */ @Override public boolean onTrackballEvent(MotionEvent event) { // TODO: to be implemented return super.onTrackballEvent(event); } // =========================================================== // Private classes // =========================================================== private class MapEventsHandler extends Handler { @Override public void handleMessage(final Message msg) { switch(msg.what) { case MapEvents.BASELAYER_CHANGED: //Util.printDebugMessage(" ...MapEventsHandler: base layer changed..."); break; case MapEvents.LAYER_ADDED: //Util.printDebugMessage(" ...MapEventsHandler: layer added..."); break; case MapEvents.LAYER_CHANGED: //Util.printDebugMessage(" ...MapEventsHandler: layer changed..."); break; case MapEvents.LAYER_REMOVED: //Util.printDebugMessage(" ...MapEventsHandler: layer moved..."); break; case MapEvents.MOVE: //Util.printDebugMessage(" ...MapEventsHandler: move..."); break; case MapEvents.MOVE_START: //Util.printDebugMessage(" ...MapEventsHandler: move start..."); break; case MapEvents.MOVE_END: //Util.printDebugMessage(" ...MapEventsHandler: move end..."); break; case MapEvents.ZOOM_END: //Util.printDebugMessage(" ...MapEventsHandler: zoom end..."); break; } } } /** * Class MapViewGestureListener * */ private class MapViewGestureListener implements OnGestureListener { @Override public boolean onDown(MotionEvent e) { //Util.printDebugMessage("...MapViewGestureListener.onDown() is called..."); return false; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { //Util.printDebugMessage("...MapViewGestureListener.onFling() is called..."); return false; } @Override public void onLongPress(MotionEvent e) { Util.printDebugMessage("...MapViewGestureListener.onLongPress() is called..."); Util.printDebugMessage("...long press to center the map..."); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { //Util.printDebugMessage("...MapViewGestureListener.onScroll() is called..."); return false; } @Override public void onShowPress(MotionEvent e) { //Util.printDebugMessage("...MapViewGestureListener.onShowPress() is called..."); } @Override public boolean onSingleTapUp(MotionEvent e) { //Util.printDebugMessage("...MapViewGestureListener.onSingleTapUp() is called..."); return false; } } /** * Class MapViewDoubleTapListener */ private class MapViewDoubleTapListener implements OnDoubleTapListener { @Override public boolean onDoubleTap(MotionEvent arg0) { Util.printDebugMessage("...MapViewDoubleTapListener.onDoubleTap() is called..."); return false; } @Override public boolean onDoubleTapEvent(MotionEvent e) { Util.printDebugMessage("...MapViewDoubleTapListener.onDoubleTapEvent() is called..."); return false; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { //Util.printDebugMessage("...MapViewDoubleTapListener.onSingleTapConfirmed() is called..."); return false; } } // =========================================================== // Getters and Setters // =========================================================== public String getName() { return name; } public void setName(String name) { this.name = name; } public void setSchema(MapSchema newSchema) { if(newSchema != null) { this.schema = newSchema; } applyMapSchema(this.schema); // TODO: re-center map with force-zoom-change so that it redraws //setCenter(this.center); } /** * @return the tileSchema */ public MapSchema getMapSchema() { return schema; } /** * @return the numZoomLevels */ public int getNumZoomLevels() { return numZoomLevels; } /** * @return the maxExtent */ public Envelope getMaxExtent() { return maxExtent; } /** * @return the minExtent */ public Envelope getMinExtent() { return minExtent; } /** * @return the maxResolution */ public double getMaxResolution() { return maxResolution; } /** * @return the minResolution */ public double getMinResolution() { return minResolution; } /** * @return the projection */ public String getProjection() { return projection; } /** * @param projection the projection to set */ public void setProjection(String projection) { this.projection = projection; } /** * @return the scales */ public double[] getScales() { return scales; } /** * @return the resolutions */ public double[] getResolutions() { return resolutions; } /** * @return the maxZoomLevel */ public int getMaxZoomLevel() { return maxZoomLevel; } /** * @return the minZoomLevel */ public int getMinZoomLevel() { return minZoomLevel; } /** * @return the maxScale */ public double getMaxScale() { return maxScale; } /** * @return the minScale */ public double getMinScale() { return minScale; } /** * @return the fractionalZoom */ public boolean isFractionalZoom() { return fractionalZoom; } /** * @param fractionalZoom the fractionalZoom to set */ /* public void setFractionalZoom(boolean fractionalZoom) { this.fractionalZoom = fractionalZoom; } */ /** * @return the layerContainerCanvasLeft */ public double getLayerContainerCanvasLeft() { return layerContainerCanvasLeft; } /** * @return the layerContainerCanvasTop */ public double getLayerContainerCanvasTop() { return layerContainerCanvasTop; } /** * @return the baseLayer */ public Layer getBaseLayer() { return baseLayer; } /** * * @return */ public MapEvents getEvents() { return events; } /** * * @param events */ public void setEvents(MapEvents events) { this.events = events; } } // ===================================================================================================== // test code // ===================================================================================================== /* // add layers and remove layer if(this.layers.size() <= 2) { Layer mapnik_overlay = new Mapnik(this.getContext()); mapnik_overlay.setName("mapnik_overlay"); mapnik_overlay.setBaseLayer(false); mapnik_overlay.getPaint().setAlpha((int)(0.64*255)); addLayer(mapnik_overlay); } else { Layer overlay = this.layers.get(2); removeLayer(overlay, false); } // test reorder layer Layer overlay = null; for(int i=0; i<getChildCount(); i++) { if((getChildAt(i) instanceof Layer)) { Layer layer = (Layer)getChildAt(i); if(layer.isBaseLayer() == false) { overlay = (Layer)this.getChildAt(i); break; } } } // zoom controls is at getChildCount()-1, so always below zoom controls view Util.printDebugMessage("...change layer order..."); this.setLayerZIndex(overlay, getChildCount()-1); */