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

Última atualização: 2026-02-09

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