package com.revolsys.swing.map.overlay; import java.awt.Cursor; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Point2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.swing.JOptionPane; import javax.swing.undo.UndoableEdit; import com.revolsys.awt.WebColors; import com.revolsys.collection.map.Maps; import com.revolsys.datatype.DataType; import com.revolsys.datatype.DataTypes; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Lineal; import com.revolsys.geometry.model.LinearRing; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.Polygonal; import com.revolsys.geometry.model.Punctual; import com.revolsys.geometry.model.segment.LineSegment; import com.revolsys.geometry.model.vertex.Vertex; import com.revolsys.io.BaseCloseable; import com.revolsys.record.schema.FieldDefinition; import com.revolsys.record.schema.RecordDefinition; import com.revolsys.swing.Icons; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.map.MapPanel; import com.revolsys.swing.map.Viewport2D; import com.revolsys.swing.map.layer.AbstractLayer; import com.revolsys.swing.map.layer.Layer; import com.revolsys.swing.map.layer.LayerGroup; import com.revolsys.swing.map.layer.Project; import com.revolsys.swing.map.layer.record.AbstractRecordLayer; import com.revolsys.swing.map.layer.record.LayerRecord; import com.revolsys.swing.map.layer.record.LayerRecordMenu; import com.revolsys.swing.map.layer.record.renderer.MarkerStyleRenderer; import com.revolsys.swing.map.layer.record.style.MarkerStyle; import com.revolsys.swing.undo.AbstractUndoableEdit; import com.revolsys.swing.undo.MultipleUndo; public class EditRecordGeometryOverlay extends AbstractOverlay implements PropertyChangeListener, MouseListener, MouseMotionListener { private class AddGeometryUndoEdit extends AbstractUndoableEdit { private static final long serialVersionUID = 1L; private final DataType geometryPartDataType = EditRecordGeometryOverlay.this.addGeometryPartDataType; private final int[] geometryPartIndex = EditRecordGeometryOverlay.this.addGeometryPartIndex; private final Geometry newGeometry; private final Geometry oldGeometry = EditRecordGeometryOverlay.this.addGeometry; private AddGeometryUndoEdit(final Geometry geometry) { this.newGeometry = geometry; } @Override public boolean canRedo() { if (super.canRedo()) { if (DataTypes.GEOMETRY.equals(this.oldGeometry, EditRecordGeometryOverlay.this.addGeometry)) { return true; } } return false; } @Override public boolean canUndo() { if (super.canUndo()) { if (DataTypes.GEOMETRY.equals(this.newGeometry, EditRecordGeometryOverlay.this.addGeometry)) { return true; } } return false; } @Override protected void redoDo() { EditRecordGeometryOverlay.this.addGeometry = this.newGeometry; setXorGeometry(null); repaint(); } @Override protected void undoDo() { EditRecordGeometryOverlay.this.addGeometry = this.oldGeometry; EditRecordGeometryOverlay.this.addGeometryPartDataType = this.geometryPartDataType; EditRecordGeometryOverlay.this.addGeometryPartIndex = this.geometryPartIndex; setXorGeometry(null); repaint(); } } private static final String ACTION_ADD_GEOMETRY = "addGeometry"; private static final String ACTION_ADD_GEOMETRY_EDIT_VERTICES = "addGeometryEditVertices"; private static final String ACTION_EDIT_GEOMETRY_VERTICES = "editGeometryVertices"; static final String ACTION_MOVE_GEOMETRY = "moveGeometry"; private static final Cursor CURSOR_MOVE = Icons.getCursor("cursor_move", 8, 7); private static final VertexStyleRenderer GEOMETRY_CLOSE_VERTEX_RENDERER = new VertexStyleRenderer( WebColors.RoyalBlue); private static final SelectedRecordsRenderer GEOMETRY_RENDERER = new SelectedRecordsRenderer( WebColors.Aqua, 127); private static final SelectedRecordsVertexRenderer GEOMETRY_VERTEX_RENDERER = new SelectedRecordsVertexRenderer( WebColors.Aqua, true); private static final long serialVersionUID = 1L; private int actionId = 0; private AddGeometryCompleteAction addCompleteAction; private Geometry addGeometry; private DataType addGeometryDataType; private DataType addGeometryPartDataType; /** Index to the part of the addGeometry that new points should be added too. */ private int[] addGeometryPartIndex = {}; private AbstractRecordLayer addLayer; private boolean dragged = false; private boolean editGeometryVerticesStart; private boolean addGeometryEditVerticesStart; private List<CloseLocation> mouseOverLocations = Collections.emptyList(); private Point moveGeometryEnd; private List<CloseLocation> moveGeometryLocations; private Point moveGeometryStart; public EditRecordGeometryOverlay(final MapPanel map) { super(map); addOverlayAction( // ACTION_ADD_GEOMETRY, // CURSOR_NODE_ADD, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, // ZoomOverlay.ACTION_ZOOM_BOX, // ACTION_MOVE_GEOMETRY, // ACTION_ADD_GEOMETRY_EDIT_VERTICES // ); addOverlayAction(// ACTION_MOVE_GEOMETRY, // CURSOR_MOVE, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM // ); for (final String overlayAction : Arrays.asList(ACTION_EDIT_GEOMETRY_VERTICES, ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { addOverlayAction( // overlayAction, // CURSOR_NODE_EDIT, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, // ACTION_MOVE_GEOMETRY // ); } } /** * Set the addLayer that a new feature is to be added to. * * @param addLayer */ public void addRecord(final AbstractRecordLayer layer, final AddGeometryCompleteAction addCompleteAction) { if (layer != null) { final RecordDefinition recordDefinition = layer.getRecordDefinition(); final FieldDefinition geometryField = recordDefinition.getGeometryField(); if (geometryField != null) { this.addLayer = layer; this.addCompleteAction = addCompleteAction; final GeometryFactory geometryFactory = recordDefinition.getGeometryFactory(); this.setGeometryFactory(geometryFactory); clearUndoHistory(); this.addGeometry = geometryFactory.geometry(); setOverlayAction(ACTION_ADD_GEOMETRY); setAddGeometryDataType(geometryField.getDataType()); setMapCursor(CURSOR_NODE_ADD); if (Arrays.asList(DataTypes.POINT, DataTypes.LINE_STRING, DataTypes.MULTI_POINT, DataTypes.MULTI_LINE_STRING).contains(this.addGeometryDataType)) { this.addGeometryPartIndex = new int[0]; } else if (Arrays.asList(DataTypes.MULTI_POLYGON, DataTypes.POLYGON) .contains(this.addGeometryDataType)) { this.addGeometryPartIndex = new int[] { 0 }; } else { this.addGeometryPartIndex = new int[] { 0, 0 }; } } } } private void addRecords(final List<LayerRecord> results, final LayerGroup group, final Geometry boundingBox) { final double scale = getViewport().getScale(); final List<Layer> layers = group.getLayers(); Collections.reverse(layers); for (final Layer layer : layers) { if (layer instanceof LayerGroup) { final LayerGroup childGroup = (LayerGroup)layer; addRecords(results, childGroup, boundingBox); } else if (layer instanceof AbstractRecordLayer) { final AbstractRecordLayer recordLayer = (AbstractRecordLayer)layer; if (recordLayer.isSelectable(scale)) { final List<LayerRecord> selectedRecords = recordLayer.getSelectedRecords(); for (final LayerRecord selectedRecord : selectedRecords) { final Geometry geometry = selectedRecord.getGeometry(); if (boundingBox.intersects(geometry)) { results.add(selectedRecord); } } } } } } protected boolean addSnapLayers(final Set<AbstractRecordLayer> layers, final Project project, final AbstractRecordLayer layer, final double scale) { if (layer != null) { if (layer.isSnapToAllLayers()) { return true; } else { layers.add(layer); final Collection<String> layerPaths = layer.getSnapLayerPaths(); if (layerPaths != null) { for (final String layerPath : layerPaths) { final Layer snapLayer = project.getLayer(layerPath); if (snapLayer instanceof AbstractRecordLayer) { if (snapLayer.isVisible(scale)) { layers.add((AbstractRecordLayer)snapLayer); } } } } } } return false; } protected Geometry appendVertex(final Point newPoint) { final GeometryFactory geometryFactory = this.addLayer.getGeometryFactory(); Geometry geometry = this.addGeometry; if (geometry.isEmpty()) { geometry = newPoint.convertGeometry(geometryFactory); } else { final DataType geometryDataType = this.addGeometryDataType; final int[] geometryPartIndex = this.addGeometryPartIndex; if (DataTypes.MULTI_POINT.equals(geometryDataType)) { if (geometry instanceof Point) { final Point point = (Point)geometry; geometry = geometryFactory.punctual(point, newPoint); } else { geometry = geometry.appendVertex(newPoint, this.addGeometryPartIndex); } } else if (DataTypes.LINE_STRING.equals(geometryDataType)) { if (geometry instanceof Point) { final Point point = (Point)geometry; geometry = geometryFactory.lineString(point, newPoint); } else if (geometry instanceof LineString) { final LineString line = (LineString)geometry; geometry = line.appendVertex(newPoint, geometryPartIndex); } } else if (DataTypes.MULTI_LINE_STRING.equals(geometryDataType)) { if (geometry instanceof Point) { final Point point = (Point)geometry; geometry = geometryFactory.lineString(point, newPoint); } else if (geometry instanceof LineString) { final LineString line = (LineString)geometry; geometry = line.appendVertex(newPoint); } else if (geometry instanceof Lineal) { final Lineal line = (Lineal)geometry; geometry = line.appendVertex(newPoint, geometryPartIndex); } } else if (DataTypes.POLYGON.equals(geometryDataType) || DataTypes.MULTI_POLYGON.equals(geometryDataType) || DataTypes.GEOMETRY.equals(geometryDataType)) { if (geometry instanceof Point) { final Point point = (Point)geometry; geometry = geometryFactory.lineString(point, newPoint); } else if (geometry instanceof LineString) { final LineString line = (LineString)geometry; final Point p0 = line.getPoint(0); final Point p1 = line.getPoint(1); final LinearRing ring = geometryFactory.linearRing(Arrays.asList(p0, p1, newPoint, p0)); geometry = geometryFactory.polygon(ring); } else if (geometry instanceof Polygon) { final Polygon polygon = (Polygon)geometry; geometry = polygon.appendVertex(newPoint, geometryPartIndex[0]); } // TODO MultiPolygon // TODO Rings } else { // TODO multi point, oldGeometry collection } } return geometry; } protected void cancel() { clearMouseOverLocations(); if (this.addCompleteAction != null) { this.addCompleteAction.addComplete(this, null); } modeMoveGeometryClear(); modeAddGeometryClear(); modeEditGeometryVerticesClear(); } public void clearMouseOverGeometry() { if (!hasOverlayAction()) { clearMapCursor(); } setMouseOverLocationsDo(Collections.emptyList()); clearSnapLocations(); } protected void clearMouseOverLocations() { setXorGeometry(null); clearMouseOverGeometry(); final MapPanel map = getMap(); map.clearCloseSelected(); map.clearToolTipText(); repaint(); } @Override public void destroy() { super.destroy(); setMouseOverLocationsDo(Collections.emptyList()); } protected void fireActionPerformed(final ActionListener listener, final String command) { if (listener != null) { final ActionEvent actionEvent = new ActionEvent(this, this.actionId++, command); listener.actionPerformed(actionEvent); } } public DataType getAddGeometryPartDataType() { return this.addGeometryPartDataType; } public AbstractLayer getAddLayer() { return this.addLayer; } public Point getClosestPoint(final GeometryFactory geometryFactory, final LineSegment closestSegment, final Point point, final double maxDistance) { final LineSegment segment = closestSegment.convertGeometry(geometryFactory); final Point fromPoint = segment.getPoint(0); final Point toPoint = segment.getPoint(1); final double fromPointDistance = point.distancePoint(fromPoint); final double toPointDistance = point.distancePoint(toPoint); if (fromPointDistance < maxDistance) { if (fromPointDistance <= toPointDistance) { return fromPoint; } else { return toPoint; } } else if (toPointDistance <= maxDistance) { return toPoint; } else { final Point pointOnLine = segment.project(point); return geometryFactory.point(pointOnLine); } } public DataType getGeometryPartDataType(final DataType dataType) { if (Arrays.asList(DataTypes.POINT, DataTypes.MULTI_POINT).contains(dataType)) { return DataTypes.POINT; } else if (Arrays.asList(DataTypes.LINE_STRING, DataTypes.MULTI_LINE_STRING) .contains(dataType)) { return DataTypes.LINE_STRING; } else if (Arrays.asList(DataTypes.POLYGON, DataTypes.MULTI_POLYGON).contains(dataType)) { return DataTypes.POLYGON; } else { return DataTypes.GEOMETRY; } } protected Graphics2D getGraphics2D() { return getGraphics(); } public Point getLineNextVertex(final LineString line, final int vertexIndex, final int offset) { final int nextVertexIndex = vertexIndex + offset; if (nextVertexIndex < line.getVertexCount()) { return line.getPoint(nextVertexIndex); } else { return null; } } public Point getLinePreviousVertex(final LineString line, final int vertexIndex, final int offset) { final int previousVertexIndex = vertexIndex + offset; if (previousVertexIndex < 0) { return null; } else { return line.getPoint(previousVertexIndex); } } public List<CloseLocation> getMouseOverLocations() { return this.mouseOverLocations; } @Override protected List<LayerRecord> getSelectedRecords(final BoundingBox boundingBox) { return super.getSelectedRecords(boundingBox); } @Override protected List<AbstractRecordLayer> getSnapLayers() { final Project project = getProject(); final double scale = project.getMapPanel().getScale(); final Set<AbstractRecordLayer> layers = new LinkedHashSet<>(); boolean snapAll = false; if (isOverlayAction(ACTION_ADD_GEOMETRY)) { snapAll = addSnapLayers(layers, project, this.addLayer, scale); } else { for (final CloseLocation location : getMouseOverLocations()) { final AbstractRecordLayer layer = location.getLayer(); snapAll |= addSnapLayers(layers, project, layer, scale); } } if (snapAll) { final List<AbstractRecordLayer> visibleDescendants = project .getVisibleDescendants(AbstractRecordLayer.class, scale); return visibleDescendants; } return new ArrayList<>(layers); } protected Geometry getVertexGeometry(final MouseEvent event, final CloseLocation location) { final Geometry geometry = location.getGeometry(); final DataType geometryDataType = DataTypes.getDataType(geometry); final DataType geometryPartDataType = getGeometryPartDataType(geometryDataType); int previousPointOffset; int[] vertexId = location.getVertexId(); if (vertexId == null) { previousPointOffset = 0; vertexId = location.getSegmentId(); } else { previousPointOffset = -1; } final GeometryFactory geometryFactory = location.getGeometryFactory(); if (DataTypes.GEOMETRY.equals(geometryPartDataType)) { } else if (DataTypes.POINT.equals(geometryPartDataType)) { } else { final Point point = getPoint(geometryFactory, event); final Vertex vertex = geometry.getVertex(vertexId); Point previousPoint = null; Point nextPoint = null; if (DataTypes.LINE_STRING.equals(geometryPartDataType) || DataTypes.POLYGON.equals(geometryPartDataType)) { if (previousPointOffset == 0) { previousPoint = vertex; } else { previousPoint = vertex.getLinePrevious(); } nextPoint = vertex.getLineNext(); } final List<LineString> lines = new ArrayList<>(); if (previousPoint != null && !previousPoint.isEmpty()) { lines.add(newXorLine(geometryFactory, previousPoint, point)); } if (nextPoint != null && !nextPoint.isEmpty()) { lines.add(newXorLine(geometryFactory, nextPoint, point)); } if (!lines.isEmpty()) { return geometryFactory.lineal(lines); } } return null; } private boolean hasMouseOverLocation() { return !getMouseOverLocations().isEmpty(); } protected boolean isEditable(final AbstractRecordLayer recordLayer) { return recordLayer.isExists() && recordLayer.isVisible() && recordLayer.isCanEditRecords(); } protected boolean isGeometryValid(final Geometry geometry) { if (DataTypes.POINT.equals(this.addGeometryDataType)) { if (geometry instanceof Point) { return true; } else { return false; } } else if (DataTypes.MULTI_POINT.equals(this.addGeometryDataType)) { if (geometry instanceof Punctual) { return true; } else { return false; } } else if (DataTypes.LINE_STRING.equals(this.addGeometryDataType)) { if (geometry instanceof LineString) { return true; } else { return false; } } else if (DataTypes.MULTI_LINE_STRING.equals(this.addGeometryDataType)) { if (geometry instanceof Lineal) { return true; } else { return false; } } else if (DataTypes.POLYGON.equals(this.addGeometryDataType)) { if (geometry instanceof Polygon) { return true; } else { return false; } } else if (DataTypes.MULTI_POLYGON.equals(this.addGeometryDataType)) { if (geometry instanceof Polygonal) { return true; } else { return false; } } else if (DataTypes.GEOMETRY.equals(this.addGeometryDataType)) { return true; } else { return false; } } @Override public void keyPressed(final KeyEvent e) { final int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_ESCAPE) { if (this.dragged) { clearMouseOverLocations(); modeMoveGeometryClear(); modeEditGeometryVerticesClear(); modeAddGeometryEditVerticesClear(); if (this.addLayer == null) { } else { modeAddGeometryMove(null); } } else { cancel(); } } else if (keyCode == KeyEvent.VK_ALT) { if (!this.dragged) { if (hasMouseOverLocation() && !this.editGeometryVerticesStart) { if (setOverlayAction(ACTION_MOVE_GEOMETRY)) { updateMouseOverLocations(); } } else { if (isOverlayAction(ACTION_ADD_GEOMETRY)) { clearMapCursor(); setXorGeometry(null); } } } } } @Override public void keyReleased(final KeyEvent e) { final int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_ALT) { if (!this.dragged) { if (isOverlayAction(ACTION_MOVE_GEOMETRY)) { if (clearOverlayAction(ACTION_MOVE_GEOMETRY)) { if (this.addLayer != null) { modeAddGeometryMove(null); } else { updateMouseOverLocations(); } } } else { if (this.addLayer == null) { updateMouseOverLocations(); } else { modeAddGeometryMove(null); } } } } else if (keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE) { if (hasMouseOverLocation()) { final MultipleUndo edit = new MultipleUndo(); for (final CloseLocation location : getMouseOverLocations()) { final Geometry geometry = location.getGeometry(); final int[] vertexIndex = location.getVertexId(); if (vertexIndex != null) { try { final Geometry newGeometry = geometry.deleteVertex(vertexIndex); if (newGeometry.isEmpty()) { Toolkit.getDefaultToolkit().beep(); } else { final UndoableEdit geometryEdit = setGeometry(location, newGeometry); edit.addEdit(geometryEdit); } } catch (final Throwable t) { Toolkit.getDefaultToolkit().beep(); } } } if (!edit.isEmpty()) { addUndo(edit); } clearMouseOverLocations(); } } else if (keyCode == KeyEvent.VK_F2 || keyCode == KeyEvent.VK_F) { clearMouseOverLocations(); modeMoveGeometryClear(); if (this.addCompleteAction != null) { this.addCompleteAction.addComplete(this, this.addGeometry); } modeAddGeometryClear(); } else if (splitLineKeyPress(e)) { } } @Override public void keyTyped(final KeyEvent e) { super.keyTyped(e); } protected void modeAddGeometryClear() { boolean cleared = clearOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); cleared |= clearOverlayAction(ACTION_ADD_GEOMETRY); if (cleared || !hasOverlayAction()) { this.addCompleteAction = null; this.addGeometry = null; this.addGeometryDataType = null; this.addGeometryEditVerticesStart = false; this.addGeometryPartDataType = null; this.addGeometryPartIndex = null; this.addLayer = null; setGeometryFactory(null); clearMouseOverLocations(); setXorGeometry(null); repaint(); } } protected boolean modeAddGeometryClick(final MouseEvent event) { final int modifiers = event.getModifiersEx(); if (modifiers == 0 && event.getButton() == MouseEvent.BUTTON1) { final int clickCount = event.getClickCount(); if (clickCount == 1) { if (isOverlayAction(ACTION_ADD_GEOMETRY) && !hasMouseOverLocation()) { Point point = getSnapPoint(); if (point == null) { point = getPoint(event); } final GeometryFactory geometryFactory = this.addLayer.getGeometryFactory(); point = point.newGeometry(geometryFactory); if (this.addGeometry.isEmpty()) { setAddGeometry(point); } else { final int[] toVertexId = Geometry.newVertexId(this.addGeometryPartIndex, 0); final Point previousPoint = this.addGeometry.getToVertex(toVertexId); if (!point.equals(previousPoint)) { final Geometry newGeometry = appendVertex(point); setAddGeometry(newGeometry); } } setXorGeometry(null); event.consume(); if (DataTypes.POINT.equals(this.addGeometryDataType)) { if (isOverlayAction(ACTION_ADD_GEOMETRY)) { if (isGeometryValid(this.addGeometry)) { try { setXorGeometry(null); if (this.addCompleteAction != null) { final Geometry geometry = this.addGeometry .newGeometry(this.addLayer.getGeometryFactory()); this.addCompleteAction.addComplete(this, geometry); modeAddGeometryClear(); } } finally { clearMapCursor(); } } } repaint(); } return true; } } else if (clickCount == 2) { if (isOverlayAction(ACTION_ADD_GEOMETRY) || isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { setXorGeometry(null); event.consume(); Point point = getSnapPoint(); if (point == null) { point = getPoint(event); } final GeometryFactory geometryFactory = this.addLayer.getGeometryFactory(); point = point.newGeometry(geometryFactory); final int[] toVertexId = Geometry.newVertexId(this.addGeometryPartIndex, 0); final Point previousPoint = this.addGeometry.getToVertex(toVertexId); if (!point.equals(previousPoint)) { final Geometry newGeometry = appendVertex(point); setAddGeometry(newGeometry); } if (isGeometryValid(this.addGeometry)) { try { setXorGeometry(null); if (this.addCompleteAction != null) { final Geometry geometry = this.addGeometry .newGeometry(this.addLayer.getGeometryFactory()); this.addCompleteAction.addComplete(this, geometry); modeAddGeometryClear(); } } finally { clearMapCursor(); } } repaint(); return true; } } } return false; } protected boolean modeAddGeometryDrag(final MouseEvent event) { if (isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { if (this.addGeometryEditVerticesStart) { Geometry xorGeometry = null; final List<CloseLocation> mouseOverLocations = getMouseOverLocations(); for (final CloseLocation location : mouseOverLocations) { final Geometry locationGeometry = getVertexGeometry(event, location); if (locationGeometry != null) { if (xorGeometry == null) { xorGeometry = locationGeometry; } else { xorGeometry = xorGeometry.union(locationGeometry); } } } setXorGeometry(xorGeometry); if (!hasSnapPoint()) { setMapCursor(CURSOR_NODE_EDIT); } return true; } } else if (isOverlayAction(ACTION_ADD_GEOMETRY)) { modeAddGeometryUpdateXorGeometry(); } return false; } protected void modeAddGeometryEditVerticesClear() { clearOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); clearMouseOverLocations(); this.addGeometryEditVerticesStart = false; } protected boolean modeAddGeometryFinish(final MouseEvent event) { if (isOverlayAction(ACTION_ADD_GEOMETRY)) { } else if (isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { if (this.addGeometryEditVerticesStart && hasMouseOverLocation()) { if (event.getButton() == MouseEvent.BUTTON1) { this.addGeometryEditVerticesStart = false; for (final CloseLocation location : getMouseOverLocations()) { final Geometry geometry = location.getGeometry(); final GeometryFactory geometryFactory = location.getGeometryFactory(); final Point point; if (getSnapPoint() == null) { point = getPoint(geometryFactory, event); } else { point = getSnapPoint().newGeometry(geometryFactory); } final int[] vertexIndex = location.getVertexId(); Geometry newGeometry; final Point newPoint = point; if (vertexIndex == null) { final int[] segmentIndex = location.getSegmentId(); final int[] newIndex = segmentIndex.clone(); newIndex[newIndex.length - 1] = newIndex[newIndex.length - 1] + 1; newGeometry = geometry.insertVertex(newPoint, newIndex); } else { newGeometry = geometry.moveVertex(newPoint, vertexIndex); } setAddGeometry(newGeometry); } setMouseOverLocationsDo(Collections.emptyList()); clearOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); return true; } } } return false; } protected boolean modeAddGeometryMove(final MouseEvent event) { if (this.addGeometry != null) { if (isOverlayAction(ACTION_ADD_GEOMETRY) || isOverlayAction(ACTION_MOVE_GEOMETRY) || isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { final MapPanel map = getMap(); final CloseLocation location = map.findCloseLocation(this.addLayer, null, this.addGeometry); final List<CloseLocation> locations = new ArrayList<>(); if (location != null) { locations.add(location); } final boolean hasMouseOver = setMouseOverLocations(locations); if (event != null && event.isAltDown()) { if (hasMouseOver) { setOverlayAction(ACTION_MOVE_GEOMETRY); } else { clearOverlayAction(ACTION_MOVE_GEOMETRY); if (!hasMouseOverLocation()) { clearMapCursor(); } } } else { clearOverlayAction(ACTION_MOVE_GEOMETRY); // TODO make work with multi-part if (hasMouseOver) { setOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); } else { clearOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); modeAddGeometryUpdateXorGeometry(); } } return true; } } return false; } protected boolean modeAddGeometryStart(final MouseEvent event) { final int modifiers = event.getModifiersEx(); if (modifiers == InputEvent.BUTTON1_DOWN_MASK) { if (isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { if (hasMouseOverLocation()) { this.addGeometryEditVerticesStart = true; repaint(); return true; } } } return false; } protected void modeAddGeometryUpdateXorGeometry() { final Point point = getOverlayPoint(); if (!hasSnapPoint()) { setMapCursor(CURSOR_NODE_ADD); } final int[] firstVertexId = Geometry.newVertexId(this.addGeometryPartIndex, 0); Geometry xorGeometry = null; if (DataTypes.POINT.equals(this.addGeometryPartDataType)) { } else { Vertex firstVertex; final Vertex toVertex; if (this.addGeometry instanceof LineString) { firstVertex = this.addGeometry.getVertex(0); toVertex = this.addGeometry.getToVertex(0); } else { firstVertex = this.addGeometry.getVertex(firstVertexId); toVertex = this.addGeometry.getToVertex(firstVertexId); } final GeometryFactory geometryFactory = this.addLayer.getGeometryFactory(); if (toVertex != null && !toVertex.isEmpty()) { if (DataTypes.LINE_STRING.equals(this.addGeometryPartDataType)) { xorGeometry = newXorLine(geometryFactory, toVertex, point); } else if (DataTypes.POLYGON.equals(this.addGeometryPartDataType)) { if (toVertex.equals(firstVertex)) { xorGeometry = newXorLine(geometryFactory, toVertex, point); } else { final Point p1 = geometryFactory.point(toVertex); final Point p3 = geometryFactory.point(firstVertex); final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory2d(); xorGeometry = viewportGeometryFactory.lineString(p1, point, p3); } } } } setXorGeometry(xorGeometry); } protected void modeEditGeometryVerticesClear() { clearOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES); clearMouseOverLocations(); this.editGeometryVerticesStart = false; } protected boolean modeEditGeometryVerticesDrag(final MouseEvent event) { if (this.editGeometryVerticesStart && isOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES)) { this.dragged = true; Geometry xorGeometry = null; for (final CloseLocation location : getMouseOverLocations()) { final Geometry locationGeometry = getVertexGeometry(event, location); if (locationGeometry != null) { if (xorGeometry == null) { xorGeometry = locationGeometry; } else { xorGeometry = xorGeometry.union(locationGeometry); } } } setXorGeometry(xorGeometry); if (!hasSnapPoint()) { setMapCursor(CURSOR_NODE_ADD); } return true; } return false; } protected boolean modeEditGeometryVerticesFinish(final MouseEvent event) { if (this.editGeometryVerticesStart && clearOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES)) { if (this.dragged && event.getButton() == MouseEvent.BUTTON1) { try { final MultipleUndo edit = new MultipleUndo(); final List<CloseLocation> locations = getMouseOverLocations(); for (final CloseLocation location : locations) { final Geometry geometry = location.getGeometry(); final GeometryFactory geometryFactory = location.getGeometryFactory(); final Point point; final Point snapPoint = getSnapPoint(); if (snapPoint == null) { point = getPoint(geometryFactory, event); } else { point = snapPoint.newGeometry(geometryFactory); } final int[] vertexIndex = location.getVertexId(); Geometry newGeometry; final Point newPoint = point; if (vertexIndex == null) { final int[] segmentIndex = location.getSegmentId(); final int[] newIndex = segmentIndex.clone(); newIndex[newIndex.length - 1] = newIndex[newIndex.length - 1] + 1; newGeometry = geometry.insertVertex(newPoint, newIndex); } else { newGeometry = geometry.moveVertex(newPoint, vertexIndex); } final UndoableEdit geometryEdit = setGeometry(location, newGeometry); edit.addEdit(geometryEdit); } if (!edit.isEmpty()) { addUndo(edit); } } finally { modeEditGeometryVerticesClear(); } return true; } } return false; } protected boolean modeEditGeometryVerticesMove(final MouseEvent event) { if (canOverrideOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES) || isOverlayAction(ACTION_MOVE_GEOMETRY)) { final double scale = getViewport().getScale(); final List<CloseLocation> closeLocations = new ArrayList<>(); final MapPanel map = getMap(); for (final CloseLocation location : map.getCloseSelectedLocations()) { final AbstractRecordLayer layer = location.getLayer(); if (layer.isEditable(scale)) { closeLocations.add(location); } } if (closeLocations.isEmpty()) { modeMoveGeometryClear(); modeEditGeometryVerticesClear(); } else if (event.isAltDown()) { setOverlayAction(ACTION_MOVE_GEOMETRY); } else { setOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES); } return setMouseOverLocations(closeLocations); } return false; } protected boolean modeEditGeometryVerticesStart(final MouseEvent event) { final int modifiers = event.getModifiersEx(); if (modifiers == InputEvent.BUTTON1_DOWN_MASK) { if (isOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES)) { this.editGeometryVerticesStart = true; repaint(); return true; } } return false; } protected void modeMoveGeometryClear() { clearOverlayAction(ACTION_MOVE_GEOMETRY); this.moveGeometryStart = null; this.moveGeometryEnd = null; this.moveGeometryLocations = null; clearMouseOverLocations(); } protected boolean modeMoveGeometryDrag(final MouseEvent event) { if (isOverlayAction(ACTION_MOVE_GEOMETRY)) { this.moveGeometryEnd = getEventPoint(); repaint(); return true; } return false; } protected boolean modeMoveGeometryFinish(final MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1) { if (clearOverlayAction(ACTION_MOVE_GEOMETRY)) { clearOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES); clearOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES); for (final CloseLocation location : this.moveGeometryLocations) { final GeometryFactory geometryFactory = location.getGeometryFactory(); final Point from = this.moveGeometryStart.convertGeometry(geometryFactory); final Point to = this.moveGeometryEnd.convertGeometry(geometryFactory); final double deltaX = to.getX() - from.getX(); final double deltaY = to.getY() - from.getY(); if (deltaX != 0 || deltaY != 0) { final Geometry geometry = location.getGeometry(); final Geometry newGeometry = geometry.move(deltaX, deltaY); final UndoableEdit geometryEdit = setGeometry(location, newGeometry); addUndo(geometryEdit); } } modeMoveGeometryClear(); repaint(); return true; } } return false; } protected boolean modeMoveGeometryStart(final MouseEvent event) { if (isOverlayAction(ACTION_MOVE_GEOMETRY) && event.getButton() == MouseEvent.BUTTON1) { this.moveGeometryStart = this.moveGeometryEnd = getEventPoint(); this.moveGeometryLocations = getMouseOverLocations(); clearMouseOverLocations(); return true; } return false; } private boolean modePopupMenu(final MouseEvent event) { if (event.isPopupTrigger()) { for (final CloseLocation location : getMouseOverLocations()) { final LayerRecord record = location.getRecord(); if (record != null) { final LayerRecordMenu menu = record.getMenu(); menu.showMenu(record, event); } return true; } } return false; } @Override public void mouseClicked(final MouseEvent event) { if (modeAddGeometryClick(event)) { } else if (SwingUtil.isLeftButtonAndNoModifiers(event) && event.getClickCount() == 2) { final List<LayerRecord> records = new ArrayList<>(); final BoundingBox boundingBox = getHotspotBoundingBox(); final Geometry boundary = boundingBox.toPolygon().prepare(); addRecords(records, getProject(), boundary); final int size = records.size(); if (size == 0) { } else if (size < 10) { for (final LayerRecord record : records) { final AbstractRecordLayer layer = record.getLayer(); layer.showForm(record); } event.consume(); } else { JOptionPane.showMessageDialog(this, "There are too many " + size + " selected to view. Maximum 10. Select fewer records or move mouse to middle of geometry.", "Too Many Selected Records", JOptionPane.ERROR_MESSAGE); event.consume(); } } } @Override public void mouseDragged(final MouseEvent event) { if ((event.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) == InputEvent.BUTTON1_DOWN_MASK) { this.dragged = true; if (modeMoveGeometryDrag(event)) { } else if (modeAddGeometryDrag(event)) { } else if (modeEditGeometryVerticesDrag(event)) { } } } @Override public void mouseExited(final MouseEvent e) { if (isOverlayAction(ACTION_EDIT_GEOMETRY_VERTICES)) { } else if (isOverlayAction(ACTION_MOVE_GEOMETRY)) { } else if (isOverlayAction(ACTION_ADD_GEOMETRY)) { } else if (isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { } else { cancel(); } } @Override public void mouseMoved(final MouseEvent event) { if (modeAddGeometryMove(event)) { } else if (modeEditGeometryVerticesMove(event)) { } } @Override public void mousePressed(final MouseEvent event) { if (modeAddGeometryStart(event)) { } else if (modeMoveGeometryStart(event)) { } else if (modeEditGeometryVerticesStart(event)) { } else if (modePopupMenu(event)) { } } @Override public void mouseReleased(final MouseEvent event) { if (modeAddGeometryFinish(event)) { } else if (modeMoveGeometryFinish(event)) { } else if (modeEditGeometryVerticesFinish(event)) { } else if (modePopupMenu(event)) { } if (event.getButton() == MouseEvent.BUTTON1) { if (this.dragged) { this.dragged = false; } } } @Override public void paintComponent(final Viewport2D viewport, final Graphics2D graphics) { final GeometryFactory geometryFactory2dFloating = getViewportGeometryFactory2d(); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (isOverlayAction(ACTION_MOVE_GEOMETRY) && this.moveGeometryStart != null) { try ( BaseCloseable transformCloseable = viewport.setUseModelCoordinates(graphics, true)) { for (final CloseLocation location : this.moveGeometryLocations) { Geometry geometry = location.getGeometry(); final GeometryFactory geometryFactory = location.getGeometryFactory(); final Point from = this.moveGeometryStart.convertGeometry(geometryFactory); final Point to = this.moveGeometryEnd.convertGeometry(geometryFactory); final double deltaX = to.getX() - from.getX(); final double deltaY = to.getY() - from.getY(); geometry = geometry.move(deltaX, deltaY); GEOMETRY_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, geometry); GEOMETRY_VERTEX_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, geometry); } } } else if (this.addGeometry != null) { try ( BaseCloseable transformCloseable = viewport.setUseModelCoordinates(graphics, true)) { graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); GEOMETRY_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, this.addGeometry); GEOMETRY_VERTEX_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, this.addGeometry); } } if (this.moveGeometryStart == null) { final List<CloseLocation> mouseOverLocations = getMouseOverLocations(); try ( BaseCloseable transformCloseable = viewport.setUseModelCoordinates(graphics, true)) { for (final CloseLocation location : mouseOverLocations) { final Geometry geometry = location.getGeometry(); GEOMETRY_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, geometry); } } for (final CloseLocation location : mouseOverLocations) { final Geometry geometry = location.getGeometry(); GEOMETRY_VERTEX_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, geometry); if (!isOverlayAction(ACTION_MOVE_GEOMETRY) && !this.addGeometryEditVerticesStart && !this.editGeometryVerticesStart) { final Vertex vertex = location.getVertex(); if (vertex == null) { final MarkerStyle style = MarkerStyle.marker("xLine", 9, WebColors.Blue, 3, WebColors.Blue); final double orientation = location.getSegment().getOrientaton(); final Point pointOnLine = location.getViewportPoint(); MarkerStyleRenderer.renderMarker(viewport, graphics, pointOnLine, style, orientation); } else { GEOMETRY_CLOSE_VERTEX_RENDERER.paintSelected(viewport, graphics, geometryFactory2dFloating, vertex); } } } } drawXorGeometry(graphics); } @Override public void propertyChange(final PropertyChangeEvent event) { super.propertyChange(event); final Object source = event.getSource(); final String propertyName = event.getPropertyName(); if ("preEditable".equals(propertyName)) { if (isOverlayAction(ACTION_ADD_GEOMETRY)) { if (isGeometryValid(this.addGeometry)) { try { setXorGeometry(null); if (this.addCompleteAction != null) { final Geometry geometry = this.addGeometry .newGeometry(this.addLayer.getGeometryFactory()); this.addCompleteAction.addComplete(this, geometry); modeAddGeometryClear(); } } finally { clearMapCursor(); } } } } else if ("editable".equals(propertyName)) { repaint(); if (source == this.addLayer) { // if (!isEditable(addLayer)) { // setEditingObject(null, null); // } } } else if (source instanceof LayerRecord) { if (event.getNewValue() instanceof Geometry) { // TODO update mouse over locations // clearMouseOverLocations(); } } } private void setAddGeometry(final Geometry geometry) { if (!DataTypes.GEOMETRY.equals(geometry, this.addGeometry)) { final AddGeometryUndoEdit undo = new AddGeometryUndoEdit(geometry); addUndo(undo); repaint(); } } protected void setAddGeometryDataType(final DataType dataType) { this.addGeometryDataType = dataType; this.addGeometryPartDataType = getGeometryPartDataType(dataType); } protected UndoableEdit setGeometry(final CloseLocation location, final Geometry newGeometry) { if (isOverlayAction(ACTION_ADD_GEOMETRY) || isOverlayAction(ACTION_ADD_GEOMETRY_EDIT_VERTICES)) { if (DataTypes.GEOMETRY.equals(newGeometry, this.addGeometry)) { return null; } else { return new AddGeometryUndoEdit(newGeometry); } } else { final LayerRecord record = location.getRecord(); final String geometryFieldName = record.getGeometryFieldName(); final Geometry oldValue = record.getGeometry(); if (DataTypes.GEOMETRY.equals(newGeometry, oldValue)) { return null; } else { final AbstractRecordLayer layer = location.getLayer(); return layer.newSetFieldUndo(record, geometryFieldName, oldValue, newGeometry); } } } protected boolean setMouseOverLocations(final List<CloseLocation> mouseOverLocations) { if (this.mouseOverLocations.equals(mouseOverLocations)) { return !this.mouseOverLocations.isEmpty(); } else { setMouseOverLocationsDo(mouseOverLocations); setSnapPoint(null); setXorGeometry(null); return updateMouseOverLocations(); } } private void setMouseOverLocationsDo(final List<CloseLocation> mouseOverLocations) { this.mouseOverLocations = mouseOverLocations; } // K key to split a record protected boolean splitLineKeyPress(final KeyEvent e) { final int keyCode = e.getKeyCode(); if (keyCode == KeyEvent.VK_K) { if (!isOverlayAction(ACTION_ADD_GEOMETRY) && hasMouseOverLocation()) { for (final CloseLocation mouseLocation : getMouseOverLocations()) { final LayerRecord record = mouseLocation.getRecord(); final AbstractRecordLayer layer = record.getLayer(); layer.splitRecord(record, mouseLocation); } e.consume(); return true; } } return false; } private boolean updateMouseOverLocations() { final MapPanel map = getMap(); if (hasMouseOverLocation()) { if (isOverlayAction(ACTION_MOVE_GEOMETRY)) { map.clearToolTipText(); } else { final Map<String, Set<CloseLocation>> vertexLocations = new TreeMap<>(); final Map<String, Set<CloseLocation>> segmentLocations = new TreeMap<>(); for (final CloseLocation location : getMouseOverLocations()) { final String typePath = location.getLayerPath(); if (location.getVertexId() == null) { Maps.addToSet(segmentLocations, typePath, location); } else { Maps.addToSet(vertexLocations, typePath, location); } } final StringBuilder text = new StringBuilder("<html>"); appendLocations(text, "Move Vertices", vertexLocations); appendLocations(text, "Insert Vertices", segmentLocations); text.append("</html>"); final Point2D eventPoint = getEventPosition(); map.setToolTipText(eventPoint, text); } return true; } else { map.clearToolTipText(); return false; } } }