Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

  • Loading branch information...
commit 0625b970d86adc5efde7f3aae4bd4c03a702e19d 1 parent 66b5e6e
@mjumbewu authored
Showing with 130 additions and 149 deletions.
  1. +117 −140 septa/carto.py
  2. +13 −9 septa/views.py
View
257 septa/carto.py
@@ -1,138 +1,113 @@
import itertools
+import math
+import os
+from random import random, randint
+
+# Import imaging libraries
try:
import cairo
except ImportError:
- pass
-import Image, ImageDraw
+ # Use PIL as a fallback
+ import Image, ImageDraw
-from random import random, randint
-#img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 100, 100)
-#ctx = cairo.Context(img)
-#ctx.scale(100, 100)
-#ctx.move_to(0, 0)
-#ctx.line_to(30, 50)
-#img.write_to_png('hello.png')
-#ctx.stroke()
+import settings
class Cartographer (object):
pass
-class PilTransitMap (object):
+class BaseTransitMap (object):
def __init__(self, w, h):
- self.wide = w
- self.high = h
+ self.img_wide = w
+ self.img_high = h
- self.img = Image.new("RGBA", (w, h), (255, 255, 255))
-
- def _draw_poly(self, path_data, color, size):
+ def draw_route(self, route, threshold=0.0):
+ w, n, e, s = route.the_geom_900913.extent
+ self._calc_t(w, n, e, s)
- #
- # This is a recursive function. ``path_data`` may be a tuple of tuples.
- # Check the first element and see if it is a pair of numbers. If so,
- # it's a polyline so draw it. Otherwise, we need to go deeper, so call
- # the function again.
- #
+ self._draw_route(route, threshold, (255, 0, 0), 4)
- if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
- for coord in path_data[0]]):
- draw = ImageDraw.Draw(self.img)
- prev = self._t(*path_data[0])
- for coord in path_data[1:]:
- curr = self._t(*coord)
- draw.line(prev + curr, fill=color, width=size)
- prev = curr
+ def draw_routes(self, routes, threshold=0.0, center=None):
+ all_paths = routes[0].the_geom_900913
+ for route in routes[1:]:
+ all_paths = all_paths.union(route.the_geom_900913)
+ w, n, e, s = all_paths.extent
+ center.transform(all_paths.srid)
+ self._calc_t(w, n, e, s, center)
- else:
- for sub_path_data in path_data:
- self._draw_poly(sub_path_data, color, size)
+ legend = {}
+ for route in routes:
+ color = (randint(0,255),randint(0,255),randint(0,255))
+ self._draw_route(route, threshold, color, 4)
+ legend[route.route] = "#%x%x%x" % color
-# def _mark_beginnings(self, path_data):
+ return legend
-# if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
-# for coord in path_data[0]]):
-# self.ctx.arc(*(path_data[0]),
-# )
+ def store(self):
+ fn = 'map%s.png' % randint(0,1000)
+ fullpath = os.path.join(settings.MY_STATIC_ROOT, fn)
+ map_url = (settings.STATIC_URL + fn)
-# else:
-# for sub_path_data in path_data:
-# self._trace_poly(sub_path_data)
+ self._store_img(fullpath)
+ return map_url
def _draw_route(self, route, threshold, color, size):
path_data = route.the_geom_900913.simplify(threshold).coords
self._draw_poly(path_data, color, size)
-# def _trace_features(self, route):
-# path_data = route.the_geom_900913.coords
+ def _calc_t(self, w, n, e, s, center=None):
+ self.geo_wide = max(e - center.x, center.x - w)*2
+ self.geo_high = max(s - center.y, center.y - n)*2
+ self.geo_center = center
+ print w, n, e, s, center.x, center.y
+ print self.geo_wide, self.geo_high
- def draw_route(self, route, threshold=0.0):
- w, n, e, s = route.the_geom_900913.extent
- self._calc_t(w, n, e, s)
-
- self._draw_route(route, threshold, (255, 0, 0), 4)
-
- def _calc_t(self, w, n, e, s):
- self.x_offset = -w
- self.y_offset = -n
+ self.x_offset = -(center.x - self.geo_wide/2)
+ self.y_offset = -(center.y - self.geo_high/2)
- hscale_factor = self.wide / float(e - w)
- vscale_factor = self.high / float(s - n)
+ hscale_factor = self.img_wide / self.geo_wide
+ vscale_factor = self.img_high / self.geo_high
self.scale_factor = min(hscale_factor, vscale_factor)
- self.cx_offset = (self.wide - (e - w)*self.scale_factor) / 2
- self.cy_offset = (self.high - (s - n)*self.scale_factor) / 2
+ self.cx_offset = (self.img_wide - self.geo_wide*self.scale_factor) / 2
+ self.cy_offset = (self.img_high - self.geo_high*self.scale_factor) / 2
def _t(self, x, y):
+# print '-'*60
+# print x, y
x += self.x_offset
y += self.y_offset
x *= self.scale_factor
- y *= -self.scale_factor
-
- y += self.high
+ y *= -self.scale_factor # flip the vertical
+ y += self.img_high
x += self.cx_offset
y += self.cy_offset
- return x, y
+# print x, y
- def draw_routes(self, routes, threshold=0.0):
- all_paths = routes[0].the_geom_900913
- for route in routes[1:]:
- all_paths = all_paths.union(route.the_geom_900913)
- w, n, e, s = all_paths.extent
- self._calc_t(w, n, e, s)
+# x -= self.img_wide / 2
+# x = math.log(abs(x), ((self.img_wide / 2) ** 0.1)) * self.img_wide / 20
+# x += self.img_wide / 2
- legend = {}
- for route in routes:
- color = (randint(0,255),randint(0,255),randint(0,255))
- self._draw_route(route, threshold, color, 4)
- legend[route.route] = "#%x%x%x" % color
+# y -= self.img_high / 2
+# y = math.log(abs(y), ((self.img_high / 2) ** 0.1)) * self.img_high / 20
+# y += self.img_high / 2
+# print x, y
- return legend
+ return x, y
-class TransitMap (object):
- def __init__(self, w, h):
- self.wide = w
- self.high = h
- self.img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
- self.ctx = cairo.Context(self.img)
- self.ctx.rectangle(0, 0, w, h)
- self.ctx.set_source_rgb(1,1,1)
- self.ctx.fill()
+class PilTransitMap (BaseTransitMap):
+ def __init__(self, w, h):
+ super(PilTransitMap, self).__init__(w, h)
- def _transform_to(self, w, n, e, s):
- hscale_factor = self.wide / float(e - w)
- vscale_factor = self.high / float(s - n)
- scale_factor = min(hscale_factor, vscale_factor)
- self.ctx.translate(0, self.high)
- self.ctx.scale(scale_factor, -scale_factor)
- self.ctx.translate(-w, -n)
+ self.img = Image.new("RGBA", (w, h), (255, 255, 255))
- def _trace_poly(self, path_data):
+ def _draw_poly(self, path_data, color, size):
#
# This is a recursive function. ``path_data`` may be a tuple of tuples.
@@ -143,73 +118,75 @@ def _trace_poly(self, path_data):
if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
for coord in path_data[0]]):
- self.ctx.move_to(*(path_data[0]))
+ draw = ImageDraw.Draw(self.img)
+ prev = self._t(*path_data[0])
for coord in path_data[1:]:
- self.ctx.line_to(*coord)
+ curr = self._t(*coord)
+ draw.line(prev + curr, fill=color, width=size)
+ prev = curr
else:
for sub_path_data in path_data:
- self._trace_poly(sub_path_data)
-
-# def _mark_beginnings(self, path_data):
+ self._draw_poly(sub_path_data, color, size)
-# if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
-# for coord in path_data[0]]):
-# self.ctx.arc(*(path_data[0]),
-# )
+ def _store_img(self, fullpath):
+ self.img.save(fullpath)
-# else:
-# for sub_path_data in path_data:
-# self._trace_poly(sub_path_data)
- def _trace_route(self, route, threshold):
- path_data = route.the_geom_900913.simplify(threshold).coords
- self._trace_poly(path_data)
+class CairoTransitMap (BaseTransitMap):
+ def __init__(self, w, h):
+ super(CairoTransitMap, self).__init__(w, h)
-# def _trace_features(self, route):
-# path_data = route.the_geom_900913.coords
+ self.img = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
+ self.ctx = cairo.Context(self.img)
+ self.ctx.rectangle(0, 0, w, h)
+ self.ctx.set_source_rgb(1,1,1)
+ self.ctx.fill()
+# def _transform_to(self, w, n, e, s, center=None):
+# self.geo_wide = max(e - center.x, center.x - w)*2
+# self.geo_high = max(s - center.y, center.y - n)*2
+# self.geo_center = center
- def _stroke_route(self, width, color):
- self.ctx.set_line_width(width)
- self.ctx.set_line_join(cairo.LINE_JOIN_ROUND)
- self.ctx.set_source_rgb(*color)
- self.ctx.stroke()
+# x_offset = -(center.x - self.geo_wide/2)
+# y_offset = -(center.y - self.geo_high/2)
- def draw_route(self, route, threshold=0.0):
- w, n, e, s = route.the_geom_900913.extent
+# hscale_factor = self.img_wide / self.geo_wide
+# vscale_factor = self.img_high / self.geo_high
+# scale_factor = min(hscale_factor, vscale_factor)
- self.ctx.save()
- self._transform_to(w, n, e, s)
- self._trace_route(route, threshold)
- self.ctx.restore()
+# cx_offset = (self.img_wide - self.geo_wide*scale_factor) / 2
+# cy_offset = (self.img_high - self.geo_high*scale_factor) / 2
- self._stroke_route(10, (1, 0, 0))
+# self.ctx.translate(cx_offset, cy_offset)
+# self.ctx.translate(0, self.img_high)
+# self.ctx.scale(scale_factor, -scale_factor)
+# self.ctx.translate(x_offset, y_offset)
- def draw_routes(self, routes, threshold=0.0):
- all_paths = routes[0].the_geom_900913
- for route in routes[1:]:
- all_paths = all_paths.union(route.the_geom_900913)
- w, n, e, s = all_paths.extent
+ def _draw_poly(self, path_data, color, size, stroke=True):
- legend = {}
+ #
+ # This is a recursive function. ``path_data`` may be a tuple of tuples.
+ # Check the first element and see if it is a pair of numbers. If so,
+ # it's a polyline so draw it. Otherwise, we need to go deeper, so call
+ # the function again.
+ #
- for route in routes:
- self.ctx.save()
- self._transform_to(w, n, e, s)
- self._trace_route(route, threshold)
- self.ctx.restore()
+ if len(path_data[0]) == 2 and all([isinstance(coord, (int, float))
+ for coord in path_data[0]]):
+ self.ctx.move_to(*self._t(*path_data[0]))
+ for coord in path_data[1:]:
+ self.ctx.line_to(*self._t(*coord))
- color = (random(), random(), random())
- self._stroke_route(10, color)
+ else:
+ for sub_path_data in path_data:
+ self._draw_poly(sub_path_data, color, size, stroke=False)
- legend[route.route] = "#%x%x%x" % color
+ if stroke:
+ self.ctx.set_line_width(size)
+ self.ctx.set_line_join(cairo.LINE_JOIN_ROUND)
+ self.ctx.set_source_rgb(*[component/255.0 for component in color])
+ self.ctx.stroke()
- return legend
-#
-# self.ctx.save()
-# self._transform_to(w, n, e, s)
-# self._trace_features(route)
-# self.ctx.restore()
-#
-# self._stroke_route(2, (0,0,0))
+ def _store_img(self, fullpath):
+ self.img.write_to_png(fullpath)
View
22 septa/views.py
@@ -13,7 +13,7 @@
import settings
from models import SeptaRoutes
-from carto import PilTransitMap
+from carto import PilTransitMap, CairoTransitMap
class MapApp (views.TemplateView):
template_name = 'index.html'
@@ -60,6 +60,8 @@ def get_nearby_routes(origin, radius=None, count=None, srid='900913'):
class IntersectingRoutesView (rest.View):
+ NUM_ROUTES_DEFAULT = 12
+
def get(self, request, left, bottom, right, top, *args, **kwargs):
srid = request.REQUEST.get('srid', '4326')
width = request.REQUEST.get('width', None)
@@ -68,7 +70,7 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
width = int(width) if width else 1024
height = int(height) if height else 768
- count = int(count) if count else 10
+ count = int(count) if count else self.NUM_ROUTES_DEFAULT
left = float(left)
bottom = float(bottom)
right = float(right)
@@ -82,12 +84,14 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
srid=srid,
center=origin)
- transit_map = PilTransitMap(width, height)
- legend = transit_map.draw_routes(routes)
+ import carto
+ if hasattr(carto, 'cairo'):
+ transit_map = CairoTransitMap(width, height)
+ else:
+ transit_map = PilTransitMap(width, height)
- fn = 'map%s.png' % randint(0,1000)
- transit_map.img.save(os.path.join(settings.MY_STATIC_ROOT, fn))
- map_url = (settings.STATIC_URL + fn)
+ legend = transit_map.draw_routes(routes, center=origin)
+ map_url = transit_map.store()
res = {
'routes': [{
@@ -97,13 +101,13 @@ def get(self, request, left, bottom, right, top, *args, **kwargs):
'distance': route.distance,
} for route in routes],
'map_url': map_url,
- 'centroid': "[%s, %s]" % (origin.x, origin.y),
+ 'center': "(%s, %s)" % (origin.x, origin.y),
}
# return [json.loads(route.geojson) for route in routes]
return res
-def get_intersecting_routes(bbox, count=10, srid='4326', center=Point(0,0)):
+def get_intersecting_routes(bbox, count=None, srid='4326', center=Point(0,0)):
routes = SeptaRoutes.objects.all() \
.distance(center, field_name='the_geom_' + srid) \
.order_by('distance')
Please sign in to comment.
Something went wrong with that request. Please try again.