User:DressyPear4/Elipse
From OpenStreetMap Wiki
Jump to navigation
Jump to search
Criar uma elipse
JOSM já possui uma função para criar círculos, atalho ⇧ Shift+O. Partindo do mesmo princípio, um código para criar uma elipse onde um círculo não seria adequado.
Como funciona?
- Adicione uma linha com exatamente 3 nós
- Selecione apenas os nós dessa linha
- Escolha a quantidade de nós
- Quanto maior a elipse, mais nós devem ser adicionados
Demonstração

Código
Python
Última atualização: 2026-03-11
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, DeleteCommand
from org.openstreetmap.josm.data import UndoRedoHandler
from javax.swing import JOptionPane, JPanel, JLabel, JSpinner, SpinnerNumberModel, UIManager
import math
def criar_elipse_de_tres_pontos():
layer = MainApplication.getLayerManager().getEditLayer()
if not layer:
Notification(u"Nenhuma camada de edição ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon"))\
.show()
return
sel = layer.data.getSelectedNodes()
if len(sel) != 3:
Notification(u"Selecione exatamente 3 nós.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
.show()
return
# Interface com spinner
panel = JPanel()
panel.add(JLabel(u"Número de pontos na elipse:"))
spinner = JSpinner(SpinnerNumberModel(30, 12, 360, 5))
panel.add(spinner)
option = JOptionPane.showConfirmDialog(None, panel, u"Parâmetros da Elipse", JOptionPane.OK_CANCEL_OPTION)
if option != JOptionPane.OK_OPTION:
return
num_pontos = spinner.getValue()
n1, n2, n3 = sel
p1 = (n1.getCoor().lon(), n1.getCoor().lat())
p2 = (n2.getCoor().lon(), n2.getCoor().lat())
p3 = (n3.getCoor().lon(), n3.getCoor().lat())
# Centro da elipse
cx = (p1[0] + p3[0]) / 2
cy = (p1[1] + p3[1]) / 2
dx = p3[0] - p1[0]
dy = p3[1] - p1[1]
a = math.hypot(dx, dy) / 2
angle = math.atan2(dy, dx)
def dist_perp(px, py, x1, y1, x2, y2):
num = abs((x2 - x1)*(y1 - py) - (x1 - px)*(y2 - y1))
den = math.hypot(x2 - x1, y2 - y1)
return num / den
b = dist_perp(p2[0], p2[1], p1[0], p1[1], p3[0], p3[1])
pontos = []
for i in range(num_pontos):
t = 2 * math.pi * i / num_pontos
ex = a * math.cos(t)
ey = b * math.sin(t)
rx = ex * math.cos(angle) - ey * math.sin(angle)
ry = ex * math.sin(angle) + ey * math.cos(angle)
lon = cx + rx
lat = cy + ry
pontos.append(Node(LatLon(lat, lon)))
cmds = []
for n in pontos:
cmds.append(AddCommand(layer.data, n))
way = Way()
for n in pontos:
way.addNode(n)
way.addNode(pontos[0]) # Fecha a elipse
cmds.append(AddCommand(layer.data, way))
# Deletar caminho com exatamente os 3 nos
for w in layer.data.getWays():
nodes = w.getNodes()
if all(n in nodes for n in [n1, n2, n3]) and len(nodes) == 3:
cmds.append(DeleteCommand(layer.data, w))
# Deletar os nos selecionados
cmds.append(DeleteCommand(layer.data, sel))
UndoRedoHandler.getInstance().add(SequenceCommand("Criar elipse", cmds))
layer.data.setSelected(way)
Notification(u"Elipse criada com sucesso!")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
.show()
criar_elipse_de_tres_pontos()
JavaScript
Última atualização: 2026-03-11
"use strict";
// ── IMPORTS
const MainApplication = Java.type("org.openstreetmap.josm.gui.MainApplication");
const Notification = Java.type("org.openstreetmap.josm.gui.Notification");
const Node = Java.type("org.openstreetmap.josm.data.osm.Node");
const Way = Java.type("org.openstreetmap.josm.data.osm.Way");
const LatLon = Java.type("org.openstreetmap.josm.data.coor.LatLon");
const AddCommand = Java.type("org.openstreetmap.josm.command.AddCommand");
const ChangeCommand = Java.type("org.openstreetmap.josm.command.ChangeCommand");
const DeleteCommand = Java.type("org.openstreetmap.josm.command.DeleteCommand");
const SequenceCommand = Java.type("org.openstreetmap.josm.command.SequenceCommand");
const UndoRedoHandler = Java.type("org.openstreetmap.josm.data.UndoRedoHandler");
const JDialog = Java.type("javax.swing.JDialog");
const JPanel = Java.type("javax.swing.JPanel");
const JLabel = Java.type("javax.swing.JLabel");
const JButton = Java.type("javax.swing.JButton");
const JSpinner = Java.type("javax.swing.JSpinner");
const SpinnerNumberModel = Java.type("javax.swing.SpinnerNumberModel");
const BorderFactory = Java.type("javax.swing.BorderFactory");
const BoxLayout = Java.type("javax.swing.BoxLayout");
const Box = Java.type("javax.swing.Box");
const UIManager = Java.type("javax.swing.UIManager");
const SwingUtilities = Java.type("javax.swing.SwingUtilities");
const ImageProvider = Java.type("org.openstreetmap.josm.tools.ImageProvider");
const FlowLayout = Java.type("java.awt.FlowLayout");
const Font = Java.type("java.awt.Font");
const Dimension = Java.type("java.awt.Dimension");
const ArrayList = Java.type("java.util.ArrayList");
const ActionListener = Java.extend(Java.type("java.awt.event.ActionListener"));
const WindowAdapter = Java.extend(Java.type("java.awt.event.WindowAdapter"));
const LayerChangeListener = Java.extend(
Java.type("org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener")
);
// ── VERIFICAÇÃO INICIAL
const layer = MainApplication.getLayerManager().getEditLayer();
if (!layer || !layer.data) {
new Notification("Nenhuma camada de edição ativa.")
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show();
} else {
const selSet = layer.data.getSelectedNodes();
if (selSet.size() !== 3) {
new Notification("Selecione exatamente 3 nós.")
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show();
} else {
const sel = [];
const it = selSet.iterator();
while (it.hasNext()) sel.push(it.next());
SwingUtilities.invokeLater(function() { mostrarDialogo(sel); });
}
}
// ── GEOMETRIA
// Distância perpendicular de (px,py) à reta (x1,y1)→(x2,y2)
function dist_perp(px, py, x1, y1, x2, y2) {
const num = Math.abs((x2 - x1) * (y1 - py) - (x1 - px) * (y2 - y1));
const den = Math.hypot(x2 - x1, y2 - y1);
return den === 0 ? 0 : num / den;
}
// Calcula os parâmetros base da elipse a partir dos 3 nós
function calcular_base(n1, n2, n3) {
const p1 = [n1.getCoor().lon(), n1.getCoor().lat()];
const p2 = [n2.getCoor().lon(), n2.getCoor().lat()];
const p3 = [n3.getCoor().lon(), n3.getCoor().lat()];
const cx = (p1[0] + p3[0]) / 2;
const cy = (p1[1] + p3[1]) / 2;
const dx = p3[0] - p1[0];
const dy = p3[1] - p1[1];
const a0 = Math.hypot(dx, dy) / 2; // semi-eixo maior inicial
const angle = Math.atan2(dy, dx); // ângulo de rotação
const b0 = dist_perp(p2[0], p2[1], p1[0], p1[1], p3[0], p3[1]); // semi-eixo menor
return { cx, cy, angle, a0, b0 };
}
// Gera as coordenadas dos pontos da elipse
function gerar_pontos(cx, cy, angle, a, b, num_pontos) {
const pts = [];
for (let i = 0; i < num_pontos; i++) {
const t = 2 * Math.PI * i / num_pontos;
const ex = a * Math.cos(t);
const ey = b * Math.sin(t);
const rx = ex * Math.cos(angle) - ey * Math.sin(angle);
const ry = ex * Math.sin(angle) + ey * Math.cos(angle);
pts.push([cx + rx, cy + ry]);
}
return pts;
}
// ── DIÁLOGO
function mostrarDialogo(sel) {
const ds = layer.data;
const n1 = sel[0], n2 = sel[1], n3 = sel[2];
const base = calcular_base(n1, n2, n3);
// Estado mutável da elipse
let a = base.a0;
let b = base.b0;
const passo = 0.000005; // ~0.5m em graus de lon/lat — ajuste fino
// Nós e way de preview (criados antes de abrir o diálogo)
let preview_nodes = [];
let preview_way = null;
let preview_criado = false;
// ── Cria preview inicial
function criar_preview() {
const pts = gerar_pontos(base.cx, base.cy, base.angle, a, b, 30);
const cmds = new ArrayList();
preview_nodes = pts.map(function(p) {
const nn = new Node(new LatLon(p[1], p[0]));
cmds.add(new AddCommand(ds, nn));
return nn;
});
preview_way = new Way();
preview_nodes.forEach(function(n) { preview_way.addNode(n); });
preview_way.addNode(preview_nodes[0]); // fecha
cmds.add(new AddCommand(ds, preview_way));
UndoRedoHandler.getInstance().add(new SequenceCommand("Preview elipse", cmds));
ds.setSelected(preview_way);
layer.invalidate();
preview_criado = true;
}
// ── Atualiza preview movendo os nós existentes (sem novo histórico)
function atualizar_preview() {
const pts = gerar_pontos(base.cx, base.cy, base.angle, a, b,
preview_nodes.length);
for (let i = 0; i < preview_nodes.length; i++) {
preview_nodes[i].setCoor(new LatLon(pts[i][1], pts[i][0]));
}
layer.invalidate();
MainApplication.getMap().mapView.repaint();
}
// ── Confirma no histórico (padrão aprendido)
function confirmar_no_historico() {
const pts = gerar_pontos(base.cx, base.cy, base.angle, a, b,
preview_nodes.length);
const cmds = new ArrayList();
for (let i = 0; i < preview_nodes.length; i++) {
const n_after = new Node(preview_nodes[i]);
n_after.setCoor(new LatLon(pts[i][1], pts[i][0]));
preview_nodes[i].setCoor(preview_nodes[i].getCoor()); // garante before
cmds.add(new ChangeCommand(preview_nodes[i], n_after));
}
// Remove os 3 nós de referência e eventuais triângulos auxiliares
const ways_ds = ds.getWays();
const it = ways_ds.iterator();
while (it.hasNext()) {
const w = it.next();
const wn = w.getNodes();
if (wn.size() === 3 &&
wn.contains(n1) && wn.contains(n2) && wn.contains(n3)) {
cmds.add(new DeleteCommand(ds, w));
}
}
cmds.add(new DeleteCommand(ds, n1));
cmds.add(new DeleteCommand(ds, n2));
cmds.add(new DeleteCommand(ds, n3));
UndoRedoHandler.getInstance().add(new SequenceCommand("Elipse final", cmds));
layer.invalidate();
}
// ── Desfaz preview (Cancelar)
function desfazer_preview() {
if (preview_criado) {
// Sempre apenas 1 undo — o spinner não usa histórico (ver abaixo)
UndoRedoHandler.getInstance().undo(); // desfaz "Preview elipse"
layer.invalidate();
}
}
// ── UI
const main_panel = new JPanel();
main_panel.setLayout(new BoxLayout(main_panel, 1)); // Y_AXIS
main_panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
// Info
const lbl_info = new JLabel(
"<html><b>a</b> = semi-eixo maior | <b>b</b> = semi-eixo menor</html>"
);
lbl_info.setFont(new Font("Dialog", 0, 11));
lbl_info.setAlignmentX(0.0);
main_panel.add(lbl_info);
main_panel.add(Box.createVerticalStrut(6));
// ── Spinner de pontos
const sp_panel = new JPanel(new FlowLayout(0, 6, 2));
sp_panel.setBorder(BorderFactory.createTitledBorder("Resolução"));
sp_panel.setAlignmentX(0.0);
sp_panel.add(new JLabel("Pontos:"));
const spinner = new JSpinner(new SpinnerNumberModel(30, 12, 360, 5));
sp_panel.add(spinner);
main_panel.add(sp_panel);
main_panel.add(Box.createVerticalStrut(4));
// ── Controles de forma
function criar_linha_controle(titulo, label_neg, label_pos, acao_neg, acao_pos) {
const p = new JPanel(new FlowLayout(2, 4, 2)); // CENTER
p.setBorder(BorderFactory.createTitledBorder(titulo));
p.setAlignmentX(0.0);
const btn_neg = new JButton(label_neg);
const btn_pos = new JButton(label_pos);
const dim = new Dimension(90, 26);
btn_neg.setPreferredSize(dim);
btn_pos.setPreferredSize(dim);
btn_neg.addActionListener(new ActionListener({ actionPerformed: function() {
acao_neg(); atualizar_preview();
}}));
btn_pos.addActionListener(new ActionListener({ actionPerformed: function() {
acao_pos(); atualizar_preview();
}}));
p.add(btn_neg);
p.add(btn_pos);
return p;
}
// Eixo A — Alongar / Encurtar
main_panel.add(criar_linha_controle(
"Eixo A (comprimento)",
"▼ Encurtar",
"Alongar ▲",
function() { a = Math.max(passo, a - passo * 5); },
function() { a += passo * 5; }
));
main_panel.add(Box.createVerticalStrut(2));
// Eixo B — Engordar / Estreitar
main_panel.add(criar_linha_controle(
"Eixo B (largura)",
"◀ Estreitar",
"Alargar ▶",
function() { b = Math.max(passo, b - passo * 5); },
function() { b += passo * 5; }
));
main_panel.add(Box.createVerticalStrut(6));
// ── Botões OK / Cancelar
const btn_panel = new JPanel(new FlowLayout(2, 8, 6));
btn_panel.setAlignmentX(0.0);
const btn_ok = new JButton("OK", UIManager.getIcon("OptionPane.yesIcon"));
const btn_can = new JButton("Cancelar", UIManager.getIcon("OptionPane.noIcon"));
btn_panel.add(btn_ok);
btn_panel.add(btn_can);
main_panel.add(btn_panel);
// ── Diálogo
const dialog = new JDialog(MainApplication.getMainFrame(), "Criar Elipse", false);
dialog.setContentPane(main_panel);
dialog.setDefaultCloseOperation(2);
dialog.pack();
dialog.setLocationRelativeTo(MainApplication.getMainFrame());
// Spinner de pontos — ao mudar resolução, desfaz o preview atual e recria.
// Assim desfazer_preview() continua precisando de apenas 1 undo().
const ChangeListener = Java.extend(Java.type("javax.swing.event.ChangeListener"));
spinner.addChangeListener(new ChangeListener({ stateChanged: function() {
if (!preview_criado) return;
const nova_qtd = spinner.getValue();
if (nova_qtd === preview_nodes.length) return;
// Desfaz o preview atual (1 undo) e recria com nova resolução
UndoRedoHandler.getInstance().undo();
preview_criado = false;
preview_nodes = [];
preview_way = null;
const pts = gerar_pontos(base.cx, base.cy, base.angle, a, b, nova_qtd);
const cmds = new ArrayList();
preview_nodes = pts.map(function(p) {
const nn = new Node(new LatLon(p[1], p[0]));
cmds.add(new AddCommand(ds, nn));
return nn;
});
preview_way = new Way();
preview_nodes.forEach(function(n) { preview_way.addNode(n); });
preview_way.addNode(preview_nodes[0]);
cmds.add(new AddCommand(ds, preview_way));
UndoRedoHandler.getInstance().add(new SequenceCommand("Preview elipse", cmds));
ds.setSelected(preview_way);
preview_criado = true;
layer.invalidate();
MainApplication.getMap().mapView.repaint();
}}));
// ── Layer listener
const layerListener = new LayerChangeListener({
layerAdded: function(e) {},
layerOrderChanged: function(e) {},
layerRemoving: function(e) {
if (e.getRemovedLayer() === layer) {
SwingUtilities.invokeLater(function() {
MainApplication.getLayerManager().removeLayerChangeListener(layerListener);
dialog.dispose();
new Notification("Camada removida. Fechando diálogo.")
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
});
}
}
});
MainApplication.getLayerManager().addLayerChangeListener(layerListener);
// ── Listeners botões
btn_ok.addActionListener(new ActionListener({ actionPerformed: function() {
MainApplication.getLayerManager().removeLayerChangeListener(layerListener);
dialog.dispose();
confirmar_no_historico();
new Notification("Elipse criada com sucesso.")
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show();
}}));
btn_can.addActionListener(new ActionListener({ actionPerformed: function() {
MainApplication.getLayerManager().removeLayerChangeListener(layerListener);
dialog.dispose();
desfazer_preview();
new Notification("Operação cancelada.")
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show();
}}));
dialog.addWindowListener(new WindowAdapter({ windowClosing: function() {
MainApplication.getLayerManager().removeLayerChangeListener(layerListener);
desfazer_preview();
}}));
// Cria preview e abre diálogo
criar_preview();
dialog.setVisible(true);
}