Index: src/django_gheat/website/static/OpenStreetMap.js
===================================================================
--- src/django_gheat/website/static/OpenStreetMap.js	(revision 9139)
+++ src/django_gheat/website/static/OpenStreetMap.js	(revision 9147)
@@ -154,2 +154,15 @@
     CLASS_NAME: "OpenLayers.Layer.Overlay1"
 });
+
+OpenLayers.Layer.OSM.Overlay2 = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    initialize: function(name, options) {
+        var url = [
+            "/website/tile/${z}/${x},${y}.png"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 21 }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Overlay2"
+});
Index: src/django_gheat/website/static/heatmap.js
===================================================================
--- src/django_gheat/website/static/heatmap.js	(revision 9139)
+++ src/django_gheat/website/static/heatmap.js	(revision 9147)
@@ -23,4 +23,7 @@
 	layerHeatmap = new OpenLayers.Layer.OSM.Overlay1("Overlay 1", {isBaseLayer: false, visibility: true});
 	map.addLayer(layerHeatmap);
+
+	layerHeatmap2 = new OpenLayers.Layer.OSM.Overlay2("Overlay 2", {isBaseLayer: false, visibility: false});
+	map.addLayer(layerHeatmap2);
 	
 	var lon   = 4.48907;
Index: src/django_gheat/website/tile.py
===================================================================
--- src/django_gheat/website/tile.py	(revision 9147)
+++ src/django_gheat/website/tile.py	(revision 9147)
@@ -0,0 +1,163 @@
+#!/usr/bin/env python
+#
+# Hack to show image generation realtime, sample tile server implementation
+#
+# Rick van der Zwet <info@rickvanderzwet.nl>
+from PIL import Image
+from django.core.management import setup_environ
+from django.http import HttpResponse
+from gheat.models import *
+import ImageDraw
+import logging
+import numpy as np
+import settings
+import sys
+
+log = logging.getLogger('sample_tile')
+logging.basicConfig(level=logging.DEBUG)
+setup_environ(settings)
+
+
+def make_circle(draw, center, radius,colour=(0,255,0)):
+  """ Cicle gradient is created by creating smaller and smaller cicles """
+  (center_x, center_y) = center
+  for i in range(0,radius):
+    draw.ellipse(
+      (center_x - radius + i,
+       center_y - radius + i,
+       center_x + radius - i,
+       center_y + radius - i
+      ),
+      colour +(255 * i/(radius * 2),)
+    )
+
+
+class Picture():
+  """ Basic class, allowing simple image manipulations """
+  im = None
+  def __init__(self, method, size):
+    self.im = Image.new(method, size)
+    self.data = np.array(self.im)
+
+  def save(self,*args, **kw):
+        self.im.save(*args, **kw)
+
+  def add_circle(self, center, radius, colour):
+    """ Adding a new cicle is a matter of creating a new one in a empty layer
+    and merging it with the current one 
+
+    XXX: Very heavy code, should actually only work on the data arrays, instead
+    of doing all the magic with high-level images """
+
+    im_new = Image.new("RGBA", self.im.size)
+    draw = ImageDraw.Draw(im_new)
+    make_circle(draw, center, radius, colour)
+    
+    data2 = np.array(im_new)
+    
+    # Add channels to make new images
+    self.data = self.data + data2
+    self.im = Image.fromarray(self.data)
+
+
+
+class LatLonDeg():
+  """ Helper class for coordinate conversions """
+  def __init__(self,lat_deg, lon_deg):
+    self.lat = lat_deg
+    self.lon = lon_deg
+  def __str__(self):
+    return "%.5f,%.5f" % (self.lat, self.lon)
+
+  def deg_per_pixel(self,other,pixel_max):
+    return(LatLonDeg(abs(self.lat - other.lat) / pixel_max, abs(self.lon - other.lon) / pixel_max))
+
+
+
+# Convertions of tile XYZ to WSG coordinates stolen from:
+#    http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
+# <stolen>
+import math
+def deg2num(lat_deg, lon_deg, zoom):
+  lat_rad = math.radians(lat_deg)
+  n = 2.0 ** zoom
+  xtile = int((lon_deg + 180.0) / 360.0 * n)
+  ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
+  return(xtile, ytile)
+
+def num2deg(xtile, ytile, zoom):
+  n = 2.0 ** zoom
+  lon_deg = xtile / n * 360.0 - 180.0
+  lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
+  lat_deg = math.degrees(lat_rad)
+  return(LatLonDeg(lat_deg,lon_deg))
+# </stolen>
+
+
+def boundbox_deg(x,y,z):
+  """ Calculate the boundingbox for a image """
+  return (num2deg(x,y,z), num2deg(x+1,y+1,z))
+
+
+
+def make_tile(x,y,z):
+  """ Crude attempt to generate tiles, by placing a gradient circle on a
+  coordinate point. Many stuff NOT implemented yet, like:
+  * Caching Images
+  * Behaviour around edges (generate larger imagea)
+  """
+  nw_deg,se_deg = boundbox_deg(x,y,z)
+  
+  resolution_deg = nw_deg.deg_per_pixel(se_deg, 250)
+  im = Picture("RGBA",(250,250))
+  
+  lat_min = 999
+  lon_min = 999
+  lat_max = 0
+  lon_max = 0
+  metingen = Meting.objects.select_related().filter(
+     latitude__lte=nw_deg.lat,latitude__gte=se_deg.lat,
+     longitude__lte=se_deg.lon,longitude__gte=nw_deg.lon)
+  if metingen.count() > 100:
+    metingen = metingen[1:100]
+  
+  def dif(x,y):
+    return max(x,y) - min(x,y)
+  
+  for meting in metingen:
+    lat_min = min(lat_min, meting.latitude)
+    lat_max = max(lat_max, meting.latitude)
+    lon_min = min(lon_min, meting.longitude)
+    lon_max = max(lon_max, meting.longitude)
+    xcoord = dif(nw_deg.lon,meting.longitude) / (resolution_deg.lon)
+    ycoord = dif(nw_deg.lat,meting.latitude) / (resolution_deg.lat)
+    log.info(meting.accespoint.ssid, meting.latitude, meting.longitude, xcoord, ycoord)
+    im.add_circle((xcoord,ycoord),z,(255,0,0))
+  
+  log.info("BoundingBox NW: %s" % nw_deg)
+  log.info("BoundingBox SE: %s" % se_deg)
+  log.info("")
+  log.info("MetingBox   NW: %.5f,%.5f" % (lat_max, lon_min))
+  log.info("MetingBox   SE: %.5f,%.5f" % (lat_min, lon_max))
+  log.info("")
+  log.info("Metingen Count: %s" % metingen.count())
+
+  return im
+
+
+# Create your views here.
+def serve_tile(request,zoom,x,y):
+  im = make_tile(int(x),int(y),int(zoom))
+  response = HttpResponse(mimetype="image/png")
+  im.save(response, 'PNG')
+  return response
+
+if __name__ == '__main__':
+  x = int(sys.argv[1])
+  y = int(sys.argv[2])
+  z = 14
+
+  im = make_tile(x,y,z)
+  filename = 'sample-gradient.png'
+  im.save(filename)
+  log.info("#INFO: Output saved as '%s'" % filename)
Index: src/django_gheat/website/urls.py
===================================================================
--- src/django_gheat/website/urls.py	(revision 9139)
+++ src/django_gheat/website/urls.py	(revision 9147)
@@ -10,2 +10,11 @@
     ('^$', HomeView.as_view())
 )
+
+urlpatterns += patterns('website.tile',
+    url(
+        # Example : today/fire/12/3,2.png
+        regex = r'^tile/(?P<zoom>\d+)/(?P<x>\d+),(?P<y>\d+).png$',
+        view = 'serve_tile',
+        name = 'serve_tile',
+       ),
+    )
