source: src/django_gheat/wlheatmap/tile.py

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

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

  • Property svn:executable set to *
File size: 8.3 KB
RevLine 
[9147]1#!/usr/bin/env python
2#
[9150]3# Hack to show image generation realtime, sample tile server implementation.
[9147]4#
5# Rick van der Zwet <info@rickvanderzwet.nl>
[9662]6from collections import defaultdict
[9147]7from django.core.management import setup_environ
[9151]8from django.db.models import Max
[9147]9from django.http import HttpResponse
[9392]10from django.views.decorators.cache import cache_page
[9147]11from gheat.models import *
[9549]12import os
[9149]13import pygame
[9147]14import sys
[9148]15import tempfile
[9184]16import time
[9147]17
[9150]18
[9148]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)
[9147]23
[9149]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
[9549]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()
[9147]39
[9184]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()
[9147]45
[9184]46
[9549]47
[9151]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
[9149]57 new_surf = pygame.Surface(self.surf.get_size(),flags=pygame.SRCALPHA)
[9151]58 alpha_per_radius = float(2.55 * (100 - transparancy)) / radius
[9148]59 for r in range(radius,1,-1):
[9174]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)
[9148]63 self.surf.blit(new_surf,(0,0),special_flags=pygame.BLEND_RGBA_MAX)
64
65
[9147]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
[9166]105def make_tile(x,y,z,filter={},colour=(255,0,0)):
[9150]106 """
107 Crude attempt to generate tiles, by placing a gradient circle on a
[9149]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).
[9148]110
111 Many stuff NOT implemented yet, like:
[9150]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.
[9147]119 """
[9150]120
[9149]121 SIZE = 250
122
[9147]123 nw_deg,se_deg = boundbox_deg(x,y,z)
[9149]124
[9147]125
[9148]126 Picture = PyGamePicture
[9149]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
[9147]132
[9149]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
[9147]144 lat_min = 999
145 lon_min = 999
146 lat_max = 0
147 lon_max = 0
[9166]148
[9577]149 for key in filter.keys():
150 if filter[key] == 'all':
151 del filter[key]
152
[9166]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 })
[9549]159 # Limit such that high level zooms does not get the whole database
[9571]160 metingen = Meting.objects.filter(**filter).order_by('?')[:1000].values_list('latitude', 'longitude', 'signaal')
[9151]161
[9662]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
[9151]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
[9152]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
[9147]183
184 def dif(x,y):
[9150]185 """ Return difference between two points """
[9147]186 return max(x,y) - min(x,y)
187
[9549]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))
[9150]195
[9152]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.
[9148]200 #
[9152]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
[9150]204 #
[9152]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
[9549]208 signal_normalized = MAX_RANGE - (MAX_SIGNAL - signaal)
209 im.add_circle((xcoord,ycoord),float(signal_normalized) / meters_per_pixel,colour, MAX_SIGNAL - signaal)
[9184]210 #im.add_point((xcoord,ycoord),float(signal_normalized) / meters_per_pixel,colour, MAX_SIGNAL - meting.signaal)
[9147]211
[9149]212 im.center_crop((SIZE,SIZE))
[9147]213 return im
214
[9549]215def pre_process_tile(request,zoom,x,y):
[9392]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)
[9549]225 return im
226
227# Create your views here.
[9572]228# N.B: This cache is handly is you are using in standalone mode
229#@cache_page(60 * 60 * 24, cache="tile_cache")
[9549]230def serve_tile(request,zoom,x,y):
231 im = pre_process_tile(request,zoom,x,y)
[9392]232 data = im.get_image('png')
[9549]233 return HttpResponse(data,mimetype="image/png")
[9188]234
[9549]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.