/[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 6 - (hide annotations)
Tue Aug 28 15:41:52 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: 18452 byte(s)
import all the source files

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     def __init__(self, parent, winid):
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     import main
250     self.interactor = main.app.interactor
251     self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
252    
253     def OnPaint(self, event):
254     dc = wxPaintDC(self)
255     if self.map is None or not self.map.HasLayers():
256     return
257     self.redraw_on_idle = 1
258    
259     def do_redraw(self):
260     width, height = self.GetSizeTuple()
261     bitmap = wx.wxEmptyBitmap(width, height)
262    
263     dc = wx.wxMemoryDC()
264     dc.SelectObject(bitmap)
265    
266     dc.BeginDrawing()
267    
268     dc.SetBrush(wx.wxWHITE_BRUSH)
269     dc.SetPen(wx.wxTRANSPARENT_PEN)
270     dc.DrawRectangle(0, 0, width, height)
271    
272     if 1: #self.interactor.selected_map is self.map:
273     selected_layer = self.interactor.selected_layer
274     selected_shape = self.interactor.selected_shape
275     else:
276     selected_layer = None
277     selected_shape = None
278    
279     renderer = ScreenRenderer(dc, self.scale, self.offset)
280     renderer.RenderMap(self.map, selected_layer, selected_shape)
281    
282     clientdc = wxClientDC(self)
283     clientdc.BeginDrawing()
284     clientdc.Blit(0, 0, width, height, dc, 0, 0)
285     clientdc.EndDrawing()
286    
287    
288     def Print(self):
289     printer = wx.wxPrinter()
290     printout = MapPrintout(self.map)
291     printer.Print(self, printout, wx.true)
292     printout.Destroy()
293    
294     def SetMap(self, map):
295     redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
296     LAYER_VISIBILITY_CHANGED)
297     if self.map is not None:
298     for channel in redraw_channels:
299     self.map.Unsubscribe(channel, self.redraw)
300     self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
301     self.projection_changed)
302     self.map = map
303     if self.map is not None:
304     for channel in redraw_channels:
305     self.map.Subscribe(channel, self.redraw)
306     self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
307     self.FitMapToWindow()
308    
309     def Map(self):
310     return self.map
311    
312     def redraw(self, *args):
313     self.Refresh(0)
314    
315     def projection_changed(self, *args):
316     self.FitMapToWindow()
317     self.redraw()
318    
319     def set_view_transform(self, scale, offset):
320     self.scale = scale
321     self.offset = offset
322     self.redraw()
323    
324     def proj_to_win(self, x, y):
325     """\
326     Return the point in window coords given by projected coordinates x y
327     """
328     offx, offy = self.offset
329     return (self.scale * x + offx, -self.scale * y + offy)
330    
331     def win_to_proj(self, x, y):
332     """\
333     Return the point in projected coordinates given by window coords x y
334     """
335     offx, offy = self.offset
336     return ((x - offx) / self.scale, (offy - y) / self.scale)
337    
338     def FitRectToWindow(self, rect):
339     width, height = self.GetSizeTuple()
340     llx, lly, urx, ury = rect
341     scalex = width / (urx - llx)
342     scaley = height / (ury - lly)
343     scale = min(scalex, scaley)
344     offx = 0.5 * (width - (urx + llx) * scale)
345     offy = 0.5 * (height + (ury + lly) * scale)
346     self.set_view_transform(scale, (offx, offy))
347    
348     def FitMapToWindow(self):
349     """\
350     Set the scale and offset so that the map is centered in the
351     window
352     """
353     bbox = self.map.ProjectedBoundingBox()
354     if bbox is not None:
355     self.FitRectToWindow(bbox)
356    
357     def ZoomFactor(self, factor):
358     width, height = self.GetSizeTuple()
359     scale = self.scale * factor
360     offx, offy = self.offset
361     offset = (factor * (offx - width / 2) + width / 2,
362     factor * (offy - height / 2) + height / 2)
363     self.set_view_transform(scale, offset)
364    
365     def ZoomOutToRect(self, rect):
366     # rect is given in window coordinates
367    
368     # determine the bbox of the displayed region in projected
369     # coordinates
370     width, height = self.GetSizeTuple()
371     llx, lly = self.win_to_proj(0, height - 1)
372     urx, ury = self.win_to_proj(width - 1, 0)
373    
374     sx, sy, ex, ey = rect
375     scalex = (ex - sx) / (urx - llx)
376     scaley = (ey - sy) / (ury - lly)
377     scale = min(scalex, scaley)
378    
379     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
380     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
381     self.set_view_transform(scale, (offx, offy))
382    
383     def Translate(self, dx, dy):
384     offx, offy = self.offset
385     self.set_view_transform(self.scale, (offx + dx, offy + dy))
386    
387     def ZoomInTool(self):
388     self.tool = ZoomInTool(self)
389    
390     def ZoomOutTool(self):
391     self.tool = ZoomOutTool(self)
392    
393     def PanTool(self):
394     self.tool = PanTool(self)
395    
396     def IdentifyTool(self):
397     self.tool = IdentifyTool(self)
398    
399     def LabelTool(self):
400     self.tool = LabelTool(self)
401    
402     def CurrentTool(self):
403     return self.tool and self.tool.Name() or None
404    
405     def OnLeftDown(self, event):
406     if self.tool is not None:
407     self.drag_dc = wxClientDC(self)
408     self.drag_dc.SetLogicalFunction(wxINVERT)
409     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
410     self.CaptureMouse()
411     self.tool.MouseDown(event)
412     self.tool.Show(self.drag_dc)
413     self.dragging = 1
414    
415     def OnLeftUp(self, event):
416     self.ReleaseMouse()
417     if self.dragging:
418     self.tool.Hide(self.drag_dc)
419     self.tool.MouseUp(event)
420     self.drag_dc = None
421     self.dragging = 0
422    
423     def OnMotion(self, event):
424     if self.dragging:
425     self.tool.Hide(self.drag_dc)
426     self.tool.MouseMove(event)
427     self.tool.Show(self.drag_dc)
428    
429     def OnIdle(self, event):
430     if self.redraw_on_idle:
431     self.do_redraw()
432     self.redraw_on_idle = 0
433    
434     def shape_selected(self, layer, shape):
435     self.redraw()
436    
437     def find_shape_at(self, px, py, select_labels = 0):
438     """Return a tuple shape at point px, py in window coords."""
439     map_proj = self.map.projection
440     if map_proj is not None:
441     forward = map_proj.Forward
442     else:
443     forward = None
444    
445     scale = self.scale
446     offx, offy = self.offset
447    
448     if select_labels:
449     labels = self.map.LabelLayer().Labels()
450    
451     if labels:
452     dc = wxClientDC(self)
453     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
454     dc.SetFont(font)
455     for i in range(len(labels)):
456     label = labels[i]
457     x = label.x
458     y = label.y
459     text = label.text
460     if forward:
461     x, y = forward(x, y)
462     x = x * scale + offx
463     y = -y * scale + offy
464     width, height = dc.GetTextExtent(text)
465     if label.halign == ALIGN_LEFT:
466     # nothing to be done
467     pass
468     elif label.halign == ALIGN_RIGHT:
469     x = x - width
470     elif label.halign == ALIGN_CENTER:
471     x = x - width/2
472     if label.valign == ALIGN_TOP:
473     # nothing to be done
474     pass
475     elif label.valign == ALIGN_BOTTOM:
476     y = y - height
477     elif label.valign == ALIGN_CENTER:
478     y = y - height/2
479     if x <= px < x + width and y <= py <= y + height:
480     return None, i
481    
482     layers = self.map.Layers()
483     for layer_index in range(len(layers) - 1, -1, -1):
484     layer = layers[layer_index]
485    
486     # search only in visible layers
487     if not layer.Visible():
488     continue
489    
490     filled = layer.fill is not None
491     stroked = layer.stroke is not None
492    
493     layer_proj = layer.projection
494     if layer_proj is not None:
495     inverse = layer_proj.Inverse
496     else:
497     inverse = None
498    
499     shapetype = layer.ShapeType()
500    
501     select_shape = -1
502     if shapetype == SHAPETYPE_POLYGON:
503     for i in range(layer.NumShapes()):
504     result = point_in_polygon_shape(layer.shapefile.cobject(),
505     i,
506     filled, stroked,
507     map_proj, layer_proj,
508     scale, -scale, offx, offy,
509     px, py)
510     if result:
511     select_shape = i
512     break
513     elif shapetype == SHAPETYPE_ARC:
514     for i in range(layer.NumShapes()):
515     result = point_in_polygon_shape(layer.shapefile.cobject(),
516     i, 0, 1,
517     map_proj, layer_proj,
518     scale, -scale, offx, offy,
519     px, py)
520     if result < 0:
521     select_shape = i
522     break
523     elif shapetype == SHAPETYPE_POINT:
524     for i in range(layer.NumShapes()):
525     shape = layer.Shape(i)
526     x, y = shape.Points()[0]
527     if inverse:
528     x, y = inverse(x, y)
529     if forward:
530     x, y = forward(x, y)
531     x = x * scale + offx
532     y = -y * scale + offy
533     if hypot(px - x, py - y) < 5:
534     select_shape = i
535     break
536    
537     if select_shape >= 0:
538     return layer, select_shape
539     return None, None
540    
541     def SelectShapeAt(self, x, y):
542     layer, shape = self.find_shape_at(x, y)
543     self.interactor.SelectLayerAndShape(layer, shape)
544    
545     def LabelShapeAt(self, x, y):
546     ox = x; oy = y
547     label_layer = self.map.LabelLayer()
548     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
549     if layer is None and shape_index is not None:
550     # a label was selected
551     label_layer.RemoveLabel(shape_index)
552     elif layer is not None:
553     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
554     if text:
555     proj = self.map.projection
556     if proj is not None:
557     map_proj = proj
558     else:
559     map_proj = None
560     proj = layer.projection
561     if proj is not None:
562     layer_proj = proj
563     else:
564     layer_proj = None
565    
566     shapetype = layer.ShapeType()
567     if shapetype == SHAPETYPE_POLYGON:
568     x, y = shape_centroid(layer.shapefile.cobject(),
569     shape_index,
570     map_proj, layer_proj, 1, 1, 0, 0)
571     if map_proj is not None:
572     x, y = map_proj.Inverse(x, y)
573     else:
574     shape = layer.Shape(shape_index)
575     if shapetype == SHAPETYPE_POINT:
576     x, y = shape.Points()[0]
577     else:
578     # assume SHAPETYPE_ARC
579     points = shape.Points()
580     x, y = points[len(points) / 2]
581     if layer_proj is not None:
582     x, y = layer_proj.Inverse(x, y)
583     if shapetype == SHAPETYPE_POINT:
584     halign = ALIGN_LEFT
585     valign = ALIGN_CENTER
586     elif shapetype == SHAPETYPE_POLYGON:
587     halign = ALIGN_CENTER
588     valign = ALIGN_CENTER
589     elif shapetype == SHAPETYPE_ARC:
590     halign = ALIGN_LEFT
591     valign = ALIGN_CENTER
592     label_layer.AddLabel(x, y, text,
593     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