// License: GPL. See LICENSE file for details. Copyright 2012 by Josh Doe and others. package org.openstreetmap.josm.plugins.conflation; import com.vividsolutions.jcs.conflate.polygonmatch.FCMatchFinder; import com.vividsolutions.jcs.conflate.polygonmatch.Matches; import com.vividsolutions.jump.feature.*; import com.vividsolutions.jump.task.TaskMonitor; import java.awt.Component; import java.awt.Dialog; import java.awt.Rectangle; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.TableCellRenderer; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.AutoScaleAction; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.data.SelectionChangedListener; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.event.*; import org.openstreetmap.josm.gui.MapView.EditLayerChangeListener; import org.openstreetmap.josm.gui.OsmPrimitivRenderer; import org.openstreetmap.josm.gui.SideButton; import org.openstreetmap.josm.gui.dialogs.ToggleDialog; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor; import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; import org.openstreetmap.josm.plugins.conflation.ConflateMatchCommand.UserCancelException; import org.openstreetmap.josm.plugins.jts.JTSConverter; import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryException; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.InputMapUtils; import org.openstreetmap.josm.tools.Shortcut; public class ConflationToggleDialog extends ToggleDialog implements EditLayerChangeListener, SelectionChangedListener, DataSetListener, SimpleMatchListListener { public final static String TITLE_PREFIX = tr("Conflation"); public final static String PREF_PREFIX = "conflation"; JTabbedPane tabbedPane; JTable matchTable; UnmatchedJList referenceOnlyList; UnmatchedObjectListModel referenceOnlyListModel; UnmatchedJList subjectOnlyList; UnmatchedObjectListModel subjectOnlyListModel; ConflationLayer conflationLayer; SimpleMatchesTableModel matchTableModel; SimpleMatchList matches; SimpleMatchSettings settings; SettingsDialog settingsDialog; ConflateAction conflateAction; RemoveAction removeAction; ZoomToListSelectionAction zoomToListSelectionAction; SelectionPopup selectionPopup; public ConflationToggleDialog(ConflationPlugin conflationPlugin) { // TODO: create shortcut? super(TITLE_PREFIX, "conflation.png", tr("Activates the conflation plugin"), null, 150); matches = new SimpleMatchList(); settingsDialog = new SettingsDialog(); settingsDialog.setModalityType(Dialog.ModalityType.MODELESS); settingsDialog.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { // "Generate matches" was clicked if (settingsDialog.getValue() == 1) { settings = settingsDialog.getSettings(); performMatching(); } } }); // create table to show matches and allow multiple selections matchTableModel = new SimpleMatchesTableModel(); matchTable = new JTable(matchTableModel); // add selection handler, to center/zoom view matchTable.getSelectionModel().addListSelectionListener( new MatchListSelectionHandler()); matchTable.getColumnModel().getSelectionModel().addListSelectionListener( new MatchListSelectionHandler()); // FIXME: doesn't work right now matchTable.getColumnModel().getColumn(0).setCellRenderer(new OsmPrimitivRenderer()); matchTable.getColumnModel().getColumn(1).setCellRenderer(new OsmPrimitivRenderer()); matchTable.getColumnModel().getColumn(4).setCellRenderer(new ColorTableCellRenderer("Tags")); matchTable.setRowSelectionAllowed(true); matchTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); referenceOnlyListModel = new UnmatchedObjectListModel(); referenceOnlyList = new UnmatchedJList(referenceOnlyListModel); referenceOnlyList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); referenceOnlyList.setCellRenderer(new OsmPrimitivRenderer()); referenceOnlyList.setTransferHandler(null); // no drag & drop subjectOnlyListModel = new UnmatchedObjectListModel(); subjectOnlyList = new UnmatchedJList(subjectOnlyListModel); subjectOnlyList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); subjectOnlyList.setCellRenderer(new OsmPrimitivRenderer()); subjectOnlyList.setTransferHandler(null); // no drag & drop //add popup menu for zoom on selection zoomToListSelectionAction = new ZoomToListSelectionAction(); selectionPopup = new SelectionPopup(); SelectionPopupMenuLauncher launcher = new SelectionPopupMenuLauncher(); matchTable.addMouseListener(launcher); subjectOnlyList.addMouseListener(launcher); referenceOnlyList.addMouseListener(launcher); //on enter key zoom to selection InputMapUtils.addEnterAction(matchTable, zoomToListSelectionAction); InputMapUtils.addEnterAction(subjectOnlyList, zoomToListSelectionAction); InputMapUtils.addEnterAction(referenceOnlyList, zoomToListSelectionAction); DoubleClickHandler dblClickHandler = new DoubleClickHandler(); matchTable.addMouseListener(dblClickHandler); referenceOnlyList.addMouseListener(dblClickHandler); subjectOnlyList.addMouseListener(dblClickHandler); tabbedPane = new JTabbedPane(); tabbedPane.addTab(tr("Matches"), matchTable); tabbedPane.addTab(tr("Reference only"), referenceOnlyList); tabbedPane.addTab(tr("Subject only"), subjectOnlyList); conflateAction = new ConflateAction(); final SideButton conflateButton = new SideButton(conflateAction); // TODO: don't need this arrow box now, but likely will shortly // conflateButton.createArrow(new ActionListener() { // @Override // public void actionPerformed(ActionEvent e) { // ConflatePopupMenu.launch(conflateButton); // } // }); removeAction = new RemoveAction(); // add listeners to update enable state of buttons tabbedPane.addChangeListener(conflateAction); tabbedPane.addChangeListener(removeAction); referenceOnlyList.addListSelectionListener(conflateAction); referenceOnlyList.addListSelectionListener(removeAction); subjectOnlyList.addListSelectionListener(conflateAction); subjectOnlyList.addListSelectionListener(removeAction); UnmatchedListDataListener unmatchedListener = new UnmatchedListDataListener(); subjectOnlyListModel.addListDataListener(unmatchedListener); referenceOnlyListModel.addListDataListener(unmatchedListener); createLayout(tabbedPane, true, Arrays.asList(new SideButton[]{ new SideButton(new ConfigureAction()), conflateButton, new SideButton(removeAction) // new SideButton("Replace Geometry", false), // new SideButton("Merge Tags", false), // new SideButton("Remove", false) })); } @Override public void simpleMatchListChanged(SimpleMatchList list) { updateTabTitles(); } @Override public void simpleMatchSelectionChanged(Collection<SimpleMatch> selected) { // adjust table selection to match match list selection // FIXME: is this really where I should be doing this? // selection is the same, don't do anything Collection<SimpleMatch> tableSelection = getSelectedFromTable(); if (tableSelection.containsAll(selected) && tableSelection.size() == selected.size()) return; ListSelectionModel lsm = matchTable.getSelectionModel(); lsm.setValueIsAdjusting(true); lsm.clearSelection(); for (SimpleMatch c : selected) { int idx = matches.indexOf(c); lsm.addSelectionInterval(idx, idx); } lsm.setValueIsAdjusting(false); } private void updateTabTitles() { tabbedPane.setTitleAt(tabbedPane.indexOfComponent(matchTable), tr(marktr("Matches ({0})"), matches.size())); tabbedPane.setTitleAt(tabbedPane.indexOfComponent(referenceOnlyList), tr(marktr("Reference only ({0})"), referenceOnlyListModel.size())); tabbedPane.setTitleAt(tabbedPane.indexOfComponent(subjectOnlyList), tr(marktr("Subject only ({0})"), subjectOnlyListModel.size())); } private List<OsmPrimitive> getSelectedReferencePrimitives() { List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(); if (tabbedPane == null || tabbedPane.getSelectedComponent() == null) return selection; if (tabbedPane.getSelectedComponent().equals(matchTable)) { for (SimpleMatch c : matches.getSelected()) { selection.add(c.getReferenceObject()); } } else if (tabbedPane.getSelectedComponent().equals(referenceOnlyList)) { selection.addAll(referenceOnlyList.getSelectedValuesList()); } return selection; } private List<OsmPrimitive> getSelectedSubjectPrimitives() { List<OsmPrimitive> selection = new ArrayList<OsmPrimitive>(); if (tabbedPane == null || tabbedPane.getSelectedComponent() == null) return selection; if (tabbedPane.getSelectedComponent().equals(matchTable)) { for (SimpleMatch c : matches.getSelected()) { selection.add(c.getSubjectObject()); } } else if (tabbedPane.getSelectedComponent().equals(subjectOnlyList)) { selection.addAll(subjectOnlyList.getSelectedValuesList()); } return selection; } private Collection<OsmPrimitive> getAllSelectedPrimitives() { Collection<OsmPrimitive> allSelected = new HashSet<OsmPrimitive>(); allSelected.addAll(getSelectedReferencePrimitives()); allSelected.addAll(getSelectedSubjectPrimitives()); return allSelected; } private void selectAllListSelectedPrimitives() { List<OsmPrimitive> refSelected = getSelectedReferencePrimitives(); List<OsmPrimitive> subSelected = getSelectedSubjectPrimitives(); //clear current selection and add list-selected primitives, handling both //same and different reference/subject layers settings.getReferenceDataSet().clearSelection(); settings.getSubjectDataSet().clearSelection(); settings.getReferenceDataSet().addSelected(refSelected); settings.getSubjectDataSet().addSelected(subSelected); } class DoubleClickHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() < 2 || !SwingUtilities.isLeftMouseButton(e)) return; selectAllListSelectedPrimitives(); // zoom/center on selection AutoScaleAction.zoomTo(getAllSelectedPrimitives()); } } public class ConfigureAction extends JosmAction { public ConfigureAction() { // TODO: settle on sensible shortcuts super(tr("Configure"), "dialogs/settings", tr("Configure conflation options"), null, false); } @Override public void actionPerformed(ActionEvent e) { settingsDialog.setVisible(true); } } @Override public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { // TODO } @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { // TODO } private Collection<SimpleMatch> getSelectedFromTable() { ListSelectionModel lsm = matchTable.getSelectionModel(); Collection<SimpleMatch> selMatches = new HashSet<SimpleMatch>(); for (int i = lsm.getMinSelectionIndex(); i <= lsm.getMaxSelectionIndex(); i++) { if (lsm.isSelectedIndex(i) && i < matches.size()) { selMatches.add(matches.get(i)); } } return selMatches; } private class UnmatchedJList<E> extends JList { public UnmatchedJList(ListModel listModel) { super(listModel); } // TODO: remove this once JOSM uses Java 1.7 public List<E> getSelectedValuesList() { List<E> list = new ArrayList<E>(); for (Object o : getSelectedValues()) { list.add((E)o); } return list; } } protected static class ConflateMenuItem extends JMenuItem implements ActionListener { public ConflateMenuItem(String name) { super(name); addActionListener(this); //TODO: is this needed? } @Override public void actionPerformed(ActionEvent e) { //TODO: do something! } } protected static class ConflatePopupMenu extends JPopupMenu { static public void launch(Component parent) { JPopupMenu menu = new ConflatePopupMenu(); Rectangle r = parent.getBounds(); menu.show(parent, r.x, r.y + r.height); } public ConflatePopupMenu() { add(new ConflateMenuItem("Use reference geometry, reference tags")); add(new ConflateMenuItem("Use reference geometry, subject tags")); add(new ConflateMenuItem("Use subject geometry, reference tags")); } } class MatchListSelectionHandler implements ListSelectionListener { @Override public void valueChanged(ListSelectionEvent e) { matches.setSelected(getSelectedFromTable()); Main.map.mapView.repaint(); } } class ColorTableCellRenderer extends JLabel implements TableCellRenderer { private String columnName; public ColorTableCellRenderer(String column) { this.columnName = column; setOpaque(true); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Object columnValue = table.getValueAt(row, table.getColumnModel().getColumnIndex(columnName)); if (value != null) { setText(value.toString()); } if (isSelected) { setBackground(table.getSelectionBackground()); setForeground(table.getSelectionForeground()); } else { setBackground(table.getBackground()); setForeground(table.getForeground()); if (columnValue.equals("Conflicts!")) { setBackground(java.awt.Color.red); } else { setBackground(java.awt.Color.green); } } return this; } } static public class LayerListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { Layer layer = (Layer) value; JLabel label = (JLabel) super.getListCellRendererComponent(list, layer.getName(), index, isSelected, cellHasFocus); Icon icon = layer.getIcon(); label.setIcon(icon); label.setToolTipText(layer.getToolTipText()); return label; } } /** * Command to delete selected matches. */ class RemoveMatchCommand extends Command { private Collection<SimpleMatch> toRemove; public RemoveMatchCommand(Collection<SimpleMatch> toRemove) { this.toRemove = toRemove; } @Override public boolean executeCommand() { return matches.removeAll(toRemove); } @Override public void undoCommand() { matches.addAll(toRemove); } @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { } @Override public String getDescriptionText() { return tr(marktr("Delete {0} conflation matches"), toRemove.size()); } @Override public Icon getDescriptionIcon() { return ImageProvider.get("dialogs", "delete"); } } class RemoveUnmatchedObjectCommand extends Command { private UnmatchedObjectListModel model; private Collection<OsmPrimitive> objects; public RemoveUnmatchedObjectCommand(UnmatchedObjectListModel model, Collection<OsmPrimitive> objects) { this.model = model; this.objects = objects; } @Override public boolean executeCommand() { return model.removeAll(objects); } @Override public void undoCommand() { model.addAll(objects); } @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { } @Override public String getDescriptionText() { return tr(marktr("Remove {0} unmatched objects"), objects.size()); } @Override public Icon getDescriptionIcon() { return ImageProvider.get("dialogs", "delete"); } @Override public Collection<OsmPrimitive> getParticipatingPrimitives() { return objects; } } class RemoveAction extends JosmAction implements SimpleMatchListListener, ChangeListener, ListSelectionListener { public RemoveAction() { super(tr("Remove"), "dialogs/delete", tr("Remove selected matches"), null, false); } @Override public void actionPerformed(ActionEvent e) { Component selComponent = tabbedPane.getSelectedComponent(); if (selComponent.equals(matchTable)) { Main.main.undoRedo.add(new RemoveMatchCommand(matches.getSelected())); } else if (selComponent.equals(referenceOnlyList)) { Main.main.undoRedo.add( new RemoveUnmatchedObjectCommand(referenceOnlyListModel, referenceOnlyList.getSelectedValuesList())); } else if (selComponent.equals(subjectOnlyList)) { Main.main.undoRedo.add( new RemoveUnmatchedObjectCommand(subjectOnlyListModel, subjectOnlyList.getSelectedValuesList())); } } @Override public void updateEnabledState() { Component selComponent = tabbedPane.getSelectedComponent(); if (selComponent.equals(matchTable)) { if (matches != null && matches.getSelected() != null && !matches.getSelected().isEmpty()) setEnabled(true); else setEnabled(false); } else if (selComponent.equals(referenceOnlyList) && !referenceOnlyList.getSelectedValuesList().isEmpty()) { setEnabled(true); } else if (selComponent.equals(subjectOnlyList) && !subjectOnlyList.getSelectedValuesList().isEmpty()) { setEnabled(true); } else { setEnabled(false); } } @Override public void simpleMatchListChanged(SimpleMatchList list) { } @Override public void simpleMatchSelectionChanged(Collection<SimpleMatch> selected) { updateEnabledState(); } @Override public void stateChanged(ChangeEvent ce) { updateEnabledState(); } @Override public void valueChanged(ListSelectionEvent lse) { updateEnabledState(); } } class ConflateAction extends JosmAction implements SimpleMatchListListener, ChangeListener, ListSelectionListener { public ConflateAction() { // TODO: make sure shortcuts make sense super(tr("Conflate"), "dialogs/conflation", tr("Conflate selected objects"), Shortcut.registerShortcut("conflation:replace", tr("Conflation: {0}", tr("Replace")), KeyEvent.VK_F, Shortcut.ALT_CTRL), false); } @Override public void actionPerformed(ActionEvent e) { if (tabbedPane.getSelectedComponent().equals(matchTable)) conflateMatchActionPerformed(); else if (tabbedPane.getSelectedComponent().equals(referenceOnlyList)) conflateUnmatchedObjectActionPerformed(); } private void conflateUnmatchedObjectActionPerformed() { List<OsmPrimitive> unmatchedObjects = referenceOnlyList.getSelectedValuesList(); Command cmd = new ConflateUnmatchedObjectCommand(settings.getReferenceLayer(), settings.getSubjectLayer(), unmatchedObjects, referenceOnlyListModel); Main.main.undoRedo.add(cmd); // TODO: change layer and select newly copied objects? } private void conflateMatchActionPerformed() { SimpleMatch nextSelection = matches.findNextSelection(); List<Command> cmds = new LinkedList<Command>(); try { // iterate over selected matches in reverse order since they will be removed as we go List<SimpleMatch> selMatches = new ArrayList(matches.getSelected()); for (SimpleMatch c : selMatches) { ConflateMatchCommand conflateCommand; try { conflateCommand = new ConflateMatchCommand(c, matches, settings); } catch (UserCancelException ex) { break; } cmds.add(conflateCommand); // FIXME: how to chain commands which change relations? (see below) Main.main.undoRedo.add(conflateCommand); } } catch (ReplaceGeometryException ex) { JOptionPane.showMessageDialog(Main.parent, ex.getMessage(), tr("Cannot replace geometry."), JOptionPane.INFORMATION_MESSAGE); } // FIXME: ReplaceGeometry changes relations, so can't put it in a SequenceCommand // if (cmds.size() == 1) { // Main.main.undoRedo.add(cmds.iterator().next()); // } else if (cmds.size() > 1) { // SequenceCommand seqCmd = new SequenceCommand(tr(marktr("Conflate {0} objects"), cmds.size()), cmds); // Main.main.undoRedo.add(seqCmd); // } if (matches.getSelected().isEmpty()) matches.setSelected(nextSelection); } @Override public void updateEnabledState() { if (tabbedPane.getSelectedComponent().equals(matchTable) && matches != null && matches.getSelected() != null && !matches.getSelected().isEmpty()) setEnabled(true); else if (tabbedPane.getSelectedComponent().equals(referenceOnlyList) && !referenceOnlyList.getSelectedValuesList().isEmpty()) setEnabled(true); else setEnabled(false); } @Override public void simpleMatchListChanged(SimpleMatchList list) { } @Override public void simpleMatchSelectionChanged(Collection<SimpleMatch> selected) { updateEnabledState(); } @Override public void stateChanged(ChangeEvent ce) { updateEnabledState(); } @Override public void valueChanged(ListSelectionEvent lse) { updateEnabledState(); } } /** * The action for zooming to the primitives which are currently selected in * the list (either matches or single primitives). * */ class ZoomToListSelectionAction extends JosmAction implements ListSelectionListener{ public ZoomToListSelectionAction() { super(tr("Zoom to selected primitive(s)"), "dialogs/autoscale/selection", tr("Zoom to selected primitive(s)"), null, false); } @Override public void actionPerformed(ActionEvent e) { if (matchTable == null) return; Collection<OsmPrimitive> sel = getAllSelectedPrimitives(); if (sel.isEmpty()) return; AutoScaleAction.zoomTo(sel); } @Override public void updateEnabledState() { setEnabled(!getAllSelectedPrimitives().isEmpty()); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * The action for selecting the primitives which are currently selected in * the list (either matches or single primitives). * */ class SelectListSelectionAction extends JosmAction implements ListSelectionListener{ public SelectListSelectionAction() { super(tr("Select selected primitive(s)"), "dialogs/select", tr("Select the primitives currently selected in the list"), null, false); } @Override public void actionPerformed(ActionEvent e) { if (matchTable == null) return; selectAllListSelectedPrimitives(); } @Override public void updateEnabledState() { setEnabled(!getAllSelectedPrimitives().isEmpty()); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * The popup menu launcher */ class SelectionPopupMenuLauncher extends PopupMenuLauncher { @Override public void launch(MouseEvent evt) { //if none selected, select row under cursor Component c = tabbedPane.getSelectedComponent(); if (getAllSelectedPrimitives().isEmpty()) { if (c == matchTable) { //FIXME: this doesn't seem to be working int row = matchTable.rowAtPoint(evt.getPoint()); matchTable.getSelectionModel().addSelectionInterval(row, row); } else if (c == subjectOnlyList || c == referenceOnlyList) { int idx = ((UnmatchedJList)c).locationToIndex(evt.getPoint()); if (idx < 0) return; ((UnmatchedJList)c).setSelectedIndex(idx); } } selectionPopup.show(c, evt.getX(), evt.getY()); } } /** * The popup menu for the selection list */ class SelectionPopup extends JPopupMenu { public SelectionPopup() { matchTable.getSelectionModel().addListSelectionListener(zoomToListSelectionAction); subjectOnlyList.addListSelectionListener(zoomToListSelectionAction); referenceOnlyList.addListSelectionListener(zoomToListSelectionAction); add(zoomToListSelectionAction); SelectListSelectionAction selectListSelectionAction = new SelectListSelectionAction(); matchTable.getSelectionModel().addListSelectionListener(selectListSelectionAction); subjectOnlyList.addListSelectionListener(selectListSelectionAction); referenceOnlyList.addListSelectionListener(selectListSelectionAction); add(selectListSelectionAction); } } @Override public void primitivesAdded(PrimitivesAddedEvent event) { } @Override public void primitivesRemoved(PrimitivesRemovedEvent event) { List<? extends OsmPrimitive> prims = event.getPrimitives(); for (OsmPrimitive p : prims) { // TODO: use hashmap for (SimpleMatch c : matches) { if (c.getReferenceObject().equals(p) || c.getSubjectObject().equals(p)) { matches.remove(c); break; } } referenceOnlyListModel.removeElement(p); subjectOnlyListModel.removeElement(p); } } @Override public void tagsChanged(TagsChangedEvent event) { } @Override public void nodeMoved(NodeMovedEvent event) { } @Override public void wayNodesChanged(WayNodesChangedEvent event) { } @Override public void relationMembersChanged(RelationMembersChangedEvent event) { } @Override public void otherDatasetChange(AbstractDatasetChangedEvent event) { } @Override public void dataChanged(DataChangedEvent event) { } /** * Create FeatureSchema using union of all keys from all selected primitives * @param prims * @return */ private FeatureSchema createSchema(Collection<OsmPrimitive> prims) { Set<String> keys = new HashSet<String>(); for (OsmPrimitive prim : prims) { keys.addAll(prim.getKeys().keySet()); } FeatureSchema schema = new FeatureSchema(); schema.addAttribute("__GEOMETRY__", AttributeType.GEOMETRY); for (String key : keys) { schema.addAttribute(key, AttributeType.STRING); } return schema; } private FeatureCollection createFeatureCollection(Collection<OsmPrimitive> prims) { FeatureDataset dataset = new FeatureDataset(createSchema(prims)); //TODO: use factory instead of passing converter JTSConverter converter = new JTSConverter(true); for (OsmPrimitive prim : prims) { dataset.add(new OsmFeature(prim, converter)); } return dataset; } /** * Progress monitor for use with JCS */ private class JosmTaskMonitor extends PleaseWaitProgressMonitor implements TaskMonitor { @Override public void report(String description) { subTask(description); } @Override public void report(int itemsDone, int totalItems, String itemDescription) { subTask(String.format("Processing %d of %d %s", itemsDone, totalItems, itemDescription)); } @Override public void report(Exception exception) { throw new UnsupportedOperationException("Not supported yet."); } @Override public void allowCancellationRequests() { setCancelable(true); } @Override public boolean isCancelRequested() { return isCanceled(); } } private SimpleMatchList generateMatches(SimpleMatchSettings settings) { JosmTaskMonitor monitor = new JosmTaskMonitor(); monitor.beginTask("Generating matches"); // create Features and collections from primitive selections Set<OsmPrimitive> allPrimitives = new HashSet<OsmPrimitive>(); allPrimitives.addAll(settings.getReferenceSelection()); allPrimitives.addAll(settings.getSubjectSelection()); FeatureCollection allFeatures = createFeatureCollection(allPrimitives); FeatureCollection refColl = new FeatureDataset(allFeatures.getFeatureSchema()); FeatureCollection subColl = new FeatureDataset(allFeatures.getFeatureSchema()); for (Feature f : allFeatures.getFeatures()) { OsmFeature osmFeature = (OsmFeature)f; if (settings.getReferenceSelection().contains(osmFeature.getPrimitive())) refColl.add(osmFeature); if (settings.getSubjectSelection().contains(osmFeature.getPrimitive())) subColl.add(osmFeature); } //TODO: pass to MatchFinderPanel to use as hint/default for DistanceMatchers // get maximum possible distance so scores can be scaled (FIXME: not quite accurate) // Envelope envelope = refColl.getEnvelope(); // envelope.expandToInclude(subColl.getEnvelope()); // double maxDistance = Point2D.distance( // envelope.getMinX(), // envelope.getMinY(), // envelope.getMaxX(), // envelope.getMaxY()); // build matcher FCMatchFinder finder = settings.getMatchFinder(); // FIXME: ignore/filter duplicate objects (i.e. same object in both sets) // FIXME: fix match functions to work on point/linestring features as well // find matches Map<OsmFeature, Matches> map = finder.match(refColl, subColl, monitor); monitor.subTask("Finishing match list"); // convert to simple one-to-one match SimpleMatchList list = new SimpleMatchList(); for (Map.Entry<OsmFeature, Matches> entry: map.entrySet()) { OsmFeature target = entry.getKey(); OsmFeature subject = (OsmFeature)entry.getValue().getTopMatch(); if (target != null && subject != null) list.add(new SimpleMatch(target.getPrimitive(), subject.getPrimitive(), entry.getValue().getTopScore())); } monitor.finishTask(); monitor.close(); return list; } private void performMatching() { matches = generateMatches(settings); // populate unmatched objects List<OsmPrimitive> referenceOnly = new ArrayList<OsmPrimitive>(settings.getReferenceSelection()); List<OsmPrimitive> subjectOnly = new ArrayList<OsmPrimitive>(settings.getSubjectSelection()); for (SimpleMatch match : matches) { referenceOnly.remove(match.getReferenceObject()); subjectOnly.remove(match.getSubjectObject()); } referenceOnlyListModel.clear(); referenceOnlyListModel.addAll(referenceOnly); subjectOnlyListModel.clear(); subjectOnlyListModel.addAll(subjectOnly); updateTabTitles(); matchTableModel.setMatches(matches); matches.addConflationListChangedListener(matchTableModel); matches.addConflationListChangedListener(conflateAction); matches.addConflationListChangedListener(removeAction); matches.addConflationListChangedListener(this); settings.getSubjectDataSet().addDataSetListener(this); settings.getReferenceDataSet().addDataSetListener(this); // add conflation layer try { if (conflationLayer == null) { conflationLayer = new ConflationLayer(); Main.main.addLayer(conflationLayer); } } catch (Exception ex) { JOptionPane.showMessageDialog(Main.parent, ex.toString(), "Error adding conflation layer", JOptionPane.ERROR_MESSAGE); } conflationLayer.setMatches(matches); // matches.addConflationListChangedListener(conflationLayer); } class UnmatchedListDataListener implements ListDataListener { @Override public void intervalAdded(ListDataEvent lde) { updateTabTitles(); } @Override public void intervalRemoved(ListDataEvent lde) { updateTabTitles(); } @Override public void contentsChanged(ListDataEvent lde) { updateTabTitles(); } } }