/[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 295 - (show annotations)
Fri Aug 30 10:39:17 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: 28662 byte(s)
Add some more doc-strings.

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