/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 565 - (show annotations)
Wed Mar 26 11:07:40 2003 UTC (21 years, 11 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 30280 byte(s)
(MapCanves.SetMap): Listen for LAYER_CHANGED, not LAYER_LEGEND_CHANGED,messages.

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