2
* Copyright (C) 2007-2008 Camptocamp
4
* This file is part of MapFish Client
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.
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.
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/>.
21
* In this file people will find :
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
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.
42
mapfish.GeoStat = OpenLayers.Class({
46
* {<OpenLayers.Layer.Vector>} The vector layer containing the features that
47
* are styled based on statistical values. If none is provided, one will
54
* {<OpenLayers.Format>} The OpenLayers format used to get features from
55
* the HTTP request response. GeoJSON is used if none is provided.
61
* {String} The URL to the web service. If none is provided, the features
62
* found in the provided vector layer will be used.
67
* APIProperty: requestSuccess
68
* {Function} Function called upon success with the HTTP request.
70
requestSuccess: function(request) {},
73
* APIProperty: requestFailure
74
* {Function} Function called upon failure with the HTTP request.
76
requestFailure: function(request) {},
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.
84
featureSelection: true,
87
* APIProperty: nameAttribute
88
* {String} The feature attribute that will be used as the popup title.
89
* Only applies if featureSelection is true.
94
* APIProperty: indicator
95
* {String} Defines the attribute to apply classification on
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.
107
defaultSymbolizer: {},
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.
117
selectSymbolizer: {'strokeColor': '#000000'}, // neutral stroke color
120
* Property: legendDiv
121
* {Object} Reference to the DOM container for the legend to be
127
* Constructor: mapfish.GeoStat
130
* map - {<OpenLayers.Map>} OpenLayers map object
131
* options - {Object} Hashtable of extra options
133
initialize: function(map, options) {
135
this.addOptions(options);
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']
145
'select': new OpenLayers.Style(this.selectSymbolizer)
147
var layer = new OpenLayers.Layer.Vector('geostat', {
148
'displayInLayerSwitcher': false,
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,
165
var selectFeature = new OpenLayers.Control.SelectFeature(
169
map.addControl(selectFeature);
170
selectFeature.activate();
174
this.setUrl(this.url);
175
this.legendDiv = Ext.get(options.legendDiv);
178
setUrl: function(url) {
180
// get features from web service if a url is specified
184
this.url, '', this, this.onSuccess, this.onFailure);
194
onSuccess: function(request) {
196
// alert("onSuccess i GeoStat.js");
197
var doc = request.responseXML;
198
if (!doc || !doc.documentElement) {
199
doc = request.responseText;
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);
213
onFailure: function(request) {
214
this.requestFailure(request);
221
* newOptions - {Object}
223
addOptions: function(newOptions) {
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);
236
* Method: extendStyle
237
* Extent layer style for the default render intent and
238
* for the select render intent if featureSelection is
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
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
255
style.setDefaultStyle(
256
OpenLayers.Util.applyDefaults(
263
if (!style.context) {
266
OpenLayers.Util.extend(style.context, context);
271
* APIMethod: applyClassification
272
* To be overriden by subclasses.
277
applyClassification: function(options) {
278
// alert("applyClassification");
279
this.layer.renderer.clear();
282
this.layer.setVisibility(true);
286
* Method: showDetails
291
showDetails: function(obj) {
292
var feature = obj.feature;
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);
311
* Method: hideDetails
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]);
324
CLASS_NAME: "mapfish.GeoStat"
331
mapfish.GeoStat.Distribution = OpenLayers.Class({
334
* Property: labelGenerator
335
* Generator for bin labels
337
labelGenerator: function(bin, binIndex, nbBins) {
338
return this.defaultLabelGenerator(bin, binIndex, nbBins)
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;
358
* Method: labelGenerator
359
* Generator for bin labels
362
* bin - {<mapfish.GeoStat.Bin>} Lower bound limit value
363
* binIndex - {Integer} Current bin index
364
* nBins - {Integer} Total number of bins
366
defaultLabelGenerator: function(bin, binIndex, nbBins) {
367
return bin.lowerBound.toFixed(3) + ' - ' + bin.upperBound.toFixed(3) + ' (' + bin.nbVal + ')'
370
classifyWithBounds: function(bounds) {
373
var sortedValues = [];
374
for (var i = 0; i < this.values.length; i++) {
375
sortedValues.push(this.values[i]);
377
sortedValues.sort(function(a,b) {return a-b;});
378
var nbBins = bounds.length - 1;
380
for (var i = 0; i < nbBins; i++) {
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();
393
binCount[nbBins - 1] = this.nbVal - mapfish.Util.sum(binCount);
395
for (var i = 0; i < nbBins; i++) {
396
bins[i] = new mapfish.GeoStat.Bin(binCount[i], bounds[i], bounds[i + 1],
398
var labelGenerator = this.labelGenerator || this.defaultLabelGenerator;
399
bins[i].label = labelGenerator(bins[i], i, nbBins);
401
return new mapfish.GeoStat.Classification(bins);
404
classifyByEqIntervals: function(nbBins) {
407
for(var i = 0; i <= nbBins; i++) {
408
bounds[i] = this.minVal +
409
i*(this.maxVal - this.minVal) / nbBins;
412
return this.classifyWithBounds(bounds);
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);
421
var binLastValPos = (binSize == 0) ? 0 : binSize;
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;
429
bounds.push(values[values.length - 1]);
431
return this.classifyWithBounds(bounds);
436
* {Number} Maximal number of classes according to the Sturge's rule
438
sturgesRule: function() {
439
return Math.floor(1 + 3.3 * Math.log(this.nbVal, 10));
444
* This function calls the appropriate classifyBy... function.
445
* The name of classification methods are defined by class constants
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
453
* {<mapfish.GeoStat.Classification>} Classification
455
classify: function(method, nbBins, bounds) {
456
var classification = null;
458
nbBins = this.sturgesRule();
461
case mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS:
462
classification = this.classifyWithBounds(bounds);
464
case mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS :
465
classification = this.classifyByEqIntervals(nbBins);
467
case mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS :
468
classification = this.classifyByQuantils(nbBins);
471
OpenLayers.Console.error("unsupported or invalid classification method");
473
return classification;
476
CLASS_NAME: "mapfish.GeoStat.Distribution"
480
* Constant: mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS
482
mapfish.GeoStat.Distribution.CLASSIFY_WITH_BOUNDS = 0;
485
* Constant: mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS
487
mapfish.GeoStat.Distribution.CLASSIFY_BY_EQUAL_INTERVALS = 1;
490
* Constant: mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS
492
mapfish.GeoStat.Distribution.CLASSIFY_BY_QUANTILS = 2;
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.
499
mapfish.GeoStat.Bin = OpenLayers.Class({
506
initialize: function(nbVal, lowerBound, upperBound, isLast) {
508
this.lowerBound = lowerBound;
509
this.upperBound = upperBound;
510
this.isLast = isLast;
513
CLASS_NAME: "mapfish.GeoStat.Bin"
517
* Classification summarizes a Distribution by regrouping data within several Bins.
519
mapfish.GeoStat.Classification = OpenLayers.Class({
522
initialize: function(bins) {
526
getBoundsArray: function() {
528
for (var i = 0; i < this.bins.length; i++) {
529
bounds.push(this.bins[i].lowerBound);
531
if (this.bins.length > 0) {
532
bounds.push(this.bins[this.bins.length - 1].upperBound);
537
CLASS_NAME: "mapfish.GeoStat.Classification"