/[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 433 - (hide annotations)
Mon Feb 24 18:47:49 2003 UTC (22 years ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28758 byte(s)
(MapCanvas.find_shape_at): Use method calls
        instead of accessing now non-existent class variables.

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