/[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 60 - (hide annotations)
Thu Sep 13 14:47:39 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: 21508 byte(s)
	(MapCanvas.find_shape_at): Iterate backwards (i.e. with decreasing
	index, i.e. reversed drawing order) so that objects appearing to
	be in from of others are selected first. This is probably mostly
	relevant for point shapes where the symbols used may overlap

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 bh 60 for i in range(len(labels) - 1, -1, -1):
511 bh 6 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 bh 60 for i in range(layer.NumShapes() - 1, -1, -1):
569 bh 6 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 bh 60 for i in range(layer.NumShapes() - 1, -1, -1):
580 bh 6 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 bh 60 for i in range(layer.NumShapes() - 1, -1, -1):
590 bh 6 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