~mortenoh/+junk/dhis2-detailed-import-export

« back to all changes in this revision

Viewing changes to gis/dhis-gis-geostat/mfbase/mapfish/core/GeoStat.js

  • Committer: larshelge at gmail
  • Date: 2009-03-03 16:46:36 UTC
  • Revision ID: larshelge@gmail.com-20090303164636-2sjlrquo7ib1gf7r
Initial check-in

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2007-2008  Camptocamp
 
3
 *
 
4
 * This file is part of MapFish Client
 
5
 *
 
6
 * MapFish Client is free software: you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation, either version 3 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * MapFish Client is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with MapFish Client.  If not, see <http://www.gnu.org/licenses/>.
 
18
 */
 
19
 
 
20
/**
 
21
 * In this file people will find :
 
22
 *   - GeoStat
 
23
 *   - Distribution
 
24
 *   - Classification
 
25
 *
 
26
 */
 
27
 
 
28
/**
 
29
 * @requires OpenLayers/Layer/Vector.js
 
30
 * @requires OpenLayers/Popup/AnchoredBubble.js
 
31
 * @requires OpenLayers/Feature/Vector.js
 
32
 * @requires OpenLayers/Format/GeoJSON.js
 
33
 * @requires OpenLayers/Control/SelectFeature.js
 
34
 * @requires OpenLayers/Ajax.js
 
35
 */
 
36
 
 
37
/**
 
38
 * Class: mapfish.GeoStat
 
39
 * Base class for geo-statistics. This class is not meant to be used directly, it serves
 
40
 * as the base for specific geo-statistics implementations.
 
41
 */
 
42
mapfish.GeoStat = OpenLayers.Class({
 
43
 
 
44
    /**
 
45
     * APIProperty: layer
 
46
     * {<OpenLayers.Layer.Vector>} The vector layer containing the features that
 
47
     *      are styled based on statistical values. If none is provided, one will
 
48
     *      be created.
 
49
     */
 
50
    layer: null,
 
51
 
 
52
    /**
 
53
     * APIProperty: format
 
54
     * {<OpenLayers.Format>} The OpenLayers format used to get features from
 
55
     *      the HTTP request response. GeoJSON is used if none is provided.
 
56
     */
 
57
    format: null,
 
58
 
 
59
    /**
 
60
     * APIProperty: url
 
61
     * {String} The URL to the web service. If none is provided, the features
 
62
     *      found in the provided vector layer will be used.
 
63
     */
 
64
    url: null,
 
65
 
 
66
    /**
 
67
     * APIProperty: requestSuccess
 
68
     * {Function} Function called upon success with the HTTP request.
 
69
     */
 
70
    requestSuccess: function(request) {},
 
71
 
 
72
    /**
 
73
     * APIProperty: requestFailure
 
74
     * {Function} Function called upon failure with the HTTP request.
 
75
     */
 
76
    requestFailure: function(request) {},
 
77
 
 
78
    /**
 
79
     * APIProperty: featureSelection
 
80
     * {Boolean} A boolean value specifying whether feature selection must
 
81
     *      be put in place. If true a popup will be displayed when the
 
82
     *      mouse goes over a feature.
 
83
     */
 
84
    featureSelection: true,
 
85
 
 
86
    /**
 
87
     * APIProperty: nameAttribute
 
88
     * {String} The feature attribute that will be used as the popup title.
 
89
     *      Only applies if featureSelection is true.
 
90
     */
 
91
    nameAttribute: null,
 
92
 
 
93
    /**
 
94
     * APIProperty: indicator
 
95
     * {String} Defines the attribute to apply classification on
 
96
     */
 
97
    indicator: null,
 
98
 
 
99
    /**
 
100
     * Property: defaultSymbolizer
 
101
     * {Object} This symbolizer is used in the constructor to define
 
102
     *      the default style in the style object associated with the
 
103
     *      "default" render intent. This symbolizer is extended with
 
104
     *      OpenLayers.Feature.Vector.style['default']. It can be
 
105
     *      overridden in subclasses.
 
106
     */
 
107
    defaultSymbolizer: {},
 
108
 
 
109
    /**
 
110
     * Property: selectSymbolizer
 
111
     * {Object} This symbolizer is used in the constructor to define
 
112
     *      the select style in the style object associated with the
 
113
     *      "select" render intent. When rendering selected features
 
114
     *      it is extended with the default symbolizer. It can be
 
115
     *      overridden in subclasses.
 
116
     */
 
117
    selectSymbolizer: {'strokeColor': '#000000'}, // neutral stroke color
 
118
 
 
119
    /**
 
120
     * Property: legendDiv
 
121
     * {Object} Reference to the DOM container for the legend to be
 
122
     *     generated.
 
123
     */
 
124
    legendDiv: null,
 
125
 
 
126
    /**
 
127
     * Constructor: mapfish.GeoStat
 
128
     *
 
129
     * Parameters:
 
130
     * map - {<OpenLayers.Map>} OpenLayers map object
 
131
     * options - {Object} Hashtable of extra options
 
132
     */
 
133
    initialize: function(map, options) {
 
134
        this.map = map;
 
135
        this.addOptions(options);
 
136
        if (!this.layer) {
 
137
            // no layer specified, create one
 
138
            var styleMap = new OpenLayers.StyleMap({
 
139
                'default': new OpenLayers.Style(
 
140
                    OpenLayers.Util.applyDefaults(
 
141
                        this.defaultSymbolizer,
 
142
                        OpenLayers.Feature.Vector.style['default']
 
143
                    )
 
144
                ),
 
145
                'select': new OpenLayers.Style(this.selectSymbolizer)
 
146
            });
 
147
            var layer = new OpenLayers.Layer.Vector('geostat', {
 
148
                'displayInLayerSwitcher': false,
 
149
                'visibility': false,
 
150
                'styleMap': styleMap
 
151
            });
 
152
            map.addLayer(layer);
 
153
            this.layer = layer;
 
154
        }
 
155
 
 
156
/*
 
157
        if (this.featureSelection) {
 
158
            // create select feature control so that popups can
 
159
            // be displayed on feature selection
 
160
            this.layer.events.on({
 
161
                'featureselected': this.showDetails,
 
162
                'featureunselected': this.hideDetails,
 
163
                scope: this
 
164
            });
 
165
            var selectFeature = new OpenLayers.Control.SelectFeature(
 
166
                this.layer,
 
167
                {'hover': true}
 
168
            );
 
169
            map.addControl(selectFeature);
 
170
            selectFeature.activate();
 
171
        }
 
172
*/        
 
173
        
 
174
        this.setUrl(this.url);
 
175
        this.legendDiv = Ext.get(options.legendDiv);
 
176
    },
 
177
 
 
178
    setUrl: function(url) {
 
179
    
 
180
        // get features from web service if a url is specified
 
181
        this.url = url;
 
182
        if (this.url) {
 
183
            OpenLayers.loadURL(
 
184
                this.url, '', this, this.onSuccess, this.onFailure);
 
185
        }
 
186
    },
 
187
 
 
188
    /**
 
189
     * Method: onSuccess
 
190
     *
 
191
     * Parameters:
 
192
     * request - {Object}
 
193
     */
 
194
    onSuccess: function(request) {
 
195
    
 
196
// alert("onSuccess i GeoStat.js");    
 
197
        var doc = request.responseXML;
 
198
        if (!doc || !doc.documentElement) {
 
199
            doc = request.responseText;
 
200
        }
 
201
        var format = this.format || new OpenLayers.Format.GeoJSON()
 
202
        this.layer.removeFeatures(this.layer.features);
 
203
        this.layer.addFeatures(format.read(doc));
 
204
        this.requestSuccess(request);
 
205
    },
 
206
 
 
207
    /**
 
208
     * Method: onFailure
 
209
     *
 
210
     * Parameters:
 
211
     * request - {Object}
 
212
     */
 
213
    onFailure: function(request) {
 
214
        this.requestFailure(request);
 
215
    },
 
216
 
 
217
    /**
 
218
     * Method: addOptions
 
219
     *
 
220
     * Parameters:
 
221
     * newOptions - {Object}
 
222
     */
 
223
    addOptions: function(newOptions) {
 
224
        if (newOptions) {
 
225
            if (!this.options) {
 
226
                this.options = {};
 
227
            }
 
228
            // update our copy for clone
 
229
            OpenLayers.Util.extend(this.options, newOptions);
 
230
            // add new options to this
 
231
            OpenLayers.Util.extend(this, newOptions);
 
232
        }
 
233
    },
 
234
 
 
235
    /**
 
236
     * Method: extendStyle
 
237
     *      Extent layer style for the default render intent and
 
238
     *      for the select render intent if featureSelection is
 
239
     *      set.
 
240
     *
 
241
     * Parameters:
 
242
     * renderIntent - {String} The render intent
 
243
     * rules - {Array({<OpenLayers.Rule>})} Array of new rules to add
 
244
     * symbolizer - {Object} Object with new styling options
 
245
     * context - {Object} Object representing the new context
 
246
     */
 
247
    extendStyle: function(rules, symbolizer, context) {
 
248
        var style = this.layer.styleMap.styles['default'];
 
249
        // replace rules entirely - the geostat object takes control
 
250
        // on the style rules of the "default" render intent
 
251
        if (rules) {
 
252
            style.rules = rules;
 
253
        }
 
254
        if (symbolizer) {
 
255
            style.setDefaultStyle(
 
256
                OpenLayers.Util.applyDefaults(
 
257
                    symbolizer,
 
258
                    style.defaultStyle
 
259
                )
 
260
            );
 
261
        }
 
262
        if (context) {
 
263
            if (!style.context) {
 
264
                style.context = {};
 
265
            }
 
266
            OpenLayers.Util.extend(style.context, context);
 
267
        }
 
268
    },
 
269
 
 
270
    /**
 
271
     * APIMethod: applyClassification
 
272
     *      To be overriden by subclasses.
 
273
     *
 
274
     * Parameters:
 
275
     * options - {Object}
 
276
     */
 
277
    applyClassification: function(options) {
 
278
//    alert("applyClassification");
 
279
        this.layer.renderer.clear();
 
280
        this.layer.redraw();
 
281
        this.updateLegend();
 
282
        this.layer.setVisibility(true);
 
283
    },
 
284
 
 
285
    /**
 
286
     * Method: showDetails
 
287
     *
 
288
     * Parameters:
 
289
     * obj - {Object}
 
290
     */
 
291
    showDetails: function(obj) {
 
292
        var feature = obj.feature;
 
293
        // popup html
 
294
        var html = typeof this.nameAttribute == 'string' ?
 
295
            '<h4 style="margin-top:5px">'
 
296
                + feature.attributes[this.nameAttribute] +'</h4>' : '';
 
297
        html += this.indicator + ": " + feature.attributes[this.indicator];
 
298
        // create popup located in the bottom right of the map
 
299
        var bounds = this.layer.map.getExtent();
 
300
        var lonlat = new OpenLayers.LonLat(bounds.right, bounds.bottom);
 
301
        var size = new OpenLayers.Size(200, 100);
 
302
        var popup = new OpenLayers.Popup.AnchoredBubble(
 
303
            feature.attributes[this.nameAttribute],
 
304
            lonlat, size, html, 0.5, false);
 
305
        var symbolizer = feature.layer.styleMap.createSymbolizer(feature, 'default');
 
306
        popup.setBackgroundColor(symbolizer.fillColor);
 
307
        this.layer.map.addPopup(popup);
 
308
    },
 
309
 
 
310
    /**
 
311
     * Method: hideDetails
 
312
     *
 
313
     * Parameters:
 
314
     * obj - {Object}
 
315
     */
 
316
    hideDetails: function(obj) {
 
317
        //remove all other popups from screen
 
318
        var map= this.layer.map;
 
319
        for (var i = map.popups.length - 1; i >= 0; --i) {
 
320
            map.removePopup(map.popups[i]);
 
321
        }
 
322
    },
 
323
 
 
324
    CLASS_NAME: "mapfish.GeoStat"
 
325
 
 
326
});
 
327
 
 
328
/**
 
329
 * Distribution Class
 
330
 */
 
331
mapfish.GeoStat.Distribution = OpenLayers.Class({
 
332
 
 
333
    /**
 
334
     * Property: labelGenerator
 
335
     *     Generator for bin labels
 
336
     */
 
337
    labelGenerator: function(bin, binIndex, nbBins) {
 
338
        return this.defaultLabelGenerator(bin, binIndex, nbBins)
 
339
    },
 
340
 
 
341
    values: null,
 
342
 
 
343
    nbVal: null,
 
344
 
 
345
    minVal: null,
 
346
 
 
347
    maxVal: null,
 
348
 
 
349
    initialize: function(values, options) {
 
350
        OpenLayers.Util.extend(this, options);
 
351
        this.values = values;
 
352
        this.nbVal = values.length;
 
353
        this.minVal = this.nbVal ? mapfish.Util.min(this.values) : 0;
 
354
        this.maxVal = this.nbVal ? mapfish.Util.max(this.values) : 0;
 
355
    },
 
356
 
 
357
    /**
 
358
     * Method: labelGenerator
 
359
     *    Generator for bin labels
 
360
     *
 
361
     * Parameters:
 
362
     *   bin - {<mapfish.GeoStat.Bin>} Lower bound limit value
 
363
     *   binIndex - {Integer} Current bin index
 
364
     *   nBins - {Integer} Total number of bins
 
365
     */
 
366
    defaultLabelGenerator: function(bin, binIndex, nbBins) {
 
367
        return bin.lowerBound.toFixed(3) + ' - ' + bin.upperBound.toFixed(3) + ' (' + bin.nbVal + ')'
 
368
    },
 
369
 
 
370
    classifyWithBounds: function(bounds) {
 
371
        var bins = [];
 
372
        var binCount = [];
 
373
        var sortedValues = [];
 
374
        for (var i = 0; i < this.values.length; i++) {
 
375
            sortedValues.push(this.values[i]);
 
376
        }
 
377
        sortedValues.sort(function(a,b) {return a-b;});
 
378
        var nbBins = bounds.length - 1;
 
379
 
 
380
        for (var i = 0; i < nbBins; i++) {
 
381
            binCount[i] = 0;
 
382
        }
 
383
 
 
384
        for (var i = 0; i < nbBins - 1; i) {
 
385
            if (sortedValues[0] < bounds[i + 1]) {
 
386
                binCount[i] = binCount[i] + 1;
 
387
                sortedValues.shift();
 
388
            } else {
 
389
                i++;
 
390
            }
 
391
        }
 
392
 
 
393
        binCount[nbBins - 1] = this.nbVal - mapfish.Util.sum(binCount);
 
394
 
 
395
        for (var i = 0; i < nbBins; i++) {
 
396
            bins[i] = new mapfish.GeoStat.Bin(binCount[i], bounds[i], bounds[i + 1],
 
397
                i == (nbBins - 1));
 
398
            var labelGenerator = this.labelGenerator || this.defaultLabelGenerator;
 
399
            bins[i].label = labelGenerator(bins[i], i, nbBins);
 
400
        }
 
401
        return new mapfish.GeoStat.Classification(bins);
 
402
    },
 
403
 
 
404
    classifyByEqIntervals: function(nbBins) {
 
405
        var bounds = [];
 
406
 
 
407
        for(var i = 0; i <= nbBins; i++) {
 
408
            bounds[i] = this.minVal +
 
409
                i*(this.maxVal - this.minVal) / nbBins;
 
410
        }
 
411
 
 
412
        return this.classifyWithBounds(bounds);
 
413
    },
 
414
 
 
415
    classifyByQuantils: function(nbBins) {
 
416
        var values = this.values;
 
417
        values.sort(function(a,b) {return a-b;});
 
418
        var binSize = Math.round(this.values.length / nbBins);
 
419
 
 
420
        var bounds = [];
 
421
        var binLastValPos = (binSize == 0) ? 0 : binSize;
 
422
 
 
423
        if (values.length > 0) {
 
424
            bounds[0] = values[0];
 
425
            for (i = 1; i < nbBins; i++) {
 
426
                bounds[i] = values[binLastValPos];
 
427
                binLastValPos += binSize;
 
428
            }
 
429
            bounds.push(values[values.length - 1]);
 
430
        }
 
431
        return this.classifyWithBounds(bounds);
 
432
    },
 
433
 
 
434
    /**
 
435
     * Returns:
 
436
     * {Number} Maximal number of classes according to the Sturge's rule
 
437
     */
 
438
    sturgesRule: function() {
 
439
        return Math.floor(1 + 3.3 * Math.log(this.nbVal, 10));
 
440
    },
 
441
 
 
442
    /**
 
443
     * Method: classify
 
444
     *    This function calls the appropriate classifyBy... function.
 
445
     *    The name of classification methods are defined by class constants
 
446
     *
 
447
     * Parameters:
 
448
     * method - {Integer} Method name constant as defined in this class
 
449
     * nbBins - {Integer} Number of classes
 
450
     * bounds - {Array(Integer)} Array of bounds to be used for by bounds method
 
451
     *
 
452
     * Returns:
 
453
     * {<mapfish.GeoStat.Classification>} Classification
 
454
     */
 
455
    classify: function(method, nbBins, bounds) {
 
456
        var classification = null;
 
457
        if (!nbBins) {
 
458
            nbBins = this.sturgesRule();
 
459
        }
 
460
        switch (method) {
 
461
        case mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS:
 
462
            classification = this.classifyWithBounds(bounds);
 
463
            break;
 
464
        case mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS :
 
465
            classification = this.classifyByEqIntervals(nbBins);
 
466
            break;
 
467
        case mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS :
 
468
            classification = this.classifyByQuantils(nbBins);
 
469
            break;
 
470
        default:
 
471
            OpenLayers.Console.error("unsupported or invalid classification method");
 
472
        }
 
473
        return classification;
 
474
    },
 
475
 
 
476
    CLASS_NAME: "mapfish.GeoStat.Distribution"
 
477
});
 
478
 
 
479
/**
 
480
 * Constant: mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS
 
481
 */
 
482
mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS = 0;
 
483
 
 
484
/**
 
485
 * Constant: mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS
 
486
 */
 
487
mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS = 1;
 
488
 
 
489
/**
 
490
 * Constant: mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS
 
491
 */
 
492
mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS = 2;
 
493
 
 
494
/**
 
495
 * Bin is category of the Classification.
 
496
 * When they are defined, lowerBound is within the class
 
497
 * and upperBound is outside de the class.
 
498
 */
 
499
mapfish.GeoStat.Bin = OpenLayers.Class({
 
500
    label: null,
 
501
    nbVal: null,
 
502
    lowerBound: null,
 
503
    upperBound: null,
 
504
    isLast: false,
 
505
 
 
506
    initialize: function(nbVal, lowerBound, upperBound, isLast) {
 
507
        this.nbVal = nbVal;
 
508
        this.lowerBound = lowerBound;
 
509
        this.upperBound = upperBound;
 
510
        this.isLast = isLast;
 
511
    },
 
512
 
 
513
    CLASS_NAME: "mapfish.GeoStat.Bin"
 
514
});
 
515
 
 
516
/**
 
517
 * Classification summarizes a Distribution by regrouping data within several Bins.
 
518
 */
 
519
mapfish.GeoStat.Classification = OpenLayers.Class({
 
520
    bins: [],
 
521
 
 
522
    initialize: function(bins) {
 
523
        this.bins = bins;
 
524
    },
 
525
 
 
526
    getBoundsArray: function() {
 
527
        var bounds = [];
 
528
        for (var i = 0; i < this.bins.length; i++) {
 
529
            bounds.push(this.bins[i].lowerBound);
 
530
        }
 
531
        if (this.bins.length > 0) {
 
532
            bounds.push(this.bins[this.bins.length - 1].upperBound);
 
533
        }
 
534
        return bounds;
 
535
    },
 
536
 
 
537
    CLASS_NAME: "mapfish.GeoStat.Classification"
 
538
});