/[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 910 - (hide annotations)
Fri May 16 16:23:43 2003 UTC (21 years, 9 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 35859 byte(s)
(MapPrintout.__init__): Enhanced parameter set to fullfil information
	needed for PrinterRenderer.
(MapCanvas.Export): New. Export Map (currently only to WMF on Win32).
(MapCanvas.Print): Adapted to new MapPrintout.
(OutputTransform): General calculations to transform from canvas
	coordinates to export/printing devices.

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    
38     from Thuban.Model.messages import MAP_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 bh 6 return wx.true
276    
277 bh 246
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     # scale and offset describe the transformation from projected
307     # coordinates to window coordinates.
308 bh 6 self.scale = 1.0
309     self.offset = (0, 0)
310 bh 125
311     # whether the user is currently dragging the mouse, i.e. moving
312     # the mouse while pressing a mouse button
313 bh 6 self.dragging = 0
314 bh 125
315     # the currently active tool
316 bh 6 self.tool = None
317 bh 125
318     # The current mouse position of the last OnMotion event or None
319     # if the mouse is outside the window.
320     self.current_position = None
321    
322     # the bitmap serving as backing store
323     self.bitmap = None
324    
325 bh 535 # the selection
326     self.selection = Selection()
327     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
328 bh 125
329 bh 174 # keep track of which layers/shapes are selected to make sure we
330     # only redraw when necessary
331     self.last_selected_layer = None
332     self.last_selected_shape = None
333    
334 bh 125 # subscribe the WX events we're interested in
335 bh 6 EVT_PAINT(self, self.OnPaint)
336     EVT_LEFT_DOWN(self, self.OnLeftDown)
337     EVT_LEFT_UP(self, self.OnLeftUp)
338     EVT_MOTION(self, self.OnMotion)
339 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
340 bh 125 wx.EVT_SIZE(self, self.OnSize)
341 bh 6
342 bh 122 def __del__(self):
343     wxWindow.__del__(self)
344     Publisher.__del__(self)
345    
346 bh 535 def Subscribe(self, channel, *args):
347     """Extend the inherited method to handle delegated messages.
348    
349     If channel is one of the delegated messages call the appropriate
350     object's Subscribe method. Otherwise just call the inherited
351     method.
352     """
353     if channel in self.delegated_messages:
354     object = getattr(self, self.delegated_messages[channel])
355     object.Subscribe(channel, *args)
356     else:
357     Publisher.Subscribe(self, channel, *args)
358    
359     def Unsubscribe(self, channel, *args):
360     """Extend the inherited method to handle delegated messages.
361    
362     If channel is one of the delegated messages call the appropriate
363     object's Unsubscribe method. Otherwise just call the inherited
364     method.
365     """
366     if channel in self.delegated_messages:
367     object = getattr(self, self.delegated_messages[channel])
368     object.Unsubscribe(channel, *args)
369     else:
370     Publisher.Unsubscribe(self, channel, *args)
371    
372     def __getattr__(self, attr):
373     if attr in self.delegated_methods:
374     return getattr(getattr(self, self.delegated_methods[attr]), attr)
375     raise AttributeError(attr)
376    
377 bh 6 def OnPaint(self, event):
378     dc = wxPaintDC(self)
379 jonathan 799 clear = self.map is None or not self.map.HasLayers()
380    
381 jonathan 822 #wxBeginBusyCursor()
382    
383 jonathan 799 if not clear:
384     try:
385     self.do_redraw()
386     except:
387     print "Error during drawing:", sys.exc_info()[0]
388     clear = True
389    
390     if clear:
391 bh 57 # If we've got no map or if the map is empty, simply clear
392     # the screen.
393 bh 246
394 bh 57 # 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 bh 246 dc.Clear()
400 bh 57 dc.EndDrawing()
401 bh 6
402 jonathan 822 #wxEndBusyCursor()
403    
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     if hasattr(self, "export_path"):
451     export_path = self.export_path
452     else:
453     export_path="."
454     dlg = wxFileDialog(self, _("Export Map"), export_path, "",
455     "Enhanced Metafile (*.wmf)|*.wmf",
456     wxSAVE|wxOVERWRITE_PROMPT)
457     if dlg.ShowModal() == wxID_OK:
458     self.export_path = os.path.dirname(dlg.GetPath())
459     dc = wxMetaFileDC(dlg.GetPath())
460    
461     scale, offset, mapregion = OutputTransform(self.scale,
462     self.canvas.offset,
463     self.GetSizeTuple(),
464     dc.GetSizeTuple())
465    
466     selected_layer = self.selection.SelectedLayer()
467     selected_shapes = self.selection.SelectedShapes()
468    
469     renderer = ExportRenderer(dc, scale, offset)
470    
471     # Pass the entire bitmap as update region to the renderer.
472     # We're redrawing the whole bitmap, after all.
473     width, height = self.GetSizeTuple()
474     renderer.RenderMap(self.map,
475     (0,0,
476     (width/self.scale)*scale,
477     (height/self.scale)*scale),
478     mapregion,
479     selected_layer, selected_shapes)
480     dc.EndDrawing()
481     dc.Close()
482     dlg.Destroy()
483    
484 bh 6 def Print(self):
485     printer = wx.wxPrinter()
486 frank 910 width, height = self.GetSizeTuple()
487     selected_layer = self.selection.SelectedLayer()
488     selected_shapes = self.selection.SelectedShapes()
489    
490     printout = MapPrintout(self, self.map, (0, 0, width, height),
491     selected_layer, selected_shapes)
492 bh 6 printer.Print(self, printout, wx.true)
493     printout.Destroy()
494 bh 246
495 bh 6 def SetMap(self, map):
496 jonathan 565 redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
497 bh 6 LAYER_VISIBILITY_CHANGED)
498     if self.map is not None:
499     for channel in redraw_channels:
500 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
501 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
502     self.projection_changed)
503     self.map = map
504 bh 535 self.selection.ClearSelection()
505 bh 6 if self.map is not None:
506     for channel in redraw_channels:
507 bh 125 self.map.Subscribe(channel, self.full_redraw)
508 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
509     self.FitMapToWindow()
510 bh 57 # force a redraw. If map is not empty, it's already been called
511     # by FitMapToWindow but if map is empty it hasn't been called
512     # yet so we have to explicitly call it.
513 bh 125 self.full_redraw()
514 bh 6
515     def Map(self):
516 bh 295 """Return the map displayed by this canvas"""
517 bh 6 return self.map
518    
519     def redraw(self, *args):
520     self.Refresh(0)
521    
522 bh 125 def full_redraw(self, *args):
523     self.bitmap = None
524     self.redraw()
525    
526 bh 6 def projection_changed(self, *args):
527     self.FitMapToWindow()
528 bh 125 self.full_redraw()
529 bh 6
530     def set_view_transform(self, scale, offset):
531     self.scale = scale
532     self.offset = offset
533 bh 125 self.full_redraw()
534 frank 855 self.issue(SCALE_CHANGED, scale)
535 bh 6
536     def proj_to_win(self, x, y):
537     """\
538     Return the point in window coords given by projected coordinates x y
539     """
540     offx, offy = self.offset
541     return (self.scale * x + offx, -self.scale * y + offy)
542    
543     def win_to_proj(self, x, y):
544     """\
545     Return the point in projected coordinates given by window coords x y
546     """
547     offx, offy = self.offset
548     return ((x - offx) / self.scale, (offy - y) / self.scale)
549    
550     def FitRectToWindow(self, rect):
551 bh 293 """Fit the rectangular region given by rect into the window.
552 bh 535
553 bh 293 Set scale so that rect (in projected coordinates) just fits into
554     the window and center it.
555     """
556 bh 6 width, height = self.GetSizeTuple()
557     llx, lly, urx, ury = rect
558 bh 45 if llx == urx or lly == ury:
559 jonathan 799 # zero width or zero height. Do Nothing
560 bh 45 return
561 bh 6 scalex = width / (urx - llx)
562     scaley = height / (ury - lly)
563     scale = min(scalex, scaley)
564     offx = 0.5 * (width - (urx + llx) * scale)
565     offy = 0.5 * (height + (ury + lly) * scale)
566     self.set_view_transform(scale, (offx, offy))
567    
568     def FitMapToWindow(self):
569 bh 293 """Fit the map to the window
570 bh 535
571 bh 293 Set the scale so that the map fits exactly into the window and
572     center it in the window.
573 bh 6 """
574     bbox = self.map.ProjectedBoundingBox()
575     if bbox is not None:
576     self.FitRectToWindow(bbox)
577    
578 jonathan 822 def FitLayerToWindow(self, layer):
579     """Fit the given layer to the window.
580    
581     Set the scale so that the layer fits exactly into the window and
582     center it in the window.
583     """
584    
585     bbox = layer.LatLongBoundingBox()
586     if bbox is not None:
587     proj = self.map.GetProjection()
588     if proj is not None:
589     bbox = proj.ForwardBBox(bbox)
590    
591 jonathan 824 if bbox is not None:
592     self.FitRectToWindow(bbox)
593    
594 jonathan 831 def FitSelectedToWindow(self):
595     layer = self.selection.SelectedLayer()
596     shapes = self.selection.SelectedShapes()
597    
598     bbox = layer.ShapesBoundingBox(shapes)
599     if bbox is not None:
600     proj = self.map.GetProjection()
601     if proj is not None:
602     bbox = proj.ForwardBBox(bbox)
603    
604     if bbox is not None:
605     self.FitRectToWindow(bbox)
606    
607 bh 57 def ZoomFactor(self, factor, center = None):
608     """Multiply the zoom by factor and center on center.
609    
610     The optional parameter center is a point in window coordinates
611     that should be centered. If it is omitted, it defaults to the
612     center of the window
613     """
614 bh 6 width, height = self.GetSizeTuple()
615     scale = self.scale * factor
616     offx, offy = self.offset
617 bh 57 if center is not None:
618     cx, cy = center
619     else:
620     cx = width / 2
621     cy = height / 2
622     offset = (factor * (offx - cx) + width / 2,
623     factor * (offy - cy) + height / 2)
624 bh 6 self.set_view_transform(scale, offset)
625    
626     def ZoomOutToRect(self, rect):
627 bh 293 """Zoom out to fit the currently visible region into rect.
628 bh 6
629 bh 293 The rect parameter is given in window coordinates
630     """
631 bh 6 # determine the bbox of the displayed region in projected
632     # coordinates
633     width, height = self.GetSizeTuple()
634     llx, lly = self.win_to_proj(0, height - 1)
635     urx, ury = self.win_to_proj(width - 1, 0)
636    
637     sx, sy, ex, ey = rect
638     scalex = (ex - sx) / (urx - llx)
639     scaley = (ey - sy) / (ury - lly)
640     scale = min(scalex, scaley)
641    
642     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
643     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
644     self.set_view_transform(scale, (offx, offy))
645    
646     def Translate(self, dx, dy):
647 bh 295 """Move the map by dx, dy pixels"""
648 bh 6 offx, offy = self.offset
649     self.set_view_transform(self.scale, (offx + dx, offy + dy))
650    
651 bh 356 def SelectTool(self, tool):
652     """Make tool the active tool.
653    
654     The parameter should be an instance of Tool or None to indicate
655     that no tool is active.
656     """
657     self.tool = tool
658    
659 bh 6 def ZoomInTool(self):
660 bh 295 """Start the zoom in tool"""
661 bh 356 self.SelectTool(ZoomInTool(self))
662 bh 6
663     def ZoomOutTool(self):
664 bh 295 """Start the zoom out tool"""
665 bh 356 self.SelectTool(ZoomOutTool(self))
666 bh 6
667     def PanTool(self):
668 bh 295 """Start the pan tool"""
669 bh 356 self.SelectTool(PanTool(self))
670 jonathan 822 #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
671     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
672     #print bmp
673     #img = wxImageFromBitmap(bmp)
674     #print img
675     #cur = wxCursor(img)
676     #print cur
677     #self.SetCursor(cur)
678 bh 6
679     def IdentifyTool(self):
680 bh 295 """Start the identify tool"""
681 bh 356 self.SelectTool(IdentifyTool(self))
682 bh 6
683     def LabelTool(self):
684 bh 295 """Start the label tool"""
685 bh 356 self.SelectTool(LabelTool(self))
686 bh 6
687     def CurrentTool(self):
688 bh 295 """Return the name of the current tool or None if no tool is active"""
689 bh 6 return self.tool and self.tool.Name() or None
690    
691 bh 122 def CurrentPosition(self):
692     """Return current position of the mouse in projected coordinates.
693    
694     The result is a 2-tuple of floats with the coordinates. If the
695     mouse is not in the window, the result is None.
696     """
697     if self.current_position is not None:
698     x, y = self.current_position
699     return self.win_to_proj(x, y)
700     else:
701     return None
702    
703     def set_current_position(self, event):
704     """Set the current position from event
705    
706     Should be called by all events that contain mouse positions
707     especially EVT_MOTION. The event paramete may be None to
708     indicate the the pointer left the window.
709     """
710     if event is not None:
711     self.current_position = (event.m_x, event.m_y)
712     else:
713     self.current_position = None
714     self.issue(VIEW_POSITION)
715    
716 bh 6 def OnLeftDown(self, event):
717 bh 122 self.set_current_position(event)
718 bh 6 if self.tool is not None:
719     self.drag_dc = wxClientDC(self)
720     self.drag_dc.SetLogicalFunction(wxINVERT)
721     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
722     self.CaptureMouse()
723     self.tool.MouseDown(event)
724     self.tool.Show(self.drag_dc)
725     self.dragging = 1
726 bh 246
727 bh 6 def OnLeftUp(self, event):
728 bh 122 self.set_current_position(event)
729 bh 6 if self.dragging:
730 bh 261 self.ReleaseMouse()
731 bh 404 try:
732     self.tool.Hide(self.drag_dc)
733     self.tool.MouseUp(event)
734     finally:
735     self.drag_dc = None
736     self.dragging = 0
737 bh 6
738     def OnMotion(self, event):
739 bh 122 self.set_current_position(event)
740 bh 6 if self.dragging:
741     self.tool.Hide(self.drag_dc)
742     self.tool.MouseMove(event)
743     self.tool.Show(self.drag_dc)
744    
745 bh 122 def OnLeaveWindow(self, event):
746     self.set_current_position(None)
747    
748 bh 125 def OnSize(self, event):
749     # the window's size has changed. We have to get a new bitmap. If
750     # we want to be clever we could try to get by without throwing
751     # everything away. E.g. when the window gets smaller, we could
752     # either keep the bitmap or create the new one from the old one.
753     # Even when the window becomes larger some parts of the bitmap
754     # could be reused.
755     self.full_redraw()
756 jonathan 799 pass
757 bh 125
758 bh 6 def shape_selected(self, layer, shape):
759 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
760     # The selection object takes care that it only issues
761     # SHAPES_SELECTED messages when the set of selected shapes has
762     # actually changed, so we can do a full redraw unconditionally.
763     # FIXME: We should perhaps try to limit the redraw to the are
764     # actually covered by the shapes before and after the selection
765     # change.
766     self.full_redraw()
767 bh 6
768 bh 301 def unprojected_rect_around_point(self, x, y, dist):
769     """return a rect dist pixels around (x, y) in unprojected corrdinates
770 bh 159
771     The return value is a tuple (minx, miny, maxx, maxy) suitable a
772     parameter to a layer's ShapesInRegion method.
773     """
774     map_proj = self.map.projection
775     if map_proj is not None:
776     inverse = map_proj.Inverse
777     else:
778     inverse = None
779    
780     xs = []
781     ys = []
782     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
783 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
784 bh 159 if inverse:
785     px, py = inverse(px, py)
786     xs.append(px)
787     ys.append(py)
788     return (min(xs), min(ys), max(xs), max(ys))
789    
790 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
791 bh 43 """Determine the shape at point px, py in window coords
792    
793     Return the shape and the corresponding layer as a tuple (layer,
794     shape).
795    
796     If the optional parameter select_labels is true (default false)
797     search through the labels. If a label is found return it's index
798     as the shape and None as the layer.
799    
800 bh 246 If the optional parameter searched_layer is given (or not None
801     which it defaults to), only search in that layer.
802 bh 43 """
803 bh 6 map_proj = self.map.projection
804     if map_proj is not None:
805     forward = map_proj.Forward
806     else:
807     forward = None
808    
809     scale = self.scale
810     offx, offy = self.offset
811    
812     if select_labels:
813     labels = self.map.LabelLayer().Labels()
814 bh 246
815 bh 6 if labels:
816     dc = wxClientDC(self)
817     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
818     dc.SetFont(font)
819 bh 60 for i in range(len(labels) - 1, -1, -1):
820 bh 6 label = labels[i]
821     x = label.x
822     y = label.y
823     text = label.text
824     if forward:
825     x, y = forward(x, y)
826     x = x * scale + offx
827     y = -y * scale + offy
828     width, height = dc.GetTextExtent(text)
829     if label.halign == ALIGN_LEFT:
830     # nothing to be done
831     pass
832     elif label.halign == ALIGN_RIGHT:
833     x = x - width
834     elif label.halign == ALIGN_CENTER:
835     x = x - width/2
836     if label.valign == ALIGN_TOP:
837     # nothing to be done
838     pass
839     elif label.valign == ALIGN_BOTTOM:
840     y = y - height
841     elif label.valign == ALIGN_CENTER:
842     y = y - height/2
843     if x <= px < x + width and y <= py <= y + height:
844     return None, i
845 bh 43
846 bh 246 if searched_layer:
847     layers = [searched_layer]
848 bh 43 else:
849     layers = self.map.Layers()
850    
851 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
852     layer = layers[layer_index]
853    
854     # search only in visible layers
855     if not layer.Visible():
856     continue
857    
858 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
859 jonathan 610 is not Color.Transparent
860 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
861 jonathan 610 is not Color.Transparent
862 bh 246
863 bh 6 layer_proj = layer.projection
864     if layer_proj is not None:
865     inverse = layer_proj.Inverse
866     else:
867     inverse = None
868 bh 246
869 bh 6 shapetype = layer.ShapeType()
870    
871     select_shape = -1
872 bh 159
873 bh 301 # Determine the ids of the shapes that overlap a tiny area
874     # around the point. For layers containing points we have to
875     # choose a larger size of the box we're testing agains so
876     # that we take the size of the markers into account
877     # FIXME: Once the markers are more flexible this part has to
878     # become more flexible too, of course
879     if shapetype == SHAPETYPE_POINT:
880     box = self.unprojected_rect_around_point(px, py, 5)
881     else:
882     box = self.unprojected_rect_around_point(px, py, 1)
883 bh 159 shape_ids = layer.ShapesInRegion(box)
884     shape_ids.reverse()
885    
886 bh 6 if shapetype == SHAPETYPE_POLYGON:
887 bh 159 for i in shape_ids:
888 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
889     i,
890     filled, stroked,
891     map_proj, layer_proj,
892     scale, -scale, offx, offy,
893     px, py)
894     if result:
895     select_shape = i
896     break
897     elif shapetype == SHAPETYPE_ARC:
898 bh 159 for i in shape_ids:
899 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
900     i, 0, 1,
901     map_proj, layer_proj,
902     scale, -scale, offx, offy,
903     px, py)
904     if result < 0:
905     select_shape = i
906     break
907     elif shapetype == SHAPETYPE_POINT:
908 bh 159 for i in shape_ids:
909 bh 6 shape = layer.Shape(i)
910     x, y = shape.Points()[0]
911     if inverse:
912     x, y = inverse(x, y)
913     if forward:
914     x, y = forward(x, y)
915     x = x * scale + offx
916     y = -y * scale + offy
917     if hypot(px - x, py - y) < 5:
918     select_shape = i
919     break
920    
921     if select_shape >= 0:
922     return layer, select_shape
923     return None, None
924    
925 bh 246 def SelectShapeAt(self, x, y, layer = None):
926     """\
927     Select and return the shape and its layer at window position (x, y)
928    
929     If layer is given, only search in that layer. If no layer is
930     given, search through all layers.
931    
932     Return a tuple (layer, shapeid). If no shape is found, return
933     (None, None).
934     """
935     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
936 bh 43 # If layer is None, then shape will also be None. We don't want
937     # to deselect the currently selected layer, so we simply select
938     # the already selected layer again.
939     if layer is None:
940 bh 535 layer = self.selection.SelectedLayer()
941     shapes = []
942     else:
943     shapes = [shape]
944     self.selection.SelectShapes(layer, shapes)
945 bh 246 return result
946 bh 6
947     def LabelShapeAt(self, x, y):
948 bh 295 """Add or remove a label at window position x, y.
949    
950     If there's a label at the given position, remove it. Otherwise
951     determine the shape at the position, run the label dialog and
952     unless the user cancels the dialog, add a laber.
953     """
954 bh 6 ox = x; oy = y
955     label_layer = self.map.LabelLayer()
956     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
957     if layer is None and shape_index is not None:
958     # a label was selected
959     label_layer.RemoveLabel(shape_index)
960     elif layer is not None:
961     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
962     if text:
963     proj = self.map.projection
964     if proj is not None:
965     map_proj = proj
966     else:
967     map_proj = None
968     proj = layer.projection
969     if proj is not None:
970     layer_proj = proj
971     else:
972     layer_proj = None
973    
974     shapetype = layer.ShapeType()
975     if shapetype == SHAPETYPE_POLYGON:
976     x, y = shape_centroid(layer.shapefile.cobject(),
977     shape_index,
978     map_proj, layer_proj, 1, 1, 0, 0)
979     if map_proj is not None:
980     x, y = map_proj.Inverse(x, y)
981     else:
982     shape = layer.Shape(shape_index)
983     if shapetype == SHAPETYPE_POINT:
984     x, y = shape.Points()[0]
985     else:
986     # assume SHAPETYPE_ARC
987     points = shape.Points()
988     x, y = points[len(points) / 2]
989     if layer_proj is not None:
990     x, y = layer_proj.Inverse(x, y)
991     if shapetype == SHAPETYPE_POINT:
992     halign = ALIGN_LEFT
993     valign = ALIGN_CENTER
994     elif shapetype == SHAPETYPE_POLYGON:
995     halign = ALIGN_CENTER
996     valign = ALIGN_CENTER
997     elif shapetype == SHAPETYPE_ARC:
998     halign = ALIGN_LEFT
999     valign = ALIGN_CENTER
1000     label_layer.AddLabel(x, y, text,
1001     halign = halign, valign = valign)
1002 frank 910
1003     def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):
1004     """Calculate dimensions to transform canvas content to output device."""
1005     width, height = device_extend
1006    
1007     # Only 80 % of the with are available for the map
1008     width = width * 0.8
1009    
1010     # Define the distance of the map from DC border
1011     distance = 20
1012    
1013     if height < width:
1014     # landscape
1015     map_height = height - 2*distance
1016     map_width = map_height
1017     else:
1018     # portrait, recalibrate width (usually the legend width is too
1019     # small
1020     width = width * 0.9
1021     map_height = width - 2*distance
1022     map_width = map_height
1023    
1024     mapregion = (distance, distance,
1025     distance+map_width, distance+map_height)
1026    
1027     canvas_width, canvas_height = canvas_size
1028    
1029     scalex = map_width / (canvas_width/canvas_scale)
1030     scaley = map_height / (canvas_height/canvas_scale)
1031     scale = min(scalex, scaley)
1032     canvas_offx, canvas_offy = canvas_offset
1033     offx = scale*canvas_offx/canvas_scale
1034     offy = scale*canvas_offy/canvas_scale
1035    
1036     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