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

Imagem.gif, clique para visualizar.

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); 
})();