/[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 822 - (hide 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 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     if bbox is not None:
533     self.FitRectToWindow(bbox)
534    
535 bh 57 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 bh 6 width, height = self.GetSizeTuple()
543     scale = self.scale * factor
544     offx, offy = self.offset
545 bh 57 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 bh 6 self.set_view_transform(scale, offset)
553    
554     def ZoomOutToRect(self, rect):
555 bh 293 """Zoom out to fit the currently visible region into rect.
556 bh 6
557 bh 293 The rect parameter is given in window coordinates
558     """
559 bh 6 # 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 bh 295 """Move the map by dx, dy pixels"""
576 bh 6 offx, offy = self.offset
577     self.set_view_transform(self.scale, (offx + dx, offy + dy))
578    
579 bh 356 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 bh 6 def ZoomInTool(self):
588 bh 295 """Start the zoom in tool"""
589 bh 356 self.SelectTool(ZoomInTool(self))
590 bh 6
591     def ZoomOutTool(self):
592 bh 295 """Start the zoom out tool"""
593 bh 356 self.SelectTool(ZoomOutTool(self))
594 bh 6
595     def PanTool(self):
596 bh 295 """Start the pan tool"""
597 bh 356 self.SelectTool(PanTool(self))
598 jonathan 822 #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 bh 6
607     def IdentifyTool(self):
608 bh 295 """Start the identify tool"""
609 bh 356 self.SelectTool(IdentifyTool(self))
610 bh 6
611     def LabelTool(self):
612 bh 295 """Start the label tool"""
613 bh 356 self.SelectTool(LabelTool(self))
614 bh 6
615     def CurrentTool(self):
616 bh 295 """Return the name of the current tool or None if no tool is active"""
617 bh 6 return self.tool and self.tool.Name() or None
618    
619 bh 122 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 bh 6 def OnLeftDown(self, event):
645 bh 122 self.set_current_position(event)
646 bh 6 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 bh 246
655 bh 6 def OnLeftUp(self, event):
656 bh 122 self.set_current_position(event)
657 bh 6 if self.dragging:
658 bh 261 self.ReleaseMouse()
659 bh 404 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 bh 6
666     def OnMotion(self, event):
667 bh 122 self.set_current_position(event)
668 bh 6 if self.dragging:
669     self.tool.Hide(self.drag_dc)
670     self.tool.MouseMove(event)
671     self.tool.Show(self.drag_dc)
672    
673 bh 122 def OnLeaveWindow(self, event):
674     self.set_current_position(None)
675    
676 bh 125 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 jonathan 799 pass
685 bh 125
686 bh 6 def shape_selected(self, layer, shape):
687 bh 535 """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 bh 6
696 bh 301 def unprojected_rect_around_point(self, x, y, dist):
697     """return a rect dist pixels around (x, y) in unprojected corrdinates
698 bh 159
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 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
712 bh 159 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 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
719 bh 43 """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 bh 246 If the optional parameter searched_layer is given (or not None
729     which it defaults to), only search in that layer.
730 bh 43 """
731 bh 6 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 bh 246
743 bh 6 if labels:
744     dc = wxClientDC(self)
745     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
746     dc.SetFont(font)
747 bh 60 for i in range(len(labels) - 1, -1, -1):
748 bh 6 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 bh 43
774 bh 246 if searched_layer:
775     layers = [searched_layer]
776 bh 43 else:
777     layers = self.map.Layers()
778    
779 bh 6 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 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
787 jonathan 610 is not Color.Transparent
788 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
789 jonathan 610 is not Color.Transparent
790 bh 246
791 bh 6 layer_proj = layer.projection
792     if layer_proj is not None:
793     inverse = layer_proj.Inverse
794     else:
795     inverse = None
796 bh 246
797 bh 6 shapetype = layer.ShapeType()
798    
799     select_shape = -1
800 bh 159
801 bh 301 # 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 bh 159 shape_ids = layer.ShapesInRegion(box)
812     shape_ids.reverse()
813    
814 bh 6 if shapetype == SHAPETYPE_POLYGON:
815 bh 159 for i in shape_ids:
816 bh 6 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 bh 159 for i in shape_ids:
827 bh 6 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 bh 159 for i in shape_ids:
837 bh 6 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 bh 246 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 bh 43 # 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 bh 535 layer = self.selection.SelectedLayer()
869     shapes = []
870     else:
871     shapes = [shape]
872     self.selection.SelectShapes(layer, shapes)
873 bh 246 return result
874 bh 6
875     def LabelShapeAt(self, x, y):
876 bh 295 """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 bh 6 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