From e25a35f1e37c27ae551927c4861ca81f06da6ffe Mon Sep 17 00:00:00 2001 From: David Gillingham Date: Fri, 24 Apr 2015 13:21:22 -0500 Subject: [PATCH] Omaha #4354: Support GeoJSON Feature objects in damage path tool. Change-Id: I62733373cb5da690ff03aebbb9826020b24e2585 Former-commit-id: 28845ce71a8d94b03bd4389f3cfdadbb29d06b3a --- .../META-INF/MANIFEST.MF | 5 +- .../com.raytheon.uf.viz.damagepath/plugin.xml | 12 +- .../uf/viz/damagepath/AddKeyValueDlg.java | 173 ++++++++++++ .../uf/viz/damagepath/DamagePathLayer.java | 137 +++++++++- .../damagepath/EditGeoJsonPropertiesDlg.java | 247 ++++++++++++++++++ .../damagepath/ExportDamagePathAction.java | 13 +- .../damagepath/ImportDamagePathAction.java | 43 ++- .../OpenGeoJsonPropertiesDlgAction.java | 82 ++++++ .../feature.xml | 6 +- .../common/feature/JsonFeatureFormatter.java | 6 +- 10 files changed, 686 insertions(+), 38 deletions(-) create mode 100644 cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/AddKeyValueDlg.java create mode 100644 cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/EditGeoJsonPropertiesDlg.java create mode 100644 cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/OpenGeoJsonPropertiesDlgAction.java diff --git a/cave/com.raytheon.uf.viz.damagepath/META-INF/MANIFEST.MF b/cave/com.raytheon.uf.viz.damagepath/META-INF/MANIFEST.MF index 7b9e5bad9b..150e8f0463 100644 --- a/cave/com.raytheon.uf.viz.damagepath/META-INF/MANIFEST.MF +++ b/cave/com.raytheon.uf.viz.damagepath/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Damage Path Bundle-SymbolicName: com.raytheon.uf.viz.damagepath;singleton:=true -Bundle-Version: 1.15.1.qualifier +Bundle-Version: 1.15.2.qualifier Bundle-Vendor: RAYTHEON Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Require-Bundle: com.raytheon.uf.viz.core;bundle-version="1.14.6", @@ -14,5 +14,6 @@ Require-Bundle: com.raytheon.uf.viz.core;bundle-version="1.14.6", com.raytheon.viz.ui;bundle-version="1.14.1", com.raytheon.viz.awipstools;bundle-version="1.14.0", com.raytheon.viz.radar;bundle-version="1.14.0", - javax.measure;bundle-version="1.0.0" + javax.measure;bundle-version="1.0.0", + com.raytheon.uf.common.util;bundle-version="1.14.1" Export-Package: com.raytheon.uf.viz.damagepath diff --git a/cave/com.raytheon.uf.viz.damagepath/plugin.xml b/cave/com.raytheon.uf.viz.damagepath/plugin.xml index 0e694ea06d..9f96371dfd 100644 --- a/cave/com.raytheon.uf.viz.damagepath/plugin.xml +++ b/cave/com.raytheon.uf.viz.damagepath/plugin.xml @@ -3,23 +3,29 @@ + + + sortID="4"> + sortID="5"> + sortID="6"> diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/AddKeyValueDlg.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/AddKeyValueDlg.java new file mode 100644 index 0000000000..a70e32321a --- /dev/null +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/AddKeyValueDlg.java @@ -0,0 +1,173 @@ +package com.raytheon.uf.viz.damagepath; + +import java.util.Collection; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import com.raytheon.uf.common.util.Pair; +import com.raytheon.viz.ui.dialogs.CaveSWTDialog; + +/** + * Dialog to add new property key/value pairs for the + * {@code EditGeoJsonPropertiesDlg}. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 23, 2015  #4354     dgilling     Initial creation based on dialog from 
+ *                                      lvenable.
+ * 
+ * 
+ * + * @author lvenable + * @version 1.0 + */ +public class AddKeyValueDlg extends CaveSWTDialog { + + private static final String EMPTY_STRING = ""; + + private static final String EMPTY_KEY_MSG = "Enter a key."; + + private static final String USED_KEY_MSG = "Key %s is already in use."; + + private Text keyTF; + + private Text valueTF; + + private Button okBtn; + + private Label verificationLbl; + + private final Collection reservedKeys; + + public AddKeyValueDlg(Shell parentShell, Collection reservedKeys) { + super(parentShell, SWT.DIALOG_TRIM, CAVE.DO_NOT_BLOCK + | CAVE.MODE_INDEPENDENT); + this.reservedKeys = reservedKeys; + } + + @Override + protected Layout constructShellLayout() { + GridLayout mainLayout = new GridLayout(1, false); + return mainLayout; + } + + @Override + protected Object constructShellLayoutData() { + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + return gd; + } + + @Override + protected void initializeComponents(Shell shell) { + setText("Add GeoJSON Property"); + + createKeyValueControls(); + createBottomButtons(); + } + + private void createKeyValueControls() { + Composite keyValueComp = new Composite(shell, SWT.NONE); + keyValueComp.setLayout(new GridLayout(2, false)); + keyValueComp.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, + false)); + + Label keyLbl = new Label(keyValueComp, SWT.NONE); + keyLbl.setText("Key:"); + + GridData gd = new GridData(130, SWT.DEFAULT); + keyTF = new Text(keyValueComp, SWT.BORDER); + keyTF.setLayoutData(gd); + keyTF.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + String newKey = keyTF.getText().trim(); + if (reservedKeys.contains(newKey)) { + verificationLbl.setText(String.format(USED_KEY_MSG, newKey)); + okBtn.setEnabled(false); + } else if (newKey.isEmpty()) { + verificationLbl.setText(EMPTY_KEY_MSG); + okBtn.setEnabled(false); + } else { + verificationLbl.setText(EMPTY_STRING); + okBtn.setEnabled(true); + } + + getShell().pack(); + getShell().layout(); + } + }); + + Label valueLbl = new Label(keyValueComp, SWT.NONE); + valueLbl.setText("Value:"); + + gd = new GridData(130, SWT.DEFAULT); + valueTF = new Text(keyValueComp, SWT.BORDER); + valueTF.setLayoutData(gd); + + verificationLbl = new Label(keyValueComp, SWT.NONE); + verificationLbl.setText(EMPTY_KEY_MSG); + gd = new GridData(SWT.FILL, SWT.DEFAULT, true, true); + gd.horizontalSpan = 2; + verificationLbl.setLayoutData(gd); + + } + + private void createBottomButtons() { + Composite buttonComp = new Composite(shell, SWT.NONE); + buttonComp.setLayout(new GridLayout(2, false)); + buttonComp.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, + false)); + + int buttonWidth = 70; + + GridData gd = new GridData(SWT.RIGHT, SWT.DEFAULT, true, false); + gd.widthHint = buttonWidth; + okBtn = new Button(buttonComp, SWT.PUSH); + okBtn.setText(" OK "); + okBtn.setLayoutData(gd); + okBtn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + handleOkAction(); + } + }); + okBtn.setEnabled(false); + + gd = new GridData(SWT.LEFT, SWT.DEFAULT, true, false); + gd.widthHint = buttonWidth; + Button cancelBtn = new Button(buttonComp, SWT.PUSH); + cancelBtn.setText(" Cancel "); + cancelBtn.setLayoutData(gd); + cancelBtn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setReturnValue(null); + close(); + } + }); + } + + private void handleOkAction() { + Pair pair = new Pair<>(keyTF.getText().trim(), valueTF + .getText().trim()); + setReturnValue(pair); + close(); + } +} diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/DamagePathLayer.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/DamagePathLayer.java index 9ba07f3d9e..5e0f57d10d 100644 --- a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/DamagePathLayer.java +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/DamagePathLayer.java @@ -22,14 +22,31 @@ package com.raytheon.uf.viz.damagepath; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; +import org.geotools.feature.simple.SimpleFeatureBuilder; +import org.geotools.feature.simple.SimpleFeatureTypeBuilder; +import org.opengis.feature.Property; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.simple.SimpleFeatureType; +import org.opengis.feature.type.Name; -import com.raytheon.uf.common.json.geo.GeoJsonUtil; -import com.raytheon.uf.common.json.geo.GeoJsonUtilSimpleImpl; +import com.raytheon.uf.common.json.JsonException; +import com.raytheon.uf.common.json.geo.BasicJsonService; +import com.raytheon.uf.common.json.geo.GeoJsonMapUtil; +import com.raytheon.uf.common.json.geo.IGeoJsonService; +import com.raytheon.uf.common.json.geo.SimpleGeoJsonService; import com.raytheon.uf.common.localization.FileUpdatedMessage; import com.raytheon.uf.common.localization.ILocalizationFileObserver; import com.raytheon.uf.common.localization.IPathManager; @@ -45,23 +62,25 @@ import com.raytheon.uf.viz.core.rsc.LoadProperties; import com.raytheon.uf.viz.drawing.polygon.PolygonLayer; import com.raytheon.uf.viz.drawing.polygon.PolygonUtil; import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Polygon; /** * A layer for displaying and customizing a weather event's damage path. - * + * *
- *
+ * 
  * SOFTWARE HISTORY
- *
+ * 
  * Date         Ticket#    Engineer    Description
  * ------------ ---------- ----------- --------------------------
  * Jan 19, 2015  3974      njensen     Initial creation
  * Mar 31, 2015  3977      nabowle     Reset polygon when initializing from the
  *                                     localization file fails.
- *
+ * Apr 23, 2015  4354      dgilling    Support GeoJSON Feature properties.
+ * 
  * 
- * + * * @author njensen * @version 1.0 */ @@ -77,6 +96,8 @@ public class DamagePathLayer extends private static final String PATH = DIR + IPathManager.SEPARATOR + FILE; + private Map featureProperties = Collections.emptyMap(); + /** * JVM property to specify the localization level to attempt to save/load * with. Falls back to USER if not defined. @@ -181,7 +202,7 @@ public class DamagePathLayer extends * TODO create a new InputAdapter that takes highest priority and blocks * other inputs. left clicking adds vertices, right click indicates the * last point on the polygon, then connect the first and last point. - * + * * afterwards, remove and dispose of that input adapter */ return null; @@ -226,8 +247,46 @@ public class DamagePathLayer extends protected void loadDamagePath(LocalizationFile file) { try (InputStream is = file.openInputStream()) { - GeoJsonUtil json = new GeoJsonUtilSimpleImpl(); - Polygon geometry = (Polygon) json.deserializeGeom(is); + Geometry deserializedGeom = null; + Map deserializedProps = Collections.emptyMap(); + GeoJsonMapUtil geoJsonUtil = new GeoJsonMapUtil(); + + /* + * For compatibility with any users that may have an autosaved + * damage path file from build 15.1, we'll support deserializing + * both Geometry and Feature GeoJSON types. + * + * TODO: remove this code for code that just expects the file to + * always be a Feature. + */ + Map jsonObject = (Map) new BasicJsonService() + .deserialize(is, LinkedHashMap.class); + String geoJsonType = jsonObject.get(GeoJsonMapUtil.TYPE_KEY) + .toString(); + if (geoJsonType.equals(GeoJsonMapUtil.FEATURE_TYPE)) { + SimpleFeature feature = geoJsonUtil.populateFeature(jsonObject); + deserializedGeom = (Geometry) feature.getDefaultGeometry(); + + Name defaultGeomAttrib = feature.getDefaultGeometryProperty() + .getName(); + deserializedProps = new LinkedHashMap<>(); + deserializedProps.put(GeoJsonMapUtil.ID_KEY, feature.getID()); + for (Property p : feature.getProperties()) { + if (!defaultGeomAttrib.equals(p.getName())) { + deserializedProps.put(p.getName().toString(), p + .getValue().toString()); + } + } + } else if (isGeometryType(geoJsonType)) { + deserializedGeom = geoJsonUtil.populateGeometry(jsonObject); + } else { + String message = "Unexpected GeoJSON object type " + + geoJsonType + + ". This tool only supports Feature and Geometry objects."; + throw new JsonException(message); + } + + Polygon geometry = (Polygon) deserializedGeom; /* * specifically call super.resetPolygon() cause this.resetPolygon() * will save the file and we don't want to do that or we could @@ -237,6 +296,8 @@ public class DamagePathLayer extends if (current == null || !current.equals(geometry)) { super.resetPolygon(geometry.getExteriorRing().getCoordinates()); } + + featureProperties = deserializedProps; } catch (Exception e) { statusHandler.error( "Error loading damage path file " + file.getName(), e); @@ -247,8 +308,9 @@ public class DamagePathLayer extends LocalizationFileOutputStream fos = null; try { fos = file.openOutputStream(); - GeoJsonUtil json = new GeoJsonUtilSimpleImpl(); - json.serialize(this.getPolygon(), fos); + IGeoJsonService json = new SimpleGeoJsonService(); + SimpleFeature feature = buildFeature(); + json.serialize(feature, fos); fos.closeAndSave(); } catch (Throwable t) { if (fos != null) { @@ -262,4 +324,55 @@ public class DamagePathLayer extends "Error saving damage path file " + file.getName(), t); } } + + public SimpleFeature buildFeature() { + Map jsonProps = getFeatureProperties(); + + String id = jsonProps.get(GeoJsonMapUtil.ID_KEY); + SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder(); + typeBuilder.setName("feature"); + Geometry geom = getPolygon(); + if (geom != null) { + typeBuilder.setDefaultGeometry("the_geom"); + typeBuilder.add("the_geom", geom.getClass()); + } + + Collection keysToIgnore = Arrays.asList(GeoJsonMapUtil.ID_KEY); + Set keySet = jsonProps.keySet(); + List values = new ArrayList(keySet.size()); + for (String key : keySet) { + if (!keysToIgnore.contains(key)) { + Object val = jsonProps.get(key); + typeBuilder.add(key, val.getClass()); + values.add(val); + } + } + + SimpleFeatureType type = typeBuilder.buildFeatureType(); + SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(type); + if (geom != null) { + featureBuilder.add(geom); + } + featureBuilder.addAll(values); + return featureBuilder.buildFeature(id); + } + + public Map getFeatureProperties() { + return featureProperties; + } + + public void setFeatureProperties(Map featureProperties) { + this.featureProperties = featureProperties; + saveJob.schedule(); + } + + public boolean isGeometryType(String geoJsonType) { + return ((GeoJsonMapUtil.GEOM_COLL_TYPE.equals(geoJsonType)) + || (GeoJsonMapUtil.LINE_STR_TYPE.equals(geoJsonType)) + || (GeoJsonMapUtil.MULT_LINE_STR_TYPE.equals(geoJsonType)) + || (GeoJsonMapUtil.MULT_POINT_TYPE.equals(geoJsonType)) + || (GeoJsonMapUtil.MULT_POLY_TYPE.equals(geoJsonType)) + || (GeoJsonMapUtil.POINT_TYPE.equals(geoJsonType)) || (GeoJsonMapUtil.POLY_TYPE + .equals(geoJsonType))); + } } diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/EditGeoJsonPropertiesDlg.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/EditGeoJsonPropertiesDlg.java new file mode 100644 index 0000000000..0d21d41ddb --- /dev/null +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/EditGeoJsonPropertiesDlg.java @@ -0,0 +1,247 @@ +package com.raytheon.uf.viz.damagepath; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +import com.raytheon.uf.common.util.Pair; +import com.raytheon.viz.ui.dialogs.CaveSWTDialog; +import com.raytheon.viz.ui.dialogs.ICloseCallback; + +/** + * Dialog to add/remove the key/value pairs that are part of the "properties" + * member object in GeoJSON Feature objects. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 23, 2015  #4354     dgilling     Initial creation based on dialog from 
+ *                                      lvenable.
+ * 
+ * 
+ * + * @author lvenable + * @version 1.0 + */ +public class EditGeoJsonPropertiesDlg extends CaveSWTDialog { + + private Table table; + + private Button deleteBtn; + + private Map properties; + + public EditGeoJsonPropertiesDlg(Shell parentShell, + Map properties) { + super(parentShell, SWT.DIALOG_TRIM, CAVE.DO_NOT_BLOCK); + + this.properties = new LinkedHashMap<>(properties); + } + + @Override + protected Layout constructShellLayout() { + // Create the main layout for the shell. + GridLayout mainLayout = new GridLayout(1, false); + mainLayout.marginHeight = 2; + mainLayout.marginWidth = 2; + + return mainLayout; + } + + @Override + protected Object constructShellLayoutData() { + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + return gd; + } + + @Override + protected void initializeComponents(Shell shell) { + setText("GeoJSON Properties Editor"); + + shell.addShellListener(new ShellAdapter() { + + @Override + public void shellClosed(ShellEvent e) { + setReturnValue(properties); + } + }); + + createTableComp(); + createAddDeleteButtons(); + addSeparator(shell, SWT.HORIZONTAL); + createBottomButtons(); + + populateTable(); + } + + private void createTableComp() { + int columnWidth = 150; + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + gd.heightHint = 250; + gd.widthHint = columnWidth * 2; + + table = new Table(shell, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL + | SWT.SINGLE); + table.setLayoutData(gd); + table.setHeaderVisible(true); + table.setLinesVisible(true); + + TableColumn column = new TableColumn(table, SWT.NONE); + column.setText("Key"); + column = new TableColumn(table, SWT.NONE); + column.setText("Value"); + + for (int i = 0; i < table.getColumnCount(); i++) { + table.getColumn(i).setWidth(columnWidth); + } + } + + private void createAddDeleteButtons() { + Composite buttonComp = new Composite(shell, SWT.NONE); + buttonComp.setLayout(new GridLayout(2, false)); + buttonComp.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, + false)); + + int buttonWidth = 70; + + GridData gd = new GridData(SWT.RIGHT, SWT.DEFAULT, true, false); + gd.widthHint = buttonWidth; + Button addBtn = new Button(buttonComp, SWT.PUSH); + addBtn.setText(" Add "); + addBtn.setLayoutData(gd); + addBtn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + AddKeyValueDlg akvd = new AddKeyValueDlg(shell, properties + .keySet()); + akvd.setCloseCallback(new ICloseCallback() { + @Override + public void dialogClosed(Object returnValue) { + if (returnValue == null) { + return; + } + + if (returnValue instanceof Pair) { + Pair newProperty = (Pair) returnValue; + properties.put(newProperty.getFirst(), + newProperty.getSecond()); + populateTable(); + } + } + }); + akvd.open(); + } + }); + + gd = new GridData(SWT.LEFT, SWT.DEFAULT, true, false); + gd.widthHint = buttonWidth; + deleteBtn = new Button(buttonComp, SWT.PUSH); + deleteBtn.setText(" Delete "); + deleteBtn.setLayoutData(gd); + deleteBtn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + handleDeleteAction(); + } + }); + } + + private void createBottomButtons() { + Composite buttonComp = new Composite(shell, SWT.NONE); + buttonComp.setLayout(new GridLayout(1, false)); + buttonComp.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, + false)); + + int buttonWidth = 70; + + GridData gd = new GridData(SWT.CENTER, SWT.DEFAULT, true, false); + gd.widthHint = buttonWidth; + Button okBtn = new Button(buttonComp, SWT.PUSH); + okBtn.setText(" Close "); + okBtn.setLayoutData(gd); + okBtn.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setReturnValue(properties); + close(); + } + }); + } + + public void addSeparator(Composite comp, int orientation) { + GridData gd; + + if (orientation == SWT.HORIZONTAL) { + gd = new GridData(SWT.FILL, SWT.DEFAULT, true, false); + + if (comp.getLayout() instanceof GridLayout) { + int columns = ((GridLayout) comp.getLayout()).numColumns; + gd.horizontalSpan = columns; + } + } else { + gd = new GridData(SWT.DEFAULT, SWT.FILL, false, true); + } + + Label sepLbl = new Label(comp, SWT.SEPARATOR | orientation); + sepLbl.setLayoutData(gd); + } + + private void populateTable() { + table.removeAll(); + for (Entry entry : properties.entrySet()) { + TableItem ti = new TableItem(table, SWT.NONE); + ti.setText(0, entry.getKey()); + ti.setText(1, entry.getValue()); + } + + if (table.getItemCount() > 0) { + table.select(0); + } + + enableDeleteButton(); + } + + private void handleDeleteAction() { + int index = table.getSelectionIndex(); + + if (index < 0) { + return; + } + + properties.remove(table.getItem(index).getText(0)); + populateTable(); + + if (index < table.getItemCount()) { + table.select(index); + } else if (table.getItemCount() > 0) { + table.select(table.getItemCount() - 1); + } + } + + private void enableDeleteButton() { + if ((table.getItemCount() > 0) && (table.getSelectionIndex() >= 0)) { + deleteBtn.setEnabled(true); + } else { + deleteBtn.setEnabled(false); + } + } +} diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ExportDamagePathAction.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ExportDamagePathAction.java index 1c8200abcc..38c9291a1c 100644 --- a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ExportDamagePathAction.java +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ExportDamagePathAction.java @@ -24,9 +24,10 @@ import java.io.FileOutputStream; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Shell; +import org.opengis.feature.simple.SimpleFeature; -import com.raytheon.uf.common.json.geo.GeoJsonUtil; -import com.raytheon.uf.common.json.geo.GeoJsonUtilSimpleImpl; +import com.raytheon.uf.common.json.geo.IGeoJsonService; +import com.raytheon.uf.common.json.geo.SimpleGeoJsonService; import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.viz.core.VizApp; @@ -42,7 +43,8 @@ import com.raytheon.viz.ui.cmenu.AbstractRightClickAction; * * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- - * Feb 9, 2015 3975 njensen Initial creation + * Feb 09, 2015 3975 njensen Initial creation + * Apr 23, 2015 4354 dgilling Export as GeoJSON Feature object. * * * @@ -75,8 +77,9 @@ public class ExportDamagePathAction extends AbstractRightClickAction { if (filename != null) { DamagePathLayer layer = (DamagePathLayer) getSelectedRsc(); try (FileOutputStream fos = new FileOutputStream(filename)) { - GeoJsonUtil json = new GeoJsonUtilSimpleImpl(); - json.serialize(layer.getPolygon(), fos); + IGeoJsonService json = new SimpleGeoJsonService(); + SimpleFeature feature = layer.buildFeature(); + json.serialize(feature, fos); } catch (Exception e) { statusHandler.error( "Error exporting damage path file to " diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ImportDamagePathAction.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ImportDamagePathAction.java index 681723e588..b0207f4434 100644 --- a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ImportDamagePathAction.java +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/ImportDamagePathAction.java @@ -20,14 +20,20 @@ package com.raytheon.uf.viz.damagepath; import java.io.FileInputStream; +import java.util.LinkedHashMap; +import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Shell; +import org.opengis.feature.Property; +import org.opengis.feature.simple.SimpleFeature; +import org.opengis.feature.type.Name; import com.raytheon.uf.common.json.JsonException; -import com.raytheon.uf.common.json.geo.GeoJsonUtil; -import com.raytheon.uf.common.json.geo.GeoJsonUtilSimpleImpl; +import com.raytheon.uf.common.json.geo.GeoJsonMapUtil; +import com.raytheon.uf.common.json.geo.IGeoJsonService; +import com.raytheon.uf.common.json.geo.SimpleGeoJsonService; import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.viz.core.VizApp; @@ -38,18 +44,19 @@ import com.vividsolutions.jts.geom.Polygon; /** * Action to import a damage path from a GeoJSON file specified by the user. - * + * *
- *
+ * 
  * SOFTWARE HISTORY
- *
+ * 
  * Date         Ticket#    Engineer    Description
  * ------------ ---------- ----------- --------------------------
  * Feb 12, 2015  3975      njensen     Initial creation
  * Mar 31, 2015  3977      nabowle     Make sure the polygon is not empty.
- *
+ * Apr 23, 2015  4354      dgilling    Support GeoJSON Feature objects.
+ * 
  * 
- * + * * @author njensen * @version 1.0 */ @@ -77,15 +84,28 @@ public class ImportDamagePathAction extends AbstractRightClickAction { if (filename != null) { DamagePathLayer layer = (DamagePathLayer) getSelectedRsc(); try (FileInputStream fis = new FileInputStream(filename)) { - GeoJsonUtil json = new GeoJsonUtilSimpleImpl(); - Geometry geom = json.deserializeGeom(fis); - if (geom instanceof Polygon - && geom.getCoordinates().length > 0) { + IGeoJsonService json = new SimpleGeoJsonService(); + SimpleFeature feature = json.deserializeFeature(fis); + Geometry geom = (Geometry) feature.getDefaultGeometry(); + if ((geom instanceof Polygon) + && (geom.getCoordinates().length > 0)) { layer.setPolygon((Polygon) geom); } else { throw new JsonException("Damage path file " + filename + " must contain a Polygon!"); } + + Name defaultGeomAttrib = feature + .getDefaultGeometryProperty().getName(); + Map featureProps = new LinkedHashMap<>(); + featureProps.put(GeoJsonMapUtil.ID_KEY, feature.getID()); + for (Property p : feature.getProperties()) { + if (!defaultGeomAttrib.equals(p.getName())) { + featureProps.put(p.getName().toString(), p + .getValue().toString()); + } + } + layer.setFeatureProperties(featureProps); } catch (Exception e) { statusHandler.error("Error importing damage path from " + filename, e); @@ -94,5 +114,4 @@ public class ImportDamagePathAction extends AbstractRightClickAction { } }); } - } diff --git a/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/OpenGeoJsonPropertiesDlgAction.java b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/OpenGeoJsonPropertiesDlgAction.java new file mode 100644 index 0000000000..8a4343186e --- /dev/null +++ b/cave/com.raytheon.uf.viz.damagepath/src/com/raytheon/uf/viz/damagepath/OpenGeoJsonPropertiesDlgAction.java @@ -0,0 +1,82 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.viz.damagepath; + +import java.util.Map; + +import org.eclipse.swt.widgets.Shell; + +import com.raytheon.uf.viz.core.VizApp; +import com.raytheon.viz.ui.VizWorkbenchManager; +import com.raytheon.viz.ui.cmenu.AbstractRightClickAction; +import com.raytheon.viz.ui.dialogs.ICloseCallback; + +/** + * {@code Action} class to launch the {@code EditGeoJsonPropertiesDlg}. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Apr 23, 2015  #4354     dgilling     Initial creation
+ * 
+ * 
+ * + * @author dgilling + * @version 1.0 + */ + +public class OpenGeoJsonPropertiesDlgAction extends AbstractRightClickAction { + + public OpenGeoJsonPropertiesDlgAction() { + super("Set GeoJSON Properties..."); + } + + @Override + public void run() { + VizApp.runSync(new Runnable() { + @Override + public void run() { + Shell shell = VizWorkbenchManager.getInstance() + .getCurrentWindow().getShell(); + final DamagePathLayer layer = (DamagePathLayer) getSelectedRsc(); + final Map geoJsonProps = layer + .getFeatureProperties(); + + EditGeoJsonPropertiesDlg dlg = new EditGeoJsonPropertiesDlg( + shell, geoJsonProps); + dlg.setCloseCallback(new ICloseCallback() { + + @Override + public void dialogClosed(Object returnValue) { + if ((returnValue != null) + && (!geoJsonProps.equals(returnValue))) { + Map updatedProperties = (Map) returnValue; + layer.setFeatureProperties(updatedProperties); + } + } + }); + dlg.open(); + } + }); + } +} diff --git a/cave/com.raytheon.viz.feature.awips.developer/feature.xml b/cave/com.raytheon.viz.feature.awips.developer/feature.xml index e36d646a2f..364a60621d 100644 --- a/cave/com.raytheon.viz.feature.awips.developer/feature.xml +++ b/cave/com.raytheon.viz.feature.awips.developer/feature.xml @@ -185,7 +185,7 @@ id="com.raytheon.uf.viz.aviation.advisory.feature" version="0.0.0"/> - @@ -197,6 +197,10 @@ id="com.raytheon.uf.viz.d2d.damagepath.feature" version="0.0.0"/> + + diff --git a/edexOsgi/com.raytheon.uf.edex.ogc.common/src/com/raytheon/uf/edex/ogc/common/feature/JsonFeatureFormatter.java b/edexOsgi/com.raytheon.uf.edex.ogc.common/src/com/raytheon/uf/edex/ogc/common/feature/JsonFeatureFormatter.java index 0edf65e4d7..814b1d6545 100644 --- a/edexOsgi/com.raytheon.uf.edex.ogc.common/src/com/raytheon/uf/edex/ogc/common/feature/JsonFeatureFormatter.java +++ b/edexOsgi/com.raytheon.uf.edex.ogc.common/src/com/raytheon/uf/edex/ogc/common/feature/JsonFeatureFormatter.java @@ -29,8 +29,8 @@ import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.raytheon.uf.common.http.MimeType; -import com.raytheon.uf.common.json.geo.GeoJsonUtil; -import com.raytheon.uf.common.json.geo.GeoJsonUtilSimpleImpl; +import com.raytheon.uf.common.json.geo.IGeoJsonService; +import com.raytheon.uf.common.json.geo.SimpleGeoJsonService; import com.raytheon.uf.common.json.geo.MixedFeatureCollection; import com.raytheon.uf.edex.ogc.common.OgcResponse; import com.raytheon.uf.edex.ogc.common.OgcResponse.TYPE; @@ -56,7 +56,7 @@ public class JsonFeatureFormatter implements SimpleFeatureFormatter { public static MimeType mimeType = new MimeType("application/json"); - protected GeoJsonUtil jsonUtil = new GeoJsonUtilSimpleImpl(); + protected IGeoJsonService jsonUtil = new SimpleGeoJsonService(); /* * (non-Javadoc)