/[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 940 - (hide annotations)
Tue May 20 15:25:33 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 35965 byte(s)
(MapCanvas.set_view_transform): Try to limit
        how small the scale can get. This still needs more testing.

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