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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1464 - (hide annotations)
Fri Jul 18 18:20:40 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/viewport.py
File MIME type: text/x-python
File size: 30543 byte(s)
* Thuban/UI/messages.py (MAP_REPLACED): New message.

* Thuban/UI/viewport.py (ViewPort.SetMap): Issue MAP_REPLACED
after the new map has been assigned

* Thuban/UI/mainwindow.py (MainWindow.delegated_messages):
Delegate MAP_REPLACED to the canvas too
(MainWindow.prepare_new_session): Removed. Thanks to the new
MAP_REPLACED message it's no longer needed
(MainWindow.OpenSession, MainWindow.NewSession):
prepare_new_session has been removed.

* Thuban/UI/classifier.py (Classifier.__init__): Subscribe to
MAP_REPLACED so that we can close the dialog if a new map is set.
(Classifier.unsubscribe_messages): Unsubscribe from MAP_REPLACED
(Classifier.map_replaced): Handle MAP_REPLACED by closing the
dialog

* test/test_viewport.py (SimpleViewPortTest)
(SimpleViewPortTest.test_default_size): Add doc-strings
(ViewPortTest.setUp): Bind map to self.map so we can use it in
tests. Subscribe to MAP_REPLACED messages too.
(ViewPortTest.tearDown): No need to explicitly unsubscribe
(ViewPortTest.test_set_map): New test for the SetMap method.

1 jonathan 1386 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2     # Authors:
3     # Bernhard Herzog <[email protected]>
4     # Frank Koormann <[email protected]>
5     #
6     # This program is free software under the GPL (>=v2)
7     # Read the file COPYING coming with Thuban for details.
8    
9     """
10     Classes for display of a map and interaction with it
11     """
12    
13     __version__ = "$Revision$"
14    
15     from math import hypot
16    
17     from wxproj import point_in_polygon_shape, shape_centroid
18    
19     from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
20 bh 1456 LAYER_PROJECTION_CHANGED
21 jonathan 1386 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
22     SHAPETYPE_POINT
23     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
24     ALIGN_LEFT, ALIGN_RIGHT
25     from Thuban.Lib.connector import Publisher
26 bh 1456 from Thuban.Model.color import Transparent
27 jonathan 1386
28     from selection import Selection
29    
30     from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
31 bh 1464 SCALE_CHANGED, MAP_REPLACED
32 jonathan 1386
33     #
34     # The tools
35     #
36    
37     class Tool:
38    
39     """
40     Base class for the interactive tools
41     """
42    
43     def __init__(self, view):
44     """Intitialize the tool. The view is the canvas displaying the map"""
45     self.view = view
46     self.start = self.current = None
47     self.dragging = 0
48     self.drawn = 0
49    
50     def __del__(self):
51     del self.view
52    
53     def Name(self):
54     """Return the tool's name"""
55     return ''
56    
57     def drag_start(self, x, y):
58     self.start = self.current = x, y
59     self.dragging = 1
60    
61     def drag_move(self, x, y):
62     self.current = x, y
63    
64     def drag_stop(self, x, y):
65     self.current = x, y
66     self.dragging = 0
67    
68     def Show(self, dc):
69     if not self.drawn:
70     self.draw(dc)
71     self.drawn = 1
72    
73     def Hide(self, dc):
74     if self.drawn:
75     self.draw(dc)
76     self.drawn = 0
77    
78     def draw(self, dc):
79     pass
80    
81     def MouseDown(self, event):
82     self.drag_start(event.m_x, event.m_y)
83    
84     def MouseMove(self, event):
85     if self.dragging:
86     self.drag_move(event.m_x, event.m_y)
87    
88     def MouseUp(self, event):
89     if self.dragging:
90 jonathan 1405 self.drag_stop(event.m_x, event.m_y)
91 jonathan 1386
92     def Cancel(self):
93     self.dragging = 0
94    
95    
96     class RectTool(Tool):
97    
98     """Base class for tools that draw rectangles while dragging"""
99    
100     def draw(self, dc):
101     sx, sy = self.start
102     cx, cy = self.current
103     dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
104    
105     class ZoomInTool(RectTool):
106    
107     """The Zoom-In Tool"""
108    
109     def Name(self):
110     return "ZoomInTool"
111    
112     def proj_rect(self):
113     """return the rectangle given by start and current in projected
114     coordinates"""
115     sx, sy = self.start
116     cx, cy = self.current
117     left, top = self.view.win_to_proj(sx, sy)
118     right, bottom = self.view.win_to_proj(cx, cy)
119     return (min(left, right), min(top, bottom),
120     max(left, right), max(top, bottom))
121    
122     def MouseUp(self, event):
123     if self.dragging:
124     Tool.MouseUp(self, event)
125     sx, sy = self.start
126     cx, cy = self.current
127     if sx == cx or sy == cy:
128     # Just a mouse click or a degenerate rectangle. Simply
129     # zoom in by a factor of two
130     # FIXME: For a click this is the desired behavior but should we
131     # really do this for degenrate rectagles as well or
132     # should we ignore them?
133     self.view.ZoomFactor(2, center = (cx, cy))
134     else:
135     # A drag. Zoom in to the rectangle
136     self.view.FitRectToWindow(self.proj_rect())
137    
138    
139     class ZoomOutTool(RectTool):
140    
141     """The Zoom-Out Tool"""
142    
143     def Name(self):
144     return "ZoomOutTool"
145    
146     def MouseUp(self, event):
147     if self.dragging:
148     Tool.MouseUp(self, event)
149     sx, sy = self.start
150     cx, cy = self.current
151     if sx == cx or sy == cy:
152     # Just a mouse click or a degenerate rectangle. Simply
153     # zoom out by a factor of two.
154     # FIXME: For a click this is the desired behavior but should we
155     # really do this for degenrate rectagles as well or
156     # should we ignore them?
157     self.view.ZoomFactor(0.5, center = (cx, cy))
158     else:
159     # A drag. Zoom out to the rectangle
160     self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
161     max(sx, cx), max(sy, cy)))
162    
163     class PanTool(Tool):
164    
165     """The Pan Tool"""
166    
167     def Name(self):
168     return "PanTool"
169    
170     def MouseMove(self, event):
171     if self.dragging:
172     Tool.MouseMove(self, event)
173    
174     def MouseUp(self, event):
175     if self.dragging:
176     Tool.MouseUp(self, event)
177     sx, sy = self.start
178     cx, cy = self.current
179     self.view.Translate(cx - sx, cy - sy)
180    
181     class IdentifyTool(Tool):
182    
183     """The "Identify" Tool"""
184    
185     def Name(self):
186     return "IdentifyTool"
187    
188     def MouseUp(self, event):
189     self.view.SelectShapeAt(event.m_x, event.m_y)
190    
191    
192     class LabelTool(Tool):
193    
194     """The "Label" Tool"""
195    
196     def Name(self):
197     return "LabelTool"
198    
199     def MouseUp(self, event):
200     self.view.LabelShapeAt(event.m_x, event.m_y)
201    
202    
203     class ViewPort(Publisher):
204    
205     """An abstract view of the main window"""
206    
207     # Some messages that can be subscribed/unsubscribed directly through
208     # the MapCanvas come in fact from other objects. This is a dict
209     # mapping those messages to the names of the instance variables they
210     # actually come from. The delegation is implemented in the Subscribe
211     # and Unsubscribe methods
212     delegated_messages = {LAYER_SELECTED: "selection",
213     SHAPES_SELECTED: "selection"}
214    
215     # Methods delegated to some instance variables. The delegation is
216     # implemented in the __getattr__ method.
217     delegated_methods = {"SelectLayer": "selection",
218     "SelectShapes": "selection",
219     "SelectedLayer": "selection",
220     "HasSelectedLayer": "selection",
221     "HasSelectedShapes": "selection",
222     "SelectedShapes": "selection"}
223    
224     def __init__(self, size = (400, 300)):
225    
226     self.size = size
227    
228     # the map displayed in this canvas. Set with SetMap()
229     self.map = None
230    
231     # scale and offset describe the transformation from projected
232     # coordinates to window coordinates.
233     self.scale = 1.0
234     self.offset = (0, 0)
235    
236     # whether the user is currently dragging the mouse, i.e. moving
237     # the mouse while pressing a mouse button
238     self.dragging = 0
239    
240     # the currently active tool
241     self.tool = None
242    
243     # The current mouse position of the last OnMotion event or None
244     # if the mouse is outside the window.
245     self.current_position = None
246    
247     # the selection
248     self.selection = Selection()
249     self.selection.Subscribe(SHAPES_SELECTED, self.shape_selected)
250    
251     # keep track of which layers/shapes are selected to make sure we
252     # only redraw when necessary
253     self.last_selected_layer = None
254     self.last_selected_shape = None
255    
256     def Destroy(self):
257     #import traceback
258     #traceback.print_stack()
259     self.map = None
260     self.selection.Destroy()
261     self.tool = None
262     #self.selection.Unsubscribe(SHAPES_SELECTED, self.shape_selected)
263    
264     def Subscribe(self, channel, *args):
265     """Extend the inherited method to handle delegated messages.
266    
267     If channel is one of the delegated messages call the appropriate
268     object's Subscribe method. Otherwise just call the inherited
269     method.
270     """
271     if channel in self.delegated_messages:
272     object = getattr(self, self.delegated_messages[channel])
273     object.Subscribe(channel, *args)
274     else:
275     Publisher.Subscribe(self, channel, *args)
276    
277     def Unsubscribe(self, channel, *args):
278     """Extend the inherited method to handle delegated messages.
279    
280     If channel is one of the delegated messages call the appropriate
281     object's Unsubscribe method. Otherwise just call the inherited
282     method.
283     """
284     if channel in self.delegated_messages:
285     object = getattr(self, self.delegated_messages[channel])
286     object.Unsubscribe(channel, *args)
287     else:
288     Publisher.Unsubscribe(self, channel, *args)
289    
290     def __getattr__(self, attr):
291     if attr in self.delegated_methods:
292     return getattr(getattr(self, self.delegated_methods[attr]), attr)
293     raise AttributeError(attr)
294    
295     def SetMap(self, map):
296     if self.map is not None:
297     self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
298     self.map_projection_changed)
299     self.map.Unsubscribe(LAYER_PROJECTION_CHANGED,
300     self.layer_projection_changed)
301 bh 1464 changed = self.map is not map
302 jonathan 1386 self.map = map
303     self.selection.ClearSelection()
304     if self.map is not None:
305 bh 1464 self.map.Subscribe(MAP_PROJECTION_CHANGED,
306     self.map_projection_changed)
307     self.map.Subscribe(LAYER_PROJECTION_CHANGED,
308     self.layer_projection_changed)
309 jonathan 1386 self.FitMapToWindow()
310 bh 1464 self.issue(MAP_REPLACED)
311 jonathan 1386
312     def Map(self):
313     """Return the map displayed by this canvas"""
314     return self.map
315    
316     def map_projection_changed(self, map, old_proj):
317    
318     proj = self.map.GetProjection()
319    
320     bbox = None
321    
322     if old_proj is not None and proj is not None:
323     width, height = self.GetPortSizeTuple()
324     llx, lly = self.win_to_proj(0, height)
325     urx, ury = self.win_to_proj(width, 0)
326     bbox = old_proj.Inverse(llx, lly) + old_proj.Inverse(urx, ury)
327     bbox = proj.ForwardBBox(bbox)
328    
329     if bbox is not None:
330     self.FitRectToWindow(bbox)
331     else:
332     self.FitMapToWindow()
333    
334     def layer_projection_changed(self, *args):
335     pass
336    
337     def set_view_transform(self, scale, offset):
338     # width/height of the projected bbox
339     llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()
340     pwidth = float(urx - llx)
341     pheight = float(ury - lly)
342    
343     # width/height of the window
344     wwidth, wheight = self.GetPortSizeTuple()
345    
346     # The window's center in projected coordinates assuming the new
347     # scale/offset
348     pcenterx = (wwidth/2 - offset[0]) / scale
349     pcentery = (offset[1] - wheight/2) / scale
350    
351     # The window coordinates used when drawing the shapes must fit
352     # into 16bit signed integers.
353     max_len = max(pwidth, pheight)
354     if max_len:
355     max_scale = 32767.0 / max_len
356     else:
357     # FIXME: What to do in this case? The bbox is effectively
358     # empty so any scale should work.
359     max_scale = scale
360    
361     # The minimal scale is somewhat arbitrarily set to half that of
362     # the bbox fit into the window
363     scales = []
364     if pwidth:
365     scales.append(wwidth / pwidth)
366     if pheight:
367     scales.append(wheight / pheight)
368     if scales:
369     min_scale = 0.5 * min(scales)
370     else:
371     min_scale = scale
372    
373     if scale > max_scale:
374     scale = max_scale
375     elif scale < min_scale:
376     scale = min_scale
377    
378     self.scale = scale
379    
380     # determine new offset to preserve the center
381     self.offset = (wwidth/2 - scale * pcenterx,
382     wheight/2 + scale * pcentery)
383     self.issue(SCALE_CHANGED, scale)
384    
385     def GetPortSizeTuple(self):
386     return self.size
387    
388     def proj_to_win(self, x, y):
389     """\
390     Return the point in window coords given by projected coordinates x y
391     """
392     offx, offy = self.offset
393     return (self.scale * x + offx, -self.scale * y + offy)
394    
395     def win_to_proj(self, x, y):
396     """\
397     Return the point in projected coordinates given by window coords x y
398     """
399     offx, offy = self.offset
400     return ((x - offx) / self.scale, (offy - y) / self.scale)
401    
402     def FitRectToWindow(self, rect):
403     """Fit the rectangular region given by rect into the window.
404    
405     Set scale so that rect (in projected coordinates) just fits into
406     the window and center it.
407     """
408     width, height = self.GetPortSizeTuple()
409     llx, lly, urx, ury = rect
410     if llx == urx or lly == ury:
411     # zero width or zero height. Do Nothing
412     return
413     scalex = width / (urx - llx)
414     scaley = height / (ury - lly)
415     scale = min(scalex, scaley)
416     offx = 0.5 * (width - (urx + llx) * scale)
417     offy = 0.5 * (height + (ury + lly) * scale)
418     self.set_view_transform(scale, (offx, offy))
419    
420     def FitMapToWindow(self):
421     """Fit the map to the window
422    
423     Set the scale so that the map fits exactly into the window and
424     center it in the window.
425     """
426     if self.map is not None:
427     bbox = self.map.ProjectedBoundingBox()
428     if bbox is not None:
429     self.FitRectToWindow(bbox)
430    
431     def FitLayerToWindow(self, layer):
432     """Fit the given layer to the window.
433    
434     Set the scale so that the layer fits exactly into the window and
435     center it in the window.
436     """
437    
438     bbox = layer.LatLongBoundingBox()
439     if bbox is not None:
440     proj = self.map.GetProjection()
441     if proj is not None:
442     bbox = proj.ForwardBBox(bbox)
443    
444     if bbox is not None:
445     self.FitRectToWindow(bbox)
446    
447     def FitSelectedToWindow(self):
448     layer = self.selection.SelectedLayer()
449     shapes = self.selection.SelectedShapes()
450    
451     bbox = layer.ShapesBoundingBox(shapes)
452     if bbox is not None:
453     proj = self.map.GetProjection()
454     if proj is not None:
455     bbox = proj.ForwardBBox(bbox)
456    
457     if bbox is not None:
458     if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:
459     self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))
460     else:
461     self.FitRectToWindow(bbox)
462    
463     def ZoomFactor(self, factor, center = None):
464     """Multiply the zoom by factor and center on center.
465    
466     The optional parameter center is a point in window coordinates
467     that should be centered. If it is omitted, it defaults to the
468     center of the window
469     """
470     width, height = self.GetPortSizeTuple()
471     scale = self.scale * factor
472     offx, offy = self.offset
473     if center is not None:
474     cx, cy = center
475     else:
476     cx = width / 2
477     cy = height / 2
478     offset = (factor * (offx - cx) + width / 2,
479     factor * (offy - cy) + height / 2)
480     self.set_view_transform(scale, offset)
481    
482     def ZoomOutToRect(self, rect):
483     """Zoom out to fit the currently visible region into rect.
484    
485     The rect parameter is given in window coordinates
486     """
487     # determine the bbox of the displayed region in projected
488     # coordinates
489     width, height = self.GetPortSizeTuple()
490     llx, lly = self.win_to_proj(0, height - 1)
491     urx, ury = self.win_to_proj(width - 1, 0)
492    
493     sx, sy, ex, ey = rect
494     scalex = (ex - sx) / (urx - llx)
495     scaley = (ey - sy) / (ury - lly)
496     scale = min(scalex, scaley)
497    
498     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
499     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
500     self.set_view_transform(scale, (offx, offy))
501    
502     def Translate(self, dx, dy):
503     """Move the map by dx, dy pixels"""
504     offx, offy = self.offset
505     self.set_view_transform(self.scale, (offx + dx, offy + dy))
506    
507     def SelectTool(self, tool):
508     """Make tool the active tool.
509    
510     The parameter should be an instance of Tool or None to indicate
511     that no tool is active.
512     """
513     self.tool = tool
514    
515     def ZoomInTool(self):
516     """Start the zoom in tool"""
517     self.SelectTool(ZoomInTool(self))
518    
519     def ZoomOutTool(self):
520     """Start the zoom out tool"""
521     self.SelectTool(ZoomOutTool(self))
522    
523     def PanTool(self):
524     """Start the pan tool"""
525     self.SelectTool(PanTool(self))
526     #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
527     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
528     #print bmp
529     #img = wxImageFromBitmap(bmp)
530     #print img
531     #cur = wxCursor(img)
532     #print cur
533     #self.SetCursor(cur)
534    
535     def IdentifyTool(self):
536     """Start the identify tool"""
537     self.SelectTool(IdentifyTool(self))
538    
539     def LabelTool(self):
540     """Start the label tool"""
541     self.SelectTool(LabelTool(self))
542    
543     def CurrentTool(self):
544     """Return the name of the current tool or None if no tool is active"""
545     return self.tool and self.tool.Name() or None
546    
547     def CurrentPosition(self):
548     """Return current position of the mouse in projected coordinates.
549    
550     The result is a 2-tuple of floats with the coordinates. If the
551     mouse is not in the window, the result is None.
552     """
553     if self.current_position is not None:
554     x, y = self.current_position
555     return self.win_to_proj(x, y)
556     else:
557     return None
558    
559     def set_current_position(self, event):
560     """Set the current position from event
561    
562     Should be called by all events that contain mouse positions
563     especially EVT_MOTION. The event parameter may be None to
564     indicate the the pointer left the window.
565     """
566     if event is not None:
567     self.current_position = (event.m_x, event.m_y)
568     else:
569     self.current_position = None
570     self.issue(VIEW_POSITION)
571    
572     def MouseLeftDown(self, event):
573     self.set_current_position(event)
574     if self.tool is not None:
575     self.tool.MouseDown(event)
576    
577     def MouseLeftUp(self, event):
578     self.set_current_position(event)
579     if self.tool is not None:
580     self.tool.MouseUp(event)
581    
582     def MouseMove(self, event):
583     self.set_current_position(event)
584     if self.tool is not None:
585     self.tool.MouseMove(event)
586    
587     def shape_selected(self, layer, shape):
588     """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
589     # The selection object takes care that it only issues
590     # SHAPES_SELECTED messages when the set of selected shapes has
591     # actually changed, so we can do a full redraw unconditionally.
592     # FIXME: We should perhaps try to limit the redraw to the are
593     # actually covered by the shapes before and after the selection
594     # change.
595     pass
596    
597     def unprojected_rect_around_point(self, x, y, dist):
598     """Return a rect dist pixels around (x, y) in unprojected coordinates
599    
600     The return value is a tuple (minx, miny, maxx, maxy) suitable a
601     parameter to a layer's ShapesInRegion method.
602     """
603     map_proj = self.map.projection
604     if map_proj is not None:
605     inverse = map_proj.Inverse
606     else:
607     inverse = None
608    
609     xs = []
610     ys = []
611     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
612     px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
613     if inverse:
614     px, py = inverse(px, py)
615     xs.append(px)
616     ys.append(py)
617     return (min(xs), min(ys), max(xs), max(ys))
618    
619     def GetTextExtent(self, text):
620     # arbitrary numbers, really just so the tests pass
621     return 40, 20
622    
623     def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
624     """Determine the shape at point px, py in window coords
625    
626     Return the shape and the corresponding layer as a tuple (layer,
627     shape).
628    
629     If the optional parameter select_labels is true (default false)
630     search through the labels. If a label is found return it's index
631     as the shape and None as the layer.
632    
633     If the optional parameter searched_layer is given (or not None
634     which it defaults to), only search in that layer.
635     """
636     map_proj = self.map.projection
637     if map_proj is not None:
638     forward = map_proj.Forward
639     else:
640     forward = None
641    
642     scale = self.scale
643    
644     offx, offy = self.offset
645    
646     if select_labels:
647     labels = self.map.LabelLayer().Labels()
648    
649     if labels:
650     for i in range(len(labels) - 1, -1, -1):
651     label = labels[i]
652     x = label.x
653     y = label.y
654     text = label.text
655     if forward:
656     x, y = forward(x, y)
657     x = x * scale + offx
658     y = -y * scale + offy
659     width, height = self.GetTextExtent(text)
660     if label.halign == ALIGN_LEFT:
661     # nothing to be done
662     pass
663     elif label.halign == ALIGN_RIGHT:
664     x = x - width
665     elif label.halign == ALIGN_CENTER:
666     x = x - width/2
667     if label.valign == ALIGN_TOP:
668     # nothing to be done
669     pass
670     elif label.valign == ALIGN_BOTTOM:
671     y = y - height
672     elif label.valign == ALIGN_CENTER:
673     y = y - height/2
674     if x <= px < x + width and y <= py <= y + height:
675     return None, i
676    
677     if searched_layer:
678     layers = [searched_layer]
679     else:
680     layers = self.map.Layers()
681    
682     for layer_index in range(len(layers) - 1, -1, -1):
683     layer = layers[layer_index]
684    
685     # search only in visible layers
686     if not layer.Visible() or not layer.HasShapes():
687     continue
688    
689     table = layer.ShapeStore().Table()
690     lc = layer.GetClassification()
691 bh 1452 field = layer.GetClassificationColumn()
692 jonathan 1386
693     # defaults to fall back on
694     filled = lc.GetDefaultFill() is not Transparent
695     stroked = lc.GetDefaultLineColor() is not Transparent
696    
697     layer_proj = layer.projection
698     if layer_proj is not None:
699     inverse = layer_proj.Inverse
700     else:
701     inverse = None
702    
703     shapetype = layer.ShapeType()
704    
705     select_shape = -1
706    
707     # Determine the ids of the shapes that overlap a tiny area
708     # around the point. For layers containing points we have to
709     # choose a larger size of the box we're testing agains so
710     # that we take the size of the markers into account
711     # FIXME: Once the markers are more flexible this part has to
712     # become more flexible too, of course
713     if shapetype == SHAPETYPE_POINT:
714     box = self.unprojected_rect_around_point(px, py, 5)
715     else:
716     box = self.unprojected_rect_around_point(px, py, 1)
717     shape_ids = layer.ShapesInRegion(box)
718     shape_ids.reverse()
719    
720     if shapetype == SHAPETYPE_POLYGON:
721     for i in shape_ids:
722     shapefile = layer.ShapeStore().Shapefile().cobject()
723     if field:
724     record = table.ReadRowAsDict(i)
725     group = lc.FindGroup(record[field])
726     props = group.GetProperties()
727     filled = props.GetFill() is not Transparent
728     stroked = props.GetLineColor() is not Transparent
729     result = point_in_polygon_shape(shapefile, i,
730     filled, stroked,
731     map_proj, layer_proj,
732     scale, -scale, offx, offy,
733     px, py)
734     if result:
735     select_shape = i
736     break
737     elif shapetype == SHAPETYPE_ARC:
738     for i in shape_ids:
739     shapefile = layer.ShapeStore().Shapefile().cobject()
740     if field:
741     record = table.ReadRowAsDict(i)
742     group = lc.FindGroup(record[field])
743     props = group.GetProperties()
744     stroked = props.GetLineColor() is not Transparent
745     result = point_in_polygon_shape(shapefile,
746     i, 0, stroked,
747     map_proj, layer_proj,
748     scale, -scale, offx, offy,
749     px, py)
750     if result < 0:
751     select_shape = i
752     break
753     elif shapetype == SHAPETYPE_POINT:
754     for i in shape_ids:
755     shape = layer.Shape(i)
756     x, y = shape.Points()[0]
757     if inverse:
758     x, y = inverse(x, y)
759     if forward:
760     x, y = forward(x, y)
761     x = x * scale + offx
762     y = -y * scale + offy
763    
764     if field:
765     record = table.ReadRowAsDict(i)
766     group = lc.FindGroup(record[field])
767     props = group.GetProperties()
768     filled = props.GetFill() is not Transparent
769     stroked = props.GetLineColor() is not Transparent
770    
771     if hypot(px - x, py - y) < 5 and (filled or stroked):
772     select_shape = i
773     break
774    
775     if select_shape >= 0:
776     return layer, select_shape
777     return None, None
778    
779     def SelectShapeAt(self, x, y, layer = None):
780     """\
781     Select and return the shape and its layer at window position (x, y)
782    
783     If layer is given, only search in that layer. If no layer is
784     given, search through all layers.
785    
786     Return a tuple (layer, shapeid). If no shape is found, return
787     (None, None).
788     """
789     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
790     # If layer is None, then shape will also be None. We don't want
791     # to deselect the currently selected layer, so we simply select
792     # the already selected layer again.
793     if layer is None:
794     layer = self.selection.SelectedLayer()
795     shapes = []
796     else:
797     shapes = [shape]
798     self.selection.SelectShapes(layer, shapes)
799     return result
800    
801     def LabelShapeAt(self, x, y, text = None):
802     """Add or remove a label at window position x, y.
803    
804     If there's a label at the given position, remove it. Otherwise
805     determine the shape at the position and add a label.
806    
807     Return True is an action was performed, False otherwise.
808     """
809     label_layer = self.map.LabelLayer()
810     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
811     if layer is None and shape_index is not None:
812     # a label was selected
813     label_layer.RemoveLabel(shape_index)
814     return True
815     elif layer is not None and text:
816     proj = self.map.projection
817     if proj is not None:
818     map_proj = proj
819     else:
820     map_proj = None
821     proj = layer.projection
822     if proj is not None:
823     layer_proj = proj
824     else:
825     layer_proj = None
826    
827     shapetype = layer.ShapeType()
828     if shapetype == SHAPETYPE_POLYGON:
829     shapefile = layer.ShapeStore().Shapefile().cobject()
830     x, y = shape_centroid(shapefile, shape_index,
831     map_proj, layer_proj, 1, 1, 0, 0)
832     if map_proj is not None:
833     x, y = map_proj.Inverse(x, y)
834     else:
835     shape = layer.Shape(shape_index)
836     if shapetype == SHAPETYPE_POINT:
837     x, y = shape.Points()[0]
838     else:
839     # assume SHAPETYPE_ARC
840     points = shape.Points()
841     x, y = points[len(points) / 2]
842     if layer_proj is not None:
843     x, y = layer_proj.Inverse(x, y)
844     if shapetype == SHAPETYPE_POINT:
845     halign = ALIGN_LEFT
846     valign = ALIGN_CENTER
847     elif shapetype == SHAPETYPE_POLYGON:
848     halign = ALIGN_CENTER
849     valign = ALIGN_CENTER
850     elif shapetype == SHAPETYPE_ARC:
851     halign = ALIGN_LEFT
852     valign = ALIGN_CENTER
853     label_layer.AddLabel(x, y, text,
854     halign = halign, valign = valign)
855     return True
856     return False
857    
858 bh 1454 def output_transform(canvas_scale, canvas_offset, canvas_size, device_extend):
859 jonathan 1386 """Calculate dimensions to transform canvas content to output device."""
860     width, height = device_extend
861    
862     # Only 80 % of the with are available for the map
863     width = width * 0.8
864    
865     # Define the distance of the map from DC border
866     distance = 20
867    
868     if height < width:
869     # landscape
870     map_height = height - 2*distance
871     map_width = map_height
872     else:
873     # portrait, recalibrate width (usually the legend width is too
874     # small
875     width = width * 0.9
876     map_height = width - 2*distance
877     map_width = map_height
878    
879     mapregion = (distance, distance,
880     distance+map_width, distance+map_height)
881    
882     canvas_width, canvas_height = canvas_size
883    
884     scalex = map_width / (canvas_width/canvas_scale)
885     scaley = map_height / (canvas_height/canvas_scale)
886     scale = min(scalex, scaley)
887     canvas_offx, canvas_offy = canvas_offset
888     offx = scale*canvas_offx/canvas_scale
889     offy = scale*canvas_offy/canvas_scale
890    
891     return scale, (offx, offy), mapregion

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26