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

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26