/[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 1105 - (hide annotations)
Fri May 30 06:30:15 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 36423 byte(s)
(MapCanvas.OnPaint): Wrap code
        with try/finally. Fixes RTBug #1904.
(MapCanvas.FitSelectedToWindow): If a single point is selected
        simply center it on the display. Fixes RTBug #1849.

1 bh 404 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4 frank 910 # Frank Koormann <[email protected]>
5 bh 6 #
6     # This program is free software under the GPL (>=v2)
7     # Read the file COPYING coming with Thuban for details.
8    
9     """
10     Classes for display of a map and interaction with it
11     """
12    
13     __version__ = "$Revision$"
14    
15 frank 910 from Thuban import _
16    
17 jonathan 799 import sys
18 frank 910 import os.path
19 jonathan 799
20 bh 6 from math import hypot
21    
22     from wxPython.wx import wxWindow,\
23     wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
24 jonathan 822 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
25     wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \
26 frank 910 wxImageFromBitmap, wxPlatform
27 bh 6
28 frank 910 # Export related stuff
29     if wxPlatform == '__WXMSW__':
30     from wxPython.wx import wxMetaFileDC
31     from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK
32 bh 6
33     from wxPython import wx
34    
35     from wxproj import point_in_polygon_shape, shape_centroid
36    
37     from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
38 jonathan 565 MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
39 bh 6 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
40     SHAPETYPE_POINT
41     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
42     ALIGN_LEFT, ALIGN_RIGHT
43 bh 122 from Thuban.Lib.connector import Publisher
44 jonathan 433 from Thuban.Model.color import Color
45 bh 6
46 jonathan 822 import resource
47    
48 bh 535 from selection import Selection
49 frank 910 from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
50 bh 6
51     import labeldialog
52    
53 frank 855 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
54     SCALE_CHANGED
55 bh 6
56    
57     #
58     # The tools
59     #
60    
61     class Tool:
62    
63     """
64     Base class for the interactive tools
65     """
66    
67     def __init__(self, view):
68     """Intitialize the tool. The view is the canvas displaying the map"""
69     self.view = view
70     self.start = self.current = None
71     self.dragging = 0
72     self.drawn = 0
73    
74     def Name(self):
75     """Return the tool's name"""
76     return ''
77    
78     def drag_start(self, x, y):
79     self.start = self.current = x, y
80     self.dragging = 1
81    
82     def drag_move(self, x, y):
83     self.current = x, y
84    
85     def drag_stop(self, x, y):
86     self.current = x, y
87     self.dragging = 0
88    
89     def Show(self, dc):
90     if not self.drawn:
91     self.draw(dc)
92     self.drawn = 1
93    
94     def Hide(self, dc):
95     if self.drawn:
96     self.draw(dc)
97     self.drawn = 0
98    
99     def draw(self, dc):
100     pass
101    
102     def MouseDown(self, event):
103     self.drag_start(event.m_x, event.m_y)
104    
105     def MouseMove(self, event):
106     if self.dragging:
107     self.drag_move(event.m_x, event.m_y)
108    
109     def MouseUp(self, event):
110     if self.dragging:
111     self.drag_move(event.m_x, event.m_y)
112    
113     def Cancel(self):
114     self.dragging = 0
115    
116    
117     class RectTool(Tool):
118    
119     """Base class for tools that draw rectangles while dragging"""
120    
121     def draw(self, dc):
122     sx, sy = self.start
123     cx, cy = self.current
124     dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
125    
126     class ZoomInTool(RectTool):
127    
128     """The Zoom-In Tool"""
129    
130     def Name(self):
131     return "ZoomInTool"
132    
133     def proj_rect(self):
134     """return the rectangle given by start and current in projected
135     coordinates"""
136     sx, sy = self.start
137     cx, cy = self.current
138     left, top = self.view.win_to_proj(sx, sy)
139     right, bottom = self.view.win_to_proj(cx, cy)
140     return (min(left, right), min(top, bottom),
141     max(left, right), max(top, bottom))
142    
143     def MouseUp(self, event):
144     if self.dragging:
145     Tool.MouseUp(self, event)
146 bh 57 sx, sy = self.start
147     cx, cy = self.current
148 bh 288 if sx == cx or sy == cy:
149     # Just a mouse click or a degenerate rectangle. Simply
150     # zoom in by a factor of two
151     # FIXME: For a click this is the desired behavior but should we
152     # really do this for degenrate rectagles as well or
153     # should we ignore them?
154 bh 57 self.view.ZoomFactor(2, center = (cx, cy))
155     else:
156     # A drag. Zoom in to the rectangle
157     self.view.FitRectToWindow(self.proj_rect())
158 bh 6
159    
160     class ZoomOutTool(RectTool):
161    
162     """The Zoom-Out Tool"""
163 bh 239
164 bh 6 def Name(self):
165     return "ZoomOutTool"
166    
167     def MouseUp(self, event):
168     if self.dragging:
169     Tool.MouseUp(self, event)
170     sx, sy = self.start
171     cx, cy = self.current
172 bh 288 if sx == cx or sy == cy:
173     # Just a mouse click or a degenerate rectangle. Simply
174     # zoom out by a factor of two.
175     # FIXME: For a click this is the desired behavior but should we
176     # really do this for degenrate rectagles as well or
177     # should we ignore them?
178 bh 239 self.view.ZoomFactor(0.5, center = (cx, cy))
179 bh 57 else:
180     # A drag. Zoom out to the rectangle
181     self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
182     max(sx, cx), max(sy, cy)))
183 bh 6
184    
185     class PanTool(Tool):
186    
187     """The Pan Tool"""
188    
189     def Name(self):
190     return "PanTool"
191    
192     def MouseMove(self, event):
193     if self.dragging:
194     Tool.MouseMove(self, event)
195 bh 159 sx, sy = self.start
196 bh 6 x, y = self.current
197     width, height = self.view.GetSizeTuple()
198 bh 159
199     bitmapdc = wx.wxMemoryDC()
200     bitmapdc.SelectObject(self.view.bitmap)
201    
202 bh 6 dc = self.view.drag_dc
203 bh 159 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
204 bh 6
205     def MouseUp(self, event):
206     if self.dragging:
207     Tool.MouseUp(self, event)
208     sx, sy = self.start
209     cx, cy = self.current
210     self.view.Translate(cx - sx, cy - sy)
211 bh 246
212 bh 6 class IdentifyTool(Tool):
213    
214     """The "Identify" Tool"""
215 bh 246
216 bh 6 def Name(self):
217     return "IdentifyTool"
218    
219     def MouseUp(self, event):
220     self.view.SelectShapeAt(event.m_x, event.m_y)
221    
222    
223     class LabelTool(Tool):
224    
225     """The "Label" Tool"""
226    
227     def Name(self):
228     return "LabelTool"
229    
230     def MouseUp(self, event):
231     self.view.LabelShapeAt(event.m_x, event.m_y)
232    
233    
234     class MapPrintout(wx.wxPrintout):
235    
236     """
237     wxPrintout class for printing Thuban maps
238     """
239    
240 frank 910 def __init__(self, canvas, map, region, selected_layer, selected_shapes):
241 bh 6 wx.wxPrintout.__init__(self)
242 frank 910 self.canvas = canvas
243 bh 6 self.map = map
244 frank 910 self.region = region
245     self.selected_layer = selected_layer
246     self.selected_shapes = selected_shapes
247 bh 6
248     def GetPageInfo(self):
249     return (1, 1, 1, 1)
250    
251     def HasPage(self, pagenum):
252     return pagenum == 1
253    
254     def OnPrintPage(self, pagenum):
255     if pagenum == 1:
256     self.draw_on_dc(self.GetDC())
257    
258     def draw_on_dc(self, dc):
259     width, height = self.GetPageSizePixels()
260 frank 910 scale, offset, mapregion = OutputTransform(self.canvas.scale,
261     self.canvas.offset,
262     self.canvas.GetSizeTuple(),
263     self.GetPageSizePixels())
264 bh 6 resx, resy = self.GetPPIPrinter()
265 frank 910 renderer = PrinterRenderer(dc, scale, offset, resolution = resy)
266     x, y, width, height = self.region
267     canvas_scale = self.canvas.scale
268     renderer.RenderMap(self.map,
269     (0,0,
270     (width/canvas_scale)*scale,
271     (height/canvas_scale)*scale),
272     mapregion,
273     self.selected_layer, self.selected_shapes)
274 jan 1035 return True
275 bh 6
276 bh 246
277 bh 122 class MapCanvas(wxWindow, Publisher):
278 bh 6
279     """A widget that displays a map and offers some interaction"""
280    
281 bh 535 # Some messages that can be subscribed/unsubscribed directly through
282 bh 789 # the MapCanvas come in fact from other objects. This is a dict
283     # mapping those messages to the names of the instance variables they
284     # actually come from. The delegation is implemented in the Subscribe
285     # and Unsubscribe methods
286 bh 535 delegated_messages = {LAYER_SELECTED: "selection",
287     SHAPES_SELECTED: "selection"}
288    
289     # Methods delegated to some instance variables. The delegation is
290     # implemented in the __getattr__ method.
291     delegated_methods = {"SelectLayer": "selection",
292     "SelectShapes": "selection",
293     "SelectedLayer": "selection",
294 jonathan 831 "HasSelectedLayer": "selection",
295 jonathan 883 "HasSelectedShapes": "selection",
296     "SelectedShapes": "selection"}
297 bh 535
298     def __init__(self, parent, winid):
299 bh 6 wxWindow.__init__(self, parent, winid)
300     self.SetBackgroundColour(wxColour(255, 255, 255))
301 bh 125
302     # the map displayed in this canvas. Set with SetMap()
303 bh 6 self.map = None
304 bh 125
305     # scale and offset describe the transformation from projected
306     # coordinates to window coordinates.
307 bh 6 self.scale = 1.0
308     self.offset = (0, 0)
309 bh 125
310     # whether the user is currently dragging the mouse, i.e. moving
311     # the mouse while pressing a mouse button
312 bh 6 self.dragging = 0
313 bh 125
314     # the currently active tool
315 bh 6 self.tool = None
316 bh 125
317     # The current mouse position of the last OnMotion event or None
318     # if the mouse is outside the window.
319     self.current_position = None
320    
321     # the bitmap serving as backing store
322     self.bitmap = None
323    
324 bh 535 # the selection
325     self.selection = Selection()
326     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
327 bh 125
328 bh 174 # keep track of which layers/shapes are selected to make sure we
329     # only redraw when necessary
330     self.last_selected_layer = None
331     self.last_selected_shape = None
332    
333 bh 125 # subscribe the WX events we're interested in
334 bh 6 EVT_PAINT(self, self.OnPaint)
335     EVT_LEFT_DOWN(self, self.OnLeftDown)
336     EVT_LEFT_UP(self, self.OnLeftUp)
337     EVT_MOTION(self, self.OnMotion)
338 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
339 bh 125 wx.EVT_SIZE(self, self.OnSize)
340 bh 6
341 bh 122 def __del__(self):
342     wxWindow.__del__(self)
343     Publisher.__del__(self)
344    
345 bh 535 def Subscribe(self, channel, *args):
346     """Extend the inherited method to handle delegated messages.
347    
348     If channel is one of the delegated messages call the appropriate
349     object's Subscribe method. Otherwise just call the inherited
350     method.
351     """
352     if channel in self.delegated_messages:
353     object = getattr(self, self.delegated_messages[channel])
354     object.Subscribe(channel, *args)
355     else:
356     Publisher.Subscribe(self, channel, *args)
357    
358     def Unsubscribe(self, channel, *args):
359     """Extend the inherited method to handle delegated messages.
360    
361     If channel is one of the delegated messages call the appropriate
362     object's Unsubscribe method. Otherwise just call the inherited
363     method.
364     """
365     if channel in self.delegated_messages:
366     object = getattr(self, self.delegated_messages[channel])
367     object.Unsubscribe(channel, *args)
368     else:
369     Publisher.Unsubscribe(self, channel, *args)
370    
371     def __getattr__(self, attr):
372     if attr in self.delegated_methods:
373     return getattr(getattr(self, self.delegated_methods[attr]), attr)
374     raise AttributeError(attr)
375    
376 bh 6 def OnPaint(self, event):
377     dc = wxPaintDC(self)
378 jonathan 799 clear = self.map is None or not self.map.HasLayers()
379    
380 jonathan 940 wxBeginBusyCursor()
381 jonathan 1105 try:
382     if not clear:
383     self.do_redraw()
384     try:
385     pass
386     except:
387     print "Error during drawing:", sys.exc_info()[0]
388     clear = True
389 jonathan 822
390 jonathan 1105 if clear:
391     # If we've got no map or if the map is empty, simply clear
392     # the screen.
393 jonathan 799
394 jonathan 1105 # XXX it's probably possible to get rid of this. The
395     # background color of the window is already white and the
396     # only thing we may have to do is to call self.Refresh()
397     # with a true argument in the right places.
398     dc.BeginDrawing()
399     dc.Clear()
400     dc.EndDrawing()
401     finally:
402     wxEndBusyCursor()
403 bh 246
404 bh 6 def do_redraw(self):
405 bh 145 # This should only be called if we have a non-empty map.
406 bh 125
407 bh 145 # Get the window size.
408 bh 6 width, height = self.GetSizeTuple()
409    
410 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
411     if self.bitmap is not None:
412     bitmap = self.bitmap
413 bh 6 else:
414 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
415     dc = wx.wxMemoryDC()
416     dc.SelectObject(bitmap)
417     dc.BeginDrawing()
418 bh 57
419 bh 125 # clear the background
420 jonathan 799 #dc.SetBrush(wx.wxWHITE_BRUSH)
421     #dc.SetPen(wx.wxTRANSPARENT_PEN)
422     #dc.DrawRectangle(0, 0, width, height)
423     dc.SetBackground(wx.wxWHITE_BRUSH)
424     dc.Clear()
425 bh 6
426 bh 535 selected_layer = self.selection.SelectedLayer()
427     selected_shapes = self.selection.SelectedShapes()
428 bh 57
429 bh 125 # draw the map into the bitmap
430     renderer = ScreenRenderer(dc, self.scale, self.offset)
431 bh 149
432 bh 296 # Pass the entire bitmap as update region to the renderer.
433 bh 149 # We're redrawing the whole bitmap, after all.
434     renderer.RenderMap(self.map, (0, 0, width, height),
435 bh 535 selected_layer, selected_shapes)
436 bh 125
437     dc.EndDrawing()
438     dc.SelectObject(wx.wxNullBitmap)
439     self.bitmap = bitmap
440    
441 bh 57 # blit the bitmap to the screen
442 bh 125 dc = wx.wxMemoryDC()
443     dc.SelectObject(bitmap)
444 bh 6 clientdc = wxClientDC(self)
445     clientdc.BeginDrawing()
446     clientdc.Blit(0, 0, width, height, dc, 0, 0)
447     clientdc.EndDrawing()
448    
449 frank 910 def Export(self):
450 jonathan 967 if self.scale == 0:
451     return
452    
453 frank 910 if hasattr(self, "export_path"):
454     export_path = self.export_path
455     else:
456     export_path="."
457     dlg = wxFileDialog(self, _("Export Map"), export_path, "",
458     "Enhanced Metafile (*.wmf)|*.wmf",
459     wxSAVE|wxOVERWRITE_PROMPT)
460     if dlg.ShowModal() == wxID_OK:
461     self.export_path = os.path.dirname(dlg.GetPath())
462     dc = wxMetaFileDC(dlg.GetPath())
463    
464     scale, offset, mapregion = OutputTransform(self.scale,
465 frank 926 self.offset,
466 frank 910 self.GetSizeTuple(),
467     dc.GetSizeTuple())
468    
469     selected_layer = self.selection.SelectedLayer()
470     selected_shapes = self.selection.SelectedShapes()
471    
472     renderer = ExportRenderer(dc, scale, offset)
473    
474     # Pass the entire bitmap as update region to the renderer.
475     # We're redrawing the whole bitmap, after all.
476     width, height = self.GetSizeTuple()
477     renderer.RenderMap(self.map,
478     (0,0,
479     (width/self.scale)*scale,
480     (height/self.scale)*scale),
481     mapregion,
482     selected_layer, selected_shapes)
483     dc.EndDrawing()
484     dc.Close()
485     dlg.Destroy()
486    
487 bh 6 def Print(self):
488     printer = wx.wxPrinter()
489 frank 910 width, height = self.GetSizeTuple()
490     selected_layer = self.selection.SelectedLayer()
491     selected_shapes = self.selection.SelectedShapes()
492    
493     printout = MapPrintout(self, self.map, (0, 0, width, height),
494     selected_layer, selected_shapes)
495 jan 1035 printer.Print(self, printout, True)
496 bh 6 printout.Destroy()
497 bh 246
498 bh 6 def SetMap(self, map):
499 jonathan 565 redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
500 bh 6 LAYER_VISIBILITY_CHANGED)
501     if self.map is not None:
502     for channel in redraw_channels:
503 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
504 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
505     self.projection_changed)
506     self.map = map
507 bh 535 self.selection.ClearSelection()
508 bh 6 if self.map is not None:
509     for channel in redraw_channels:
510 bh 125 self.map.Subscribe(channel, self.full_redraw)
511 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
512     self.FitMapToWindow()
513 bh 57 # force a redraw. If map is not empty, it's already been called
514     # by FitMapToWindow but if map is empty it hasn't been called
515     # yet so we have to explicitly call it.
516 bh 125 self.full_redraw()
517 bh 6
518     def Map(self):
519 bh 295 """Return the map displayed by this canvas"""
520 bh 6 return self.map
521    
522     def redraw(self, *args):
523     self.Refresh(0)
524    
525 bh 125 def full_redraw(self, *args):
526     self.bitmap = None
527     self.redraw()
528    
529 bh 6 def projection_changed(self, *args):
530     self.FitMapToWindow()
531 bh 125 self.full_redraw()
532 bh 6
533     def set_view_transform(self, scale, offset):
534     self.scale = scale
535 jonathan 940
536 bh 6 self.offset = offset
537 bh 125 self.full_redraw()
538 frank 855 self.issue(SCALE_CHANGED, scale)
539 bh 6
540     def proj_to_win(self, x, y):
541     """\
542     Return the point in window coords given by projected coordinates x y
543     """
544 jonathan 967 if self.scale == 0:
545     return (0, 0)
546    
547 bh 6 offx, offy = self.offset
548     return (self.scale * x + offx, -self.scale * y + offy)
549    
550     def win_to_proj(self, x, y):
551     """\
552     Return the point in projected coordinates given by window coords x y
553     """
554 jonathan 967 if self.scale == 0:
555     return (0, 0)
556    
557 bh 6 offx, offy = self.offset
558     return ((x - offx) / self.scale, (offy - y) / self.scale)
559    
560     def FitRectToWindow(self, rect):
561 bh 293 """Fit the rectangular region given by rect into the window.
562 bh 535
563 bh 293 Set scale so that rect (in projected coordinates) just fits into
564     the window and center it.
565     """
566 bh 6 width, height = self.GetSizeTuple()
567     llx, lly, urx, ury = rect
568 bh 45 if llx == urx or lly == ury:
569 jonathan 799 # zero width or zero height. Do Nothing
570 bh 45 return
571 bh 6 scalex = width / (urx - llx)
572     scaley = height / (ury - lly)
573     scale = min(scalex, scaley)
574     offx = 0.5 * (width - (urx + llx) * scale)
575     offy = 0.5 * (height + (ury + lly) * scale)
576     self.set_view_transform(scale, (offx, offy))
577    
578     def FitMapToWindow(self):
579 bh 293 """Fit the map to the window
580 bh 535
581 bh 293 Set the scale so that the map fits exactly into the window and
582     center it in the window.
583 bh 6 """
584     bbox = self.map.ProjectedBoundingBox()
585     if bbox is not None:
586     self.FitRectToWindow(bbox)
587    
588 jonathan 822 def FitLayerToWindow(self, layer):
589     """Fit the given layer to the window.
590    
591     Set the scale so that the layer fits exactly into the window and
592     center it in the window.
593     """
594    
595     bbox = layer.LatLongBoundingBox()
596     if bbox is not None:
597     proj = self.map.GetProjection()
598     if proj is not None:
599     bbox = proj.ForwardBBox(bbox)
600    
601 jonathan 824 if bbox is not None:
602     self.FitRectToWindow(bbox)
603    
604 jonathan 831 def FitSelectedToWindow(self):
605     layer = self.selection.SelectedLayer()
606     shapes = self.selection.SelectedShapes()
607    
608     bbox = layer.ShapesBoundingBox(shapes)
609     if bbox is not None:
610     proj = self.map.GetProjection()
611     if proj is not None:
612     bbox = proj.ForwardBBox(bbox)
613    
614     if bbox is not None:
615 jonathan 1105 if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:
616     self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))
617     else:
618     self.FitRectToWindow(bbox)
619 jonathan 831
620 bh 57 def ZoomFactor(self, factor, center = None):
621     """Multiply the zoom by factor and center on center.
622    
623     The optional parameter center is a point in window coordinates
624     that should be centered. If it is omitted, it defaults to the
625     center of the window
626     """
627 jonathan 967 if self.scale > 0:
628     width, height = self.GetSizeTuple()
629     scale = self.scale * factor
630     offx, offy = self.offset
631     if center is not None:
632     cx, cy = center
633     else:
634     cx = width / 2
635     cy = height / 2
636     offset = (factor * (offx - cx) + width / 2,
637     factor * (offy - cy) + height / 2)
638     self.set_view_transform(scale, offset)
639 bh 6
640     def ZoomOutToRect(self, rect):
641 bh 293 """Zoom out to fit the currently visible region into rect.
642 bh 6
643 bh 293 The rect parameter is given in window coordinates
644     """
645 bh 6 # determine the bbox of the displayed region in projected
646     # coordinates
647     width, height = self.GetSizeTuple()
648     llx, lly = self.win_to_proj(0, height - 1)
649     urx, ury = self.win_to_proj(width - 1, 0)
650    
651     sx, sy, ex, ey = rect
652     scalex = (ex - sx) / (urx - llx)
653     scaley = (ey - sy) / (ury - lly)
654     scale = min(scalex, scaley)
655    
656     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
657     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
658     self.set_view_transform(scale, (offx, offy))
659    
660     def Translate(self, dx, dy):
661 bh 295 """Move the map by dx, dy pixels"""
662 bh 6 offx, offy = self.offset
663     self.set_view_transform(self.scale, (offx + dx, offy + dy))
664    
665 bh 356 def SelectTool(self, tool):
666     """Make tool the active tool.
667    
668     The parameter should be an instance of Tool or None to indicate
669     that no tool is active.
670     """
671     self.tool = tool
672    
673 bh 6 def ZoomInTool(self):
674 bh 295 """Start the zoom in tool"""
675 bh 356 self.SelectTool(ZoomInTool(self))
676 bh 6
677     def ZoomOutTool(self):
678 bh 295 """Start the zoom out tool"""
679 bh 356 self.SelectTool(ZoomOutTool(self))
680 bh 6
681     def PanTool(self):
682 bh 295 """Start the pan tool"""
683 bh 356 self.SelectTool(PanTool(self))
684 jonathan 822 #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
685     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
686     #print bmp
687     #img = wxImageFromBitmap(bmp)
688     #print img
689     #cur = wxCursor(img)
690     #print cur
691     #self.SetCursor(cur)
692 bh 6
693     def IdentifyTool(self):
694 bh 295 """Start the identify tool"""
695 bh 356 self.SelectTool(IdentifyTool(self))
696 bh 6
697     def LabelTool(self):
698 bh 295 """Start the label tool"""
699 bh 356 self.SelectTool(LabelTool(self))
700 bh 6
701     def CurrentTool(self):
702 bh 295 """Return the name of the current tool or None if no tool is active"""
703 bh 6 return self.tool and self.tool.Name() or None
704    
705 bh 122 def CurrentPosition(self):
706     """Return current position of the mouse in projected coordinates.
707    
708     The result is a 2-tuple of floats with the coordinates. If the
709     mouse is not in the window, the result is None.
710     """
711     if self.current_position is not None:
712     x, y = self.current_position
713     return self.win_to_proj(x, y)
714     else:
715     return None
716    
717     def set_current_position(self, event):
718     """Set the current position from event
719    
720     Should be called by all events that contain mouse positions
721     especially EVT_MOTION. The event paramete may be None to
722     indicate the the pointer left the window.
723     """
724     if event is not None:
725     self.current_position = (event.m_x, event.m_y)
726     else:
727     self.current_position = None
728     self.issue(VIEW_POSITION)
729    
730 bh 6 def OnLeftDown(self, event):
731 bh 122 self.set_current_position(event)
732 bh 6 if self.tool is not None:
733     self.drag_dc = wxClientDC(self)
734     self.drag_dc.SetLogicalFunction(wxINVERT)
735     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
736     self.CaptureMouse()
737     self.tool.MouseDown(event)
738     self.tool.Show(self.drag_dc)
739     self.dragging = 1
740 bh 246
741 bh 6 def OnLeftUp(self, event):
742 bh 122 self.set_current_position(event)
743 bh 6 if self.dragging:
744 bh 261 self.ReleaseMouse()
745 bh 404 try:
746     self.tool.Hide(self.drag_dc)
747     self.tool.MouseUp(event)
748     finally:
749     self.drag_dc = None
750     self.dragging = 0
751 bh 6
752     def OnMotion(self, event):
753 bh 122 self.set_current_position(event)
754 bh 6 if self.dragging:
755     self.tool.Hide(self.drag_dc)
756     self.tool.MouseMove(event)
757     self.tool.Show(self.drag_dc)
758    
759 bh 122 def OnLeaveWindow(self, event):
760     self.set_current_position(None)
761    
762 bh 125 def OnSize(self, event):
763     # the window's size has changed. We have to get a new bitmap. If
764     # we want to be clever we could try to get by without throwing
765     # everything away. E.g. when the window gets smaller, we could
766     # either keep the bitmap or create the new one from the old one.
767     # Even when the window becomes larger some parts of the bitmap
768     # could be reused.
769     self.full_redraw()
770 jonathan 799 pass
771 bh 125
772 bh 6 def shape_selected(self, layer, shape):
773 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
774     # The selection object takes care that it only issues
775     # SHAPES_SELECTED messages when the set of selected shapes has
776     # actually changed, so we can do a full redraw unconditionally.
777     # FIXME: We should perhaps try to limit the redraw to the are
778     # actually covered by the shapes before and after the selection
779     # change.
780     self.full_redraw()
781 bh 6
782 bh 301 def unprojected_rect_around_point(self, x, y, dist):
783     """return a rect dist pixels around (x, y) in unprojected corrdinates
784 bh 159
785     The return value is a tuple (minx, miny, maxx, maxy) suitable a
786     parameter to a layer's ShapesInRegion method.
787     """
788     map_proj = self.map.projection
789     if map_proj is not None:
790     inverse = map_proj.Inverse
791     else:
792     inverse = None
793    
794     xs = []
795     ys = []
796     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
797 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
798 bh 159 if inverse:
799     px, py = inverse(px, py)
800     xs.append(px)
801     ys.append(py)
802     return (min(xs), min(ys), max(xs), max(ys))
803    
804 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
805 bh 43 """Determine the shape at point px, py in window coords
806    
807     Return the shape and the corresponding layer as a tuple (layer,
808     shape).
809    
810     If the optional parameter select_labels is true (default false)
811     search through the labels. If a label is found return it's index
812     as the shape and None as the layer.
813    
814 bh 246 If the optional parameter searched_layer is given (or not None
815     which it defaults to), only search in that layer.
816 bh 43 """
817 bh 6 map_proj = self.map.projection
818     if map_proj is not None:
819     forward = map_proj.Forward
820     else:
821     forward = None
822    
823     scale = self.scale
824 jonathan 967
825     if scale == 0:
826     return None, None
827    
828 bh 6 offx, offy = self.offset
829    
830     if select_labels:
831     labels = self.map.LabelLayer().Labels()
832 bh 246
833 bh 6 if labels:
834     dc = wxClientDC(self)
835     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
836     dc.SetFont(font)
837 bh 60 for i in range(len(labels) - 1, -1, -1):
838 bh 6 label = labels[i]
839     x = label.x
840     y = label.y
841     text = label.text
842     if forward:
843     x, y = forward(x, y)
844     x = x * scale + offx
845     y = -y * scale + offy
846     width, height = dc.GetTextExtent(text)
847     if label.halign == ALIGN_LEFT:
848     # nothing to be done
849     pass
850     elif label.halign == ALIGN_RIGHT:
851     x = x - width
852     elif label.halign == ALIGN_CENTER:
853     x = x - width/2
854     if label.valign == ALIGN_TOP:
855     # nothing to be done
856     pass
857     elif label.valign == ALIGN_BOTTOM:
858     y = y - height
859     elif label.valign == ALIGN_CENTER:
860     y = y - height/2
861     if x <= px < x + width and y <= py <= y + height:
862     return None, i
863 bh 43
864 bh 246 if searched_layer:
865     layers = [searched_layer]
866 bh 43 else:
867     layers = self.map.Layers()
868    
869 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
870     layer = layers[layer_index]
871    
872     # search only in visible layers
873     if not layer.Visible():
874     continue
875    
876 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
877 jonathan 610 is not Color.Transparent
878 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
879 jonathan 610 is not Color.Transparent
880 bh 246
881 bh 6 layer_proj = layer.projection
882     if layer_proj is not None:
883     inverse = layer_proj.Inverse
884     else:
885     inverse = None
886 bh 246
887 bh 6 shapetype = layer.ShapeType()
888    
889     select_shape = -1
890 bh 159
891 bh 301 # Determine the ids of the shapes that overlap a tiny area
892     # around the point. For layers containing points we have to
893     # choose a larger size of the box we're testing agains so
894     # that we take the size of the markers into account
895     # FIXME: Once the markers are more flexible this part has to
896     # become more flexible too, of course
897     if shapetype == SHAPETYPE_POINT:
898     box = self.unprojected_rect_around_point(px, py, 5)
899     else:
900     box = self.unprojected_rect_around_point(px, py, 1)
901 bh 159 shape_ids = layer.ShapesInRegion(box)
902     shape_ids.reverse()
903    
904 bh 6 if shapetype == SHAPETYPE_POLYGON:
905 bh 159 for i in shape_ids:
906 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
907     i,
908     filled, stroked,
909     map_proj, layer_proj,
910     scale, -scale, offx, offy,
911     px, py)
912     if result:
913     select_shape = i
914     break
915     elif shapetype == SHAPETYPE_ARC:
916 bh 159 for i in shape_ids:
917 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
918     i, 0, 1,
919     map_proj, layer_proj,
920     scale, -scale, offx, offy,
921     px, py)
922     if result < 0:
923     select_shape = i
924     break
925     elif shapetype == SHAPETYPE_POINT:
926 bh 159 for i in shape_ids:
927 bh 6 shape = layer.Shape(i)
928     x, y = shape.Points()[0]
929     if inverse:
930     x, y = inverse(x, y)
931     if forward:
932     x, y = forward(x, y)
933     x = x * scale + offx
934     y = -y * scale + offy
935     if hypot(px - x, py - y) < 5:
936     select_shape = i
937     break
938    
939     if select_shape >= 0:
940     return layer, select_shape
941     return None, None
942    
943 bh 246 def SelectShapeAt(self, x, y, layer = None):
944     """\
945     Select and return the shape and its layer at window position (x, y)
946    
947     If layer is given, only search in that layer. If no layer is
948     given, search through all layers.
949    
950     Return a tuple (layer, shapeid). If no shape is found, return
951     (None, None).
952     """
953     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
954 bh 43 # If layer is None, then shape will also be None. We don't want
955     # to deselect the currently selected layer, so we simply select
956     # the already selected layer again.
957     if layer is None:
958 bh 535 layer = self.selection.SelectedLayer()
959     shapes = []
960     else:
961     shapes = [shape]
962     self.selection.SelectShapes(layer, shapes)
963 bh 246 return result
964 bh 6
965     def LabelShapeAt(self, x, y):
966 bh 295 """Add or remove a label at window position x, y.
967    
968     If there's a label at the given position, remove it. Otherwise
969     determine the shape at the position, run the label dialog and
970     unless the user cancels the dialog, add a laber.
971     """
972 bh 6 ox = x; oy = y
973     label_layer = self.map.LabelLayer()
974     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
975     if layer is None and shape_index is not None:
976     # a label was selected
977     label_layer.RemoveLabel(shape_index)
978     elif layer is not None:
979     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
980     if text:
981     proj = self.map.projection
982     if proj is not None:
983     map_proj = proj
984     else:
985     map_proj = None
986     proj = layer.projection
987     if proj is not None:
988     layer_proj = proj
989     else:
990     layer_proj = None
991    
992     shapetype = layer.ShapeType()
993     if shapetype == SHAPETYPE_POLYGON:
994     x, y = shape_centroid(layer.shapefile.cobject(),
995     shape_index,
996     map_proj, layer_proj, 1, 1, 0, 0)
997     if map_proj is not None:
998     x, y = map_proj.Inverse(x, y)
999     else:
1000     shape = layer.Shape(shape_index)
1001     if shapetype == SHAPETYPE_POINT:
1002     x, y = shape.Points()[0]
1003     else:
1004     # assume SHAPETYPE_ARC
1005     points = shape.Points()
1006     x, y = points[len(points) / 2]
1007     if layer_proj is not None:
1008     x, y = layer_proj.Inverse(x, y)
1009     if shapetype == SHAPETYPE_POINT:
1010     halign = ALIGN_LEFT
1011     valign = ALIGN_CENTER
1012     elif shapetype == SHAPETYPE_POLYGON:
1013     halign = ALIGN_CENTER
1014     valign = ALIGN_CENTER
1015     elif shapetype == SHAPETYPE_ARC:
1016     halign = ALIGN_LEFT
1017     valign = ALIGN_CENTER
1018     label_layer.AddLabel(x, y, text,
1019     halign = halign, valign = valign)
1020 frank 910
1021     def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):
1022     """Calculate dimensions to transform canvas content to output device."""
1023     width, height = device_extend
1024    
1025     # Only 80 % of the with are available for the map
1026     width = width * 0.8
1027    
1028     # Define the distance of the map from DC border
1029     distance = 20
1030    
1031     if height < width:
1032     # landscape
1033     map_height = height - 2*distance
1034     map_width = map_height
1035     else:
1036     # portrait, recalibrate width (usually the legend width is too
1037     # small
1038     width = width * 0.9
1039     map_height = width - 2*distance
1040     map_width = map_height
1041    
1042     mapregion = (distance, distance,
1043     distance+map_width, distance+map_height)
1044    
1045     canvas_width, canvas_height = canvas_size
1046    
1047     scalex = map_width / (canvas_width/canvas_scale)
1048     scaley = map_height / (canvas_height/canvas_scale)
1049     scale = min(scalex, scaley)
1050     canvas_offx, canvas_offy = canvas_offset
1051     offx = scale*canvas_offx/canvas_scale
1052     offy = scale*canvas_offy/canvas_scale
1053    
1054     return scale, (offx, offy), mapregion

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26