source: subversion/applications/rendering/pyrender/render_cairo_base.py @ 28929

Last change on this file since 28929 was 8749, checked in by ojw, 11 years ago

test scripts didn't include the layer parameter, so that wasn't working
for output to a file

File size: 6.2 KB
Line 
1#!/usr/bin/python
2#----------------------------------------------------------------------------
3# Python rendering engine for OpenStreetMap data
4#
5# Input:  OSM XML files
6# Output: 256x256px PNG images in slippy-map format
7#----------------------------------------------------------------------------
8# Copyright 2008, authors:
9# * Sebastian Spaeth
10# * Oliver White
11#
12# This program is free software: you can redistribute it and/or modify
13# it under the terms of the GNU General Public License as published by
14# the Free Software Foundation, either version 3 of the License, or
15# (at your option) any later version.
16#
17# This program is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20# GNU General Public License for more details.
21#
22# You should have received a copy of the GNU General Public License
23# along with this program.  If not, see <http://www.gnu.org/licenses/>.
24#---------------------------------------------------------------------------
25import StringIO
26import cairo
27import math
28import xml.sax
29from tiledata import *
30from parseOsm import *
31from tilenames import *
32
33class proj:
34  """Simple projection class for geographic data.  Converts lat/long to pixel position"""
35  def __init__(self, tx,ty,tz, to):
36    """Setup a projection. 
37    tx,ty,tz is the slippy map tile number. 
38    to is the (width,height) of an image to render onto
39    """ 
40    dd = 1.0 / float(numTiles(tz))
41    self.to = to
42    self.N = ty * dd
43    self.S = self.N + dd
44    self.W = tx * dd
45    self.E = self.W + dd
46    self.dLat = -dd
47    self.dLon = dd
48   
49    self.x1 = 0
50    self.y1 = 0
51    self.x2 = to[0]
52    self.y2 = to[1]
53    self.dx = self.x2 - self.x1
54    self.dy = self.y2 - self.y1
55  def project(self,lat,lon):
56    """Project lat/long (in degrees) into pixel position on the tile"""
57    pLat = (lat - self.S) / self.dLat
58    pLon = (lon - self.W) / self.dLon
59    x = self.x1 + pLon * self.dx
60    y = self.y2 - pLat * self.dy
61    return(x,y)
62
63class OsmRenderBase:
64 
65  def imageBackgroundColour(self, mapLayer=None):
66    return (0,0,0.5,0.5) # Override this function (r,g,b,a)
67
68  def requireDataTile(self):
69    return(True)  # override this if you don't want OSM data
70 
71  def draw(self, layer):
72    pass # Override this function
73 
74  def getLayer(self, layernum):
75    layerid = str(layernum)
76    layer = self.layers.get(layerid, None)
77    if(not layer):
78      # Create the image
79      im = cairo.ImageSurface (cairo.FORMAT_ARGB32, 256, 256)
80      ctx = cairo.Context(im)  # create a drawing context
81      layer = [im, ctx, layerid]
82      self.layers[layerid] = layer
83    return(layer)
84
85  def getCtx(self, layernum):
86    layer = self.getLayer(layernum)
87    return(layer[1])
88   
89  def Render(self, filename,tx,ty,tz,layer):
90    """Render an OSM tile
91    im - an image to render onto
92    filename - location of OSM data for the tile
93    tx,ty,tz - the tile number
94    layer - which map style to use
95    """
96
97    #set context variables that should be available during drawing
98    self.z = tz
99    self.mapLayer = layer
100
101    if(filename):
102      self.osm = parseOsm(filename)  # get OSM data into memory
103    self.proj = proj(tx,ty,tz,(256,256))  # create a projection for this tile
104
105    # Call the draw function
106    self.draw()
107
108  def addBackground(self,ctx, mapLayer):
109    # Fill with background color
110    (r,g,b,a) = self.imageBackgroundColour(mapLayer)
111    ctx.set_source_rgba(r,g,b,a)
112    ctx.rectangle(0, 0, 256, 256)
113    ctx.fill()
114
115  def RenderTile(self, z,x,y, mapLayer, outputFile=None):
116    """Render an OSM tile
117    z,x,y - slippy map tilename
118    mapLayer - what style of map to draw
119    outputFile - optional file to save to
120    otherwise returns PNG image data"""
121
122    print "Rendering %d,%d@%d, %s to %s" % (x,y,z,mapLayer, outputFile)
123    self.layers = {}
124   
125    # Get some OSM data for the area, and return which file it's stored in
126    if(self.requireDataTile()):
127      filename = GetOsmTileData(z,x,y)
128      if(filename == None):
129        return(None)
130    else:
131      filename = None
132 
133    # Render the map
134    self.Render(filename,x,y,z,mapLayer)
135 
136    out = cairo.ImageSurface (cairo.FORMAT_ARGB32, 256, 256)
137    outCtx = cairo.Context(out)
138    self.addBackground(outCtx, mapLayer)
139   
140    layerIDs = self.layers.keys()
141    layerIDs.sort()
142    for num in layerIDs:
143      layer = self.layers[num]
144      #(im,ctx,num) = layer
145      outCtx.set_source_surface(layer[0], 0, 0)
146      outCtx.set_operator(cairo.OPERATOR_OVER)  # was: OPERATOR_SOURCE
147      outCtx.paint();
148
149    # Either save the result to a file
150    if(outputFile):
151      out.write_to_png(outputFile) # Output to PNG
152      return
153    else:
154      # Or return it in a string
155      f = StringIO.StringIO()
156      out.write_to_png(f)
157      data = f.getvalue()
158      return(data)
159
160  def beziercurve(self,xy):
161    """is handed a list of (x,y) tuples of a path and returns a list
162       of 2*xy control points that can be used to draw bezier curves.
163       Algorithm based on:
164       http://www.antigrain.com/research/bezier_interpolation/index.html
165    """
166   
167    point_distance = lambda a,b: math.sqrt(pow(a[0]-b[0],2)+pow(a[1]-b[1],2))
168
169    # first control point is 1st point
170    cp = list()
171    cp.append(xy[0])
172
173    for index in range(1,len(xy)-1):
174      x1,y1 = xy[index-1]
175      x2,y2 = xy[index]
176      x3,y3 = xy[index+1]
177     
178      L1=point_distance((x2,y2),(x1,y1))
179      L2=point_distance((x3,y3),(x2,y2))
180      C1=0.5 * (x1+x2), 0.5 *(y1+y2)
181      C2=0.5 * (x2+x3), 0.5 *(y2+y3)
182      C1x,C1y = C1
183      C2x,C2y = C2
184     
185      if (L1+L2==0) or (C2x==C1x) or (C2y==C1y):
186        #if impossible, just use the point as control points
187        cp.append(xy[index])
188        cp.append(xy[index])
189      else:
190        #usually just calculate the control points properly:
191        Mx = L1/(L1+L2) * (C2x - C1x) + C1x
192        My = L1/(L1+L2) * (C2y - C1y) + C1y
193        cp1x,cp1y = C1x-Mx + x2, C1y-My + y2
194        cp2x,cp2y = C2x-Mx + x2, C2y-My + y2     
195
196        cp.append((cp1x,cp1y))
197        cp.append((cp2x,cp2y))
198    #last control point is last point
199    cp.append(xy[-1])
200    return cp
201
202
203if(__name__ == '__main__'):
204  # Test suite: render a tile in london, and save it to a file
205  a = OsmRenderBase()
206  a.RenderTile(15, 16372, 10895, "default", "sample2.png")
Note: See TracBrowser for help on using the repository browser.