Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Use Cairo as a renderer if available. Otherwise, use PIL.

  • Loading branch information...
commit 0625b970d86adc5efde7f3aae4bd4c03a702e19d 1 parent 66b5e6e
Mjumbe Poe authored

Showing 2 changed files with 130 additions and 149 deletions. Show diff stats Hide diff stats

  1. +117 140 septa/carto.py
  2. +13 9 septa/views.py
257 septa/carto.py
... ... @@ -1,138 +1,113 @@
1 1 import itertools
  2 +import math
  3 +import os
  4 +from random import random, randint
  5 +
  6 +# Import imaging libraries
2 7 try:
3 8 import cairo
4 9 except ImportError:
5   - pass
6   -import Image, ImageDraw
  10 + # Use PIL as a fallback
  11 + import Image, ImageDraw
7 12
8   -from random import random, randint
9   -#img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 100, 100)
10   -#ctx = cairo.Context(img)
11   -#ctx.scale(100, 100)
12   -#ctx.move_to(0, 0)
13   -#ctx.line_to(30, 50)
14   -#img.write_to_png('hello.png')
15   -#ctx.stroke()
  13 +import settings
16 14
17 15
18 16 class Cartographer (object):
19 17 pass
20 18
21 19
22   -class PilTransitMap (object):
  20 +class BaseTransitMap (object):
23 21 def __init__(self, w, h):
24   - self.wide = w
25   - self.high = h
  22 + self.img_wide = w
  23 + self.img_high = h
26 24
27   - self.img = Image.new("RGBA", (w, h), (255, 255, 255))
28   -
29   - def _draw_poly(self, path_data, color, size):
  25 + def draw_route(self, route, threshold=0.0):
  26 + w, n, e, s = route.the_geom_900913.extent
  27 + self._calc_t(w, n, e, s)
30 28
31   - #
32   - # This is a recursive function. ``path_data`` may be a tuple of tuples.
33   - # Check the first element and see if it is a pair of numbers. If so,
34   - # it's a polyline so draw it. Otherwise, we need to go deeper, so call
35   - # the function again.
36   - #
  29 + self._draw_route(route, threshold, (255, 0, 0), 4)
37 30
38   - if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
39   - for coord in path_data[0]]):
40   - draw = ImageDraw.Draw(self.img)
41   - prev = self._t(*path_data[0])
42   - for coord in path_data[1:]:
43   - curr = self._t(*coord)
44   - draw.line(prev + curr, fill=color, width=size)
45   - prev = curr
  31 + def draw_routes(self, routes, threshold=0.0, center=None):
  32 + all_paths = routes[0].the_geom_900913
  33 + for route in routes[1:]:
  34 + all_paths = all_paths.union(route.the_geom_900913)
  35 + w, n, e, s = all_paths.extent
  36 + center.transform(all_paths.srid)
  37 + self._calc_t(w, n, e, s, center)
46 38
47   - else:
48   - for sub_path_data in path_data:
49   - self._draw_poly(sub_path_data, color, size)
  39 + legend = {}
  40 + for route in routes:
  41 + color = (randint(0,255),randint(0,255),randint(0,255))
  42 + self._draw_route(route, threshold, color, 4)
  43 + legend[route.route] = "#%x%x%x" % color
50 44
51   -# def _mark_beginnings(self, path_data):
  45 + return legend
52 46
53   -# if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
54   -# for coord in path_data[0]]):
55   -# self.ctx.arc(*(path_data[0]),
56   -# )
  47 + def store(self):
  48 + fn = 'map%s.png' % randint(0,1000)
  49 + fullpath = os.path.join(settings.MY_STATIC_ROOT, fn)
  50 + map_url = (settings.STATIC_URL + fn)
57 51
58   -# else:
59   -# for sub_path_data in path_data:
60   -# self._trace_poly(sub_path_data)
  52 + self._store_img(fullpath)
  53 + return map_url
61 54
62 55 def _draw_route(self, route, threshold, color, size):
63 56 path_data = route.the_geom_900913.simplify(threshold).coords
64 57 self._draw_poly(path_data, color, size)
65 58
66   -# def _trace_features(self, route):
67   -# path_data = route.the_geom_900913.coords
  59 + def _calc_t(self, w, n, e, s, center=None):
  60 + self.geo_wide = max(e - center.x, center.x - w)*2
  61 + self.geo_high = max(s - center.y, center.y - n)*2
  62 + self.geo_center = center
68 63
  64 + print w, n, e, s, center.x, center.y
  65 + print self.geo_wide, self.geo_high
69 66
70   - def draw_route(self, route, threshold=0.0):
71   - w, n, e, s = route.the_geom_900913.extent
72   - self._calc_t(w, n, e, s)
73   -
74   - self._draw_route(route, threshold, (255, 0, 0), 4)
75   -
76   - def _calc_t(self, w, n, e, s):
77   - self.x_offset = -w
78   - self.y_offset = -n
  67 + self.x_offset = -(center.x - self.geo_wide/2)
  68 + self.y_offset = -(center.y - self.geo_high/2)
79 69
80   - hscale_factor = self.wide / float(e - w)
81   - vscale_factor = self.high / float(s - n)
  70 + hscale_factor = self.img_wide / self.geo_wide
  71 + vscale_factor = self.img_high / self.geo_high
82 72 self.scale_factor = min(hscale_factor, vscale_factor)
83 73
84   - self.cx_offset = (self.wide - (e - w)*self.scale_factor) / 2
85   - self.cy_offset = (self.high - (s - n)*self.scale_factor) / 2
  74 + self.cx_offset = (self.img_wide - self.geo_wide*self.scale_factor) / 2
  75 + self.cy_offset = (self.img_high - self.geo_high*self.scale_factor) / 2
86 76
87 77 def _t(self, x, y):
  78 +# print '-'*60
  79 +# print x, y
88 80 x += self.x_offset
89 81 y += self.y_offset
90 82
91 83 x *= self.scale_factor
92   - y *= -self.scale_factor
93   -
94   - y += self.high
  84 + y *= -self.scale_factor # flip the vertical
  85 + y += self.img_high
95 86
96 87 x += self.cx_offset
97 88 y += self.cy_offset
98 89
99   - return x, y
  90 +# print x, y
100 91
101   - def draw_routes(self, routes, threshold=0.0):
102   - all_paths = routes[0].the_geom_900913
103   - for route in routes[1:]:
104   - all_paths = all_paths.union(route.the_geom_900913)
105   - w, n, e, s = all_paths.extent
106   - self._calc_t(w, n, e, s)
  92 +# x -= self.img_wide / 2
  93 +# x = math.log(abs(x), ((self.img_wide / 2) ** 0.1)) * self.img_wide / 20
  94 +# x += self.img_wide / 2
107 95
108   - legend = {}
109   - for route in routes:
110   - color = (randint(0,255),randint(0,255),randint(0,255))
111   - self._draw_route(route, threshold, color, 4)
112   - legend[route.route] = "#%x%x%x" % color
  96 +# y -= self.img_high / 2
  97 +# y = math.log(abs(y), ((self.img_high / 2) ** 0.1)) * self.img_high / 20
  98 +# y += self.img_high / 2
  99 +# print x, y
113 100
114   - return legend
  101 + return x, y
115 102
116   -class TransitMap (object):
117   - def __init__(self, w, h):
118   - self.wide = w
119   - self.high = h
120 103
121   - self.img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
122   - self.ctx = cairo.Context(self.img)
123   - self.ctx.rectangle(0, 0, w, h)
124   - self.ctx.set_source_rgb(1,1,1)
125   - self.ctx.fill()
  104 +class PilTransitMap (BaseTransitMap):
  105 + def __init__(self, w, h):
  106 + super(PilTransitMap, self).__init__(w, h)
126 107
127   - def _transform_to(self, w, n, e, s):
128   - hscale_factor = self.wide / float(e - w)
129   - vscale_factor = self.high / float(s - n)
130   - scale_factor = min(hscale_factor, vscale_factor)
131   - self.ctx.translate(0, self.high)
132   - self.ctx.scale(scale_factor, -scale_factor)
133   - self.ctx.translate(-w, -n)
  108 + self.img = Image.new("RGBA", (w, h), (255, 255, 255))
134 109
135   - def _trace_poly(self, path_data):
  110 + def _draw_poly(self, path_data, color, size):
136 111
137 112 #
138 113 # This is a recursive function. ``path_data`` may be a tuple of tuples.
@@ -143,73 +118,75 @@ def _trace_poly(self, path_data):
143 118
144 119 if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
145 120 for coord in path_data[0]]):
146   - self.ctx.move_to(*(path_data[0]))
  121 + draw = ImageDraw.Draw(self.img)
  122 + prev = self._t(*path_data[0])
147 123 for coord in path_data[1:]:
148   - self.ctx.line_to(*coord)
  124 + curr = self._t(*coord)
  125 + draw.line(prev + curr, fill=color, width=size)
  126 + prev = curr
149 127
150 128 else:
151 129 for sub_path_data in path_data:
152   - self._trace_poly(sub_path_data)
153   -
154   -# def _mark_beginnings(self, path_data):
  130 + self._draw_poly(sub_path_data, color, size)
155 131
156   -# if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
157   -# for coord in path_data[0]]):
158   -# self.ctx.arc(*(path_data[0]),
159   -# )
  132 + def _store_img(self, fullpath):
  133 + self.img.save(fullpath)
160 134
161   -# else:
162   -# for sub_path_data in path_data:
163   -# self._trace_poly(sub_path_data)
164 135
165   - def _trace_route(self, route, threshold):
166   - path_data = route.the_geom_900913.simplify(threshold).coords
167   - self._trace_poly(path_data)
  136 +class CairoTransitMap (BaseTransitMap):
  137 + def __init__(self, w, h):
  138 + super(CairoTransitMap, self).__init__(w, h)
168 139
169   -# def _trace_features(self, route):
170   -# path_data = route.the_geom_900913.coords
  140 + self.img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
  141 + self.ctx = cairo.Context(self.img)
  142 + self.ctx.rectangle(0, 0, w, h)
  143 + self.ctx.set_source_rgb(1,1,1)
  144 + self.ctx.fill()
171 145
  146 +# def _transform_to(self, w, n, e, s, center=None):
  147 +# self.geo_wide = max(e - center.x, center.x - w)*2
  148 +# self.geo_high = max(s - center.y, center.y - n)*2
  149 +# self.geo_center = center
172 150
173   - def _stroke_route(self, width, color):
174   - self.ctx.set_line_width(width)
175   - self.ctx.set_line_join(cairo.LINE_JOIN_ROUND)
176   - self.ctx.set_source_rgb(*color)
177   - self.ctx.stroke()
  151 +# x_offset = -(center.x - self.geo_wide/2)
  152 +# y_offset = -(center.y - self.geo_high/2)
178 153
179   - def draw_route(self, route, threshold=0.0):
180   - w, n, e, s = route.the_geom_900913.extent
  154 +# hscale_factor = self.img_wide / self.geo_wide
  155 +# vscale_factor = self.img_high / self.geo_high
  156 +# scale_factor = min(hscale_factor, vscale_factor)
181 157
182   - self.ctx.save()
183   - self._transform_to(w, n, e, s)
184   - self._trace_route(route, threshold)
185   - self.ctx.restore()
  158 +# cx_offset = (self.img_wide - self.geo_wide*scale_factor) / 2
  159 +# cy_offset = (self.img_high - self.geo_high*scale_factor) / 2
186 160
187   - self._stroke_route(10, (1, 0, 0))
  161 +# self.ctx.translate(cx_offset, cy_offset)
  162 +# self.ctx.translate(0, self.img_high)
  163 +# self.ctx.scale(scale_factor, -scale_factor)
  164 +# self.ctx.translate(x_offset, y_offset)
188 165
189   - def draw_routes(self, routes, threshold=0.0):
190   - all_paths = routes[0].the_geom_900913
191   - for route in routes[1:]:
192   - all_paths = all_paths.union(route.the_geom_900913)
193   - w, n, e, s = all_paths.extent
  166 + def _draw_poly(self, path_data, color, size, stroke=True):
194 167
195   - legend = {}
  168 + #
  169 + # This is a recursive function. ``path_data`` may be a tuple of tuples.
  170 + # Check the first element and see if it is a pair of numbers. If so,
  171 + # it's a polyline so draw it. Otherwise, we need to go deeper, so call
  172 + # the function again.
  173 + #
196 174
197   - for route in routes:
198   - self.ctx.save()
199   - self._transform_to(w, n, e, s)
200   - self._trace_route(route, threshold)
201   - self.ctx.restore()
  175 + if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
  176 + for coord in path_data[0]]):
  177 + self.ctx.move_to(*self._t(*path_data[0]))
  178 + for coord in path_data[1:]:
  179 + self.ctx.line_to(*self._t(*coord))
202 180
203   - color = (random(), random(), random())
204   - self._stroke_route(10, color)
  181 + else:
  182 + for sub_path_data in path_data:
  183 + self._draw_poly(sub_path_data, color, size, stroke=False)
205 184
206   - legend[route.route] = "#%x%x%x" % color
  185 + if stroke:
  186 + self.ctx.set_line_width(size)
  187 + self.ctx.set_line_join(cairo.LINE_JOIN_ROUND)
  188 + self.ctx.set_source_rgb(*[component/255.0 for component in color])
  189 + self.ctx.stroke()
207 190
208   - return legend
209   -#
210   -# self.ctx.save()
211   -# self._transform_to(w, n, e, s)
212   -# self._trace_features(route)
213   -# self.ctx.restore()
214   -#
215   -# self._stroke_route(2, (0,0,0))
  191 + def _store_img(self, fullpath):
  192 + self.img.write_to_png(fullpath)
22 septa/views.py
@@ -13,7 +13,7 @@
13 13 import settings
14 14
15 15 from models import SeptaRoutes
16   -from carto import PilTransitMap
  16 +from carto import PilTransitMap, CairoTransitMap
17 17
18 18 class MapApp (views.TemplateView):
19 19 template_name = 'index.html'
@@ -60,6 +60,8 @@ def get_nearby_routes(origin, radius=None, count=None, srid='900913'):
60 60
61 61 class IntersectingRoutesView (rest.View):
62 62
  63 + NUM_ROUTES_DEFAULT = 12
  64 +
63 65 def get(self, request, left, bottom, right, top, *args, **kwargs):
64 66 srid = request.REQUEST.get('srid', '4326')
65 67 width = request.REQUEST.get('width', None)
@@ -68,7 +70,7 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
68 70
69 71 width = int(width) if width else 1024
70 72 height = int(height) if height else 768
71   - count = int(count) if count else 10
  73 + count = int(count) if count else self.NUM_ROUTES_DEFAULT
72 74 left = float(left)
73 75 bottom = float(bottom)
74 76 right = float(right)
@@ -82,12 +84,14 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
82 84 srid=srid,
83 85 center=origin)
84 86
85   - transit_map = PilTransitMap(width, height)
86   - legend = transit_map.draw_routes(routes)
  87 + import carto
  88 + if hasattr(carto, 'cairo'):
  89 + transit_map = CairoTransitMap(width, height)
  90 + else:
  91 + transit_map = PilTransitMap(width, height)
87 92
88   - fn = 'map%s.png' % randint(0,1000)
89   - transit_map.img.save(os.path.join(settings.MY_STATIC_ROOT, fn))
90   - map_url = (settings.STATIC_URL + fn)
  93 + legend = transit_map.draw_routes(routes, center=origin)
  94 + map_url = transit_map.store()
91 95
92 96 res = {
93 97 'routes': [{
@@ -97,13 +101,13 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
97 101 'distance': route.distance,
98 102 } for route in routes],
99 103 'map_url': map_url,
100   - 'centroid': "[%s, %s]" % (origin.x, origin.y),
  104 + 'center': "(%s, %s)" % (origin.x, origin.y),
101 105 }
102 106
103 107 # return [json.loads(route.geojson) for route in routes]
104 108 return res
105 109
106   -def get_intersecting_routes(bbox, count=10, srid='4326', center=Point(0,0)):
  110 +def get_intersecting_routes(bbox, count=None, srid='4326', center=Point(0,0)):
107 111 routes = SeptaRoutes.objects.all() \
108 112 .distance(center, field_name='the_geom_' + srid) \
109 113 .order_by('distance')

0 comments on commit 0625b97

Please sign in to comment.
Something went wrong with that request. Please try again.