/[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 404 - (show annotations)
Fri Feb 14 17:40:26 2003 UTC (22 years ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28598 byte(s)
(MapCanvas.OnLeftUp): Make sure that the
dragging flag is always set to 0 even when the tool implementation
raises an exception

1 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with Thuban for details.
7
8 """
9 Classes for display of a map and interaction with it
10 """
11
12 __version__ = "$Revision$"
13
14 from math import hypot
15
16 from wxPython.wx import wxWindow,\
17 wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
18 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW
19
20
21 from wxPython import wx
22
23 from wxproj import point_in_polygon_shape, shape_centroid
24
25
26 from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
27 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 try:
573 self.tool.Hide(self.drag_dc)
574 self.tool.MouseUp(event)
575 finally:
576 self.drag_dc = None
577 self.dragging = 0
578
579 def OnMotion(self, event):
580 self.set_current_position(event)
581 if self.dragging:
582 self.tool.Hide(self.drag_dc)
583 self.tool.MouseMove(event)
584 self.tool.Show(self.drag_dc)
585
586 def OnLeaveWindow(self, event):
587 self.set_current_position(None)
588
589 def OnSize(self, event):
590 # the window's size has changed. We have to get a new bitmap. If
591 # we want to be clever we could try to get by without throwing
592 # everything away. E.g. when the window gets smaller, we could
593 # either keep the bitmap or create the new one from the old one.
594 # Even when the window becomes larger some parts of the bitmap
595 # could be reused.
596 self.full_redraw()
597
598 def shape_selected(self, layer, shape):
599 """Redraw the map.
600
601 Receiver for the SELECTED_SHAPE messages. Try to redraw only
602 when necessary.
603 """
604 # A redraw is necessary when the display has to change, which
605 # means that either the status changes from having no selection
606 # to having a selection shape or vice versa, or when the fact
607 # whether there is a selection at all doesn't change, when the
608 # shape which is selected has changed (which means that layer or
609 # shapeid changes).
610 if ((shape is not None or self.last_selected_shape is not None)
611 and (shape != self.last_selected_shape
612 or layer != self.last_selected_layer)):
613 self.full_redraw()
614
615 # remember the selection so we can compare when it changes again.
616 self.last_selected_layer = layer
617 self.last_selected_shape = shape
618
619 def unprojected_rect_around_point(self, x, y, dist):
620 """return a rect dist pixels around (x, y) in unprojected corrdinates
621
622 The return value is a tuple (minx, miny, maxx, maxy) suitable a
623 parameter to a layer's ShapesInRegion method.
624 """
625 map_proj = self.map.projection
626 if map_proj is not None:
627 inverse = map_proj.Inverse
628 else:
629 inverse = None
630
631 xs = []
632 ys = []
633 for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
634 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
635 if inverse:
636 px, py = inverse(px, py)
637 xs.append(px)
638 ys.append(py)
639 return (min(xs), min(ys), max(xs), max(ys))
640
641 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
642 """Determine the shape at point px, py in window coords
643
644 Return the shape and the corresponding layer as a tuple (layer,
645 shape).
646
647 If the optional parameter select_labels is true (default false)
648 search through the labels. If a label is found return it's index
649 as the shape and None as the layer.
650
651 If the optional parameter searched_layer is given (or not None
652 which it defaults to), only search in that layer.
653 """
654 map_proj = self.map.projection
655 if map_proj is not None:
656 forward = map_proj.Forward
657 else:
658 forward = None
659
660 scale = self.scale
661 offx, offy = self.offset
662
663 if select_labels:
664 labels = self.map.LabelLayer().Labels()
665
666 if labels:
667 dc = wxClientDC(self)
668 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
669 dc.SetFont(font)
670 for i in range(len(labels) - 1, -1, -1):
671 label = labels[i]
672 x = label.x
673 y = label.y
674 text = label.text
675 if forward:
676 x, y = forward(x, y)
677 x = x * scale + offx
678 y = -y * scale + offy
679 width, height = dc.GetTextExtent(text)
680 if label.halign == ALIGN_LEFT:
681 # nothing to be done
682 pass
683 elif label.halign == ALIGN_RIGHT:
684 x = x - width
685 elif label.halign == ALIGN_CENTER:
686 x = x - width/2
687 if label.valign == ALIGN_TOP:
688 # nothing to be done
689 pass
690 elif label.valign == ALIGN_BOTTOM:
691 y = y - height
692 elif label.valign == ALIGN_CENTER:
693 y = y - height/2
694 if x <= px < x + width and y <= py <= y + height:
695 return None, i
696
697 if searched_layer:
698 layers = [searched_layer]
699 else:
700 layers = self.map.Layers()
701
702 for layer_index in range(len(layers) - 1, -1, -1):
703 layer = layers[layer_index]
704
705 # search only in visible layers
706 if not layer.Visible():
707 continue
708
709 filled = layer.fill is not None
710 stroked = layer.stroke is not None
711
712 layer_proj = layer.projection
713 if layer_proj is not None:
714 inverse = layer_proj.Inverse
715 else:
716 inverse = None
717
718 shapetype = layer.ShapeType()
719
720 select_shape = -1
721
722 # Determine the ids of the shapes that overlap a tiny area
723 # around the point. For layers containing points we have to
724 # choose a larger size of the box we're testing agains so
725 # that we take the size of the markers into account
726 # FIXME: Once the markers are more flexible this part has to
727 # become more flexible too, of course
728 if shapetype == SHAPETYPE_POINT:
729 box = self.unprojected_rect_around_point(px, py, 5)
730 else:
731 box = self.unprojected_rect_around_point(px, py, 1)
732 shape_ids = layer.ShapesInRegion(box)
733 shape_ids.reverse()
734
735 if shapetype == SHAPETYPE_POLYGON:
736 for i in shape_ids:
737 result = point_in_polygon_shape(layer.shapefile.cobject(),
738 i,
739 filled, stroked,
740 map_proj, layer_proj,
741 scale, -scale, offx, offy,
742 px, py)
743 if result:
744 select_shape = i
745 break
746 elif shapetype == SHAPETYPE_ARC:
747 for i in shape_ids:
748 result = point_in_polygon_shape(layer.shapefile.cobject(),
749 i, 0, 1,
750 map_proj, layer_proj,
751 scale, -scale, offx, offy,
752 px, py)
753 if result < 0:
754 select_shape = i
755 break
756 elif shapetype == SHAPETYPE_POINT:
757 for i in shape_ids:
758 shape = layer.Shape(i)
759 x, y = shape.Points()[0]
760 if inverse:
761 x, y = inverse(x, y)
762 if forward:
763 x, y = forward(x, y)
764 x = x * scale + offx
765 y = -y * scale + offy
766 if hypot(px - x, py - y) < 5:
767 select_shape = i
768 break
769
770 if select_shape >= 0:
771 return layer, select_shape
772 return None, None
773
774 def SelectShapeAt(self, x, y, layer = None):
775 """\
776 Select and return the shape and its layer at window position (x, y)
777
778 If layer is given, only search in that layer. If no layer is
779 given, search through all layers.
780
781 Return a tuple (layer, shapeid). If no shape is found, return
782 (None, None).
783 """
784 layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
785 # If layer is None, then shape will also be None. We don't want
786 # to deselect the currently selected layer, so we simply select
787 # the already selected layer again.
788 if layer is None:
789 layer = self.interactor.SelectedLayer()
790 self.interactor.SelectLayerAndShape(layer, shape)
791 return result
792
793 def LabelShapeAt(self, x, y):
794 """Add or remove a label at window position x, y.
795
796 If there's a label at the given position, remove it. Otherwise
797 determine the shape at the position, run the label dialog and
798 unless the user cancels the dialog, add a laber.
799 """
800 ox = x; oy = y
801 label_layer = self.map.LabelLayer()
802 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
803 if layer is None and shape_index is not None:
804 # a label was selected
805 label_layer.RemoveLabel(shape_index)
806 elif layer is not None:
807 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
808 if text:
809 proj = self.map.projection
810 if proj is not None:
811 map_proj = proj
812 else:
813 map_proj = None
814 proj = layer.projection
815 if proj is not None:
816 layer_proj = proj
817 else:
818 layer_proj = None
819
820 shapetype = layer.ShapeType()
821 if shapetype == SHAPETYPE_POLYGON:
822 x, y = shape_centroid(layer.shapefile.cobject(),
823 shape_index,
824 map_proj, layer_proj, 1, 1, 0, 0)
825 if map_proj is not None:
826 x, y = map_proj.Inverse(x, y)
827 else:
828 shape = layer.Shape(shape_index)
829 if shapetype == SHAPETYPE_POINT:
830 x, y = shape.Points()[0]
831 else:
832 # assume SHAPETYPE_ARC
833 points = shape.Points()
834 x, y = points[len(points) / 2]
835 if layer_proj is not None:
836 x, y = layer_proj.Inverse(x, y)
837 if shapetype == SHAPETYPE_POINT:
838 halign = ALIGN_LEFT
839 valign = ALIGN_CENTER
840 elif shapetype == SHAPETYPE_POLYGON:
841 halign = ALIGN_CENTER
842 valign = ALIGN_CENTER
843 elif shapetype == SHAPETYPE_ARC:
844 halign = ALIGN_LEFT
845 valign = ALIGN_CENTER
846 label_layer.AddLabel(x, y, text,
847 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