/[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 1219 - (hide annotations)
Mon Jun 16 17:42:54 2003 UTC (21 years, 8 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 38063 byte(s)
Update to the layer interface: Direct access to the table,
shapetable, shapefile and filename attributes is now actively
deprecated by issuing deprecation warnings for all places where
this happens.

* Thuban/Model/layer.py (Layer.__getattr__): New. Implement access
to the instance variables table, shapetable, shapefile and
filename via __getattr__ so that we can issue a deprecation
warning.
(Layer.SetShapeStore): Don't set the deprecated instance variables
any more
(Layer.SetShapeStore): Don't use deprecated layer instance
variables
(Layer.Destroy): No need to explicitly remove the instance
variables any more
(Layer.GetFieldType, Layer.Shape): Don't use deprecated layer
instance variables

* Thuban/UI/classgen.py (ClassGenDialog.__init__)
(GenUniformPanel._OnRetrieve, GenUniquePanel._OnRetrieve)
(GenQuantilesPanel.GetList, GenQuantilesPanel.OnRetrieve): Don't
use deprecated layer instance variables

* Thuban/UI/classifier.py (Classifier.__init__): Don't use
deprecated layer instance variables

* Thuban/UI/identifyview.py (IdentifyListCtrl.selected_shape)
(IdentifyGridCtrl.selected_shape): Don't set the deprecated layer
instance variables

* Thuban/UI/tableview.py (LayerTableGrid.select_shapes): Don't use
deprecated layer instance variables

* Thuban/UI/mainwindow.py (MainWindow.LayerShowTable): Don't use
deprecated layer instance variables

* Thuban/Model/save.py (SessionSaver.write_layer): Don't use
deprecated layer instance variables

* Thuban/UI/renderer.py (MapRenderer.draw_shape_layer)
(MapRenderer.polygon_render_param): Don't use deprecated layer instance
variables

* test/runtests.py (main): Turn Thuban's deprecation warnings into
errors so that they're cought by the tests

* test/test_load.py (TestSingleLayer.test): Don't use deprecated
layer instance variables

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