/[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 293 - (hide annotations)
Fri Aug 30 10:18:50 2002 UTC (22 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28035 byte(s)
Add/update some doc-strings.

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