User:DressyPear4/EdificioTipoH

From OpenStreetMap Wiki
Jump to navigation Jump to search

Criar uma edificação tipo H

Em conjuntos residenciais é comum encontrar edificações com um formato de H. Criar um retângulo e depis adicionar nós para fazer a extrusão de cavidades pode deixar os lados de diferentes tamanhos, esse script mantém todos os lados simétricos.

Como funciona?

  • Selecione um polígono com quatro lados
  • Use as opções para escolher entre 2 ou 4 cavidades
  • Use os controles de distância para ajustar largura e profundidade

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.command import SequenceCommand, AddCommand, DeleteCommand
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 javax.swing import JPanel, JLabel, JButton, JOptionPane, BoxLayout, Box, UIManager, JRadioButton, ButtonGroup
from java.awt import Dimension, FlowLayout
import math

# Variáveis globais para os parâmetros do Polígono e estado
POLY_WIDTH = [0.0]
POLY_HEIGHT = [0.0]
CAVITY_X_WIDTH = [0.0]
CAVITY_Y_WIDTH = [0.0]
CAVITY_X_DEPTH = [0.0]
CAVITY_Y_DEPTH = [0.0]
CAVITY_DIRECTION_OUT = [False]  # False = Para Dentro (Padrão), True = Para Fora
# 0: 4 Cavidades (Todos), 1: 2 Cavidades (Laterais - Esquerda/Direita), 2: 2 Cavidades (Horizontal - Cima/Baixo)
CAVITY_MODE = [0]

preview_cmd = [None]
delete_cmd_ref = [None]
REFERENCE_TAGS = {}
center_poly = [None]
angle_poly = [0.0]

# --- Funções Geométricas ---

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


def latlon_to_en(latlon):
    return ProjectionRegistry.getProjection().latlon2eastNorth(latlon)


def en_to_latlon(en):
    return ProjectionRegistry.getProjection().eastNorth2latlon(en)


def generate_geometry(W, H, CW_X, CW_Y, D_X, D_Y, is_outward, cavity_mode):
    hw = W / 2.0
    hh = H / 2.0
    half_cw_x = CW_X / 2.0
    half_cw_y = CW_Y / 2.0
    d_sign = 1.0 if not is_outward else -1.0

    base_coords = [
        EastNorth(hw, hh),
        EastNorth(-hw, hh),
        EastNorth(-hw, -hh),
        EastNorth(hw, -hh)
    ]

    final_coords = []

    for i in range(len(base_coords)):
        p1 = base_coords[i]
        p2 = base_coords[(i + 1) % len(base_coords)]

        final_coords.append(p1)

        if cavity_mode == 1 and (i == 0 or i == 2):
            continue
        if cavity_mode == 2 and (i == 1 or i == 3):
            continue

        is_horizontal = (abs(p1.getY() - p2.getY()) < 1e-9)

        if is_horizontal:
            y_edge = p1.getY()
            x_mid = (p1.getX() + p2.getX()) / 2.0
            x_start = x_mid - half_cw_x
            x_end = x_mid + half_cw_x

            if y_edge > 0:  # TOPO
                y_depth = y_edge - D_Y * d_sign
            else:  # FUNDO
                y_depth = y_edge + D_Y * d_sign

            if i == 2:  # Fundo-Esquerda -> Fundo-Direita
                final_coords.append(EastNorth(x_start, y_edge))
                final_coords.append(EastNorth(x_start, y_depth))
                final_coords.append(EastNorth(x_end, y_depth))
                final_coords.append(EastNorth(x_end, y_edge))
            elif i == 0:  # Topo-Direita -> Topo-Esquerda
                final_coords.append(EastNorth(x_end, y_edge))
                final_coords.append(EastNorth(x_end, y_depth))
                final_coords.append(EastNorth(x_start, y_depth))
                final_coords.append(EastNorth(x_start, y_edge))

        else:
            x_edge = p1.getX()
            y_mid = (p1.getY() + p2.getY()) / 2.0
            y_start = y_mid - half_cw_y
            y_end = y_mid + half_cw_y

            if x_edge > 0:  # DIREITA
                x_depth = x_edge - D_X * d_sign
            else:  # ESQUERDA
                x_depth = x_edge + D_X * d_sign

            if i == 3:  # Fundo-Direita -> Topo-Direita
                final_coords.append(EastNorth(x_edge, y_start))
                final_coords.append(EastNorth(x_depth, y_start))
                final_coords.append(EastNorth(x_depth, y_end))
                final_coords.append(EastNorth(x_edge, y_end))
            elif i == 1:  # Topo-Esquerda -> Fundo-Esquerda
                final_coords.append(EastNorth(x_edge, y_end))
                final_coords.append(EastNorth(x_depth, y_end))
                final_coords.append(EastNorth(x_depth, y_start))
                final_coords.append(EastNorth(x_edge, y_start))

    return final_coords

# --- Desenho e Preview ---
def desenhar_preview():
    if preview_cmd[0]:
        UndoRedoHandler.getInstance().undo()
        preview_cmd[0] = None

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

    W = max(POLY_WIDTH[0], 0.1)
    H = max(POLY_HEIGHT[0], 0.1)

    CW_X = max(CAVITY_X_WIDTH[0], 0.1)
    CW_Y = max(CAVITY_Y_WIDTH[0], 0.1)
    D_X = max(CAVITY_X_DEPTH[0], 0.0)
    D_Y = max(CAVITY_Y_DEPTH[0], 0.0)

    CW_X = min(CW_X, W * 0.9)
    CW_Y = min(CW_Y, H * 0.9)

    if not CAVITY_DIRECTION_OUT[0]:
        D_X = min(D_X, W * 0.4)
        D_Y = min(D_Y, H * 0.4)
    D_X = max(D_X, 0.0)
    D_Y = max(D_Y, 0.0)

    path_base = generate_geometry(
        W, H, CW_X, CW_Y, D_X, D_Y,
        CAVITY_DIRECTION_OUT[0],
        CAVITY_MODE[0]
    )

    center_en = center_poly[0]
    angle_rad = angle_poly[0]

    path_rotated_only = [rotate_point(p, angle_rad) for p in path_base]

    path_final = []
    for p in path_rotated_only:
        x_final = p.getX() + center_en.getX()
        y_final = p.getY() + center_en.getY()
        path_final.append(EastNorth(x_final, y_final))

    commands = []
    nodes = []

    for en in path_final:
        latlon = en_to_latlon(en)
        node = Node(latlon)
        node.setModified(True)
        commands.append(AddCommand(dataset, node))
        nodes.append(node)

    way = Way()
    way.setNodes(nodes + [nodes[0]])
    way.setModified(True)

    # aplica tags copiadas do polígono de referência
    try:
        if REFERENCE_TAGS:
            way.putAll(REFERENCE_TAGS)
    except Exception:
        for k, v in REFERENCE_TAGS.items():
            try:
                way.put(k, v)
            except Exception:
                pass

    commands.append(AddCommand(dataset, way))

    if CAVITY_MODE[0] == 0:
        poly_type = u"20 Lados"
    else:
        poly_type = u"12 Lados"

    action_type = u"Extrusão" if CAVITY_DIRECTION_OUT[0] else u"Cavidade"

    cmd = SequenceCommand(u"Preview Polígono {} ({})".format(poly_type, action_type), commands)
    UndoRedoHandler.getInstance().add(cmd)
    preview_cmd[0] = cmd

    try:
        map_view = MainApplication.getMapFrame().getMapView()
        if map_view:
            map_view.repaint()
    except Exception as e:
        print(u"Erro ao tentar redesenhar o mapa: ", e)

# --- Função Main (GUI Completa) ---

def main():
    dataset = MainApplication.getLayerManager().getEditDataSet()
    if not dataset:
        Notification(u"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 (tamanho e centro)")\
            .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
            .show()
        return

    poly = selection[0]

    # calcula centro, ângulo e dimensões com base no polígono atual
    coords = [latlon_to_en(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)

    if len(poly.getNodes()) >= 2:
        dx = coords[1].getX() - coords[0].getX()
        dy = coords[1].getY() - coords[0].getY()
        angle_poly[0] = math.atan2(dy, dx)
    else:
        angle_poly[0] = 0.0

    ref_width = max_x - min_x
    ref_height = max_y - min_y

    POLY_WIDTH[0] = ref_width
    POLY_HEIGHT[0] = ref_height

    # Valores Iniciais
    CAVITY_X_WIDTH[0] = ref_width / 3.0
    CAVITY_Y_WIDTH[0] = ref_height / 3.0
    CAVITY_X_DEPTH[0] = ref_width / 10.0
    CAVITY_Y_DEPTH[0] = ref_height / 10.0

    WIDTH_DEFAULT = POLY_WIDTH[0]
    HEIGHT_DEFAULT = POLY_HEIGHT[0]
    CWX_DEFAULT = CAVITY_X_WIDTH[0]
    CWY_DEFAULT = CAVITY_Y_WIDTH[0]
    DX_DEFAULT = CAVITY_X_DEPTH[0]
    DY_DEFAULT = CAVITY_Y_DEPTH[0]

    # COPIA AS TAGS DO POLÍGONO DE REFERÊNCIA (forma segura)
    global REFERENCE_TAGS
    REFERENCE_TAGS = {}
    try:
        REFERENCE_TAGS = dict(poly.getKeys())
    except Exception:
        try:
            for t in poly.getTags():
                REFERENCE_TAGS[t.getKey()] = t.getValue()
        except Exception:
            REFERENCE_TAGS = {}

    # PREPARA E EXECUTA EXCLUSÃO DO POLÍGONO DE REFERÊNCIA (polígono + nós órfãos)
    ds = poly.getDataSet()
    nodes_to_delete = [n for n in poly.getNodes() if len(n.getParentWays()) == 1]
    primitives = [poly] + nodes_to_delete

    try:
        cmd_delete = DeleteCommand(ds, primitives)
    except Exception:
        try:
            cmd_delete = DeleteCommand(primitives)
        except Exception as e:
            Notification(u"Erro ao criar DeleteCommand: {}".format(e))\
                .setIcon(UIManager.getIcon("OptionPane.errorIcon"))\
                .show()
            return

    UndoRedoHandler.getInstance().add(cmd_delete)
    delete_cmd_ref[0] = cmd_delete

    # Valores Iniciais para Radio Buttons
    CAVITY_DIRECTION_OUT[0] = False
    CAVITY_MODE[0] = 0  # Padrão: 4 Cavidades

    # --- Construção da GUI ---
    panel = JPanel()
    panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))

    # Funções internas da GUI (labels, botões, etc.)
    def update_label():
        label_largura.setText(u"Largura Total (W): {:.2f}".format(POLY_WIDTH[0]))
        label_altura.setText(u"Altura Total (H): {:.2f}".format(POLY_HEIGHT[0]))
        label_cavidade_largura_y.setText(u"Larg. Cav. (Dir/Esq - CWY): {:.1f}".format(CAVITY_Y_WIDTH[0]))
        label_profundidade_x.setText(u"Prof. Cav. (Dir/Esq - DX): {:.1f}".format(CAVITY_X_DEPTH[0]))
        label_cavidade_largura_x.setText(u"Larg. Cav. (Cima/Baixo - CWX): {:.1f}".format(CAVITY_X_WIDTH[0]))
        label_profundidade_y.setText(u"Prof. Cav. (Cima/Baixo - DY): {:.1f}".format(CAVITY_Y_DEPTH[0]))

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

    def change_float(param, delta, min_val=0.0):
        new_val = param[0] + delta
        is_outward = CAVITY_DIRECTION_OUT[0]

        if param is CAVITY_X_WIDTH:
            max_val = POLY_WIDTH[0] * 0.9
            new_val = min(new_val, max_val)
        elif param is CAVITY_Y_WIDTH:
            max_val = POLY_HEIGHT[0] * 0.9
            new_val = min(new_val, max_val)
        elif param is CAVITY_X_DEPTH and not is_outward:
            max_val = POLY_WIDTH[0] * 0.4
            new_val = min(new_val, max_val)
        elif param is CAVITY_Y_DEPTH and not is_outward:
            max_val = POLY_HEIGHT[0] * 0.4
            new_val = min(new_val, max_val)

        param[0] = max(min_val, round(new_val, 1))
        update_label()
        desenhar_preview()

    def handle_direction_change(e):
        CAVITY_DIRECTION_OUT[0] = e.getSource().getText() == u"Para Fora (Extrusão)"
        desenhar_preview()

    def handle_count_change(e):
        selected_text = e.getSource().getText()
        if selected_text == u"4 Cavidades (Todos os Lados)":
            CAVITY_MODE[0] = 0
        elif selected_text == u"2 Cavidades (Lados Direito/Esquerdo)":
            CAVITY_MODE[0] = 1
        elif selected_text == u"2 Cavidades (Cima/Baixo)":
            CAVITY_MODE[0] = 2
        desenhar_preview()

    # --- Seção 0: Opções (Radio Buttons) ---
    panel.add(JLabel("---------------"))

    direction_panel = JPanel()
    direction_panel.setLayout(BoxLayout(direction_panel, BoxLayout.Y_AXIS))
    title_panel = JPanel(FlowLayout(FlowLayout.LEFT))
    title_panel.add(JLabel(u"**Direção:**"))
    direction_panel.add(title_panel)
    direction_group = ButtonGroup()

    rb_inward = JRadioButton(u"Para Dentro (Cavidade)", not CAVITY_DIRECTION_OUT[0])
    rb_outward = JRadioButton(u"Para Fora (Extrusão)", CAVITY_DIRECTION_OUT[0])

    rb_inward.addActionListener(handle_direction_change)
    rb_outward.addActionListener(handle_direction_change)

    direction_group.add(rb_inward)
    direction_group.add(rb_outward)
    direction_panel.add(rb_inward)
    direction_panel.add(rb_outward)
    panel.add(direction_panel)

    count_panel = JPanel()
    count_panel.setLayout(BoxLayout(count_panel, BoxLayout.Y_AXIS))
    title_panel = JPanel(FlowLayout(FlowLayout.LEFT))
    title_panel.add(JLabel(u"**Contagem:**"))
    count_panel.add(title_panel)
    count_group = ButtonGroup()

    rb_4cavity = JRadioButton(u"4 Cavidades (Todos os Lados)", CAVITY_MODE[0] == 0)
    rb_2cavity_vert = JRadioButton(u"2 Cavidades (Lados Direito/Esquerdo)", CAVITY_MODE[0] == 1)
    rb_2cavity_horiz = JRadioButton(u"2 Cavidades (Cima/Baixo)", CAVITY_MODE[0] == 2)

    rb_4cavity.addActionListener(handle_count_change)
    rb_2cavity_vert.addActionListener(handle_count_change)
    rb_2cavity_horiz.addActionListener(handle_count_change)

    count_group.add(rb_4cavity)
    count_group.add(rb_2cavity_vert)
    count_group.add(rb_2cavity_horiz)
    count_panel.add(rb_4cavity)
    count_panel.add(rb_2cavity_vert)
    count_panel.add(rb_2cavity_horiz)
    panel.add(count_panel)

    panel.add(JLabel("-----------------------------------------------"))

    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("-0.5", lambda e: change_float(POLY_WIDTH, -0.5, 0.1)))
    b1.add(make_button("+0.5", lambda e: change_float(POLY_WIDTH, 0.5, 0.1)))
    row1.add(b1)
    panel.add(row1)

    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("-0.5", lambda e: change_float(POLY_HEIGHT, -0.5, 0.1)))
    b2.add(make_button("+0.5", lambda e: change_float(POLY_HEIGHT, 0.5, 0.1)))
    row2.add(b2)
    panel.add(row2)

    panel.add(Box.createRigidArea(Dimension(0, 5)))
    panel.add(JLabel("-----------------------------------------------"))
    panel.add(Box.createRigidArea(Dimension(0, 5)))

    global label_cavidade_largura_y
    label_cavidade_largura_y = JLabel()
    row4 = JPanel()
    row4.setLayout(BoxLayout(row4, BoxLayout.X_AXIS))
    row4.add(label_cavidade_largura_y)
    row4.add(Box.createHorizontalGlue())
    b4 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b4.add(make_button("-0.5", lambda e: change_float(CAVITY_Y_WIDTH, -0.5, 0.1)))
    b4.add(make_button("+0.5", lambda e: change_float(CAVITY_Y_WIDTH, 0.5, 0.1)))
    row4.add(b4)
    panel.add(row4)
    
    global label_profundidade_x
    label_profundidade_x = JLabel()
    row5 = JPanel()
    row5.setLayout(BoxLayout(row5, BoxLayout.X_AXIS))
    row5.add(label_profundidade_x)
    row5.add(Box.createHorizontalGlue())
    b5 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b5.add(make_button("-0.5", lambda e: change_float(CAVITY_X_DEPTH, -0.5, 0.0)))
    b5.add(make_button("+0.5", lambda e: change_float(CAVITY_X_DEPTH, 0.5, 0.0)))
    row5.add(b5)
    panel.add(row5)

    panel.add(Box.createRigidArea(Dimension(0, 5)))
    panel.add(JLabel("-----------------------------------------------"))
    panel.add(Box.createRigidArea(Dimension(0, 5)))

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

    global label_profundidade_y
    label_profundidade_y = JLabel()
    row6 = JPanel()
    row6.setLayout(BoxLayout(row6, BoxLayout.X_AXIS))
    row6.add(label_profundidade_y)
    row6.add(Box.createHorizontalGlue())
    b6 = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
    b6.add(make_button("-0.5", lambda e: change_float(CAVITY_Y_DEPTH, -0.5, 0.0)))
    b6.add(make_button("+0.5", lambda e: change_float(CAVITY_Y_DEPTH, 0.5, 0.0)))
    row6.add(b6)
    panel.add(row6)

    panel.add(Box.createRigidArea(Dimension(0, 10)))

    btn_reset = JButton(u"Resetar Dimensões")
    btn_reset.setAlignmentX(0.5)

    def reset_vals(e):
        POLY_WIDTH[0] = WIDTH_DEFAULT
        POLY_HEIGHT[0] = HEIGHT_DEFAULT
        CAVITY_X_WIDTH[0] = CWX_DEFAULT
        CAVITY_Y_WIDTH[0] = CWY_DEFAULT
        CAVITY_X_DEPTH[0] = DX_DEFAULT
        CAVITY_Y_DEPTH[0] = DY_DEFAULT
        CAVITY_DIRECTION_OUT[0] = False
        CAVITY_MODE[0] = 0
        rb_inward.setSelected(True)
        rb_4cavity.setSelected(True)
        update_label()
        desenhar_preview()

    btn_reset.addActionListener(reset_vals)
    panel.add(btn_reset)

    update_label()
    desenhar_preview()

    result = JOptionPane.showConfirmDialog(
        None,
        panel,
        u"Desenhar Polígono Modulado (12 ou 20 Lados)",
        JOptionPane.OK_CANCEL_OPTION,
        JOptionPane.PLAIN_MESSAGE,
    )

    # Finalização: tratar resultado
    if result == JOptionPane.CANCEL_OPTION or result == JOptionPane.CLOSED_OPTION:
        # desfaz preview (se existir)
        if preview_cmd[0]:
            UndoRedoHandler.getInstance().undo()
            preview_cmd[0] = None
        # desfaz deleção do polígono original (se existir)
        if delete_cmd_ref[0]:
            UndoRedoHandler.getInstance().undo()
            delete_cmd_ref[0] = None
        Notification(u"Operação cancelada")\
            .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
            .show()
        return

    preview_cmd[0] = None
    delete_cmd_ref[0] = None

    Notification(u"Polígono modulado criado com sucesso")\
        .setIcon(UIManager.getIcon("OptionPane.informationIcon"))\
        .show()

main()