Package org.geomajas.gwt.client.map

Source Code of org.geomajas.gwt.client.map.MapModel$LayerSelectionPropagator

/*
* This is part of Geomajas, a GIS framework, http://www.geomajas.org/.
*
* Copyright 2008-2011 Geosparc nv, http://www.geosparc.com/, Belgium.
*
* The program is available in open source according to the GNU Affero
* General Public License. All contributions in this program are covered
* by the Geomajas Contributors License Agreement. For full licensing
* details, see LICENSE.txt in the project root.
*/

package org.geomajas.gwt.client.map;

import java.util.ArrayList;
import java.util.List;

import org.geomajas.configuration.client.ClientLayerInfo;
import org.geomajas.configuration.client.ClientMapInfo;
import org.geomajas.configuration.client.ClientRasterLayerInfo;
import org.geomajas.configuration.client.ClientVectorLayerInfo;
import org.geomajas.configuration.client.ScaleConfigurationInfo;
import org.geomajas.configuration.client.ScaleInfo;
import org.geomajas.annotation.Api;
import org.geomajas.gwt.client.gfx.Paintable;
import org.geomajas.gwt.client.gfx.PainterVisitor;
import org.geomajas.gwt.client.map.event.FeatureDeselectedEvent;
import org.geomajas.gwt.client.map.event.FeatureSelectedEvent;
import org.geomajas.gwt.client.map.event.FeatureSelectionHandler;
import org.geomajas.gwt.client.map.event.FeatureTransactionEvent;
import org.geomajas.gwt.client.map.event.FeatureTransactionHandler;
import org.geomajas.gwt.client.map.event.HasFeatureSelectionHandlers;
import org.geomajas.gwt.client.map.event.LayerDeselectedEvent;
import org.geomajas.gwt.client.map.event.LayerSelectedEvent;
import org.geomajas.gwt.client.map.event.LayerSelectionHandler;
import org.geomajas.gwt.client.map.event.MapModelEvent;
import org.geomajas.gwt.client.map.event.MapModelHandler;
import org.geomajas.gwt.client.map.event.MapViewChangedEvent;
import org.geomajas.gwt.client.map.event.MapViewChangedHandler;
import org.geomajas.gwt.client.map.feature.Feature;
import org.geomajas.gwt.client.map.feature.FeatureEditor;
import org.geomajas.gwt.client.map.feature.FeatureTransaction;
import org.geomajas.gwt.client.map.layer.Layer;
import org.geomajas.gwt.client.map.layer.RasterLayer;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.gwt.client.spatial.Bbox;
import org.geomajas.gwt.client.spatial.geometry.GeometryFactory;

import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;

/**
* <p>
* The model behind a map. This object contains all the layers related to the map. When re-rendering the entire map, it
* is actually this model that is rendered. Therefore the MapModel implements the <code>Paintable</code> interface.
* </p>
*
* @author Pieter De Graef
* @since 1.6.0
*/
@Api
public class MapModel implements Paintable, MapViewChangedHandler, HasFeatureSelectionHandlers {

  /**
   * The models ID. This is necessary mainly because of the <code>Paintable</code> interface. Still, every painted
   * object needs a unique identifier.
   */
  private String id;

  /** The map's coordinate system as an EPSG code. (i.e. lonlat = 'epsg:4326' => srid = 4326) */
  private int srid;

  /**
   * An ordered list of layers. The drawing order on the map is as follows: the first layer will be placed at the
   * bottom, the last layer on top.
   */
  private List<Layer<?>> layers = new ArrayList<Layer<?>>();

  /** Reference to the <code>MapView</code> object of the <code>MapWidget</code>. */
  private MapView mapView;

  private ClientMapInfo mapInfo;

  private FeatureEditor featureEditor;

  private HandlerManager handlerManager;

  private GeometryFactory geometryFactory;

  private boolean initialized;

  private LayerSelectionPropagator selectionPropagator = new LayerSelectionPropagator();
 
  // -------------------------------------------------------------------------
  // Constructors:
  // -------------------------------------------------------------------------

  /**
   * Initialize map model, coordinate system has to be filled in later (from configuration).
   *
   * @param id
   *            map id
   * @since 1.6.0
   */
  @Api
  public MapModel(String id) {
    this.id = id;
    featureEditor = new FeatureEditor(this);
    handlerManager = new HandlerManager(this);
    mapView = new MapView();
    mapView.addMapViewChangedHandler(this);
  }

  // -------------------------------------------------------------------------
  // MapModel event handling:
  // -------------------------------------------------------------------------

  /**
   * Adds this handler to the model.
   *
   * @param handler
   *            the handler
   * @return {@link com.google.gwt.event.shared.HandlerRegistration} used to remove the handler
   * @since 1.6.0
   */
  @Api
  public final HandlerRegistration addMapModelHandler(final MapModelHandler handler) {
    return handlerManager.addHandler(MapModelEvent.TYPE, handler);
  }

  /**
   *
   * @param handler
   *            The handler to be registered.
   * @return
   * @since 1.6.0
   */
  @Api
  public final HandlerRegistration addFeatureSelectionHandler(final FeatureSelectionHandler handler) {
    return handlerManager.addHandler(FeatureSelectionHandler.TYPE, handler);
  }

  /**
   *
   * @param handler
   *            the handler to be registered
   * @return handler registration
   * @since 1.6.0
   */
  @Api
  public HandlerRegistration addLayerSelectionHandler(final LayerSelectionHandler handler) {
    return handlerManager.addHandler(LayerSelectionHandler.TYPE, handler);
  }

  /**
   *
   * @param handler
   * @since 1.6.0
   */
  @Api
  public void removeMapModelHandler(final MapModelHandler handler) {
    handlerManager.removeHandler(MapModelEvent.TYPE, handler);
  }

  /**
   * Add a new handler for {@link FeatureTransactionEvent}s.
   *
   * @param handler
   *            the handler to be registered
   * @return
   * @since 1.7.0
   */
  @Api
  public HandlerRegistration addFeatureTransactionHandler(final FeatureTransactionHandler handler) {
    return handlerManager.addHandler(FeatureTransactionHandler.TYPE, handler);
  }

  // -------------------------------------------------------------------------
  // Implementation of the Paintable interface:
  // -------------------------------------------------------------------------

  /**
   * Paintable implementation. First let the PainterVisitor paint this object, then if recursive is true, painter the
   * layers in order.
   */
  public void accept(PainterVisitor visitor, Object group, Bbox bounds, boolean recursive) {
    // Paint the MapModel itself (see MapModelPainter):
    visitor.visit(this, group);

    // Paint the layers:
    if (recursive) {
      for (Layer<?> layer : layers) {
        if (layer.isShowing()) {
          layer.accept(visitor, group, bounds, recursive);
        } else {
          // JDM: paint the top part of the layer, if not we loose the map order
          layer.accept(visitor, group, bounds, false);
        }
      }
    }

    // Paint the editing of a feature (if a feature is being edited):
    if (featureEditor.getFeatureTransaction() != null) {
      featureEditor.getFeatureTransaction().accept(visitor, group, bounds, recursive);
    }
  }

  /**
   * Return this map model's id.
   *
   * @return id
   */
  public String getId() {
    return id;
  }

  // -------------------------------------------------------------------------
  // Implementation of the MapViewChangedHandler interface:
  // -------------------------------------------------------------------------

  /**
   * Update the visibility of the layers.
   *
   * @param event
   *            change event
   */
  public void onMapViewChanged(MapViewChangedEvent event) {
    for (Layer<?> layer : layers) {
      layer.updateShowing();

      // If the map is resized quickly after a previous resize, tile requests are sent out, but when they come
      // back, the world-to-pan matrix will have altered, and so the tiles are placed at the wrong positions....
      // so we clear the store.
      if (layer instanceof RasterLayer && event.isMapResized()) {
        ((RasterLayer) layer).getStore().clear();
      }
    }
  }

  // -------------------------------------------------------------------------
  // Public methods:
  // -------------------------------------------------------------------------

  /**
   * Initialize the MapModel object, using a configuration object acquired from the server. This will automatically
   * build the list of layers.
   *
   * @param mapInfo
   *            The configuration object.
   */
  public void initialize(final ClientMapInfo mapInfo) {
    if (!initialized) {
      this.mapInfo = mapInfo;
      srid = Integer.parseInt(mapInfo.getCrs().substring(mapInfo.getCrs().indexOf(":") + 1));
      ScaleConfigurationInfo scaleConfigurationInfo = mapInfo.getScaleConfiguration();
      List<Double> realResolutions = new ArrayList<Double>();
      for (ScaleInfo scale : scaleConfigurationInfo.getZoomLevels()) {
        realResolutions.add(1. / scale.getPixelPerUnit());
      }
      mapView.setResolutions(realResolutions);
      mapView.setMaximumScale(scaleConfigurationInfo.getMaximumScale().getPixelPerUnit());
      // replace layers by new layers
      removeAllLayers();
      for (ClientLayerInfo layerInfo : mapInfo.getLayers()) {
        addLayer(layerInfo);
      }
      Bbox maxBounds = new Bbox(mapInfo.getMaxBounds());
      Bbox initialBounds = new Bbox(mapInfo.getInitialBounds());
      // if the max bounds was not configured, take the union of initial and layer bounds
      if (maxBounds.isAll()) {
        for (ClientLayerInfo layerInfo : mapInfo.getLayers()) {
          maxBounds = (Bbox) initialBounds.clone();
          maxBounds = maxBounds.union(new Bbox(layerInfo.getMaxExtent()));
        }
      }
      mapView.setMaxBounds(maxBounds);
      mapView.applyBounds(initialBounds, MapView.ZoomOption.LEVEL_CLOSEST);
    }
    initialized = true;
    handlerManager.fireEvent(new MapModelEvent());
  }

  /**
   * Is this map model initialized yet ?
   *
   * @return true if initialized
   * @since 1.6.0
   */
  @Api
  public boolean isInitialized() {
    return initialized;
  }

  /**
   * Search a layer by it's id.
   *
   * @param layerId
   *            The layer's client ID.
   * @return Returns either a Layer, or null.
   * @since 1.6.0
   */
  @Api
  public Layer<?> getLayer(String layerId) {
    if (layers != null) {
      for (Layer<?> layer : layers) {
        if (layer.getId().equals(layerId)) {
          return layer;
        }
      }
    }
    return null;
  }

  /**
   * Get all layers with the specified server layer id.
   *
   * @param serverLayerId
   *            The layer's server layer ID.
   * @return Returns list of layers with the specified server layer id.
   */
  public List<Layer<?>> getLayersByServerId(String serverLayerId) {
    List<Layer<?>> l = new ArrayList<Layer<?>>();
    if (layers != null) {
      for (Layer<?> layer : layers) {
        if (layer.getServerLayerId().equals(serverLayerId)) {
          l.add(layer);
        }
      }
    }
    return l;
  }

  /**
   * Get all vector layers with the specified server layer id.
   *
   * @param serverLayerId
   *            The layer's server layer ID.
   * @return Returns list of layers with the specified server layer id.
   */
  public List<VectorLayer> getVectorLayersByServerId(String serverLayerId) {
    List<VectorLayer> l = new ArrayList<VectorLayer>();
    if (layers != null) {
      for (VectorLayer layer : getVectorLayers()) {
        if (layer.getServerLayerId().equals(serverLayerId)) {
          l.add(layer);
        }
      }
    }
    return l;
  }

  /**
   * Search a vector layer by it's id.
   *
   * @param layerId
   *            The layer's client ID.
   * @return Returns either a Layer, or null.
   * @since 1.6.0
   */
  @Api
  public VectorLayer getVectorLayer(String layerId) {
    if (layers != null) {
      for (VectorLayer layer : getVectorLayers()) {
        if (layer.getId().equals(layerId)) {
          return layer;
        }
      }
    }
    return null;
  }

  /**
   * Select a new layer. Only one layer can be selected at a time, so this function first tries to deselect the
   * currently selected (if there is one).
   *
   * @param layer
   *            The layer to select. If layer is null, then the currently selected layer will be deselected!
   */
  public void selectLayer(Layer<?> layer) {
    if (layer == null) {
      deselectLayer(this.getSelectedLayer());
    } else {
      Layer<?> selLayer = this.getSelectedLayer();
      if (selLayer != null && !layer.getId().equals(selLayer.getId())) {
        deselectLayer(selLayer);
      }
      layer.setSelected(true);
      handlerManager.fireEvent(new LayerSelectedEvent(layer));
    }
  }

  /** Return a list containing all vector layers within this model. */
  public List<VectorLayer> getVectorLayers() {
    ArrayList<VectorLayer> list = new ArrayList<VectorLayer>();
    for (Layer<?> layer : layers) {
      if (layer instanceof VectorLayer) {
        list.add((VectorLayer) layer);
      }
    }
    return list;
  }

  /** Clear the list of selected features in all vector layers. */
  public void clearSelectedFeatures() {
    for (VectorLayer layer : getVectorLayers()) {
      layer.clearSelectedFeatures();
    }
  }

  /** Return the total number of selected features in all vector layers. */
  public int getNrSelectedFeatures() {
    int count = 0;
    for (VectorLayer layer : getVectorLayers()) {
      count += layer.getSelectedFeatures().size();
    }
    return count;
  }
 
  /**
   * Return the selected feature if there is 1 selected feature.
   *
   * @return the selected feature or null if none or multiple features are selected
   */
  public String getSelectedFeature() {
    if (getNrSelectedFeatures() == 1) {
      for (VectorLayer layer : getVectorLayers()) {
        if (layer.getSelectedFeatures().size() > 0) {
          return layer.getSelectedFeatures().iterator().next();
        }
      }
    }
    return null;
  }

  /**
   * Searches for the selected layer, and returns it.
   *
   * @return Returns the selected layer object, or null if none is selected.
   */
  public Layer<?> getSelectedLayer() {
    if (layers != null) {
      for (Layer<?> layer : layers) {
        if (layer.isSelected()) {
          return layer;
        }
      }
    }
    return null;
  }

  /**
   * Apply a certain feature transaction onto the client side map model. This method is usually called after that same
   * feature transaction has been successfully applied on the server.
   *
   * @param ft
   *            The feature transaction to apply. It can create, update or delete features.
   */
  public void applyFeatureTransaction(FeatureTransaction ft) {
    if (ft != null) {
      VectorLayer layer = ft.getLayer();
      if (layer != null) {
        // clear all the tiles
        layer.getFeatureStore().clear();
      }
      // now update/add the features
      if (ft.getNewFeatures() != null) {
        for (Feature feature : ft.getNewFeatures()) {
          ft.getLayer().getFeatureStore().addFeature(feature);
        }
      }
      // make it fetch the tiles
      mapView.translate(0, 0);
      handlerManager.fireEvent(new FeatureTransactionEvent(ft));
    }
  }

  /**
   * Set a new position for the given layer. This will automatically redraw the map to apply this new order. Note that
   * at any time, all raster layers will always lie behind all vector layers. This means that position 0 for a vector
   * layer is the first(=back) vector layer to be drawn AFTER all raster layers have already been drawn.
   *
   * @param layer
   *            The vector layer to place at a new position.
   * @param position
   *            The new layer order position in the layer array:
   *            <ul>
   *            <li>Back = 0 (but still in front of all raster layers)</li>
   *            <li>Front = (vector layer count - 1)</li>
   *            </ul>
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveVectorLayer(VectorLayer layer, int position) {
    if (layer == null) {
      return false;
    }

    // Find attached ClientLayerInfo object:
    ClientLayerInfo layerInfo = null;
    String layerId = layer.getId();
    for (ClientLayerInfo info : mapInfo.getLayers()) {
      if (info.getId().equals(layerId)) {
        layerInfo = info;
        break;
      }
    }
    if (layerInfo == null) {
      return false;
    }

    // First remove the layer from the list:
    if (!layers.remove(layer)) {
      return false;
    }
    if (!mapInfo.getLayers().remove(layerInfo)) {
      return false;
    }

    int rasterCount = rasterLayerCount();
    position += rasterCount;
    if (position < rasterCount) {
      position = rasterCount;
    } else if (position > layers.size()) {
      position = layers.size();
    }
    try {
      layers.add(position, layer);
      mapInfo.getLayers().add(position, layerInfo);
    } catch (Exception e) {
      return false;
    }
    handlerManager.fireEvent(new MapModelEvent());
    return true;
  }

  /**
   * Set a new position for the given layer. This will automatically redraw the map to apply this new order. Note that
   * at any time, all raster layers will always lie behind all vector layers. This means that position 0 for a vector
   * layer is the first(=back) vector layer to be drawn AFTER all raster layers have already been drawn.
   *
   * @param layer
   *            The raster layer to place at a new position.
   * @param position
   *            The new layer order position in the layer array:
   *            <ul>
   *            <li>Back = 0</li>
   *            <li>Front = (raster layer count - 1); Larger numbers won't make a difference. Rasters stay behind
   *            vectors...</li>
   *            </ul>
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveRasterLayer(RasterLayer layer, int position) {
    if (layer == null) {
      return false;
    }

    // Find attached ClientLayerInfo object:
    ClientLayerInfo layerInfo = null;
    String layerId = layer.getId();
    for (ClientLayerInfo info : mapInfo.getLayers()) {
      if (info.getId().equals(layerId)) {
        layerInfo = info;
        break;
      }
    }
    if (layerInfo == null) {
      return false;
    }

    int rasterCount = rasterLayerCount();

    // First remove the layer from the list:
    if (!layers.remove(layer)) {
      return false;
    }
    if (!mapInfo.getLayers().remove(layerInfo)) {
      return false;
    }

    if (position < 0) {
      position = 0;
    } else if (position > rasterCount - 1) {
      position = rasterCount - 1;
    }
    try {
      layers.add(position, layer);
      mapInfo.getLayers().add(position, layerInfo);
    } catch (Exception e) {
      return false;
    }
    handlerManager.fireEvent(new MapModelEvent());
    return true;
  }

  /**
   * Move a vector layer up (=front) one place. Note that at any time, all raster layers will always lie behind all
   * vector layers. This means that position 0 for a vector layer is the first(=back) vector layer to be drawn AFTER
   * all raster layers have already been drawn.
   *
   * @param layer
   *            The vector layer to move more to the front.
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveVectorLayerUp(VectorLayer layer) {
    int position = getLayerPosition(layer);
    if (position < 0) {
      return false;
    }
    return moveVectorLayer(layer, position + 1);
  }

  /**
   * Move a vector layer down (=back) one place. Note that at any time, all raster layers will always lie behind all
   * vector layers. This means that position 0 for a vector layer is the first(=back) vector layer to be drawn AFTER
   * all raster layers have already been drawn.
   *
   * @param layer
   *            The vector layer to move more to the back.
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveVectorLayerDown(VectorLayer layer) {
    int position = getLayerPosition(layer);
    if (position < 0) {
      return false;
    }
    return moveVectorLayer(layer, position - 1);
  }

  /**
   * Move a raster layer up (=front) one place. Note that at any time, all raster layers will always lie behind all
   * vector layers. This means that position 0 for a vector layer is the first(=back) vector layer to be drawn AFTER
   * all raster layers have already been drawn.
   *
   * @param layer
   *            The raster layer to move more to the front.
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveRasterLayerUp(RasterLayer layer) {
    int position = getLayerPosition(layer);
    if (position < 0) {
      return false;
    }
    return moveRasterLayer(layer, position + 1);
  }

  /**
   * Move a raster layer down (=back) one place. Note that at any time, all raster layers will always lie behind all
   * vector layers. This means that position 0 for a vector layer is the first(=back) vector layer to be drawn AFTER
   * all raster layers have already been drawn.
   *
   * @param layer
   *            The raster layer to move more to the back.
   * @return Returns if the re-ordering was successful or not.
   * @since 1.8.0
   */
  public boolean moveRasterLayerDown(RasterLayer layer) {
    int position = getLayerPosition(layer);
    if (position < 0) {
      return false;
    }
    return moveRasterLayer(layer, position - 1);
  }

  /**
   * Get the position of a certain layer in this map model. Note that for both raster layers and vector layer, the
   * count starts at 0! On the map, all raster layers always lie behind all vector layers.
   *
   * @param layer
   *            The layer to return the position for.
   * @return Returns the position of the layer in the map. This position determines layer order.
   * @since 1.8.0
   */
  public int getLayerPosition(Layer<?> layer) {
    if (layer == null) {
      return -1;
    }
    String layerId = layer.getId();
    if (layer instanceof RasterLayer) {
      for (int index = 0; index < mapInfo.getLayers().size(); index++) {
        if (mapInfo.getLayers().get(index).getId().equals(layerId)) {
          return index;
        }
      }
    } else if (layer instanceof VectorLayer) {
      int rasterCount = 0;
      for (int index = 0; index < mapInfo.getLayers().size(); index++) {
        if (layers.get(index) instanceof RasterLayer) {
          rasterCount++;
        }
        if (mapInfo.getLayers().get(index).getId().equals(layerId)) {
          return index - rasterCount;
        }
      }
    }
    return 0;
  }

  // -------------------------------------------------------------------------
  // Getters:
  // -------------------------------------------------------------------------

  public List<Layer<?>> getLayers() {
    return layers;
  }

  public MapView getMapView() {
    return mapView;
  }

  public FeatureEditor getFeatureEditor() {
    return featureEditor;
  }

  public int getSrid() {
    return srid;
  }

  public String getCrs() {
    return "EPSG:" + srid;
  }

  public int getPrecision() {
    if (mapInfo != null) {
      return mapInfo.getPrecision();
    }
    return -1;
  }

  public ClientMapInfo getMapInfo() {
    return mapInfo;
  }

  /**
   * Return a factory for geometries that is suited perfectly for geometries within this model. The SRID and precision
   * will for the factory will be correct.
   */
  public GeometryFactory getGeometryFactory() {
    if (null == geometryFactory) {
      if (0 == srid) {
        throw new IllegalArgumentException("srid needs to be set on MapModel to obtain GeometryFactory");
      }
      geometryFactory = new GeometryFactory(srid, -1); // @todo precision is not yet implemented
    }
    return geometryFactory;
  }

  // -------------------------------------------------------------------------
  // Private methods:
  // -------------------------------------------------------------------------

  private void removeAllLayers() {
    layers = new ArrayList<Layer<?>>();
  }

  private void addLayer(ClientLayerInfo layerInfo) {
    switch (layerInfo.getLayerType()) {
      case RASTER:
        RasterLayer rasterLayer = new RasterLayer(this, (ClientRasterLayerInfo) layerInfo);
        layers.add(rasterLayer);
        break;
      default:
        VectorLayer vectorLayer = new VectorLayer(this, (ClientVectorLayerInfo) layerInfo);
        layers.add(vectorLayer);
        vectorLayer.addFeatureSelectionHandler(selectionPropagator);
        break;
    }
  }

  /**
   * Deselect the currently selected layer, includes sending the deselect events.
   *
   * @param layer
   *            layer to clear
   */
  private void deselectLayer(Layer<?> layer) {
    if (layer != null) {
      layer.setSelected(false);
      handlerManager.fireEvent(new LayerDeselectedEvent(layer));
    }
  }

  /** Count the total number of raster layers in this model. */
  private int rasterLayerCount() {
    int rasterLayerCount = 0;
    for (int index = 0; index < mapInfo.getLayers().size(); index++) {
      if (layers.get(index) instanceof RasterLayer) {
        rasterLayerCount++;
      }
    }
    return rasterLayerCount;
  }

  // -------------------------------------------------------------------------
  // Private classes:
  // -------------------------------------------------------------------------

  /**
   * Propagates layer selection events to interested listeners.
   *
   * @author Jan De Moerloose
   *
   */
  private class LayerSelectionPropagator implements FeatureSelectionHandler {

    public void onFeatureDeselected(FeatureDeselectedEvent event) {
      handlerManager.fireEvent(event);
    }

    public void onFeatureSelected(FeatureSelectedEvent event) {
      handlerManager.fireEvent(event);
    }
  }
}
TOP

Related Classes of org.geomajas.gwt.client.map.MapModel$LayerSelectionPropagator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.