User:DressyPear4/AlinharEscada

From OpenStreetMap Wiki
Jump to navigation Jump to search

Alinhamento de polígonos ou nós

Usado principalmente para paineis solares, uma maneira automatizada de fazer alinhamento de nós ou polígonos quando dispostos em formato tipo escada.

Como funciona?

  • Selecione um grupo de nós ou polígonos
  • Ecolha entre alinhamento individual ou pares
  • Para seleção de "pares" é necessário que a quantidade total selecionada seja um número par

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.UndoRedoHandler import getInstance
from org.openstreetmap.josm.data.osm import Way
from org.openstreetmap.josm.data.coor import EastNorth
from org.openstreetmap.josm.data.projection import ProjectionRegistry
from javax.swing import UIManager, JRadioButton, ButtonGroup, JPanel, BorderFactory, JButton, JDialog
from java.awt import GridLayout, BorderLayout
from java.util import ArrayList

# Funções auxiliares

def ponto_ao_longo(a, b, t):
    return EastNorth(
        a.east() + (b.east() - a.east()) * t,
        a.north() + (b.north() - a.north()) * t
    )

def distancia2(a, b):
    dx = a.east() - b.east()
    dy = a.north() - b.north()
    return dx * dx + dy * dy

def centroide(way, proj):
    coords = [proj.latlon2eastNorth(n.getCoor()) for n in way.getNodes()]
    x = sum(p.east() for p in coords) / len(coords)
    y = sum(p.north() for p in coords) / len(coords)
    return EastNorth(x, y)

def calcular_centroide_par(ways, proj):
    coords = [proj.latlon2eastNorth(n.getCoor()) for way in ways for n in way.getNodes()]
    x = sum(p.east() for p in coords) / len(coords)
    y = sum(p.north() for p in coords) / len(coords)
    return EastNorth(x, y)

# Alinhar NÓS

def alinhar_nos(dataset, modo_pares):
    selected_nodes = ArrayList(dataset.getSelectedNodes())
    num_nodes = selected_nodes.size()
    
    if num_nodes < 4 or num_nodes % 2 != 0:
        Notification(u"Selecione um número par de nós (mínimo de 4).")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
        return

    ref_start = selected_nodes[0].getEastNorth()
    ref_end = selected_nodes[-1].getEastNorth()
    vx = ref_end.east() - ref_start.east()
    vy = ref_end.north() - ref_start.north()
    length2 = vx*vx + vy*vy or 1.0

    def proj_param(node):
        p = node.getEastNorth()
        return ((p.east() - ref_start.east()) * vx + (p.north() - ref_start.north()) * vy) / length2

    sorted_nodes = sorted(selected_nodes, key=proj_param)

    commands = []
    num_pairs = num_nodes // 2

    node1_start = sorted_nodes[0]
    node2_start = sorted_nodes[1]
    coor1_start = node1_start.getEastNorth()
    coor2_start = node2_start.getEastNorth()
    start_point = EastNorth((coor1_start.east() + coor2_start.east()) / 2,
                            (coor1_start.north() + coor2_start.north()) / 2)

    node_last1 = sorted_nodes[num_nodes - 2]
    node_last2 = sorted_nodes[num_nodes - 1]
    coor_last1 = node_last1.getEastNorth()
    coor_last2 = node_last2.getEastNorth()
    end_point = EastNorth((coor_last1.east() + coor_last2.east()) / 2,
                          (coor_last1.north() + coor_last2.north()) / 2)

    if modo_pares:
        if num_pairs < 6:
            Notification(u"Selecione pelo menos 6 pares para o modo 'Pares'.")\
            .setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
            return

        coor_first_4 = [(sorted_nodes[i].getEastNorth()) for i in range(4)]
        start_point_pares = EastNorth(
            sum(c.east() for c in coor_first_4) / 4,
            sum(c.north() for c in coor_first_4) / 4
        )
        coor_last_4 = [(sorted_nodes[i].getEastNorth()) for i in range(num_nodes - 4, num_nodes)]
        end_point_pares = EastNorth(
            sum(c.east() for c in coor_last_4) / 4,
            sum(c.north() for c in coor_last_4) / 4
        )

        num_double_pairs_intermediate = (num_pairs - 4) // 2
        for i in range(num_double_pairs_intermediate):
            pair_a_start_node = sorted_nodes[4 + i * 4]
            pair_a_end_node = sorted_nodes[4 + i * 4 + 1]
            pair_b_start_node = sorted_nodes[4 + i * 4 + 2]
            pair_b_end_node = sorted_nodes[4 + i * 4 + 3]
            
            all_4_nodes = [pair_a_start_node, pair_a_end_node, pair_b_start_node, pair_b_end_node]
            coor_all_4 = [n.getEastNorth() for n in all_4_nodes]
            current_center = EastNorth(
                sum(c.east() for c in coor_all_4) / 4,
                sum(c.north() for c in coor_all_4) / 4
            )
            t = float(i + 1) / (num_double_pairs_intermediate + 1)
            target_coor = ponto_ao_longo(start_point_pares, end_point_pares, t)

            dx = target_coor.east() - current_center.east()
            dy = target_coor.north() - current_center.north()
            if dx or dy:
                for node in all_4_nodes:
                    commands.append(MoveCommand(node, dx, dy))
    else:
        for i in range(1, num_pairs - 1):
            first_node_in_pair = sorted_nodes[i * 2]
            second_node_in_pair = sorted_nodes[i * 2 + 1]
            coor_pair_1 = first_node_in_pair.getEastNorth()
            coor_pair_2 = second_node_in_pair.getEastNorth()
            current_pair_center = EastNorth((coor_pair_1.east() + coor_pair_2.east()) / 2,
                                            (coor_pair_1.north() + coor_pair_2.north()) / 2)
            t = float(i) / (num_pairs - 1)
            target_coor = ponto_ao_longo(start_point, end_point, t)
            dx = target_coor.east() - current_pair_center.east()
            dy = target_coor.north() - current_pair_center.north()
            if dx or dy:
                commands.append(MoveCommand(first_node_in_pair, dx, dy))
                commands.append(MoveCommand(second_node_in_pair, dx, dy))

    if commands:
        getInstance().add(SequenceCommand(u"Alinhar pares de nós em degraus", commands))
        Notification(u"Nós alinhados com sucesso.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
    else:
        Notification(u"Nenhum nó precisou ser movido.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()

# Alinhar RETÂNGULOS


def alinhar_retangulos(dataset, modo_pares):
    ways = list(dataset.getSelectedWays())
    total = len(ways)
    
    if total < 3:
        Notification(u"Selecione pelo menos 3 retângulos fechados.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
        return

    if modo_pares and total % 2 != 0:
        Notification(u"Para o modo 'Pares', selecione um número PAR de retângulos (mínimo de 4).")\
        .setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
        return

    proj = ProjectionRegistry.getProjection()
    centros = [centroide(w, proj) for w in ways]

    max_dist = -1
    idx_a, idx_b = 0, 0
    for i in range(len(centros)):
        for j in range(i + 1, len(centros)):
            d2 = distancia2(centros[i], centros[j])
            if d2 > max_dist:
                max_dist = d2
                idx_a, idx_b = i, j
    
    inicio_ref = centros[idx_a]
    fim_ref = centros[idx_b]
    
    dx_ref = fim_ref.east() - inicio_ref.east()
    dy_ref = fim_ref.north() - inicio_ref.north()
    denom = dx_ref * dx_ref + dy_ref * dy_ref or 1

    alinhados = []
    for i, (way, centro) in enumerate(zip(ways, centros)):
        t = ((centro.east() - inicio_ref.east()) * dx_ref + (centro.north() - inicio_ref.north()) * dy_ref) / denom
        alinhados.append((t, way, centro))
    alinhados.sort(key=lambda x: x[0])

    comandos = []

    if modo_pares:
        num_pares = total // 2
        way_p0, way_p1 = alinhados[0][1], alinhados[1][1]
        centro_par_start = calcular_centroide_par([way_p0, way_p1], proj)
        
        way_p_last0, way_p_last1 = alinhados[total-2][1], alinhados[total-1][1]
        centro_par_end = calcular_centroide_par([way_p_last0, way_p_last1], proj)

        # Iterar sobre todos os pares e movê-los
        for i in range(num_pares):
            idx_start = i * 2
            
            way1, centro1 = alinhados[idx_start][1], alinhados[idx_start][2]
            way2, centro2 = alinhados[idx_start + 1][1], alinhados[idx_start + 1][2]
            centro_par_atual = calcular_centroide_par([way1, way2], proj)
            t = float(i) / (num_pares - 1) if num_pares > 1 else 0.5
            alvo = ponto_ao_longo(centro_par_start, centro_par_end, t)
            
            dx = alvo.east() - centro_par_atual.east()
            dy = alvo.north() - centro_par_atual.north()
            
            if dx or dy:
                comandos.append(MoveCommand(way1, dx, dy))
                comandos.append(MoveCommand(way2, dx, dy))
            
    else: # Modo Individual
        for i, (t_orig, way, centro) in enumerate(alinhados):
            if i == 0 or i == total - 1:
                continue
            t = float(i) / (total - 1)
            alvo = ponto_ao_longo(inicio_ref, fim_ref, t)
            dx = alvo.east() - centro.east()
            dy = alvo.north() - centro.north()
            if dx or dy:
                comandos.append(MoveCommand(way, dx, dy))

    if comandos:
        getInstance().add(SequenceCommand(u"Distribuir retângulos como escada", comandos))
        Notification(u"Retângulos reposicionados corretamente.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
    else:
        Notification(u"Nada foi ajustado.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()

# Main

def main():
    dataset = MainApplication.getLayerManager().getEditDataSet()
    if not dataset:
        Notification(u"Nenhuma camada ativa.")\
        .setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
        return

    # Obtém a janela principal do JOSM (o 'parent')
    parent = MainApplication.getMainFrame()
    
    # Cria o diálogo NÃO MODAL (o 'False' é crucial)
    dialog = JDialog(parent, u"Configurações de Alinhamento", False)
    dialog.setLayout(BorderLayout())

    # --- Painel de opções ---
    main_panel = JPanel(GridLayout(0, 1))

    # Tipo de Alinhamento
    type_panel = JPanel()
    type_panel.setBorder(BorderFactory.createTitledBorder(u"Tipo de Alinhamento"))
    type_group = ButtonGroup()
    radio_nos = JRadioButton(u"Alinhar Nós", False)
    radio_rets = JRadioButton(u"Alinhar Retângulos", True)
    type_group.add(radio_nos)
    type_group.add(radio_rets)
    type_panel.add(radio_nos)
    type_panel.add(radio_rets)

    # Modo (Individual ou Pares)
    mode_panel = JPanel()
    mode_panel.setBorder(BorderFactory.createTitledBorder(u"Modo"))
    mode_group = ButtonGroup()
    radio_individual = JRadioButton(u"Individual", True)
    radio_pares = JRadioButton(u"Pares", False)
    mode_group.add(radio_individual)
    mode_group.add(radio_pares)
    mode_panel.add(radio_individual)
    mode_panel.add(radio_pares)
    
    main_panel.add(type_panel)
    main_panel.add(mode_panel)

    button_panel = JPanel()
    icon_ok = UIManager.getIcon("OptionPane.yesIcon")
    icon_cancel = UIManager.getIcon("OptionPane.cancelIcon")
    ok_button = JButton(u"Aceitar", icon_ok)
    cancel_button = JButton(u"Cancelar", icon_cancel)
    button_panel.add(ok_button)
    button_panel.add(cancel_button)

    dialog.add(main_panel, BorderLayout.CENTER)
    dialog.add(button_panel, BorderLayout.SOUTH)

    def on_ok_click(event):
        modo_pares = radio_pares.isSelected()

        if radio_nos.isSelected():
            alinhar_nos(dataset, modo_pares)
        else:
            alinhar_retangulos(dataset, modo_pares)
            
        dialog.dispose()

    def on_cancel_click(event):
        dialog.dispose()

    ok_button.addActionListener(on_ok_click)
    cancel_button.addActionListener(on_cancel_click)

    dialog.pack()
    dialog.setLocationRelativeTo(parent)
    dialog.setVisible(True)

main()