/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 45 - (show annotations)
Fri Sep 7 15:00:21 2001 UTC (23 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 19538 byte(s)
	(MapCanvas.FitRectToWindow): If the rect has zero with or zero
	height do nothing (avoids zero division errors)

1 # Copyright (c) 2001 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[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 Classes for display of a map and interaction with it
10 """
11
12 __version__ = "$Revision$"
13
14 from math import hypot
15
16 from wxPython.wx import wxWindow,\
17 wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
18 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION
19
20
21 from wxPython import wx
22
23 from wxproj import point_in_polygon_shape, shape_centroid
24
25
26 from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
27 LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED
28 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
29 SHAPETYPE_POINT
30 from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
31 ALIGN_LEFT, ALIGN_RIGHT
32
33
34 from renderer import ScreenRenderer, PrinterRender
35
36 import labeldialog
37
38 from messages import SELECTED_SHAPE
39
40
41 #
42 # The tools
43 #
44
45 class Tool:
46
47 """
48 Base class for the interactive tools
49 """
50
51 def __init__(self, view):
52 """Intitialize the tool. The view is the canvas displaying the map"""
53 self.view = view
54 self.start = self.current = None
55 self.dragging = 0
56 self.drawn = 0
57
58 def Name(self):
59 """Return the tool's name"""
60 return ''
61
62 def drag_start(self, x, y):
63 self.start = self.current = x, y
64 self.dragging = 1
65
66 def drag_move(self, x, y):
67 self.current = x, y
68
69 def drag_stop(self, x, y):
70 self.current = x, y
71 self.dragging = 0
72
73 def Show(self, dc):
74 if not self.drawn:
75 self.draw(dc)
76 self.drawn = 1
77
78 def Hide(self, dc):
79 if self.drawn:
80 self.draw(dc)
81 self.drawn = 0
82
83 def draw(self, dc):
84 pass
85
86 def MouseDown(self, event):
87 self.drag_start(event.m_x, event.m_y)
88
89 def MouseMove(self, event):
90 if self.dragging:
91 self.drag_move(event.m_x, event.m_y)
92
93 def MouseUp(self, event):
94 if self.dragging:
95 self.drag_move(event.m_x, event.m_y)
96
97 def Cancel(self):
98 self.dragging = 0
99
100
101 class RectTool(Tool):
102
103 """Base class for tools that draw rectangles while dragging"""
104
105 def draw(self, dc):
106 sx, sy = self.start
107 cx, cy = self.current
108 dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
109
110 class ZoomInTool(RectTool):
111
112 """The Zoom-In Tool"""
113
114 def Name(self):
115 return "ZoomInTool"
116
117 def proj_rect(self):
118 """return the rectangle given by start and current in projected
119 coordinates"""
120 sx, sy = self.start
121 cx, cy = self.current
122 left, top = self.view.win_to_proj(sx, sy)
123 right, bottom = self.view.win_to_proj(cx, cy)
124 return (min(left, right), min(top, bottom),
125 max(left, right), max(top, bottom))
126
127 def MouseUp(self, event):
128 if self.dragging:
129 Tool.MouseUp(self, event)
130 self.view.FitRectToWindow(self.proj_rect())
131
132
133 class ZoomOutTool(RectTool):
134
135 """The Zoom-Out Tool"""
136
137 def Name(self):
138 return "ZoomOutTool"
139
140 def MouseUp(self, event):
141 if self.dragging:
142 Tool.MouseUp(self, event)
143 sx, sy = self.start
144 cx, cy = self.current
145 self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
146 max(sx, cx), max(sy, cy)))
147
148
149 class PanTool(Tool):
150
151 """The Pan Tool"""
152
153 def Name(self):
154 return "PanTool"
155
156 def MouseMove(self, event):
157 if self.dragging:
158 x0, y0 = self.current
159 Tool.MouseMove(self, event)
160 x, y = self.current
161 width, height = self.view.GetSizeTuple()
162 dc = self.view.drag_dc
163 dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)
164
165 def MouseUp(self, event):
166 if self.dragging:
167 Tool.MouseUp(self, event)
168 sx, sy = self.start
169 cx, cy = self.current
170 self.view.Translate(cx - sx, cy - sy)
171
172 class IdentifyTool(Tool):
173
174 """The "Identify" Tool"""
175
176 def Name(self):
177 return "IdentifyTool"
178
179 def MouseUp(self, event):
180 self.view.SelectShapeAt(event.m_x, event.m_y)
181
182
183 class LabelTool(Tool):
184
185 """The "Label" Tool"""
186
187 def Name(self):
188 return "LabelTool"
189
190 def MouseUp(self, event):
191 self.view.LabelShapeAt(event.m_x, event.m_y)
192
193
194
195
196 class MapPrintout(wx.wxPrintout):
197
198 """
199 wxPrintout class for printing Thuban maps
200 """
201
202 def __init__(self, map):
203 wx.wxPrintout.__init__(self)
204 self.map = map
205
206 def GetPageInfo(self):
207 return (1, 1, 1, 1)
208
209 def HasPage(self, pagenum):
210 return pagenum == 1
211
212 def OnPrintPage(self, pagenum):
213 if pagenum == 1:
214 self.draw_on_dc(self.GetDC())
215
216 def draw_on_dc(self, dc):
217 width, height = self.GetPageSizePixels()
218 llx, lly, urx, ury = self.map.ProjectedBoundingBox()
219 scalex = width / (urx - llx)
220 scaley = height / (ury - lly)
221 scale = min(scalex, scaley)
222 offx = 0.5 * (width - (urx + llx) * scale)
223 offy = 0.5 * (height + (ury + lly) * scale)
224
225 resx, resy = self.GetPPIPrinter()
226 renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
227 renderer.RenderMap(self.map)
228 return wx.true
229
230
231 class MapCanvas(wxWindow):
232
233 """A widget that displays a map and offers some interaction"""
234
235 def __init__(self, parent, winid, interactor):
236 wxWindow.__init__(self, parent, winid)
237 self.SetBackgroundColour(wxColour(255, 255, 255))
238 self.map = None
239 self.scale = 1.0
240 self.offset = (0, 0)
241 self.dragging = 0
242 self.tool = None
243 self.redraw_on_idle = 0
244 EVT_PAINT(self, self.OnPaint)
245 EVT_LEFT_DOWN(self, self.OnLeftDown)
246 EVT_LEFT_UP(self, self.OnLeftUp)
247 EVT_MOTION(self, self.OnMotion)
248 wx.EVT_IDLE(self, self.OnIdle)
249 self.interactor = interactor
250 self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
251
252 def OnPaint(self, event):
253 dc = wxPaintDC(self)
254 if self.map is None or not self.map.HasLayers():
255 return
256 self.redraw_on_idle = 1
257
258 def do_redraw(self):
259 width, height = self.GetSizeTuple()
260 bitmap = wx.wxEmptyBitmap(width, height)
261
262 dc = wx.wxMemoryDC()
263 dc.SelectObject(bitmap)
264
265 dc.BeginDrawing()
266
267 dc.SetBrush(wx.wxWHITE_BRUSH)
268 dc.SetPen(wx.wxTRANSPARENT_PEN)
269 dc.DrawRectangle(0, 0, width, height)
270
271 if 1: #self.interactor.selected_map is self.map:
272 selected_layer = self.interactor.selected_layer
273 selected_shape = self.interactor.selected_shape
274 else:
275 selected_layer = None
276 selected_shape = None
277
278 renderer = ScreenRenderer(dc, self.scale, self.offset)
279 renderer.RenderMap(self.map, selected_layer, selected_shape)
280
281 clientdc = wxClientDC(self)
282 clientdc.BeginDrawing()
283 clientdc.Blit(0, 0, width, height, dc, 0, 0)
284 clientdc.EndDrawing()
285
286
287 def Print(self):
288 printer = wx.wxPrinter()
289 printout = MapPrintout(self.map)
290 printer.Print(self, printout, wx.true)
291 printout.Destroy()
292
293 def SetMap(self, map):
294 redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
295 LAYER_VISIBILITY_CHANGED)
296 if self.map is not None:
297 for channel in redraw_channels:
298 self.map.Unsubscribe(channel, self.redraw)
299 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
300 self.projection_changed)
301 self.map = map
302 if self.map is not None:
303 for channel in redraw_channels:
304 self.map.Subscribe(channel, self.redraw)
305 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
306 self.FitMapToWindow()
307
308 def Map(self):
309 return self.map
310
311 def redraw(self, *args):
312 self.Refresh(0)
313
314 def projection_changed(self, *args):
315 self.FitMapToWindow()
316 self.redraw()
317
318 def set_view_transform(self, scale, offset):
319 self.scale = scale
320 self.offset = offset
321 self.redraw()
322
323 def proj_to_win(self, x, y):
324 """\
325 Return the point in window coords given by projected coordinates x y
326 """
327 offx, offy = self.offset
328 return (self.scale * x + offx, -self.scale * y + offy)
329
330 def win_to_proj(self, x, y):
331 """\
332 Return the point in projected coordinates given by window coords x y
333 """
334 offx, offy = self.offset
335 return ((x - offx) / self.scale, (offy - y) / self.scale)
336
337 def FitRectToWindow(self, rect):
338 width, height = self.GetSizeTuple()
339 llx, lly, urx, ury = rect
340 if llx == urx or lly == ury:
341 # zero with or zero height. Do Nothing
342 return
343 scalex = width / (urx - llx)
344 scaley = height / (ury - lly)
345 scale = min(scalex, scaley)
346 offx = 0.5 * (width - (urx + llx) * scale)
347 offy = 0.5 * (height + (ury + lly) * scale)
348 self.set_view_transform(scale, (offx, offy))
349
350 def FitMapToWindow(self):
351 """\
352 Set the scale and offset so that the map is centered in the
353 window
354 """
355 bbox = self.map.ProjectedBoundingBox()
356 if bbox is not None:
357 self.FitRectToWindow(bbox)
358
359 def ZoomFactor(self, factor):
360 width, height = self.GetSizeTuple()
361 scale = self.scale * factor
362 offx, offy = self.offset
363 offset = (factor * (offx - width / 2) + width / 2,
364 factor * (offy - height / 2) + height / 2)
365 self.set_view_transform(scale, offset)
366
367 def ZoomOutToRect(self, rect):
368 # rect is given in window coordinates
369
370 # determine the bbox of the displayed region in projected
371 # coordinates
372 width, height = self.GetSizeTuple()
373 llx, lly = self.win_to_proj(0, height - 1)
374 urx, ury = self.win_to_proj(width - 1, 0)
375
376 sx, sy, ex, ey = rect
377 scalex = (ex - sx) / (urx - llx)
378 scaley = (ey - sy) / (ury - lly)
379 scale = min(scalex, scaley)
380
381 offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
382 offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
383 self.set_view_transform(scale, (offx, offy))
384
385 def Translate(self, dx, dy):
386 offx, offy = self.offset
387 self.set_view_transform(self.scale, (offx + dx, offy + dy))
388
389 def ZoomInTool(self):
390 self.tool = ZoomInTool(self)
391
392 def ZoomOutTool(self):
393 self.tool = ZoomOutTool(self)
394
395 def PanTool(self):
396 self.tool = PanTool(self)
397
398 def IdentifyTool(self):
399 self.tool = IdentifyTool(self)
400
401 def LabelTool(self):
402 self.tool = LabelTool(self)
403
404 def CurrentTool(self):
405 return self.tool and self.tool.Name() or None
406
407 def OnLeftDown(self, event):
408 if self.tool is not None:
409 self.drag_dc = wxClientDC(self)
410 self.drag_dc.SetLogicalFunction(wxINVERT)
411 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
412 self.CaptureMouse()
413 self.tool.MouseDown(event)
414 self.tool.Show(self.drag_dc)
415 self.dragging = 1
416
417 def OnLeftUp(self, event):
418 self.ReleaseMouse()
419 if self.dragging:
420 self.tool.Hide(self.drag_dc)
421 self.tool.MouseUp(event)
422 self.drag_dc = None
423 self.dragging = 0
424
425 def OnMotion(self, event):
426 if self.dragging:
427 self.tool.Hide(self.drag_dc)
428 self.tool.MouseMove(event)
429 self.tool.Show(self.drag_dc)
430
431 def OnIdle(self, event):
432 if self.redraw_on_idle:
433 self.do_redraw()
434 self.redraw_on_idle = 0
435
436 def shape_selected(self, layer, shape):
437 self.redraw()
438
439 def find_shape_at(self, px, py, select_labels = 0, selected_layer = 1):
440 """Determine the shape at point px, py in window coords
441
442 Return the shape and the corresponding layer as a tuple (layer,
443 shape).
444
445 If the optional parameter select_labels is true (default false)
446 search through the labels. If a label is found return it's index
447 as the shape and None as the layer.
448
449 If the optional parameter selected_layer is true (default), only
450 search in the currently selected layer.
451 """
452 map_proj = self.map.projection
453 if map_proj is not None:
454 forward = map_proj.Forward
455 else:
456 forward = None
457
458 scale = self.scale
459 offx, offy = self.offset
460
461 if select_labels:
462 labels = self.map.LabelLayer().Labels()
463
464 if labels:
465 dc = wxClientDC(self)
466 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
467 dc.SetFont(font)
468 for i in range(len(labels)):
469 label = labels[i]
470 x = label.x
471 y = label.y
472 text = label.text
473 if forward:
474 x, y = forward(x, y)
475 x = x * scale + offx
476 y = -y * scale + offy
477 width, height = dc.GetTextExtent(text)
478 if label.halign == ALIGN_LEFT:
479 # nothing to be done
480 pass
481 elif label.halign == ALIGN_RIGHT:
482 x = x - width
483 elif label.halign == ALIGN_CENTER:
484 x = x - width/2
485 if label.valign == ALIGN_TOP:
486 # nothing to be done
487 pass
488 elif label.valign == ALIGN_BOTTOM:
489 y = y - height
490 elif label.valign == ALIGN_CENTER:
491 y = y - height/2
492 if x <= px < x + width and y <= py <= y + height:
493 return None, i
494
495 if selected_layer:
496 layer = self.interactor.SelectedLayer()
497 if layer is not None:
498 layers = [layer]
499 else:
500 # no layer selected. Use an empty list to effectively
501 # ignore all layers.
502 layers = []
503 else:
504 layers = self.map.Layers()
505
506 for layer_index in range(len(layers) - 1, -1, -1):
507 layer = layers[layer_index]
508
509 # search only in visible layers
510 if not layer.Visible():
511 continue
512
513 filled = layer.fill is not None
514 stroked = layer.stroke is not None
515
516 layer_proj = layer.projection
517 if layer_proj is not None:
518 inverse = layer_proj.Inverse
519 else:
520 inverse = None
521
522 shapetype = layer.ShapeType()
523
524 select_shape = -1
525 if shapetype == SHAPETYPE_POLYGON:
526 for i in range(layer.NumShapes()):
527 result = point_in_polygon_shape(layer.shapefile.cobject(),
528 i,
529 filled, stroked,
530 map_proj, layer_proj,
531 scale, -scale, offx, offy,
532 px, py)
533 if result:
534 select_shape = i
535 break
536 elif shapetype == SHAPETYPE_ARC:
537 for i in range(layer.NumShapes()):
538 result = point_in_polygon_shape(layer.shapefile.cobject(),
539 i, 0, 1,
540 map_proj, layer_proj,
541 scale, -scale, offx, offy,
542 px, py)
543 if result < 0:
544 select_shape = i
545 break
546 elif shapetype == SHAPETYPE_POINT:
547 for i in range(layer.NumShapes()):
548 shape = layer.Shape(i)
549 x, y = shape.Points()[0]
550 if inverse:
551 x, y = inverse(x, y)
552 if forward:
553 x, y = forward(x, y)
554 x = x * scale + offx
555 y = -y * scale + offy
556 if hypot(px - x, py - y) < 5:
557 select_shape = i
558 break
559
560 if select_shape >= 0:
561 return layer, select_shape
562 return None, None
563
564 def SelectShapeAt(self, x, y):
565 layer, shape = self.find_shape_at(x, y)
566 # If layer is None, then shape will also be None. We don't want
567 # to deselect the currently selected layer, so we simply select
568 # the already selected layer again.
569 if layer is None:
570 layer = self.interactor.SelectedLayer()
571 self.interactor.SelectLayerAndShape(layer, shape)
572
573 def LabelShapeAt(self, x, y):
574 ox = x; oy = y
575 label_layer = self.map.LabelLayer()
576 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
577 if layer is None and shape_index is not None:
578 # a label was selected
579 label_layer.RemoveLabel(shape_index)
580 elif layer is not None:
581 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
582 if text:
583 proj = self.map.projection
584 if proj is not None:
585 map_proj = proj
586 else:
587 map_proj = None
588 proj = layer.projection
589 if proj is not None:
590 layer_proj = proj
591 else:
592 layer_proj = None
593
594 shapetype = layer.ShapeType()
595 if shapetype == SHAPETYPE_POLYGON:
596 x, y = shape_centroid(layer.shapefile.cobject(),
597 shape_index,
598 map_proj, layer_proj, 1, 1, 0, 0)
599 if map_proj is not None:
600 x, y = map_proj.Inverse(x, y)
601 else:
602 shape = layer.Shape(shape_index)
603 if shapetype == SHAPETYPE_POINT:
604 x, y = shape.Points()[0]
605 else:
606 # assume SHAPETYPE_ARC
607 points = shape.Points()
608 x, y = points[len(points) / 2]
609 if layer_proj is not None:
610 x, y = layer_proj.Inverse(x, y)
611 if shapetype == SHAPETYPE_POINT:
612 halign = ALIGN_LEFT
613 valign = ALIGN_CENTER
614 elif shapetype == SHAPETYPE_POLYGON:
615 halign = ALIGN_CENTER
616 valign = ALIGN_CENTER
617 elif shapetype == SHAPETYPE_ARC:
618 halign = ALIGN_LEFT
619 valign = ALIGN_CENTER
620 label_layer.AddLabel(x, y, text,
621 halign = halign, valign = valign)

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26