/[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 296 - (show annotations)
Fri Aug 30 16:10:45 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: 28182 byte(s)
* Thuban/UI/view.py (MapCanvas.__init__, MapCanvas.OnPaint)
(MapCanvas.do_redraw): Get rid of the unused update_region
instance variable

1 # Copyright (c) 2001, 2002 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 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 from Thuban.Lib.connector import Publisher
33
34 from renderer import ScreenRenderer, PrinterRender
35
36 import labeldialog
37
38 from messages import SELECTED_SHAPE, VIEW_POSITION
39
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 sx, sy = self.start
131 cx, cy = self.current
132 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 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
143
144 class ZoomOutTool(RectTool):
145
146 """The Zoom-Out Tool"""
147
148 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 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 self.view.ZoomFactor(0.5, center = (cx, cy))
163 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
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 sx, sy = self.start
180 x, y = self.current
181 width, height = self.view.GetSizeTuple()
182
183 bitmapdc = wx.wxMemoryDC()
184 bitmapdc.SelectObject(self.view.bitmap)
185
186 dc = self.view.drag_dc
187 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
188
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
196 class IdentifyTool(Tool):
197
198 """The "Identify" Tool"""
199
200 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
255 class MapCanvas(wxWindow, Publisher):
256
257 """A widget that displays a map and offers some interaction"""
258
259 def __init__(self, parent, winid, interactor):
260 wxWindow.__init__(self, parent, winid)
261 self.SetBackgroundColour(wxColour(255, 255, 255))
262
263 # the map displayed in this canvas. Set with SetMap()
264 self.map = None
265
266 # scale and offset describe the transformation from projected
267 # coordinates to window coordinates.
268 self.scale = 1.0
269 self.offset = (0, 0)
270
271 # whether the user is currently dragging the mouse, i.e. moving
272 # the mouse while pressing a mouse button
273 self.dragging = 0
274
275 # the currently active tool
276 self.tool = None
277
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 self.redraw_on_idle = 0
286
287 # the bitmap serving as backing store
288 self.bitmap = None
289
290 # the interactor
291 self.interactor = interactor
292 self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
293
294 # keep track of which layers/shapes are selected to make sure we
295 # only redraw when necessary
296 self.last_selected_layer = None
297 self.last_selected_shape = None
298
299 # subscribe the WX events we're interested in
300 EVT_PAINT(self, self.OnPaint)
301 EVT_LEFT_DOWN(self, self.OnLeftDown)
302 EVT_LEFT_UP(self, self.OnLeftUp)
303 EVT_MOTION(self, self.OnMotion)
304 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
305 wx.EVT_SIZE(self, self.OnSize)
306 wx.EVT_IDLE(self, self.OnIdle)
307
308 def __del__(self):
309 wxWindow.__del__(self)
310 Publisher.__del__(self)
311
312 def OnPaint(self, event):
313 dc = wxPaintDC(self)
314 if self.map is not None and self.map.HasLayers():
315 # We have a non-empty map. Redraw it in idle time
316 self.redraw_on_idle = 1
317 else:
318 # If we've got no map or if the map is empty, simply clear
319 # the screen.
320
321 # XXX it's probably possible to get rid of this. The
322 # background color of the window is already white and the
323 # only thing we may have to do is to call self.Refresh()
324 # with a true argument in the right places.
325 dc.BeginDrawing()
326 dc.Clear()
327 dc.EndDrawing()
328
329 def do_redraw(self):
330 # This should only be called if we have a non-empty map.
331
332 # Get the window size.
333 width, height = self.GetSizeTuple()
334
335 # If self.bitmap's still there, reuse it. Otherwise redraw it
336 if self.bitmap is not None:
337 bitmap = self.bitmap
338 else:
339 bitmap = wx.wxEmptyBitmap(width, height)
340 dc = wx.wxMemoryDC()
341 dc.SelectObject(bitmap)
342 dc.BeginDrawing()
343
344 # clear the background
345 dc.SetBrush(wx.wxWHITE_BRUSH)
346 dc.SetPen(wx.wxTRANSPARENT_PEN)
347 dc.DrawRectangle(0, 0, width, height)
348
349 if 1: #self.interactor.selected_map is self.map:
350 selected_layer = self.interactor.selected_layer
351 selected_shape = self.interactor.selected_shape
352 else:
353 selected_layer = None
354 selected_shape = None
355
356 # draw the map into the bitmap
357 renderer = ScreenRenderer(dc, self.scale, self.offset)
358
359 # Pass the entire bitmap as update region to the renderer.
360 # We're redrawing the whole bitmap, after all.
361 renderer.RenderMap(self.map, (0, 0, width, height),
362 selected_layer, selected_shape)
363
364 dc.EndDrawing()
365 dc.SelectObject(wx.wxNullBitmap)
366 self.bitmap = bitmap
367
368 # blit the bitmap to the screen
369 dc = wx.wxMemoryDC()
370 dc.SelectObject(bitmap)
371 clientdc = wxClientDC(self)
372 clientdc.BeginDrawing()
373 clientdc.Blit(0, 0, width, height, dc, 0, 0)
374 clientdc.EndDrawing()
375
376 def Print(self):
377 printer = wx.wxPrinter()
378 printout = MapPrintout(self.map)
379 printer.Print(self, printout, wx.true)
380 printout.Destroy()
381
382 def SetMap(self, map):
383 redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
384 LAYER_VISIBILITY_CHANGED)
385 if self.map is not None:
386 for channel in redraw_channels:
387 self.map.Unsubscribe(channel, self.full_redraw)
388 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
389 self.projection_changed)
390 self.map = map
391 if self.map is not None:
392 for channel in redraw_channels:
393 self.map.Subscribe(channel, self.full_redraw)
394 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
395 self.FitMapToWindow()
396 # force a redraw. If map is not empty, it's already been called
397 # by FitMapToWindow but if map is empty it hasn't been called
398 # yet so we have to explicitly call it.
399 self.full_redraw()
400
401 def Map(self):
402 """Return the map displayed by this canvas"""
403 return self.map
404
405 def redraw(self, *args):
406 self.Refresh(0)
407
408 def full_redraw(self, *args):
409 self.bitmap = None
410 self.redraw()
411
412 def projection_changed(self, *args):
413 self.FitMapToWindow()
414 self.full_redraw()
415
416 def set_view_transform(self, scale, offset):
417 self.scale = scale
418 self.offset = offset
419 self.full_redraw()
420
421 def proj_to_win(self, x, y):
422 """\
423 Return the point in window coords given by projected coordinates x y
424 """
425 offx, offy = self.offset
426 return (self.scale * x + offx, -self.scale * y + offy)
427
428 def win_to_proj(self, x, y):
429 """\
430 Return the point in projected coordinates given by window coords x y
431 """
432 offx, offy = self.offset
433 return ((x - offx) / self.scale, (offy - y) / self.scale)
434
435 def FitRectToWindow(self, rect):
436 """Fit the rectangular region given by rect into the window.
437
438 Set scale so that rect (in projected coordinates) just fits into
439 the window and center it.
440 """
441 width, height = self.GetSizeTuple()
442 llx, lly, urx, ury = rect
443 if llx == urx or lly == ury:
444 # zero with or zero height. Do Nothing
445 return
446 scalex = width / (urx - llx)
447 scaley = height / (ury - lly)
448 scale = min(scalex, scaley)
449 offx = 0.5 * (width - (urx + llx) * scale)
450 offy = 0.5 * (height + (ury + lly) * scale)
451 self.set_view_transform(scale, (offx, offy))
452
453 def FitMapToWindow(self):
454 """Fit the map to the window
455
456 Set the scale so that the map fits exactly into the window and
457 center it in the window.
458 """
459 bbox = self.map.ProjectedBoundingBox()
460 if bbox is not None:
461 self.FitRectToWindow(bbox)
462
463 def ZoomFactor(self, factor, center = None):
464 """Multiply the zoom by factor and center on center.
465
466 The optional parameter center is a point in window coordinates
467 that should be centered. If it is omitted, it defaults to the
468 center of the window
469 """
470 width, height = self.GetSizeTuple()
471 scale = self.scale * factor
472 offx, offy = self.offset
473 if center is not None:
474 cx, cy = center
475 else:
476 cx = width / 2
477 cy = height / 2
478 offset = (factor * (offx - cx) + width / 2,
479 factor * (offy - cy) + height / 2)
480 self.set_view_transform(scale, offset)
481
482 def ZoomOutToRect(self, rect):
483 """Zoom out to fit the currently visible region into rect.
484
485 The rect parameter is given in window coordinates
486 """
487 # determine the bbox of the displayed region in projected
488 # coordinates
489 width, height = self.GetSizeTuple()
490 llx, lly = self.win_to_proj(0, height - 1)
491 urx, ury = self.win_to_proj(width - 1, 0)
492
493 sx, sy, ex, ey = rect
494 scalex = (ex - sx) / (urx - llx)
495 scaley = (ey - sy) / (ury - lly)
496 scale = min(scalex, scaley)
497
498 offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
499 offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
500 self.set_view_transform(scale, (offx, offy))
501
502 def Translate(self, dx, dy):
503 """Move the map by dx, dy pixels"""
504 offx, offy = self.offset
505 self.set_view_transform(self.scale, (offx + dx, offy + dy))
506
507 def ZoomInTool(self):
508 """Start the zoom in tool"""
509 self.tool = ZoomInTool(self)
510
511 def ZoomOutTool(self):
512 """Start the zoom out tool"""
513 self.tool = ZoomOutTool(self)
514
515 def PanTool(self):
516 """Start the pan tool"""
517 self.tool = PanTool(self)
518
519 def IdentifyTool(self):
520 """Start the identify tool"""
521 self.tool = IdentifyTool(self)
522
523 def LabelTool(self):
524 """Start the label tool"""
525 self.tool = LabelTool(self)
526
527 def CurrentTool(self):
528 """Return the name of the current tool or None if no tool is active"""
529 return self.tool and self.tool.Name() or None
530
531 def CurrentPosition(self):
532 """Return current position of the mouse in projected coordinates.
533
534 The result is a 2-tuple of floats with the coordinates. If the
535 mouse is not in the window, the result is None.
536 """
537 if self.current_position is not None:
538 x, y = self.current_position
539 return self.win_to_proj(x, y)
540 else:
541 return None
542
543 def set_current_position(self, event):
544 """Set the current position from event
545
546 Should be called by all events that contain mouse positions
547 especially EVT_MOTION. The event paramete may be None to
548 indicate the the pointer left the window.
549 """
550 if event is not None:
551 self.current_position = (event.m_x, event.m_y)
552 else:
553 self.current_position = None
554 self.issue(VIEW_POSITION)
555
556 def OnLeftDown(self, event):
557 self.set_current_position(event)
558 if self.tool is not None:
559 self.drag_dc = wxClientDC(self)
560 self.drag_dc.SetLogicalFunction(wxINVERT)
561 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
562 self.CaptureMouse()
563 self.tool.MouseDown(event)
564 self.tool.Show(self.drag_dc)
565 self.dragging = 1
566
567 def OnLeftUp(self, event):
568 self.set_current_position(event)
569 if self.dragging:
570 self.ReleaseMouse()
571 self.tool.Hide(self.drag_dc)
572 self.tool.MouseUp(event)
573 self.drag_dc = None
574 self.dragging = 0
575
576 def OnMotion(self, event):
577 self.set_current_position(event)
578 if self.dragging:
579 self.tool.Hide(self.drag_dc)
580 self.tool.MouseMove(event)
581 self.tool.Show(self.drag_dc)
582
583 def OnLeaveWindow(self, event):
584 self.set_current_position(None)
585
586 def OnIdle(self, event):
587 if self.redraw_on_idle:
588 self.do_redraw()
589 self.redraw_on_idle = 0
590
591 def OnSize(self, event):
592 # the window's size has changed. We have to get a new bitmap. If
593 # we want to be clever we could try to get by without throwing
594 # everything away. E.g. when the window gets smaller, we could
595 # either keep the bitmap or create the new one from the old one.
596 # Even when the window becomes larger some parts of the bitmap
597 # could be reused.
598 self.full_redraw()
599
600 def shape_selected(self, layer, shape):
601 """Redraw the map.
602
603 Receiver for the SELECTED_SHAPE messages. Try to redraw only
604 when necessary.
605 """
606 # A redraw is necessary when the display has to change, which
607 # means that either the status changes from having no selection
608 # to having a selection shape or vice versa, or when the fact
609 # whether there is a selection at all doesn't change, when the
610 # shape which is selected has changed (which means that layer or
611 # shapeid changes).
612 if ((shape is not None or self.last_selected_shape is not None)
613 and (shape != self.last_selected_shape
614 or layer != self.last_selected_layer)):
615 self.full_redraw()
616
617 # remember the selection so we can compare when it changes again.
618 self.last_selected_layer = layer
619 self.last_selected_shape = shape
620
621 def unprojected_rect_around_point(self, x, y):
622 """return a rect a few pixels around (x, y) in unprojected corrdinates
623
624 The return value is a tuple (minx, miny, maxx, maxy) suitable a
625 parameter to a layer's ShapesInRegion method.
626 """
627 map_proj = self.map.projection
628 if map_proj is not None:
629 inverse = map_proj.Inverse
630 else:
631 inverse = None
632
633 xs = []
634 ys = []
635 for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
636 px, py = self.win_to_proj(x + dx, y + dy)
637 if inverse:
638 px, py = inverse(px, py)
639 xs.append(px)
640 ys.append(py)
641 return (min(xs), min(ys), max(xs), max(ys))
642
643 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
644 """Determine the shape at point px, py in window coords
645
646 Return the shape and the corresponding layer as a tuple (layer,
647 shape).
648
649 If the optional parameter select_labels is true (default false)
650 search through the labels. If a label is found return it's index
651 as the shape and None as the layer.
652
653 If the optional parameter searched_layer is given (or not None
654 which it defaults to), only search in that layer.
655 """
656 map_proj = self.map.projection
657 if map_proj is not None:
658 forward = map_proj.Forward
659 else:
660 forward = None
661
662 scale = self.scale
663 offx, offy = self.offset
664
665 box = self.unprojected_rect_around_point(px, py)
666
667 if select_labels:
668 labels = self.map.LabelLayer().Labels()
669
670 if labels:
671 dc = wxClientDC(self)
672 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
673 dc.SetFont(font)
674 for i in range(len(labels) - 1, -1, -1):
675 label = labels[i]
676 x = label.x
677 y = label.y
678 text = label.text
679 if forward:
680 x, y = forward(x, y)
681 x = x * scale + offx
682 y = -y * scale + offy
683 width, height = dc.GetTextExtent(text)
684 if label.halign == ALIGN_LEFT:
685 # nothing to be done
686 pass
687 elif label.halign == ALIGN_RIGHT:
688 x = x - width
689 elif label.halign == ALIGN_CENTER:
690 x = x - width/2
691 if label.valign == ALIGN_TOP:
692 # nothing to be done
693 pass
694 elif label.valign == ALIGN_BOTTOM:
695 y = y - height
696 elif label.valign == ALIGN_CENTER:
697 y = y - height/2
698 if x <= px < x + width and y <= py <= y + height:
699 return None, i
700
701 if searched_layer:
702 layers = [searched_layer]
703 else:
704 layers = self.map.Layers()
705
706 for layer_index in range(len(layers) - 1, -1, -1):
707 layer = layers[layer_index]
708
709 # search only in visible layers
710 if not layer.Visible():
711 continue
712
713 filled = layer.fill is not None
714 stroked = layer.stroke is not None
715
716 layer_proj = layer.projection
717 if layer_proj is not None:
718 inverse = layer_proj.Inverse
719 else:
720 inverse = None
721
722 shapetype = layer.ShapeType()
723
724 select_shape = -1
725
726 shape_ids = layer.ShapesInRegion(box)
727 shape_ids.reverse()
728
729 if shapetype == SHAPETYPE_POLYGON:
730 for i in shape_ids:
731 result = point_in_polygon_shape(layer.shapefile.cobject(),
732 i,
733 filled, stroked,
734 map_proj, layer_proj,
735 scale, -scale, offx, offy,
736 px, py)
737 if result:
738 select_shape = i
739 break
740 elif shapetype == SHAPETYPE_ARC:
741 for i in shape_ids:
742 result = point_in_polygon_shape(layer.shapefile.cobject(),
743 i, 0, 1,
744 map_proj, layer_proj,
745 scale, -scale, offx, offy,
746 px, py)
747 if result < 0:
748 select_shape = i
749 break
750 elif shapetype == SHAPETYPE_POINT:
751 for i in shape_ids:
752 shape = layer.Shape(i)
753 x, y = shape.Points()[0]
754 if inverse:
755 x, y = inverse(x, y)
756 if forward:
757 x, y = forward(x, y)
758 x = x * scale + offx
759 y = -y * scale + offy
760 if hypot(px - x, py - y) < 5:
761 select_shape = i
762 break
763
764 if select_shape >= 0:
765 return layer, select_shape
766 return None, None
767
768 def SelectShapeAt(self, x, y, layer = None):
769 """\
770 Select and return the shape and its layer at window position (x, y)
771
772 If layer is given, only search in that layer. If no layer is
773 given, search through all layers.
774
775 Return a tuple (layer, shapeid). If no shape is found, return
776 (None, None).
777 """
778 layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
779 # If layer is None, then shape will also be None. We don't want
780 # to deselect the currently selected layer, so we simply select
781 # the already selected layer again.
782 if layer is None:
783 layer = self.interactor.SelectedLayer()
784 self.interactor.SelectLayerAndShape(layer, shape)
785 return result
786
787 def LabelShapeAt(self, x, y):
788 """Add or remove a label at window position x, y.
789
790 If there's a label at the given position, remove it. Otherwise
791 determine the shape at the position, run the label dialog and
792 unless the user cancels the dialog, add a laber.
793 """
794 ox = x; oy = y
795 label_layer = self.map.LabelLayer()
796 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
797 if layer is None and shape_index is not None:
798 # a label was selected
799 label_layer.RemoveLabel(shape_index)
800 elif layer is not None:
801 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
802 if text:
803 proj = self.map.projection
804 if proj is not None:
805 map_proj = proj
806 else:
807 map_proj = None
808 proj = layer.projection
809 if proj is not None:
810 layer_proj = proj
811 else:
812 layer_proj = None
813
814 shapetype = layer.ShapeType()
815 if shapetype == SHAPETYPE_POLYGON:
816 x, y = shape_centroid(layer.shapefile.cobject(),
817 shape_index,
818 map_proj, layer_proj, 1, 1, 0, 0)
819 if map_proj is not None:
820 x, y = map_proj.Inverse(x, y)
821 else:
822 shape = layer.Shape(shape_index)
823 if shapetype == SHAPETYPE_POINT:
824 x, y = shape.Points()[0]
825 else:
826 # assume SHAPETYPE_ARC
827 points = shape.Points()
828 x, y = points[len(points) / 2]
829 if layer_proj is not None:
830 x, y = layer_proj.Inverse(x, y)
831 if shapetype == SHAPETYPE_POINT:
832 halign = ALIGN_LEFT
833 valign = ALIGN_CENTER
834 elif shapetype == SHAPETYPE_POLYGON:
835 halign = ALIGN_CENTER
836 valign = ALIGN_CENTER
837 elif shapetype == SHAPETYPE_ARC:
838 halign = ALIGN_LEFT
839 valign = ALIGN_CENTER
840 label_layer.AddLabel(x, y, text,
841 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