Package org.geomajas.gwt.client.spatial.snapping

Source Code of org.geomajas.gwt.client.spatial.snapping.Snapper

/*
* 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.spatial.snapping;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.geomajas.command.CommandResponse;
import org.geomajas.command.dto.SearchByLocationRequest;
import org.geomajas.command.dto.SearchByLocationResponse;
import org.geomajas.configuration.SnappingRuleInfo;
import org.geomajas.configuration.SnappingRuleInfo.SnappingType;
import org.geomajas.geometry.Coordinate;
import org.geomajas.global.GeomajasConstant;
import org.geomajas.gwt.client.command.CommandCallback;
import org.geomajas.gwt.client.command.GwtCommand;
import org.geomajas.gwt.client.command.GwtCommandDispatcher;
import org.geomajas.gwt.client.map.MapModel;
import org.geomajas.gwt.client.map.feature.Feature;
import org.geomajas.gwt.client.map.layer.VectorLayer;
import org.geomajas.gwt.client.spatial.Bbox;
import org.geomajas.gwt.client.spatial.geometry.Polygon;
import org.geomajas.gwt.client.util.GeometryConverter;
import org.geomajas.layer.LayerType;

/**
* <p>
* This is the main handler for snapping to coordinates. It supports different
* modes of operating and different algorithms for the actual snapping. The
* different algorithms to use are defined in the vector layer configurations
* objects, while the modes are defined by the {@link SnappingMode} class.
* </p>
* <p>
* All you have to do to make use of snapping, is to make an instance of this
* class, and call the <code>snap</code> method.
* </p>
*
* @author Pieter De Graef
* @author Kristof Heirwegh
*/
public class Snapper {

  /**
   * General definition for the different snapping modes. The
   * ALL_GEOMETRIES_EQUAL will use a SnappingMode that treats all geometries
   * equally (@see EqualSnappingMode), while the
   * PRIORITY_TO_INTERSECTING_GEOMETRIES will give priority to geometries that
   * intersect the given coordinate (@see IntersectPriorityMode).
   */
  public static enum SnapMode {
    ALL_GEOMETRIES_EQUAL, PRIORITY_TO_INTERSECTING_GEOMETRIES
  };

  /**
   * The MapModel that contains the layers to which snapping is possible. If a
   * snapping rule should exist that points to a layer that does not exist
   * within this MapModel, then no snapping will occur.
   */
  private MapModel mapModel;

  /**
   * Features are cached so they do not need to be retrieved from the server
   * every time the mouse is moved.
   */
  private Map<VectorLayer, List<Feature>> featureCache;

  /**
   * The featureCache needs to be invalidated if the bounds change between
   * requests.
   */
  private Bbox currentBounds;

  /**
   * The full list of snapping rules to be used.
   */
  private List<SnappingRuleInfo> rules;

  private String[] serverLayerIds;

  private Map<String, String> layerFilters;

  /**
   * The current snapping mode to use.
   */
  private SnapMode mode;

  // -------------------------------------------------------------------------
  // Constructors:
  // -------------------------------------------------------------------------

  /**
   * Create a snapper with a certain set of rules and the given MapModel. As
   * mode, the SnapMode.ALL_GEOMETRIES_EQUAL is used by default.
   */
  public Snapper(MapModel mapModel, List<SnappingRuleInfo> rules) {
    this(mapModel, rules, SnapMode.ALL_GEOMETRIES_EQUAL);
  }

  /**
   * Create a snapper with a certain set of rules and the given MapModel,
   * thereby immediately setting the snapping mode to use.
   */
  public Snapper(MapModel mapModel, List<SnappingRuleInfo> rules, SnapMode mode) {
    this.featureCache = new HashMap<VectorLayer, List<Feature>>();
    this.mapModel = mapModel;
    this.rules = rules;
    setMode(mode);
    retrieveFeatures();
  }

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

  /**
   * Execute the actual snapping!
   *
   * @param coordinate
   *            The original coordinate that needs snapping.
   * @return Returns the given coordinate, or a snapped coordinate if one was
   *         found.
   */
  public Coordinate snap(Coordinate coordinate) {
    if (rules == null || mapModel == null) {
      return coordinate;
    }
    Coordinate snappedCoordinate = coordinate;
    double snappedDistance = Double.MAX_VALUE;

    for (int i = 0; i < rules.size(); i++) {
      SnappingRuleInfo rule = rules.get(i);

      // Check for supported snapping algorithms: TODO use factory
      if (rule.getType() != SnappingType.CLOSEST_ENDPOINT && rule.getType() != SnappingType.NEAREST_POINT) {
        throw new IllegalArgumentException("Unknown snapping rule type was found: " + rule.getType());
      }

      // Get the target snap layer:
      VectorLayer snapLayer;
      try {
        snapLayer = mapModel.getVectorLayer(rule.getLayerId());
      } catch (Exception e) {
        throw new IllegalArgumentException("Target snapping layer (" + rule.getLayerId()
            + ") was not a vector layer.");
      }

      SnapMode tempMode = this.mode;
      if (snapLayer.getLayerInfo().getLayerType() != LayerType.POLYGON
          && snapLayer.getLayerInfo().getLayerType() != LayerType.MULTIPOLYGON) {
        // For mode=MODE_PRIORITY_TO_INTERSECTING_GEOMETRIES, an area > 0 is required.
        tempMode = SnapMode.ALL_GEOMETRIES_EQUAL;
      }

      // TODO: don't create the handler every time...
      SnappingMode handler;
      if (tempMode == SnapMode.ALL_GEOMETRIES_EQUAL) {
        handler = new EqualSnappingMode(rule);
      } else {
        handler = new IntersectPriorityMode(rule);
      }

      // Calculate snapping:
      handler.setCoordinate(coordinate);
      iterateFeatures(snapLayer, handler);

      if (handler.getDistance() < snappedDistance) {
        snappedCoordinate = handler.getSnappedCoordinate();
        snappedDistance = handler.getDistance();
      }
    }
    return snappedCoordinate;
  }

  // -------------------------------------------------------------------------
  // Getters and setters:
  // -------------------------------------------------------------------------

  public SnapMode getMode() {
    return mode;
  }

  public void setMode(SnapMode mode) {
    this.mode = mode;
  }

  public List<SnappingRuleInfo> getRules() {
    return rules;
  }

  public void setRules(List<SnappingRuleInfo> rules) {
    this.rules = rules;
  }

  // ----------------------------------------------------------
  // private methods
  // ----------------------------------------------------------

  private void iterateFeatures(VectorLayer layer, SnappingMode handler) {
    if (currentBounds != null && currentBounds.equals(mapModel.getMapView().getBounds(), 0)) {
      List<Feature> feats = featureCache.get(layer);
      if (feats != null) {
        handler.execute(feats);
      }
    } else {
      retrieveFeatures();
    }
  }

  private void retrieveFeatures() {
    // setting current bounds before method returns so it isn't called
    // multiple times while waiting for result
    // (this is adequate for javascript, no real concurrency)
    currentBounds = mapModel.getMapView().getBounds();
    if (serverLayerIds == null) {
      init();
    }

    Polygon polygon = mapModel.getGeometryFactory().createPolygon(currentBounds);
    GwtCommand commandRequest = new GwtCommand(SearchByLocationRequest.COMMAND);
    SearchByLocationRequest request = new SearchByLocationRequest();
    request.setLayerIds(serverLayerIds);
    addFilters(request);
    request.setFeatureIncludes(GeomajasConstant.FEATURE_INCLUDE_GEOMETRY);
    request.setLocation(GeometryConverter.toDto(polygon));
    request.setCrs(mapModel.getCrs());
    request.setQueryType(SearchByLocationRequest.QUERY_INTERSECTS);
    request.setSearchType(SearchByLocationRequest.SEARCH_ALL_LAYERS);
    commandRequest.setCommandRequest(request);
    GwtCommandDispatcher.getInstance().execute(commandRequest, new CommandCallback() {
      public void execute(CommandResponse commandResponse) {
        if (commandResponse instanceof SearchByLocationResponse) {
          SearchByLocationResponse response = (SearchByLocationResponse) commandResponse;
          Map<String, List<org.geomajas.layer.feature.Feature>> featureMap = response.getFeatureMap();
          featureCache.clear();
          for (String serverLayerId : featureMap.keySet()) {
            VectorLayer vl = findLayer(serverLayerId);
            List<Feature> features = new ArrayList<Feature>();
            featureCache.put(vl, features);
            for (org.geomajas.layer.feature.Feature dtoFeat : featureMap.get(serverLayerId)) {
              features.add(new Feature(dtoFeat, vl));
            }
          }
        }
      }
    });
  }

  private VectorLayer findLayer(String serverLayerId) {
    List<VectorLayer> res = mapModel.getVectorLayersByServerId(serverLayerId);
    if (res.size() == 1) {
      return res.get(0);
    } else {
      for (VectorLayer vl : res) {
        for (SnappingRuleInfo sri : rules) {
          if (sri.getLayerId().equals(vl.getId())) {
            return vl; // there's no way to know if it's the correct one
          }
        }
      }
      return null; // shouldn't happen
    }
  }

  private void addFilters(SearchByLocationRequest sblr) {
    // -- a simple String[], like ids would have been easier...
    for (Entry<String, String> entry : layerFilters.entrySet()) {
      sblr.setFilter(entry.getKey(), entry.getValue());
    }
  }

  private void init() {
    List<String> layerIds = new ArrayList<String>();
    layerFilters = new HashMap<String, String>();
    for (SnappingRuleInfo sri : rules) {
      VectorLayer vl = mapModel.getVectorLayer(sri.getLayerId());
      layerIds.add(vl.getServerLayerId());
      if (vl.getFilter() != null) {
        layerFilters.put(vl.getServerLayerId(), vl.getFilter());
      }
    }
    serverLayerIds = layerIds.toArray(new String[layerIds.size()]);
  }
}
TOP

Related Classes of org.geomajas.gwt.client.spatial.snapping.Snapper

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.