User:DressyPear4/MoveNos
From OpenStreetMap Wiki
Jump to navigation
Jump to search
Mover nós selecionadodos
Mover nós individuais ou em grupos (passo de 1m ou 10m) mantendo sempre o mesmo espaço entre eles, pode ser usado para fazer o deslocamento de poligonos quando selecionado todos, ou alterar o tamanho.
Como funciona?
- Selecione quantidade de nós desejada
- Escolha entre deslocamento paralelo ou perpendicular
- Todos os nós do poligono - desloca
- Apenas parte dos nós - altera geometria
Demonstração

Código
Python
Última atualização: 2026-03-11
from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.command import MoveCommand, SequenceCommand
from org.openstreetmap.josm.data.coor import EastNorth, LatLon
from org.openstreetmap.josm.data import UndoRedoHandler
from org.openstreetmap.josm.data.osm import Way, Node
from javax.swing import (JPanel, JLabel, JButton, BoxLayout, JSpinner,
SpinnerNumberModel, UIManager, JDialog, BorderFactory)
from java.awt import Dimension, BorderLayout, Font
from javax.swing.event import ChangeListener
import math
import java.awt.event as awte
initial_positions = {}
def get_meter_to_en_ratio(node):
lat = node.getCoor().lat()
return 1.0 / math.cos(math.radians(lat))
def validar_requisitos():
dataset = MainApplication.getLayerManager().getEditDataSet()
if not dataset:
Notification(u"Nenhuma camada de edição ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
return False
selected_nodes = list(dataset.getSelectedNodes())
if len(selected_nodes) < 2:
Notification(u"Selecione pelo menos 2 nós.\nOs dois primeiros definem a direção do eixo.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
return False
return True
def mover_nos(passo_metros, direcao_tipo, dialog_state):
global initial_positions
dataset = MainApplication.getLayerManager().getEditDataSet()
if not dataset: return
selected_nodes = list(dataset.getSelectedNodes())
if len(selected_nodes) < 2:
Notification(u"Ação interrompida: Seleção insuficiente (mínimo 2 nós).")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
return
# Dois primeiros nós selecionados para definir a geometria do movimento
n1, n2 = selected_nodes[0], selected_nodes[1]
# Cálculos Geométricos
c1, c2 = n1.getCoor(), n2.getCoor()
lat_rad = math.radians((c1.lat() + c2.lat()) / 2.0)
m_per_deg_lat = 111319.492
m_per_deg_lon = m_per_deg_lat * math.cos(lat_rad)
dx_m = (c2.lon() - c1.lon()) * m_per_deg_lon
dy_m = (c2.lat() - c1.lat()) * m_per_deg_lat
comp = math.hypot(dx_m, dy_m)
if comp < 1e-6:
Notification(u"Os nós de referência são coincidentes demais.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
return
ux, uy = dx_m / comp, dy_m / comp
if direcao_tipo == "dy":
ux, uy = -uy, ux
cmds = []
scale = get_meter_to_en_ratio(n1)
delta_en_x = ux * passo_metros * scale
delta_en_y = uy * passo_metros * scale
for n in selected_nodes:
if n not in initial_positions:
initial_positions[n] = n.getEastNorth()
cmds.append(MoveCommand(n, delta_en_x, delta_en_y))
if cmds:
UndoRedoHandler.getInstance().add(SequenceCommand(u"Mover nós", cmds))
dialog_state['has_moved'] = True
def main():
if not validar_requisitos():
return
dialog_state = {'has_moved': False}
global initial_positions
initial_positions = {}
# --- Construção da Interface ---
panel = JPanel()
panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))
# Painel do Spinner
spinner_panel = JPanel()
spinner_panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10))
model = SpinnerNumberModel(1.0, 0.0, 50.0, 1.0)
spinner = JSpinner(model)
spinner.setEditor(JSpinner.NumberEditor(spinner, "0.0"))
spinner.setPreferredSize(Dimension(80, 30))
# Lógica de Passo Dinâmico (0.1 -> 1.0 -> 10.0)
class StepListener(ChangeListener):
def stateChanged(self, e):
val = float(model.getValue())
if val < 1.0: model.setStepSize(0.1)
elif val < 10.0: model.setStepSize(1.0)
else: model.setStepSize(10.0)
spinner.addChangeListener(StepListener())
label_spinner = JLabel("Passo (metros): ")
label_spinner.setFont(Font("SansSerif", Font.BOLD, 12))
spinner_panel.add(label_spinner)
spinner_panel.add(spinner)
panel.add(spinner_panel)
# Painéis de Controle (Botões + e -)
def create_btn_panel(titulo, direcao):
p = JPanel()
p.setBorder(BorderFactory.createTitledBorder(titulo))
btn_m = JButton("-")
btn_p = JButton("+")
btn_m.setPreferredSize(Dimension(60, 32))
btn_p.setPreferredSize(Dimension(60, 32))
def move_action(e):
val = float(model.getValue())
passo = val if e.getSource() == btn_p else -val
mover_nos(passo, direcao, dialog_state)
btn_m.addActionListener(move_action)
btn_p.addActionListener(move_action)
p.add(btn_m)
p.add(btn_p)
return p
panel.add(create_btn_panel(u"Paralelo", "dx"))
panel.add(create_btn_panel(u"Perpendicular", "dy"))
# Rodapé de Ação
footer = JPanel()
btn_ok = JButton("Aceitar", UIManager.getIcon("OptionPane.yesIcon"))
btn_cc = JButton("Cancelar", UIManager.getIcon("OptionPane.noIcon"))
footer.add(btn_ok)
footer.add(btn_cc)
# Configuração da Janela (JDialog)
parent = MainApplication.getMainFrame()
dialog = JDialog(parent, u"Ajuste Fino de Posição", False)
def on_ok(e):
if dialog_state['has_moved']:
Notification(u"Movimentos aplicados com sucesso!")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
else:
Notification(u"Nenhuma alteração realizada.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
dialog.dispose()
def on_cancel(e):
if dialog_state['has_moved']:
cmds = []
for node, pos in initial_positions.items():
cur = node.getEastNorth()
# Calcula o vetor de retorno ao ponto original
cmds.append(MoveCommand(node, pos.east() - cur.east(), pos.north() - cur.north()))
if cmds:
UndoRedoHandler.getInstance().add(SequenceCommand(u"Reverter Ajuste Fino", cmds))
Notification(u"Movimentos revertidos.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
dialog.dispose()
btn_ok.addActionListener(on_ok)
btn_cc.addActionListener(on_cancel)
content = JPanel(BorderLayout())
content.add(panel, BorderLayout.CENTER)
content.add(footer, BorderLayout.SOUTH)
dialog.setContentPane(content)
dialog.pack()
dialog.setLocationRelativeTo(parent)
dialog.setAlwaysOnTop(True)
dialog.setVisible(True)
# Execução
main()
JavaScript
Última atualização: 2026-03-11
"use strict";
const MainApplication = Java.type("org.openstreetmap.josm.gui.MainApplication");
const Notification = Java.type("org.openstreetmap.josm.gui.Notification");
const MoveCommand = Java.type("org.openstreetmap.josm.command.MoveCommand");
const SequenceCommand = Java.type("org.openstreetmap.josm.command.SequenceCommand");
const UndoRedoHandler = Java.type("org.openstreetmap.josm.data.UndoRedoHandler");
const UIManager = Java.type("javax.swing.UIManager");
const JDialog = Java.type("javax.swing.JDialog");
const JPanel = Java.type("javax.swing.JPanel");
const JLabel = Java.type("javax.swing.JLabel");
const JButton = Java.type("javax.swing.JButton");
const JSpinner = Java.type("javax.swing.JSpinner");
const SpinnerNumberModel = Java.type("javax.swing.SpinnerNumberModel");
const BoxLayout = Java.type("javax.swing.BoxLayout");
const BorderFactory = Java.type("javax.swing.BorderFactory");
const BorderLayout = Java.type("java.awt.BorderLayout");
const Dimension = Java.type("java.awt.Dimension");
const Font = Java.type("java.awt.Font");
const ArrayList = Java.type("java.util.ArrayList");
(function() {
const layer = MainApplication.getLayerManager().getEditLayer();
if (!layer) {
new Notification("Nenhuma camada ativa.")
.setIcon(UIManager.getIcon("OptionPane.errorIcon"))
.show();
return;
}
const initialPositions = new Map();
let hasMoved = false;
// --- LÓGICA DE MOVIMENTO ---
const aplicarMovimento = function(distancia, tipo) {
try {
const currentLayer = MainApplication.getLayerManager().getEditLayer();
if (!currentLayer) return;
const nodes = currentLayer.data.getSelectedNodes();
if (nodes.size() < 2) {
new Notification("Selecione pelo menos 2 nós.")
.setIcon(UIManager.getIcon("OptionPane.warningIcon"))
.show();
return;
}
const it = nodes.iterator();
const n1 = it.next();
const n2 = it.next();
const c1 = n1.getCoor();
const c2 = n2.getCoor();
const latRad = ((c1.lat() + c2.lat()) / 2.0) * (Math.PI / 180.0);
const mPerDegLat = 111319.492;
const mPerDegLon = mPerDegLat * Math.cos(latRad);
const dxM = (c2.lon() - c1.lon()) * mPerDegLon;
const dyM = (c2.lat() - c1.lat()) * mPerDegLat;
const comp = Math.sqrt(dxM * dxM + dyM * dyM);
if (comp < 1e-6) return;
let ux = dxM / comp;
let uy = dyM / comp;
if (tipo === "dy") { const tmp = ux; ux = -uy; uy = tmp; }
const scale = 1.0 / Math.cos(c1.lat() * Math.PI / 180.0);
const dEnX = ux * distancia * scale;
const dEnY = uy * distancia * scale;
const cmds = new ArrayList();
const itAll = nodes.iterator();
while (itAll.hasNext()) {
const n = itAll.next();
if (!initialPositions.has(n)) initialPositions.set(n, n.getEastNorth());
cmds.add(new MoveCommand(n, dEnX, dEnY));
}
UndoRedoHandler.getInstance().add(new SequenceCommand("Ajuste Fino", cmds));
hasMoved = true;
} catch (e) {
print("Erro na execução: " + e);
}
};
// --- INTERFACE (MODAL) ---
const dialog = new JDialog(MainApplication.getMainFrame(), "Ajuste Fino", false);
const mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
const model = new SpinnerNumberModel(1.0, 0.0, 50.0, 0.5);
const spinner = new JSpinner(model);
spinner.setPreferredSize(new Dimension(85, 28));
const sP = new JPanel();
sP.add(new JLabel("Passo (m):"));
sP.add(spinner);
mainPanel.add(sP);
const btnSize = new Dimension(65, 30);
const btnFont = new Font("SansSerif", Font.BOLD, 14);
const criarGrupo = (tit, tp) => {
const p = new JPanel();
p.setBorder(BorderFactory.createTitledBorder(tit));
const bM = new JButton("-");
const bP = new JButton("+");
[bM, bP].forEach(b => {
b.setPreferredSize(btnSize);
b.setFont(btnFont);
p.add(b);
b.addActionListener(function() {
aplicarMovimento((b.getText() === "+" ? 1 : -1) * model.getValue(), tp);
});
});
return p;
};
mainPanel.add(criarGrupo("Paralelo", "dx"));
mainPanel.add(criarGrupo("Perpendicular", "dy"));
// --- RODAPÉ COM BOTÕES E ÍCONES ---
const footer = new JPanel();
const btnOk = new JButton("Concluir", UIManager.getIcon("OptionPane.okIcon"));
const btnCc = new JButton("Reverter", UIManager.getIcon("OptionPane.noIcon"));
btnOk.setFont(new Font("SansSerif", Font.PLAIN, 12));
btnCc.setFont(new Font("SansSerif", Font.PLAIN, 12));
btnOk.addActionListener(function() {
if (hasMoved) {
new Notification("Ajustes finalizados.")
.setIcon(UIManager.getIcon("OptionPane.informationIcon"))
.show();
}
dialog.dispose();
});
btnCc.addActionListener(function() {
if (hasMoved) {
const cmds = new ArrayList();
initialPositions.forEach((pos, node) => {
const cur = node.getEastNorth();
cmds.add(new MoveCommand(node, pos.east() - cur.east(), pos.north() - cur.north()));
});
UndoRedoHandler.getInstance().add(new SequenceCommand("Reverter Ajustes", cmds));
new Notification("Ajustes revertidos.")
.setIcon(UIManager.getIcon("OptionPane.warningIcon"))
.show();
}
dialog.dispose();
});
footer.add(btnOk);
footer.add(btnCc);
const content = new JPanel(new BorderLayout());
content.add(mainPanel, BorderLayout.CENTER);
content.add(footer, BorderLayout.SOUTH);
dialog.setContentPane(content);
dialog.pack();
dialog.setAlwaysOnTop(false);
dialog.setLocationRelativeTo(MainApplication.getMainFrame());
// --- EXECUÇÃO ---
dialog.setVisible(true);
})();