/[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 45 - (hide 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 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     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 bh 23 def __init__(self, parent, winid, interactor):
236 bh 6 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 bh 23 self.interactor = interactor
250 bh 6 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 bh 45 if llx == urx or lly == ury:
341     # zero with or zero height. Do Nothing
342     return
343 bh 6 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 bh 43 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 bh 6 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 bh 43
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 bh 6 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 bh 43 # 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 bh 6 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