/[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 295 - (hide annotations)
Fri Aug 30 10:39:17 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: 28662 byte(s)
Add some more 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 bh 295 """Return the map displayed by this canvas"""
416 bh 6 return self.map
417    
418     def redraw(self, *args):
419     self.Refresh(0)
420    
421 bh 125 def full_redraw(self, *args):
422     self.bitmap = None
423     self.redraw()
424    
425 bh 6 def projection_changed(self, *args):
426     self.FitMapToWindow()
427 bh 125 self.full_redraw()
428 bh 6
429     def set_view_transform(self, scale, offset):
430     self.scale = scale
431     self.offset = offset
432 bh 125 self.full_redraw()
433 bh 6
434     def proj_to_win(self, x, y):
435     """\
436     Return the point in window coords given by projected coordinates x y
437     """
438     offx, offy = self.offset
439     return (self.scale * x + offx, -self.scale * y + offy)
440    
441     def win_to_proj(self, x, y):
442     """\
443     Return the point in projected coordinates given by window coords x y
444     """
445     offx, offy = self.offset
446     return ((x - offx) / self.scale, (offy - y) / self.scale)
447    
448     def FitRectToWindow(self, rect):
449 bh 293 """Fit the rectangular region given by rect into the window.
450    
451     Set scale so that rect (in projected coordinates) just fits into
452     the window and center it.
453     """
454 bh 6 width, height = self.GetSizeTuple()
455     llx, lly, urx, ury = rect
456 bh 45 if llx == urx or lly == ury:
457     # zero with or zero height. Do Nothing
458     return
459 bh 6 scalex = width / (urx - llx)
460     scaley = height / (ury - lly)
461     scale = min(scalex, scaley)
462     offx = 0.5 * (width - (urx + llx) * scale)
463     offy = 0.5 * (height + (ury + lly) * scale)
464     self.set_view_transform(scale, (offx, offy))
465    
466     def FitMapToWindow(self):
467 bh 293 """Fit the map to the window
468    
469     Set the scale so that the map fits exactly into the window and
470     center it in the window.
471 bh 6 """
472     bbox = self.map.ProjectedBoundingBox()
473     if bbox is not None:
474     self.FitRectToWindow(bbox)
475    
476 bh 57 def ZoomFactor(self, factor, center = None):
477     """Multiply the zoom by factor and center on center.
478    
479     The optional parameter center is a point in window coordinates
480     that should be centered. If it is omitted, it defaults to the
481     center of the window
482     """
483 bh 6 width, height = self.GetSizeTuple()
484     scale = self.scale * factor
485     offx, offy = self.offset
486 bh 57 if center is not None:
487     cx, cy = center
488     else:
489     cx = width / 2
490     cy = height / 2
491     offset = (factor * (offx - cx) + width / 2,
492     factor * (offy - cy) + height / 2)
493 bh 6 self.set_view_transform(scale, offset)
494    
495     def ZoomOutToRect(self, rect):
496 bh 293 """Zoom out to fit the currently visible region into rect.
497 bh 6
498 bh 293 The rect parameter is given in window coordinates
499     """
500 bh 6 # determine the bbox of the displayed region in projected
501     # coordinates
502     width, height = self.GetSizeTuple()
503     llx, lly = self.win_to_proj(0, height - 1)
504     urx, ury = self.win_to_proj(width - 1, 0)
505    
506     sx, sy, ex, ey = rect
507     scalex = (ex - sx) / (urx - llx)
508     scaley = (ey - sy) / (ury - lly)
509     scale = min(scalex, scaley)
510    
511     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
512     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
513     self.set_view_transform(scale, (offx, offy))
514    
515     def Translate(self, dx, dy):
516 bh 295 """Move the map by dx, dy pixels"""
517 bh 6 offx, offy = self.offset
518     self.set_view_transform(self.scale, (offx + dx, offy + dy))
519    
520     def ZoomInTool(self):
521 bh 295 """Start the zoom in tool"""
522 bh 6 self.tool = ZoomInTool(self)
523    
524     def ZoomOutTool(self):
525 bh 295 """Start the zoom out tool"""
526 bh 6 self.tool = ZoomOutTool(self)
527    
528     def PanTool(self):
529 bh 295 """Start the pan tool"""
530 bh 6 self.tool = PanTool(self)
531    
532     def IdentifyTool(self):
533 bh 295 """Start the identify tool"""
534 bh 6 self.tool = IdentifyTool(self)
535    
536     def LabelTool(self):
537 bh 295 """Start the label tool"""
538 bh 6 self.tool = LabelTool(self)
539    
540     def CurrentTool(self):
541 bh 295 """Return the name of the current tool or None if no tool is active"""
542 bh 6 return self.tool and self.tool.Name() or None
543    
544 bh 122 def CurrentPosition(self):
545     """Return current position of the mouse in projected coordinates.
546    
547     The result is a 2-tuple of floats with the coordinates. If the
548     mouse is not in the window, the result is None.
549     """
550     if self.current_position is not None:
551     x, y = self.current_position
552     return self.win_to_proj(x, y)
553     else:
554     return None
555    
556     def set_current_position(self, event):
557     """Set the current position from event
558    
559     Should be called by all events that contain mouse positions
560     especially EVT_MOTION. The event paramete may be None to
561     indicate the the pointer left the window.
562     """
563     if event is not None:
564     self.current_position = (event.m_x, event.m_y)
565     else:
566     self.current_position = None
567     self.issue(VIEW_POSITION)
568    
569 bh 6 def OnLeftDown(self, event):
570 bh 122 self.set_current_position(event)
571 bh 6 if self.tool is not None:
572     self.drag_dc = wxClientDC(self)
573     self.drag_dc.SetLogicalFunction(wxINVERT)
574     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
575     self.CaptureMouse()
576     self.tool.MouseDown(event)
577     self.tool.Show(self.drag_dc)
578     self.dragging = 1
579 bh 246
580 bh 6 def OnLeftUp(self, event):
581 bh 122 self.set_current_position(event)
582 bh 6 if self.dragging:
583 bh 261 self.ReleaseMouse()
584 bh 6 self.tool.Hide(self.drag_dc)
585     self.tool.MouseUp(event)
586     self.drag_dc = None
587     self.dragging = 0
588    
589     def OnMotion(self, event):
590 bh 122 self.set_current_position(event)
591 bh 6 if self.dragging:
592     self.tool.Hide(self.drag_dc)
593     self.tool.MouseMove(event)
594     self.tool.Show(self.drag_dc)
595    
596 bh 122 def OnLeaveWindow(self, event):
597     self.set_current_position(None)
598    
599 bh 6 def OnIdle(self, event):
600     if self.redraw_on_idle:
601     self.do_redraw()
602     self.redraw_on_idle = 0
603    
604 bh 125 def OnSize(self, event):
605     # the window's size has changed. We have to get a new bitmap. If
606     # we want to be clever we could try to get by without throwing
607     # everything away. E.g. when the window gets smaller, we could
608     # either keep the bitmap or create the new one from the old one.
609     # Even when the window becomes larger some parts of the bitmap
610     # could be reused.
611     self.full_redraw()
612    
613 bh 6 def shape_selected(self, layer, shape):
614 bh 174 """Redraw the map.
615 bh 6
616 bh 174 Receiver for the SELECTED_SHAPE messages. Try to redraw only
617     when necessary.
618     """
619     # A redraw is necessary when the display has to change, which
620     # means that either the status changes from having no selection
621     # to having a selection shape or vice versa, or when the fact
622     # whether there is a selection at all doesn't change, when the
623     # shape which is selected has changed (which means that layer or
624     # shapeid changes).
625     if ((shape is not None or self.last_selected_shape is not None)
626     and (shape != self.last_selected_shape
627     or layer != self.last_selected_layer)):
628     self.full_redraw()
629 bh 176
630     # remember the selection so we can compare when it changes again.
631 bh 174 self.last_selected_layer = layer
632     self.last_selected_shape = shape
633    
634 bh 159 def unprojected_rect_around_point(self, x, y):
635     """return a rect a few pixels around (x, y) in unprojected corrdinates
636    
637     The return value is a tuple (minx, miny, maxx, maxy) suitable a
638     parameter to a layer's ShapesInRegion method.
639     """
640     map_proj = self.map.projection
641     if map_proj is not None:
642     inverse = map_proj.Inverse
643     else:
644     inverse = None
645    
646     xs = []
647     ys = []
648     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
649     px, py = self.win_to_proj(x + dx, y + dy)
650     if inverse:
651     px, py = inverse(px, py)
652     xs.append(px)
653     ys.append(py)
654     return (min(xs), min(ys), max(xs), max(ys))
655    
656 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
657 bh 43 """Determine the shape at point px, py in window coords
658    
659     Return the shape and the corresponding layer as a tuple (layer,
660     shape).
661    
662     If the optional parameter select_labels is true (default false)
663     search through the labels. If a label is found return it's index
664     as the shape and None as the layer.
665    
666 bh 246 If the optional parameter searched_layer is given (or not None
667     which it defaults to), only search in that layer.
668 bh 43 """
669 bh 6 map_proj = self.map.projection
670     if map_proj is not None:
671     forward = map_proj.Forward
672     else:
673     forward = None
674    
675     scale = self.scale
676     offx, offy = self.offset
677    
678 bh 159 box = self.unprojected_rect_around_point(px, py)
679    
680 bh 6 if select_labels:
681     labels = self.map.LabelLayer().Labels()
682 bh 246
683 bh 6 if labels:
684     dc = wxClientDC(self)
685     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
686     dc.SetFont(font)
687 bh 60 for i in range(len(labels) - 1, -1, -1):
688 bh 6 label = labels[i]
689     x = label.x
690     y = label.y
691     text = label.text
692     if forward:
693     x, y = forward(x, y)
694     x = x * scale + offx
695     y = -y * scale + offy
696     width, height = dc.GetTextExtent(text)
697     if label.halign == ALIGN_LEFT:
698     # nothing to be done
699     pass
700     elif label.halign == ALIGN_RIGHT:
701     x = x - width
702     elif label.halign == ALIGN_CENTER:
703     x = x - width/2
704     if label.valign == ALIGN_TOP:
705     # nothing to be done
706     pass
707     elif label.valign == ALIGN_BOTTOM:
708     y = y - height
709     elif label.valign == ALIGN_CENTER:
710     y = y - height/2
711     if x <= px < x + width and y <= py <= y + height:
712     return None, i
713 bh 43
714 bh 246 if searched_layer:
715     layers = [searched_layer]
716 bh 43 else:
717     layers = self.map.Layers()
718    
719 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
720     layer = layers[layer_index]
721    
722     # search only in visible layers
723     if not layer.Visible():
724     continue
725    
726     filled = layer.fill is not None
727     stroked = layer.stroke is not None
728 bh 246
729 bh 6 layer_proj = layer.projection
730     if layer_proj is not None:
731     inverse = layer_proj.Inverse
732     else:
733     inverse = None
734 bh 246
735 bh 6 shapetype = layer.ShapeType()
736    
737     select_shape = -1
738 bh 159
739     shape_ids = layer.ShapesInRegion(box)
740     shape_ids.reverse()
741    
742 bh 6 if shapetype == SHAPETYPE_POLYGON:
743 bh 159 for i in shape_ids:
744 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
745     i,
746     filled, stroked,
747     map_proj, layer_proj,
748     scale, -scale, offx, offy,
749     px, py)
750     if result:
751     select_shape = i
752     break
753     elif shapetype == SHAPETYPE_ARC:
754 bh 159 for i in shape_ids:
755 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
756     i, 0, 1,
757     map_proj, layer_proj,
758     scale, -scale, offx, offy,
759     px, py)
760     if result < 0:
761     select_shape = i
762     break
763     elif shapetype == SHAPETYPE_POINT:
764 bh 159 for i in shape_ids:
765 bh 6 shape = layer.Shape(i)
766     x, y = shape.Points()[0]
767     if inverse:
768     x, y = inverse(x, y)
769     if forward:
770     x, y = forward(x, y)
771     x = x * scale + offx
772     y = -y * scale + offy
773     if hypot(px - x, py - y) < 5:
774     select_shape = i
775     break
776    
777     if select_shape >= 0:
778     return layer, select_shape
779     return None, None
780    
781 bh 246 def SelectShapeAt(self, x, y, layer = None):
782     """\
783     Select and return the shape and its layer at window position (x, y)
784    
785     If layer is given, only search in that layer. If no layer is
786     given, search through all layers.
787    
788     Return a tuple (layer, shapeid). If no shape is found, return
789     (None, None).
790     """
791     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
792 bh 43 # If layer is None, then shape will also be None. We don't want
793     # to deselect the currently selected layer, so we simply select
794     # the already selected layer again.
795     if layer is None:
796     layer = self.interactor.SelectedLayer()
797 bh 6 self.interactor.SelectLayerAndShape(layer, shape)
798 bh 246 return result
799 bh 6
800     def LabelShapeAt(self, x, y):
801 bh 295 """Add or remove a label at window position x, y.
802    
803     If there's a label at the given position, remove it. Otherwise
804     determine the shape at the position, run the label dialog and
805     unless the user cancels the dialog, add a laber.
806     """
807 bh 6 ox = x; oy = y
808     label_layer = self.map.LabelLayer()
809     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
810     if layer is None and shape_index is not None:
811     # a label was selected
812     label_layer.RemoveLabel(shape_index)
813     elif layer is not None:
814     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
815     if text:
816     proj = self.map.projection
817     if proj is not None:
818     map_proj = proj
819     else:
820     map_proj = None
821     proj = layer.projection
822     if proj is not None:
823     layer_proj = proj
824     else:
825     layer_proj = None
826    
827     shapetype = layer.ShapeType()
828     if shapetype == SHAPETYPE_POLYGON:
829     x, y = shape_centroid(layer.shapefile.cobject(),
830     shape_index,
831     map_proj, layer_proj, 1, 1, 0, 0)
832     if map_proj is not None:
833     x, y = map_proj.Inverse(x, y)
834     else:
835     shape = layer.Shape(shape_index)
836     if shapetype == SHAPETYPE_POINT:
837     x, y = shape.Points()[0]
838     else:
839     # assume SHAPETYPE_ARC
840     points = shape.Points()
841     x, y = points[len(points) / 2]
842     if layer_proj is not None:
843     x, y = layer_proj.Inverse(x, y)
844     if shapetype == SHAPETYPE_POINT:
845     halign = ALIGN_LEFT
846     valign = ALIGN_CENTER
847     elif shapetype == SHAPETYPE_POLYGON:
848     halign = ALIGN_CENTER
849     valign = ALIGN_CENTER
850     elif shapetype == SHAPETYPE_ARC:
851     halign = ALIGN_LEFT
852     valign = ALIGN_CENTER
853     label_layer.AddLabel(x, y, text,
854     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