/[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 2082 - (hide annotations)
Fri Feb 20 14:33:22 2004 UTC (21 years ago) by bh
File MIME type: text/x-python
File size: 20956 byte(s)
Reorder the imports and
doc-string a bit.  The doc-string must come first, otherwise it's
not a doc-string.  The __future__ import must be the first thing
after the doc-string.  Use only double quotes in doc-strings.
Single quotes trip up emacs syntax highlighting if the text
contains apostrophes.

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