User:DressyPear4/GirarProprioEixo

From OpenStreetMap Wiki
Jump to navigation Jump to search

Girar polígonos no próprio eixo

Ideal para fazer a correção da inclinação de grupos de polígonos. Há uma diferença na função atual do JOSM, segurando Shift+Ctrl+Click também faz o giro mas quando selecionado multiplos, é feito o giro sobre o pivô central de todo grupo, com o script cada geometria gira no seu próprio eixo.

Como funciona?

  • Selecione um ou mais polígonos
  • Selecione o grau de rotação
  • Escolha a direção de inclinação

Demonstração

Imagem.gif, clique para visualizar.

Código

from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.data.osm import Way, Node
from org.openstreetmap.josm.data.coor import EastNorth, LatLon
from org.openstreetmap.josm.data.projection import ProjectionRegistry
from org.openstreetmap.josm.command import ChangeCommand, SequenceCommand
from org.openstreetmap.josm.data.UndoRedoHandler import getInstance
from javax.swing import JDialog, JPanel, JButton, JLabel, JOptionPane, BoxLayout, Timer, BorderFactory, JSpinner, SpinnerNumberModel, UIManager
from java.awt import Dimension, Component, event
from java.lang import Math

# --------------------------
# Funções auxiliares
# --------------------------

def get_centroid_en(way):
    proj = ProjectionRegistry.getProjection()
    en_list = [proj.latlon2eastNorth(n.getCoor()) for n in way.getNodes() if n.isUsable()]
    if not en_list:
        return None
    sx, sy = 0.0, 0.0
    for en in en_list:
        sx += en.east()
        sy += en.north()
    return EastNorth(sx / len(en_list), sy / len(en_list))

def rotate_point(en, center, angle):
    dx, dy = en.east() - center.east(), en.north() - center.north()
    cos_a, sin_a = Math.cos(angle), Math.sin(angle)
    x_new = dx * cos_a - dy * sin_a + center.east()
    y_new = dx * sin_a + dy * cos_a + center.north()
    return EastNorth(x_new, y_new)

def rotate_way(way, angle):
    center = get_centroid_en(way)
    if center is None:
        return []
    proj = ProjectionRegistry.getProjection()
    cmds = []
    node_changes = {}
    for n in way.getNodes():
        if not n.isUsable(): continue
        if n in node_changes: continue
        en = proj.latlon2eastNorth(n.getCoor())
        en_rot = rotate_point(en, center, angle)
        newCoor = proj.eastNorth2latlon(en_rot)
        n_new = Node(n)
        n_new.setCoor(newCoor)
        node_changes[n] = n_new
        cmds.append(ChangeCommand(n, n_new))
    return cmds

# --------------------------
# Painel de rotação
# --------------------------

class RotateDialog(JDialog):
    def __init__(self, ways):
        JDialog.__init__(self, MainApplication.getMainFrame(), u"Rotacionar Polígonos", True)
        self.ways = ways
        self.timer_delay = 50
        self.total_angle_rad = 0.0
        self.original_nodes = {}
        for w in ways:
            for n in w.getNodes():
                if n.isUsable() and n not in self.original_nodes:
                    self.original_nodes[n] = n.getCoor()

        self.spinner_model = SpinnerNumberModel(5, 1, 360, 1)
        self.angle_spinner = JSpinner(self.spinner_model)

        self.timer = Timer(self.timer_delay, self.timer_action_listener)
        self.timer.setInitialDelay(200)

        panel = JPanel()
        panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10))

        lbl_angle = JLabel(u"Passo (graus):")
        lbl_angle.setAlignmentX(Component.CENTER_ALIGNMENT)
        self.angle_spinner.setAlignmentX(Component.CENTER_ALIGNMENT)

        panel.add(lbl_angle)
        panel.add(self.angle_spinner)
        panel.add(JLabel(u" "))

        btn_plus = JButton(u"(+) ⟲")
        btn_plus.setAlignmentX(Component.CENTER_ALIGNMENT)
        btn_plus.setMaximumSize(Dimension(250, 40))
        btn_plus.addMouseListener(RotationMouseListener(self, 1))
        btn_plus.addActionListener(lambda e: self.apply_rotation(self.get_rotation_angle_rad(1)))
        panel.add(btn_plus)

        panel.add(JLabel(u" "))

        btn_minus = JButton(u"(-) ⟳")
        btn_minus.setAlignmentX(Component.CENTER_ALIGNMENT)
        btn_minus.setMaximumSize(Dimension(250, 40))
        btn_minus.addMouseListener(RotationMouseListener(self, -1))
        btn_minus.addActionListener(lambda e: self.apply_rotation(self.get_rotation_angle_rad(-1)))
        panel.add(btn_minus)

        panel.add(JLabel(u" "))

        btn_accept = JButton(u"Aceitar", UIManager.getIcon("OptionPane.okIcon"))
        btn_accept.setMaximumSize(Dimension(120, 40))
        btn_accept.addActionListener(lambda e: self.accept_rotation())

        btn_cancel = JButton(u"Cancelar", UIManager.getIcon("OptionPane.noIcon"))
        btn_cancel.setMaximumSize(Dimension(120, 40))
        btn_cancel.addActionListener(lambda e: self.cancel_rotation())

        button_panel = JPanel()
        button_panel.setLayout(BoxLayout(button_panel, BoxLayout.X_AXIS))
        button_panel.setAlignmentX(Component.CENTER_ALIGNMENT)
        button_panel.add(btn_accept)
        button_panel.add(JLabel("   "))  # Espaço entre os botões
        button_panel.add(btn_cancel)

        panel.add(button_panel)

        self.add(panel)
        self.pack()
        self.setResizable(False)
        self.setLocationRelativeTo(MainApplication.getMainFrame())

        self.addWindowListener(DialogWindowListener(self.timer))

        self.setVisible(True)

    def timer_action_listener(self, e):
        angle_rad = float(self.timer.getActionCommand())
        if angle_rad:
            try:
                self.apply_rotation(angle_rad)
            except Exception, ex:
                self.timer.stop()
                Notification(u"Erro ao aplicar rotação contínua: %s" % ex).setIcon(JOptionPane.ERROR_MESSAGE).show()

    def get_rotation_angle_rad(self, sign):
        angle_degrees = self.angle_spinner.getValue()
        angle_rad = Math.toRadians(angle_degrees)
        return angle_rad * sign

    def apply_rotation(self, angle):
        cmds = []
        for w in self.ways:
            cmds.extend(rotate_way(w, angle))
        if cmds:
            seq = SequenceCommand(u"Rotacionar polígonos (%.2f°)" % Math.toDegrees(angle), cmds)
            getInstance().add(seq)
            self.total_angle_rad += angle

    def accept_rotation(self):
        if abs(self.total_angle_rad) > 1e-6:
            Notification(u"Girado com sucesso (%.2f°)" % Math.toDegrees(self.total_angle_rad)).setIcon(JOptionPane.INFORMATION_MESSAGE).show()
        else:
            Notification(u"Nenhuma rotação foi aplicada.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        self.dispose()

    def cancel_rotation(self):
        cmds = []
        for n, original_coor in self.original_nodes.items():
            n_new = Node(n)
            n_new.setCoor(original_coor)
            cmds.append(ChangeCommand(n, n_new))
        if cmds:
            seq = SequenceCommand(u"Cancelar rotação", cmds)
            getInstance().add(seq)
        Notification(u"Rotação cancelada pelo usuário.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        self.dispose()

class RotationMouseListener(event.MouseAdapter):
    def __init__(self, dialog, sign):
        self.dialog = dialog
        self.sign = sign

    def mousePressed(self, e):
        angle_rad = self.dialog.get_rotation_angle_rad(self.sign)
        self.dialog.timer.setActionCommand(str(angle_rad))
        self.dialog.timer.start()

    def mouseReleased(self, e):
        self.dialog.timer.stop()

class DialogWindowListener(event.WindowAdapter):
    def __init__(self, timer):
        self.timer = timer

    def windowClosing(self, e):
        self.timer.stop()

# --------------------------
# Função principal
# --------------------------

def run_rotation_script():
    layer = MainApplication.getLayerManager().getEditLayer()
    if not layer:
        Notification(u"Nenhuma camada de edição ativa.").setIcon(JOptionPane.ERROR_MESSAGE).show()
        return

    sel = layer.data.getSelected()
    ways = [o for o in sel if isinstance(o, Way) and o.isClosed() and o.isUsable()]
    if not ways:
        Notification(u"Nenhum polígono selecionado.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    try:
        RotateDialog(ways)
    except Exception, e:
        Notification(u"A interface de rotação encontrou um erro: %s" % e).setIcon(JOptionPane.ERROR_MESSAGE).show()

# --------------------------
# EXECUÇÃO
# --------------------------
run_rotation_script()