/[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 1285 - (hide annotations)
Mon Jun 23 10:30:53 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 39104 byte(s)
(MapCanvas.OnPaint): Call wxBeginBusyCursor()
        directly to avoid the wxSafeYield() call which generates an
        OnPaint event causing infinite recursion. Don't try to catch
        exception anymore. This was for before there were limits on map
        scaling.

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