/[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 1456 - (hide annotations)
Fri Jul 18 14:56:13 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: 30396 byte(s)
* Thuban/UI/viewport.py: Remove unused imports

* Thuban/UI/view.py: Remove unused imports

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     SCALE_CHANGED
32    
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     self.map = map
302     self.selection.ClearSelection()
303     if self.map is not None:
304     self.map.Subscribe(MAP_PROJECTION_CHANGED, self.map_projection_changed)
305     self.map.Subscribe(LAYER_PROJECTION_CHANGED, self.layer_projection_changed)
306     self.FitMapToWindow()
307    
308     def Map(self):
309     """Return the map displayed by this canvas"""
310     return self.map
311    
312     def map_projection_changed(self, map, old_proj):
313    
314     proj = self.map.GetProjection()
315    
316     bbox = None
317    
318     if old_proj is not None and proj is not None:
319     width, height = self.GetPortSizeTuple()
320     llx, lly = self.win_to_proj(0, height)
321     urx, ury = self.win_to_proj(width, 0)
322     bbox = old_proj.Inverse(llx, lly) + old_proj.Inverse(urx, ury)
323     bbox = proj.ForwardBBox(bbox)
324    
325     if bbox is not None:
326     self.FitRectToWindow(bbox)
327     else:
328     self.FitMapToWindow()
329    
330     def layer_projection_changed(self, *args):
331     pass
332    
333     def set_view_transform(self, scale, offset):
334     # width/height of the projected bbox
335     llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()
336     pwidth = float(urx - llx)
337     pheight = float(ury - lly)
338    
339     # width/height of the window
340     wwidth, wheight = self.GetPortSizeTuple()
341    
342     # The window's center in projected coordinates assuming the new
343     # scale/offset
344     pcenterx = (wwidth/2 - offset[0]) / scale
345     pcentery = (offset[1] - wheight/2) / scale
346    
347     # The window coordinates used when drawing the shapes must fit
348     # into 16bit signed integers.
349     max_len = max(pwidth, pheight)
350     if max_len:
351     max_scale = 32767.0 / max_len
352     else:
353     # FIXME: What to do in this case? The bbox is effectively
354     # empty so any scale should work.
355     max_scale = scale
356    
357     # The minimal scale is somewhat arbitrarily set to half that of
358     # the bbox fit into the window
359     scales = []
360     if pwidth:
361     scales.append(wwidth / pwidth)
362     if pheight:
363     scales.append(wheight / pheight)
364     if scales:
365     min_scale = 0.5 * min(scales)
366     else:
367     min_scale = scale
368    
369     if scale > max_scale:
370     scale = max_scale
371     elif scale < min_scale:
372     scale = min_scale
373    
374     self.scale = scale
375    
376     # determine new offset to preserve the center
377     self.offset = (wwidth/2 - scale * pcenterx,
378     wheight/2 + scale * pcentery)
379     self.issue(SCALE_CHANGED, scale)
380    
381     def GetPortSizeTuple(self):
382     return self.size
383    
384     def proj_to_win(self, x, y):
385     """\
386     Return the point in window coords given by projected coordinates x y
387     """
388     offx, offy = self.offset
389     return (self.scale * x + offx, -self.scale * y + offy)
390    
391     def win_to_proj(self, x, y):
392     """\
393     Return the point in projected coordinates given by window coords x y
394     """
395     offx, offy = self.offset
396     return ((x - offx) / self.scale, (offy - y) / self.scale)
397    
398     def FitRectToWindow(self, rect):
399     """Fit the rectangular region given by rect into the window.
400    
401     Set scale so that rect (in projected coordinates) just fits into
402     the window and center it.
403     """
404     width, height = self.GetPortSizeTuple()
405     llx, lly, urx, ury = rect
406     if llx == urx or lly == ury:
407     # zero width or zero height. Do Nothing
408     return
409     scalex = width / (urx - llx)
410     scaley = height / (ury - lly)
411     scale = min(scalex, scaley)
412     offx = 0.5 * (width - (urx + llx) * scale)
413     offy = 0.5 * (height + (ury + lly) * scale)
414     self.set_view_transform(scale, (offx, offy))
415    
416     def FitMapToWindow(self):
417     """Fit the map to the window
418    
419     Set the scale so that the map fits exactly into the window and
420     center it in the window.
421     """
422     if self.map is not None:
423     bbox = self.map.ProjectedBoundingBox()
424     if bbox is not None:
425     self.FitRectToWindow(bbox)
426    
427     def FitLayerToWindow(self, layer):
428     """Fit the given layer to the window.
429    
430     Set the scale so that the layer fits exactly into the window and
431     center it in the window.
432     """
433    
434     bbox = layer.LatLongBoundingBox()
435     if bbox is not None:
436     proj = self.map.GetProjection()
437     if proj is not None:
438     bbox = proj.ForwardBBox(bbox)
439    
440     if bbox is not None:
441     self.FitRectToWindow(bbox)
442    
443     def FitSelectedToWindow(self):
444     layer = self.selection.SelectedLayer()
445     shapes = self.selection.SelectedShapes()
446    
447     bbox = layer.ShapesBoundingBox(shapes)
448     if bbox is not None:
449     proj = self.map.GetProjection()
450     if proj is not None:
451     bbox = proj.ForwardBBox(bbox)
452    
453     if bbox is not None:
454     if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:
455     self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))
456     else:
457     self.FitRectToWindow(bbox)
458    
459     def ZoomFactor(self, factor, center = None):
460     """Multiply the zoom by factor and center on center.
461    
462     The optional parameter center is a point in window coordinates
463     that should be centered. If it is omitted, it defaults to the
464     center of the window
465     """
466     width, height = self.GetPortSizeTuple()
467     scale = self.scale * factor
468     offx, offy = self.offset
469     if center is not None:
470     cx, cy = center
471     else:
472     cx = width / 2
473     cy = height / 2
474     offset = (factor * (offx - cx) + width / 2,
475     factor * (offy - cy) + height / 2)
476     self.set_view_transform(scale, offset)
477    
478     def ZoomOutToRect(self, rect):
479     """Zoom out to fit the currently visible region into rect.
480    
481     The rect parameter is given in window coordinates
482     """
483     # determine the bbox of the displayed region in projected
484     # coordinates
485     width, height = self.GetPortSizeTuple()
486     llx, lly = self.win_to_proj(0, height - 1)
487     urx, ury = self.win_to_proj(width - 1, 0)
488    
489     sx, sy, ex, ey = rect
490     scalex = (ex - sx) / (urx - llx)
491     scaley = (ey - sy) / (ury - lly)
492     scale = min(scalex, scaley)
493    
494     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
495     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
496     self.set_view_transform(scale, (offx, offy))
497    
498     def Translate(self, dx, dy):
499     """Move the map by dx, dy pixels"""
500     offx, offy = self.offset
501     self.set_view_transform(self.scale, (offx + dx, offy + dy))
502    
503     def SelectTool(self, tool):
504     """Make tool the active tool.
505    
506     The parameter should be an instance of Tool or None to indicate
507     that no tool is active.
508     """
509     self.tool = tool
510    
511     def ZoomInTool(self):
512     """Start the zoom in tool"""
513     self.SelectTool(ZoomInTool(self))
514    
515     def ZoomOutTool(self):
516     """Start the zoom out tool"""
517     self.SelectTool(ZoomOutTool(self))
518    
519     def PanTool(self):
520     """Start the pan tool"""
521     self.SelectTool(PanTool(self))
522     #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
523     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
524     #print bmp
525     #img = wxImageFromBitmap(bmp)
526     #print img
527     #cur = wxCursor(img)
528     #print cur
529     #self.SetCursor(cur)
530    
531     def IdentifyTool(self):
532     """Start the identify tool"""
533     self.SelectTool(IdentifyTool(self))
534    
535     def LabelTool(self):
536     """Start the label tool"""
537     self.SelectTool(LabelTool(self))
538    
539     def CurrentTool(self):
540     """Return the name of the current tool or None if no tool is active"""
541     return self.tool and self.tool.Name() or None
542    
543     def CurrentPosition(self):
544     """Return current position of the mouse in projected coordinates.
545    
546     The result is a 2-tuple of floats with the coordinates. If the
547     mouse is not in the window, the result is None.
548     """
549     if self.current_position is not None:
550     x, y = self.current_position
551     return self.win_to_proj(x, y)
552     else:
553     return None
554    
555     def set_current_position(self, event):
556     """Set the current position from event
557    
558     Should be called by all events that contain mouse positions
559     especially EVT_MOTION. The event parameter may be None to
560     indicate the the pointer left the window.
561     """
562     if event is not None:
563     self.current_position = (event.m_x, event.m_y)
564     else:
565     self.current_position = None
566     self.issue(VIEW_POSITION)
567    
568     def MouseLeftDown(self, event):
569     self.set_current_position(event)
570     if self.tool is not None:
571     self.tool.MouseDown(event)
572    
573     def MouseLeftUp(self, event):
574     self.set_current_position(event)
575     if self.tool is not None:
576     self.tool.MouseUp(event)
577    
578     def MouseMove(self, event):
579     self.set_current_position(event)
580     if self.tool is not None:
581     self.tool.MouseMove(event)
582    
583     def shape_selected(self, layer, shape):
584     """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
585     # The selection object takes care that it only issues
586     # SHAPES_SELECTED messages when the set of selected shapes has
587     # actually changed, so we can do a full redraw unconditionally.
588     # FIXME: We should perhaps try to limit the redraw to the are
589     # actually covered by the shapes before and after the selection
590     # change.
591     pass
592    
593     def unprojected_rect_around_point(self, x, y, dist):
594     """Return a rect dist pixels around (x, y) in unprojected coordinates
595    
596     The return value is a tuple (minx, miny, maxx, maxy) suitable a
597     parameter to a layer's ShapesInRegion method.
598     """
599     map_proj = self.map.projection
600     if map_proj is not None:
601     inverse = map_proj.Inverse
602     else:
603     inverse = None
604    
605     xs = []
606     ys = []
607     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
608     px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
609     if inverse:
610     px, py = inverse(px, py)
611     xs.append(px)
612     ys.append(py)
613     return (min(xs), min(ys), max(xs), max(ys))
614    
615     def GetTextExtent(self, text):
616     # arbitrary numbers, really just so the tests pass
617     return 40, 20
618    
619     def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
620     """Determine the shape at point px, py in window coords
621    
622     Return the shape and the corresponding layer as a tuple (layer,
623     shape).
624    
625     If the optional parameter select_labels is true (default false)
626     search through the labels. If a label is found return it's index
627     as the shape and None as the layer.
628    
629     If the optional parameter searched_layer is given (or not None
630     which it defaults to), only search in that layer.
631     """
632     map_proj = self.map.projection
633     if map_proj is not None:
634     forward = map_proj.Forward
635     else:
636     forward = None
637    
638     scale = self.scale
639    
640     offx, offy = self.offset
641    
642     if select_labels:
643     labels = self.map.LabelLayer().Labels()
644    
645     if labels:
646     for i in range(len(labels) - 1, -1, -1):
647     label = labels[i]
648     x = label.x
649     y = label.y
650     text = label.text
651     if forward:
652     x, y = forward(x, y)
653     x = x * scale + offx
654     y = -y * scale + offy
655     width, height = self.GetTextExtent(text)
656     if label.halign == ALIGN_LEFT:
657     # nothing to be done
658     pass
659     elif label.halign == ALIGN_RIGHT:
660     x = x - width
661     elif label.halign == ALIGN_CENTER:
662     x = x - width/2
663     if label.valign == ALIGN_TOP:
664     # nothing to be done
665     pass
666     elif label.valign == ALIGN_BOTTOM:
667     y = y - height
668     elif label.valign == ALIGN_CENTER:
669     y = y - height/2
670     if x <= px < x + width and y <= py <= y + height:
671     return None, i
672    
673     if searched_layer:
674     layers = [searched_layer]
675     else:
676     layers = self.map.Layers()
677    
678     for layer_index in range(len(layers) - 1, -1, -1):
679     layer = layers[layer_index]
680    
681     # search only in visible layers
682     if not layer.Visible() or not layer.HasShapes():
683     continue
684    
685     table = layer.ShapeStore().Table()
686     lc = layer.GetClassification()
687 bh 1452 field = layer.GetClassificationColumn()
688 jonathan 1386
689     # defaults to fall back on
690     filled = lc.GetDefaultFill() is not Transparent
691     stroked = lc.GetDefaultLineColor() is not Transparent
692    
693     layer_proj = layer.projection
694     if layer_proj is not None:
695     inverse = layer_proj.Inverse
696     else:
697     inverse = None
698    
699     shapetype = layer.ShapeType()
700    
701     select_shape = -1
702    
703     # Determine the ids of the shapes that overlap a tiny area
704     # around the point. For layers containing points we have to
705     # choose a larger size of the box we're testing agains so
706     # that we take the size of the markers into account
707     # FIXME: Once the markers are more flexible this part has to
708     # become more flexible too, of course
709     if shapetype == SHAPETYPE_POINT:
710     box = self.unprojected_rect_around_point(px, py, 5)
711     else:
712     box = self.unprojected_rect_around_point(px, py, 1)
713     shape_ids = layer.ShapesInRegion(box)
714     shape_ids.reverse()
715    
716     if shapetype == SHAPETYPE_POLYGON:
717     for i in shape_ids:
718     shapefile = layer.ShapeStore().Shapefile().cobject()
719     if field:
720     record = table.ReadRowAsDict(i)
721     group = lc.FindGroup(record[field])
722     props = group.GetProperties()
723     filled = props.GetFill() is not Transparent
724     stroked = props.GetLineColor() is not Transparent
725     result = point_in_polygon_shape(shapefile, i,
726     filled, stroked,
727     map_proj, layer_proj,
728     scale, -scale, offx, offy,
729     px, py)
730     if result:
731     select_shape = i
732     break
733     elif shapetype == SHAPETYPE_ARC:
734     for i in shape_ids:
735     shapefile = layer.ShapeStore().Shapefile().cobject()
736     if field:
737     record = table.ReadRowAsDict(i)
738     group = lc.FindGroup(record[field])
739     props = group.GetProperties()
740     stroked = props.GetLineColor() is not Transparent
741     result = point_in_polygon_shape(shapefile,
742     i, 0, stroked,
743     map_proj, layer_proj,
744     scale, -scale, offx, offy,
745     px, py)
746     if result < 0:
747     select_shape = i
748     break
749     elif shapetype == SHAPETYPE_POINT:
750     for i in shape_ids:
751     shape = layer.Shape(i)
752     x, y = shape.Points()[0]
753     if inverse:
754     x, y = inverse(x, y)
755     if forward:
756     x, y = forward(x, y)
757     x = x * scale + offx
758     y = -y * scale + offy
759    
760     if field:
761     record = table.ReadRowAsDict(i)
762     group = lc.FindGroup(record[field])
763     props = group.GetProperties()
764     filled = props.GetFill() is not Transparent
765     stroked = props.GetLineColor() is not Transparent
766    
767     if hypot(px - x, py - y) < 5 and (filled or stroked):
768     select_shape = i
769     break
770    
771     if select_shape >= 0:
772     return layer, select_shape
773     return None, None
774    
775     def SelectShapeAt(self, x, y, layer = None):
776     """\
777     Select and return the shape and its layer at window position (x, y)
778    
779     If layer is given, only search in that layer. If no layer is
780     given, search through all layers.
781    
782     Return a tuple (layer, shapeid). If no shape is found, return
783     (None, None).
784     """
785     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
786     # If layer is None, then shape will also be None. We don't want
787     # to deselect the currently selected layer, so we simply select
788     # the already selected layer again.
789     if layer is None:
790     layer = self.selection.SelectedLayer()
791     shapes = []
792     else:
793     shapes = [shape]
794     self.selection.SelectShapes(layer, shapes)
795     return result
796    
797     def LabelShapeAt(self, x, y, text = None):
798     """Add or remove a label at window position x, y.
799    
800     If there's a label at the given position, remove it. Otherwise
801     determine the shape at the position and add a label.
802    
803     Return True is an action was performed, False otherwise.
804     """
805     label_layer = self.map.LabelLayer()
806     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
807     if layer is None and shape_index is not None:
808     # a label was selected
809     label_layer.RemoveLabel(shape_index)
810     return True
811     elif layer is not None and text:
812     proj = self.map.projection
813     if proj is not None:
814     map_proj = proj
815     else:
816     map_proj = None
817     proj = layer.projection
818     if proj is not None:
819     layer_proj = proj
820     else:
821     layer_proj = None
822    
823     shapetype = layer.ShapeType()
824     if shapetype == SHAPETYPE_POLYGON:
825     shapefile = layer.ShapeStore().Shapefile().cobject()
826     x, y = shape_centroid(shapefile, shape_index,
827     map_proj, layer_proj, 1, 1, 0, 0)
828     if map_proj is not None:
829     x, y = map_proj.Inverse(x, y)
830     else:
831     shape = layer.Shape(shape_index)
832     if shapetype == SHAPETYPE_POINT:
833     x, y = shape.Points()[0]
834     else:
835     # assume SHAPETYPE_ARC
836     points = shape.Points()
837     x, y = points[len(points) / 2]
838     if layer_proj is not None:
839     x, y = layer_proj.Inverse(x, y)
840     if shapetype == SHAPETYPE_POINT:
841     halign = ALIGN_LEFT
842     valign = ALIGN_CENTER
843     elif shapetype == SHAPETYPE_POLYGON:
844     halign = ALIGN_CENTER
845     valign = ALIGN_CENTER
846     elif shapetype == SHAPETYPE_ARC:
847     halign = ALIGN_LEFT
848     valign = ALIGN_CENTER
849     label_layer.AddLabel(x, y, text,
850     halign = halign, valign = valign)
851     return True
852     return False
853    
854 bh 1454 def output_transform(canvas_scale, canvas_offset, canvas_size, device_extend):
855 jonathan 1386 """Calculate dimensions to transform canvas content to output device."""
856     width, height = device_extend
857    
858     # Only 80 % of the with are available for the map
859     width = width * 0.8
860    
861     # Define the distance of the map from DC border
862     distance = 20
863    
864     if height < width:
865     # landscape
866     map_height = height - 2*distance
867     map_width = map_height
868     else:
869     # portrait, recalibrate width (usually the legend width is too
870     # small
871     width = width * 0.9
872     map_height = width - 2*distance
873     map_width = map_height
874    
875     mapregion = (distance, distance,
876     distance+map_width, distance+map_height)
877    
878     canvas_width, canvas_height = canvas_size
879    
880     scalex = map_width / (canvas_width/canvas_scale)
881     scaley = map_height / (canvas_height/canvas_scale)
882     scale = min(scalex, scaley)
883     canvas_offx, canvas_offy = canvas_offset
884     offx = scale*canvas_offx/canvas_scale
885     offy = scale*canvas_offy/canvas_scale
886    
887     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