/[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 301 - (show annotations)
Mon Sep 2 15:59:11 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: 28742 byte(s)
(MapCanvas.unprojected_rect_around_point): Add
a parameter to determine the size of the rectangle.
(MapCanvas.find_shape_at): Create the box around the point on a
layer by layer basis and make the size depend on the shape type.
This solves a problem with the selection of point shapes at the
border of the layer's bounding box

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