source: src/django_gheat/gheat/base.py@ 9039

Last change on this file since 9039 was 9026, checked in by dennisw, 14 years ago

django_gheat - werkt met database, transprancy gefixed

File size: 7.7 KB
Line 
1import settings
2import datetime
3import os
4import stat
5
6from django.core.exceptions import ImproperlyConfigured
7
8import gheat
9import gheat.opacity
10from gheat.models import Accespoint, Gebruiker, Meting
11from gheat import gheatsettings as settings
12from gheat import gmerc
13from gheat import BUILD_EMPTIES, DIRMODE, SIZE, log
14
15
16class ColorScheme(object):
17 """Base class for color scheme representations.
18 """
19
20 def __init__(self, name, fspath):
21 """Takes the name and filesystem path of the defining PNG.
22 """
23# if aspen.mode.DEVDEB:
24# aspen.restarter.track(fspath)
25 self.hook_set(fspath)
26 self.empties_dir = os.path.join(settings.GHEAT_MEDIA_ROOT, name, 'empties')
27 self.build_empties()
28
29
30 def build_empties(self):
31 """Build empty tiles for this color scheme.
32 """
33 empties_dir = self.empties_dir
34
35 if not BUILD_EMPTIES:
36 log.info("not building empty tiles for %s " % self)
37 else:
38 if not os.path.isdir(empties_dir):
39 os.makedirs(empties_dir, DIRMODE)
40 if not os.access(empties_dir, os.R_OK|os.W_OK|os.X_OK):
41 raise ImproperlyConfigured( "Permissions too restrictive on "
42 + "empties directory "
43 + "(%s)." % empties_dir
44 )
45 for fname in os.listdir(empties_dir):
46 if fname.endswith('.png'):
47 os.remove(os.path.join(empties_dir, fname))
48 for zoom, opacity in gheat.opacity.zoom_to_opacity.items():
49 fspath = os.path.join(empties_dir, str(zoom)+'.png')
50 self.hook_build_empty(opacity, fspath)
51
52 log.info("building empty tiles in %s" % empties_dir)
53
54
55 def get_empty_fspath(self, zoom):
56 fspath = os.path.join(self.empties_dir, str(zoom)+'.png')
57 if not os.path.isfile(fspath):
58 self.build_empties() # so we can rebuild empties on the fly
59 return fspath
60
61
62 def hook_set(self):
63 """Set things that your backend will want later.
64 """
65 raise NotImplementedError
66
67
68 def hook_build_empty(self, opacity, fspath):
69 """Given an opacity and a path, save an empty tile.
70 """
71 raise NotImplementedError
72
73
74class Dot(object):
75 """Base class for dot representations.
76
77 Unlike color scheme, the same basic external API works for both backends.
78 How we compute that API is different, though.
79
80 """
81
82 def __init__(self, zoom):
83 """Takes a zoom level.
84 """
85 name = 'dot%d.png' % zoom
86 fspath = os.path.join(settings.GHEAT_CONF_DIR, 'dots', name)
87 self.img, self.half_size = self.hook_get(fspath)
88
89 def hook_get(self, fspath):
90 """Given a filesystem path, return two items.
91 """
92 raise NotImplementedError
93
94
95class Tile(object):
96 """Base class for tile representations.
97 """
98
99 img = None
100
101 def __init__(self, color_scheme, dots, zoom, x, y, fspath):
102 """x and y are tile coords per Google Maps.
103 """
104
105 # Calculate some things.
106 # ======================
107
108 dot = dots[zoom]
109
110
111 # Translate tile to pixel coords.
112 # -------------------------------
113
114 x1 = x * SIZE
115 x2 = x1 + 255
116 y1 = y * SIZE
117 y2 = y1 + 255
118
119
120 # Expand bounds by one-half dot width.
121 # ------------------------------------
122
123 x1 = x1 - dot.half_size
124 x2 = x2 + dot.half_size
125 y1 = y1 - dot.half_size
126 y2 = y2 + dot.half_size
127 expanded_size = (x2-x1, y2-y1)
128
129
130 # Translate new pixel bounds to lat/lng.
131 # --------------------------------------
132
133 n, w = gmerc.px2ll(x1, y1, zoom)
134 s, e = gmerc.px2ll(x2, y2, zoom)
135
136
137 # Save
138 # ====
139
140 self.dot = dot.img
141 self.pad = dot.half_size
142
143 self.x = x
144 self.y = y
145
146 self.x1 = x1
147 self.y1 = y1
148
149 self.x2 = x2
150 self.y2 = y2
151
152 self.expanded_size = expanded_size
153 self.llbound = (n,s,e,w)
154 self.zoom = zoom
155 self.fspath = fspath
156 self.opacity = gheat.opacity.zoom_to_opacity[zoom]
157 self.color_scheme = color_scheme
158
159
160 def is_empty(self):
161 """With attributes set on self, return a boolean.
162
163 Calc lat/lng bounds of this tile (include half-dot-width of padding)
164 SELECT count(uid) FROM points
165 """
166 numpoints = Meting.objects.num_points(self)
167 return numpoints == 0
168
169
170 def is_stale(self):
171 """With attributes set on self, return a boolean.
172
173 Calc lat/lng bounds of this tile (include half-dot-width of padding)
174 SELECT count(uid) FROM points WHERE modtime < modtime_tile
175 """
176 if not os.path.isfile(self.fspath):
177 return True
178
179 timestamp = os.stat(self.fspath)[stat.ST_MTIME]
180 datum = datetime.datetime.fromtimestamp(timestamp)
181
182 numpoints = Meting.objects.num_points(self, datum)
183
184 return numpoints > 0
185
186
187 def rebuild(self):
188 """Rebuild the image at self.img. Real work delegated to subclasses.
189 """
190
191 # Calculate points.
192 # =================
193 # Build a closure that gives us the x,y pixel coords of the points
194 # to be included on this tile, relative to the top-left of the tile.
195
196 _points = Meting.objects.points_inside(self)
197
198 def points():
199 """Yield x,y pixel coords within this tile, top-left of dot.
200 """
201 result = []
202 for point in _points:
203 x, y = gmerc.ll2px(point.latitude, point.longitude, self.zoom)
204 x = x - self.x1 # account for tile offset relative to
205 y = y - self.y1 # overall map
206 point_signaal = point.signaal
207 while point_signaal > 0:
208 result.append((x-self.pad,y-self.pad))
209 point_signaal = point_signaal - 1
210 return result
211
212
213 # Main logic
214 # ==========
215 # Hand off to the subclass to actually build the image, then come back
216 # here to maybe create a directory before handing back to the backend
217 # to actually write to disk.
218
219 self.img = self.hook_rebuild(points())
220
221 dirpath = os.path.dirname(self.fspath)
222 if dirpath and not os.path.isdir(dirpath):
223 os.makedirs(dirpath, DIRMODE)
224
225
226 def hook_rebuild(self, points, opacity):
227 """Rebuild and save the file using the current library.
228
229 The algorithm runs something like this:
230
231 o start a tile canvas/image that is a dots-worth oversized
232 o loop through points and multiply dots on the tile
233 o trim back down to straight tile size
234 o invert/colorize the image
235 o make it transparent
236
237 Return the img object; it will be sent back to hook_save after a
238 directory is made if needed.
239
240 Trim after looping because we multiply is the only step that needs the
241 extra information.
242
243 The coloring and inverting can happen in the same pixel manipulation
244 because you can invert colors.png. That is a 1px by 256px PNG that maps
245 grayscale values to color values. You can customize that file to change
246 the coloration.
247
248 """
249 raise NotImplementedError
250
251
252 def save(self):
253 """Write the image at self.img to disk.
254 """
255 raise NotImplementedError
256
257
Note: See TracBrowser for help on using the repository browser.