/[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 1482 - (hide annotations)
Thu Jul 24 17:53:04 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: 30526 byte(s)
Remove commented out code that wouldn't
belong in viewport anyway

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