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: * Copyright (C) 2003 Vivid Solutions
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or (at your option) any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: *
021: * For more information, contact:
022: *
023: * Vivid Solutions
024: * Suite #1A
025: * 2328 Government Street
026: * Victoria BC V8T 5G5
027: * Canada
028: *
029: * (250)385-6040
030: * www.vividsolutions.com
031: */
032:
033: package com.vividsolutions.jump.workbench.ui.plugin.analysis;
034:
035: import java.awt.event.ItemEvent;
036: import java.awt.event.ItemListener;
037: import java.util.ArrayList;
038: import java.util.Collection;
039: import java.util.Iterator;
040:
041: import javax.swing.JComboBox;
042: import javax.swing.JLabel;
043: import javax.swing.JRadioButton;
044: import javax.swing.JTextField;
045:
046: import com.vividsolutions.jts.geom.Geometry;
047: import com.vividsolutions.jump.feature.Feature;
048: import com.vividsolutions.jump.feature.FeatureCollection;
049: import com.vividsolutions.jump.feature.FeatureDataset;
050: import com.vividsolutions.jump.task.TaskMonitor;
051: import com.vividsolutions.jump.workbench.model.Layer;
052: import com.vividsolutions.jump.workbench.model.StandardCategoryNames;
053: import com.vividsolutions.jump.workbench.model.UndoableCommand;
054: import com.vividsolutions.jump.workbench.plugin.*;
055: import com.vividsolutions.jump.workbench.plugin.util.*;
056: import com.vividsolutions.jump.workbench.ui.GUIUtil;
057: import com.vividsolutions.jump.workbench.ui.MultiInputDialog;
058: import com.vividsolutions.jump.I18N;
059:
060: /**
061: * Provides basic functions for computation with {@link Geometry} objects.
062: * <p>
063: * Uses {@link GeometryFunction} objects obtained from the Registry
064: * by the key GEOMETRY_FUNCTION_REG_KEY.
065: * Other plug-ins can add further Geometry functions to the Registry.
066: *
067: * @see GeometryFunction
068: */
069: public class GeometryFunctionPlugIn extends AbstractPlugIn implements
070: ThreadedPlugIn {
071: public static final String GEOMETRY_FUNCTION_REG_KEY = "Geometry Function Registry Key";
072:
073: //-- [sstein 15.02.2006]
074: private String sErrorsFound = I18N
075: .get("ui.plugin.analysis.GeometryFunctionPlugIn.errors-found-while-executing-function");
076: private String sFunction = I18N
077: .get("ui.plugin.analysis.GeometryFunctionPlugIn.function");
078: private String sFeatures = I18N
079: .get("ui.plugin.analysis.GeometryFunctionPlugIn.features");
080:
081: private String SRC_LAYER = I18N
082: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Source");
083: private String MASK_LAYER = I18N
084: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Mask");
085: private String METHODS = sFunction;
086: private String PARAM = I18N
087: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Parameter");
088: private String SELECTED_ONLY = I18N
089: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Use-selected-features-only");
090: private String UPDATE_SRC = I18N
091: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Update-Source-features-with-result");
092: private String ADD_TO_SRC = I18N
093: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Add-result-to-Source-layer");
094: private String CREATE_LYR = I18N
095: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Create-new-layer-for-result");
096:
097: private Collection functions;
098: private MultiInputDialog dialog;
099: private Layer srcLayer, maskLayer;
100: //private String funcNameToRun;
101: private GeometryFunction functionToRun = null;
102: private boolean exceptionThrown = false;
103:
104: private boolean createLayer = false;
105: private boolean updateSource = false;
106: private boolean addToSource = false;
107:
108: private boolean useSelected = false;
109:
110: private Geometry geoms[] = new Geometry[2];
111: private double[] params = new double[2];
112:
113: public GeometryFunctionPlugIn() {
114: }
115:
116: private String categoryName = StandardCategoryNames.RESULT;
117:
118: public void setCategoryName(String value) {
119: categoryName = value;
120: }
121:
122: private boolean addToSourceAllowed = true;
123:
124: public void setAddToSourceAllowed(boolean value) {
125: addToSourceAllowed = value;
126: }
127:
128: public void initialize(PlugInContext context) throws Exception {
129: registerFunctions(context);
130: }
131:
132: private void registerFunctions(PlugInContext context) {
133: // register standard functions
134: GeometryFunction[] functions = GeometryFunction.getFunctions();
135: context.getWorkbenchContext().getRegistry()
136: .createClassification(GEOMETRY_FUNCTION_REG_KEY,
137: GeometryFunction.class);
138: for (int i = 0; i < functions.length; i++) {
139: context.getWorkbenchContext().getRegistry().createEntry(
140: GEOMETRY_FUNCTION_REG_KEY, functions[i]);
141: }
142: }
143:
144: public boolean execute(PlugInContext context) throws Exception {
145: //-- [sstein 16.07.2006] put here again for langugae settings
146: sErrorsFound = I18N
147: .get("ui.plugin.analysis.GeometryFunctionPlugIn.errors-found-while-executing-function");
148: sFunction = I18N
149: .get("ui.plugin.analysis.GeometryFunctionPlugIn.function");
150: sFeatures = I18N
151: .get("ui.plugin.analysis.GeometryFunctionPlugIn.features");
152:
153: SRC_LAYER = I18N
154: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Source");
155: MASK_LAYER = I18N
156: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Mask");
157: METHODS = sFunction;
158: PARAM = I18N
159: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Parameter");
160: SELECTED_ONLY = I18N
161: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Use-selected-features-only");
162: UPDATE_SRC = I18N
163: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Update-Source-features-with-result");
164: ADD_TO_SRC = I18N
165: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Add-result-to-Source-layer");
166: CREATE_LYR = I18N
167: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Create-new-layer-for-result");
168:
169: functions = context.getWorkbenchContext().getRegistry()
170: .getEntries(GEOMETRY_FUNCTION_REG_KEY);
171: dialog = new MultiInputDialog(context.getWorkbenchFrame(),
172: getName(), true);
173: setDialogValues(dialog, context);
174: GUIUtil.centreOnWindow(dialog);
175: dialog.setVisible(true);
176: if (!dialog.wasOKPressed()) {
177: return false;
178: }
179: getDialogValues(dialog);
180: return true;
181: }
182:
183: public void run(TaskMonitor monitor, PlugInContext context)
184: throws Exception {
185: monitor.allowCancellationRequests();
186:
187: // input-proofing
188: if (functionToRun == null)
189: return;
190: if (srcLayer == null)
191: return;
192: if ((updateSource || addToSource) && !srcLayer.isEditable()) {
193: context
194: .getWorkbenchFrame()
195: .warnUser(
196: srcLayer.getName()
197: + " "
198: + I18N
199: .get("ui.plugin.analysis.GeometryFunctionPlugIn.is-not-editable"));
200: return;
201: }
202:
203: monitor
204: .report(I18N
205: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Executing-function")
206: + " " + functionToRun.getName() + "...");
207:
208: ArrayList modifiedFeatures = new ArrayList();
209: Collection resultFeatures = null;
210: int nArgs = functionToRun.getGeometryArgumentCount();
211:
212: if (nArgs == 2) {
213: if (maskLayer == null)
214: return;
215:
216: Collection fc1 = getFeaturesToProcess(srcLayer, context);
217: Collection fc2 = getFeaturesToProcess(maskLayer, context);
218:
219: // check for valid size of input
220: if (fc2.size() != 1) {
221: context
222: .getWorkbenchFrame()
223: .warnUser(
224: I18N
225: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Mask-must-contain-exactly-one-geometry"));
226: return;
227: }
228: Geometry geomMask = ((Feature) fc2.iterator().next())
229: .getGeometry();
230:
231: resultFeatures = runGeometryMethodWithMask(monitor, fc1,
232: geomMask, functionToRun, modifiedFeatures);
233: } else {
234: Collection fc1 = getFeaturesToProcess(srcLayer, context);
235: resultFeatures = runGeometryMethod(monitor, fc1,
236: functionToRun, modifiedFeatures);
237: }
238:
239: // this will happen if plugin was cancelled
240: if (resultFeatures == null)
241: return;
242:
243: if (modifiedFeatures.size() == 0) {
244: context
245: .getWorkbenchFrame()
246: .warnUser(
247: I18N
248: .get("ui.plugin.analysis.GeometryFunctionPlugIn.No-geometries-were-processed"));
249: return;
250: }
251:
252: if (createLayer) {
253: String outputLayerName = LayerNameGenerator
254: .generateOperationOnLayerName(functionToRun
255: .toString(), srcLayer.getName());
256: FeatureCollection resultFC = new FeatureDataset(srcLayer
257: .getFeatureCollectionWrapper().getFeatureSchema());
258: resultFC.addAll(resultFeatures);
259: context.getLayerManager().addCategory(categoryName);
260: context.addLayer(categoryName, outputLayerName, resultFC);
261: } else if (updateSource) {
262: final Collection undoableNewFeatures = resultFeatures;
263: final Collection undoableModifiedFeatures = modifiedFeatures;
264:
265: UndoableCommand cmd = new UndoableCommand(getName()) {
266: public void execute() {
267: srcLayer.getFeatureCollectionWrapper().removeAll(
268: undoableModifiedFeatures);
269: srcLayer.getFeatureCollectionWrapper().addAll(
270: undoableNewFeatures);
271: }
272:
273: public void unexecute() {
274: srcLayer.getFeatureCollectionWrapper().removeAll(
275: undoableNewFeatures);
276: srcLayer.getFeatureCollectionWrapper().addAll(
277: undoableModifiedFeatures);
278: }
279: };
280:
281: execute(cmd, context);
282: } else if (addToSource) {
283: final Collection undoableFeatures = resultFeatures;
284:
285: UndoableCommand cmd = new UndoableCommand(getName()) {
286: public void execute() {
287: srcLayer.getFeatureCollectionWrapper().addAll(
288: undoableFeatures);
289: }
290:
291: public void unexecute() {
292: srcLayer.getFeatureCollectionWrapper().removeAll(
293: undoableFeatures);
294: }
295: };
296:
297: execute(cmd, context);
298: }
299:
300: if (exceptionThrown) {
301: context.getWorkbenchFrame().warnUser(sErrorsFound);
302: }
303: }
304:
305: private Feature getSelectedFeature(Layer lyr, PlugInContext context) {
306: Collection selected = context.getLayerViewPanel()
307: .getSelectionManager()
308: .getFeaturesWithSelectedItems(lyr);
309: if (selected.size() != 1)
310: return null;
311: return (Feature) selected.iterator().next();
312: }
313:
314: private Collection getSelectedFeatures(Layer lyr,
315: PlugInContext context) {
316: Collection selected = context.getLayerViewPanel()
317: .getSelectionManager()
318: .getFeaturesWithSelectedItems(lyr);
319: return selected;
320: }
321:
322: private Collection getFeaturesToProcess(Layer lyr,
323: PlugInContext context) {
324: if (useSelected)
325: return context.getLayerViewPanel().getSelectionManager()
326: .getFeaturesWithSelectedItems(lyr);
327: return lyr.getFeatureCollectionWrapper().getFeatures();
328: }
329:
330: private Collection runGeometryMethodWithMask(TaskMonitor monitor,
331: Collection fcA, Geometry geomB, GeometryFunction func,
332: Collection modifiedFeatures) {
333: exceptionThrown = false;
334: Collection resultColl = new ArrayList();
335: int total = fcA.size();
336: int count = 0;
337: for (Iterator ia = fcA.iterator(); ia.hasNext();) {
338:
339: monitor.report(count++, total, sFeatures);
340: if (monitor.isCancelRequested())
341: return null;
342:
343: Feature fa = (Feature) ia.next();
344: Geometry ga = fa.getGeometry();
345: geoms[0] = ga;
346: geoms[1] = geomB;
347: Geometry result = execute(func, geoms, params);
348:
349: saveResult(fa, result, resultColl, modifiedFeatures);
350:
351: }
352: return resultColl;
353: }
354:
355: private Collection runGeometryMethod(TaskMonitor monitor,
356: Collection fc, GeometryFunction func,
357: Collection modifiedFeatures) {
358: exceptionThrown = false;
359: Collection resultColl = new ArrayList();
360: int total = fc.size();
361: int count = 0;
362: for (Iterator iSrc = fc.iterator(); iSrc.hasNext();) {
363:
364: monitor.report(count++, total, sFeatures);
365: if (monitor.isCancelRequested())
366: return null;
367:
368: Feature fSrc = (Feature) iSrc.next();
369: Geometry gSrc = fSrc.getGeometry();
370: if (gSrc == null)
371: continue;
372:
373: geoms[0] = gSrc;
374: Geometry result = execute(func, geoms, params);
375:
376: saveResult(fSrc, result, resultColl, modifiedFeatures);
377: }
378:
379: return resultColl;
380: }
381:
382: private void saveResult(Feature srcFeat, Geometry resultGeom,
383: Collection resultColl, Collection modifiedFeatures) {
384: if (resultGeom == null || resultGeom.isEmpty()) {
385: // do nothing
386: } else {
387: Feature fNew = srcFeat.clone(true);
388: fNew.setGeometry(resultGeom);
389: resultColl.add(fNew);
390: modifiedFeatures.add(srcFeat);
391: }
392: }
393:
394: private Geometry execute(GeometryFunction func, Geometry[] geoms,
395: double[] params) {
396: try {
397: return func.execute(geoms, params);
398: } catch (RuntimeException ex) {
399: // simply eat exceptions and report them by returning null
400: exceptionThrown = true;
401: }
402: return null;
403:
404: }
405:
406: private JComboBox layer2ComboBox;
407: private JTextField paramField;
408: private JLabel labelField;
409: // private JCheckBox replaceSrcChkBox;
410: private JRadioButton updateSourceRB;
411: private JRadioButton createNewLayerRB;
412: private JRadioButton addToSourceRB;
413:
414: private void setDialogValues(MultiInputDialog dialog,
415: PlugInContext context) {
416: //dialog.setSideBarImage(new ImageIcon(getClass().getResource("DiffSegments.png")));
417: dialog
418: .setSideBarDescription(I18N
419: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Computes-a-geometric-function-on-features-in-the-Source-layer")
420: + " "
421: + I18N
422: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Geometry-can-be-saved-to-a-new-layer,-updated-in-place,-or-added-to-the-Source-layer")
423: + " "
424: + I18N
425: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Binary-geometric-functions-take-a-mask-feature-as-their-second-operand"));
426:
427: //Set initial layer values to the first and second layers in the layer list.
428: //In #initialize we've already checked that the number of layers >= 1. [Jon Aquino]
429: if (srcLayer == null)
430: srcLayer = context.getCandidateLayer(0);
431: dialog
432: .addLayerComboBox(
433: SRC_LAYER,
434: srcLayer,
435: I18N
436: .get("ui.plugin.analysis.GeometryFunctionPlugIn.The-Source-layer-features-provide-the-first-operand-for-the-chosen-function"),
437: context.getLayerManager());
438:
439: JComboBox functionComboBox = dialog.addComboBox(METHODS,
440: functionToRun, functions, null);
441: functionComboBox.addItemListener(new MethodItemListener());
442:
443: paramField = dialog.addDoubleField(PARAM, params[0], 10);
444:
445: layer2ComboBox = dialog
446: .addLayerComboBox(
447: MASK_LAYER,
448: maskLayer,
449: I18N
450: .get("ui.plugin.analysis.GeometryFunctionPlugIn.The-Mask-layer-must-contain-a-single-feature,-which-is-used-as-the-second-operand-for-binary-functions"),
451: context.getLayerManager());
452: dialog.addCheckBox(SELECTED_ONLY, useSelected);
453:
454: final String OUTPUT_GROUP = I18N
455: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Match-Type");
456: createNewLayerRB = dialog
457: .addRadioButton(
458: CREATE_LYR,
459: OUTPUT_GROUP,
460: true,
461: I18N
462: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Create-a-new-layer-for-the-results"));
463: updateSourceRB = dialog
464: .addRadioButton(
465: UPDATE_SRC,
466: OUTPUT_GROUP,
467: false,
468: I18N
469: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Replace-the-geometry-of-Source-features-with-the-result-geometry")
470: + " ");
471:
472: if (addToSourceAllowed) {
473: addToSourceRB = dialog
474: .addRadioButton(
475: ADD_TO_SRC,
476: OUTPUT_GROUP,
477: false,
478: I18N
479: .get("ui.plugin.analysis.GeometryFunctionPlugIn.Add-the-result-geometry-to-the-Source-layer")
480: + " ");
481: }
482:
483: updateUIForMethod(functionToRun);
484: }
485:
486: private void getDialogValues(MultiInputDialog dialog) {
487: srcLayer = dialog.getLayer(SRC_LAYER);
488: maskLayer = dialog.getLayer(MASK_LAYER);
489: functionToRun = (GeometryFunction) dialog.getComboBox(METHODS)
490: .getSelectedItem();
491: params[0] = dialog.getDouble(PARAM);
492: useSelected = dialog.getBoolean(SELECTED_ONLY);
493: createLayer = dialog.getBoolean(CREATE_LYR);
494: updateSource = dialog.getBoolean(UPDATE_SRC);
495:
496: if (addToSourceAllowed) {
497: addToSource = dialog.getBoolean(ADD_TO_SRC);
498: }
499: }
500:
501: private void updateUIForMethod(GeometryFunction func) {
502: boolean layer2Used = false;
503: boolean paramUsed = false;
504: if (func != null) {
505: layer2Used = func.getGeometryArgumentCount() > 1;
506: paramUsed = func.getParameterCount() > 0;
507: }
508: layer2ComboBox.setEnabled(layer2Used);
509: paramField.setEnabled(paramUsed);
510: // this has the effect of making the background gray (disabled)
511: paramField.setOpaque(paramUsed);
512:
513: dialog.validate();
514: }
515:
516: private class MethodItemListener implements ItemListener {
517: public void itemStateChanged(ItemEvent e) {
518: updateUIForMethod((GeometryFunction) e.getItem());
519: }
520: }
521:
522: }
|