User:DressyPear4/RetanguloArredondado

From OpenStreetMap Wiki
Jump to navigation Jump to search

Criar um retângulo arredondado

Para geometrias que precisam de suavização das bordas e controle do tamanho

Como funciona?

  • Selecione uma geometria com quatro lados
  • Abrirá um diálogo com controles
    • Ajuste de largura
    • Ajuste de altura
    • Ajuste de circunferência
    • Adição/subtração de nós

Demonstração

Imagem.gif, clique para visualizar.

Código

from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.data.osm import Node, Way
from org.openstreetmap.josm.data.projection import ProjectionRegistry
from org.openstreetmap.josm.data.coor import EastNorth, LatLon
from org.openstreetmap.josm.data import UndoRedoHandler
from org.openstreetmap.josm.command import SequenceCommand, AddCommand
from javax.swing import JPanel, JLabel, JButton, JOptionPane, BoxLayout, Box, UIManager
from java.awt import Dimension, FlowLayout
import math

WIDTH = [0.0]
HEIGHT = [0.0]
RADIUS = [0.0]
SEGMENTS_PER_CORNER = [4]
preview_cmd = [None]
center_poly = [None]
angle_poly = [0.0]

def create_rounded_corner(center, start_angle, end_angle, radius, segments):
    points = []
    for i in range(segments + 1):
        angle = math.radians(start_angle + (end_angle - start_angle) * i / segments)
        x = center.getX() + radius * math.cos(angle)
        y = center.getY() + radius * math.sin(angle)
        points.append(EastNorth(x, y))
    return points

def rotate_point(p, center, angle_rad):
    dx = p.getX() - center.getX()
    dy = p.getY() - center.getY()
    cos_a = math.cos(angle_rad)
    sin_a = math.sin(angle_rad)
    x_new = center.getX() + dx * cos_a - dy * sin_a
    y_new = center.getY() + dx * sin_a + dy * cos_a
    return EastNorth(x_new, y_new)

def is_clockwise(coords):
    s = 0.0
    for i in range(len(coords) - 1):
        p1 = coords[i]
        p2 = coords[i + 1]
        s += (p2.getX() - p1.getX()) * (p2.getY() + p1.getY())
    return s > 0

def desenhar_preview():
    if preview_cmd[0]:
        UndoRedoHandler.getInstance().undo()
        preview_cmd[0] = None

    projection = ProjectionRegistry.getProjection()
    dataset = MainApplication.getLayerManager().getEditDataSet()
    if not dataset or center_poly[0] is None:
        return

    height = max(WIDTH[0], 0.1)
    width = max(HEIGHT[0], 0.1)
    radius = min(RADIUS[0], width / 2, height / 2)
    segs = SEGMENTS_PER_CORNER[0]

    cx, cy = center_poly[0].getX(), center_poly[0].getY()
    x0, y0 = cx - width / 2, cy - height / 2
    x1, y1 = cx + width / 2, cy + height / 2

    en_tl = EastNorth(x0 + radius, y1 - radius)
    en_tr = EastNorth(x1 - radius, y1 - radius)
    en_br = EastNorth(x1 - radius, y0 + radius)
    en_bl = EastNorth(x0 + radius, y0 + radius)

    path = []
    path += create_rounded_corner(en_tl, 180, 90, radius, segs)
    path += create_rounded_corner(en_tr, 90, 0, radius, segs)
    path += create_rounded_corner(en_br, 360, 270, radius, segs)
    path += create_rounded_corner(en_bl, 270, 180, radius, segs)

    path_rotated = [rotate_point(p, center_poly[0], angle_poly[0]) for p in path]

    nodes = []
    commands = []
    for en in path_rotated:
        latlon = projection.eastNorth2latlon(en)
        node = Node(latlon)
        node.setModified(True)
        commands.append(AddCommand(dataset, node))
        nodes.append(node)

    nodes.append(nodes[0])
    coords = [projection.latlon2eastNorth(n.getCoor()) for n in nodes]
    if is_clockwise(coords):
        nodes.reverse()

    way = Way()
    way.setNodes(nodes)
    way.setModified(True)
    commands.append(AddCommand(dataset, way))

    cmd = SequenceCommand("Preview retângulo arredondado", commands)
    UndoRedoHandler.getInstance().add(cmd)
    preview_cmd[0] = cmd

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

    selection = [p for p in dataset.getSelected() if isinstance(p, Way) and p.isArea()]
    if len(selection) != 1:
        Notification(u"Selecione um polígono fechado como referência")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()
        return

    poly = selection[0]
    proj = ProjectionRegistry.getProjection()
    coords = [proj.latlon2eastNorth(n.getCoor()) for n in poly.getNodes()]
    xs = [c.getX() for c in coords]
    ys = [c.getY() for c in coords]

    min_x, max_x = min(xs), max(xs)
    min_y, max_y = min(ys), max(ys)

    center_poly[0] = EastNorth((min_x + max_x) / 2, (min_y + max_y) / 2)
    dx = coords[1].getX() - coords[0].getX()
    dy = coords[1].getY() - coords[0].getY()
    angle_poly[0] = math.atan2(dy, dx)

    WIDTH[0] = max_x - min_x
    HEIGHT[0] = max_y - min_y
    RADIUS[0] = min(WIDTH[0], HEIGHT[0]) / 4.0
    SEGMENTS_PER_CORNER[0] = 4

    WIDTH_DEFAULT = WIDTH[0]
    HEIGHT_DEFAULT = HEIGHT[0]
    RADIUS_DEFAULT = RADIUS[0]
    SEGMENTS_DEFAULT = SEGMENTS_PER_CORNER[0]

    def update_label():
        label_largura.setText("Largura: {:.1f}".format(WIDTH[0]))
        label_altura.setText("Altura: {:.1f}".format(HEIGHT[0]))
        label_raio.setText("Raio: {:.1f}".format(RADIUS[0]))
        label_segmentos.setText("Segmentos: {}".format(SEGMENTS_PER_CORNER[0]))

    def make_button(text, callback):
        btn = JButton(text)
        btn.setPreferredSize(Dimension(40, 30))
        btn.addActionListener(callback)
        return btn

    def change_value(param, delta, min_val=1):
        param[0] = max(min_val, param[0] + delta)
        update_label()
        desenhar_preview()

    def change_float(param, delta, min_val=0.1):
        param[0] = max(min_val, round(param[0] + delta, 1))
        update_label()
        desenhar_preview()

    panel = JPanel()
    panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))

    # Largura
    global label_largura
    label_largura = JLabel()
    row1 = JPanel()
    row1.setLayout(BoxLayout(row1, BoxLayout.X_AXIS))
    row1.add(label_largura)
    row1.add(Box.createHorizontalGlue())
    b1 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b1.add(make_button("-", lambda e: change_float(WIDTH, -1.0)))
    b1.add(make_button("+", lambda e: change_float(WIDTH, 1.0)))
    row1.add(b1)
    panel.add(row1)

    # Altura
    global label_altura
    label_altura = JLabel()
    row2 = JPanel()
    row2.setLayout(BoxLayout(row2, BoxLayout.X_AXIS))
    row2.add(label_altura)
    row2.add(Box.createHorizontalGlue())
    b2 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b2.add(make_button("-", lambda e: change_float(HEIGHT, -1.0)))
    b2.add(make_button("+", lambda e: change_float(HEIGHT, 1.0)))
    row2.add(b2)
    panel.add(row2)

    # Raio
    global label_raio
    label_raio = JLabel()
    row3 = JPanel()
    row3.setLayout(BoxLayout(row3, BoxLayout.X_AXIS))
    row3.add(label_raio)
    row3.add(Box.createHorizontalGlue())
    b3 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b3.add(make_button("-", lambda e: change_float(RADIUS, -0.5)))
    b3.add(make_button("+", lambda e: change_float(RADIUS, 0.5)))
    row3.add(b3)
    panel.add(row3)

    # Segmentos
    global label_segmentos
    label_segmentos = JLabel()
    row4 = JPanel()
    row4.setLayout(BoxLayout(row4, BoxLayout.X_AXIS))
    row4.add(label_segmentos)
    row4.add(Box.createHorizontalGlue())
    b4 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b4.add(make_button("-", lambda e: change_value(SEGMENTS_PER_CORNER, -1)))
    b4.add(make_button("+", lambda e: change_value(SEGMENTS_PER_CORNER, 1)))
    row4.add(b4)
    panel.add(row4)

    btn_reset = JButton("Resetar")
    btn_reset.setAlignmentX(0.5)
    def reset_vals(e):
        WIDTH[0] = WIDTH_DEFAULT
        HEIGHT[0] = HEIGHT_DEFAULT
        RADIUS[0] = RADIUS_DEFAULT
        SEGMENTS_PER_CORNER[0] = SEGMENTS_DEFAULT
        update_label()
        desenhar_preview()
    btn_reset.addActionListener(reset_vals)
    panel.add(Box.createRigidArea(Dimension(0, 10)))
    panel.add(btn_reset)

    update_label()
    desenhar_preview()
    result = JOptionPane.showConfirmDialog(None, panel, u"Desenhar retângulo arredondado", JOptionPane.OK_CANCEL_OPTION)

    if preview_cmd[0]:
        UndoRedoHandler.getInstance().undo()
        preview_cmd[0] = None

    if result == JOptionPane.OK_OPTION:
        desenhar_preview()
        Notification(u"Retângulo criado com sucesso")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()

main()