/[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 356 - (show annotations)
Mon Dec 9 10:32:15 2002 UTC (22 years, 3 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28534 byte(s)
(MapCanvas.SelectTool): New method to select
the tool to avoid direct assignments to instance variables
(MapCanvas.ZoomInTool, MapCanvas.ZoomOutTool, MapCanvas.PanTool)
(MapCanvas.IdentifyTool, MapCanvas.LabelTool): Use SelectTool to
change the tool

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 SelectTool(self, tool):
501 """Make tool the active tool.
502
503 The parameter should be an instance of Tool or None to indicate
504 that no tool is active.
505 """
506 self.tool = tool
507
508 def ZoomInTool(self):
509 """Start the zoom in tool"""
510 self.SelectTool(ZoomInTool(self))
511
512 def ZoomOutTool(self):
513 """Start the zoom out tool"""
514 self.SelectTool(ZoomOutTool(self))
515
516 def PanTool(self):
517 """Start the pan tool"""
518 self.SelectTool(PanTool(self))
519
520 def IdentifyTool(self):
521 """Start the identify tool"""
522 self.SelectTool(IdentifyTool(self))
523
524 def LabelTool(self):
525 """Start the label tool"""
526 self.SelectTool(LabelTool(self))
527
528 def CurrentTool(self):
529 """Return the name of the current tool or None if no tool is active"""
530 return self.tool and self.tool.Name() or None
531
532 def CurrentPosition(self):
533 """Return current position of the mouse in projected coordinates.
534
535 The result is a 2-tuple of floats with the coordinates. If the
536 mouse is not in the window, the result is None.
537 """
538 if self.current_position is not None:
539 x, y = self.current_position
540 return self.win_to_proj(x, y)
541 else:
542 return None
543
544 def set_current_position(self, event):
545 """Set the current position from event
546
547 Should be called by all events that contain mouse positions
548 especially EVT_MOTION. The event paramete may be None to
549 indicate the the pointer left the window.
550 """
551 if event is not None:
552 self.current_position = (event.m_x, event.m_y)
553 else:
554 self.current_position = None
555 self.issue(VIEW_POSITION)
556
557 def OnLeftDown(self, event):
558 self.set_current_position(event)
559 if self.tool is not None:
560 self.drag_dc = wxClientDC(self)
561 self.drag_dc.SetLogicalFunction(wxINVERT)
562 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
563 self.CaptureMouse()
564 self.tool.MouseDown(event)
565 self.tool.Show(self.drag_dc)
566 self.dragging = 1
567
568 def OnLeftUp(self, event):
569 self.set_current_position(event)
570 if self.dragging:
571 self.ReleaseMouse()
572 self.tool.Hide(self.drag_dc)
573 self.tool.MouseUp(event)
574 self.drag_dc = None
575 self.dragging = 0
576
577 def OnMotion(self, event):
578 self.set_current_position(event)
579 if self.dragging:
580 self.tool.Hide(self.drag_dc)
581 self.tool.MouseMove(event)
582 self.tool.Show(self.drag_dc)
583
584 def OnLeaveWindow(self, event):
585 self.set_current_position(None)
586
587 def OnSize(self, event):
588 # the window's size has changed. We have to get a new bitmap. If
589 # we want to be clever we could try to get by without throwing
590 # everything away. E.g. when the window gets smaller, we could
591 # either keep the bitmap or create the new one from the old one.
592 # Even when the window becomes larger some parts of the bitmap
593 # could be reused.
594 self.full_redraw()
595
596 def shape_selected(self, layer, shape):
597 """Redraw the map.
598
599 Receiver for the SELECTED_SHAPE messages. Try to redraw only
600 when necessary.
601 """
602 # A redraw is necessary when the display has to change, which
603 # means that either the status changes from having no selection
604 # to having a selection shape or vice versa, or when the fact
605 # whether there is a selection at all doesn't change, when the
606 # shape which is selected has changed (which means that layer or
607 # shapeid changes).
608 if ((shape is not None or self.last_selected_shape is not None)
609 and (shape != self.last_selected_shape
610 or layer != self.last_selected_layer)):
611 self.full_redraw()
612
613 # remember the selection so we can compare when it changes again.
614 self.last_selected_layer = layer
615 self.last_selected_shape = shape
616
617 def unprojected_rect_around_point(self, x, y, dist):
618 """return a rect dist pixels around (x, y) in unprojected corrdinates
619
620 The return value is a tuple (minx, miny, maxx, maxy) suitable a
621 parameter to a layer's ShapesInRegion method.
622 """
623 map_proj = self.map.projection
624 if map_proj is not None:
625 inverse = map_proj.Inverse
626 else:
627 inverse = None
628
629 xs = []
630 ys = []
631 for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
632 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
633 if inverse:
634 px, py = inverse(px, py)
635 xs.append(px)
636 ys.append(py)
637 return (min(xs), min(ys), max(xs), max(ys))
638
639 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
640 """Determine the shape at point px, py in window coords
641
642 Return the shape and the corresponding layer as a tuple (layer,
643 shape).
644
645 If the optional parameter select_labels is true (default false)
646 search through the labels. If a label is found return it's index
647 as the shape and None as the layer.
648
649 If the optional parameter searched_layer is given (or not None
650 which it defaults to), only search in that layer.
651 """
652 map_proj = self.map.projection
653 if map_proj is not None:
654 forward = map_proj.Forward
655 else:
656 forward = None
657
658 scale = self.scale
659 offx, offy = self.offset
660
661 if select_labels:
662 labels = self.map.LabelLayer().Labels()
663
664 if labels:
665 dc = wxClientDC(self)
666 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
667 dc.SetFont(font)
668 for i in range(len(labels) - 1, -1, -1):
669 label = labels[i]
670 x = label.x
671 y = label.y
672 text = label.text
673 if forward:
674 x, y = forward(x, y)
675 x = x * scale + offx
676 y = -y * scale + offy
677 width, height = dc.GetTextExtent(text)
678 if label.halign == ALIGN_LEFT:
679 # nothing to be done
680 pass
681 elif label.halign == ALIGN_RIGHT:
682 x = x - width
683 elif label.halign == ALIGN_CENTER:
684 x = x - width/2
685 if label.valign == ALIGN_TOP:
686 # nothing to be done
687 pass
688 elif label.valign == ALIGN_BOTTOM:
689 y = y - height
690 elif label.valign == ALIGN_CENTER:
691 y = y - height/2
692 if x <= px < x + width and y <= py <= y + height:
693 return None, i
694
695 if searched_layer:
696 layers = [searched_layer]
697 else:
698 layers = self.map.Layers()
699
700 for layer_index in range(len(layers) - 1, -1, -1):
701 layer = layers[layer_index]
702
703 # search only in visible layers
704 if not layer.Visible():
705 continue
706
707 filled = layer.fill is not None
708 stroked = layer.stroke is not None
709
710 layer_proj = layer.projection
711 if layer_proj is not None:
712 inverse = layer_proj.Inverse
713 else:
714 inverse = None
715
716 shapetype = layer.ShapeType()
717
718 select_shape = -1
719
720 # Determine the ids of the shapes that overlap a tiny area
721 # around the point. For layers containing points we have to
722 # choose a larger size of the box we're testing agains so
723 # that we take the size of the markers into account
724 # FIXME: Once the markers are more flexible this part has to
725 # become more flexible too, of course
726 if shapetype == SHAPETYPE_POINT:
727 box = self.unprojected_rect_around_point(px, py, 5)
728 else:
729 box = self.unprojected_rect_around_point(px, py, 1)
730 shape_ids = layer.ShapesInRegion(box)
731 shape_ids.reverse()
732
733 if shapetype == SHAPETYPE_POLYGON:
734 for i in shape_ids:
735 result = point_in_polygon_shape(layer.shapefile.cobject(),
736 i,
737 filled, stroked,
738 map_proj, layer_proj,
739 scale, -scale, offx, offy,
740 px, py)
741 if result:
742 select_shape = i
743 break
744 elif shapetype == SHAPETYPE_ARC:
745 for i in shape_ids:
746 result = point_in_polygon_shape(layer.shapefile.cobject(),
747 i, 0, 1,
748 map_proj, layer_proj,
749 scale, -scale, offx, offy,
750 px, py)
751 if result < 0:
752 select_shape = i
753 break
754 elif shapetype == SHAPETYPE_POINT:
755 for i in shape_ids:
756 shape = layer.Shape(i)
757 x, y = shape.Points()[0]
758 if inverse:
759 x, y = inverse(x, y)
760 if forward:
761 x, y = forward(x, y)
762 x = x * scale + offx
763 y = -y * scale + offy
764 if hypot(px - x, py - y) < 5:
765 select_shape = i
766 break
767
768 if select_shape >= 0:
769 return layer, select_shape
770 return None, None
771
772 def SelectShapeAt(self, x, y, layer = None):
773 """\
774 Select and return the shape and its layer at window position (x, y)
775
776 If layer is given, only search in that layer. If no layer is
777 given, search through all layers.
778
779 Return a tuple (layer, shapeid). If no shape is found, return
780 (None, None).
781 """
782 layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
783 # If layer is None, then shape will also be None. We don't want
784 # to deselect the currently selected layer, so we simply select
785 # the already selected layer again.
786 if layer is None:
787 layer = self.interactor.SelectedLayer()
788 self.interactor.SelectLayerAndShape(layer, shape)
789 return result
790
791 def LabelShapeAt(self, x, y):
792 """Add or remove a label at window position x, y.
793
794 If there's a label at the given position, remove it. Otherwise
795 determine the shape at the position, run the label dialog and
796 unless the user cancels the dialog, add a laber.
797 """
798 ox = x; oy = y
799 label_layer = self.map.LabelLayer()
800 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
801 if layer is None and shape_index is not None:
802 # a label was selected
803 label_layer.RemoveLabel(shape_index)
804 elif layer is not None:
805 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
806 if text:
807 proj = self.map.projection
808 if proj is not None:
809 map_proj = proj
810 else:
811 map_proj = None
812 proj = layer.projection
813 if proj is not None:
814 layer_proj = proj
815 else:
816 layer_proj = None
817
818 shapetype = layer.ShapeType()
819 if shapetype == SHAPETYPE_POLYGON:
820 x, y = shape_centroid(layer.shapefile.cobject(),
821 shape_index,
822 map_proj, layer_proj, 1, 1, 0, 0)
823 if map_proj is not None:
824 x, y = map_proj.Inverse(x, y)
825 else:
826 shape = layer.Shape(shape_index)
827 if shapetype == SHAPETYPE_POINT:
828 x, y = shape.Points()[0]
829 else:
830 # assume SHAPETYPE_ARC
831 points = shape.Points()
832 x, y = points[len(points) / 2]
833 if layer_proj is not None:
834 x, y = layer_proj.Inverse(x, y)
835 if shapetype == SHAPETYPE_POINT:
836 halign = ALIGN_LEFT
837 valign = ALIGN_CENTER
838 elif shapetype == SHAPETYPE_POLYGON:
839 halign = ALIGN_CENTER
840 valign = ALIGN_CENTER
841 elif shapetype == SHAPETYPE_ARC:
842 halign = ALIGN_LEFT
843 valign = ALIGN_CENTER
844 label_layer.AddLabel(x, y, text,
845 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