User:DressyPear4/RepeticaoDistancia

From OpenStreetMap Wiki
Jump to navigation Jump to search

Criar repetições de polígonos (Distância)

Script ideal para quando se quer adicionar geometrias com espaços centrais já definidos, pode-se criar cópias a partir de polígonos simples ou grupo de polígonos.

Como funciona?

  • Selecione um polígono ou grupo de polígonos
  • Selecione dois nós para definir a direção das cópias
    • Em sentido horário, caso contrário as cópias serão criadas na direção oposta
  • Selecione a distância em metros
  • Selecione a quantidade de repetições

Demonstração

Código

from org.openstreetmap.josm.data.osm import Way, Node
from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.data.coor import LatLon
from org.openstreetmap.josm.command import AddCommand, SequenceCommand
from org.openstreetmap.josm.data.UndoRedoHandler import getInstance
from javax.swing import (JOptionPane, JPanel, UIManager, JButton, JTextField, JLabel,
                          BoxLayout, JSpinner, SpinnerNumberModel, JDialog)
from java.awt import Dimension, Color, GridBagLayout, GridBagConstraints, Insets, BorderLayout
from java.util import ArrayList
import math
import java.awt.event as awte

saved_repetitions = 1
saved_side = "direita"
saved_offset = 10

def validar_selecao():
    layer = MainApplication.getLayerManager().getEditLayer()
    if layer is None or not hasattr(layer, "data"):
        Notification(u"Nenhuma camada de edição ativa.")\
        .setIcon(UIManager.getIcon("OptionPane.errorIcon"))\
        .show()
        return False

    dataset = layer.data
    selected = dataset.getSelected()

    if not selected:
        msg = (
            u"Selecione dois nós e pelo menos um polígono;"
            u"\n A sequência de seleção dos nós deve ser em sentido horário;"
            u"\n Os nós servem para indicar a direção, e não precisam pertencer ao polígono."
        )
        Notification(msg)\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()

        return False

    selected_nodes = [i for i in selected if isinstance(i, Node)]
    selected_ways = [i for i in selected if isinstance(i, Way)]

    if len(selected_nodes) < 2:
        Notification(u"Selecione dois nós para definir a direção.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()
        return False

    if len(selected_ways) < 1:
        Notification(u"Selecione pelo menos um polígono.")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()
        return False

    return True

def agrupar_poligonos_conectados(ways):
    grupos = []
    visitados = set()

    for way in ways:
        if way in visitados:
            continue

        grupo = set()
        fila = [way]

        while fila:
            atual = fila.pop()
            if atual in grupo:
                continue
            grupo.add(atual)
            visitados.add(atual)

            nos_atual = set(atual.getNodes())
            for outro in ways:
                if outro in grupo:
                    continue
                if nos_atual.intersection(outro.getNodes()):
                    fila.append(outro)

        grupos.append(grupo)

    return grupos

def copiar_tags(origem, destino):
    destino.setKeys(origem.getKeys())


def executar_criacao():
    global saved_repetitions, saved_side, saved_offset

    layer = MainApplication.getLayerManager().getEditLayer()
    dataset = layer.data
    selected = dataset.getSelected()

    selected_nodes = [i for i in selected if isinstance(i, Node)]
    selected_ways = [i for i in selected if isinstance(i, Way)]

    no1, no2 = selected_nodes[0], selected_nodes[1]
    lat1, lon1 = no1.getCoor().lat(), no1.getCoor().lon()
    lat2, lon2 = no2.getCoor().lat(), no2.getCoor().lon()

    dx = (lon2 - lon1) * 111320 * math.cos(math.radians((lat1 + lat2) / 2))
    dy = (lat2 - lat1) * 111320

    comprimento = math.hypot(dx, dy)
    if comprimento == 0:
        Notification(u"Os dois nós de referência são coincidentes.")\
        .setIcon(UIManager.getIcon("OptionPane.warningIcon"))\
        .show()
        return

    nx = -dy / comprimento
    ny = dx / comprimento

    sinal = -1 if saved_side == "esquerda" else 1

    grupos = agrupar_poligonos_conectados(selected_ways)
    comandos = []

    for grupo in grupos:
        todos_nos = set()
        for way in grupo:
            todos_nos.update(way.getNodes())

        min_proj = max_proj = None
        for node in todos_nos:
            lat = node.getCoor().lat()
            lon = node.getCoor().lon()

            dxn = (lon - lon1) * 111320 * math.cos(math.radians((lat + lat1) / 2))
            dyn = (lat - lat1) * 111320

            proj = dxn * nx + dyn * ny

            if min_proj is None or proj < min_proj:
                min_proj = proj
            if max_proj is None or proj > max_proj:
                max_proj = proj

        largura = abs(max_proj - min_proj)
        if largura == 0:
            largura = saved_offset

        deslocamento_base = largura + saved_offset

        for rep in range(saved_repetitions):
            deslocamento = deslocamento_base * (rep + 1) * sinal

            dlon = (nx * deslocamento) / (111320 * math.cos(math.radians((lat1 + lat2) / 2)))
            dlat = (ny * deslocamento) / 111320

            cache = {}
            node_map = {}

            for node in todos_nos:
                lat = node.getCoor().lat() + dlat
                lon = node.getCoor().lon() + dlon
                key = (round(lat, 8), round(lon, 8))

                if key in cache:
                    novo_no = cache[key]
                else:
                    novo_no = Node(LatLon(lat, lon))
                    comandos.append(AddCommand(dataset, novo_no))
                    cache[key] = novo_no

                node_map[node] = novo_no

            for way in grupo:
                novos_nos = [node_map[n] for n in way.getNodes()]
                new_way = Way()
                new_way.setNodes(ArrayList(novos_nos))
                copiar_tags(way, new_way)
                comandos.append(AddCommand(dataset, new_way))

    if comandos:
        getInstance().add(SequenceCommand(u"Cópia de polígono deslocado", comandos))

def abrir_dialogo():
    global saved_repetitions, saved_offset

    # 1. Configuração do JDialog
    parent = MainApplication.getMainFrame()
    dialog = JDialog(parent, u"Configurar Cópia de Polígono", True)
    dialog.setLayout(BorderLayout())

    # 2. Painel de Conteúdo (GridBagLayout)
    panel = JPanel()
    panel.setLayout(GridBagLayout())
    c = GridBagConstraints()
    c.insets = Insets(5, 5, 5, 5)

    # Label deslocamento extra (m)
    c.gridx = 0
    c.gridy = 0
    c.anchor = GridBagConstraints.EAST
    panel.add(JLabel("Deslocamento (m):"), c)

    # Campo deslocamento
    c.gridx = 1
    c.gridy = 0
    c.anchor = GridBagConstraints.WEST
    deslocamento_field = JTextField(str(saved_offset), 5)
    deslocamento_field.setPreferredSize(Dimension(80, 25))
    panel.add(deslocamento_field, c)

    # Label quantidade repeticoes
    c.gridx = 0
    c.gridy = 1
    c.anchor = GridBagConstraints.EAST
    panel.add(JLabel(u"Quantidade de repetições:"), c)

    # Spinner para repeticoes
    c.gridx = 1
    c.gridy = 1
    c.anchor = GridBagConstraints.WEST
    spinner_model = SpinnerNumberModel(saved_repetitions, 1, 100, 1)
    repeticoes_spinner = JSpinner(spinner_model)
    repeticoes_spinner.setPreferredSize(Dimension(80, 25))
    panel.add(repeticoes_spinner, c)

    # Texto informativo
    c.gridx = 0
    c.gridy = 2
    c.gridwidth = 2
    c.anchor = GridBagConstraints.CENTER
    info_label = JLabel(
        "<html><div style='text-align: center; font-size: 9px;'><i>"
        u"<span style='color: #66cc66;'>*A direção fica invertida quando os nós <br>"
        u"são selecionados em sentido anti-horário.<br>"
        u"<span style='color: #ff4444;'>*Se o deslocamento for = 0, selecione todos os nós <br>"
        u"e use a validação para remover duplicados."
        "</i></div></html>"
    )
    panel.add(info_label, c)

    # 3. Painel de Botões (Rodapé)
    button_panel = JPanel()
    icon_ok = UIManager.getIcon("OptionPane.yesIcon")
    icon_cancel = UIManager.getIcon("OptionPane.noIcon")
    btn_criar = JButton(u"Criar", icon_ok)
    btn_cancelar = JButton(u"Cancelar", icon_cancel)
    button_panel.add(btn_criar)
    button_panel.add(btn_cancelar)

    # --- ACTION LISTENERS
    def on_criar(e):
        try:
            # Captura e salva os novos valores
            global saved_offset, saved_repetitions
            saved_offset = float(deslocamento_field.getText())
            saved_repetitions = int(repeticoes_spinner.getValue())
            
            executar_criacao()
            
            Notification(u"Cópia concluída.")\
                .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
                .show()
            dialog.dispose()
        except ValueError:
            Notification(u"Valores inválidos inseridos.")\
                .setIcon(UIManager.getIcon("OptionPane.warningIcon"))\
                .show()

    def on_cancelar(e):
        dialog.dispose()

    btn_criar.addActionListener(on_criar)
    btn_cancelar.addActionListener(on_cancelar)

    # 4. Monta e Exibe o Diálogo
    dialog.add(panel, BorderLayout.CENTER)
    dialog.add(button_panel, BorderLayout.SOUTH)
    dialog.pack()
    dialog.setLocationRelativeTo(parent)
    dialog.setVisible(True)

# --- EXECUÇÃO PRINCIPAL ---
if validar_selecao():
    abrir_dialogo()