User:DressyPear4/ResolverNotas
From OpenStreetMap Wiki
Jump to navigation
Jump to search
Resolver notas selecionadas
Resolver_Notas é um script para o JOSM que automatiza o fechamento de notas OSM vinculadas a um upload de changeset.
Funcionalidades:
- Captura notas selecionadas no mapa e as mantém em uma lista visual com destaque laranja no mapa e no painel de notas do JOSM;
- Ao abrir a janela de upload, injeta automaticamente a tag closed:note com os IDs das notas no changeset;
- Detecta a conclusão do upload e fecha as notas no servidor OSM com o comentário "Resolvido com changeset/ID";
- Opção de adicionar um comentário personalizado por nota antes de capturá-la;
- Checkbox para controlar se o campo comment do changeset é preenchido automaticamente;
- Notas já fechadas são ignoradas automaticamente.
Como funciona?
- Execute o script, um painel flutuante será aberto
- Selecione uma nota no mapa
- Clique em Capturar Nota Selecionada
- Faça suas edições normalmente e envie o changeset
- O script detecta o upload concluído e fecha as notas automaticamente no servidor
Demonstração
-
Diálogo e destaques
-
Etiqueta closed:note JOSM
-
Etiqueta closed:note OSM
-
Comentário inserido
Código
JavaScript
Última atualização: 2026-05-19
"use strict";
import { println } from 'josm/scriptingconsole';
(function() {
const MainApplication = org.openstreetmap.josm.gui.MainApplication;
// --- Importacoes Java ---
const JDialog = Java.type("javax.swing.JDialog");
const JPanel = Java.type("javax.swing.JPanel");
const JButton = Java.type("javax.swing.JButton");
const JLabel = Java.type("javax.swing.JLabel");
const BoxLayout = Java.type("javax.swing.BoxLayout");
const JScrollPane = Java.type("javax.swing.JScrollPane");
const BorderLayout = Java.type("java.awt.BorderLayout");
const Dimension = Java.type("java.awt.Dimension");
const Notification = Java.type("org.openstreetmap.josm.gui.Notification");
const UIManager = Java.type("javax.swing.UIManager");
const Color = Java.type("java.awt.Color");
const BasicStroke = Java.type("java.awt.BasicStroke");
const AlphaComposite = Java.type("java.awt.AlphaComposite");
const Path2D = Java.type("java.awt.geom.Path2D");
const Path2DFloat = Java.type("java.awt.geom.Path2D$Float");
const SwingUtilities = Java.type("javax.swing.SwingUtilities");
const Timer = Java.type("javax.swing.Timer");
const WindowAdapter = Java.type("java.awt.event.WindowAdapter");
const KeyboardFocusManager = Java.type("java.awt.KeyboardFocusManager");
const JOptionPane = Java.type("javax.swing.JOptionPane");
const BorderFactory = Java.type("javax.swing.BorderFactory");
const GridBagLayout = Java.type("java.awt.GridBagLayout");
const GridBagConstraints= Java.type("java.awt.GridBagConstraints");
const Insets = Java.type("java.awt.Insets");
const Font = Java.type("java.awt.Font");
const Integer = Java.type("java.lang.Integer");
const JCheckBox = Java.type("javax.swing.JCheckBox");
const MapViewPaintable = org.openstreetmap.josm.gui.layer.MapViewPaintable;
const NoteLayer = org.openstreetmap.josm.gui.layer.NoteLayer;
const LayerChangeListener = org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
// --- Variáveis de Estado e Destaque ---
let rememberedNotes = [];
let currentPainter = null;
let scanTimer = null;
let lastChangesetId = null;
let notesJList = null;
let rendererOriginal = null;
const COR_DESTAQUE = new Color(255, 220, 100); // Amarelo
// --- Lógica de Destaque no Painel Lateral ---
const encontrarNotesJList = function() {
try {
const map = MainApplication.getMap();
if (!map) return null;
const buscarRecursivo = function(comp, classe) {
if (!comp) return null;
if (comp.getClass().getSimpleName() === classe) return comp;
if (comp.getComponents) {
let kids = comp.getComponents();
for (let i = 0; i < kids.length; i++) {
let r = buscarRecursivo(kids[i], classe);
if (r) return r;
}
}
return null;
};
const notesDialog = buscarRecursivo(map, "NotesDialog");
return notesDialog ? buscarRecursivo(notesDialog, "JList") : null;
} catch(e) { return null; }
};
const instalarRendererDestaque = function() {
notesJList = encontrarNotesJList();
if (!notesJList) return;
rendererOriginal = notesJList.getCellRenderer();
const DefaultListCellRenderer = Java.extend(Java.type("javax.swing.DefaultListCellRenderer"));
const customRenderer = new DefaultListCellRenderer({
getListCellRendererComponent: function(list, value, index, isSelected, cellHasFocus) {
const comp = rendererOriginal.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (!isSelected && value !== null) {
const noteId = Number(value.getId());
const estaCapturada = rememberedNotes.some(function(n) { return Number(n.note.getId()) === noteId; });
if (estaCapturada) {
comp.setBackground(COR_DESTAQUE);
comp.setOpaque(true);
}
}
return comp;
}
});
notesJList.setCellRenderer(customRenderer);
};
// --- Monitoramento de UI e Captura de Changeset ---
const monitorChangesetID = function() {
const windows = java.awt.Window.getWindows();
for (let i = 0; i < windows.length; i++) {
const win = windows[i];
if (!win || !win.isVisible()) continue;
const winClass = win.getClass().getSimpleName();
// Janelas a ignorar para otimização
if (winClass.match(/HistoryBrowserDialog|NotePopup|OSMObjInfoDialog|ValidatorDialog/i)) {
continue;
}
let foundId = null;
let isConfirmacaoUpload = false;
const find = function(comp) {
try {
const compClass = comp.getClass().getSimpleName();
if (compClass.match(/NotePopup/i)) return;
let txt = comp.getText ? String(comp.getText()) : (comp.getContent ? String(comp.getContent()) : "");
if (txt) {
const m = txt.match(/changeset\/(\d+)/) || txt.match(/changeset\s(\d+)/);
if (m) {
foundId = m[1];
// As notificações de sucesso usam H3 ou H4 no JOSM
// As notas normais possuem "Nota XXXXX" e tags <hr>
if (txt.toLowerCase().match(/<h[34]>/) && txt.toLowerCase().match(/sucesso|successful|upload|enviado/)) {
isConfirmacaoUpload = true;
}
}
}
if (!isConfirmacaoUpload && comp.getComponents) {
const children = comp.getComponents();
for (let j = 0; j < children.length; j++) {
find(children[j]);
if (isConfirmacaoUpload) break;
}
}
} catch(e) {}
};
find(win);
if (foundId && isConfirmacaoUpload && foundId !== lastChangesetId) {
lastChangesetId = foundId;
processarFechamentoNotas(foundId);
}
}
};
const processarFechamentoNotas = function(id) {
SwingUtilities.invokeLater(function() {
const noteLayer = MainApplication.getLayerManager().getNoteLayer();
if (!noteLayer || rememberedNotes.length === 0) return;
const nd = noteLayer.getNoteData();
const notaAtualPorId = {};
const notasAtuais = nd.getNotes();
const itN = notasAtuais.iterator();
while (itN.hasNext()) {
const n = itN.next();
notaAtualPorId[String(n.getId())] = n;
}
rememberedNotes.forEach(function(item) {
try {
const notaIdStr = String(item.note.getId());
const notaAtual = notaAtualPorId[notaIdStr] || item.note;
if (notaAtual.getState().toString() !== "CLOSED") {
let finalComment = (item.comment ? item.comment + "\n" : "") + "Resolvido com https://www.openstreetmap.org/changeset/" + id;
nd.closeNote(notaAtual, finalComment);
}
} catch (err) { println("Erro ao fechar nota: " + item.note.getId() + " - " + err); }
});
rememberedNotes = [];
updateUI(); refreshMap(); syncTags();
if (notesJList) notesJList.repaint();
try {
const UploadNotesAction = Java.type("org.openstreetmap.josm.actions.UploadNotesAction");
new UploadNotesAction().actionPerformed(null);
} catch (err) { println("Erro Upload: " + err); }
});
};
// --- Lógica de Destaque no Notas no Mapa ---
const createPainter = function() {
const PaintClass = Java.extend(MapViewPaintable, {
paint: function(g, mv, bbox) {
try {
if (rememberedNotes.length === 0) return;
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75));
g.setStroke(new BasicStroke(2.0));
g.setColor(Color.ORANGE);
rememberedNotes.forEach(function(item) {
const p = mv.getPoint(item.note.getLatLon());
const drop = new Path2DFloat();
drop.moveTo(p.x, p.y);
drop.curveTo(p.x - 5, p.y - 5, p.x - 7, p.y - 15, p.x, p.y - 15);
drop.curveTo(p.x + 7, p.y - 15, p.x + 5, p.y - 5, p.x, p.y);
drop.closePath();
g.fill(drop); g.draw(drop);
});
} catch(e) {}
}
});
return new PaintClass();
};
const refreshMap = function() {
if (!MainApplication.getMap()) return;
const mv = MainApplication.getMap().mapView;
if (currentPainter) { try { mv.removeTemporaryLayer(currentPainter); } catch(e) {} }
currentPainter = createPainter();
mv.addTemporaryLayer(currentPainter);
mv.repaint();
};
const syncTags = function() {
const ds = MainApplication.getLayerManager().getEditDataSet();
if (!ds) return;
if (rememberedNotes.length > 0) {
const ids = rememberedNotes.map(function(n) { return n.note.getId().toString(); }).join(';');
ds.addChangeSetTag("closed:note", ids);
if (chkInject.isSelected()) ds.addChangeSetTag("comment", "Resolvendo notas, " + ids.replace(/;/g, ", "));
} else {
ds.addChangeSetTag("closed:note", null);
if (chkInject.isSelected()) ds.addChangeSetTag("comment", null);
}
};
const focusHandler = new (Java.extend(Java.type("java.beans.PropertyChangeListener"), {
propertyChange: function(e) {
if (e.getPropertyName() === "activeWindow") {
const win = e.getNewValue();
if (win instanceof JDialog) {
const title = String(win.getTitle() || "");
if (title.match(/Upload|Enviar/)) {
lastChangesetId = null;
syncTags();
}
}
}
}
}))();
const layerHandler = new (Java.extend(LayerChangeListener, {
layerRemoving: function(e) {
if (e.getRemovedLayer() instanceof NoteLayer) {
SwingUtilities.invokeLater(function() {
rememberedNotes = [];
updateUI(); refreshMap(); syncTags();
if (notesJList) notesJList.repaint();
});
}
},
layerAdded: function(e) {
if (e.getAddedLayer() instanceof NoteLayer && !notesJList) {
SwingUtilities.invokeLater(function() { instalarRendererDestaque(); });
}
},
activeLayerChange: function(e) {}, layerOrderChanged: function(e) {}
}))();
// --- Limpeza Total ao Fechar a Janela ---
const fecharDialogoCompleto = function() {
try {
if (notesJList && rendererOriginal) notesJList.setCellRenderer(rendererOriginal);
if (focusHandler) KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(focusHandler);
if (layerHandler) MainApplication.getLayerManager().removeLayerChangeListener(layerHandler);
if (scanTimer) scanTimer.stop();
const mv = MainApplication.getMap() ? MainApplication.getMap().mapView : null;
if (currentPainter && mv) mv.removeTemporaryLayer(currentPainter);
} catch(err) { println("Erro Resolver_Notas: " + err); } finally { dialog.dispose(); }
};
// --- Interface UI ---
const dialog = new JDialog(MainApplication.getMainFrame(), "Resolver_Notas", false);
dialog.setLayout(new BorderLayout(8, 8));
dialog.setSize(new Dimension(250, 350));
dialog.setLocationRelativeTo(MainApplication.getMainFrame());
const outerPanel = new JPanel(new BorderLayout(6, 6));
outerPanel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
dialog.setContentPane(outerPanel);
const capturePanel = new JPanel();
capturePanel.setLayout(new BoxLayout(capturePanel, BoxLayout.Y_AXIS));
const btnAdd = new JButton("Capturar Nota Selecionada");
btnAdd.setAlignmentX(0.0);
btnAdd.setMaximumSize(new Dimension(Integer.MAX_VALUE, 30));
const chkInject = new JCheckBox("Inserir comentário no changeset 📝", true);
chkInject.setAlignmentX(0.0);
chkInject.setFont(chkInject.getFont().deriveFont(10.0));
chkInject.setToolTipText("Preenche automaticamente o campo 'comment' do changeset com os IDs das notas");
const chkCustomComment = new JCheckBox("Inserir comentário na nota 🚩");
chkCustomComment.setAlignmentX(0.0);
chkCustomComment.setFont(chkCustomComment.getFont().deriveFont(10.0));
chkCustomComment.setToolTipText("Abre uma caixa de texto para adicionar um prefixo personalizado ao fechar a nota");
capturePanel.add(btnAdd);
capturePanel.add(chkInject);
capturePanel.add(chkCustomComment);
outerPanel.add(capturePanel, BorderLayout.NORTH);
const listWrapper = new JPanel(new BorderLayout());
const listBorder = BorderFactory.createTitledBorder("Notas capturadas (0)");
listWrapper.setBorder(listBorder);
const listPanel = new JPanel(new GridBagLayout());
const scrollPane = new JScrollPane(listPanel);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
listWrapper.add(scrollPane, BorderLayout.CENTER);
outerPanel.add(listWrapper, BorderLayout.CENTER);
const updateUI = function() {
listPanel.removeAll();
listBorder.setTitle("Notas capturadas (" + rememberedNotes.length + ")");
const gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.insets = new Insets(2, 2, 2, 2); gbc.gridx = 0;
rememberedNotes.forEach(function(item, index) {
gbc.gridy = index;
const itemPanel = new JPanel(new BorderLayout(6, 0));
itemPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(200, 200, 200)),
BorderFactory.createEmptyBorder(3, 6, 3, 6)
));
const btnX = new JButton("x");
btnX.setPreferredSize(new Dimension(24, 20));
btnX.addActionListener(function() {
rememberedNotes.splice(index, 1);
updateUI(); refreshMap(); syncTags();
if (notesJList) notesJList.repaint();
});
const lbl = new JLabel("Nota #" + item.note.getId());
lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN, 11.0));
if (item.comment) {
const infoLabel = new JLabel("ⓘ");
infoLabel.setForeground(new Color(0, 100, 200));
infoLabel.setToolTipText(item.comment);
itemPanel.add(infoLabel, BorderLayout.EAST);
}
itemPanel.add(btnX, BorderLayout.WEST);
itemPanel.add(lbl, BorderLayout.CENTER);
listPanel.add(itemPanel, gbc);
});
gbc.gridy = rememberedNotes.length; gbc.weighty = 1.0; listPanel.add(new JPanel(), gbc);
listWrapper.revalidate(); listWrapper.repaint();
};
btnAdd.addActionListener(function() {
const noteLayer = MainApplication.getLayerManager().getNoteLayer();
if (!noteLayer) return;
const selected = noteLayer.getNoteData().getSelectedNote();
if (!selected) return;
if (selected.getState().toString() === "CLOSED") {
new Notification("Esta nota já está fechada.").setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
return;
}
if (!rememberedNotes.some(function(n){ return n.note.getId() === selected.getId(); })) {
let userComment = null;
if (chkCustomComment.isSelected()) {
userComment = JOptionPane.showInputDialog(dialog, "Comentário para a nota #" + selected.getId() + ":", "Comentário Personalizado", JOptionPane.PLAIN_MESSAGE);
if (userComment === null) return;
chkCustomComment.setSelected(false);
}
rememberedNotes.push({ note: selected, comment: userComment ? String(userComment).trim() : null });
updateUI(); refreshMap(); syncTags();
if (notesJList) notesJList.repaint();
}
});
// --- Inicialização ---
SwingUtilities.invokeLater(function() { instalarRendererDestaque(); });
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(focusHandler);
MainApplication.getLayerManager().addLayerChangeListener(layerHandler);
scanTimer = new Timer(1000, monitorChangesetID);
scanTimer.start();
dialog.addWindowListener(new (Java.extend(WindowAdapter, { windowClosing: function(e) { fecharDialogoCompleto(); } })));
dialog.setVisible(true);
})();