/* * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI * for visualizing and manipulating spatial features with geometry and attributes. * * Copyright (C) 2003 Vivid Solutions * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * For more information, contact: * * Vivid Solutions * Suite #1A * 2328 Government Street * Victoria BC V8T 5G5 * Canada * * (250)385-6040 * www.vividsolutions.com */ package com.vividsolutions.jump.workbench.ui.cursortool.editing; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Toolkit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.CoordinateList; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.util.Assert; import com.vividsolutions.jump.I18N; import com.vividsolutions.jump.feature.Feature; import com.vividsolutions.jump.feature.FeatureUtil; import com.vividsolutions.jump.workbench.model.Layer; import com.vividsolutions.jump.workbench.model.StandardCategoryNames; import com.vividsolutions.jump.workbench.model.UndoableCommand; import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn; import com.vividsolutions.jump.workbench.ui.EditTransaction; import com.vividsolutions.jump.workbench.ui.GeometryEditor; import com.vividsolutions.jump.workbench.ui.LayerNamePanelProxy; import com.vividsolutions.jump.workbench.ui.LayerViewPanel; import com.vividsolutions.jump.workbench.ui.cursortool.AbstractCursorTool; import com.vividsolutions.jump.workbench.ui.cursortool.CursorTool; import com.vividsolutions.jump.workbench.ui.cursortool.DelegatingTool; import com.vividsolutions.jump.workbench.ui.images.IconLoader; import com.vividsolutions.jump.workbench.ui.plugin.AddNewLayerPlugIn; public class FeatureDrawingUtil { private Collection selectedFeaturesContaining(Polygon polygon, LayerViewPanel panel) { if (layerNamePanelProxy.getLayerNamePanel().chooseEditableLayer() == null) { return new ArrayList(); } ArrayList selectedFeaturesContainingPolygon = new ArrayList(); for (Iterator i = panel.getSelectionManager().getFeaturesWithSelectedItems(layerNamePanelProxy.getLayerNamePanel().chooseEditableLayer()).iterator(); i.hasNext(); ) { Feature feature = (Feature) i.next(); //Unfortunately, GeometryCollection does not yet support either //#contains or (more importantly) #difference. [Jon Aquino] //Use == rather than instanceof because MultiPoint, MultiLineString and //MultiPolygon do not have this problem. [Jon Aquino] if (feature.getGeometry().getClass() == GeometryCollection.class) { continue; } if (!feature.getGeometry().getEnvelopeInternal().contains(polygon.getEnvelopeInternal())) { continue; } if (feature.getGeometry().contains(polygon)) { selectedFeaturesContainingPolygon.add(feature); } } return selectedFeaturesContainingPolygon; } private void createHole( Polygon hole, Collection features, Layer layer, LayerViewPanel panel, boolean rollingBackInvalidEdits, String transactionName) { Assert.isTrue(hole.getNumInteriorRing() == 0); EditTransaction transaction = new EditTransaction(features, transactionName, layer, rollingBackInvalidEdits, false, panel); for (int i = 0; i < transaction.size(); i++) { transaction.setGeometry(i, transaction.getGeometry(i).difference(hole)); } transaction.commit(); } private LayerNamePanelProxy layerNamePanelProxy; public FeatureDrawingUtil(LayerNamePanelProxy layerNamePanelProxy) { this.layerNamePanelProxy = layerNamePanelProxy; } private Layer layer(LayerViewPanel layerViewPanel) { if (layerNamePanelProxy.getLayerNamePanel().chooseEditableLayer() == null) { Layer layer = layerViewPanel.getLayerManager().addLayer( StandardCategoryNames.WORKING, I18N.get("ui.cursortool.editing.FeatureDrawingUtil.new"), AddNewLayerPlugIn.createBlankFeatureCollection()); layer.setEditable(true); layerViewPanel.getContext().warnUser( I18N.get("ui.cursortool.editing.FeatureDrawingUtil.no-layer-is-editable-creating-new-editable-layer")); } return layerNamePanelProxy.getLayerNamePanel().chooseEditableLayer(); } /** * The calling CursorTool should call #preserveUndoHistory; otherwise the * undo history will be (unnecessarily) truncated if a problem occurs. * @return null if the geometry is invalid */ public UndoableCommand createAddCommand( Geometry geometry, boolean rollingBackInvalidEdits, LayerViewPanel layerViewPanel, AbstractCursorTool tool) { if (rollingBackInvalidEdits && !geometry.isValid()) { layerViewPanel.getContext().warnUser(I18N.get("ui.cursortool.editing.FeatureDrawingUtil.draw-feature-tool-topology-error")); return null; } //Don't want viewport to change at this stage. [Jon Aquino] layerViewPanel.setViewportInitialized(true); final Layer layer = layer(layerViewPanel); final Feature feature = FeatureUtil.toFeature(editor.removeRepeatedPoints(geometry), layer.getFeatureCollectionWrapper().getFeatureSchema()); return new UndoableCommand(tool.getName()) { public void execute() { layer.getFeatureCollectionWrapper().add(feature); } public void unexecute() { layer.getFeatureCollectionWrapper().remove(feature); } }; } private GeometryEditor editor = new GeometryEditor(); /** * Apply settings common to all feature-drawing tools. */ public CursorTool prepare(final AbstractCursorTool drawFeatureTool, boolean allowSnapping) { drawFeatureTool.setColor(Color.red); if (allowSnapping) { drawFeatureTool.allowSnapping(); } return new DelegatingTool(drawFeatureTool) { public String getName() { return drawFeatureTool.getName(); } public Cursor getCursor() { if (Toolkit .getDefaultToolkit() .getBestCursorSize(32, 32) .equals(new Dimension(0, 0))) { return Cursor.getDefaultCursor(); } return Toolkit.getDefaultToolkit().createCustomCursor( IconLoader.icon("Pen.gif").getImage(), new java.awt.Point(1, 31), drawFeatureTool.getName()); } }; } public void drawRing( Polygon polygon, boolean rollingBackInvalidEdits, AbstractCursorTool tool, LayerViewPanel panel) { Collection selectedFeaturesContainingPolygon = selectedFeaturesContaining(polygon, panel); if (selectedFeaturesContainingPolygon.isEmpty()) { AbstractPlugIn.execute(createAddCommand(polygon, rollingBackInvalidEdits, panel, tool), panel); } else { createHole( polygon, selectedFeaturesContainingPolygon, layer(panel), panel, rollingBackInvalidEdits, tool.getName()); } } private Collection selectedFeaturesMatchingEndPoint( LineString lineString, LayerViewPanel panel) { if (layerNamePanelProxy.getLayerNamePanel().chooseEditableLayer() == null) { return new ArrayList(); } ArrayList selectedFeaturesMatchingEndPoints = new ArrayList(); for (Iterator i = panel.getSelectionManager().getFeaturesWithSelectedItems( layerNamePanelProxy.getLayerNamePanel(). chooseEditableLayer()).iterator(); i.hasNext(); ) { Feature feature = (Feature) i.next(); if (!(feature.getGeometry() instanceof LineString)) { continue; } LineString lineGeom = ((LineString) feature.getGeometry()); if (!( lineGeom.getCoordinate() .equals(lineString.getCoordinate()) //1=1 || lineGeom.getCoordinate() .equals(lineString.getCoordinateN(lineString.getNumPoints()-1)) //1=n || lineGeom.getCoordinateN(lineGeom.getNumPoints()-1) .equals(lineString.getCoordinate()) //m=1 || lineGeom.getCoordinateN(lineGeom.getNumPoints()-1) .equals(lineString.getCoordinateN(lineString.getNumPoints()-1)))) //m=n continue; selectedFeaturesMatchingEndPoints.add(feature); } return selectedFeaturesMatchingEndPoints; } /** * @param lineString to reverse * @return new LineString made from old LineString's points in reverse order */ public LineString reverse(LineString lineString) { CoordinateList coordList = new CoordinateList(lineString.getCoordinates()); Collections.reverse(coordList); return new GeometryFactory().createLineString(coordList.toCoordinateArray()); } /** * @param ls1 first LineString to concatenate * @param ls2 second LineString to concatenate * @return new LineString made of (first - last point) + second */ public LineString concatLineStrings(LineString ls1, LineString ls2) { CoordinateList coordList1 = new CoordinateList(ls1.getCoordinates()); CoordinateList coordList2 = new CoordinateList(ls2.getCoordinates()); coordList1.remove(coordList1.size()-1); coordList1.addAll(coordList2); return new GeometryFactory().createLineString(coordList1.toCoordinateArray()); } /** * @param ls1 first LineString to merge * @param ls2 second LineString to merge * @return merged LineString if end point in common, otherwise return second LineString */ public LineString mergeLineStrings(LineString ls1, LineString ls2) { if (ls1.getCoordinateN(ls1.getNumPoints()-1).equals(ls2.getCoordinate())) { return concatLineStrings(ls1, ls2); }else if (ls1.getCoordinateN(ls1.getNumPoints()-1).equals(ls2.getCoordinateN(ls2.getNumPoints()-1))) { return concatLineStrings(ls1, reverse(ls2)); } else if (ls1.getCoordinate().equals(ls2.getCoordinate())) { return concatLineStrings(reverse(ls2), ls1); } else if (ls1.getCoordinate().equals(ls2.getCoordinateN(ls2.getNumPoints()-1))) { return concatLineStrings(ls2, ls1); } else { return ls2; } } /** * Implement the special check for adding to the end of a selected LineString * @param newLineString LineString to create or add to selected * @param rollingBackInvalidEdits * @param tool AbstractCursorTool - the current cursor tool * @param panel LayerViewPanel */ public void drawLineString( LineString newLineString, boolean rollingBackInvalidEdits, AbstractCursorTool tool, LayerViewPanel panel) { Collection matchingLineStringFeatures = selectedFeaturesMatchingEndPoint(newLineString, panel); if (matchingLineStringFeatures.size() == 0) { AbstractPlugIn.execute( createAddCommand(newLineString, rollingBackInvalidEdits, panel, tool), panel); } else { LineString oldLineString = null; Iterator iter=matchingLineStringFeatures.iterator(); EditTransaction transaction = new EditTransaction(matchingLineStringFeatures, tool.getName(), layer(panel), rollingBackInvalidEdits, true, panel); Geometry empty = new GeometryFactory().createLineString(new Coordinate[0]); for (int i = 0; i < transaction.size(); i++) { oldLineString = (LineString) ((Feature) iter.next()).getGeometry(); newLineString = mergeLineStrings(oldLineString, newLineString); if (i > 0) transaction.setGeometry(transaction.getFeature(i), empty); } transaction.setGeometry(0, newLineString ); transaction.commit(); } } }