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

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);
}