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

from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.command import MoveCommand, SequenceCommand
from org.openstreetmap.josm.data.coor import EastNorth
from org.openstreetmap.josm.data import UndoRedoHandler
from org.openstreetmap.josm.data.osm import Way
from javax.swing import (JPanel, JLabel, JButton, BoxLayout, JRadioButton,
                         ButtonGroup, UIManager, JDialog, BorderFactory)
from java.awt import Dimension, BorderLayout, Font
import java.awt.event as awte
import math

initial_positions = {}

def group_nodes_by_way(selected_nodes):
    groups = {}
    for n in selected_nodes:
        for ref in n.getReferrers():
            if isinstance(ref, Way):
                if ref not in groups:
                    groups[ref] = set()
                groups[ref].add(n)
    return groups


def get_sorted_nodes(nodes, way):
    way_nodes = way.getNodes()
    node_indices = {n: i for i, n in enumerate(way_nodes) if n in nodes}

    sorted_nodes = sorted(nodes, key=lambda n: node_indices.get(n, -1))

    if len(sorted_nodes) >= 2 and way.isClosed():
        diffs = [node_indices[sorted_nodes[i + 1]] - node_indices[sorted_nodes[i]]
                 for i in range(len(sorted_nodes) - 1)]
        if diffs and diffs[0] < 0:
            sorted_nodes.reverse()
    return sorted_nodes


def get_dominant_edge_vector(nodes):
    if len(nodes) < 2:
        return (1, 0)
    max_len = 0
    best_dx, best_dy = 1, 0
    for i in range(len(nodes) - 1):
        p0 = nodes[i].getEastNorth()
        p1 = nodes[i + 1].getEastNorth()
        dx = p1.east() - p0.east()
        dy = p1.north() - p0.north()
        length = math.hypot(dx, dy)
        if length > max_len:
            max_len = length
            best_dx, best_dy = dx, dy
    if max_len == 0:
        return (1, 0)
    dx, dy = best_dx / max_len, best_dy / max_len
    if dx < 0: 
        dx, dy = -dx, -dy
    return (dx, dy)


def mover_nos(passo, direcao, dialog_state):
    global initial_positions
    dataset = MainApplication.getLayerManager().getEditDataSet()
    if not dataset:
        return
    selected_nodes = list(dataset.getSelectedNodes())
    if not selected_nodes:
        return

    groups = group_nodes_by_way(selected_nodes)
    cmds = []

    for way, nodes in groups.items():
        sorted_nodes = get_sorted_nodes(list(nodes), way)
        dx, dy = get_dominant_edge_vector(sorted_nodes)
        if direcao == "dy":
            dx, dy = dy, -dx
        delta_x = dx * passo
        delta_y = dy * passo

        for n in nodes:
            if n not in initial_positions:
                initial_positions[n] = n.getEastNorth()
            cmds.append(MoveCommand(n, delta_x, delta_y))

    if cmds:
        UndoRedoHandler.getInstance().add(SequenceCommand(u"Mover nós", cmds))
        dialog_state['has_moved'] = True


def main():
    dataset = MainApplication.getLayerManager().getEditDataSet()
    if not dataset:
        Notification(u"Nenhuma camada de edição ativa.")\
            .setIcon(UIManager.getIcon("OptionPane.errorIcon"))\
            .show()
        return
    if not dataset.getSelectedNodes():
        Notification(u"Por favor, selecione pelo menos um nó.")\
            .setIcon(UIManager.getIcon("OptionPane.warningIcon"))\
            .show()
        return

    dialog_state = {'has_moved': False}

    panel = JPanel()
    panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))

    rb_1 = JRadioButton("Passo: 1")
    rb_10 = JRadioButton("Passo: 10")
    rb_1.setSelected(True)
    original_font = rb_1.getFont()
    bold_font = Font(original_font.getName(), Font.BOLD, original_font.getSize())
    rb_1.setFont(bold_font)
    rb_10.setFont(bold_font)
    g = ButtonGroup()
    g.add(rb_1)
    g.add(rb_10)
    radio_panel = JPanel()
    radio_panel.add(rb_1)
    radio_panel.add(rb_10)
    panel.add(radio_panel)

    def get_passo():
        return 10.0 if rb_10.isSelected() else 1.0

    def create_control_panel(label_text, direcao):
        control_panel = JPanel()
        control_panel.setLayout(BoxLayout(control_panel, BoxLayout.Y_AXIS))
        padding = BorderFactory.createEmptyBorder(5, 5, 5, 5)
        etched_border = BorderFactory.createEtchedBorder()
        control_panel.setBorder(BorderFactory.createCompoundBorder(padding, etched_border))
        label_panel = JPanel()
        label = JLabel(label_text)
        label_panel.add(label)
        button_panel = JPanel()
        btn_minus = JButton("-")
        btn_plus = JButton("+")
        btn_minus.setPreferredSize(Dimension(50, 25))
        btn_plus.setPreferredSize(Dimension(50, 25))

        def action_listener(e):
            passo = get_passo() if e.getSource() == btn_plus else -get_passo()
            mover_nos(passo, direcao, dialog_state)

        btn_minus.addActionListener(action_listener)
        btn_plus.addActionListener(action_listener)
        button_panel.add(btn_minus)
        button_panel.add(btn_plus)
        control_panel.add(label_panel)
        control_panel.add(button_panel)
        return control_panel

    button_definitions = [(u"Paralelo", "dx"), (u"Perpendicular", "dy")]
    for label, direcao in button_definitions:
        panel.add(create_control_panel(label, direcao))

    footer = JPanel()
    btn_aceitar = JButton("Aceitar")
    btn_cancelar = JButton("Cancelar")
    footer.add(btn_aceitar)
    footer.add(btn_cancelar)

    parent = MainApplication.getMainFrame()
    dialog = JDialog(parent, u"Mover Nós", False)

    class DragListener(awte.MouseAdapter):
        def __init__(self): self.p = None
        def mousePressed(self, e): self.p = e.getPoint()
        def mouseDragged(self, e):
            if self.p:
                loc = dialog.getLocation()
                dialog.setLocation(loc.x + e.getX() - self.p.x, loc.y + e.getY() - self.p.y)

    drag_listener = DragListener()
    dialog.addMouseListener(drag_listener)
    dialog.addMouseMotionListener(drag_listener)

    content_panel = JPanel(BorderLayout())
    content_panel.add(panel, BorderLayout.CENTER)
    content_panel.add(footer, BorderLayout.SOUTH)
    dialog.setContentPane(content_panel)
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
    dialog.pack()
    dialog.setLocationRelativeTo(parent)

    def on_aceitar(e):
        if dialog_state['has_moved']:
            Notification(u"Nós movidos com sucesso!")\
                .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
                .show()
        else:
            Notification(u"Nenhum nó foi movido.")\
                .setIcon(UIManager.getIcon("OptionPane.warningIcon"))\
                .show()
        dialog.dispose()

    def on_cancelar(e):
        global initial_positions
        if dialog_state['has_moved']:
            cmds = []
            for node, original_pos in initial_positions.items():
                current_pos = node.getEastNorth()
                dx = original_pos.east() - current_pos.east()
                dy = original_pos.north() - current_pos.north()
                if abs(dx) > 1e-9 or abs(dy) > 1e-9:
                    cmds.append(MoveCommand(node, dx, dy))
            if cmds:
                UndoRedoHandler.getInstance().add(
                    SequenceCommand(u"Cancelar movimento de nós", cmds)
                )
            Notification(u"Ação cancelada pelo usuário.")\
                .setIcon(UIManager.getIcon("OptionPane.warningIcon"))\
                .show()
        dialog.dispose()

    btn_aceitar.addActionListener(on_aceitar)
    btn_cancelar.addActionListener(on_cancelar)

    dialog.setVisible(True)

main()