User:DressyPear4/RotasOnibusDirecao

From OpenStreetMap Wiki
Jump to navigation Jump to search

Correções em rotas de ônibus

O script destaca as funções forward/backward em relações selecionadas, ideal para ver "buracos" onde houve modificações em vias ou em funções que foram invertidas

Como funciona?

  • Selecione uma relação
  • Clique em ligar
    • Destaque em AZUL para vias com função forward/backward
    • Destaque em AMARELO para vias sem função forward/backward
  • "Corrigir roles" adicionará a função forward/backward nas vias em amarelo
  • "Inverter todos os roles" inverterá forward/backward de todas as vias da relação
  • Inverter da seleção" irá mudar forward/backward apenas da via selecionada

Demonstração

Imagem.gif, clique para visualizar.

Código

from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.gui.layer import MapViewPaintable, LayerManager, OsmDataLayer
from org.openstreetmap.josm.command import SequenceCommand, ChangeRelationMemberRoleCommand
from org.openstreetmap.josm.data.osm import RelationMember, Way
from org.openstreetmap.josm.data import UndoRedoHandler
from javax.swing import JOptionPane, JDialog, JButton, JPanel, SwingUtilities, BoxLayout, Box, JLabel 
from javax.swing.border import EmptyBorder
from java.awt import Color, BasicStroke, BorderLayout, AlphaComposite, Component
from java.awt.geom import Line2D, AffineTransform
from java.awt.event import WindowAdapter
import math

# Variáveis globais para gerenciar o estado do script e UI
current_arrows = None
toggle_button = None
fix_roles_button = None
invert_roles_button = None
invert_selected_way_role_button = None
dialog = None
layer_change_listener = None

class BusRouteArrows(MapViewPaintable):
    def __init__(self, relation):
        self.relation = relation

    def paint(self, g, mv, bbox):
        if not self.relation or not self.relation.isUsable():
            return

        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.75))
        g.setStroke(BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))

        for m in self.relation.getMembers():
            if m.isWay():
                way = m.getWay()
                if not way.isUsable():
                    continue
                nodes = way.getNodes()

                role = m.getRole()
                
                if role not in [u"forward", u"backward"]:
                    g.setColor(Color.YELLOW)
                else:
                    g.setColor(Color(0, 100, 255))

                is_backward = (role == u"backward")
                if is_backward:
                    nodes = list(reversed(nodes))
                
                for i in range(len(nodes) - 1):
                    p1 = mv.getPoint(nodes[i].getEastNorth())
                    p2 = mv.getPoint(nodes[i+1].getEastNorth())
                    
                    g.draw(Line2D.Float(p1.x, p1.y, p2.x, p2.y))

                    mid_x = (p1.x + p2.x) / 2
                    mid_y = (p1.y + p2.y) / 2
                    self._draw_arrow_head(g, mid_x, mid_y, p1.x, p1.y, p2.x, p2.y)

    def _draw_arrow_head(self, g, x, y, x1, y1, x2, y2):
        arrow_size = 15
        angle = math.atan2(y2 - y1, x2 - x1)
        g.setStroke(BasicStroke(4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND))
        
        transform = AffineTransform()
        transform.translate(x, y)
        transform.rotate(angle)
        
        arrow_head = [
            (arrow_size, 0),
            (0, -arrow_size / 2),
            (0, arrow_size / 2)
        ]
        
        g2 = g.create()
        g2.transform(transform)
        g2.draw(Line2D.Float(arrow_head[0][0], arrow_head[0][1], arrow_head[1][0], arrow_head[1][1]))
        g2.draw(Line2D.Float(arrow_head[0][0], arrow_head[0][1], arrow_head[2][0], arrow_head[2][1]))
        g2.dispose()

def toggle_arrows():
    global current_arrows, toggle_button, fix_roles_button, invert_roles_button, invert_selected_way_role_button

    ds = MainApplication.getLayerManager().getEditDataSet()
    if not ds:
        Notification(u"Nenhum dataset ativo. Crie ou abra uma camada de dados.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    if current_arrows:
        _remove_arrows()
        return
        
    sel = list(ds.getSelectedRelations())
    if not sel:
        Notification(u"Nenhuma relação selecionada.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return
        
    relation = sel[0]
    
    mv = MainApplication.getMap().mapView
    current_arrows = BusRouteArrows(relation)
    mv.addTemporaryLayer(current_arrows)
    mv.repaint()
    
    if toggle_button:
        toggle_button.setText(u"Desligar")
    if fix_roles_button:
        fix_roles_button.setEnabled(True)
    if invert_roles_button:
        invert_roles_button.setEnabled(True)
    if invert_selected_way_role_button:
        invert_selected_way_role_button.setEnabled(True)


    Notification(u"Setas ativadas para relação: <b>%s</b>" % (relation.get(u"name") or u"sem nome")).setIcon(JOptionPane.INFORMATION_MESSAGE).show()

def _remove_arrows():
    global current_arrows, toggle_button, fix_roles_button, invert_roles_button, invert_selected_way_role_button, dialog, layer_change_listener
    if current_arrows:
        mv = MainApplication.getMap().mapView
        mv.removeTemporaryLayer(current_arrows)
        current_arrows = None
        mv.repaint()
        if toggle_button:
            toggle_button.setText(u"Ligar")
        if fix_roles_button:
            fix_roles_button.setEnabled(False)
        if invert_roles_button:
            invert_roles_button.setEnabled(False)
        if invert_selected_way_role_button:
            invert_selected_way_role_button.setEnabled(False)
        
        Notification(u"Setas desativadas.").setIcon(JOptionPane.INFORMATION_MESSAGE).show()
        
    if dialog:
        try:
            MainApplication.getLayerManager().removeLayerChangeListener(layer_change_listener)
        except:
            pass 
        dialog.dispose()
        globals()['dialog'] = None
        globals()['layer_change_listener'] = None

def fix_roles():
    global current_arrows
    if not current_arrows:
        Notification(u"Ative as setas primeiro para corrigir os roles.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return
    
    relation = current_arrows.relation
    commands = []
    
    for i, member in enumerate(relation.getMembers()):
        if not member.getRole():
            commands.append(ChangeRelationMemberRoleCommand(relation, i, "forward"))
            
    if commands:
        command_sequence = SequenceCommand(u"Corrigir roles da rota", commands)
        UndoRedoHandler.getInstance().add(command_sequence)
        MainApplication.getMap().mapView.repaint()
        Notification(u"Roles corrigidos com sucesso!").setIcon(JOptionPane.INFORMATION_MESSAGE).show()
    else:
        Notification(u"Nenhum role precisou ser corrigido.").setIcon(JOptionPane.INFORMATION_MESSAGE).show()

def invert_roles():
    global current_arrows
    if not current_arrows:
        Notification(u"Ative as setas primeiro para inverter os roles.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    relation = current_arrows.relation
    commands = []
    has_invertible_roles = False

    for i, member in enumerate(relation.getMembers()):
        role = member.getRole()
        if role == u"forward":
            commands.append(ChangeRelationMemberRoleCommand(relation, i, "backward"))
            has_invertible_roles = True
        elif role == u"backward":
            commands.append(ChangeRelationMemberRoleCommand(relation, i, "forward"))
            has_invertible_roles = True

    if has_invertible_roles:
        command_sequence = SequenceCommand(u"Inverter todos os roles da rota", commands)
        UndoRedoHandler.getInstance().add(command_sequence)
        MainApplication.getMap().mapView.repaint()
        Notification(u"Roles invertidos com sucesso!").setIcon(JOptionPane.INFORMATION_MESSAGE).show()
    else:
        Notification(u"Nenhum role de 'forward' ou 'backward' encontrado para inverter.").setIcon(JOptionPane.WARNING_MESSAGE).show()

def invert_selected_way_role():
    global current_arrows
    if not current_arrows:
        Notification(u"Ative as setas primeiro.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    ds = MainApplication.getLayerManager().getEditDataSet()
    selected_objects = ds.getSelected()
    
    if not selected_objects:
        Notification(u"Nenhum objeto selecionado. Por favor, selecione uma via.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    sel_ways = [o for o in selected_objects if isinstance(o, Way)]

    if len(sel_ways) == 0:
        Notification(u"O objeto selecionado não é uma via. Por favor, selecione uma via.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return
    
    if len(sel_ways) > 1:
        Notification(u"Mais de uma via selecionada. Selecione apenas uma via.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return
    
    selected_way = sel_ways[0]
    relation = current_arrows.relation

    member_found = False
    for i, member in enumerate(relation.getMembers()):
        if member.getMember() == selected_way:
            role = member.getRole()
            new_role = None
            if role == "forward":
                new_role = "backward"
            elif role == "backward":
                new_role = "forward"
            else:
                Notification(u"A via selecionada não tem um role 'forward' ou 'backward'.").setIcon(JOptionPane.WARNING_MESSAGE).show()
                return

            command = ChangeRelationMemberRoleCommand(relation, i, new_role)
            UndoRedoHandler.getInstance().add(command)
            MainApplication.getMap().mapView.repaint()
            Notification(u"Role da via selecionada invertido para '%s'." % new_role).setIcon(JOptionPane.INFORMATION_MESSAGE).show()
            member_found = True
            break
    
    if not member_found:
        Notification(u"A via selecionada não é membro da relação de rota ativa.").setIcon(JOptionPane.WARNING_MESSAGE).show()

def show_toggle_window(relation_name):
    global toggle_button, fix_roles_button, invert_roles_button, invert_selected_way_role_button, dialog, layer_change_listener
    
    if dialog and dialog.isShowing():
        dialog.requestFocus()
        return
        
    dialog_title = u"Direção da Rota"
    dialog = JDialog(MainApplication.getMainFrame(), dialog_title, False)
    panel = JPanel()
    
    panel.setBorder(EmptyBorder(0, 10, 10, 10)) 
    panel.setLayout(BoxLayout(panel, BoxLayout.Y_AXIS))

    relation_label = JLabel(u"<html><b><br>%s</b></html>" % relation_name)
    relation_label.setAlignmentX(Component.CENTER_ALIGNMENT)
 
    panel.add(relation_label)
    panel.add(Box.createVerticalStrut(5))
    
    toggle_button = JButton(u"Ligar", actionPerformed=lambda e: toggle_arrows())
    toggle_button.setAlignmentX(Component.CENTER_ALIGNMENT)
    
    fix_roles_button = JButton(u"Corrigir Roles", actionPerformed=lambda e: fix_roles())
    fix_roles_button.setAlignmentX(Component.CENTER_ALIGNMENT)
    
    invert_roles_button = JButton(u"Inverter Todos os Roles", actionPerformed=lambda e: invert_roles())
    invert_roles_button.setAlignmentX(Component.CENTER_ALIGNMENT)
    
    invert_selected_way_role_button = JButton(u"Inverter da Seleção", actionPerformed=lambda e: invert_selected_way_role())
    invert_selected_way_role_button.setAlignmentX(Component.CENTER_ALIGNMENT)
    
    fix_roles_button.setEnabled(False)
    invert_roles_button.setEnabled(False)
    invert_selected_way_role_button.setEnabled(False)
    
    panel.add(toggle_button)
    panel.add(Box.createVerticalStrut(5))
    panel.add(fix_roles_button)
    panel.add(Box.createVerticalStrut(5))
    panel.add(invert_roles_button)
    panel.add(Box.createVerticalStrut(5))
    panel.add(invert_selected_way_role_button)
    
    dialog.add(panel, BorderLayout.CENTER)
    dialog.setSize(300, 200)
    dialog.setLocationRelativeTo(MainApplication.getMainFrame())
    
    # Listeners para eventos de janela e camada
    class MyWindowListener(WindowAdapter):
        def windowClosing(self, e):
            _remove_arrows()

    class MyLayerChangeListener(LayerManager.LayerChangeListener):
        def layerAdded(self, e): pass
        def layerRemoving(self, e):
            removed_layer = e.getRemovedLayer()
            if current_arrows and isinstance(removed_layer, OsmDataLayer):
                if current_arrows.relation and hasattr(current_arrows.relation, 'get_layer') and current_arrows.relation.get_layer() == removed_layer:
                    _remove_arrows()
        def layerOrderChanged(self, e): pass

    dialog.addWindowListener(MyWindowListener())
    
    if not layer_change_listener:
        layer_change_listener = MyLayerChangeListener()
        MainApplication.getLayerManager().addLayerChangeListener(layer_change_listener)

    dialog.setVisible(True)

def start_bus_arrows():
    global dialog
    if dialog and dialog.isShowing():
        dialog.requestFocus()
        return

    if not MainApplication.isDisplayingMapView():
        Notification(u"Nenhuma camada ativa.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    ds = MainApplication.getLayerManager().getEditDataSet()
    if not ds:
        Notification(u"Nenhum dataset ativo.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    sel = list(ds.getSelectedRelations())
    if not sel:
        Notification(u"Nenhuma relação selecionada.").setIcon(JOptionPane.WARNING_MESSAGE).show()
        return

    relation = sel[0]
    relation_name = relation.get(u"name") or u"(sem nome)"

    show_toggle_window(relation_name)

SwingUtilities.invokeLater(start_bus_arrows)