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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 57 - (hide annotations)
Thu Sep 13 13:55:33 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: 21460 byte(s)
	* Thuban/UI/view.py (MapCanvas.OnPaint): Only delay drawing to
	idle time when there actually is something to draw. If there's
	nothing to draw simply clear the window
	(MapCanvas.do_redraw): Call dc.EndDrawing and add some comments.
	(MapCanvas.SetMap): force a redraw in all cases because
	FitMapToWindow doesn't always do it.
	(MapCanvas.ZoomFactor): Add an optional parameter, center, to
	specify the point to move into the center of the window
	(ZoomOutTool.MouseUp, ZoomInTool.MouseUp): If the mouse wasn't
	dragged, zoon in/out by a factor of 2

1 bh 6 # 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 bh 57 sx, sy = self.start
131     cx, cy = self.current
132     if sx == cx and sy == cy:
133     # Just a mouse click. Simply zoom in by a factor of two
134     self.view.ZoomFactor(2, center = (cx, cy))
135     else:
136     # A drag. Zoom in to the rectangle
137     self.view.FitRectToWindow(self.proj_rect())
138 bh 6
139    
140     class ZoomOutTool(RectTool):
141    
142     """The Zoom-Out Tool"""
143    
144     def Name(self):
145     return "ZoomOutTool"
146    
147     def MouseUp(self, event):
148     if self.dragging:
149     Tool.MouseUp(self, event)
150     sx, sy = self.start
151     cx, cy = self.current
152 bh 57 if sx == cx and sy == cy:
153     # Just a mouse click. Simply zoom out by a factor of two
154     self.view.ZoomFactor(0.5, center = (cy, cy))
155     else:
156     # A drag. Zoom out to the rectangle
157     self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
158     max(sx, cx), max(sy, cy)))
159 bh 6
160    
161     class PanTool(Tool):
162    
163     """The Pan Tool"""
164    
165     def Name(self):
166     return "PanTool"
167    
168     def MouseMove(self, event):
169     if self.dragging:
170     x0, y0 = self.current
171     Tool.MouseMove(self, event)
172     x, y = self.current
173     width, height = self.view.GetSizeTuple()
174     dc = self.view.drag_dc
175     dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)
176    
177     def MouseUp(self, event):
178     if self.dragging:
179     Tool.MouseUp(self, event)
180     sx, sy = self.start
181     cx, cy = self.current
182     self.view.Translate(cx - sx, cy - sy)
183    
184     class IdentifyTool(Tool):
185    
186     """The "Identify" Tool"""
187    
188     def Name(self):
189     return "IdentifyTool"
190    
191     def MouseUp(self, event):
192     self.view.SelectShapeAt(event.m_x, event.m_y)
193    
194    
195     class LabelTool(Tool):
196    
197     """The "Label" Tool"""
198    
199     def Name(self):
200     return "LabelTool"
201    
202     def MouseUp(self, event):
203     self.view.LabelShapeAt(event.m_x, event.m_y)
204    
205    
206    
207    
208     class MapPrintout(wx.wxPrintout):
209    
210     """
211     wxPrintout class for printing Thuban maps
212     """
213    
214     def __init__(self, map):
215     wx.wxPrintout.__init__(self)
216     self.map = map
217    
218     def GetPageInfo(self):
219     return (1, 1, 1, 1)
220    
221     def HasPage(self, pagenum):
222     return pagenum == 1
223    
224     def OnPrintPage(self, pagenum):
225     if pagenum == 1:
226     self.draw_on_dc(self.GetDC())
227    
228     def draw_on_dc(self, dc):
229     width, height = self.GetPageSizePixels()
230     llx, lly, urx, ury = self.map.ProjectedBoundingBox()
231     scalex = width / (urx - llx)
232     scaley = height / (ury - lly)
233     scale = min(scalex, scaley)
234     offx = 0.5 * (width - (urx + llx) * scale)
235     offy = 0.5 * (height + (ury + lly) * scale)
236    
237     resx, resy = self.GetPPIPrinter()
238     renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
239     renderer.RenderMap(self.map)
240     return wx.true
241    
242    
243     class MapCanvas(wxWindow):
244    
245     """A widget that displays a map and offers some interaction"""
246    
247 bh 23 def __init__(self, parent, winid, interactor):
248 bh 6 wxWindow.__init__(self, parent, winid)
249     self.SetBackgroundColour(wxColour(255, 255, 255))
250     self.map = None
251     self.scale = 1.0
252     self.offset = (0, 0)
253     self.dragging = 0
254     self.tool = None
255     self.redraw_on_idle = 0
256     EVT_PAINT(self, self.OnPaint)
257     EVT_LEFT_DOWN(self, self.OnLeftDown)
258     EVT_LEFT_UP(self, self.OnLeftUp)
259     EVT_MOTION(self, self.OnMotion)
260     wx.EVT_IDLE(self, self.OnIdle)
261 bh 23 self.interactor = interactor
262 bh 6 self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
263    
264     def OnPaint(self, event):
265     dc = wxPaintDC(self)
266 bh 57 if self.map is not None and self.map.HasLayers():
267     # We have a non-empty map. Redraw it in idle time
268     self.redraw_on_idle = 1
269     else:
270     # If we've got no map or if the map is empty, simply clear
271     # the screen.
272    
273     # XXX it's probably possible to get rid of this. The
274     # background color of the window is already white and the
275     # only thing we may have to do is to call self.Refresh()
276     # with a true argument in the right places.
277     dc.BeginDrawing()
278     dc.Clear()
279     dc.EndDrawing()
280 bh 6
281     def do_redraw(self):
282 bh 57 # This should only be called if we have a non-empty map. We draw
283     # it into a memory DC and then blit it to the screen.
284 bh 6 width, height = self.GetSizeTuple()
285     bitmap = wx.wxEmptyBitmap(width, height)
286     dc = wx.wxMemoryDC()
287     dc.SelectObject(bitmap)
288     dc.BeginDrawing()
289    
290 bh 57 # clear the background
291 bh 6 dc.SetBrush(wx.wxWHITE_BRUSH)
292     dc.SetPen(wx.wxTRANSPARENT_PEN)
293     dc.DrawRectangle(0, 0, width, height)
294    
295     if 1: #self.interactor.selected_map is self.map:
296     selected_layer = self.interactor.selected_layer
297     selected_shape = self.interactor.selected_shape
298     else:
299     selected_layer = None
300     selected_shape = None
301 bh 57
302     # draw the map into the bitmap
303 bh 6 renderer = ScreenRenderer(dc, self.scale, self.offset)
304     renderer.RenderMap(self.map, selected_layer, selected_shape)
305    
306 bh 57 dc.EndDrawing()
307    
308     # blit the bitmap to the screen
309 bh 6 clientdc = wxClientDC(self)
310     clientdc.BeginDrawing()
311     clientdc.Blit(0, 0, width, height, dc, 0, 0)
312     clientdc.EndDrawing()
313    
314     def Print(self):
315     printer = wx.wxPrinter()
316     printout = MapPrintout(self.map)
317     printer.Print(self, printout, wx.true)
318     printout.Destroy()
319    
320     def SetMap(self, map):
321     redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
322     LAYER_VISIBILITY_CHANGED)
323     if self.map is not None:
324     for channel in redraw_channels:
325     self.map.Unsubscribe(channel, self.redraw)
326     self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
327     self.projection_changed)
328     self.map = map
329     if self.map is not None:
330     for channel in redraw_channels:
331     self.map.Subscribe(channel, self.redraw)
332     self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
333     self.FitMapToWindow()
334 bh 57 # force a redraw. If map is not empty, it's already been called
335     # by FitMapToWindow but if map is empty it hasn't been called
336     # yet so we have to explicitly call it.
337     self.redraw()
338 bh 6
339     def Map(self):
340     return self.map
341    
342     def redraw(self, *args):
343     self.Refresh(0)
344    
345     def projection_changed(self, *args):
346     self.FitMapToWindow()
347     self.redraw()
348    
349     def set_view_transform(self, scale, offset):
350     self.scale = scale
351     self.offset = offset
352     self.redraw()
353    
354     def proj_to_win(self, x, y):
355     """\
356     Return the point in window coords given by projected coordinates x y
357     """
358     offx, offy = self.offset
359     return (self.scale * x + offx, -self.scale * y + offy)
360    
361     def win_to_proj(self, x, y):
362     """\
363     Return the point in projected coordinates given by window coords x y
364     """
365     offx, offy = self.offset
366     return ((x - offx) / self.scale, (offy - y) / self.scale)
367    
368     def FitRectToWindow(self, rect):
369     width, height = self.GetSizeTuple()
370     llx, lly, urx, ury = rect
371 bh 45 if llx == urx or lly == ury:
372     # zero with or zero height. Do Nothing
373     return
374 bh 6 scalex = width / (urx - llx)
375     scaley = height / (ury - lly)
376     scale = min(scalex, scaley)
377     offx = 0.5 * (width - (urx + llx) * scale)
378     offy = 0.5 * (height + (ury + lly) * scale)
379     self.set_view_transform(scale, (offx, offy))
380    
381     def FitMapToWindow(self):
382     """\
383     Set the scale and offset so that the map is centered in the
384     window
385     """
386     bbox = self.map.ProjectedBoundingBox()
387     if bbox is not None:
388     self.FitRectToWindow(bbox)
389    
390 bh 57 def ZoomFactor(self, factor, center = None):
391     """Multiply the zoom by factor and center on center.
392    
393     The optional parameter center is a point in window coordinates
394     that should be centered. If it is omitted, it defaults to the
395     center of the window
396     """
397 bh 6 width, height = self.GetSizeTuple()
398     scale = self.scale * factor
399     offx, offy = self.offset
400 bh 57 if center is not None:
401     cx, cy = center
402     else:
403     cx = width / 2
404     cy = height / 2
405     offset = (factor * (offx - cx) + width / 2,
406     factor * (offy - cy) + height / 2)
407 bh 6 self.set_view_transform(scale, offset)
408    
409     def ZoomOutToRect(self, rect):
410     # rect is given in window coordinates
411    
412     # determine the bbox of the displayed region in projected
413     # coordinates
414     width, height = self.GetSizeTuple()
415     llx, lly = self.win_to_proj(0, height - 1)
416     urx, ury = self.win_to_proj(width - 1, 0)
417    
418     sx, sy, ex, ey = rect
419     scalex = (ex - sx) / (urx - llx)
420     scaley = (ey - sy) / (ury - lly)
421     scale = min(scalex, scaley)
422    
423     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
424     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
425     self.set_view_transform(scale, (offx, offy))
426    
427     def Translate(self, dx, dy):
428     offx, offy = self.offset
429     self.set_view_transform(self.scale, (offx + dx, offy + dy))
430    
431     def ZoomInTool(self):
432     self.tool = ZoomInTool(self)
433    
434     def ZoomOutTool(self):
435     self.tool = ZoomOutTool(self)
436    
437     def PanTool(self):
438     self.tool = PanTool(self)
439    
440     def IdentifyTool(self):
441     self.tool = IdentifyTool(self)
442    
443     def LabelTool(self):
444     self.tool = LabelTool(self)
445    
446     def CurrentTool(self):
447     return self.tool and self.tool.Name() or None
448    
449     def OnLeftDown(self, event):
450     if self.tool is not None:
451     self.drag_dc = wxClientDC(self)
452     self.drag_dc.SetLogicalFunction(wxINVERT)
453     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
454     self.CaptureMouse()
455     self.tool.MouseDown(event)
456     self.tool.Show(self.drag_dc)
457     self.dragging = 1
458    
459     def OnLeftUp(self, event):
460     self.ReleaseMouse()
461     if self.dragging:
462     self.tool.Hide(self.drag_dc)
463     self.tool.MouseUp(event)
464     self.drag_dc = None
465     self.dragging = 0
466    
467     def OnMotion(self, event):
468     if self.dragging:
469     self.tool.Hide(self.drag_dc)
470     self.tool.MouseMove(event)
471     self.tool.Show(self.drag_dc)
472    
473     def OnIdle(self, event):
474     if self.redraw_on_idle:
475     self.do_redraw()
476     self.redraw_on_idle = 0
477    
478     def shape_selected(self, layer, shape):
479     self.redraw()
480    
481 bh 43 def find_shape_at(self, px, py, select_labels = 0, selected_layer = 1):
482     """Determine the shape at point px, py in window coords
483    
484     Return the shape and the corresponding layer as a tuple (layer,
485     shape).
486    
487     If the optional parameter select_labels is true (default false)
488     search through the labels. If a label is found return it's index
489     as the shape and None as the layer.
490    
491     If the optional parameter selected_layer is true (default), only
492     search in the currently selected layer.
493     """
494 bh 6 map_proj = self.map.projection
495     if map_proj is not None:
496     forward = map_proj.Forward
497     else:
498     forward = None
499    
500     scale = self.scale
501     offx, offy = self.offset
502    
503     if select_labels:
504     labels = self.map.LabelLayer().Labels()
505    
506     if labels:
507     dc = wxClientDC(self)
508     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
509     dc.SetFont(font)
510     for i in range(len(labels)):
511     label = labels[i]
512     x = label.x
513     y = label.y
514     text = label.text
515     if forward:
516     x, y = forward(x, y)
517     x = x * scale + offx
518     y = -y * scale + offy
519     width, height = dc.GetTextExtent(text)
520     if label.halign == ALIGN_LEFT:
521     # nothing to be done
522     pass
523     elif label.halign == ALIGN_RIGHT:
524     x = x - width
525     elif label.halign == ALIGN_CENTER:
526     x = x - width/2
527     if label.valign == ALIGN_TOP:
528     # nothing to be done
529     pass
530     elif label.valign == ALIGN_BOTTOM:
531     y = y - height
532     elif label.valign == ALIGN_CENTER:
533     y = y - height/2
534     if x <= px < x + width and y <= py <= y + height:
535     return None, i
536 bh 43
537     if selected_layer:
538     layer = self.interactor.SelectedLayer()
539     if layer is not None:
540     layers = [layer]
541     else:
542     # no layer selected. Use an empty list to effectively
543     # ignore all layers.
544     layers = []
545     else:
546     layers = self.map.Layers()
547    
548 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
549     layer = layers[layer_index]
550    
551     # search only in visible layers
552     if not layer.Visible():
553     continue
554    
555     filled = layer.fill is not None
556     stroked = layer.stroke is not None
557    
558     layer_proj = layer.projection
559     if layer_proj is not None:
560     inverse = layer_proj.Inverse
561     else:
562     inverse = None
563    
564     shapetype = layer.ShapeType()
565    
566     select_shape = -1
567     if shapetype == SHAPETYPE_POLYGON:
568     for i in range(layer.NumShapes()):
569     result = point_in_polygon_shape(layer.shapefile.cobject(),
570     i,
571     filled, stroked,
572     map_proj, layer_proj,
573     scale, -scale, offx, offy,
574     px, py)
575     if result:
576     select_shape = i
577     break
578     elif shapetype == SHAPETYPE_ARC:
579     for i in range(layer.NumShapes()):
580     result = point_in_polygon_shape(layer.shapefile.cobject(),
581     i, 0, 1,
582     map_proj, layer_proj,
583     scale, -scale, offx, offy,
584     px, py)
585     if result < 0:
586     select_shape = i
587     break
588     elif shapetype == SHAPETYPE_POINT:
589     for i in range(layer.NumShapes()):
590     shape = layer.Shape(i)
591     x, y = shape.Points()[0]
592     if inverse:
593     x, y = inverse(x, y)
594     if forward:
595     x, y = forward(x, y)
596     x = x * scale + offx
597     y = -y * scale + offy
598     if hypot(px - x, py - y) < 5:
599     select_shape = i
600     break
601    
602     if select_shape >= 0:
603     return layer, select_shape
604     return None, None
605    
606     def SelectShapeAt(self, x, y):
607     layer, shape = self.find_shape_at(x, y)
608 bh 43 # If layer is None, then shape will also be None. We don't want
609     # to deselect the currently selected layer, so we simply select
610     # the already selected layer again.
611     if layer is None:
612     layer = self.interactor.SelectedLayer()
613 bh 6 self.interactor.SelectLayerAndShape(layer, shape)
614    
615     def LabelShapeAt(self, x, y):
616     ox = x; oy = y
617     label_layer = self.map.LabelLayer()
618     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
619     if layer is None and shape_index is not None:
620     # a label was selected
621     label_layer.RemoveLabel(shape_index)
622     elif layer is not None:
623     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
624     if text:
625     proj = self.map.projection
626     if proj is not None:
627     map_proj = proj
628     else:
629     map_proj = None
630     proj = layer.projection
631     if proj is not None:
632     layer_proj = proj
633     else:
634     layer_proj = None
635    
636     shapetype = layer.ShapeType()
637     if shapetype == SHAPETYPE_POLYGON:
638     x, y = shape_centroid(layer.shapefile.cobject(),
639     shape_index,
640     map_proj, layer_proj, 1, 1, 0, 0)
641     if map_proj is not None:
642     x, y = map_proj.Inverse(x, y)
643     else:
644     shape = layer.Shape(shape_index)
645     if shapetype == SHAPETYPE_POINT:
646     x, y = shape.Points()[0]
647     else:
648     # assume SHAPETYPE_ARC
649     points = shape.Points()
650     x, y = points[len(points) / 2]
651     if layer_proj is not None:
652     x, y = layer_proj.Inverse(x, y)
653     if shapetype == SHAPETYPE_POINT:
654     halign = ALIGN_LEFT
655     valign = ALIGN_CENTER
656     elif shapetype == SHAPETYPE_POLYGON:
657     halign = ALIGN_CENTER
658     valign = ALIGN_CENTER
659     elif shapetype == SHAPETYPE_ARC:
660     halign = ALIGN_LEFT
661     valign = ALIGN_CENTER
662     label_layer.AddLabel(x, y, text,
663     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