/[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 1271 - (hide annotations)
Fri Jun 20 16:43:04 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: 39281 byte(s)
(MapCanvas.OnPaint): Call wxYield after
        turning on the busy cursor to allow the system to change the
        cursor before we begin painting. This fixes a problem that
        was occuring only under GTK. Fixes RTbug #1840.

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