/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 824 - (hide 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 bh 404 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # 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 jonathan 799 import sys
15    
16 bh 6 from math import hypot
17    
18     from wxPython.wx import wxWindow,\
19     wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
20 jonathan 822 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
21     wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \
22     wxImageFromBitmap
23 bh 6
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 jonathan 565 MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
32 bh 6 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 bh 122 from Thuban.Lib.connector import Publisher
37 jonathan 433 from Thuban.Model.color import Color
38 bh 6
39 jonathan 822 import resource
40    
41 bh 535 from selection import Selection
42 bh 6 from renderer import ScreenRenderer, PrinterRender
43    
44     import labeldialog
45    
46 bh 535 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION
47 bh 6
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 bh 57 sx, sy = self.start
139     cx, cy = self.current
140 bh 288 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 bh 57 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 bh 6
151    
152     class ZoomOutTool(RectTool):
153    
154     """The Zoom-Out Tool"""
155 bh 239
156 bh 6 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 bh 288 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 bh 239 self.view.ZoomFactor(0.5, center = (cx, cy))
171 bh 57 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 bh 6
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 bh 159 sx, sy = self.start
188 bh 6 x, y = self.current
189     width, height = self.view.GetSizeTuple()
190 bh 159
191     bitmapdc = wx.wxMemoryDC()
192     bitmapdc.SelectObject(self.view.bitmap)
193    
194 bh 6 dc = self.view.drag_dc
195 bh 159 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
196 bh 6
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 bh 246
204 bh 6 class IdentifyTool(Tool):
205    
206     """The "Identify" Tool"""
207 bh 246
208 bh 6 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 bh 246
263 bh 122 class MapCanvas(wxWindow, Publisher):
264 bh 6
265     """A widget that displays a map and offers some interaction"""
266    
267 bh 535 # Some messages that can be subscribed/unsubscribed directly through
268 bh 789 # 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 bh 535 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 bh 6 wxWindow.__init__(self, parent, winid)
284     self.SetBackgroundColour(wxColour(255, 255, 255))
285 bh 125
286     # the map displayed in this canvas. Set with SetMap()
287 bh 6 self.map = None
288 bh 125
289     # scale and offset describe the transformation from projected
290     # coordinates to window coordinates.
291 bh 6 self.scale = 1.0
292     self.offset = (0, 0)
293 bh 125
294     # whether the user is currently dragging the mouse, i.e. moving
295     # the mouse while pressing a mouse button
296 bh 6 self.dragging = 0
297 bh 125
298     # the currently active tool
299 bh 6 self.tool = None
300 bh 125
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 bh 535 # the selection
309     self.selection = Selection()
310     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
311 bh 125
312 bh 174 # 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 bh 125 # subscribe the WX events we're interested in
318 bh 6 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 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
323 bh 125 wx.EVT_SIZE(self, self.OnSize)
324 bh 6
325 bh 122 def __del__(self):
326     wxWindow.__del__(self)
327     Publisher.__del__(self)
328    
329 bh 535 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 bh 6 def OnPaint(self, event):
361     dc = wxPaintDC(self)
362 jonathan 799 clear = self.map is None or not self.map.HasLayers()
363    
364 jonathan 822 #wxBeginBusyCursor()
365    
366 jonathan 799 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 bh 57 # If we've got no map or if the map is empty, simply clear
375     # the screen.
376 bh 246
377 bh 57 # 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 bh 246 dc.Clear()
383 bh 57 dc.EndDrawing()
384 bh 6
385 jonathan 822 #wxEndBusyCursor()
386    
387 bh 6 def do_redraw(self):
388 bh 145 # This should only be called if we have a non-empty map.
389 bh 125
390 bh 145 # Get the window size.
391 bh 6 width, height = self.GetSizeTuple()
392    
393 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
394     if self.bitmap is not None:
395     bitmap = self.bitmap
396 bh 6 else:
397 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
398     dc = wx.wxMemoryDC()
399     dc.SelectObject(bitmap)
400     dc.BeginDrawing()
401 bh 57
402 bh 125 # clear the background
403 jonathan 799 #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 bh 6
409 bh 535 selected_layer = self.selection.SelectedLayer()
410     selected_shapes = self.selection.SelectedShapes()
411 bh 57
412 bh 125 # draw the map into the bitmap
413     renderer = ScreenRenderer(dc, self.scale, self.offset)
414 bh 149
415 bh 296 # Pass the entire bitmap as update region to the renderer.
416 bh 149 # We're redrawing the whole bitmap, after all.
417     renderer.RenderMap(self.map, (0, 0, width, height),
418 bh 535 selected_layer, selected_shapes)
419 bh 125
420     dc.EndDrawing()
421     dc.SelectObject(wx.wxNullBitmap)
422     self.bitmap = bitmap
423    
424 bh 57 # blit the bitmap to the screen
425 bh 125 dc = wx.wxMemoryDC()
426     dc.SelectObject(bitmap)
427 bh 6 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 bh 246
438 bh 6 def SetMap(self, map):
439 jonathan 565 redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
440 bh 6 LAYER_VISIBILITY_CHANGED)
441     if self.map is not None:
442     for channel in redraw_channels:
443 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
444 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
445     self.projection_changed)
446     self.map = map
447 bh 535 self.selection.ClearSelection()
448 bh 6 if self.map is not None:
449     for channel in redraw_channels:
450 bh 125 self.map.Subscribe(channel, self.full_redraw)
451 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
452     self.FitMapToWindow()
453 bh 57 # 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 bh 125 self.full_redraw()
457 bh 6
458     def Map(self):
459 bh 295 """Return the map displayed by this canvas"""
460 bh 6 return self.map
461    
462     def redraw(self, *args):
463     self.Refresh(0)
464    
465 bh 125 def full_redraw(self, *args):
466     self.bitmap = None
467     self.redraw()
468    
469 bh 6 def projection_changed(self, *args):
470     self.FitMapToWindow()
471 bh 125 self.full_redraw()
472 bh 6
473     def set_view_transform(self, scale, offset):
474     self.scale = scale
475     self.offset = offset
476 bh 125 self.full_redraw()
477 bh 6
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 bh 293 """Fit the rectangular region given by rect into the window.
494 bh 535
495 bh 293 Set scale so that rect (in projected coordinates) just fits into
496     the window and center it.
497     """
498 bh 6 width, height = self.GetSizeTuple()
499     llx, lly, urx, ury = rect
500 bh 45 if llx == urx or lly == ury:
501 jonathan 799 # zero width or zero height. Do Nothing
502 bh 45 return
503 bh 6 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 bh 293 """Fit the map to the window
512 bh 535
513 bh 293 Set the scale so that the map fits exactly into the window and
514     center it in the window.
515 bh 6 """
516     bbox = self.map.ProjectedBoundingBox()
517     if bbox is not None:
518     self.FitRectToWindow(bbox)
519    
520 jonathan 822 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 jonathan 824 if bbox is not None:
534     self.FitRectToWindow(bbox)
535    
536 bh 57 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 bh 6 width, height = self.GetSizeTuple()
544     scale = self.scale * factor
545     offx, offy = self.offset
546 bh 57 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 bh 6 self.set_view_transform(scale, offset)
554    
555     def ZoomOutToRect(self, rect):
556 bh 293 """Zoom out to fit the currently visible region into rect.
557 bh 6
558 bh 293 The rect parameter is given in window coordinates
559     """
560 bh 6 # 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 bh 295 """Move the map by dx, dy pixels"""
577 bh 6 offx, offy = self.offset
578     self.set_view_transform(self.scale, (offx + dx, offy + dy))
579    
580 bh 356 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 bh 6 def ZoomInTool(self):
589 bh 295 """Start the zoom in tool"""
590 bh 356 self.SelectTool(ZoomInTool(self))
591 bh 6
592     def ZoomOutTool(self):
593 bh 295 """Start the zoom out tool"""
594 bh 356 self.SelectTool(ZoomOutTool(self))
595 bh 6
596     def PanTool(self):
597 bh 295 """Start the pan tool"""
598 bh 356 self.SelectTool(PanTool(self))
599 jonathan 822 #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 bh 6
608     def IdentifyTool(self):
609 bh 295 """Start the identify tool"""
610 bh 356 self.SelectTool(IdentifyTool(self))
611 bh 6
612     def LabelTool(self):
613 bh 295 """Start the label tool"""
614 bh 356 self.SelectTool(LabelTool(self))
615 bh 6
616     def CurrentTool(self):
617 bh 295 """Return the name of the current tool or None if no tool is active"""
618 bh 6 return self.tool and self.tool.Name() or None
619    
620 bh 122 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 bh 6 def OnLeftDown(self, event):
646 bh 122 self.set_current_position(event)
647 bh 6 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 bh 246
656 bh 6 def OnLeftUp(self, event):
657 bh 122 self.set_current_position(event)
658 bh 6 if self.dragging:
659 bh 261 self.ReleaseMouse()
660 bh 404 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 bh 6
667     def OnMotion(self, event):
668 bh 122 self.set_current_position(event)
669 bh 6 if self.dragging:
670     self.tool.Hide(self.drag_dc)
671     self.tool.MouseMove(event)
672     self.tool.Show(self.drag_dc)
673    
674 bh 122 def OnLeaveWindow(self, event):
675     self.set_current_position(None)
676    
677 bh 125 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 jonathan 799 pass
686 bh 125
687 bh 6 def shape_selected(self, layer, shape):
688 bh 535 """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 bh 6
697 bh 301 def unprojected_rect_around_point(self, x, y, dist):
698     """return a rect dist pixels around (x, y) in unprojected corrdinates
699 bh 159
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 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
713 bh 159 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 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
720 bh 43 """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 bh 246 If the optional parameter searched_layer is given (or not None
730     which it defaults to), only search in that layer.
731 bh 43 """
732 bh 6 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 bh 246
744 bh 6 if labels:
745     dc = wxClientDC(self)
746     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
747     dc.SetFont(font)
748 bh 60 for i in range(len(labels) - 1, -1, -1):
749 bh 6 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 bh 43
775 bh 246 if searched_layer:
776     layers = [searched_layer]
777 bh 43 else:
778     layers = self.map.Layers()
779    
780 bh 6 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 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
788 jonathan 610 is not Color.Transparent
789 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
790 jonathan 610 is not Color.Transparent
791 bh 246
792 bh 6 layer_proj = layer.projection
793     if layer_proj is not None:
794     inverse = layer_proj.Inverse
795     else:
796     inverse = None
797 bh 246
798 bh 6 shapetype = layer.ShapeType()
799    
800     select_shape = -1
801 bh 159
802 bh 301 # 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 bh 159 shape_ids = layer.ShapesInRegion(box)
813     shape_ids.reverse()
814    
815 bh 6 if shapetype == SHAPETYPE_POLYGON:
816 bh 159 for i in shape_ids:
817 bh 6 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 bh 159 for i in shape_ids:
828 bh 6 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 bh 159 for i in shape_ids:
838 bh 6 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 bh 246 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 bh 43 # 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 bh 535 layer = self.selection.SelectedLayer()
870     shapes = []
871     else:
872     shapes = [shape]
873     self.selection.SelectShapes(layer, shapes)
874 bh 246 return result
875 bh 6
876     def LabelShapeAt(self, x, y):
877 bh 295 """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 bh 6 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