User:DressyPear4/SubstituicaoPoligonosLote
Jump to navigation
Jump to search
Substituição de polígonos em lote
Script tem a mesma função que as teclas de atalho Ctrl+⇧ Shift+G mas ao invés de substituir um a um, é possível fazer a substituição de multiplas geometrias.
Caso o polígono a ser substituído tenha uma quantidade maior de nós que o polígono de destino, esses serão deletados
Como funciona?
- Modo 1
- Selecione um ou mais polígonos com id
- Selecione a mesma quantidade de polígonos sem id
- Se a quantidade de polígonos com id for maior que a quantidade de destino; os selecionados extras serão ignorados
- Modo 2
- Selecione apenas um polígono com id
- Selecione uma quantidade de polígonos com nós equivalentes para evitar serem deletados
- O polígono será substituído para um e os nós serão distribuídos para os demais selecionados
Demonstração
-
Imagem.gif, clique para visualizar.
-
Imagem.gif, clique para visualizar.
Código
from org.openstreetmap.josm.data.osm import Way, Node
from org.openstreetmap.josm.data import UndoRedoHandler
from org.openstreetmap.josm.gui import MainApplication, Notification
from org.openstreetmap.josm.command import SequenceCommand, ChangeCommand, DeleteCommand, AddCommand
from javax.swing import JOptionPane, UIManager, JPanel, JRadioButton, ButtonGroup
from java.awt import GridLayout
import math
# FUNÇÕES AUXILIARES
def get_centroid(way):
nodes = way.getNodes()
if not nodes:
return (0, 0)
x = sum(n.getCoor().getX() for n in nodes) / len(nodes)
y = sum(n.getCoor().getY() for n in nodes) / len(nodes)
return (x, y)
def calcular_distancia(c1, c2):
dx = c1[0] - c2[0]
dy = c1[1] - c2[1]
return math.sqrt(dx * dx + dy * dy)
def get_clean_nodes(way):
nodes = list(way.getNodes())
if way.isClosed() and nodes and nodes[0] == nodes[-1]:
return nodes[:-1]
return nodes
def safe_delete_check(node, way_context, safe_set):
if node in safe_set:
return False, False
if node.getDataSet() is not None and len(node.getReferrers()) == 1 and way_context in node.getReferrers():
safe_set.add(node)
return True, (node.getUniqueId() > 0)
return False, False
def safe_delete_check_list(node, way_list, safe_set):
if node in safe_set:
return False, False
is_safe = True
if node.getDataSet() is not None:
if not node.getReferrers():
is_safe = True
else:
for ref in node.getReferrers():
if ref not in way_list:
is_safe = False
break
if is_safe:
safe_set.add(node)
return True, (node.getUniqueId() > 0)
return False, False
def pode_deletar(node):
return not node.isDeleted() and node.getDataSet() is not None
# MODO 1: "Mesma quantidade" (N-para-N)
def substituir_varios():
layer_manager = MainApplication.getLayerManager()
dataset = layer_manager.getEditDataSet()
if not dataset:
Notification(u"Nenhuma camada de edição esta ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
return
ways = dataset.getSelectedWays()
antigas = [w for w in ways if not w.isNew()]
novas = [w for w in ways if w.isNew()]
if not antigas or not novas:
Notification(u"Selecione pelo menos uma geometria com ID e uma sem.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
pares = []
usadas = set()
for a in antigas:
c1 = get_centroid(a)
melhor = None
menor_dist = float("inf")
for n in novas:
if n in usadas: continue
dist = calcular_distancia(c1, get_centroid(n))
if dist < menor_dist:
melhor, menor_dist = n, dist
if melhor:
usadas.add(melhor)
pares.append((a, melhor))
if not pares:
Notification(u"Nenhum par válido encontrado.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
add_node_cmds, change_node_cmds, change_way_cmds = [], [], []
delete_old_node_cmds, delete_new_way_cmds, delete_new_node_cmds = [], [], []
nos_ja_marcados_delete_new = set()
nos_ja_marcados_delete_old = set()
linhas_substituidas, nos_substituidos = 0, 0
nos_removidos_antiga, nos_removidos_nova = 0, 0
novas_deletadas = []
for antiga, nova in pares:
novas_deletadas.append(nova)
nova_nodes = get_clean_nodes(nova)
antiga_nodes = get_clean_nodes(antiga)
if not nova_nodes or not antiga_nodes:
continue
min_len = min(len(nova_nodes), len(antiga_nodes))
# 1. Comandos de Mudança de Nó
for i in range(min_len):
n_antigo, n_novo = antiga_nodes[i], nova_nodes[i]
novo_estado = Node(n_antigo)
novo_estado.setCoor(n_novo.getCoor())
change_node_cmds.append(ChangeCommand(n_antigo, novo_estado))
nos_substituidos += 1
novos_nos_para_way = list(antiga_nodes[:min_len])
# 2. Comandos de Adição de Nó (Se a nova way for maior)
for i in range(min_len, len(nova_nodes)):
n = nova_nodes[i]
novo_no_obj = Node(n.getCoor())
novo_no_obj.setModified(True)
add_node_cmds.append(AddCommand(dataset, novo_no_obj))
novos_nos_para_way.append(novo_no_obj)
if antiga.isClosed() and novos_nos_para_way and novos_nos_para_way[0] != novos_nos_para_way[-1]:
novos_nos_para_way.append(novos_nos_para_way[0])
# 3. Comando de Mudança da Way Antiga
nova_way = Way(antiga)
nova_way.setNodes(novos_nos_para_way)
tags = dict(antiga.getKeys())
for entry in nova.getKeys().entrySet():
k, v = entry.getKey(), entry.getValue()
if k not in tags: tags[k] = v
nova_way.setKeys(tags)
change_way_cmds.append(ChangeCommand(antiga, nova_way))
# 4. Comando de Deleção da Way Nova
delete_new_way_cmds.append(DeleteCommand(dataset, nova))
linhas_substituidas += 1
# 6. Comandos de Deleção dos Nós Excedentes da Way Antiga
for n in antiga_nodes[min_len:]:
is_safe, is_old = safe_delete_check(n, antiga, nos_ja_marcados_delete_old)
if is_safe:
if is_old: nos_removidos_antiga += 1
delete_old_node_cmds.append(DeleteCommand(dataset, n))
# 5. Comandos de Deleção dos Nós das Ways Novas
nodes_to_check_delete = set()
for nova in novas_deletadas:
nodes_to_check_delete.update(nova.getNodes()) # Adiciona todos os nós ao set (únicos)
for n in nodes_to_check_delete:
is_safe, is_old = safe_delete_check_list(n, novas_deletadas, nos_ja_marcados_delete_new)
if is_safe:
if is_old: nos_removidos_nova += 1
delete_new_node_cmds.append(DeleteCommand(dataset, n))
# Monta a sequência final
comandos_finais = (
add_node_cmds + change_node_cmds + change_way_cmds +
delete_old_node_cmds + delete_new_way_cmds + delete_new_node_cmds
)
if comandos_finais:
UndoRedoHandler.getInstance().add(SequenceCommand("Substituir multiplas geometrias", comandos_finais))
Notification(
u"Substituicoes (Modo 1) concluídas.\n"
+ u"Linhas substituídas: %d\n" % linhas_substituidas
+ u"Nós substituídos: %d\n" % nos_substituidos
+ u"Nós antigos removidos: %d\n" % nos_removidos_antiga
+ u"Nós novos removidos: %d" % nos_removidos_nova
).setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
else:
Notification(u"Nenhuma substituição (Modo 1) foi realizada.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
# MODO 2: "Diferença quantidade" (1-para-N, com distribuição de IDs)
def substituir_diferenca():
layer_manager = MainApplication.getLayerManager()
dataset = layer_manager.getEditDataSet()
if not dataset:
Notification(u"Nenhuma camada de edição está ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
return
ways = dataset.getSelectedWays()
antigas = [w for w in ways if not w.isNew()]
novas = [w for w in ways if w.isNew()]
if len(antigas) != 1:
Notification(u"Modo 'Diferença': Selecione EXATAMENTE UMA geometria com histórico (antiga).")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
if not novas:
Notification(u"Modo 'Diferença': Selecione pelo menos UMA geometria sem histórico (nova).")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
antiga = antigas[0]
antiga_centroid = get_centroid(antiga)
novas.sort(key=lambda n: calcular_distancia(antiga_centroid, get_centroid(n)))
add_node_cmds, change_node_cmds, change_way_cmds = [], [], []
delete_old_node_cmds, delete_new_way_cmds, delete_new_node_cmds = [], [], []
nos_ja_marcados_delete_new = set()
nos_ja_marcados_delete_old = set()
nos_substituidos, nos_removidos_antiga, nos_removidos_nova = 0, 0, 0
nova_principal = novas[0]
novas_excedentes = novas[1:]
antiga_nodes_all = get_clean_nodes(antiga)
nova_principal_nodes = get_clean_nodes(nova_principal)
if not antiga_nodes_all or not nova_principal_nodes:
Notification(u"Geometria principal (antiga ou nova) não possui nós.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
min_len_principal = min(len(antiga_nodes_all), len(nova_principal_nodes))
for i in range(min_len_principal):
n_antigo, n_novo = antiga_nodes_all[i], nova_principal_nodes[i]
novo_estado = Node(n_antigo)
novo_estado.setCoor(n_novo.getCoor())
change_node_cmds.append(ChangeCommand(n_antigo, novo_estado))
nos_substituidos += 1
novos_nos_para_way_antiga = list(antiga_nodes_all[:min_len_principal])
for i in range(min_len_principal, len(nova_principal_nodes)):
n = nova_principal_nodes[i]
novo_no_obj = Node(n.getCoor())
novo_no_obj.setModified(True)
add_node_cmds.append(AddCommand(dataset, novo_no_obj))
novos_nos_para_way_antiga.append(novo_no_obj)
if antiga.isClosed() and novos_nos_para_way_antiga and novos_nos_para_way_antiga[0] != novos_nos_para_way_antiga[-1]:
novos_nos_para_way_antiga.append(novos_nos_para_way_antiga[0])
nova_way = Way(antiga)
nova_way.setNodes(novos_nos_para_way_antiga)
tags = dict(antiga.getKeys())
for entry in nova_principal.getKeys().entrySet():
k, v = entry.getKey(), entry.getValue()
if k not in tags:
tags[k] = v
nova_way.setKeys(tags)
change_way_cmds.append(ChangeCommand(antiga, nova_way))
delete_new_way_cmds.append(DeleteCommand(dataset, nova_principal))
nos_antigos_excedentes = list(antiga_nodes_all[min_len_principal:])
for nova_excedente in novas_excedentes:
if not nos_antigos_excedentes:
break
nova_excedente_nodes = get_clean_nodes(nova_excedente)
if not nova_excedente_nodes:
continue
min_len_excedente = min(len(nos_antigos_excedentes), len(nova_excedente_nodes))
novos_nos_para_way_nova = []
for i in range(min_len_excedente):
n_antigo = nos_antigos_excedentes.pop(0)
n_novo = nova_excedente_nodes[i]
novo_estado = Node(n_antigo)
novo_estado.setCoor(n_novo.getCoor())
change_node_cmds.append(ChangeCommand(n_antigo, novo_estado))
novos_nos_para_way_nova.append(n_antigo)
nos_substituidos += 1
for i in range(min_len_excedente, len(nova_excedente_nodes)):
n = nova_excedente_nodes[i]
novo_no_obj = Node(n.getCoor())
novo_no_obj.setModified(True)
add_node_cmds.append(AddCommand(dataset, novo_no_obj))
novos_nos_para_way_nova.append(novo_no_obj)
if nova_excedente.isClosed() and novos_nos_para_way_nova and novos_nos_para_way_nova[0] != novos_nos_para_way_nova[-1]:
novos_nos_para_way_nova.append(novos_nos_para_way_nova[0])
nova_way_excedente = Way(nova_excedente)
nova_way_excedente.setNodes(novos_nos_para_way_nova)
change_way_cmds.append(ChangeCommand(nova_excedente, nova_way_excedente))
ways_realmente_deletadas = [nova_principal]
nodes_to_check_delete = set()
for nova_way in ways_realmente_deletadas:
nodes_to_check_delete.update(nova_way.getNodes())
for n in nodes_to_check_delete:
is_safe, is_old = safe_delete_check_list(n, ways_realmente_deletadas, nos_ja_marcados_delete_new)
if is_safe and pode_deletar(n):
if is_old:
nos_removidos_nova += 1
delete_new_node_cmds.append(DeleteCommand(dataset, n))
for n in nos_antigos_excedentes:
is_safe, is_old = safe_delete_check(n, antiga, nos_ja_marcados_delete_old)
if is_safe and pode_deletar(n):
if is_old:
nos_removidos_antiga += 1
delete_old_node_cmds.append(DeleteCommand(dataset, n))
comandos_finais = (
add_node_cmds + change_node_cmds + change_way_cmds +
delete_old_node_cmds + delete_new_way_cmds + delete_new_node_cmds
)
if comandos_finais:
UndoRedoHandler.getInstance().add(SequenceCommand("Substituir 1-N (com distribuicao)", comandos_finais))
Notification(
u"Substituição (Modo 2) concluída.\n"
+ u"Ways selecionadas sem ID: %d\n" % len(novas_excedentes)
+ u"Nós substituídos/movidos: %d\n" % nos_substituidos
+ u"Nós antigos (não usados) removidos: %d\n" % nos_removidos_antiga
+ u"Nós novos (way principal) removidos: %d" % nos_removidos_nova
).setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
# Etapa final: limpeza de nós sem ID que ficaram órfãos
nodes_orfaos_removidos = 0
delete_orfaos_cmds = []
for node in list(dataset.getNodes()):
if node.isNew() and not node.getReferrers() and pode_deletar(node):
delete_orfaos_cmds.append(DeleteCommand(dataset, node))
nodes_orfaos_removidos += 1
if delete_orfaos_cmds:
UndoRedoHandler.getInstance().add(SequenceCommand("Limpeza de nós órfãos", delete_orfaos_cmds))
else:
Notification(u"Nenhuma substituição (Modo 2) foi realizada.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
# FUNÇÃO PRINCIPAL (Menu de Escolha)
def main():
layer_manager = MainApplication.getLayerManager()
dataset = layer_manager.getEditDataSet()
# --- VERIFICAÇÕES ANTES DE ABRIR O DIALOG ---
if not dataset:
Notification(u"Nenhuma camada de edição está ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()
return
selected_ways = dataset.getSelectedWays()
if not selected_ways:
Notification(u"Nenhuma geometria selecionada. Selecione ao menos uma way.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
# Verifica se há ao menos uma 'antiga' e uma 'nova' (útil para modo 1/2)
has_old = any(not w.isNew() for w in selected_ways)
has_new = any(w.isNew() for w in selected_ways)
if not has_old or not has_new:
Notification(u"Selecione pelo menos uma geometria com ID (antiga) e uma sem ID (nova) para usar o script.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()
return
# --- Exibe painel de escolha ---
panel = JPanel(GridLayout(0, 1))
rb_mesma = JRadioButton(u"Modo 1: Múltiplos pares (N-para-N)")
rb_mesma.setToolTipText(u"Pareia cada linha antiga com a linha nova mais próxima. (ex: 3 antigas, 3 novas)")
rb_mesma.setSelected(True)
rb_diferente = JRadioButton(u"Modo 2: Distribuir IDs (1-para-N)")
rb_diferente.setToolTipText(u"Usa 1 antiga; pareia com a mais próxima e distribui os IDs restantes para as outras novas.")
group = ButtonGroup()
group.add(rb_mesma)
group.add(rb_diferente)
panel.add(rb_mesma)
panel.add(rb_diferente)
result = JOptionPane.showConfirmDialog(
MainApplication.getMainFrame(),
panel,
u"Escolha o Modo de Substituição",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE
)
if result == JOptionPane.OK_OPTION:
if rb_mesma.isSelected():
substituir_varios()
elif rb_diferente.isSelected():
substituir_diferenca()
# Executa
main()