1 |
frank |
2196 |
# Copyright (C) 2003 by Intevation GmbH |
2 |
|
|
# Authors: |
3 |
|
|
# Frank Koormann <[email protected]> |
4 |
|
|
# |
5 |
|
|
# This program is free software under the GPL (>=v2) |
6 |
|
|
# Read the file COPYING coming with Thuban for details. |
7 |
|
|
|
8 |
|
|
""" |
9 |
|
|
Extend thuban with a bounding box dump. |
10 |
|
|
|
11 |
|
|
Dumps the bounding boxes of all shapes of the selected layer. |
12 |
|
|
An optional column can be specified to group the objects, |
13 |
|
|
in this case the bounding box is a union of the separate boxes. |
14 |
|
|
""" |
15 |
|
|
|
16 |
|
|
__version__ = '$Revision$' |
17 |
|
|
|
18 |
|
|
import os, sys |
19 |
|
|
|
20 |
|
|
# only import GUI when not called as command line tool |
21 |
|
|
from wxPython.wx import * |
22 |
|
|
from wxPython.lib.dialogs import wxScrolledMessageDialog |
23 |
|
|
|
24 |
|
|
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor |
25 |
|
|
from Thuban.UI.command import registry, Command |
26 |
|
|
from Thuban.UI.mainwindow import main_menu, _has_selected_shape_layer |
27 |
|
|
from Thuban import _ |
28 |
|
|
|
29 |
|
|
import shapelib |
30 |
|
|
import dbflib |
31 |
|
|
|
32 |
|
|
ID_FILENAME = 4001 |
33 |
|
|
ID_ATTRIBUTES = 4002 |
34 |
|
|
ID_SELFN = 4003 |
35 |
|
|
|
36 |
|
|
class BBoxDumpDialog(wxDialog): |
37 |
|
|
"""Bounding Box Dump Dialog |
38 |
|
|
|
39 |
|
|
Specify a filename for the dump and optionally a layer's column |
40 |
|
|
field to group objects. |
41 |
|
|
""" |
42 |
|
|
|
43 |
|
|
def __init__(self, parent, title, layer = None): |
44 |
|
|
wxDialog.__init__(self, parent, -1, title, |
45 |
|
|
style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) |
46 |
|
|
|
47 |
|
|
if layer is None: |
48 |
|
|
return wxID_CANCEL |
49 |
|
|
|
50 |
|
|
# Store the layer |
51 |
|
|
self.layer = layer |
52 |
|
|
|
53 |
|
|
# Filename selection elements |
54 |
|
|
self.filename = wxTextCtrl(self, ID_FILENAME, "") |
55 |
|
|
self.button_selectfile = wxButton(self, ID_SELFN, _('Select...')) |
56 |
|
|
EVT_BUTTON(self, ID_SELFN, self.OnSelectFilename) |
57 |
|
|
|
58 |
|
|
# Column choice elements |
59 |
|
|
self.choice_column = wxChoice(self, ID_ATTRIBUTES) |
60 |
|
|
self.choice_column.Append(_('Select...'), None) |
61 |
|
|
for col in self.layer.ShapeStore().Table().Columns(): |
62 |
|
|
self.choice_column.Append(col.name, col) |
63 |
|
|
self.choice_column.SetSelection(0) |
64 |
|
|
|
65 |
|
|
# Dialog button elements |
66 |
|
|
self.button_dump = wxButton(self, wxID_OK, _("OK")) |
67 |
|
|
EVT_BUTTON(self, wxID_OK, self.OnDump) |
68 |
|
|
self.button_dump.SetDefault() |
69 |
|
|
# TODO: Disable the OK button until a filename is entered ... |
70 |
|
|
# self.button_dump.Enable(False) |
71 |
|
|
self.button_cancel = wxButton(self, wxID_CANCEL, _("Cancel")) |
72 |
|
|
|
73 |
|
|
|
74 |
|
|
# Dialog Layout: three horizontal box sizers. |
75 |
|
|
topbox = wxBoxSizer(wxVERTICAL) |
76 |
|
|
|
77 |
|
|
hbox = wxBoxSizer(wxHORIZONTAL) |
78 |
|
|
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
79 |
|
|
hbox.Add(wxStaticText(self, -1, _("File:")), |
80 |
|
|
0, wxALL|wxALIGN_CENTER_VERTICAL, 4) |
81 |
|
|
hbox.Add(self.filename, 1, wxALL|wxEXPAND, 4) |
82 |
|
|
hbox.Add(self.button_selectfile, 0, wxALL, 4) |
83 |
|
|
|
84 |
|
|
hbox = wxBoxSizer(wxHORIZONTAL) |
85 |
|
|
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
86 |
|
|
hbox.Add(wxStaticText(self, -1, _("Group by:")), |
87 |
|
|
0, wxALL|wxALIGN_CENTER_VERTICAL, 4) |
88 |
|
|
hbox.Add(self.choice_column, 1, wxALL|wxEXPAND, 4) |
89 |
|
|
|
90 |
|
|
hbox = wxBoxSizer(wxHORIZONTAL) |
91 |
|
|
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
92 |
|
|
hbox.Add(self.button_dump, 0, wxALL|wxALIGN_CENTER, |
93 |
|
|
10) |
94 |
|
|
hbox.Add(self.button_cancel, 0, wxALL|wxALIGN_CENTER, |
95 |
|
|
10) |
96 |
|
|
|
97 |
|
|
# Finalize ... |
98 |
|
|
self.SetAutoLayout(True) |
99 |
|
|
self.SetSizer(topbox) |
100 |
|
|
topbox.Fit(self) |
101 |
|
|
topbox.SetSizeHints(self) |
102 |
|
|
|
103 |
|
|
# Store for later use |
104 |
|
|
self.parent = parent |
105 |
|
|
|
106 |
|
|
def OnDump(self, event): |
107 |
|
|
"""Bounding Box Dump Dialog event handler OK button. |
108 |
|
|
|
109 |
|
|
Prepare the inputs from the dialog and call processing. |
110 |
|
|
""" |
111 |
|
|
i = self.choice_column.GetSelection() |
112 |
|
|
column = self.choice_column.GetClientData(i) |
113 |
|
|
self.Close() |
114 |
|
|
|
115 |
|
|
ThubanBeginBusyCursor() |
116 |
|
|
try: |
117 |
|
|
bboxmessage = bboxdump(self.layer, column, self.filename.GetValue()) |
118 |
|
|
finally: |
119 |
|
|
ThubanEndBusyCursor() |
120 |
|
|
|
121 |
|
|
if bboxmessage: |
122 |
|
|
dlg = wxScrolledMessageDialog( |
123 |
|
|
self.parent, bboxmessage, |
124 |
|
|
_("Bounding Box Dump %s") % self.layer.Title() |
125 |
|
|
) |
126 |
|
|
dlg.ShowModal() |
127 |
|
|
|
128 |
|
|
def OnSelectFilename(self, event): |
129 |
|
|
"""Bounding Box Dump Dialog event handler File Selection. |
130 |
|
|
|
131 |
|
|
Opens a file dialog to specify a file to dump into. |
132 |
|
|
""" |
133 |
|
|
dlg = wxFileDialog(self, _("Dump Bounding Boxes To"), |
134 |
|
|
os.path.dirname(self.filename.GetValue()), |
135 |
|
|
os.path.basename(self.filename.GetValue()), |
136 |
|
|
_("CSV Files (*.csv)|*.csv|") + |
137 |
|
|
_("All Files (*.*)|*.*"), |
138 |
|
|
wxSAVE|wxOVERWRITE_PROMPT) |
139 |
|
|
if dlg.ShowModal() == wxID_OK: |
140 |
|
|
self.filename.SetValue(dlg.GetPath()) |
141 |
|
|
dlg.Destroy() |
142 |
|
|
else: |
143 |
|
|
dlg.Destroy() |
144 |
|
|
|
145 |
|
|
|
146 |
|
|
def bboxdump(layer, column, filename): |
147 |
|
|
"""Bounding Box Dump Processing |
148 |
|
|
|
149 |
|
|
layer - Layer of shapes to be dumped |
150 |
|
|
column - optional column to group shapes (else None) |
151 |
|
|
filename - optional filename to dump into (else empty string, i.e. dump |
152 |
|
|
to stdio) |
153 |
|
|
""" |
154 |
|
|
# Preparation |
155 |
|
|
shapelist = {} |
156 |
|
|
bboxmessage = "" |
157 |
|
|
|
158 |
|
|
# Collect shape ids to be dumped |
159 |
|
|
if column is None: |
160 |
|
|
# A simple dump of shapes bbox is required |
161 |
|
|
for i in xrange(layer.NumShapes()): |
162 |
|
|
shapelist[i] = (i,) |
163 |
|
|
else: |
164 |
|
|
# group them by column ... |
165 |
|
|
for i in xrange(layer.NumShapes()): |
166 |
|
|
row = layer.ShapeStore().Table().ReadRowAsDict(i) |
167 |
|
|
att = row[column.name] |
168 |
|
|
if not shapelist.has_key(att): |
169 |
|
|
shapelist[att] = [] |
170 |
|
|
shapelist[att].append(i) |
171 |
|
|
|
172 |
|
|
# Dump them, sorted |
173 |
|
|
keys = shapelist.keys() |
174 |
|
|
keys.sort() |
175 |
|
|
for key in keys: |
176 |
|
|
bbox = layer.ShapesBoundingBox(shapelist[key]) |
177 |
|
|
bboxmessage = bboxmessage + "%.3f,%.3f,%.3f,%.3f,%s\n" % ( |
178 |
|
|
bbox[0],bbox[1],bbox[2],bbox[3],key) |
179 |
|
|
|
180 |
|
|
# finally |
181 |
|
|
if filename != '': |
182 |
|
|
bboxfile = file(filename,'w+') |
183 |
|
|
bboxfile.write(bboxmessage) |
184 |
|
|
bboxfile.close() |
185 |
|
|
return None |
186 |
|
|
else: |
187 |
|
|
return bboxmessage |
188 |
|
|
|
189 |
|
|
def LayerBBoxDump(context): |
190 |
|
|
"""Menu Handler BBoxDump |
191 |
|
|
""" |
192 |
|
|
layer = context.mainwindow.canvas.SelectedLayer() |
193 |
|
|
if layer is not None: |
194 |
|
|
dlg = BBoxDumpDialog(context.mainwindow, _("Bounding Box Dump"), |
195 |
|
|
layer = layer) |
196 |
|
|
dlg.ShowModal() |
197 |
|
|
|
198 |
|
|
|
199 |
|
|
# gns2shp executed as an extension to Thuban |
200 |
|
|
|
201 |
|
|
# register the new command |
202 |
|
|
registry.Add(Command('bboxdump', _('BBox Dump'), LayerBBoxDump, |
203 |
|
|
helptext = _('Dump Bounding Boxes of Layer Objects'), |
204 |
|
|
sensitive = _has_selected_shape_layer)) |
205 |
|
|
|
206 |
|
|
# find the extensions menu (create it anew if not found) |
207 |
|
|
extensions_menu = main_menu.find_menu('extensions') |
208 |
|
|
if extensions_menu is None: |
209 |
|
|
extensions_menu = main_menu.InsertMenu('extensions', _('E&xtensions')) |
210 |
|
|
|
211 |
|
|
# finally add the new entry to the extensions menu |
212 |
|
|
extensions_menu.InsertItem('bboxdump') |