Usado principalmente para paineis solares, uma maneira automatizada de fazer alinhamento de nós ou polígonos quando dispostos em formato tipo escada.
Como funciona?
Selecione um grupo de nós ou polígonos
Ecolha entre alinhamento individual ou pares
Para seleção de "pares" é necessário que a quantidade total selecionada seja um número par
Demonstração
Imagem.gif, clique para visualizar.
Código
fromorg.openstreetmap.josm.guiimportMainApplication,Notificationfromorg.openstreetmap.josm.commandimportMoveCommand,SequenceCommandfromorg.openstreetmap.josm.data.UndoRedoHandlerimportgetInstancefromorg.openstreetmap.josm.data.osmimportWayfromorg.openstreetmap.josm.data.coorimportEastNorthfromorg.openstreetmap.josm.data.projectionimportProjectionRegistryfromjavax.swingimportUIManager,JRadioButton,ButtonGroup,JPanel,BorderFactory,JButton,JDialogfromjava.awtimportGridLayout,BorderLayoutfromjava.utilimportArrayList# Funções auxiliaresdefponto_ao_longo(a,b,t):returnEastNorth(a.east()+(b.east()-a.east())*t,a.north()+(b.north()-a.north())*t)defdistancia2(a,b):dx=a.east()-b.east()dy=a.north()-b.north()returndx*dx+dy*dydefcentroide(way,proj):coords=[proj.latlon2eastNorth(n.getCoor())forninway.getNodes()]x=sum(p.east()forpincoords)/len(coords)y=sum(p.north()forpincoords)/len(coords)returnEastNorth(x,y)defcalcular_centroide_par(ways,proj):coords=[proj.latlon2eastNorth(n.getCoor())forwayinwaysforninway.getNodes()]x=sum(p.east()forpincoords)/len(coords)y=sum(p.north()forpincoords)/len(coords)returnEastNorth(x,y)# Alinhar NÓSdefalinhar_nos(dataset,modo_pares):selected_nodes=ArrayList(dataset.getSelectedNodes())num_nodes=selected_nodes.size()ifnum_nodes<4ornum_nodes%2!=0:Notification(u"Selecione um número par de nós (mínimo de 4).")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()returnref_start=selected_nodes[0].getEastNorth()ref_end=selected_nodes[-1].getEastNorth()vx=ref_end.east()-ref_start.east()vy=ref_end.north()-ref_start.north()length2=vx*vx+vy*vyor1.0defproj_param(node):p=node.getEastNorth()return((p.east()-ref_start.east())*vx+(p.north()-ref_start.north())*vy)/length2sorted_nodes=sorted(selected_nodes,key=proj_param)commands=[]num_pairs=num_nodes//2node1_start=sorted_nodes[0]node2_start=sorted_nodes[1]coor1_start=node1_start.getEastNorth()coor2_start=node2_start.getEastNorth()start_point=EastNorth((coor1_start.east()+coor2_start.east())/2,(coor1_start.north()+coor2_start.north())/2)node_last1=sorted_nodes[num_nodes-2]node_last2=sorted_nodes[num_nodes-1]coor_last1=node_last1.getEastNorth()coor_last2=node_last2.getEastNorth()end_point=EastNorth((coor_last1.east()+coor_last2.east())/2,(coor_last1.north()+coor_last2.north())/2)ifmodo_pares:ifnum_pairs<6:Notification(u"Selecione pelo menos 6 pares para o modo 'Pares'.")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()returncoor_first_4=[(sorted_nodes[i].getEastNorth())foriinrange(4)]start_point_pares=EastNorth(sum(c.east()forcincoor_first_4)/4,sum(c.north()forcincoor_first_4)/4)coor_last_4=[(sorted_nodes[i].getEastNorth())foriinrange(num_nodes-4,num_nodes)]end_point_pares=EastNorth(sum(c.east()forcincoor_last_4)/4,sum(c.north()forcincoor_last_4)/4)num_double_pairs_intermediate=(num_pairs-4)//2foriinrange(num_double_pairs_intermediate):pair_a_start_node=sorted_nodes[4+i*4]pair_a_end_node=sorted_nodes[4+i*4+1]pair_b_start_node=sorted_nodes[4+i*4+2]pair_b_end_node=sorted_nodes[4+i*4+3]all_4_nodes=[pair_a_start_node,pair_a_end_node,pair_b_start_node,pair_b_end_node]coor_all_4=[n.getEastNorth()forninall_4_nodes]current_center=EastNorth(sum(c.east()forcincoor_all_4)/4,sum(c.north()forcincoor_all_4)/4)t=float(i+1)/(num_double_pairs_intermediate+1)target_coor=ponto_ao_longo(start_point_pares,end_point_pares,t)dx=target_coor.east()-current_center.east()dy=target_coor.north()-current_center.north()ifdxordy:fornodeinall_4_nodes:commands.append(MoveCommand(node,dx,dy))else:foriinrange(1,num_pairs-1):first_node_in_pair=sorted_nodes[i*2]second_node_in_pair=sorted_nodes[i*2+1]coor_pair_1=first_node_in_pair.getEastNorth()coor_pair_2=second_node_in_pair.getEastNorth()current_pair_center=EastNorth((coor_pair_1.east()+coor_pair_2.east())/2,(coor_pair_1.north()+coor_pair_2.north())/2)t=float(i)/(num_pairs-1)target_coor=ponto_ao_longo(start_point,end_point,t)dx=target_coor.east()-current_pair_center.east()dy=target_coor.north()-current_pair_center.north()ifdxordy:commands.append(MoveCommand(first_node_in_pair,dx,dy))commands.append(MoveCommand(second_node_in_pair,dx,dy))ifcommands:getInstance().add(SequenceCommand(u"Alinhar pares de nós em degraus",commands))Notification(u"Nós alinhados com sucesso.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()else:Notification(u"Nenhum nó precisou ser movido.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()# Alinhar RETÂNGULOSdefalinhar_retangulos(dataset,modo_pares):ways=list(dataset.getSelectedWays())total=len(ways)iftotal<3:Notification(u"Selecione pelo menos 3 retângulos fechados.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()returnifmodo_paresandtotal%2!=0:Notification(u"Para o modo 'Pares', selecione um número PAR de retângulos (mínimo de 4).")\
.setIcon(UIManager.getIcon("OptionPane.warningIcon")).show()returnproj=ProjectionRegistry.getProjection()centros=[centroide(w,proj)forwinways]max_dist=-1idx_a,idx_b=0,0foriinrange(len(centros)):forjinrange(i+1,len(centros)):d2=distancia2(centros[i],centros[j])ifd2>max_dist:max_dist=d2idx_a,idx_b=i,jinicio_ref=centros[idx_a]fim_ref=centros[idx_b]dx_ref=fim_ref.east()-inicio_ref.east()dy_ref=fim_ref.north()-inicio_ref.north()denom=dx_ref*dx_ref+dy_ref*dy_refor1alinhados=[]fori,(way,centro)inenumerate(zip(ways,centros)):t=((centro.east()-inicio_ref.east())*dx_ref+(centro.north()-inicio_ref.north())*dy_ref)/denomalinhados.append((t,way,centro))alinhados.sort(key=lambdax:x[0])comandos=[]ifmodo_pares:num_pares=total//2way_p0,way_p1=alinhados[0][1],alinhados[1][1]centro_par_start=calcular_centroide_par([way_p0,way_p1],proj)way_p_last0,way_p_last1=alinhados[total-2][1],alinhados[total-1][1]centro_par_end=calcular_centroide_par([way_p_last0,way_p_last1],proj)# Iterar sobre todos os pares e movê-losforiinrange(num_pares):idx_start=i*2way1,centro1=alinhados[idx_start][1],alinhados[idx_start][2]way2,centro2=alinhados[idx_start+1][1],alinhados[idx_start+1][2]centro_par_atual=calcular_centroide_par([way1,way2],proj)t=float(i)/(num_pares-1)ifnum_pares>1else0.5alvo=ponto_ao_longo(centro_par_start,centro_par_end,t)dx=alvo.east()-centro_par_atual.east()dy=alvo.north()-centro_par_atual.north()ifdxordy:comandos.append(MoveCommand(way1,dx,dy))comandos.append(MoveCommand(way2,dx,dy))else:# Modo Individualfori,(t_orig,way,centro)inenumerate(alinhados):ifi==0ori==total-1:continuet=float(i)/(total-1)alvo=ponto_ao_longo(inicio_ref,fim_ref,t)dx=alvo.east()-centro.east()dy=alvo.north()-centro.north()ifdxordy:comandos.append(MoveCommand(way,dx,dy))ifcomandos:getInstance().add(SequenceCommand(u"Distribuir retângulos como escada",comandos))Notification(u"Retângulos reposicionados corretamente.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()else:Notification(u"Nada foi ajustado.")\
.setIcon(UIManager.getIcon("OptionPane.informationIcon")).show()# Maindefmain():dataset=MainApplication.getLayerManager().getEditDataSet()ifnotdataset:Notification(u"Nenhuma camada ativa.")\
.setIcon(UIManager.getIcon("OptionPane.errorIcon")).show()return# Obtém a janela principal do JOSM (o 'parent')parent=MainApplication.getMainFrame()# Cria o diálogo NÃO MODAL (o 'False' é crucial)dialog=JDialog(parent,u"Configurações de Alinhamento",False)dialog.setLayout(BorderLayout())# --- Painel de opções ---main_panel=JPanel(GridLayout(0,1))# Tipo de Alinhamentotype_panel=JPanel()type_panel.setBorder(BorderFactory.createTitledBorder(u"Tipo de Alinhamento"))type_group=ButtonGroup()radio_nos=JRadioButton(u"Alinhar Nós",False)radio_rets=JRadioButton(u"Alinhar Retângulos",True)type_group.add(radio_nos)type_group.add(radio_rets)type_panel.add(radio_nos)type_panel.add(radio_rets)# Modo (Individual ou Pares)mode_panel=JPanel()mode_panel.setBorder(BorderFactory.createTitledBorder(u"Modo"))mode_group=ButtonGroup()radio_individual=JRadioButton(u"Individual",True)radio_pares=JRadioButton(u"Pares",False)mode_group.add(radio_individual)mode_group.add(radio_pares)mode_panel.add(radio_individual)mode_panel.add(radio_pares)main_panel.add(type_panel)main_panel.add(mode_panel)button_panel=JPanel()icon_ok=UIManager.getIcon("OptionPane.yesIcon")icon_cancel=UIManager.getIcon("OptionPane.cancelIcon")ok_button=JButton(u"Aceitar",icon_ok)cancel_button=JButton(u"Cancelar",icon_cancel)button_panel.add(ok_button)button_panel.add(cancel_button)dialog.add(main_panel,BorderLayout.CENTER)dialog.add(button_panel,BorderLayout.SOUTH)defon_ok_click(event):modo_pares=radio_pares.isSelected()ifradio_nos.isSelected():alinhar_nos(dataset,modo_pares)else:alinhar_retangulos(dataset,modo_pares)dialog.dispose()defon_cancel_click(event):dialog.dispose()ok_button.addActionListener(on_ok_click)cancel_button.addActionListener(on_cancel_click)dialog.pack()dialog.setLocationRelativeTo(parent)dialog.setVisible(True)main()