package org.activityinfo.geoadmin; import com.google.common.collect.Lists; import net.miginfocom.swing.MigLayout; import org.activityinfo.geoadmin.merge.MergeAction; import org.activityinfo.geoadmin.merge.MergeNode; import org.activityinfo.geoadmin.merge.MergeTreeBuilder; import org.activityinfo.geoadmin.merge.MergeTreeTableModel; import org.activityinfo.geoadmin.model.ActivityInfoClient; import org.activityinfo.geoadmin.model.AdminEntity; import org.activityinfo.geoadmin.model.AdminLevel; import org.activityinfo.geoadmin.model.VersionMetadata; import org.jdesktop.swingx.JXTreeTable; import javax.swing.*; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.util.List; /** * Window proving a user interface to match a shapefile to an existing admin * level. For example, updating with better/new geography or new entities. * * <p> * The user, with a lot of help from automatic algorithms, needs to match each * feature from the shapefile to an existing admin entity. * */ public class UpdateWindow extends JFrame { private List<Join> joins; private ImportSource source; private UpdateForm form; private AdminLevel level; private ActivityInfoClient client; private JLabel scoreLabel; private JXTreeTable treeTable; private MergeTreeTableModel treeModel; public UpdateWindow(JFrame parent, ImportSource source, AdminLevel level, ActivityInfoClient client) { super("Update " + level.getName()); setSize(650, 350); setLocationRelativeTo(parent); setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); this.client = client; this.level = level; this.source = source; form = new UpdateForm(source); MergeTreeBuilder treeBuilder = new MergeTreeBuilder(client, level, source); treeModel = new MergeTreeTableModel(treeBuilder.build(), source); treeTable = new JXTreeTable(treeModel); JComboBox actionCombo = new JComboBox(MergeAction.values()); treeTable.getColumnModel() .getColumn(MergeTreeTableModel.ACTION_COLUMN) .setCellEditor(new DefaultCellEditor(actionCombo)); treeTable.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent event) { showScore(event); } }); scoreLabel = new JLabel(); JLabel countLabel = new JLabel(source.getFeatureCount() + " features"); JPanel panel = new JPanel(new MigLayout("fill")); panel.add(form, "wrap"); panel.add(new JScrollPane(treeTable), "wrap,grow"); panel.add(scoreLabel, "height 25!, wrap, growx"); panel.add(countLabel); getContentPane().add(createToolbar(), BorderLayout.PAGE_START); getContentPane().add(panel, BorderLayout.CENTER); } /** * Displays the score of the selected match bet * * @param event */ private void showScore(TreeSelectionEvent event) { MergeNode node = (MergeNode) event.getPath().getLastPathComponent(); if (node.getFeature() == null || node.getEntity() == null) { scoreLabel.setText(""); } else { double nameSim = node.getFeature().similarity(node.getEntity().getName()); double intersection = Joiner.areaOfIntersection(node.getEntity(), node.getFeature()); scoreLabel.setText(String.format("Name match: %.2f Intersection: %.2f", nameSim, intersection)); } } private JToolBar createToolbar() { final JButton acceptTheirsButton = new JButton("Accept Theirs"); acceptTheirsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent event) { acceptTheirs(); } }); final JButton mergeButton = new JButton("Merge"); mergeButton.setEnabled(false); treeTable.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent event) { mergeButton.setEnabled(isSelectionMergeable()); } }); mergeButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { mergeSelection(); } }); JButton updateButton = new JButton("Update"); updateButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { update(); } }); JToolBar toolbar = new JToolBar(); toolbar.add(acceptTheirsButton); toolbar.add(mergeButton); toolbar.add(updateButton); return toolbar; } /** * Checks to see if the current selection is candidate for merging. */ private boolean isSelectionMergeable() { if (treeTable.getSelectedRowCount() != 2) { return false; } MergeNode a = (MergeNode) treeTable.getPathForRow( treeTable.getSelectedRows()[0]).getLastPathComponent(); MergeNode b = (MergeNode) treeTable.getPathForRow( treeTable.getSelectedRows()[1]).getLastPathComponent(); if (a.isJoined() || b.isJoined()) { return false; } return ((a.getFeature() == null && b.getFeature() != null) || (b.getFeature() == null && a.getFeature() != null)); } private void acceptTheirs() { for (MergeNode node : getLeaves()) { if(node.isLeaf()) { if(node.getFeature() == null) { treeModel.setValueAt(MergeAction.DELETE, node, MergeTreeTableModel.ACTION_COLUMN); } else if (node.getEntity() == null) { treeModel.setValueAt(MergeAction.UPDATE, node, MergeTreeTableModel.ACTION_COLUMN); } } } } /** * Merges an unmatched existing entity with an unmatched imported feature */ private void mergeSelection() { MergeNode a = (MergeNode) treeTable.getPathForRow( treeTable.getSelectedRows()[0]).getLastPathComponent(); MergeNode b = (MergeNode) treeTable.getPathForRow( treeTable.getSelectedRows()[1]).getLastPathComponent(); MergeNode entityNode; MergeNode featureNode; if (a.getEntity() != null) { entityNode = a; featureNode = b; } else { entityNode = b; featureNode = a; } entityNode.setFeature(b.getFeature()); treeModel.fireNodeChanged(entityNode); treeModel.removeNodeFromParent(featureNode); } /** * Updates the server with the imported features. */ private void update() { List<AdminEntity> entities = Lists.newArrayList(); for (MergeNode join : getLeaves()) { if (join.getAction() != null && join.getAction() != MergeAction.IGNORE) { AdminEntity unit = new AdminEntity(); if (join.getEntity() != null) { unit.setId(join.getEntity().getId()); } if (join.getFeature() != null) { unit.setName(join.getFeature().getAttributeStringValue(form.getNameProperty())); if (form.getCodeProperty() != null) { unit.setCode(join.getFeature().getAttributeStringValue(form.getCodeProperty())); } unit.setBounds(GeoUtils.toBounds(join.getFeature().getEnvelope())); unit.setGeometry(join.getFeature().getGeometry()); } unit.setDeleted(join.getAction() == MergeAction.DELETE); entities.add(unit); } } VersionMetadata metadata = new VersionMetadata(); metadata.setSourceFilename(source.getFile().getName()); metadata.setSourceMD5(source.getMd5Hash()); metadata.setSourceUrl(form.getSourceUrl()); metadata.setMessage(form.getMessage()); try { metadata.setSourceMetadata(source.getMetadata()); } catch (IOException e) { e.printStackTrace(); } AdminLevel updatedLevel = new AdminLevel(); updatedLevel.setId(level.getId()); updatedLevel.setName(level.getName()); updatedLevel.setParentId(level.getParentId()); updatedLevel.setEntities(entities); updatedLevel.setVersionMetadata(metadata); client.updateAdminLevel(updatedLevel); setVisible(false); } private List<MergeNode> getLeaves() { List<MergeNode> nodes = ((MergeNode) treeModel.getRoot()).getLeaves(); return nodes; } }