/[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 883 - (show annotations)
Fri May 9 16:34:39 2003 UTC (21 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 32108 byte(s)
(MapCanvas): Delegate "SelectedShapes" so the table view can call it.

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