/[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 822 - (show annotations)
Mon May 5 18:20:28 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: 31512 byte(s)
(MapCanvas.FitLayerToWindow): New. Centers and
        scales the given layer on the canvas using the map 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 if bbox is not None:
533 self.FitRectToWindow(bbox)
534
535 def ZoomFactor(self, factor, center = None):
536 """Multiply the zoom by factor and center on center.
537
538 The optional parameter center is a point in window coordinates
539 that should be centered. If it is omitted, it defaults to the
540 center of the window
541 """
542 width, height = self.GetSizeTuple()
543 scale = self.scale * factor
544 offx, offy = self.offset
545 if center is not None:
546 cx, cy = center
547 else:
548 cx = width / 2
549 cy = height / 2
550 offset = (factor * (offx - cx) + width / 2,
551 factor * (offy - cy) + height / 2)
552 self.set_view_transform(scale, offset)
553
554 def ZoomOutToRect(self, rect):
555 """Zoom out to fit the currently visible region into rect.
556
557 The rect parameter is given in window coordinates
558 """
559 # determine the bbox of the displayed region in projected
560 # coordinates
561 width, height = self.GetSizeTuple()
562 llx, lly = self.win_to_proj(0, height - 1)
563 urx, ury = self.win_to_proj(width - 1, 0)
564
565 sx, sy, ex, ey = rect
566 scalex = (ex - sx) / (urx - llx)
567 scaley = (ey - sy) / (ury - lly)
568 scale = min(scalex, scaley)
569
570 offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
571 offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
572 self.set_view_transform(scale, (offx, offy))
573
574 def Translate(self, dx, dy):
575 """Move the map by dx, dy pixels"""
576 offx, offy = self.offset
577 self.set_view_transform(self.scale, (offx + dx, offy + dy))
578
579 def SelectTool(self, tool):
580 """Make tool the active tool.
581
582 The parameter should be an instance of Tool or None to indicate
583 that no tool is active.
584 """
585 self.tool = tool
586
587 def ZoomInTool(self):
588 """Start the zoom in tool"""
589 self.SelectTool(ZoomInTool(self))
590
591 def ZoomOutTool(self):
592 """Start the zoom out tool"""
593 self.SelectTool(ZoomOutTool(self))
594
595 def PanTool(self):
596 """Start the pan tool"""
597 self.SelectTool(PanTool(self))
598 #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
599 #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
600 #print bmp
601 #img = wxImageFromBitmap(bmp)
602 #print img
603 #cur = wxCursor(img)
604 #print cur
605 #self.SetCursor(cur)
606
607 def IdentifyTool(self):
608 """Start the identify tool"""
609 self.SelectTool(IdentifyTool(self))
610
611 def LabelTool(self):
612 """Start the label tool"""
613 self.SelectTool(LabelTool(self))
614
615 def CurrentTool(self):
616 """Return the name of the current tool or None if no tool is active"""
617 return self.tool and self.tool.Name() or None
618
619 def CurrentPosition(self):
620 """Return current position of the mouse in projected coordinates.
621
622 The result is a 2-tuple of floats with the coordinates. If the
623 mouse is not in the window, the result is None.
624 """
625 if self.current_position is not None:
626 x, y = self.current_position
627 return self.win_to_proj(x, y)
628 else:
629 return None
630
631 def set_current_position(self, event):
632 """Set the current position from event
633
634 Should be called by all events that contain mouse positions
635 especially EVT_MOTION. The event paramete may be None to
636 indicate the the pointer left the window.
637 """
638 if event is not None:
639 self.current_position = (event.m_x, event.m_y)
640 else:
641 self.current_position = None
642 self.issue(VIEW_POSITION)
643
644 def OnLeftDown(self, event):
645 self.set_current_position(event)
646 if self.tool is not None:
647 self.drag_dc = wxClientDC(self)
648 self.drag_dc.SetLogicalFunction(wxINVERT)
649 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
650 self.CaptureMouse()
651 self.tool.MouseDown(event)
652 self.tool.Show(self.drag_dc)
653 self.dragging = 1
654
655 def OnLeftUp(self, event):
656 self.set_current_position(event)
657 if self.dragging:
658 self.ReleaseMouse()
659 try:
660 self.tool.Hide(self.drag_dc)
661 self.tool.MouseUp(event)
662 finally:
663 self.drag_dc = None
664 self.dragging = 0
665
666 def OnMotion(self, event):
667 self.set_current_position(event)
668 if self.dragging:
669 self.tool.Hide(self.drag_dc)
670 self.tool.MouseMove(event)
671 self.tool.Show(self.drag_dc)
672
673 def OnLeaveWindow(self, event):
674 self.set_current_position(None)
675
676 def OnSize(self, event):
677 # the window's size has changed. We have to get a new bitmap. If
678 # we want to be clever we could try to get by without throwing
679 # everything away. E.g. when the window gets smaller, we could
680 # either keep the bitmap or create the new one from the old one.
681 # Even when the window becomes larger some parts of the bitmap
682 # could be reused.
683 self.full_redraw()
684 pass
685
686 def shape_selected(self, layer, shape):
687 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
688 # The selection object takes care that it only issues
689 # SHAPES_SELECTED messages when the set of selected shapes has
690 # actually changed, so we can do a full redraw unconditionally.
691 # FIXME: We should perhaps try to limit the redraw to the are
692 # actually covered by the shapes before and after the selection
693 # change.
694 self.full_redraw()
695
696 def unprojected_rect_around_point(self, x, y, dist):
697 """return a rect dist pixels around (x, y) in unprojected corrdinates
698
699 The return value is a tuple (minx, miny, maxx, maxy) suitable a
700 parameter to a layer's ShapesInRegion method.
701 """
702 map_proj = self.map.projection
703 if map_proj is not None:
704 inverse = map_proj.Inverse
705 else:
706 inverse = None
707
708 xs = []
709 ys = []
710 for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
711 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
712 if inverse:
713 px, py = inverse(px, py)
714 xs.append(px)
715 ys.append(py)
716 return (min(xs), min(ys), max(xs), max(ys))
717
718 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
719 """Determine the shape at point px, py in window coords
720
721 Return the shape and the corresponding layer as a tuple (layer,
722 shape).
723
724 If the optional parameter select_labels is true (default false)
725 search through the labels. If a label is found return it's index
726 as the shape and None as the layer.
727
728 If the optional parameter searched_layer is given (or not None
729 which it defaults to), only search in that layer.
730 """
731 map_proj = self.map.projection
732 if map_proj is not None:
733 forward = map_proj.Forward
734 else:
735 forward = None
736
737 scale = self.scale
738 offx, offy = self.offset
739
740 if select_labels:
741 labels = self.map.LabelLayer().Labels()
742
743 if labels:
744 dc = wxClientDC(self)
745 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
746 dc.SetFont(font)
747 for i in range(len(labels) - 1, -1, -1):
748 label = labels[i]
749 x = label.x
750 y = label.y
751 text = label.text
752 if forward:
753 x, y = forward(x, y)
754 x = x * scale + offx
755 y = -y * scale + offy
756 width, height = dc.GetTextExtent(text)
757 if label.halign == ALIGN_LEFT:
758 # nothing to be done
759 pass
760 elif label.halign == ALIGN_RIGHT:
761 x = x - width
762 elif label.halign == ALIGN_CENTER:
763 x = x - width/2
764 if label.valign == ALIGN_TOP:
765 # nothing to be done
766 pass
767 elif label.valign == ALIGN_BOTTOM:
768 y = y - height
769 elif label.valign == ALIGN_CENTER:
770 y = y - height/2
771 if x <= px < x + width and y <= py <= y + height:
772 return None, i
773
774 if searched_layer:
775 layers = [searched_layer]
776 else:
777 layers = self.map.Layers()
778
779 for layer_index in range(len(layers) - 1, -1, -1):
780 layer = layers[layer_index]
781
782 # search only in visible layers
783 if not layer.Visible():
784 continue
785
786 filled = layer.GetClassification().GetDefaultFill() \
787 is not Color.Transparent
788 stroked = layer.GetClassification().GetDefaultLineColor() \
789 is not Color.Transparent
790
791 layer_proj = layer.projection
792 if layer_proj is not None:
793 inverse = layer_proj.Inverse
794 else:
795 inverse = None
796
797 shapetype = layer.ShapeType()
798
799 select_shape = -1
800
801 # Determine the ids of the shapes that overlap a tiny area
802 # around the point. For layers containing points we have to
803 # choose a larger size of the box we're testing agains so
804 # that we take the size of the markers into account
805 # FIXME: Once the markers are more flexible this part has to
806 # become more flexible too, of course
807 if shapetype == SHAPETYPE_POINT:
808 box = self.unprojected_rect_around_point(px, py, 5)
809 else:
810 box = self.unprojected_rect_around_point(px, py, 1)
811 shape_ids = layer.ShapesInRegion(box)
812 shape_ids.reverse()
813
814 if shapetype == SHAPETYPE_POLYGON:
815 for i in shape_ids:
816 result = point_in_polygon_shape(layer.shapefile.cobject(),
817 i,
818 filled, stroked,
819 map_proj, layer_proj,
820 scale, -scale, offx, offy,
821 px, py)
822 if result:
823 select_shape = i
824 break
825 elif shapetype == SHAPETYPE_ARC:
826 for i in shape_ids:
827 result = point_in_polygon_shape(layer.shapefile.cobject(),
828 i, 0, 1,
829 map_proj, layer_proj,
830 scale, -scale, offx, offy,
831 px, py)
832 if result < 0:
833 select_shape = i
834 break
835 elif shapetype == SHAPETYPE_POINT:
836 for i in shape_ids:
837 shape = layer.Shape(i)
838 x, y = shape.Points()[0]
839 if inverse:
840 x, y = inverse(x, y)
841 if forward:
842 x, y = forward(x, y)
843 x = x * scale + offx
844 y = -y * scale + offy
845 if hypot(px - x, py - y) < 5:
846 select_shape = i
847 break
848
849 if select_shape >= 0:
850 return layer, select_shape
851 return None, None
852
853 def SelectShapeAt(self, x, y, layer = None):
854 """\
855 Select and return the shape and its layer at window position (x, y)
856
857 If layer is given, only search in that layer. If no layer is
858 given, search through all layers.
859
860 Return a tuple (layer, shapeid). If no shape is found, return
861 (None, None).
862 """
863 layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
864 # If layer is None, then shape will also be None. We don't want
865 # to deselect the currently selected layer, so we simply select
866 # the already selected layer again.
867 if layer is None:
868 layer = self.selection.SelectedLayer()
869 shapes = []
870 else:
871 shapes = [shape]
872 self.selection.SelectShapes(layer, shapes)
873 return result
874
875 def LabelShapeAt(self, x, y):
876 """Add or remove a label at window position x, y.
877
878 If there's a label at the given position, remove it. Otherwise
879 determine the shape at the position, run the label dialog and
880 unless the user cancels the dialog, add a laber.
881 """
882 ox = x; oy = y
883 label_layer = self.map.LabelLayer()
884 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
885 if layer is None and shape_index is not None:
886 # a label was selected
887 label_layer.RemoveLabel(shape_index)
888 elif layer is not None:
889 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
890 if text:
891 proj = self.map.projection
892 if proj is not None:
893 map_proj = proj
894 else:
895 map_proj = None
896 proj = layer.projection
897 if proj is not None:
898 layer_proj = proj
899 else:
900 layer_proj = None
901
902 shapetype = layer.ShapeType()
903 if shapetype == SHAPETYPE_POLYGON:
904 x, y = shape_centroid(layer.shapefile.cobject(),
905 shape_index,
906 map_proj, layer_proj, 1, 1, 0, 0)
907 if map_proj is not None:
908 x, y = map_proj.Inverse(x, y)
909 else:
910 shape = layer.Shape(shape_index)
911 if shapetype == SHAPETYPE_POINT:
912 x, y = shape.Points()[0]
913 else:
914 # assume SHAPETYPE_ARC
915 points = shape.Points()
916 x, y = points[len(points) / 2]
917 if layer_proj is not None:
918 x, y = layer_proj.Inverse(x, y)
919 if shapetype == SHAPETYPE_POINT:
920 halign = ALIGN_LEFT
921 valign = ALIGN_CENTER
922 elif shapetype == SHAPETYPE_POLYGON:
923 halign = ALIGN_CENTER
924 valign = ALIGN_CENTER
925 elif shapetype == SHAPETYPE_ARC:
926 halign = ALIGN_LEFT
927 valign = ALIGN_CENTER
928 label_layer.AddLabel(x, y, text,
929 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