/* * 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.gwt2.plugin.editing.client.gfx; import org.geomajas.geometry.Bbox; import org.geomajas.geometry.Coordinate; import org.geomajas.geometry.Geometry; import org.geomajas.gwt.client.map.RenderSpace; import org.geomajas.gwt2.client.GeomajasImpl; import org.geomajas.gwt2.client.controller.MapController; import org.geomajas.gwt2.client.event.ViewPortChangedEvent; import org.geomajas.gwt2.client.event.ViewPortChangedHandler; import org.geomajas.gwt2.client.gfx.ShapeStyle; import org.geomajas.gwt2.client.gfx.VectorContainer; import org.geomajas.gwt2.client.map.MapPresenter; import org.geomajas.plugin.editing.client.event.GeometryEditChangeStateEvent; import org.geomajas.plugin.editing.client.event.GeometryEditChangeStateHandler; import org.geomajas.plugin.editing.client.event.GeometryEditMoveEvent; import org.geomajas.plugin.editing.client.event.GeometryEditMoveHandler; import org.geomajas.plugin.editing.client.event.GeometryEditResumeEvent; import org.geomajas.plugin.editing.client.event.GeometryEditResumeHandler; import org.geomajas.plugin.editing.client.event.GeometryEditShapeChangedEvent; import org.geomajas.plugin.editing.client.event.GeometryEditShapeChangedHandler; import org.geomajas.plugin.editing.client.event.GeometryEditStartEvent; import org.geomajas.plugin.editing.client.event.GeometryEditStartHandler; import org.geomajas.plugin.editing.client.event.GeometryEditStopEvent; import org.geomajas.plugin.editing.client.event.GeometryEditStopHandler; import org.geomajas.plugin.editing.client.event.GeometryEditSuspendEvent; import org.geomajas.plugin.editing.client.event.GeometryEditSuspendHandler; import org.geomajas.plugin.editing.client.event.GeometryEditTentativeMoveEvent; import org.geomajas.plugin.editing.client.event.GeometryEditTentativeMoveHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexDeselectedEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexDeselectedHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexDisabledEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexDisabledHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexEnabledEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexEnabledHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexHighlightBeginEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexHighlightBeginHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexHighlightEndEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexHighlightEndHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexMarkForDeletionBeginEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexMarkForDeletionBeginHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexMarkForDeletionEndEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexMarkForDeletionEndHandler; import org.geomajas.plugin.editing.client.event.state.GeometryIndexSelectedEvent; import org.geomajas.plugin.editing.client.event.state.GeometryIndexSelectedHandler; import org.geomajas.plugin.editing.client.gfx.GeometryRenderer; import org.geomajas.plugin.editing.client.handler.EdgeMapHandlerFactory; import org.geomajas.plugin.editing.client.handler.VertexMapHandlerFactory; 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.GeometryIndexType; import org.vaadin.gwtgraphics.client.VectorObject; import org.vaadin.gwtgraphics.client.shape.Path; import org.vaadin.gwtgraphics.client.shape.path.LineTo; import org.vaadin.gwtgraphics.client.shape.path.MoveTo; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Renderer for geometries during the editing process. * * @author Pieter De Graef * @author Jan De Moerloose */ public class GeometryRendererImpl implements GeometryRenderer, GeometryEditStartHandler, GeometryEditStopHandler, GeometryEditSuspendHandler, GeometryEditResumeHandler, GeometryIndexHighlightBeginHandler, GeometryIndexHighlightEndHandler, GeometryEditMoveHandler, GeometryEditShapeChangedHandler, GeometryEditChangeStateHandler, GeometryIndexSelectedHandler, GeometryIndexDeselectedHandler, GeometryIndexDisabledHandler, GeometryIndexEnabledHandler, GeometryIndexMarkForDeletionBeginHandler, GeometryIndexMarkForDeletionEndHandler, GeometryEditTentativeMoveHandler, ViewPortChangedHandler { private final MapPresenter mapPresenter; private final GeometryEditService editService; private final StyleProvider styleProvider; private final Map<GeometryIndex, VectorObject> shapes; private GeometryIndexShapeFactory shapeFactory; private GeometryIndexStyleFactory styleFactory; private GeometryIndexControllerFactory controllerFactory; private VectorContainer container; private Path tentativeMoveLine; private VectorObject nullShape; // ------------------------------------------------------------------------ // Constructor: // ------------------------------------------------------------------------ /** * Create a new renderer instance that renders on the given map and listens to the given editing service. * * @param mapPresenter The map to render on. * @param editService The geometry editing service to listen to. */ public GeometryRendererImpl(MapPresenter mapPresenter, GeometryEditService editService) { this.mapPresenter = mapPresenter; this.editService = editService; this.styleProvider = new StyleProvider(); this.shapes = new HashMap<GeometryIndex, VectorObject>(); // Initialize default factories: this.shapeFactory = new DefaultGeometryIndexShapeFactory(mapPresenter, RenderSpace.SCREEN); this.styleFactory = new DefaultGeometryIndexStyleFactory(styleProvider); this.controllerFactory = new DefaultGeometryIndexControllerFactory(mapPresenter); // Add ViewPortChangedHandler: mapPresenter.getEventBus().addHandler(ViewPortChangedHandler.TYPE, this); // Add edit base handlers: editService.addGeometryEditChangeStateHandler(this); editService.addGeometryEditMoveHandler(this); editService.addGeometryEditShapeChangedHandler(this); editService.addGeometryEditStartHandler(this); editService.addGeometryEditStopHandler(this); editService.addGeometryEditSuspendHandler(this); editService.addGeometryEditResumeHandler(this); editService.addGeometryEditTentativeMoveHandler(this); // Add GeometryIndex state handlers: editService.getIndexStateService().addGeometryIndexDeselectedHandler(this); editService.getIndexStateService().addGeometryIndexSelectedHandler(this); editService.getIndexStateService().addGeometryIndexDisabledHandler(this); editService.getIndexStateService().addGeometryIndexEnabledHandler(this); editService.getIndexStateService().addGeometryIndexHighlightBeginHandler(this); editService.getIndexStateService().addGeometryIndexHighlightEndHandler(this); editService.getIndexStateService().addGeometryIndexMarkForDeletionBeginHandler(this); editService.getIndexStateService().addGeometryIndexMarkForDeletionEndHandler(this); } // ------------------------------------------------------------------------ // Public methods: // ------------------------------------------------------------------------ /** * Clear everything and completely redraw the edited geometry. */ public void redraw() { shapes.clear(); if (container != null) { container.setTranslation(0, 0); container.clear(); try { tentativeMoveLine = new Path(-5, -5); tentativeMoveLine.lineTo(-5, -5); ShapeStyle style = styleProvider.getEdgeTentativeMoveStyle(); GeomajasImpl.getInstance().getGfxUtil().applyStyle(tentativeMoveLine, style); container.add(tentativeMoveLine); draw(); } catch (GeometryIndexNotFoundException e) { // Happens when creating new geometries...can't render points that don't exist yet. } } } // ------------------------------------------------------------------------ // ViewPortChangedHandler implementation: // ------------------------------------------------------------------------ /** * Redraw the geometry on the map. */ public void onViewPortChanged(ViewPortChangedEvent event) { redraw(); } // ------------------------------------------------------------------------ // Start & stop handler implementations: // ------------------------------------------------------------------------ /** * Clean up the previous state, create a container to draw in and then draw the geometry. */ public void onGeometryEditStart(GeometryEditStartEvent event) { if (container != null) { mapPresenter.getContainerManager().removeVectorContainer(container); } container = mapPresenter.getContainerManager().addScreenContainer(); redraw(); } /** * Clean up all rendering. */ public void onGeometryEditStop(GeometryEditStopEvent event) { // Remove the vector container from the map: mapPresenter.getContainerManager().removeVectorContainer(container); container = null; shapes.clear(); } @Override public void onGeometryEditSuspend(GeometryEditSuspendEvent event) { if (container != null) { mapPresenter.getContainerManager().removeVectorContainer(container); } container = mapPresenter.getContainerManager().addScreenContainer(); redraw(); } @Override public void onGeometryEditResume(GeometryEditResumeEvent event) { if (container != null) { mapPresenter.getContainerManager().removeVectorContainer(container); } container = mapPresenter.getContainerManager().addScreenContainer(); redraw(); } // ------------------------------------------------------------------------ // GeometryIndex state change implementations: // ------------------------------------------------------------------------ /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexMarkForDeletionEnd(GeometryIndexMarkForDeletionEndEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexMarkForDeletionBegin(GeometryIndexMarkForDeletionBeginEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexEnabled(GeometryIndexEnabledEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexDisabled(GeometryIndexDisabledEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexDeselected(GeometryIndexDeselectedEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexSelected(GeometryIndexSelectedEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexHighlightEnd(GeometryIndexHighlightEndEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } /** * Update the styles of the geometry indices in the event. */ public void onGeometryIndexHighlightBegin(GeometryIndexHighlightBeginEvent event) { for (GeometryIndex index : event.getIndices()) { update(index, false); } } // ------------------------------------------------------------------------ // GeometryEditChangeStateHandler implementation: // ------------------------------------------------------------------------ /** * Change the cursor while dragging. */ public void onChangeEditingState(GeometryEditChangeStateEvent event) { switch (event.getEditingState()) { case DRAGGING: mapPresenter.setCursor("move"); break; case IDLE: default: mapPresenter.setCursor("default"); redraw(); } } // ------------------------------------------------------------------------ // Geometry shape change implementation: // ------------------------------------------------------------------------ /** * Redraw the geometry on the map. */ public void onGeometryShapeChanged(GeometryEditShapeChangedEvent event) { redraw(); } /** * Figure out what's being moved and update only adjacent objects. This is far more performing than simply redrawing * everything. */ public void onGeometryEditMove(GeometryEditMoveEvent event) { // Find the elements that need updating: Map<GeometryIndex, Boolean> indicesToUpdate = new HashMap<GeometryIndex, Boolean>(); for (GeometryIndex index : event.getIndices()) { if (!indicesToUpdate.containsKey(index)) { indicesToUpdate.put(index, false); if (!Geometry.POINT.equals(editService.getGeometry().getGeometryType()) && !Geometry.MULTI_POINT.equals(editService.getGeometry().getGeometryType())) { try { List<GeometryIndex> neighbors; switch (editService.getIndexService().getType(index)) { case TYPE_VERTEX: // Move current vertex to the back. This helps the delete operation. indicesToUpdate.put(index, true); neighbors = editService.getIndexService().getAdjacentEdges(event.getGeometry(), index); if (neighbors != null) { for (GeometryIndex neighborIndex : neighbors) { if (!indicesToUpdate.containsKey(neighborIndex)) { indicesToUpdate.put(neighborIndex, false); } } } neighbors = editService.getIndexService().getAdjacentVertices(event.getGeometry(), index); if (neighbors != null) { for (GeometryIndex neighborIndex : neighbors) { if (!indicesToUpdate.containsKey(neighborIndex)) { indicesToUpdate.put(neighborIndex, false); } } } break; case TYPE_EDGE: neighbors = editService.getIndexService().getAdjacentVertices(event.getGeometry(), index); if (neighbors != null) { for (GeometryIndex neighborIndex : neighbors) { if (!indicesToUpdate.containsKey(neighborIndex)) { indicesToUpdate.put(neighborIndex, false); } } } break; default: } } catch (GeometryIndexNotFoundException e) { throw new IllegalStateException(e); } } } } // Check if we need to draw the background (nice, but slows down): if (styleProvider.getBackgroundStyle() != null && styleProvider.getBackgroundStyle().getFillOpacity() > 0) { if (event.getGeometry().getGeometryType().equals(Geometry.POLYGON)) { update(null, false); } else if (event.getGeometry().getGeometryType().equals(Geometry.MULTI_POLYGON) && event.getGeometry().getGeometries() != null) { for (int i = 0; i < event.getGeometry().getGeometries().length; i++) { GeometryIndex index = editService.getIndexService().create(GeometryIndexType.TYPE_GEOMETRY, i); indicesToUpdate.put(index, false); } } } // Next, redraw the list: for (GeometryIndex index : indicesToUpdate.keySet()) { update(index, indicesToUpdate.get(index)); } } /** * Renders a line from the last inserted point to the current mouse position, indicating what the new situation * would look like if a vertex where to be inserted at the mouse location. */ public void onTentativeMove(GeometryEditTentativeMoveEvent event) { try { Coordinate[] vertices = editService.getIndexService().getSiblingVertices(editService.getGeometry(), editService.getInsertIndex()); String geometryType = editService.getIndexService().getGeometryType(editService.getGeometry(), editService.getInsertIndex()); if (vertices != null && (Geometry.LINE_STRING.equals(geometryType) || Geometry.LINEAR_RING.equals(geometryType))) { Coordinate temp1 = event.getOrigin(); Coordinate temp2 = event.getCurrentPosition(); Coordinate c1 = mapPresenter.getViewPort().getTransformationService() .transform(temp1, RenderSpace.WORLD, RenderSpace.SCREEN); Coordinate c2 = mapPresenter.getViewPort().getTransformationService() .transform(temp2, RenderSpace.WORLD, RenderSpace.SCREEN); tentativeMoveLine.setStep(0, new MoveTo(false, c1.getX(), c1.getY())); tentativeMoveLine.setStep(1, new LineTo(false, c2.getX(), c2.getY())); } else if (vertices != null && Geometry.LINEAR_RING.equals(geometryType)) { // Draw the second line (as an option...) } } catch (GeometryIndexNotFoundException e) { throw new IllegalStateException(e); } } // ------------------------------------------------------------------------ // Getters and setters: // ------------------------------------------------------------------------ /** * Get the style provider. This object is used by the default style factory for providing styles for the geometry * rendering. As long as the default style factory is being used, this object can be used to change the styles. */ public StyleProvider getStyleProvider() { return styleProvider; } public void setVisible(boolean visible) { if (container != null) { container.setVisible(visible); } } @Override public void addVertexHandlerFactory(VertexMapHandlerFactory factory) { ((DefaultGeometryIndexControllerFactory) controllerFactory).addVertexHandlerFactory(factory); } @Override public void addEdgeHandlerFactory(EdgeMapHandlerFactory factory) { ((DefaultGeometryIndexControllerFactory) controllerFactory).addEdgeHandlerFactory(factory); } // ------------------------------------------------------------------------ // Private methods for updating: // ------------------------------------------------------------------------ private void update(GeometryIndex index, boolean moveToBack) { try { VectorObject shape; if (index == null) { shape = nullShape; } else { shape = shapes.get(index); } if (shape != null) { // We don't consider position at this point. Just style: ShapeStyle style = styleFactory.create(editService, index); GeomajasImpl.getInstance().getGfxUtil().applyStyle(shape, style); // Now update the location: shapeFactory.update(shape, editService, index); // Move to the front if requested: if (moveToBack) { container.moveToBack(shape); } } } catch (GeometryIndexNotFoundException e) { throw new IllegalStateException(e); } } // ------------------------------------------------------------------------ // Private methods for re-drawing: // ------------------------------------------------------------------------ private void draw() throws GeometryIndexNotFoundException { if (Geometry.POINT.equals(editService.getGeometry().getGeometryType())) { drawPoint(null); } else if (Geometry.LINE_STRING.equals(editService.getGeometry().getGeometryType()) || Geometry.LINEAR_RING.equals(editService.getGeometry().getGeometryType())) { drawLineString(null); } else if (Geometry.POLYGON.equals(editService.getGeometry().getGeometryType())) { drawPolygon(null); } } private void drawPolygon(GeometryIndex parentIndex) throws GeometryIndexNotFoundException { Geometry geometry = editService.getGeometry(); if (parentIndex != null) { geometry = editService.getIndexService().getGeometry(editService.getGeometry(), parentIndex); } if (geometry.getGeometries() != null) { // First of all we check if the background needs to be drawn: if (styleProvider.getBackgroundStyle() != null && styleProvider.getBackgroundStyle().getFillOpacity() > 0) { drawIndex(parentIndex); } // Then, we draw all LinearRings one by one: for (int i = 0; i < geometry.getGeometries().length; i++) { GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_GEOMETRY, i); drawLinearRing(index); } } } private void drawLinearRing(GeometryIndex parentIndex) throws GeometryIndexNotFoundException { Geometry geometry = editService.getGeometry(); if (parentIndex != null) { geometry = editService.getIndexService().getGeometry(editService.getGeometry(), parentIndex); } int[] indices = null; if (geometry.getCoordinates() != null) { int max = geometry.getCoordinates().length - 1; boolean inserting = false; boolean limited = false; // If we are inserting in this particular LinearRing, don't display the closing edge/vertex or it // looks like the ring is already closed GeometryIndex insertIndex = editService.getInsertIndex(); if (insertIndex != null && editService.getEditingState().equals(GeometryEditState.INSERTING) && editService.getIndexService().isChildOf(parentIndex, insertIndex)) { max--; inserting = true; } // limit to maximum 50 visible indices if max > 50 if (max > 50) { max = 50; limited = true; indices = new int[50]; Bbox bounds = mapPresenter.getViewPort().getBounds(); int j = 0; for (int i = 0; i < geometry.getCoordinates().length; i++) { if (contains(bounds, geometry.getCoordinates()[i])) { indices[j++] = i; if (j == 50) { break; } } } // if less than 50 visible, only show these if (j < 50) { max = j; } } // Draw all edges: for (int i = 0; i < max; i++) { int ii = (indices == null ? i : indices[i]); GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_EDGE, ii); drawIndex(index); } // Then draw all vertices: if (inserting && !limited) { max++; // do show all vertices when inserting } for (int i = 0; i < max; i++) { int ii = (indices == null ? i : indices[i]); GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_VERTEX, ii); drawIndex(index); } } } private void drawLineString(GeometryIndex parentIndex) throws GeometryIndexNotFoundException { Geometry geometry = editService.getGeometry(); if (parentIndex != null) { geometry = editService.getIndexService().getGeometry(editService.getGeometry(), parentIndex); } if (geometry.getCoordinates() != null) { // Draw all edges: for (int i = 0; i < geometry.getCoordinates().length - 1; i++) { GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_EDGE, i); drawIndex(index); } // Then draw all vertices: for (int i = 0; i < geometry.getCoordinates().length; i++) { GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_VERTEX, i); drawIndex(index); } } } private void drawPoint(GeometryIndex parentIndex) throws GeometryIndexNotFoundException { GeometryIndex index = editService.getIndexService().addChildren(parentIndex, GeometryIndexType.TYPE_VERTEX, 0); drawIndex(index); } private void drawIndex(GeometryIndex index) throws GeometryIndexNotFoundException { VectorObject shape = shapeFactory.create(editService, index); if (shape == null) { return; } // Apply style: ShapeStyle style = styleFactory.create(editService, index); GeomajasImpl.getInstance().getGfxUtil().applyStyle(shape, style); // Apply controller: if (!editService.isSuspended()) { MapController controller = controllerFactory.create(editService, index); if (controller != null) { controller.onActivate(mapPresenter); GeomajasImpl.getInstance().getGfxUtil().applyController(shape, controller); } } container.add(shape); if (index == null) { nullShape = shape; } else { shapes.put(index, shape); } } boolean contains(Bbox box, Coordinate c) { if (c.getX() < box.getX()) { return false; } if (c.getY() < box.getY()) { return false; } if (c.getX() > box.getMaxX()) { return false; } if (c.getY() > box.getMaxY()) { return false; } return true; } }