Skip to content

Commit ebd62bd

Browse files
committed
Merge pull request #54 from axiom-data-science/glg
GetLegendGraphic support (base support, nothing dataset specific). Fixes #53.
2 parents 137a158 + 9954e55 commit ebd62bd

File tree

9 files changed

+486
-190
lines changed

9 files changed

+486
-190
lines changed

wms/glg_handler.py

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# -*- coding: utf-8 -*-
2+
from django.http import HttpResponse
3+
4+
import numpy as np
5+
6+
import matplotlib
7+
8+
from matplotlib.pyplot import get_cmap, colorbar, legend
9+
import matplotlib.pyplot as plt
10+
from matplotlib.backends.backend_agg import FigureCanvasAgg
11+
12+
from wms import logger
13+
14+
from matplotlib import rcParams
15+
rcParams['font.family'] = 'sans-serif'
16+
rcParams['font.sans-serif'] = ['Bitstream Vera Sans']
17+
rcParams['font.serif'] = ['Bitstream Vera Sans']
18+
rcParams['font.size'] = '10'
19+
rcParams['figure.autolayout'] = True
20+
rcParams['savefig.dpi'] = 72.
21+
22+
23+
def create_axis(request, position=None):
24+
position = position or [0, 0, 1, 1]
25+
# Create figure
26+
plt.close('all')
27+
dpi = 72.
28+
width = int(request.GET['width'])
29+
height = int(request.GET['height'])
30+
fig = plt.figure(dpi=dpi, figsize=(width / dpi, height / dpi), facecolor=None, edgecolor=None, frameon=False, tight_layout=True)
31+
fig.set_alpha(0)
32+
ax = fig.add_axes(position)
33+
34+
csr = request.GET['colorscalerange']
35+
if request.GET['logscale'] is True:
36+
norm = matplotlib.colors.LogNorm(vmin=csr.min, vmax=csr.max, clip=False)
37+
else:
38+
norm = matplotlib.colors.Normalize(vmin=csr.min, vmax=csr.max, clip=False)
39+
40+
return fig, ax, norm
41+
42+
43+
def figure_response(fig, request, adjust=None, **kwargs):
44+
45+
canvas = FigureCanvasAgg(fig)
46+
response = HttpResponse(content_type='image/png')
47+
canvas.print_png(response, bbox_inches='tight', pad_inches=0.1, **kwargs)
48+
return response
49+
50+
51+
def get_position(request):
52+
if request.GET['horizontal'] is True:
53+
base = [0.08, 0.5, 0.8, 0.4]
54+
if request.GET['showlabel'] is False:
55+
base[3] += 0.1
56+
if request.GET['showvalues'] is False:
57+
base[3] += 0.2
58+
else:
59+
base = [0.05, 0.1, 0.4, 0.8]
60+
if request.GET['showlabel'] is False:
61+
base[2] += 0.1
62+
if request.GET['showvalues'] is False:
63+
base[2] += 0.2
64+
65+
return base
66+
67+
68+
def filledcontour(request):
69+
# Create figure
70+
fig, ax, norm = create_axis(request, get_position(request))
71+
72+
orientation = 'vertical'
73+
if request.GET['horizontal'] is True:
74+
orientation = 'horizontal'
75+
csr = request.GET['colorscalerange']
76+
77+
if request.GET['logscale'] is True:
78+
levs = np.hstack(([csr.min-3], np.linspace(csr.min, csr.max, request.GET['numcontours']), [csr.max+40]))
79+
x, y = np.meshgrid(np.arange(1), np.arange(1))
80+
cs = ax.contourf(x, y, x, levels=levs, norm=norm, cmap=get_cmap(request.GET['colormap']))
81+
cb = colorbar(mappable=cs, cax=ax, orientation=orientation, spacing='proportional', extendrect=False, use_gridspec=True)
82+
if request.GET['showvalues'] is False:
83+
cb.set_ticks([])
84+
else:
85+
cb.set_ticks(levs[1:-1])
86+
cb.set_ticklabels([ "%.1f" % x for x in levs[1:-1] ])
87+
88+
else:
89+
levs = np.linspace(csr.min, csr.max, request.GET['numcontours'])
90+
x, y = np.meshgrid(np.arange(1), np.arange(1))
91+
cs = ax.contourf(x, y, x, levels=levs, norm=norm, cmap=get_cmap(request.GET['colormap']), extend='both')
92+
cb = colorbar(mappable=cs, cax=ax, orientation=orientation, spacing='proportional', extendrect=False, use_gridspec=True)
93+
if request.GET['showvalues'] is False:
94+
cb.set_ticks([])
95+
else:
96+
cb.set_ticks(levs)
97+
cb.set_ticklabels([ "%.1f" % x for x in levs ])
98+
99+
if request.GET['showlabel'] is True:
100+
cb.set_label(request.GET['units'])
101+
102+
# Return HttpResponse
103+
return figure_response(fig, request)
104+
105+
106+
def contour(request):
107+
108+
# Create figure
109+
fig, ax, norm = create_axis(request)
110+
ax.set_axis_off()
111+
112+
csr = request.GET['colorscalerange']
113+
114+
if request.GET['logscale'] is True:
115+
levs = np.hstack(([csr.min-1], np.linspace(csr.min, csr.max, request.GET['numcontours']), [csr.max+1]))
116+
levs_labels = [ "%.1f" % x for x in levs[1:-1] ]
117+
if request.GET['showvalues'] is False:
118+
levs_labels = [ '' for x in range(levs.size-2) ]
119+
x, y = np.meshgrid(np.arange(1), np.arange(1))
120+
cs = ax.contourf(x, y, x, levels=levs, norm=norm, cmap=get_cmap(request.GET['colormap']))
121+
proxy = [plt.Rectangle((0, 0), 0, 0, fc=pc.get_facecolor()[0]) for pc in cs.collections]
122+
123+
else:
124+
levs = np.linspace(csr.min, csr.max, request.GET['numcontours'])
125+
levs_labels = [ "%.1f" % x for x in levs ]
126+
if request.GET['showvalues'] is False:
127+
levs_labels = [ '' for x in range(levs.size) ]
128+
x, y = np.meshgrid(np.arange(1), np.arange(1))
129+
cs = ax.contourf(x, y, x, levels=levs, norm=norm, cmap=get_cmap(request.GET['colormap']), extend='max')
130+
proxy = [plt.Rectangle((0, 0), 0, 0, fc=pc.get_facecolor()[0]) for pc in cs.collections]
131+
132+
params = dict()
133+
if request.GET['horizontal'] is True:
134+
columns = 5
135+
if request.GET['numcontours'] > 20:
136+
columns = request.GET['numcontours'] / 10
137+
params = dict(labelspacing=0, mode="expand", ncol=columns)
138+
139+
cb = legend(proxy, levs_labels, loc=10, borderaxespad=0., frameon=False, **params)
140+
141+
if request.GET['showlabel'] is True:
142+
cb.set_title(request.GET['units'])
143+
144+
# Return HttpResponse
145+
return figure_response(fig, request, bbox_extra_artists=(cb,))
146+
147+
148+
def vector(request):
149+
raise NotImplementedError
150+
151+
152+
def barb(request):
153+
raise NotImplementedError
154+
155+
156+
def gradiant(request):
157+
# Create figure
158+
fig, ax, norm = create_axis(request, get_position(request))
159+
160+
orientation = 'vertical'
161+
if request.GET['horizontal'] is True:
162+
orientation = 'horizontal'
163+
cb = matplotlib.colorbar.ColorbarBase(ax, cmap=get_cmap(request.GET['colormap']), norm=norm, orientation=orientation)
164+
165+
if request.GET['showvalues'] is False:
166+
cb.set_ticks([])
167+
else:
168+
csr = request.GET['colorscalerange']
169+
ticks = np.linspace(csr.min, csr.max, 5)
170+
cb.set_ticks(ticks)
171+
cb.set_ticklabels([ "%.1f" % x for x in ticks ])
172+
173+
if request.GET['showlabel'] is True:
174+
cb.set_label(request.GET['units'])
175+
176+
# Return HttpResponse
177+
return figure_response(fig, request)
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import unicode_literals
3+
4+
from django.db import models, migrations
5+
6+
from pyaxiom.netcdf import EnhancedDataset, EnhancedMFDataset
7+
8+
9+
def forward(apps, schema_editor):
10+
Layer = apps.get_model('wms', 'Layer')
11+
Dataset = apps.get_model('wms', 'Dataset')
12+
for d in Dataset.objects.all():
13+
try:
14+
nc = EnhancedDataset(d.uri)
15+
except:
16+
try:
17+
nc = EnhancedMFDataset(d.uri, aggdim='time')
18+
except:
19+
pass
20+
21+
for v in nc.variables:
22+
nc_var = nc.variables[v]
23+
l, _ = Layer.objects.get_or_create(dataset_id=d.id, var_name=v)
24+
if hasattr(nc_var, 'units'):
25+
l.units = nc_var.units
26+
l.save()
27+
28+
29+
def reverse(apps, schema_editor):
30+
pass
31+
32+
33+
class Migration(migrations.Migration):
34+
35+
dependencies = [
36+
('wms', '0027_auto_20150602_1603'),
37+
]
38+
39+
operations = [
40+
migrations.AddField(
41+
model_name='layer',
42+
name='units',
43+
field=models.CharField(help_text=b"The 'units' from the dataset variable", max_length=200, blank=True),
44+
preserve_default=True,
45+
),
46+
migrations.AddField(
47+
model_name='virtuallayer',
48+
name='units',
49+
field=models.CharField(help_text=b"The 'units' from the dataset variable", max_length=200, blank=True),
50+
preserve_default=True,
51+
),
52+
migrations.RunPython(forward, reverse_code=reverse),
53+
]

wms/models/datasets/base.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from wms.utils import DotDict, find_appropriate_time
2323
from wms.data_handler import blank_canvas
24+
from wms import glg_handler
2425

2526
from wms import logger
2627

@@ -87,7 +88,18 @@ def getmap(self, layer, request):
8788
raise NotImplementedError
8889

8990
def getlegendgraphic(self, layer, request):
90-
raise NotImplementedError
91+
try:
92+
if 'filledcontours' in request.GET['image_type']:
93+
return glg_handler.filledcontour(request)
94+
elif 'contours' in request.GET['image_type']:
95+
return glg_handler.contour(request)
96+
elif 'vector' in request.GET['image_type']:
97+
return glg_handler.vector(request)
98+
else:
99+
return glg_handler.gradiant(request)
100+
except BaseException:
101+
logger.exception("Could not process GetLegendGraphic request")
102+
raise
91103

92104
def setup_getfeatureinfo(self, ncd, variable_object, request, location=None):
93105

@@ -253,6 +265,9 @@ def process_layers(self):
253265
if hasattr(nc_var, 'long_name'):
254266
l.description = nc_var.long_name
255267

268+
if hasattr(nc_var, 'units'):
269+
l.units = nc_var.units
270+
256271
# Set some standard styles
257272
l.styles = Style.defaults()
258273
l.save()

wms/models/datasets/sgrid.py

-3
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,6 @@ def getmap(self, layer, request):
239239
finally:
240240
nc.close()
241241

242-
def getlegendgraphic(self, layer, request):
243-
return views.getLegendGraphic(request, self)
244-
245242
def getfeatureinfo(self, layer, request):
246243
try:
247244
nc = self.netcdf4_dataset()

wms/models/datasets/ugrid.py

-3
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,6 @@ def getmap(self, layer, request):
226226
finally:
227227
nc.close()
228228

229-
def getlegendgraphic(self, layer, request):
230-
raise NotImplementedError("GetLegendGraphic is not implemented for UGRID datasets")
231-
232229
def getfeatureinfo(self, layer, request):
233230
try:
234231
nc = self.netcdf4_dataset()

wms/models/layer.py

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class LayerBase(models.Model):
1212
var_name = models.CharField(max_length=200, help_text="Variable name from dataset")
1313
std_name = models.CharField(max_length=200, blank=True, help_text="The 'standard_name' from the dataset variable")
14+
units = models.CharField(max_length=200, blank=True, help_text="The 'units' from the dataset variable")
1415
description = models.CharField(max_length=200, blank=True, help_text="Descriptive name of this layer, optional")
1516
dataset = models.ForeignKey('Dataset')
1617
active = models.BooleanField(default=False)

0 commit comments

Comments
 (0)