/[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 831 - (show annotations)
Tue May 6 12:07:21 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: 31973 byte(s)
(MapCanvas): Added delegated method HasSelectedShapes.
(MapCanvas.FitSelectedToWindow): New. Centers and scales any selected
        shapes on the canvas using the map projection (if any).

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