/[thuban]/trunk/thuban/Extensions/svgexport/svgmapwriter.py
ViewVC logotype

Annotation of /trunk/thuban/Extensions/svgexport/svgmapwriter.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2074 - (hide annotations)
Thu Feb 19 13:38:27 2004 UTC (21 years ago) by bh
File MIME type: text/x-python
File size: 20970 byte(s)
Added again.  This time in the
correct place.

1 bh 2074 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2     # Authors:
3     # Markus Rechtien <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     __version__ = "$Revision$"
9     # $Source$
10     # $Id$
11    
12    
13     """
14     Classes needed to write a session in SVG format
15     """
16    
17    
18     # For compatibility with python > 2.3
19     from __future__ import generators
20     # Regular expressions used with Fontnames
21     import re
22     # Combining strings
23     from string import join
24     # We need to determine some object types
25     from types import ListType, TupleType
26     # VirtualDC extends XMLWriter
27     from Thuban.Model.xmlwriter import XMLWriter, escape
28     # Color related classes from the model of thuban
29     from Thuban.Model.color import Transparent, Color, Black
30     # The SVGRenderer is subclass of BaseRenderer
31     from Thuban.UI.baserenderer import BaseRenderer
32    
33     # Basic font map.
34     fontMap = { "Times" : re.compile("Times-Roman.*"),
35     "Helvetica" : re.compile("Helvetica.*"),
36     "Courier" : re.compile("Courier.*"),
37     }
38    
39     # Possible values for svg line joins.
40     svg_joins = {'miter':'miter', 'round':'round', 'bevel':'bevel'}
41     # Possible values for svg line caps.
42     svg_caps = {'':'', 'butt':'butt', 'round':'round', 'square':'square'}
43    
44     #
45     # Some pseudo classes to be compatible with the Baserenderer-class.
46     #
47     class Point:
48     '''A simple Point class.'''
49     def __init__(self, xp=0, yp=0):
50     '''Init the point object.'''
51     self.x = xp
52     self.y = yp
53    
54     class Trafo:
55     '''Class for tranformation properties transfer.'''
56     def __init__(self):
57     '''Initialize the class.'''
58     self.trafos = []
59    
60     def Append(self, type, coeffs):
61     '''Append a transformation to the list.'''
62     self.trafos.append((type, coeffs))
63    
64     def Count(self):
65     '''Get the number of transformations in list.'''
66     return len(self.trafos)
67    
68     def Pop(self):
69     '''Pop and return a transformation from the end of the list.'''
70     if len(self.trafos) > 0:
71     return self.trafos.pop()
72     else: return None
73    
74     class Pattern:
75     '''Pattern object '''
76     def __init__(self, solid=1):
77     '''Init the Pattern object.'''
78     self.solid = solid
79    
80     class Pen:
81     '''Pen object for property transfer.'''
82     def __init__(self, pcolor = Black, pwidth = 1, pdashes = None):
83     self.color = pcolor
84     self.width = pwidth
85     self.dashes = pdashes
86     self.join = 'round'
87     self.cap = 'round'
88    
89     def GetColor(self):
90     '''Return the pen's color.'''
91     return self.color
92    
93     def GetWidth(self):
94     '''Return the pen's width.'''
95     return self.width
96    
97     def GetJoin(self):
98     '''Return the pen's join type.'''
99     return self.join
100    
101     def GetCap(self):
102     '''Return the pen's cap type.'''
103     return self.cap
104    
105     def GetDashes(self):
106     '''Return the pen's dashes.'''
107     if self.dashes is None or self.dashes is SOLID:
108     return []
109     else: return self.dashes
110    
111     class Brush:
112     '''Brush property class.'''
113     def __init__(self, bfill=Black, bpattern=None):
114     '''Init the brush with the given values.'''
115     self.fill = bfill
116     self.pattern = bpattern
117    
118     def GetColor(self):
119     '''Return the brush color.'''
120     return self.fill
121    
122     def GetPattern(self):
123     '''Return the Brush pattern object.'''
124     return self.pattern
125    
126     class Font:
127     '''Font class that accts as property object.'''
128     def __init__(self, ffamily='Helvetica', fsize=12):
129     '''Init the font with the given values.'''
130     self.family = ffamily
131     self.size = fsize
132    
133     def GetFaceName(self):
134     '''Return the fontfamily the font belongs to.'''
135     return self.family
136    
137     def GetPointSize(self):
138     '''Return the size of the font in points.'''
139     return self.size
140    
141     # Instantiate an empty pen.
142     TRANSPARENT_PEN = Pen(None, 0, None)
143     # Instantiate an empty brush.
144     TRANSPARENT_BRUSH = Brush(None, None)
145     # Instantiate a solid pattern.
146     SOLID = Pattern()
147    
148     class SVGRenderer(BaseRenderer):
149     """Class to render a map onto a VirtualDC.
150    
151     This class, derived from BaseRenderer, will render a hole
152     session onto the VirtualDC to write all shapes as SVG code
153     to a file.
154     In opposite to other renderers it includes metadata, such as
155     shape ids and classification, when rendering the shapes.
156     """
157     def __init__(self, dc, map, scale, offset, region,
158     resolution = 1.0, honor_visibility = 1):
159     '''Init SVGRenderer and call superclass init.'''
160     BaseRenderer.__init__(self, dc, map, scale, offset, region,
161     resolution, honor_visibility)
162     #
163     self.factor = (abs(region[2]) + abs(region[3])) / (2.0 * 1000.0)
164    
165     def make_point(self, x, y):
166     '''Return a Point object from two values.'''
167     return Point(x, y)
168    
169     def label_font(self):
170     """Return the font object for the label layer"""
171     return Font()
172    
173     def tools_for_property(self, prop):
174     '''Return a pen/brush tuple build from a property object.'''
175     fill = prop.GetFill()
176     if fill is Transparent:
177     brush = TRANSPARENT_BRUSH
178     else:
179     brush = Brush(fill, SOLID)
180    
181     stroke = prop.GetLineColor()
182     if stroke is Transparent:
183     pen = TRANSPARENT_PEN
184     else:
185     pen = Pen(stroke, prop.GetLineWidth() * self.factor, SOLID)
186     return pen, brush
187    
188     def draw_polygon_shape(self, layer, points, pen, brush):
189     """Draw a polygon shape from layer with the given brush and pen
190    
191     The shape is given by points argument which is a the return
192     value of the shape's Points() method. The coordinates in the
193     DC's coordinate system are determined with
194     self.projected_points.
195     """
196     points = self.projected_points(layer, points)
197    
198     if brush is not TRANSPARENT_BRUSH:
199     self.dc.SetBrush(brush)
200     self.dc.SetPen(pen)
201     for part in points:
202     self.dc.DrawLines(part)
203    
204     def draw_point_shape(self, layer, points, pen, brush):
205     """Draw a point shape from layer with the given brush and pen
206    
207     The shape is given by points argument which is a the return
208     value of the shape's Points() method. The coordinates in the
209     DC's coordinate system are determined with
210     self.projected_points.
211    
212     The point is drawn as a circle centered on the point.
213     """
214     points = self.projected_points(layer, points)
215     if not points:
216     return
217    
218     radius = self.factor * 2.0
219     self.dc.SetBrush(brush)
220     self.dc.SetPen(pen)
221     for part in points:
222     for p in part:
223     self.dc.DrawCircle(p.x - radius, p.y - radius,
224     2.0 * radius)
225    
226     def draw_shape_layer_incrementally(self, layer):
227     """Draw a shapelayer incrementally.
228     """
229     dc = self.dc
230     brush = TRANSPARENT_BRUSH
231     pen = TRANSPARENT_PEN
232    
233     value = None
234     field = None
235     lc = layer.GetClassification()
236     field = layer.GetClassificationColumn()
237     defaultGroup = lc.GetDefaultGroup()
238     table = layer.ShapeStore().Table()
239    
240     if lc.GetNumGroups() == 0:
241     # There's only the default group, so we can pretend that
242     # there is no field to classifiy on which makes things
243     # faster since we don't need the attribute information at
244     # all.
245     field = None
246    
247     # Determine which render function to use.
248     useraw, draw_func, draw_func_param = \
249     self.low_level_renderer(layer)
250     tool_cache = {}
251    
252     # Set baseid - prefix of a shape id to be unique
253     dc.SetBaseID(layer.title)
254     # Titel of current layer to the groups meta informations
255     dc.BeginGroup(meta={'Layer':layer.Title(), })
256     # Delete all MetaData
257     dc.FlushMeta()
258     for shape in self.layer_shapes(layer):
259     if field is None:
260     group = defaultGroup
261     value = group.GetDisplayText()
262     else:
263     value = table.ReadValue(shape.ShapeID(), field)
264     group = lc.FindGroup(value)
265    
266     if not group.IsVisible():
267     continue
268    
269     # Render classification
270     shapeType = layer.ShapeType()
271     props = group.GetProperties()
272    
273     # put meta infos into DC
274     if field and value:
275     dc.SetMeta({field:value, })
276     # set current shape id
277     dc.SetID(shape.ShapeID())
278    
279     try:
280     pen, brush = tool_cache[id(group)]
281     except KeyError:
282     pen, brush = tool_cache[id(group)] \
283     = self.tools_for_property(group.GetProperties())
284    
285     if useraw:
286     data = shape.RawData()
287     else:
288     data = shape.Points()
289     draw_func(draw_func_param, data, pen, brush)
290     # compatibility
291     if 0:
292     yield True
293     # reset shape id
294     dc.SetID(-1)
295     dc.SetBaseID("")
296     dc.EndGroup()
297    
298     def draw_raster_layer(self, layer):
299     '''Draw the raster layer'''
300     # For now we cannot draw raster layers onto our VirtualDC
301     pass
302    
303     def draw_raster_data(self, data, format="BMP"):
304     """Draw the raster image in data onto the DC"""
305     # For now we cannot draw raster data onto our VirtualDC
306     pass
307    
308     def RenderMap(self, selected_layer, selected_shapes):
309     '''Overriden to avoid automatic rendering of legend,
310     scalbar and frame.
311     '''
312     dc = self.dc
313     self.selected_layer = selected_layer
314     self.selected_shapes = selected_shapes
315     minx, miny, width, height = self.region
316     # scale down to a size of 1000
317     trans = Trafo()
318     trans.Append('scale', (1000.0 / ((width + height) / 2.0)))
319     #
320     dc.BeginClipPath('mapclip')
321     dc.DrawRectangle(0, 0, width, height)
322     dc.EndClipPath()
323     #
324     dc.BeginGroup(meta={'Object':'map', }, clipid='mapclip', \
325     transform=trans)
326     self.render_map()
327     dc.EndGroup()
328    
329    
330     class VirtualDC(XMLWriter):
331     '''This class imitates a DC and writes SVG instead.
332    
333     All shapes and graphic objects will be turned into
334     SVG elements and will be written into a file.
335     Any properties, such as stroke width or stroke color,
336     will be written together with the SVG elementents.
337     '''
338     def __init__(self, file, dim=(0,0), units=''):
339     '''Setup some variables and objects for property collection.'''
340     XMLWriter.__init__(self)
341     self.dim = dim
342     self.units = units
343     self.pen = {}
344     self.brush = {}
345     self.font = {}
346     self.meta = {}
347     self.style = {}
348     # Some buffers
349     self.points = []
350     self.id = -1
351     self.flush_meta = 1
352     self.write(file)
353    
354     def write_indent(self, str):
355     '''Write a string to the file with the current indention level.
356     '''
357     from Thuban.Model.xmlwriter import TAB
358     self.file.write("%s%s" % (TAB*self.indent_level, str))
359    
360     def AddMeta(self, key, val):
361     '''Append some metadata to the array that will be
362     written with the next svg-element
363     '''
364     if key is '' or val is '':
365     return
366     self.meta[key] = val
367    
368     def SetMeta(self, pairs, flush_after=1):
369     '''Delete old meta informations and set the new ones.'''
370     self.meta = {}
371     self.flush_meta = flush_after
372     for key, val in pairs.items():
373     self.AddMeta(key, val)
374    
375     def FlushMeta(self):
376     '''Drop collected metadata.'''
377     self.meta = {}
378    
379     def BeginGroup(self, **args):
380     '''Begin a group of elements.
381    
382     Possible arguments:
383     meta A list of key, value metadata pairs
384     style A list of key, value style attributes
385     clipid The ID of a clipPath definition to be
386     applied to this group
387     '''
388     self.FlushMeta()
389     # adding meta data
390     if args.has_key('meta'):
391     for key, val in args['meta'].items():
392     self.AddMeta(key, val)
393     attribs = " "
394     # adding style attributes
395     if args.has_key('style'):
396     for key, val in args['style'].items():
397     attribs += '%s="%s" ' % (key, val)
398     # adding clip informations
399     if args.has_key("clipid"):
400     attribs += ' clip-path="url(#%s)"' % (args['clipid'],)
401     # FIXME: this shouldn't be static
402     attribs += ' clip-rule="evenodd"'
403     if args.has_key('transform'):
404     trafostr = self.parse_trafo(args['transform'])
405     if trafostr:
406     attribs += ' transform="%s"' % (trafostr)
407     # put everything together
408     self.write_indent('<g %s%s>\n' % (self.make_meta(), attribs))
409     self.indent_level += 1
410    
411     def parse_trafo(self, trafo):
412     '''Examine a trafo object for asigned transformations details.'''
413     if not trafo:
414     return ''
415     retval = ''
416     while trafo.Count() > 0:
417     trans, coeffs = tuple(trafo.Pop())
418     if isinstance(coeffs, ListType):
419     retval += " %s%s" % (trans, join(coeffs, ', '))
420     else: retval += " %s(%s)" % (trans, coeffs)
421     # return the string
422     return retval
423    
424     def EndGroup(self):
425     '''End a group of elements'''
426     self.indent_level -= 1
427     self.write_indent('</g>\n')
428     self.FlushMeta()
429    
430     def BeginExport(self):
431     '''Start the export process and write basic document
432     informations to the file.
433     '''
434     self.write_indent('<?xml version="1.0" encoding="ISO-8859-1" '
435     'standalone="yes"?>\n')
436     width, height = self.dim
437     self.write_indent('<svg>\n')
438     self.indent_level += 1
439    
440     def EndExport(self):
441     '''End the export process with closing the SVG tag and close
442     the file accessor'''
443     self.indent_level -= 1
444     self.write_indent('</svg>\n')
445     self.close()
446    
447     def Close(self):
448     '''Close the file.'''
449     self.close()
450    
451     def BeginDrawing(self):
452     '''Dummy function to work with the Thuban renderers.'''
453     pass
454    
455     def EndDrawing(self):
456     '''Dummy function to work with the Thuban renderers.'''
457     pass
458    
459     def GetSizeTuple(self):
460     '''Return the dimension of this virtual canvas.'''
461     return self.dim
462    
463     def GetTextExtent(self, text):
464     '''Return the dimension of the given text.'''
465     # FIXME: find something more appropriate
466     try:
467     if self.font:
468     return (int(self.font["font-size"]),
469     len(text) * int(self.font["font-size"]))
470     else: return (12,len(text) * 10)
471     except ValueError:
472     return (12,len(text) * 10)
473    
474     def SetID(self, id):
475     '''Set the ID stored by the svg elements.'''
476     self.id = id
477    
478     def SetBaseID(self, id):
479     '''Set the ID stored by the svg elements.'''
480     self.baseid = id
481    
482     def SetFont(self, font):
483     '''Set the fontproperties to use with text elements.'''
484     if font is not None:
485     fontname = font.GetFaceName()
486     size = font.GetPointSize()
487     for svgfont, pattern in fontMap.items():
488     if pattern.match(fontname):
489     fontname = svgfont
490     break
491     if fontname:
492     self.font["font-family"] = fontname
493     else: self.font["font-family"] = None
494     if size:
495     self.font["font-size"] = str(size)
496     else: self.font["font-size"] = None
497    
498     def SetPen(self, pen):
499     '''Set the style of the pen used to draw graphics.'''
500     if pen is TRANSPARENT_PEN:
501     self.pen = {}
502     else:
503     self.pen["stroke"] = pen.GetColor().hex()
504     self.pen["stroke-dasharray"] = join(pen.GetDashes(), ',')
505     self.pen["stroke-width"] = pen.GetWidth()
506     self.pen["stroke-linejoin"] = svg_joins[pen.GetJoin()]
507     self.pen["stroke-linecap"] = svg_caps[pen.GetCap()]
508    
509     def SetBrush(self, brush):
510     '''Set the fill properties.'''
511     if brush is TRANSPARENT_BRUSH:
512     self.brush['fill'] = 'none'
513     elif brush.GetPattern() is SOLID:
514     self.brush['fill'] = brush.GetColor().hex()
515     else: # TODO Handle Patterns
516     pass
517    
518     def SetTextForeground(self, color):
519     '''Set the color of the text foreground.'''
520     self.font['fill'] = color.hex()
521    
522     def make_style(self, line=0, fill=0, font=0):
523     '''Build the style attribute including desired properties
524     such as fill, forground, stroke, etc.'''
525     result = []
526     # little helper function
527     def append(pairs):
528     for key, val in pairs.items():
529     if not val in [None, '']:
530     result.append('%s:%s' % (key, val))
531     #
532     if line and len(self.pen) > 0:
533     append(self.pen)
534     if fill and len(self.brush) > 0:
535     append(self.brush)
536     if font and len(self.font) > 0:
537     append(self.font)
538     style = join(result, '; ')
539     if style:
540     return 'style="%s"' % (style, )
541     else:
542     return ''
543    
544     def make_meta(self, meta=None):
545     '''Build the meta attribute.'''
546     result = []
547     if not meta:
548     meta = self.meta
549     if len(meta) is 0:
550     return ''
551     for key, val in meta.items():
552     if not val in [None, '', 'none']:
553     result.append('%s:%s' % (key, val))
554     if self.flush_meta:
555     self.meta = {}
556     return 'meta="%s"' % (join(result, '; '))
557    
558     def make_id(self):
559     '''Get the ID for the next object - if current ID is valid.'''
560     if self.id < 0:
561     return ''
562     else: return 'id="%s%s"' % (self.baseid, self.id)
563    
564     def DrawEllipse(self, x, y, dx, dy):
565     '''Draw an ellipse.'''
566     elips = '<ellipse cx="%s" cy="%s" rx="%s" ry="%s" %s %s %s/>\n'
567     self.write_indent(elips % (x, y, dx, dy, self.make_id(),
568     self.make_style(1,1,0), self.make_meta()) )
569    
570     def DrawCircle(self, x, y, radius):
571     '''Draw a circle onto the virtual dc.'''
572     self.write_indent('<circle cx="%s" cy="%s" r="%s" %s %s %s/>\n' %
573     (x, y, radius, self.make_id(), self.make_style(1,1,0),
574     self.make_meta()) )
575    
576     def DrawRectangle(self, x, y, width, height):
577     '''Draw a rectangle with the given parameters.'''
578     rect = '<rect x="%s" y="%s" width="%s" height="%s" %s %s %s/>\n'
579     self.write_indent(rect % ( x, y, width, height, self.make_id(),
580     self.make_style(1,1,0), self.make_meta()) )
581    
582     def DrawText(self, text, x, y):
583     '''Draw Text at the given position.'''
584     beginText = '<text x="%s" y="%s" %s %s %s>'
585     self.write_indent(beginText % ( x, y, self.make_id(),
586     self.make_style(0,0,1), self.make_meta()) )
587     self.file.write(escape(text))
588     self.file.write('</text>\n')
589    
590     def DrawLines(self, points):
591     '''Draw some points into a Buffer that will be
592     written before the next object.
593     '''
594     self.DrawPolygon(points,0)
595    
596     def DrawPolygon(self, polygon, closed=1):
597     '''Draw a polygon onto the virtual dc.'''
598     self.write_indent('<path %s ' % (self.make_style(1,1,0)))
599     data = []
600     i = 0
601     for point in polygon:
602     if i is 0:
603     data.append('M %s %s ' % (point.x, point.y))
604     else:
605     data.append('L %s %s ' % (point.x, point.y))
606     i+=1
607     # FIXME: Determine if path is closed
608     if closed:
609     data.append('Z')
610     # Put everything together and write it to the file
611     self.file.write('%s %s d="%s"/>\n' % (self.make_id(),
612     self.make_meta(), join(data, '') ) )
613    
614     def DrawSpline(self, points, closed=0):
615     '''Draw a spline object.
616     '''
617     self.DrawPolygon(points, 0)
618     print "TODO: DrawSpline(..)"
619     return # TODO: Implement
620    
621     def BeginClipPath(self, id):
622     '''Build a clipping region to draw in.'''
623     self.write_indent('<clipPath id="%s">\n' % id)
624     self.indent_level += 1
625    
626     def EndClipPath(self):
627     '''End a clip path.'''
628     self.indent_level -= 1
629     self.write_indent("</clipPath>\n")

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26