Files
Phoenix/samples/floatcanvas/BNAEditor.py
Edouard Choinière 95cafd1a3f style: Normalise numpy imports with import numpy as np
The convention when importing numpy is to use `import numpy as np`

Fixes: unconventional-import-alias (ICN001)
Ruff rule: https://docs.astral.sh/ruff/rules/unconventional-import-alias/
2025-02-08 16:48:57 +00:00

353 lines
11 KiB
Python

#!/usr/bin/env python
"""
BNA-Editor: a simple app for editing polygons in BNA files
BNA is a simple text format for storing polygons in lat-long coordinates.
"""
import os, sys
import sets
import numpy as np
#### import local version:
#sys.path.append("..")
#from floatcanvas import NavCanvas, FloatCanvas
## import the installed version
from wx.lib.floatcanvas import NavCanvas, FloatCanvas
import wx
import sys
if len(sys.argv) > 1:
StartFileName = sys.argv[1]
else:
StartFileName = None
### These utilities are required to load and save BNA data.
class BNAData:
"""
Class to store the full set of data in a BNA file
"""
def __init__(self, Filename = None):
self.Filename = Filename
self.PointsData = None
self.Filename = None
self.Names = None
self.Types = None
if Filename is not None:
self.Load(Filename)
def __getitem__(self,index):
return (self.PointsData[index], self.Names[index])
def __len__(self):
return len(self.PointsData)
def Save(self, filename = None):
if not filename:
filename = self.filename
with open(filename, 'w') as file:
for i, points in enumerate(self.PointsData):
file.write('"%s","%s", %i\n'%(self.Names[i],self.Types[i],len(points) ) )
for p in points:
file.write("%.12f,%.12f\n"%(tuple(p)))
def Load(self, filename):
#print("Loading:", filename)
self.Filename = filename
self.PointsData = []
self.Names = []
self.Types = []
with open(filename,'rU') as file_:
for line in file_:
if not line:
break
line = line.strip()
Name, line = line.split('","')
Name = Name[1:]
Type,line = line.split('",')
num_points = int(line)
self.Types.append(Type)
self.Names.append(Name)
polygon = np.zeros((num_points,2),np.float)
for i in range(num_points):
polygon[i,:] = map(float, file_.readline().split(','))
self.PointsData.append(polygon)
return None
class DrawFrame(wx.Frame):
"""
A frame used for the BNA Editor
"""
def __init__(self,parent, id,title,position,size):
wx.Frame.__init__(self,parent, id,title,position, size)
## Set up the MenuBar
MenuBar = wx.MenuBar()
FileMenu = wx.Menu()
OpenMenu = FileMenu.Append(wx.ID_ANY, "&Open", "Open BNA")
self.Bind(wx.EVT_MENU, self.OpenBNA, OpenMenu)
SaveMenu = FileMenu.Append(wx.ID_ANY, "&Save", "Save BNA")
self.Bind(wx.EVT_MENU, self.SaveBNA, SaveMenu)
CloseMenu = FileMenu.Append(wx.ID_EXIT, "", "Close Application")
self.Bind(wx.EVT_MENU, self.OnQuit, CloseMenu)
MenuBar.Append(FileMenu, "&File")
view_menu = wx.Menu()
ZoomMenu = view_menu.Append(wx.ID_ANY, "Zoom to &Fit","Zoom to fit the window")
self.Bind(wx.EVT_MENU, self.ZoomToFit, ZoomMenu)
MenuBar.Append(view_menu, "&View")
help_menu = wx.Menu()
AboutMenu = help_menu.Append(wx.ID_ABOUT, "",
"More information About this program")
self.Bind(wx.EVT_MENU, self.OnAbout, AboutMenu)
MenuBar.Append(help_menu, "&Help")
self.SetMenuBar(MenuBar)
self.CreateStatusBar()
# Add the Canvas
self.Canvas = NavCanvas.NavCanvas(self,-1,(500,500),
Debug = 0,
BackgroundColor = "DARK SLATE BLUE"
).Canvas
wx.EVT_CLOSE(self, self.OnCloseWindow)
FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove )
FloatCanvas.EVT_LEFT_UP(self.Canvas, self.OnLeftUp )
FloatCanvas.EVT_LEFT_DOWN(self.Canvas, self.OnLeftDown)
try:
self.FileDialog = wx.FileDialog(self, "Pick a BNA file",".","","*", wx.FD_OPEN)
except wx._core.PyAssertionError:
self.FileDialog = None
self.ResetSelections()
return None
def ResetSelections(self):
self.SelectedPoly = None
self.SelectedPolyOrig = None
self.SelectedPoints = None
self.PointSelected = False
self.SelectedPointNeighbors = None
def OnLeftDown(self,event):
if self.SelectedPoly:
self.DeSelectPoly()
self.Canvas.Draw()
def OnAbout(self, event):
dlg = wx.MessageDialog(self, "This is a small program to demonstrate\n"
"the use of the FloatCanvas\n",
"About Me", wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
def ZoomToFit(self,event):
self.Canvas.ZoomToBB()
def OpenBNA(self, event):
if self.FileDialog is None:
self.FileDialog = wx.FileDialog(self, "Pick a BNA file",style= wx.OPEN)
dlg = self.FileDialog
dlg.SetMessage("Pick a BNA file")
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
self.LoadBNA(filename)
def SaveBNA(self, event):
for i in self.ChangedPolys:
self.BNAFile.PointsData[i] = self.AllPolys[i].Points
dlg = wx.FileDialog(self,
message="Pick a BNA file",
style=wx.FD_SAVE)
if dlg.ShowModal() == wx.ID_OK:
filename = dlg.GetPath()
self.BNAFile.Save(filename)
def Clear(self,event = None):
self.Canvas.ClearAll()
self.Canvas.Draw(True)
def OnQuit(self,event):
self.Close(True)
def OnCloseWindow(self, event):
self.Destroy()
def OnMove(self, event):
"""
Updates the status bar with the world coordinates
And moves a point if there is one
"""
self.SetStatusText("%.4f, %.4f"%tuple(event.Coords))
if self.PointSelected:
PolyPoints = self.SelectedPoly.Points
Index = self.SelectedPoints.Index
dc = wx.ClientDC(self.Canvas)
PixelCoords = event.GetPosition()
dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
dc.SetLogicalFunction(wx.XOR)
if self.SelectedPointNeighbors is None:
self.SelectedPointNeighbors = np.zeros((3,2), np.float)
#fixme: This feels very inelegant!
if Index == 0:
self.SelectedPointNeighbors[0] = self.SelectedPoly.Points[-1]
self.SelectedPointNeighbors[1:3] = self.SelectedPoly.Points[:2]
elif Index == len(self.SelectedPoly.Points)-1:
self.SelectedPointNeighbors[0:2] = self.SelectedPoly.Points[-2:]
self.SelectedPointNeighbors[2] = self.SelectedPoly.Points[0]
else:
self.SelectedPointNeighbors = self.SelectedPoly.Points[Index-1:Index+2]
self.SelectedPointNeighbors = self.Canvas.WorldToPixel(self.SelectedPointNeighbors)
else:
dc.DrawLines(self.SelectedPointNeighbors)
self.SelectedPointNeighbors[1] = PixelCoords
dc.DrawLines(self.SelectedPointNeighbors)
def OnLeftUp(self, event):
if self.PointSelected:
self.SelectedPoly.Points[self.SelectedPoints.Index] = event.GetCoords()
self.SelectedPoly.SetPoints(self.SelectedPoly.Points, copy = False)
self.SelectedPoints.SetPoints(self.SelectedPoly.Points, copy = False)
self.PointSelected = False
self.SelectedPointNeighbors = None
self.SelectedPoly.HasChanged = True
self.Canvas.Draw()
def DeSelectPoly(self):
Canvas = self.Canvas
if self.SelectedPoly.HasChanged:
self.ChangedPolys.add(self.SelectedPolyOrig.BNAIndex)
self.SelectedPolyOrig.SetPoints(self.SelectedPoly.Points, copy = False)
self.Canvas.Draw(Force = True)
Canvas.RemoveObject(self.SelectedPoly)
Canvas.RemoveObject(self.SelectedPoints)
self.ResetSelections()
def SelectPoly(self, Object):
Canvas = self.Canvas
if Object is self.SelectedPolyOrig:
pass
else:
if self.SelectedPoly is not None:
self.DeSelectPoly()
self.SelectedPolyOrig = Object
self.SelectedPoly = Canvas.AddPolygon(Object.Points,
LineWidth = 1,
LineColor = "Red",
FillColor = None,
InForeground = True)
self.SelectedPoly.HasChanged = False
# Draw points on the Vertices of the Selected Poly:
self.SelectedPoints = Canvas.AddPointSet(Object.Points,
Diameter = 4,
Color = "Red",
InForeground = True)
self.SelectedPoints.HitLineWidth = 8 # make it a bit easier to hit
self.SelectedPoints.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPointHit)
Canvas.Draw()
def SelectPointHit(self, PointSet):
PointSet.Index = PointSet.FindClosestPoint(PointSet.HitCoords)
self.PointSelected = True
def LoadBNA(self, filename):
self.ResetSelections()
self.Canvas.ClearAll()
self.Canvas.SetProjectionFun('FlatEarth')
try:
AllPolys = []
self.BNAFile = BNAData(filename)
print("loaded BNAFile:", self.BNAFile.Filename)
for i, shoreline in enumerate(self.BNAFile.PointsData):
Poly = self.Canvas.AddPolygon(shoreline,
LineWidth = 1,
LineColor = "Black",
FillColor = "Brown",
FillStyle = 'Solid')
Poly.Bind(FloatCanvas.EVT_FC_LEFT_DOWN, self.SelectPoly)
Poly.BNAIndex = i
AllPolys.append(Poly)
self.Canvas.ZoomToBB()
self.ChangedPolys = sets.Set()
self.AllPolys = AllPolys
except:
#raise
dlg = wx.MessageDialog(None,
'There was something wrong with the selected bna file',
'File Loading Error',
wx.OK | wx.ICON_ERROR)
dlg.ShowModal()
dlg.Destroy()
class BNAEditor(wx.App):
"""
Once you have a picture drawn, you can zoom in and out and move about
the picture. There is a tool bar with three tools that can be
selected.
"""
def __init__(self, *args, **kwargs):
wx.App.__init__(self, *args, **kwargs)
def OnInit(self):
frame = DrawFrame(None, -1, "BNA Editor",wx.DefaultPosition,(700,700))
self.SetTopWindow(frame)
frame.Show()
if StartFileName:
frame.LoadBNA(StartFileName)
else:
##frame.LoadBNA("Tests/Small.bna")
frame.LoadBNA("Tiny.bna")
return True
app = BNAEditor(False)# put in True if you want output to go to it's own window.
app.MainLoop()