mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2025-07-20 20:21:09 +02:00
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/
353 lines
11 KiB
Python
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|