1 |
# Copyright (C) 2003-2004 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 |
# $Source$ |
18 |
# $Id$ |
19 |
|
20 |
import os, sys |
21 |
import string |
22 |
|
23 |
from wxPython.wx import * |
24 |
from wxPython.lib.dialogs import wxScrolledMessageDialog |
25 |
|
26 |
from Thuban.UI.common import ThubanBeginBusyCursor, ThubanEndBusyCursor |
27 |
from Thuban.UI.command import registry, Command |
28 |
from Thuban.UI.mainwindow import main_menu, _has_selected_shape_layer |
29 |
from Thuban.UI.extensionregistry import ExtensionDesc, ext_registry |
30 |
from Thuban import _ |
31 |
|
32 |
import shapelib |
33 |
import dbflib |
34 |
|
35 |
ext_registry.add(ExtensionDesc( |
36 |
name = 'bboxdump', |
37 |
version = '1.0.0', |
38 |
authors= [ 'Frank Koormann' ], |
39 |
copyright = '2003-2004 Intevation GmbH', |
40 |
desc = _("Dumps the bounding boxes of all\n" \ |
41 |
"shapes of the selected layer."))) |
42 |
|
43 |
# Widget IDs |
44 |
ID_FILENAME = 4001 |
45 |
ID_ATTRIBUTES = 4002 |
46 |
ID_SELFN = 4003 |
47 |
|
48 |
class BBoxDumpDialog(wxDialog): |
49 |
"""Bounding Box Dump Dialog |
50 |
|
51 |
Specify a filename for the dump and optionally a layer's column |
52 |
field to group objects. |
53 |
""" |
54 |
|
55 |
def __init__(self, parent, title, layer = None): |
56 |
wxDialog.__init__(self, parent, -1, title, |
57 |
style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) |
58 |
|
59 |
if layer is None: |
60 |
return wxID_CANCEL |
61 |
|
62 |
# Store the layer |
63 |
self.layer = layer |
64 |
|
65 |
# Filename selection elements |
66 |
self.filename = wxTextCtrl(self, ID_FILENAME, "") |
67 |
self.button_selectfile = wxButton(self, ID_SELFN, _('Select...')) |
68 |
EVT_BUTTON(self, ID_SELFN, self.OnSelectFilename) |
69 |
|
70 |
# Column choice elements |
71 |
self.choice_column = wxChoice(self, ID_ATTRIBUTES) |
72 |
self.choice_column.Append(_('Select...'), None) |
73 |
for col in self.layer.ShapeStore().Table().Columns(): |
74 |
self.choice_column.Append(col.name, col) |
75 |
self.choice_column.SetSelection(0) |
76 |
|
77 |
# Dialog button elements |
78 |
self.button_dump = wxButton(self, wxID_OK, _("OK")) |
79 |
EVT_BUTTON(self, wxID_OK, self.OnDump) |
80 |
self.button_dump.SetDefault() |
81 |
# TODO: Disable the OK button until a filename is entered ... |
82 |
# self.button_dump.Enable(False) |
83 |
self.button_cancel = wxButton(self, wxID_CANCEL, _("Cancel")) |
84 |
|
85 |
|
86 |
# Dialog Layout: three horizontal box sizers. |
87 |
topbox = wxBoxSizer(wxVERTICAL) |
88 |
|
89 |
hbox = wxBoxSizer(wxHORIZONTAL) |
90 |
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
91 |
hbox.Add(wxStaticText(self, -1, _("File:")), |
92 |
0, wxALL|wxALIGN_CENTER_VERTICAL, 4) |
93 |
hbox.Add(self.filename, 1, wxALL|wxEXPAND, 4) |
94 |
hbox.Add(self.button_selectfile, 0, wxALL, 4) |
95 |
|
96 |
hbox = wxBoxSizer(wxHORIZONTAL) |
97 |
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
98 |
hbox.Add(wxStaticText(self, -1, _("Group by:")), |
99 |
0, wxALL|wxALIGN_CENTER_VERTICAL, 4) |
100 |
hbox.Add(self.choice_column, 1, wxALL|wxEXPAND, 4) |
101 |
|
102 |
hbox = wxBoxSizer(wxHORIZONTAL) |
103 |
topbox.Add(hbox, 0, wxALL|wxEXPAND) |
104 |
hbox.Add(self.button_dump, 0, wxALL|wxALIGN_CENTER, |
105 |
10) |
106 |
hbox.Add(self.button_cancel, 0, wxALL|wxALIGN_CENTER, |
107 |
10) |
108 |
|
109 |
# Finalize ... |
110 |
self.SetAutoLayout(True) |
111 |
self.SetSizer(topbox) |
112 |
topbox.Fit(self) |
113 |
topbox.SetSizeHints(self) |
114 |
|
115 |
# Store for later use |
116 |
self.parent = parent |
117 |
|
118 |
def OnDump(self, event): |
119 |
"""Bounding Box Dump Dialog event handler OK button. |
120 |
|
121 |
Prepare the inputs from the dialog and call processing. |
122 |
""" |
123 |
i = self.choice_column.GetSelection() |
124 |
column = self.choice_column.GetClientData(i) |
125 |
self.Close() |
126 |
|
127 |
ThubanBeginBusyCursor() |
128 |
try: |
129 |
bboxmessage = bboxdump(self.layer, column, self.filename.GetValue()) |
130 |
finally: |
131 |
ThubanEndBusyCursor() |
132 |
|
133 |
if bboxmessage: |
134 |
dlg = wxScrolledMessageDialog( |
135 |
self.parent, bboxmessage, |
136 |
_("Bounding Box Dump %s") % self.layer.Title()) |
137 |
dlg.ShowModal() |
138 |
|
139 |
def OnSelectFilename(self, event): |
140 |
"""Bounding Box Dump Dialog event handler File Selection. |
141 |
|
142 |
Opens a file dialog to specify a file to dump into. |
143 |
""" |
144 |
dlg = wxFileDialog(self, _("Dump Bounding Boxes To"), |
145 |
os.path.dirname(self.filename.GetValue()), |
146 |
os.path.basename(self.filename.GetValue()), |
147 |
_("CSV Files (*.csv)|*.csv|") + |
148 |
_("All Files (*.*)|*.*"), |
149 |
wxSAVE|wxOVERWRITE_PROMPT) |
150 |
if dlg.ShowModal() == wxID_OK: |
151 |
self.filename.SetValue(dlg.GetPath()) |
152 |
dlg.Destroy() |
153 |
else: |
154 |
dlg.Destroy() |
155 |
|
156 |
|
157 |
def bboxdump(layer, column, filename): |
158 |
"""Bounding Box Dump Processing |
159 |
|
160 |
layer - Layer of shapes to be dumped |
161 |
column - optional column to group shapes (else None) |
162 |
filename - optional filename to dump into (else empty string, i.e. dump |
163 |
to message dialog) |
164 |
""" |
165 |
# Preparation |
166 |
shapelist = {} |
167 |
bboxmessage = [] |
168 |
|
169 |
dlg= wxProgressDialog(_("Bounding Box Dump"), |
170 |
_("Collecting shapes ..."), |
171 |
layer.ShapeStore().NumShapes(), |
172 |
None) |
173 |
|
174 |
cnt = 0 |
175 |
step = int(layer.ShapeStore().NumShapes() / 100.0) |
176 |
if step == 0: |
177 |
step = 1 |
178 |
|
179 |
# Collect shape ids to be dumped |
180 |
if column is None: |
181 |
# A simple dump of shapes bbox is required |
182 |
for s in layer.ShapeStore().AllShapes(): |
183 |
i = s.ShapeID() |
184 |
shapelist[i] = (i,) |
185 |
if cnt % step == 0: |
186 |
dlg.Update(cnt) |
187 |
cnt = cnt + 1 |
188 |
else: |
189 |
# group them by column ... |
190 |
for s in layer.ShapeStore().AllShapes(): |
191 |
i = s.ShapeID() |
192 |
row = layer.ShapeStore().Table().ReadRowAsDict(i) |
193 |
att = row[column.name] |
194 |
if not shapelist.has_key(att): |
195 |
shapelist[att] = [] |
196 |
shapelist[att].append(i) |
197 |
if cnt % step == 0: |
198 |
dlg.Update(cnt) |
199 |
cnt = cnt + 1 |
200 |
|
201 |
dlg.Destroy() |
202 |
dlg= wxProgressDialog(_("Bounding Box Dump"), |
203 |
_("Dump bounding boxes of selected shapes ..."), |
204 |
len(shapelist), |
205 |
None) |
206 |
cnt = 0 |
207 |
step = int(len(shapelist) / 100.0) |
208 |
if step == 0: |
209 |
step = 1 |
210 |
|
211 |
# Dump them, sorted |
212 |
keys = shapelist.keys() |
213 |
keys.sort() |
214 |
for key in keys: |
215 |
bbox = layer.ShapesBoundingBox(shapelist[key]) |
216 |
bboxmessage.append("%.3f,%.3f,%.3f,%.3f,%s\n" % ( |
217 |
bbox[0], bbox[1], bbox[2], bbox[3], key)) |
218 |
if cnt % step == 0: |
219 |
dlg.Update(cnt) |
220 |
cnt = cnt + 1 |
221 |
dlg.Destroy() |
222 |
|
223 |
# finally |
224 |
if filename != '': |
225 |
bboxfile = file(filename, 'w+') |
226 |
bboxfile.write(string.join(bboxmessage)) |
227 |
bboxfile.close() |
228 |
return None |
229 |
else: |
230 |
return string.join(bboxmessage) |
231 |
|
232 |
def LayerBBoxDump(context): |
233 |
"""Menu Handler BBoxDump |
234 |
""" |
235 |
layer = context.mainwindow.canvas.SelectedLayer() |
236 |
if layer is not None: |
237 |
dlg = BBoxDumpDialog(context.mainwindow, _("Bounding Box Dump"), |
238 |
layer = layer) |
239 |
dlg.ShowModal() |
240 |
|
241 |
|
242 |
# bboxdump executed as an extension to Thuban |
243 |
|
244 |
# register the new command |
245 |
registry.Add(Command('bboxdump', _('BBox Dump'), LayerBBoxDump, |
246 |
helptext = _('Dump Bounding Boxes of Layer Objects'), |
247 |
sensitive = _has_selected_shape_layer)) |
248 |
|
249 |
# find the extensions menu (create it anew if not found) |
250 |
extensions_menu = main_menu.FindOrInsertMenu('extensions', _('E&xtensions')) |
251 |
|
252 |
# finally add the new entry to the extensions menu |
253 |
extensions_menu.InsertItem('bboxdump') |