/[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 799 - (hide annotations)
Wed Apr 30 17:02:21 2003 UTC (21 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 30559 byte(s)
(MapCanvas.OnPaint): Add a try/except block
        around the redraw routine to try to catch problems that the user
        may create by selecting invalid projections for the data set and
        map. Clears the display if there are any problems and prints the
        error.
(MapCanvas.do_redraw): Use DC.Clear() instead of drawing a filled
        rectangle.

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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26