User:DressyPear4/MoveMatriz

From OpenStreetMap Wiki
Jump to navigation Jump to search

Mover polígonos em Matriz

Afasta mantendo a mesma distância e alinhamento entre cada polígono, diferente de Repeticão_Distancia que cria novos poligonos, esse apenas move-os.

Como funciona?

  • Selecione um grupo de polígonos
  • Selecione dois nós para definir a direção de deslocamento
    • Nós selecionados servem para travar o polígono e afastar os outros a partir dele
  • Selecione a distância em metros
  • Use os botões + e - para controle

Demonstração

Imagem.gif, clique para visualizar.

Código

Python

Última atualização: 2026-04-06

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 javax.swing import (JPanel, UIManager, JButton, JTextField, JLabel, 
                          JSpinner, SpinnerNumberModel, JDialog, BorderFactory)
from java.awt import GridBagLayout, GridBagConstraints, Insets, BorderLayout
import math

# --- ESTADO GLOBAL ---
objetos_grade = [] 
ref_globais = []

def validar_e_analisar():
    layer = MainApplication.getLayerManager().getEditLayer()
    if not layer or not hasattr(layer, "data"):
        Notification(u"Erro: Nenhuma camada de edição ativa.").setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
        return False
    
    sel = layer.data.getSelected()
    nodes_ref = [i for i in sel if isinstance(i, Node)][0:2]
    ways = [i for i in sel if isinstance(i, Way)]
    
    if not ways:
        Notification(u"Selecione os polígonos da grade.").setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()
        return False

    # Se não houver 2 nós selecionados, tenta usar os 2 primeiros nós do primeiro polígono
    if len(nodes_ref) < 2:
        nodes_ref = [ways[0].getNode(0), ways[0].getNode(1)]
        Notification(u"Usando orientação automática baseada no primeiro polígono.").setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()

    n1 = nodes_ref[0].getCoor()
    n2 = nodes_ref[1].getCoor()
    
    m_lat = 111320.0
    m_lon = m_lat * math.cos(math.radians(n1.lat()))
    dx = (n2.lon() - n1.lon()) * m_lon
    dy = (n2.lat() - n1.lat()) * m_lat
    d_base = math.hypot(dx, dy)
    if d_base == 0: return False
    
    ux, uy = dx/d_base, dy/d_base 
    nx, ny = -uy, ux              

    grade = []
    for w in ways:
        nos_w = {n: n.getCoor() for n in w.getNodes()}
        clat = sum(n.getCoor().lat() for n in nos_w) / len(nos_w)
        clon = sum(n.getCoor().lon() for n in nos_w) / len(nos_w)
        v_dx, v_dy = (clon - n1.lon()) * m_lon, (clat - n1.lat()) * m_lat
        
        grade.append({
            'nos': nos_w,
            'p_para': v_dx * ux + v_dy * uy,
            'p_perp': v_dx * nx + v_dy * ny,
            'is_origem': any(n in nodes_ref for n in nos_w)
        })

    # Normalização da Grade
    origem = [o for o in grade if o['is_origem']]
    base_para = origem[0]['p_para'] if origem else min(o['p_para'] for o in grade)
    base_perp = origem[0]['p_perp'] if origem else min(o['p_perp'] for o in grade)

    for obj in grade:
        # Tolerância de 5m para agrupar polígonos na mesma linha/coluna
        obj['mult_para'] = round((obj['p_para'] - base_para) / 5.0)
        obj['mult_perp'] = round((obj['p_perp'] - base_perp) / 5.0)
        if obj['mult_para'] < 0: obj['mult_para'] = 0
        if obj['mult_perp'] < 0: obj['mult_perp'] = 0

    return grade, nodes_ref

def executar_afastamento_grade(off_p, off_a):
    layer = MainApplication.getLayerManager().getEditLayer()
    n1, n2 = ref_globais[0], ref_globais[1]
    c1 = n1.getCoor()
    m_lat = 111320.0
    m_lon = m_lat * math.cos(math.radians(c1.lat()))
    dx = (n2.getCoor().lon() - c1.lon()) * m_lon
    dy = (n2.getCoor().lat() - c1.lat()) * m_lat
    d_base = math.hypot(dx, dy)
    ux, uy = dx/d_base, dy/d_base 
    nx, ny = -uy, ux          

    s_para_lon, s_para_lat = (ux * off_a) / m_lon, (uy * off_a) / m_lat
    s_perp_lon, s_perp_lat = (nx * off_p) / m_lon, (ny * off_p) / m_lat

    for obj in objetos_grade:
        for node, coor_ini in obj['nos'].items():
            if node in ref_globais: continue
            m_para, m_perp = obj['mult_para'], obj['mult_perp']
            nova_lat = coor_ini.lat() + (s_para_lat * m_para) + (s_perp_lat * m_perp)
            nova_lon = coor_ini.lon() + (s_para_lon * m_para) + (s_perp_lon * m_perp)
            node.setCoor(LatLon(nova_lat, nova_lon))
    layer.invalidate()

def abrir_dialogo():
    global objetos_grade, ref_globais
    res = validar_e_analisar()
    if not res: return
    objetos_grade, ref_globais = res

    dialog = JDialog(MainApplication.getMainFrame(), u"Afastamento em Grade", False)
    panel = JPanel(GridBagLayout()); c = GridBagConstraints(); c.insets = Insets(5,5,5,5)

    txt_p = JTextField("0.0", 6); txt_a = JTextField("0.0", 6)
    sp_p = JSpinner(SpinnerNumberModel(0.1, 0.01, 5.0, 0.05))
    sp_a = JSpinner(SpinnerNumberModel(0.1, 0.01, 5.0, 0.05))

    def atualizar(direcao, txt, sp, eixo):
        try:
            val = float(txt.getText()) + (direcao * float(sp.getValue()))
            txt.setText(str(round(val, 2)))
            executar_afastamento_grade(float(txt_p.getText()), float(txt_a.getText()))
        except:
            Notification(u"Erro nos valores digitados.").setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()

    def criar_bloco(titulo, txt, sp, row, eixo):
        p = JPanel(GridBagLayout()); p.setBorder(BorderFactory.createTitledBorder(titulo))
        gc = GridBagConstraints(); gc.insets = Insets(2,2,2,2)
        bm, bp = JButton("-"), JButton("+")
        gc.gridx=0; p.add(JLabel("Afastar:"), gc); gc.gridx=1; p.add(txt, gc)
        gc.gridx=0; gc.gridy=1; pb = JPanel(); pb.add(bm); pb.add(bp); p.add(pb, gc)
        gc.gridx=1; p.add(sp, gc)
        bp.addActionListener(lambda e: atualizar(1, txt, sp, eixo))
        bm.addActionListener(lambda e: atualizar(-1, txt, sp, eixo))
        c.gridy = row; panel.add(p, c)

    criar_bloco("Afastamento entre Colunas (Lateral)", txt_p, sp_p, 0, "p")
    criar_bloco("Afastamento entre Linhas (Frontal)", txt_a, sp_a, 1, "a")

    bp = JPanel(); b_ok = JButton("OK", UIManager.getIcon("OptionPane.yesIcon"))
    b_can = JButton("Cancelar", UIManager.getIcon("OptionPane.noIcon"))
    bp.add(b_ok); bp.add(b_can)
    
    b_ok.addActionListener(lambda e: (
        Notification(u"Grade ajustada com sucesso.").setIcon(UIManager.getIcon("OptionPane.informationIcon")).show(),
        dialog.dispose()
    ))
    b_can.addActionListener(lambda e: (
        [n.setCoor(coor) for obj in objetos_grade for n, coor in obj['nos'].items()],
        MainApplication.getLayerManager().getEditLayer().invalidate(),
        Notification(u"Ajuste cancelado.").setIcon(UIManager.getIcon("OptionPane.warningIcon")).show(),
        dialog.dispose()
    ))

    dialog.add(panel, BorderLayout.CENTER); dialog.add(bp, BorderLayout.SOUTH)
    dialog.pack(); dialog.setLocationRelativeTo(None); dialog.setVisible(True)

abrir_dialogo()

JavaScript

Última atualização: 2026-04-06

"use strict";

// IMPORTS
const Way               = Java.type("org.openstreetmap.josm.data.osm.Way");
const Node              = Java.type("org.openstreetmap.josm.data.osm.Node");
const LatLon            = Java.type("org.openstreetmap.josm.data.coor.LatLon");
const MainApplication   = Java.type("org.openstreetmap.josm.gui.MainApplication");
const Notification      = Java.type("org.openstreetmap.josm.gui.Notification");
const UIManager         = Java.type("javax.swing.UIManager");
const JDialog           = Java.type("javax.swing.JDialog");
const JPanel            = Java.type("javax.swing.JPanel");
const JButton           = Java.type("javax.swing.JButton");
const JTextField        = Java.type("javax.swing.JTextField");
const JLabel            = Java.type("javax.swing.JLabel");
const JSpinner          = Java.type("javax.swing.JSpinner");
const SpinnerNumberModel = Java.type("javax.swing.SpinnerNumberModel");
const GridBagLayout     = Java.type("java.awt.GridBagLayout");
const GridBagConstraints = Java.type("java.awt.GridBagConstraints");
const Insets            = Java.type("java.awt.Insets");
const BorderLayout      = Java.type("java.awt.BorderLayout");
const BorderFactory     = Java.type("javax.swing.BorderFactory");
const Dimension         = Java.type("java.awt.Dimension");
const ArrayList         = Java.type("java.util.ArrayList");
const ChangeCommand     = Java.type("org.openstreetmap.josm.command.ChangeCommand");
const SequenceCommand   = Java.type("org.openstreetmap.josm.command.SequenceCommand");
const UndoRedoHandler   = Java.type("org.openstreetmap.josm.data.UndoRedoHandler");

// --- Java.extend para listeners ---
const ActionListener    = Java.extend(Java.type("java.awt.event.ActionListener"));
const WindowAdapter     = Java.extend(Java.type("java.awt.event.WindowAdapter"));
const KeyEventDispatcher = Java.extend(Java.type("java.awt.KeyEventDispatcher"));
const KeyboardFocusManager = Java.type("java.awt.KeyboardFocusManager");
const KeyEvent          = Java.type("java.awt.event.KeyEvent");

// INÍCIO
const layer = MainApplication.getLayerManager().getEditLayer();

if (!layer || !layer.data) {
    new Notification("Nenhuma camada de edição ativa.")
        .setIcon(UIManager.getIcon("OptionPane.errorIcon")).show();
} else {
    main();
}

function main() {
    const posicoesOriginais = new Map(); // Node → LatLon original
    //  análise da grade 
    function analisarGrade() {
        const sel = layer.data.getSelected();
        let nodesRef = [], ways = [];
        const it = sel.iterator();
        while (it.hasNext()) {
            let obj = it.next();
            if (obj instanceof Node) nodesRef.push(obj);
            else if (obj instanceof Way)  ways.push(obj);
        }

        if (ways.length === 0) {
            new Notification("Selecione os polígonos da grade.")
                .setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
            return null;
        }

        if (nodesRef.length < 2) {
            nodesRef = [ways[0].getNode(0), ways[0].getNode(1)];
            new Notification("Usando orientação baseada no primeiro polígono.")
                .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show();
        }

        // Captura posições originais de todos os nós envolvidos
        ways.forEach(w => {
            const ns = w.getNodes();
            for (let i = 0; i < ns.size(); i++) {
                const n = ns.get(i);
                if (!posicoesOriginais.has(n)) posicoesOriginais.set(n, n.getCoor());
            }
        });

        const n1 = nodesRef[0].getCoor(), n2 = nodesRef[1].getCoor();
        const mLat = 111320.0;
        const mLon = mLat * Math.cos(n1.lat() * (Math.PI / 180.0));
        const dx = (n2.lon() - n1.lon()) * mLon, dy = (n2.lat() - n1.lat()) * mLat;
        const dBase = Math.hypot(dx, dy);
        if (dBase === 0) return null;

        const ux = dx / dBase, uy = dy / dBase;
        const vx = -uy,        vy = ux;

        // Polígonos que compartilham nós são tratados como um único bloco.
        const wayParent = new Map();
        function find(w) {
            if (!wayParent.has(w)) wayParent.set(w, w);
            if (wayParent.get(w) !== w) wayParent.set(w, find(wayParent.get(w)));
            return wayParent.get(w);
        }
        function union(a, b) { wayParent.set(find(a), find(b)); }

        // Mapeia cada nó para todos os ways que o contêm
        const nodToWays = new Map();
        ways.forEach(w => {
            const ns = w.getNodes();
            for (let i = 0; i < ns.size(); i++) {
                const n = ns.get(i);
                if (!nodToWays.has(n)) nodToWays.set(n, []);
                nodToWays.get(n).push(w);
            }
        });

        // Une ways que compartilham nós
        nodToWays.forEach(wList => {
            for (let i = 1; i < wList.length; i++) union(wList[0], wList[i]);
        });

        // Agrupa ways por componente conectado
        const grupos = new Map();
        ways.forEach(w => {
            const root = find(w);
            if (!grupos.has(root)) grupos.set(root, []);
            grupos.get(root).push(w);
        });

        // Cada grupo vira uma entrada na grade, com centro médio de todos os nós únicos
        let grade = [];
        grupos.forEach(groupWays => {
            const nosUnicos = new Set();
            let sumLat = 0, sumLon = 0, contemRef = false;
            groupWays.forEach(w => {
                const ns = w.getNodes();
                for (let i = 0; i < ns.size(); i++) {
                    const n = ns.get(i);
                    if (!nosUnicos.has(n)) {
                        nosUnicos.add(n);
                        const c = posicoesOriginais.get(n);
                        sumLat += c.lat();
                        sumLon += c.lon();
                    }
                    if (n === nodesRef[0] || n === nodesRef[1]) contemRef = true;
                }
            });
            const count = nosUnicos.size;
            const vDx = ((sumLon / count) - n1.lon()) * mLon;
            const vDy = ((sumLat / count) - n1.lat()) * mLat;
            grade.push({
                nos: Array.from(nosUnicos),
                pPara: vDx * ux + vDy * uy,
                pPerp: vDx * vx + vDy * vy,
                bloqueado: contemRef
            });
        });

        // Agrupa por posição e atribui índices de linha/coluna
        const agrupar = (lista, chave) => {
            let sorted = lista.slice().sort((a, b) => a[chave] - b[chave]);
            let grupos = [], cur = [sorted[0]];
            for (let i = 1; i < sorted.length; i++) {
                if (Math.abs(sorted[i][chave] - sorted[i-1][chave]) < 4.0) cur.push(sorted[i]);
                else { grupos.push(cur); cur = [sorted[i]]; }
            }
            grupos.push(cur);
            grupos.sort((a, b) => a[0][chave] - b[0][chave])
                  .forEach((g, idx) => g.forEach(o => o["idx" + chave] = idx));
        };

        agrupar(grade, 'pPara');
        agrupar(grade, 'pPerp');

        const b = grade.find(g => g.bloqueado);
        const offsetPara = b ? b.idxpPara : 0;
        const offsetPerp = b ? b.idxpPerp : 0;
        grade.forEach(g => {
            g.multPara = g.idxpPara - offsetPara;
            g.multPerp = g.idxpPerp - offsetPerp;
        });

        return { grade, ux, uy, vx, vy, mLat, mLon };
    }

    const d = analisarGrade();
    if (!d) return;

    // Durante o ajuste, os nós são movidos diretamente no dataset.
    // Isso evita que o histórico acumule um comando por clique.
    // Apenas no OK um único SequenceCommand é registrado.
    function aplicarDireto() {
        const offP = parseFloat(txtP.getText());
        const offA = parseFloat(txtA.getText());
        const sParaLon = (d.ux * offA) / d.mLon, sParaLat = (d.uy * offA) / d.mLat;
        const sPerpLon = (d.vx * offP) / d.mLon, sPerpLat = (d.vy * offP) / d.mLat;

        d.grade.forEach(obj => {
            if (obj.bloqueado) return;
            obj.nos.forEach(node => {
                const cOri = posicoesOriginais.get(node);
                if (!cOri) return;
                const nLat = cOri.lat() + (sParaLat * obj.multPara) + (sPerpLat * obj.multPerp);
                const nLon = cOri.lon() + (sParaLon * obj.multPara) + (sPerpLon * obj.multPerp);
                node.setCoor(new LatLon(nLat, nLon));
            });
        });

        layer.invalidate();
        MainApplication.getMap().mapView.repaint();
    }

    function restaurarOriginais() {
        posicoesOriginais.forEach((coor, node) => node.setCoor(coor));
        layer.invalidate();
        MainApplication.getMap().mapView.repaint();
    }

function confirmarNoHistorico() {
    const cmds = new ArrayList();
    
    posicoesOriginais.forEach((cOri, node) => {
        const cAtual = node.getCoor();
        
        // Verifica se houve mudança significativa (tolerância de precisão)
        if (Math.abs(cAtual.lat() - cOri.lat()) < 1e-10 &&
            Math.abs(cAtual.lon() - cOri.lon()) < 1e-10) return;

        let nodDepois = new Node(node); 
        nodDepois.setCoor(cAtual);
        node.setCoor(cOri);
        cmds.add(new ChangeCommand(node, nodDepois));
    });

    if (!cmds.isEmpty()) {
        const seq = new SequenceCommand("Ajuste de Grade", cmds);
        UndoRedoHandler.getInstance().add(seq);
        
        layer.invalidate();
        MainApplication.getMap().mapView.repaint();
    }
}

    //  UI 
    const txtP = new JTextField("0.0", 5);
    const txtA = new JTextField("0.0", 5);
    const spP  = new JSpinner(new SpinnerNumberModel(0.5, 0.01, 5.0, 0.05));
    const spA  = new JSpinner(new SpinnerNumberModel(0.5, 0.01, 5.0, 0.05));

    const dialog = new JDialog(MainApplication.getMainFrame(), "Ajuste de grade", false);
    const panel  = new JPanel(new GridBagLayout());
    const gbc    = new GridBagConstraints();
    gbc.insets   = new Insets(5, 5, 5, 5);

    function bloco(tit, txt, sp, row) {
        let p = new JPanel(new GridBagLayout());
        p.setBorder(BorderFactory.createTitledBorder(tit));
        let bc = new GridBagConstraints();
        bc.insets = new Insets(2, 2, 2, 2);
        let bM = new JButton("-"), bP = new JButton("+");
        bM.setPreferredSize(new Dimension(45, 25));
        bP.setPreferredSize(new Dimension(45, 25));
        bc.gridx = 0; bc.gridy = 0;
        p.add(new JLabel("Distância (m):"), bc);
        bc.gridx = 1; p.add(txt, bc);
        bc.gridx = 0; bc.gridy = 1;
        let pb = new JPanel(); pb.add(bM); pb.add(bP);
        p.add(pb, bc);
        bc.gridx = 1; p.add(sp, bc);
        bP.addActionListener(new ActionListener({ actionPerformed: function() {
            txt.setText((parseFloat(txt.getText()) + parseFloat(sp.getValue())).toFixed(2));
            aplicarDireto();
        }}));
        bM.addActionListener(new ActionListener({ actionPerformed: function() {
            txt.setText((parseFloat(txt.getText()) - parseFloat(sp.getValue())).toFixed(2));
            aplicarDireto();
        }}));
        gbc.gridy = row;
        panel.add(p, gbc);
    }

    bloco("Colunas (Lateral)", txtP, spP, 0);
    bloco("Linhas (Frontal)",  txtA, spA, 1);

    const footer = new JPanel();
    const btnOk  = new JButton("OK",       UIManager.getIcon("OptionPane.yesIcon"));
    const btnCan = new JButton("Cancelar", UIManager.getIcon("OptionPane.noIcon"));

    // Intercepta Ctrl+Z enquanto o diálogo está aberto
    const ctrlZDispatcher = new KeyEventDispatcher({
        dispatchKeyEvent: function(e) {
            if (e.getID() === KeyEvent.KEY_PRESSED &&
                e.getKeyCode() === KeyEvent.VK_Z &&
                (e.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) !== 0) {
                restaurarOriginais();
                new Notification("Ajuste de grade cancelado.")
                    .setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
                dialog.dispose();
                return true; // consome o evento — impede o JOSM de processar seu próprio Ctrl+Z
            }
            return false;
        }
    });
    KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(ctrlZDispatcher);

    btnOk.addActionListener(new ActionListener({ actionPerformed: function() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(ctrlZDispatcher);
        confirmarNoHistorico();
        new Notification("Grade finalizada com sucesso.")
            .setIcon(UIManager.getIcon("OptionPane.informationIcon")).show();
        dialog.dispose();
    }}));

    btnCan.addActionListener(new ActionListener({ actionPerformed: function() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(ctrlZDispatcher);
        restaurarOriginais();
        new Notification("Ajuste de grade cancelado.")
            .setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
        dialog.dispose();
    }}));

    dialog.addWindowListener(new WindowAdapter({ windowClosing: function() {
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(ctrlZDispatcher);
        restaurarOriginais();
    }}));

    footer.add(btnOk);
    footer.add(btnCan);
    dialog.add(panel,  BorderLayout.CENTER);
    dialog.add(footer, BorderLayout.SOUTH);
    dialog.pack();
    dialog.setLocationRelativeTo(MainApplication.getMainFrame());
    dialog.setVisible(true);
}