/* * This is part of Geomajas, a GIS framework, http://www.geomajas.org/. * * Copyright 2008-2015 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.plugin.editing.client.snap.algorithm; import org.geomajas.annotation.Api; import org.geomajas.geometry.Coordinate; import org.geomajas.geometry.Geometry; import org.geomajas.geometry.service.MathService; import org.geomajas.plugin.editing.client.snap.SnapAlgorithm; import java.util.ArrayList; import java.util.List; /** * <p> Snapping algorithm that not only snaps to end-points of nearby geometries, but to any point on any of the edges * as well. As you might guess, this is a heavier calculation then the {@link NearestVertexSnapAlgorithm}. It tries to * calculate distances between the given coordinate, and any of the edges (one-by-one) and decides on the shortest. * </p> * * @author Pieter De Graef * @since 2.0.0 */ @Api(allMethods = true) public class NearestEdgeSnapAlgorithm implements SnapAlgorithm { /** * A list of coordinate arrays. It contains coordinate arrays from either LineStrings or LinearRings or point. The * point is that only between coordinates in the same array can edges exist. */ private List<Coordinate[]> coordinates = new ArrayList<Coordinate[]>(); private double calculatedDistance; private boolean hasSnapped; // ------------------------------------------------------------------------ // SnappingAlgorithm implementation: // ------------------------------------------------------------------------ /** * Execute the snap operation. * * @param coordinate The original location. * @param distance The maximum distance allowed for snapping. * @return The new location. If no snapping target was found, this may return the original location. */ public Coordinate snap(Coordinate coordinate, double distance) { // Some initialization: calculatedDistance = distance; hasSnapped = false; Coordinate snappingPoint = coordinate; // Calculate the distances for all coordinate arrays: for (Coordinate[] coordinateArray : coordinates) { if (coordinateArray.length > 1) { for (int j = 1; j < coordinateArray.length; j++) { double d = MathService.distance(coordinateArray[j], coordinateArray[j - 1], coordinate); if (d < calculatedDistance || (d == calculatedDistance && !hasSnapped)) { snappingPoint = MathService.nearest(coordinateArray[j], coordinateArray[j - 1], coordinate); calculatedDistance = d; hasSnapped = true; } } } else if (coordinateArray.length == 1) { // In the case of Points, see if we can snap to them: double d = MathService.distance(coordinateArray[0], coordinate); if (d < calculatedDistance) { snappingPoint = coordinateArray[0]; calculatedDistance = d; hasSnapped = true; } } } return snappingPoint; } /** * Set the full list of target geometries. These are the geometries where to this snapping algorithm can snap. * * @param geometries The list of target geometries. */ public void setGeometries(Geometry[] geometries) { coordinates.clear(); for (Geometry geometry : geometries) { addCoordinateArrays(geometry, coordinates); } } /** * Get the effective distance that was bridged during the snap operation. In case snapping occurred, this distance * will be smaller than the given "distance" value during the last call to snap. * * @return The effective snapping distance. Only valid if snapping actually occurred. */ public double getCalculatedDistance() { return calculatedDistance; } /** * Has snapping actually occurred during the last call to the <code>snap</code> method? If so the returned snap * location was different from the original location. * * @return Returns if the returned location from the snap method differs from the original location. */ public boolean hasSnapped() { return hasSnapped; } // ------------------------------------------------------------------------ // Private methods: // ------------------------------------------------------------------------ private void addCoordinateArrays(Geometry geometry, List<Coordinate[]> coords) { if (geometry.getGeometries() != null) { for (Geometry child : geometry.getGeometries()) { addCoordinateArrays(child, coords); } } else if (geometry.getCoordinates() != null) { coords.add(geometry.getCoordinates()); } } }