/[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 469 - (show annotations)
Wed Mar 5 18:19:25 2003 UTC (22 years ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28761 byte(s)
Function name changes.

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