/[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 824 - (show annotations)
Tue May 6 08:09:27 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: 31505 byte(s)
(MapCanvas.FitLayerToWindow): The bbox rectangle should be fit into the
        window whether or not the map has a projection.

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