001: /*
002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
003: * for visualizing and manipulating spatial features with geometry and attributes.
004: *
005: * JUMP is Copyright (C) 2003 Vivid Solutions
006: *
007: * This program implements extensions to JUMP and is
008: * Copyright (C) 2004 Integrated Systems Analysts, Inc.
009: *
010: * This program is free software; you can redistribute it and/or
011: * modify it under the terms of the GNU General Public License
012: * as published by the Free Software Foundation; either version 2
013: * of the License, or (at your option) any later version.
014: *
015: * This program is distributed in the hope that it will be useful,
016: * but WITHOUT ANY WARRANTY; without even the implied warranty of
017: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
018: * GNU General Public License for more details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with this program; if not, write to the Free Software
022: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
023: *
024: * For more information, contact:
025: *
026: * Integrated Systems Analysts, Inc.
027: * 630C Anchors St., Suite 101
028: * Fort Walton Beach, Florida
029: * USA
030: *
031: * (850)862-7321
032: */
033:
034: package org.openjump.core.ui.plugin.tools;
035:
036: import java.util.Collection;
037: import java.util.Iterator;
038:
039: import javax.swing.JComponent;
040:
041: import org.openjump.core.geomutils.Arc;
042: import org.openjump.core.geomutils.MathVector;
043:
044: import com.vividsolutions.jts.geom.Coordinate;
045: import com.vividsolutions.jts.geom.CoordinateList;
046: import com.vividsolutions.jts.geom.Geometry;
047: import com.vividsolutions.jts.geom.GeometryFactory;
048: import com.vividsolutions.jts.geom.LineString;
049: import com.vividsolutions.jts.geom.LinearRing;
050: import com.vividsolutions.jts.geom.Polygon;
051: import com.vividsolutions.jump.I18N;
052: import com.vividsolutions.jump.feature.Feature;
053: import com.vividsolutions.jump.feature.FeatureDataset;
054: import com.vividsolutions.jump.workbench.WorkbenchContext;
055: import com.vividsolutions.jump.workbench.model.LayerManager;
056: import com.vividsolutions.jump.workbench.model.StandardCategoryNames;
057: import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
058: import com.vividsolutions.jump.workbench.plugin.EnableCheck;
059: import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
060: import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
061: import com.vividsolutions.jump.workbench.plugin.PlugInContext;
062: import com.vividsolutions.jump.workbench.ui.MenuNames;
063: import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
064:
065: public class JoinWithArcPlugIn extends AbstractPlugIn {
066: private WorkbenchContext workbenchContext;
067:
068: private final static String sJoinWithArc = I18N
069: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.Join-With-Arc");
070: private final static String sNew = I18N
071: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.New");
072: private final static String sTheArcRadius = I18N
073: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.The-arc-radius");
074: private final static String sBetween = I18N
075: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.Between");
076: private final static String sAnd = I18N
077: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.and");
078: private final static String sFeaturesMustBeSelected = I18N
079: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.features-must-be-selected");
080:
081: private final static String RADIUS = I18N
082: .get("org.openjump.core.ui.plugin.tools.JoinWithArcPlugIn.Radius");
083: private MultiInputDialog dialog;
084: private double arcRadius = 50.0;
085:
086: public void initialize(PlugInContext context) throws Exception {
087: workbenchContext = context.getWorkbenchContext();
088: context.getFeatureInstaller().addMainMenuItemWithJava14Fix(
089: this ,
090: new String[] { MenuNames.TOOLS,
091: MenuNames.TOOLS_EDIT_GEOMETRY }, getName(),
092: false, null, this .createEnableCheck(workbenchContext));
093: }
094:
095: public String getName() {
096: return sJoinWithArc;
097: }
098:
099: public boolean execute(final PlugInContext context)
100: throws Exception {
101: reportNothingToUndoYet(context);
102: Collection selectedFeatures = context.getLayerViewPanel()
103: .getSelectionManager().getFeaturesWithSelectedItems();
104:
105: //get the arc radius
106: MultiInputDialog dialog = new MultiInputDialog(context
107: .getWorkbenchFrame(), getName(), true);
108: setDialogValues(dialog, context);
109: dialog.setVisible(true);
110: if (!dialog.wasOKPressed()) {
111: return false;
112: }
113: getDialogValues(dialog);
114:
115: Geometry fillet = null;
116:
117: if (selectedFeatures.size() == 1) {
118: Geometry geo = ((Feature) selectedFeatures.iterator()
119: .next()).getGeometry();
120: if (geo instanceof LinearRing) {
121: fillet = filletLinearRing((LinearRing) geo);
122: } else if (geo instanceof LineString) {
123: fillet = filletOneLineString((LineString) geo);
124: } else if (geo instanceof Polygon) {
125: fillet = filletPolygon((Polygon) geo);
126: }
127: } else if (selectedFeatures.size() == 2) {
128: Iterator i = selectedFeatures.iterator();
129: Feature feature1 = (Feature) i.next();
130: Feature feature2 = (Feature) i.next();
131: Geometry geo1 = feature1.getGeometry();
132: Geometry geo2 = feature2.getGeometry();
133:
134: if ((geo1 instanceof LineString)
135: && (geo2 instanceof LineString)) {
136: fillet = filletTwoLineStrings((LineString) geo1,
137: (LineString) geo2);
138: }
139: }
140:
141: if (fillet != null) {
142: Feature currFeature = (Feature) selectedFeatures.iterator()
143: .next();
144: Feature newFeature = (Feature) currFeature.clone();
145: newFeature.setGeometry(fillet);
146: Collection selectedCategories = context.getLayerNamePanel()
147: .getSelectedCategories();
148: LayerManager layerManager = context.getLayerManager();
149: FeatureDataset newFeatures = new FeatureDataset(currFeature
150: .getSchema());
151: newFeatures.add(newFeature);
152:
153: layerManager
154: .addLayer(
155: selectedCategories.isEmpty() ? StandardCategoryNames.WORKING
156: : selectedCategories.iterator()
157: .next().toString(),
158: layerManager.uniqueLayerName(sNew),
159: newFeatures);
160:
161: layerManager.getLayer(0).setFeatureCollectionModified(true);
162: layerManager.getLayer(0).setEditable(true);
163: }
164: return true;
165: }
166:
167: private void setDialogValues(MultiInputDialog dialog,
168: PlugInContext context) {
169: dialog.addDoubleField(RADIUS, arcRadius, 6, sTheArcRadius);
170: }
171:
172: private void getDialogValues(MultiInputDialog dialog) {
173: arcRadius = dialog.getDouble(RADIUS);
174: }
175:
176: public MultiEnableCheck createEnableCheck(
177: final WorkbenchContext workbenchContext) {
178: EnableCheckFactory checkFactory = new EnableCheckFactory(
179: workbenchContext);
180: return new MultiEnableCheck()
181: .add(
182: checkFactory
183: .createWindowWithLayerViewPanelMustBeActiveCheck())
184: .add(
185: checkFactory
186: .createOnlyOneLayerMayHaveSelectedFeaturesCheck())
187: .add(
188: createBetweenNAndMFeaturesMustBeSelectedCheck(
189: 1, 2))
190: .add(
191: checkFactory
192: .createAtLeastNFeaturesMustHaveSelectedItemsCheck(1));
193: }
194:
195: private EnableCheck createBetweenNAndMFeaturesMustBeSelectedCheck(
196: final int n, final int m) {
197: return new EnableCheck() {
198: public String check(JComponent component) {
199: int numSelected = workbenchContext.getLayerViewPanel()
200: .getSelectionManager()
201: .getFeaturesWithSelectedItems().size();
202: return (((numSelected > m) || (numSelected < n)) ? (sBetween
203: + " " + n + " " + sAnd + " " + m + " " + sFeaturesMustBeSelected)
204: : null);
205: }
206: };
207: }
208:
209: private Coordinate Intersect(Coordinate P1, Coordinate P2,
210: Coordinate P3, Coordinate P4) //find intersection of two lines
211: {
212: Coordinate V = new Coordinate((P2.x - P1.x), (P2.y - P1.y));
213: Coordinate W = new Coordinate((P4.x - P3.x), (P4.y - P3.y));
214: double n = W.y * (P3.x - P1.x) - W.x * (P3.y - P1.y);
215: double d = W.y * V.x - W.x * V.y;
216:
217: if (d != 0.0) {
218: double t1 = n / d;
219: Coordinate E = new Coordinate((P1.x + V.x * t1),
220: (P1.y + V.y * t1));
221: return E;
222: } else {
223: return null;
224: }
225: }
226:
227: private LineString MakeRoundCorner(Coordinate A, Coordinate B,
228: Coordinate C, Coordinate D, double r, boolean arcOnly) {
229: MathVector Gv = new MathVector();
230: MathVector Hv;
231: MathVector Fv;
232: Coordinate E = Intersect(A, B, C, D); //vector solution
233:
234: if (E != null) //non-parallel lines
235: {
236: MathVector Ev = new MathVector(E);
237:
238: if (E.distance(B) > E.distance(A)) //find longest distance from intersection
239: { //these equations assume B and D are closest to the intersection
240: //reverse points
241: Coordinate temp = A;
242: A = B;
243: B = temp;
244: }
245:
246: if (E.distance(D) > E.distance(C)) //find longest distance from intersection
247: { //these equations assume B and D are closest to the intersection
248: //reverse points
249: Coordinate temp = C;
250: C = D;
251: D = temp;
252: }
253:
254: MathVector Av = new MathVector(A);
255: MathVector Cv = new MathVector(C);
256: double alpha = Av.vectorBetween(Ev).angleRad(
257: Cv.vectorBetween(Ev)) / 2.0; //we only need the half angle
258: double h1 = Math.abs(r / Math.sin(alpha)); //from definition of sine solved for h
259:
260: if ((h1 * h1 - r * r) >= 0) {
261: double d1 = Math.sqrt(h1 * h1 - r * r); //pythagorean theorem}
262: double theta = Math.PI / 2.0 - alpha; //sum of triangle interior angles = 180 degrees
263: theta = theta * 2.0; //we only need the double angle}
264: //we now have the angles and distances needed for a vector solution:
265: //we must find the points G and H by vector addition.
266: Gv = Ev.add(Av.vectorBetween(Ev).unit().scale(d1));
267: Hv = Ev.add(Cv.vectorBetween(Ev).unit().scale(d1));
268: Fv = Ev.add(Gv.vectorBetween(Ev).rotateRad(alpha)
269: .unit().scale(h1));
270:
271: if (Math.abs(Fv.distance(Hv) - Fv.distance(Gv)) > 1.0) //rotated the wrong dirction
272: {
273: Fv = Ev.add(Gv.vectorBetween(Ev).rotateRad(-alpha)
274: .unit().scale(h1));
275: theta = -theta;
276: }
277:
278: CoordinateList coordinates = new CoordinateList();
279: if (!arcOnly)
280: coordinates.add(C);
281: Arc arc = new Arc(Fv.getCoord(), Hv.getCoord(), Math
282: .toDegrees(theta));
283: LineString lineString = arc.getLineString();
284: coordinates.add(lineString.getCoordinates(), false);
285: if (!arcOnly)
286: coordinates.add(A);
287: return new GeometryFactory()
288: .createLineString(coordinates
289: .toCoordinateArray());
290: }
291: }
292: return null;
293: }
294:
295: private LineString filletTwoLineStrings(LineString ls1,
296: LineString ls2) {
297: Coordinate A = ls1.getCoordinateN(0);
298: Coordinate B = ls1.getCoordinateN(1);
299: Coordinate C = ls2.getCoordinateN(0);
300: Coordinate D = ls2.getCoordinateN(1);
301: LineString lineString = MakeRoundCorner(A, B, C, D, arcRadius,
302: false);
303: if (lineString != null) {
304: CoordinateList coordinates = new CoordinateList();
305: coordinates.add(lineString.getCoordinates(), false);
306: return new GeometryFactory().createLineString(coordinates
307: .toCoordinateArray());
308: }
309: return null;
310: }
311:
312: private LineString filletOneLineString(LineString ls) {
313: if (ls.getNumPoints() > 2) {
314: CoordinateList filletCoordinates = new CoordinateList();
315: filletCoordinates.add(ls.getCoordinateN(0));
316:
317: for (int i = 0; i <= ls.getNumPoints() - 3; i++) {
318: Coordinate A = ls.getCoordinateN(i);
319: Coordinate B = ls.getCoordinateN(i + 1);
320: Coordinate C = ls.getCoordinateN(i + 1); //copy B
321: Coordinate D = ls.getCoordinateN(i + 2);
322: LineString lineString = MakeRoundCorner(A, B, C, D,
323: arcRadius, true);
324: if (!lineString.isEmpty()) {
325: filletCoordinates.add(lineString.getCoordinates(),
326: false, false);
327: }
328: }
329:
330: filletCoordinates.add(ls
331: .getCoordinateN(ls.getNumPoints() - 1));
332: return new GeometryFactory()
333: .createLineString(filletCoordinates
334: .toCoordinateArray());
335: }
336: return null;
337: }
338:
339: private Polygon filletPolygon(Polygon poly) {
340: LineString ls = poly.getExteriorRing();
341:
342: CoordinateList filletCoordinates = new CoordinateList();
343:
344: for (int i = 0; i <= ls.getNumPoints() - 3; i++) {
345: Coordinate A = ls.getCoordinateN(i);
346: Coordinate B = ls.getCoordinateN(i + 1);
347: Coordinate C = ls.getCoordinateN(i + 1); //copy B
348: Coordinate D = ls.getCoordinateN(i + 2);
349: LineString lineString = MakeRoundCorner(A, B, C, D,
350: arcRadius, true);
351: filletCoordinates.add(lineString.getCoordinates(), false,
352: false);
353: }
354:
355: Coordinate A = ls.getCoordinateN(ls.getNumPoints() - 2); //second to last
356: Coordinate B = ls.getCoordinateN(0); //last == first
357: Coordinate C = ls.getCoordinateN(0);
358: Coordinate D = ls.getCoordinateN(1);
359: LineString lineString = MakeRoundCorner(A, B, C, D, arcRadius,
360: true);
361: filletCoordinates
362: .add(lineString.getCoordinates(), false, false);
363: filletCoordinates.add(filletCoordinates.getCoordinate(0));
364: return new GeometryFactory().createPolygon(
365: new GeometryFactory()
366: .createLinearRing(filletCoordinates
367: .toCoordinateArray()), null);
368: }
369:
370: private LinearRing filletLinearRing(LinearRing ring) {
371: CoordinateList filletCoordinates = new CoordinateList();
372:
373: for (int i = 0; i <= ring.getNumPoints() - 3; i++) {
374: Coordinate A = ring.getCoordinateN(i);
375: Coordinate B = ring.getCoordinateN(i + 1);
376: Coordinate C = ring.getCoordinateN(i + 1); //copy B
377: Coordinate D = ring.getCoordinateN(i + 2);
378: LineString lineString = MakeRoundCorner(A, B, C, D,
379: arcRadius, true);
380: filletCoordinates.add(lineString.getCoordinates(), false,
381: false);
382: }
383:
384: Coordinate A = ring.getCoordinateN(ring.getNumPoints() - 2); //second to last
385: Coordinate B = ring.getCoordinateN(0); //last == first
386: Coordinate C = ring.getCoordinateN(0);
387: Coordinate D = ring.getCoordinateN(1);
388: LineString lineString = MakeRoundCorner(A, B, C, D, arcRadius,
389: true);
390: filletCoordinates
391: .add(lineString.getCoordinates(), false, false);
392: filletCoordinates.add(filletCoordinates.getCoordinate(0));
393: return new GeometryFactory().createLinearRing(filletCoordinates
394: .toCoordinateArray());
395: }
396: }
|