source: src/django_gheat/wlheatmap/tile.py@ 10615

Last change on this file since 10615 was 10615, checked in by rick, 13 years ago

Weed out whole bunch of unused code, to avoid huge dependency listing.

  • Property svn:executable set to *
File size: 8.3 KB
Line 
1#!/usr/bin/env python
2#
3# Hack to show image generation realtime, sample tile server implementation.
4#
5# Rick van der Zwet <info@rickvanderzwet.nl>
6from collections import defaultdict
7from django.core.management import setup_environ
8from django.db.models import Max
9from django.http import HttpResponse
10from django.views.decorators.cache import cache_page
11from gheat.models import *
12import os
13import pygame
14import sys
15import tempfile
16import time
17
18
19class PyGamePicture():
20 """ Basic PyGame class, allowing simple image manipulations """
21 def __init__(self, method, size):
22 self.surf = pygame.Surface(size,flags=pygame.SRCALPHA)
23
24 def center_crop(self,size):
25 """ Resize to make centered rectange from image """
26 new_surf = pygame.Surface(size, flags=pygame.SRCALPHA)
27 curr_size = self.surf.get_size()
28 new_surf.blit(self.surf,(0,0),
29 ((curr_size[0] - size[0]) / 2, (curr_size[1] - size[1]) / 2, size[0], size[1]))
30 self.surf = new_surf
31
32 def save_and_get_image(self,filename):
33 """ Save the file to the location and return the file """
34 basedir = os.path.dirname(filename)
35 if not os.path.isdir(basedir):
36 os.makedirs(basedir)
37 pygame.image.save(self.surf,filename)
38 return open(filename,'r').read()
39
40 def get_image(self,format='png'):
41 f = tempfile.NamedTemporaryFile(suffix=format)
42 pygame.image.save(self.surf,f.name)
43 f.seek(0)
44 return f.read()
45
46
47
48 def add_circle(self, center, radius, colour=(255,0,0), transparancy=0):
49 """
50 Hack to add lineair gradient circles and merge with the parent. The
51 transparancy can be configured to make the circles to fade out in the
52 beginning
53 """
54 # Make calculations and ranges a whole bunch more easy
55 radius = int(math.ceil(radius))
56
57 new_surf = pygame.Surface(self.surf.get_size(),flags=pygame.SRCALPHA)
58 alpha_per_radius = float(2.55 * (100 - transparancy)) / radius
59 for r in range(radius,1,-1):
60 alpha = min(255,int((radius - r) * alpha_per_radius))
61 combined_colour = colour + (alpha,)
62 pygame.draw.circle(new_surf,combined_colour,center,r,0)
63 self.surf.blit(new_surf,(0,0),special_flags=pygame.BLEND_RGBA_MAX)
64
65
66class LatLonDeg():
67 """ Helper class for coordinate conversions """
68 def __init__(self,lat_deg, lon_deg):
69 self.lat = lat_deg
70 self.lon = lon_deg
71 def __str__(self):
72 return "%.5f,%.5f" % (self.lat, self.lon)
73
74 def deg_per_pixel(self,other,pixel_max):
75 return(LatLonDeg(abs(self.lat - other.lat) / pixel_max, abs(self.lon - other.lon) / pixel_max))
76
77
78
79# Convertions of tile XYZ to WSG coordinates stolen from:
80# http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
81# <stolen>
82import math
83def deg2num(lat_deg, lon_deg, zoom):
84 lat_rad = math.radians(lat_deg)
85 n = 2.0 ** zoom
86 xtile = int((lon_deg + 180.0) / 360.0 * n)
87 ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n)
88 return(xtile, ytile)
89
90def num2deg(xtile, ytile, zoom):
91 n = 2.0 ** zoom
92 lon_deg = xtile / n * 360.0 - 180.0
93 lat_rad = math.atan(math.sinh(math.pi * (1 - 2 * ytile / n)))
94 lat_deg = math.degrees(lat_rad)
95 return(LatLonDeg(lat_deg,lon_deg))
96# </stolen>
97
98
99def boundbox_deg(x,y,z):
100 """ Calculate the boundingbox for a image """
101 return (num2deg(x,y,z), num2deg(x+1,y+1,z))
102
103
104
105def make_tile(x,y,z,filter={},colour=(255,0,0)):
106 """
107 Crude attempt to generate tiles, by placing a gradient circle on a
108 coordinate point. Generate a larger tile and make sure to plot related
109 points first and then crop it to the required size (250x250).
110
111 Many stuff NOT implemented yet, like:
112 - Caching Images.
113 - Conditional Filtering of Meting to allow display of sub-results.
114 - Defining a extra level of transparency if you like to layer multiple tiles
115 on top of each-other.
116 - Color variation, allow the user to dynamically choose a the colour the
117 points to be.
118 - Advanced data plotting, like trying to guess the remainder points.
119 """
120
121 SIZE = 250
122
123 nw_deg,se_deg = boundbox_deg(x,y,z)
124
125
126 Picture = PyGamePicture
127 resolution_deg = nw_deg.deg_per_pixel(se_deg, SIZE)
128 # Converting LatLon to Meters is discussed here:
129 # http://stackoverflow.com/questions/3024404/transform-longitude-latitude-into-meters
130 tile_height = float(40008000) / (2 ** z)
131 meters_per_pixel = float(tile_height) / SIZE
132
133 # Worst case scenario could a circle with 100% 'outside' our 250x250 range
134 # also add data to the picture as circles are used
135 border_pixels = 100 / meters_per_pixel / 2
136
137 im = Picture("RGBA", (SIZE + border_pixels * 2,) * 2)
138
139 nw_deg.lat += resolution_deg.lat * border_pixels
140 nw_deg.lon -= resolution_deg.lon * border_pixels
141 se_deg.lat -= resolution_deg.lat * border_pixels
142 se_deg.lon += resolution_deg.lon * border_pixels
143
144 lat_min = 999
145 lon_min = 999
146 lat_max = 0
147 lon_max = 0
148
149 for key in filter.keys():
150 if filter[key] == 'all':
151 del filter[key]
152
153 filter.update({
154 'latitude__lte' : nw_deg.lat,
155 'latitude__gte' : se_deg.lat,
156 'longitude__lte' : se_deg.lon,
157 'longitude__gte' : nw_deg.lon
158 })
159 # Limit such that high level zooms does not get the whole database
160 metingen = Meting.objects.filter(**filter).order_by('?')[:1000].values_list('latitude', 'longitude', 'signaal')
161
162 # Round numbers example to a less fine grain measurements
163 # d = defaultdict(list)
164 # for lat,lon, signaal in metingen:
165 # d[(round(lat,5),round(lon,5))].append(signaal)
166
167 # metingen = []
168 # for (lat,lon),signals in d.iteritems():
169 # metingen.append((lat,lon,max(signals)))
170
171 # XXX: Signal is not normalized in the database making it unknown when a
172 # signal is said to be 100% or when it is actually less, currently seems to
173 # copy the raw reported values
174 MAX_SIGNAL = 50
175 # XXX: The radius relates to the zoom-level we are in, and should represent
176 # a fixed distance, given the scale. Assume signal/distance to be lineair
177 # such that signal 100% = 100m and 1% = 1m.
178 #
179 # XXX: The relation is not lineair but from a more logeritmic scape, as we
180 # are dealing with radio signals
181 #
182 MAX_RANGE = 100
183
184 def dif(x,y):
185 """ Return difference between two points """
186 return max(x,y) - min(x,y)
187
188 for (latitude, longitude, signaal) in metingen:
189 lat_min = min(lat_min, latitude)
190 lat_max = max(lat_max, latitude)
191 lon_min = min(lon_min, longitude)
192 lon_max = max(lon_max, longitude)
193 xcoord = int(dif(nw_deg.lon,longitude) / (resolution_deg.lon))
194 ycoord = int(dif(nw_deg.lat,latitude) / (resolution_deg.lat))
195
196 # TODO: Please note that this 'logic' technically does apply to WiFi signals,
197 # if you are plotting from the 'source'. When plotting 'measurement' data you
198 # get different patterns and properly need to start looking at techniques like:
199 # Multilateration,Triangulation or Trilateration to recieve 'source' points.
200 #
201 # Also you can treat all points as seperate and use techniques like
202 # Multivariate interpolation to make the graphs. A nice overview at:
203 # http://en.wikipedia.org/wiki/Multivariate_interpolation
204 #
205 # One very intersting one to look at will be Inverse distance weighting
206 # with examples like this:
207 # http://stackoverflow.com/questions/3104781/inverse-distance-weighted-idw-interpolation-with-python
208 signal_normalized = MAX_RANGE - (MAX_SIGNAL - signaal)
209 im.add_circle((xcoord,ycoord),float(signal_normalized) / meters_per_pixel,colour, MAX_SIGNAL - signaal)
210 #im.add_point((xcoord,ycoord),float(signal_normalized) / meters_per_pixel,colour, MAX_SIGNAL - meting.signaal)
211
212 im.center_crop((SIZE,SIZE))
213 return im
214
215def pre_process_tile(request,zoom,x,y):
216 filter = {}
217 colour = (255,0,0)
218 for key, value in request.GET.iteritems():
219 if key == 'colour':
220 colour = tuple(map(int,value.split(',')))
221 else:
222 filter[key] = value
223 now = time.time()
224 im = make_tile(int(x),int(y),int(zoom),filter=filter,colour=colour)
225 return im
226
227# Create your views here.
228# N.B: This cache is handly is you are using in standalone mode
229#@cache_page(60 * 60 * 24, cache="tile_cache")
230def serve_tile(request,zoom,x,y):
231 im = pre_process_tile(request,zoom,x,y)
232 data = im.get_image('png')
233 return HttpResponse(data,mimetype="image/png")
234
235def fixed_wl_only(request,zoom,x,y):
236 """ Pre-render and save attempt """
237 im = pre_process_tile(request,zoom,x,y)
238 data = im.save_and_get_image('/usr/local/var/django/tile/fixed/wl-only/%s/%s,%s.png' % (zoom, x, y))
239 return HttpResponse(data,mimetype="image/png")
Note: See TracBrowser for help on using the repository browser.