/[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 1468 - (hide annotations)
Tue Jul 22 14:03:14 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: 30814 byte(s)
(ViewPort.calc_min_max_scales): New.
        Calculates the minimum and maximum scale values. Factored out
        of set_view_transform so that it could be used to zoom all the
        way into a single point.
(ViewPort.set_view_transform): Call calc_min_max_scales().
(ViewPort.FitSelectedToWindow): Zoom to the maximum scale
        if only a single point is selected.

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     #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
537     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
538     #print bmp
539     #img = wxImageFromBitmap(bmp)
540     #print img
541     #cur = wxCursor(img)
542     #print cur
543     #self.SetCursor(cur)
544    
545     def IdentifyTool(self):
546     """Start the identify tool"""
547     self.SelectTool(IdentifyTool(self))
548    
549     def LabelTool(self):
550     """Start the label tool"""
551     self.SelectTool(LabelTool(self))
552    
553     def CurrentTool(self):
554     """Return the name of the current tool or None if no tool is active"""
555     return self.tool and self.tool.Name() or None
556    
557     def CurrentPosition(self):
558     """Return current position of the mouse in projected coordinates.
559    
560     The result is a 2-tuple of floats with the coordinates. If the
561     mouse is not in the window, the result is None.
562     """
563     if self.current_position is not None:
564     x, y = self.current_position
565     return self.win_to_proj(x, y)
566     else:
567     return None
568    
569     def set_current_position(self, event):
570     """Set the current position from event
571    
572     Should be called by all events that contain mouse positions
573     especially EVT_MOTION. The event parameter may be None to
574     indicate the the pointer left the window.
575     """
576     if event is not None:
577     self.current_position = (event.m_x, event.m_y)
578     else:
579     self.current_position = None
580     self.issue(VIEW_POSITION)
581    
582     def MouseLeftDown(self, event):
583     self.set_current_position(event)
584     if self.tool is not None:
585     self.tool.MouseDown(event)
586    
587     def MouseLeftUp(self, event):
588     self.set_current_position(event)
589     if self.tool is not None:
590     self.tool.MouseUp(event)
591    
592     def MouseMove(self, event):
593     self.set_current_position(event)
594     if self.tool is not None:
595     self.tool.MouseMove(event)
596    
597     def shape_selected(self, layer, shape):
598     """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
599     # The selection object takes care that it only issues
600     # SHAPES_SELECTED messages when the set of selected shapes has
601     # actually changed, so we can do a full redraw unconditionally.
602     # FIXME: We should perhaps try to limit the redraw to the are
603     # actually covered by the shapes before and after the selection
604     # change.
605     pass
606    
607     def unprojected_rect_around_point(self, x, y, dist):
608     """Return a rect dist pixels around (x, y) in unprojected coordinates
609    
610     The return value is a tuple (minx, miny, maxx, maxy) suitable a
611     parameter to a layer's ShapesInRegion method.
612     """
613     map_proj = self.map.projection
614     if map_proj is not None:
615     inverse = map_proj.Inverse
616     else:
617     inverse = None
618    
619     xs = []
620     ys = []
621     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
622     px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
623     if inverse:
624     px, py = inverse(px, py)
625     xs.append(px)
626     ys.append(py)
627     return (min(xs), min(ys), max(xs), max(ys))
628    
629     def GetTextExtent(self, text):
630     # arbitrary numbers, really just so the tests pass
631     return 40, 20
632    
633     def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
634     """Determine the shape at point px, py in window coords
635    
636     Return the shape and the corresponding layer as a tuple (layer,
637     shape).
638    
639     If the optional parameter select_labels is true (default false)
640     search through the labels. If a label is found return it's index
641     as the shape and None as the layer.
642    
643     If the optional parameter searched_layer is given (or not None
644     which it defaults to), only search in that layer.
645     """
646     map_proj = self.map.projection
647     if map_proj is not None:
648     forward = map_proj.Forward
649     else:
650     forward = None
651    
652     scale = self.scale
653    
654     offx, offy = self.offset
655    
656     if select_labels:
657     labels = self.map.LabelLayer().Labels()
658    
659     if labels:
660     for i in range(len(labels) - 1, -1, -1):
661     label = labels[i]
662     x = label.x
663     y = label.y
664     text = label.text
665     if forward:
666     x, y = forward(x, y)
667     x = x * scale + offx
668     y = -y * scale + offy
669     width, height = self.GetTextExtent(text)
670     if label.halign == ALIGN_LEFT:
671     # nothing to be done
672     pass
673     elif label.halign == ALIGN_RIGHT:
674     x = x - width
675     elif label.halign == ALIGN_CENTER:
676     x = x - width/2
677     if label.valign == ALIGN_TOP:
678     # nothing to be done
679     pass
680     elif label.valign == ALIGN_BOTTOM:
681     y = y - height
682     elif label.valign == ALIGN_CENTER:
683     y = y - height/2
684     if x <= px < x + width and y <= py <= y + height:
685     return None, i
686    
687     if searched_layer:
688     layers = [searched_layer]
689     else:
690     layers = self.map.Layers()
691    
692     for layer_index in range(len(layers) - 1, -1, -1):
693     layer = layers[layer_index]
694    
695     # search only in visible layers
696     if not layer.Visible() or not layer.HasShapes():
697     continue
698    
699     table = layer.ShapeStore().Table()
700     lc = layer.GetClassification()
701 bh 1452 field = layer.GetClassificationColumn()
702 jonathan 1386
703     # defaults to fall back on
704     filled = lc.GetDefaultFill() is not Transparent
705     stroked = lc.GetDefaultLineColor() is not Transparent
706    
707     layer_proj = layer.projection
708     if layer_proj is not None:
709     inverse = layer_proj.Inverse
710     else:
711     inverse = None
712    
713     shapetype = layer.ShapeType()
714    
715     select_shape = -1
716    
717     # Determine the ids of the shapes that overlap a tiny area
718     # around the point. For layers containing points we have to
719     # choose a larger size of the box we're testing agains so
720     # that we take the size of the markers into account
721     # FIXME: Once the markers are more flexible this part has to
722     # become more flexible too, of course
723     if shapetype == SHAPETYPE_POINT:
724     box = self.unprojected_rect_around_point(px, py, 5)
725     else:
726     box = self.unprojected_rect_around_point(px, py, 1)
727     shape_ids = layer.ShapesInRegion(box)
728     shape_ids.reverse()
729    
730     if shapetype == SHAPETYPE_POLYGON:
731     for i in shape_ids:
732     shapefile = layer.ShapeStore().Shapefile().cobject()
733     if field:
734     record = table.ReadRowAsDict(i)
735     group = lc.FindGroup(record[field])
736     props = group.GetProperties()
737     filled = props.GetFill() is not Transparent
738     stroked = props.GetLineColor() is not Transparent
739     result = point_in_polygon_shape(shapefile, i,
740     filled, stroked,
741     map_proj, layer_proj,
742     scale, -scale, offx, offy,
743     px, py)
744     if result:
745     select_shape = i
746     break
747     elif shapetype == SHAPETYPE_ARC:
748     for i in shape_ids:
749     shapefile = layer.ShapeStore().Shapefile().cobject()
750     if field:
751     record = table.ReadRowAsDict(i)
752     group = lc.FindGroup(record[field])
753     props = group.GetProperties()
754     stroked = props.GetLineColor() is not Transparent
755     result = point_in_polygon_shape(shapefile,
756     i, 0, stroked,
757     map_proj, layer_proj,
758     scale, -scale, offx, offy,
759     px, py)
760     if result < 0:
761     select_shape = i
762     break
763     elif shapetype == SHAPETYPE_POINT:
764     for i in shape_ids:
765     shape = layer.Shape(i)
766     x, y = shape.Points()[0]
767     if inverse:
768     x, y = inverse(x, y)
769     if forward:
770     x, y = forward(x, y)
771     x = x * scale + offx
772     y = -y * scale + offy
773    
774     if field:
775     record = table.ReadRowAsDict(i)
776     group = lc.FindGroup(record[field])
777     props = group.GetProperties()
778     filled = props.GetFill() is not Transparent
779     stroked = props.GetLineColor() is not Transparent
780    
781     if hypot(px - x, py - y) < 5 and (filled or stroked):
782     select_shape = i
783     break
784    
785     if select_shape >= 0:
786     return layer, select_shape
787     return None, None
788    
789     def SelectShapeAt(self, x, y, layer = None):
790     """\
791     Select and return the shape and its layer at window position (x, y)
792    
793     If layer is given, only search in that layer. If no layer is
794     given, search through all layers.
795    
796     Return a tuple (layer, shapeid). If no shape is found, return
797     (None, None).
798     """
799     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
800     # If layer is None, then shape will also be None. We don't want
801     # to deselect the currently selected layer, so we simply select
802     # the already selected layer again.
803     if layer is None:
804     layer = self.selection.SelectedLayer()
805     shapes = []
806     else:
807     shapes = [shape]
808     self.selection.SelectShapes(layer, shapes)
809     return result
810    
811     def LabelShapeAt(self, x, y, text = None):
812     """Add or remove a label at window position x, y.
813    
814     If there's a label at the given position, remove it. Otherwise
815     determine the shape at the position and add a label.
816    
817     Return True is an action was performed, False otherwise.
818     """
819     label_layer = self.map.LabelLayer()
820     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
821     if layer is None and shape_index is not None:
822     # a label was selected
823     label_layer.RemoveLabel(shape_index)
824     return True
825     elif layer is not None and text:
826     proj = self.map.projection
827     if proj is not None:
828     map_proj = proj
829     else:
830     map_proj = None
831     proj = layer.projection
832     if proj is not None:
833     layer_proj = proj
834     else:
835     layer_proj = None
836    
837     shapetype = layer.ShapeType()
838     if shapetype == SHAPETYPE_POLYGON:
839     shapefile = layer.ShapeStore().Shapefile().cobject()
840     x, y = shape_centroid(shapefile, shape_index,
841     map_proj, layer_proj, 1, 1, 0, 0)
842     if map_proj is not None:
843     x, y = map_proj.Inverse(x, y)
844     else:
845     shape = layer.Shape(shape_index)
846     if shapetype == SHAPETYPE_POINT:
847     x, y = shape.Points()[0]
848     else:
849     # assume SHAPETYPE_ARC
850     points = shape.Points()
851     x, y = points[len(points) / 2]
852     if layer_proj is not None:
853     x, y = layer_proj.Inverse(x, y)
854     if shapetype == SHAPETYPE_POINT:
855     halign = ALIGN_LEFT
856     valign = ALIGN_CENTER
857     elif shapetype == SHAPETYPE_POLYGON:
858     halign = ALIGN_CENTER
859     valign = ALIGN_CENTER
860     elif shapetype == SHAPETYPE_ARC:
861     halign = ALIGN_LEFT
862     valign = ALIGN_CENTER
863     label_layer.AddLabel(x, y, text,
864     halign = halign, valign = valign)
865     return True
866     return False
867    
868 bh 1454 def output_transform(canvas_scale, canvas_offset, canvas_size, device_extend):
869 jonathan 1386 """Calculate dimensions to transform canvas content to output device."""
870     width, height = device_extend
871    
872     # Only 80 % of the with are available for the map
873     width = width * 0.8
874    
875     # Define the distance of the map from DC border
876     distance = 20
877    
878     if height < width:
879     # landscape
880     map_height = height - 2*distance
881     map_width = map_height
882     else:
883     # portrait, recalibrate width (usually the legend width is too
884     # small
885     width = width * 0.9
886     map_height = width - 2*distance
887     map_width = map_height
888    
889     mapregion = (distance, distance,
890     distance+map_width, distance+map_height)
891    
892     canvas_width, canvas_height = canvas_size
893    
894     scalex = map_width / (canvas_width/canvas_scale)
895     scaley = map_height / (canvas_height/canvas_scale)
896     scale = min(scalex, scaley)
897     canvas_offx, canvas_offy = canvas_offset
898     offx = scale*canvas_offx/canvas_scale
899     offy = scale*canvas_offy/canvas_scale
900    
901     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