Mover nós individuais ou em grupos (passo de 1m ou 10m) mantendo sempre o mesmo espaço entre eles, pode ser usado para fazer o deslocamento de poligonos quando selecionado todos, ou alterar o tamanho.
Como funciona?
Selecione quantidade de nós desejada
Escolha entre deslocamento paralelo ou perpendicular
Todos os nós do poligono - desloca
Apenas parte dos nós - altera geometria
Demonstração
Imagem.gif, clique para visualizar.
Código
Última atualização: 2026-02-09
fromorg.openstreetmap.josm.guiimportMainApplication,Notificationfromorg.openstreetmap.josm.commandimportMoveCommand,SequenceCommandfromorg.openstreetmap.josm.data.coorimportEastNorth,LatLonfromorg.openstreetmap.josm.dataimportUndoRedoHandlerfromorg.openstreetmap.josm.data.osmimportWay,Nodefromjavax.swingimport(JPanel,JLabel,JButton,BoxLayout,JSpinner,SpinnerNumberModel,UIManager,JDialog,BorderFactory)fromjava.awtimportDimension,BorderLayout,Fontfromjavax.swing.eventimportChangeListenerimportmathimportjava.awt.eventasawteinitial_positions={}defget_meter_to_en_ratio(node):lat=node.getCoor().lat()return1.0/math.cos(math.radians(lat))defvalidar_requisitos():dataset=MainApplication.getLayerManager().getEditDataSet()ifnotdataset:Notification(u"Nenhuma camada de edição ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()returnFalseselected_nodes=list(dataset.getSelectedNodes())iflen(selected_nodes)<2:Notification(u"Selecione pelo menos 2 nós.\nOs dois primeiros definem a direção do eixo.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()returnFalsereturnTruedefmover_nos(passo_metros,direcao_tipo,dialog_state):globalinitial_positionsdataset=MainApplication.getLayerManager().getEditDataSet()ifnotdataset:returnselected_nodes=list(dataset.getSelectedNodes())iflen(selected_nodes)<2:Notification(u"Ação interrompida: Seleção insuficiente (mínimo 2 nós).")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()return# Dois primeiros nós selecionados para definir a geometria do movimenton1,n2=selected_nodes[0],selected_nodes[1]# Cálculos Geométricosc1,c2=n1.getCoor(),n2.getCoor()lat_rad=math.radians((c1.lat()+c2.lat())/2.0)m_per_deg_lat=111319.492m_per_deg_lon=m_per_deg_lat*math.cos(lat_rad)dx_m=(c2.lon()-c1.lon())*m_per_deg_londy_m=(c2.lat()-c1.lat())*m_per_deg_latcomp=math.hypot(dx_m,dy_m)ifcomp<1e-6:Notification(u"Os nós de referência são coincidentes demais.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()returnux,uy=dx_m/comp,dy_m/compifdirecao_tipo=="dy":ux,uy=-uy,uxcmds=[]scale=get_meter_to_en_ratio(n1)delta_en_x=ux*passo_metros*scaledelta_en_y=uy*passo_metros*scaleforninselected_nodes:ifnnotininitial_positions:initial_positions[n]=n.getEastNorth()cmds.append(MoveCommand(n,delta_en_x,delta_en_y))ifcmds:UndoRedoHandler.getInstance().add(SequenceCommand(u"Mover nós",cmds))dialog_state['has_moved']=Truedefmain():ifnotvalidar_requisitos():returndialog_state={'has_moved':False}globalinitial_positionsinitial_positions={}# --- Construção da Interface ---panel=JPanel()panel.setLayout(BoxLayout(panel,BoxLayout.Y_AXIS))# Painel do Spinnerspinner_panel=JPanel()spinner_panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10))model=SpinnerNumberModel(1.0,0.0,50.0,1.0)spinner=JSpinner(model)spinner.setEditor(JSpinner.NumberEditor(spinner,"0.0"))spinner.setPreferredSize(Dimension(80,30))# Lógica de Passo Dinâmico (0.1 -> 1.0 -> 10.0)classStepListener(ChangeListener):defstateChanged(self,e):val=float(model.getValue())ifval<1.0:model.setStepSize(0.1)elifval<10.0:model.setStepSize(1.0)else:model.setStepSize(10.0)spinner.addChangeListener(StepListener())label_spinner=JLabel("Passo (metros): ")label_spinner.setFont(Font("SansSerif",Font.BOLD,12))spinner_panel.add(label_spinner)spinner_panel.add(spinner)panel.add(spinner_panel)# Painéis de Controle (Botões + e -)defcreate_btn_panel(titulo,direcao):p=JPanel()p.setBorder(BorderFactory.createTitledBorder(titulo))btn_m=JButton("-")btn_p=JButton("+")btn_m.setPreferredSize(Dimension(60,32))btn_p.setPreferredSize(Dimension(60,32))defmove_action(e):val=float(model.getValue())passo=valife.getSource()==btn_pelse-valmover_nos(passo,direcao,dialog_state)btn_m.addActionListener(move_action)btn_p.addActionListener(move_action)p.add(btn_m)p.add(btn_p)returnppanel.add(create_btn_panel(u"Paralelo","dx"))panel.add(create_btn_panel(u"Perpendicular","dy"))# Rodapé de Açãofooter=JPanel()btn_ok=JButton("Aceitar",UIManager.getIcon("OptionPane.yesIcon"))btn_cc=JButton("Cancelar",UIManager.getIcon("OptionPane.noIcon"))footer.add(btn_ok)footer.add(btn_cc)# Configuração da Janela (JDialog)parent=MainApplication.getMainFrame()dialog=JDialog(parent,u"Ajuste Fino de Posição",False)defon_ok(e):ifdialog_state['has_moved']:Notification(u"Movimentos aplicados com sucesso!")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()else:Notification(u"Nenhuma alteração realizada.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()dialog.dispose()defon_cancel(e):ifdialog_state['has_moved']:cmds=[]fornode,posininitial_positions.items():cur=node.getEastNorth()# Calcula o vetor de retorno ao ponto originalcmds.append(MoveCommand(node,pos.east()-cur.east(),pos.north()-cur.north()))ifcmds:UndoRedoHandler.getInstance().add(SequenceCommand(u"Reverter Ajuste Fino",cmds))Notification(u"Movimentos revertidos.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()dialog.dispose()btn_ok.addActionListener(on_ok)btn_cc.addActionListener(on_cancel)content=JPanel(BorderLayout())content.add(panel,BorderLayout.CENTER)content.add(footer,BorderLayout.SOUTH)dialog.setContentPane(content)dialog.pack()dialog.setLocationRelativeTo(parent)dialog.setAlwaysOnTop(True)dialog.setVisible(True)# Execuçãomain()