/[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 303 - (show annotations)
Mon Sep 2 16:47:53 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: 28296 byte(s)
* Thuban/UI/view.py: Get rid of the idle redraw. This is done by
wxWindows already and our implementation doesn't work correctly
with wxGTK 2.3:
(MapCanvas.__init__): Remove the instance variable
(MapCanvas.OnPaint): Always call do_redraw when there's a map to
be drawin
(MapCanvas.OnIdle): Removed.

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