#!/usr/bin/python # -*- coding: utf-8 -*- """The Buxus Canvas: an API for drawing maps. I don’t know much about maps, but I want some, so I thought I’d write a thing. See main() for an example. """ import sys # line caps and joins ROUND = object() BUTT = object() MITER = object() def main(): canvas = create((80, 20, 81.2, 23)) # Set map projection in degrees. canvas.newpath() canvas.moveto(80.01, 21) # lat, lon — not x, y canvas.lineto(81.0, 23) canvas.setlinewidth(4) canvas.setlinecap(ROUND) canvas.stroke() canvas.setlinewidth(2) canvas.setcolor(rgb(128, 128, 128)) canvas.stroke() canvas.write_svg(sys.stdout, 500, 500) def create(bbox): return Buxcanvas(bbox) class Buxcanvas: def __init__(self, bbox): self.bbox = bbox self.things = [] self._currentpath = None self._linewidth = 1 self._linecap = ROUND self._color = rgb(0, 0, 0) def newpath(self): self._currentpath = [] def moveto(self, lat, lon): self._currentpath.append([]) self._currentpath[-1].append((lat, lon)) def lineto(self, lat, lon): self._currentpath[-1].append((lat, lon)) def setlinewidth(self, n): self._linewidth = n def setlinecap(self, cap_type): assert cap_type in [BUTT, ROUND] self._linecap = cap_type def setcolor(self, color): self._color = color def stroke(self): self.things.append(Stroke(self._currentpath, self._linewidth, self._linecap, self._color)) def write_svg(self, filehandle, width, height): filehandle.write('\n') for thing in self.things: thing.write_svg(filehandle, Projection(self.bbox, width, height)) filehandle.write('\n') def write_ppm(self, filehandle, width, height): pass def rgb(r, g, b): return r, g, b def svg_color((r, g, b)): return '#%02x%02x%02x' % (r, g, b) class Stroke: def __init__(self, path, linewidth, linecap, color): self.path = path self.linewidth = linewidth self.linecap = linecap self.color = color def write_svg(self, filehandle, projection): filehandle.write("\n" % (self.svg_path(projection), self.svg_linewidth(), self.svg_linecap(), self.svg_color())) def svg_path(self, projection): return ' d="%s"' % ' '.join("M %d %d" % projection.project(coords) if i == 0 else "L %d %d" % projection.project(coords) for polygon in self.path for i, coords in enumerate(polygon)) def svg_linewidth(self): return ('' if self.linewidth == 1 else ' stroke-width="%dpx"' % self.linewidth ) def svg_linecap(self): return ('' if self.linecap == BUTT else ' stroke-linecap="round"' ) def svg_color(self): # no stroke is default for SVG? return ' stroke="%s"' % svg_color(self.color) class Projection: def __init__(self, bbox, width, height): self.lat_min, self.lon_min, self.lat_max, self.lon_max = bbox self.width = width self.height = height def project(self, (lat, lon)): # XXX this is bogus because projection. return (self.width * ((float(lon) - self.lon_min) / (self.lon_max - self.lon_min)), self.height * ((float(lat) - self.lat_min) / (self.lat_max - self.lat_min))) if __name__ == '__main__': main()