/* * 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.operation; import org.geomajas.geometry.Coordinate; import org.geomajas.geometry.Geometry; import org.geomajas.plugin.editing.client.service.GeometryEditService; import org.geomajas.plugin.editing.client.service.GeometryEditState; import org.geomajas.plugin.editing.client.service.GeometryIndex; import org.geomajas.plugin.editing.client.service.GeometryIndexNotFoundException; import org.geomajas.plugin.editing.client.service.GeometryIndexService; import org.geomajas.plugin.editing.client.service.GeometryIndexType; /** * <p> * Geometry index operation that inserts a single vertex after the given index. Supported index types are vertex and * edge. This implementation does not create a new geometry instance, but changes the given geometry. * </p> * <p> * Note that this operation will only insert into existing geometries. It will never create new sub-geometries. For * example, if you want to add an extra Point to a MultiPoint, then first make sure there is an empty Point wherein this * operation can insert the vertex. * </p> * * @author Pieter De Graef */ public class InsertVertexOperation implements GeometryIndexOperation { private final GeometryIndexService service; private final Coordinate coordinate; private GeometryIndex index; private GeometryEditService geometryEditService; /** * Initialize this operation with an indexing service. * * @param service * geometry index service. */ public InsertVertexOperation(GeometryIndexService service, Coordinate coordinate, GeometryEditService geometryEditService) { this.service = service; this.coordinate = coordinate; this.geometryEditService = geometryEditService; } @Override public Geometry execute(Geometry geometry, GeometryIndex index) throws GeometryOperationFailedException { this.index = index; if (service.getType(index) != GeometryIndexType.TYPE_VERTEX && service.getType(index) != GeometryIndexType.TYPE_EDGE) { throw new GeometryOperationFailedException("Index of wrong type. Must be TYPE_VERTEX or TYPE_EDGE."); } try { insert(geometry, index, coordinate); updateGeometryEditServiceProperties(geometry, index, coordinate); return geometry; } catch (GeometryIndexNotFoundException e) { throw new GeometryOperationFailedException(e); } } @Override public GeometryIndexOperation getInverseOperation() { return new DeleteVertexOperation(service, geometryEditService); } @Override public GeometryIndex getGeometryIndex() { switch (service.getType(index)) { case TYPE_EDGE: return service.getNextVertex(index); default: return index; } } // ------------------------------------------------------------------------ // Private methods: // ------------------------------------------------------------------------ private void insert(Geometry geom, GeometryIndex index, Coordinate coordinate) throws GeometryIndexNotFoundException { if (index.hasChild() && geom.getGeometries() != null && geom.getGeometries().length > index.getValue()) { insert(geom.getGeometries()[index.getValue()], index.getChild(), coordinate); } else if (index.getType() == GeometryIndexType.TYPE_EDGE) { insertAfterEdge(geom, index, coordinate); } else if (index.getType() == GeometryIndexType.TYPE_VERTEX) { insertAfterVertex(geom, index, coordinate); } else { throw new GeometryIndexNotFoundException("Could not match index with given geometry."); } } /** * Insert a point into a given edge. There can be only edges if there are at least 2 points in a LineString * geometry. */ private void insertAfterEdge(Geometry geom, GeometryIndex index, Coordinate coordinate) throws GeometryIndexNotFoundException { // First we check the geometry type: if (!Geometry.LINE_STRING.equals(geom.getGeometryType()) && !Geometry.LINEAR_RING.equals(geom.getGeometryType())) { throw new GeometryIndexNotFoundException("Could not match index with given geometry."); } if (index.getValue() < 0) { throw new GeometryIndexNotFoundException("Cannot insert in a negative index."); } // Then we check if the edge exists: if (geom.getCoordinates() != null && geom.getCoordinates().length > index.getValue() + 1) { // Inserting on edges allows only to insert on existing edges. No adding at the end: Coordinate[] result = new Coordinate[geom.getCoordinates().length + 1]; int count = 0; for (int i = 0; i < geom.getCoordinates().length; i++) { if (i == (index.getValue() + 1)) { result[i] = coordinate; count++; } result[i + count] = geom.getCoordinates()[i]; } geom.setCoordinates(result); } else { throw new GeometryIndexNotFoundException("Cannot insert a vertex into an edge that does not exist."); } } private void insertAfterVertex(Geometry geom, GeometryIndex index, Coordinate coordinate) throws GeometryIndexNotFoundException { // First we check the geometry type (allow only Point, LineString and LinearRing): if (Geometry.POINT.equals(geom.getGeometryType())) { if (index.getValue() != 0 || geom.getCoordinates() != null) { throw new GeometryIndexNotFoundException("A point can have only one coordinate."); } geom.setCoordinates(new Coordinate[] { coordinate }); } else if (Geometry.LINE_STRING.equals(geom.getGeometryType())) { if (geom.getCoordinates() == null && index.getValue() == 0) { geom.setCoordinates(new Coordinate[] { coordinate }); } else if (geom.getCoordinates() == null || index.getValue() < 0 || index.getValue() > geom.getCoordinates().length) { throw new GeometryIndexNotFoundException("Vertex index out of bounds."); } else { Coordinate[] result = new Coordinate[geom.getCoordinates().length + 1]; int count = 0; for (int i = 0; i < result.length; i++) { if (i == index.getValue()) { result[i] = coordinate; } else { result[i] = geom.getCoordinates()[count]; count++; } } geom.setCoordinates(result); } } else if (Geometry.LINEAR_RING.equals(geom.getGeometryType())) { if (geom.getCoordinates() == null && index.getValue() == 0) { geom.setCoordinates(new Coordinate[] { coordinate, coordinate }); } else if (geom.getCoordinates() == null || index.getValue() < 0 || index.getValue() > geom.getCoordinates().length - 1) { throw new GeometryIndexNotFoundException("Vertex index out of bounds."); } else { Coordinate[] result = new Coordinate[geom.getCoordinates().length + 1]; int count = 0; for (int i = 0; i < result.length; i++) { if (i == index.getValue()) { result[i] = coordinate; } else if (i < result.length - 1) { result[i] = geom.getCoordinates()[count]; count++; } else { result[i] = new Coordinate(result[0]); } } geom.setCoordinates(result); } } else { throw new GeometryIndexNotFoundException("Could not match index with given geometry."); } } private void updateGeometryEditServiceProperties(Geometry geometry, GeometryIndex index, Coordinate coordinate) { if (geometryEditService.getEditingState().equals(GeometryEditState.INSERTING)) { Coordinate tentativeMoveOrigin = null; GeometryIndex nextIndex = null; if (geometry.getGeometryType().equals(Geometry.POINT)) { // If the case of a single point, no more inserting: geometryEditService.setEditingState(GeometryEditState.IDLE); } else if (geometry.getGeometryType().equals(Geometry.MULTI_POINT)) { nextIndex = service.create(GeometryIndexType.TYPE_VERTEX, index.getValue() + 1, 0); } else { tentativeMoveOrigin = coordinate; nextIndex = geometryEditService.getIndexService().getNextVertex(getGeometryIndex()); } geometryEditService.setTentativeMoveOrigin(tentativeMoveOrigin); geometryEditService.setInsertIndex(nextIndex); } } }