/[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 855 - (show annotations)
Wed May 7 18:24:27 2003 UTC (21 years, 10 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 32052 byte(s)
(MapCanvas.set_view_transform): Issue SCALE_CHANGED.

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