package com.androidol.layer; import java.util.ArrayList; import java.util.HashMap; import android.content.Context; import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import com.androidol.Map; import com.androidol.events.Event; import com.androidol.events.LayerEvents; import com.androidol.protocol.Protocol; import com.androidol.renderer.CanvasRenderer; import com.androidol.renderer.Renderer; import com.androidol.strategy.Strategy; import com.androidol.style.Style; import com.androidol.style.StyleMap; //import com.esri.android.openlayers.util.Util; import com.vividsolutions.jts.geom.Envelope; public class Vector extends Layer { // =========================================================== // Fields // =========================================================== protected boolean isBaseLayer = false; protected boolean isFixed = false; public final boolean isVector = true; protected ArrayList<com.androidol.feature.Vector> features = null; protected ArrayList<com.androidol.feature.Vector> selectedFeatures = null; protected Style style = null; protected StyleMap styleMap = null; protected ArrayList<Strategy> strategies = null; protected Protocol protocol = null; protected Renderer renderer = null; protected String rendererClass = ""; protected String geometryType = ""; protected boolean drawn = false; public Vector(Context context) { super(context); // TODO: if options.get("style") is null give a default style this.style = new Style(); // TODO: initialize the styleMap this.features = new ArrayList<com.androidol.feature.Vector>(); this.selectedFeatures = new ArrayList<com.androidol.feature.Vector>(); if(this.strategies != null){ for(int i=0, len=this.strategies.size(); i<len; i++) { this.strategies.get(i).setLayer(this); } } else { this.strategies = new ArrayList<Strategy>(); } // register default event callback handler this.events.registerAll(new DefaultFeaturesAddedCallback()); } public Vector(Context context, AttributeSet attrs) { super(context, attrs); // TODO: if options.get("style") is null give a default style this.style = new Style(); // TODO: initialize the styleMap this.features = new ArrayList<com.androidol.feature.Vector>(); this.selectedFeatures = new ArrayList<com.androidol.feature.Vector>(); if(this.strategies != null){ for(int i=0, len=this.strategies.size(); i<len; i++) { this.strategies.get(i).setLayer(this); } } else { this.strategies = new ArrayList<Strategy>(); } // register default event callback handler this.events.registerAll(new DefaultFeaturesAddedCallback()); } /** * Old Vector constructor * * @param name * @param options */ /* public Vector(String name, HashMap<String, Object> options) { super(name, options); // TODO: if options.get("style") is null give a default style this.style = new Style(); // TODO: initialize the styleMap this.features = new ArrayList<com.androidol.feature.Vector>(); this.selectedFeatures = new ArrayList<com.androidol.feature.Vector>(); if(this.strategies != null){ for(int i=0, len=this.strategies.size(); i<len; i++) { this.strategies.get(i).setLayer(this); } } else { this.strategies = new ArrayList<Strategy>(); } // register default event callback handler this.events.registerAll(new DefaultFeaturesAddedCallback()); } */ /** * * @param name */ /* public Vector(String name) { this(name, null); } */ /** * API Method: setStrategies * * @param strategies */ public void setStrategies(ArrayList<Strategy> strategies) { if(strategies!=null && strategies.size()>0) { this.strategies = strategies; for(int i=0, len=this.strategies.size(); i<len; i++) { this.strategies.get(i).setLayer(this); if(this.strategies.get(i).isAutoActivate() == true) { this.strategies.get(i).activate(); } if(this.protocol != null) { this.protocol.getEvents().registerAll(this.strategies.get(i).getProtocolHandler()); } } } } /** * @param protocol the protocol to set */ public void setProtocol(Protocol protocol) { this.protocol = protocol; if(this.strategies != null) { Strategy strategy; int i, len; for(i=0, len=this.strategies.size(); i<len; i++) { strategy = this.strategies.get(i); if(strategy.getProtocolHandler() != null) { this.protocol.getEvents().registerAll(strategy.getProtocolHandler()); } } } } /** * API Method: addStrategy * @param strategy */ public void addStrategy(Strategy strategy) { if(this.strategies == null) { this.strategies = new ArrayList<Strategy>(); } strategy.setLayer(this); this.strategies.add(strategy); if(strategy.getProtocolHandler() != null) { this.protocol.getEvents().registerAll(strategy.getProtocolHandler()); } if(strategy.isAutoActivate() == true) { strategy.activate(); } } /** * API Method: removeStrategy * @param strategy */ public void removeStrategy(Strategy strategy) { // TODO: remove strategy from this.strategies // TODO: unregister protocol handler of strategy from protocol.events } /** * * @param map */ private void assignRenderer(Map map) { // TODO: instantiate different renderer based on this.rendererClass this.renderer = new CanvasRenderer(this.canvas); } /** * API Method: destroy * Destroy this layer */ public void destroy() { if(this.strategies != null) { Strategy strategy; int i, len; for(i=0, len=this.strategies.size(); i<len; i++) { strategy = this.strategies.get(i); if(strategy.isAutoDestroy()) { strategy.destroy(); } } this.strategies = null; } if(this.protocol != null) { if(this.protocol.isAutoDestroy()) { this.protocol.destroy(); } this.protocol = null; } this.destroyFeatures(null, true); this.features = null; this.selectedFeatures = null; if(this.renderer != null) { this.renderer.destroy(); } this.renderer = null; this.geometryType = null; this.drawn = false; super.destroy(true); // destroy and reset baselayer } /** * API Method: refresh * Ask the layer to request features again and redraw them. Triggers * the refresh event if the layer is in range and visible. * * @param obj - {Object} Optional object with properties for any listener of * the refresh event. */ public void refresh() { if(this.inRange && this.visible==true) { // TODO: trigger "refresh" event //this.events.triggerEvent("refresh", obj); } } /** * API Method: setMap * The layer has been added to the map. * * If there is no renderer set, the layer can't be used. Remove it. * Otherwise, give the renderer a reference to the map and set its size. * * @param map - {Map} */ @Override public void setMap(Map map) { super.setMap(map); if(this.renderer == null) { assignRenderer(this.map); } this.renderer.setMap(this.map); this.renderer.setSize(this.map.getSize()); if(this.strategies != null) { Strategy strategy; int i, len; for(i=0, len=this.strategies.size(); i<len; i++) { strategy = this.strategies.get(i); if(strategy.isAutoActivate() == true) { strategy.activate(); } } } } /** * API Method: removeMap * The layer has been removed from the map. * * @param map - {Map} */ public void removeMap(Map map) { if(this.strategies != null) { Strategy strategy; int i, len; for(i=0, len=this.strategies.size(); i<len; i++) { strategy = this.strategies.get(i); if(strategy.isAutoActivate()) { strategy.deactivate(); } } } } /** * API Method: onMapResize * Notify the renderer of the change in size. * */ @Override public void onMapResize() { super.onMapResize(); /* * OpenScales erase and redraw each feature in the layer * Should I do it? */ this.renderer.setSize(this.map.getSize()); } /** * API Method: moveTo * Reset the vector layer's div so that it once again is lined up with * the map. Notify the renderer of the change of extent, and in the * case of a change of zoom level (resolution), have the * renderer redraw features. * * If the layer has not yet been drawn, cycle through the layer's * features and draw each one. * * @param bounds - {Bounds} * @param zoomChanged - {boolean} * dragging - {boolean} */ @Override public void moveTo(Envelope bounds, boolean zoomChanged, boolean centerChanged) { super.moveTo(bounds, zoomChanged, centerChanged); //Util.printDebugMessage("@...moveTo() of vector layer is called..."); Envelope extent = this.map.getExtent(); this.renderer.setExtent(extent, zoomChanged); if(this.drawn == false || zoomChanged) { //Util.printDebugMessage(" ...draw each feature in vector layer..."); this.drawn = true; com.androidol.feature.Vector feature; for(int i=0, len=this.features.size(); i<len; i++) { feature = this.features.get(i); this.drawFeature(feature); } } } /** * API Method: addFeatures * Add Features to the layer. * * @param features - {ArrayList<com.androidol.feature.Vector>} * @param slient - {boolean} */ public void addFeatures(ArrayList<com.androidol.feature.Vector> features, boolean slient) { if(slient == false) { // TODO: trigger 'before features added' event } for(int i=0, len=features.size(); i<len; i++) { com.androidol.feature.Vector feature = features.get(i); if(this.geometryType!=null && feature.getGeometry().getClass().getName().equalsIgnoreCase(this.geometryType)==true) { // TODO: this type of geometry is banned, so throw exception } this.features.add(feature); feature.setLayer(this); if((feature.getStyle()!=null) && (this.style!=null)) { // TODO: merge feature.style with this.style? // TODO: or over write one with another? } this.preFeatureInsert(feature); if(this.drawn) { this.drawFeature(feature); } if(slient == false) { // TODO: trigger 'before feature added' event this.onFeatureInsert(feature); } } if(slient == false) { // TODO: trigger 'features added' event Event event = new Event(); event.properties.put("type", LayerEvents.FEATURES_ADDED); event.properties.put("data", features); this.events.triggerEvent(LayerEvents.FEATURES_ADDED, event); } } /** * API Method: removeFeatures * * @param features - {Array(Vector>)} * @param options - {Object} */ public void removeFeatures(ArrayList<com.androidol.feature.Vector> features, boolean slient) { if(features==null || features.size() == 0) { return; } for(int i=features.size()-1; i>=0; i--) { // We remain locked so long as we're not at 0 // and the 'next' feature has a geometry. We do the geometry check // because if all the features after the current one are 'null', we // won't call eraseGeometry, so we break the 'renderer functions // will always be called with locked=false *last*' rule. The end result // is a possible gratiutious unlocking to save a loop through the rest // of the list checking the remaining features every time. So long as // null geoms are rare, this is probably okay. com.androidol.feature.Vector feature = features.get(i); // TODO: ? this.unrenderedFeatures.remove(feature); if(slient == false) { // TODO: trigger 'before feature removed' event } this.features.remove(feature); // feature has no layer at this point feature.setLayer(null); if(feature.getGeometry() != null) { this.renderer.eraseGeometry(feature.getGeometry()); } //in the case that this feature is one of the selected features, // remove it from that array as well. this.selectedFeatures.remove(feature); if(slient == false) { // TODO: TODO: trigger 'feature removed' event } } if(slient == false) { // TODO: TODO: trigger 'features removed' event } } /** * API Method: destroyFeatures * Erase and destroy features on the layer. * * @param features - {Array(<OpenLayers.Feature.Vector>)} An optional array of * features to destroy. If not supplied, all features on the layer * will be destroyed. * options - {Object} */ public void destroyFeatures(ArrayList<com.androidol.feature.Vector> features, boolean slient) { if(features == null) { // if 'features' not defined, destroy all features = this.features; } if(features != null) { this.removeFeatures(features, slient); for(int i=features.size()-1; i>=0; i--) { features.get(i).destroy(); } } } /** * API Method: drawFeature * Draw (or redraw) a feature on the layer. If the optional style argument * is included, this style will be used. If no style is included, the * feature's style will be used. If the feature doesn't have a style, * the layer's style will be used. * * @param feature - {<com.androidol.feature.Vector>} * @param style - {Object} Symbolizer hash or {String} renderIntent */ public void drawFeature(com.androidol.feature.Vector feature, Style style) { if(style == null) { if(feature.getStyle() != null) { style = feature.getStyle(); } else { style = this.style; } } if(style == null) { //style = this.styleMap.createSymbolizer(feature, feature.getRenderIntent()); } this.renderer.drawFeature(feature, style); } /** * */ public void drawFeature(com.androidol.feature.Vector feature) { drawFeature(feature, null); } /** * API Method: eraseFeatures * Erase features from the layer. * * @param features - {Array(<com.androidol.feature.Vector>)} */ public void eraseFeatures(ArrayList<com.androidol.feature.Vector> features) { this.renderer.eraseFeatures(features); } /** * API Method: getFeatureFromEvent * Given an event, return a feature if the event occurred over one. * Otherwise, return null. * * @param evt - {Event} * * @return {<OpenLayers.Feature.Vector>} A feature if one was under the event. */ public com.androidol.feature.Vector getFeatureFromEvent(Event evt) { String featureId = this.renderer.getFeatureIdFromEvent(evt); return this.getFeatureById(featureId); } /** * API Method: getFeatureById * Given a feature id, return the feature if it exists in the features array * * @param featureId - {String} * * @return {<OpenLayers.Feature.Vector>} A feature corresponding to the given * featureId */ public com.androidol.feature.Vector getFeatureById(String featureId) { //TBD - would it be more efficient to use a hash for this.features? com.androidol.feature.Vector feature = null; for(int i=0, len=this.features.size(); i<len; ++i) { if(this.features.get(i).getId().equalsIgnoreCase(featureId)==true) { feature = this.features.get(i); break; } } return feature; } /** * */ public void preFeatureInsert(com.androidol.feature.Vector feature) { // TODO: to be implemented by sub class } /** * */ public void onFeatureInsert(com.androidol.feature.Vector feature) { // TODO: to be implemented by sub class } /** * @return the protocol */ public Protocol getProtocol() { return protocol; } /** * API Method: getStrategies * @return */ public ArrayList<Strategy> getStrategies() { return this.strategies; } /** * * @author ying4682 * */ private class DefaultFeaturesAddedCallback extends Handler { @Override public void handleMessage(Message msg) { if(msg.what == LayerEvents.FEATURES_ADDED) { //Util.printDebugMessage(" ...LayerEvents.FEATURE_ADDED captured..."); //Util.printDebugMessage(" ...DefaultFeaturesAddedCallback call map.invalidate() to redraw map..."); Vector.this.map.invalidate(); } } } }