/[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 1539 - (hide annotations)
Fri Aug 1 14:27:57 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: 30518 byte(s)
Import the SHAPETYPE_* constants from data
instead of layer.

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