/[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 1405 - (hide annotations)
Thu Jul 10 16:49:57 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/viewport.py
File MIME type: text/x-python
File size: 30607 byte(s)
(Tool.MouseUp): Should have called
    drag_stop, not drag_move when the mouse is released.

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