[OpenLayers-Commits] r6014 - sandbox/enjahova/openlayers/lib/OpenLayers/Control

commits at openlayers.org commits at openlayers.org
Thu Feb 7 11:42:22 EST 2008


Author: enjahova
Date: 2008-02-07 11:42:22 -0500 (Thu, 07 Feb 2008)
New Revision: 6014

Added:
   sandbox/enjahova/openlayers/lib/OpenLayers/Control/ScaleBar.js
Log:
Addthe scalebar .js file

Added: sandbox/enjahova/openlayers/lib/OpenLayers/Control/ScaleBar.js
===================================================================
--- sandbox/enjahova/openlayers/lib/OpenLayers/Control/ScaleBar.js	                        (rev 0)
+++ sandbox/enjahova/openlayers/lib/OpenLayers/Control/ScaleBar.js	2008-02-07 16:42:22 UTC (rev 6014)
@@ -0,0 +1,647 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ScaleBar
+ * A scale bar styled with CSS.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.ScaleBar = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * Property: element
+     * {Element}
+     */
+    element: null,
+    
+    /**
+     * Property: scale
+     * {Float} Scale denominator (1 / X) - set on update
+     */    
+    scale: 1,
+
+    /**
+     * APIProperty: displaySystem
+     * {String} Display system for scale bar - metric or english supported.
+     *     Default is metric.
+     */
+    displaySystem: 'metric',
+
+    /**
+     * APIProperty: minWidth
+     * {Integer} Minimum width of the scale bar in pixels.  Default is 100 px.
+     */
+    minWidth: 100,
+
+    /**
+     * APIProperty: maxWidth
+     * Maximum width of the scale bar in pixels.  Default is 200 px.
+     */
+    maxWidth: 200,
+
+    /**
+     * APIProperty: divisions
+     * {Integer} Number of major divisions for the scale bar.  Default is 2.
+     */
+    divisions: 2,
+
+    /**
+     * APIProperty: subdivisions
+     * {Integer} Number of subdivisions per major division.  Default is 2.
+     */
+    subdivisions: 2,
+
+    /**
+     * APIProperty: showMinorMeasures
+     * {Boolean} Show measures for subdivisions.  Default is false.
+     */
+    showMinorMeasures: false,
+
+    /**
+     * APIProperty: abbreviateLabel
+     * {Boolean} Show abbreviated measurement unit (ft, km).  Default is false.
+     */
+    abbreviateLabel: false,
+
+    /**
+     * APIProperty: singleLine
+     * {Boolean} Display scale bar length and unit after scale bar.  Default
+     *     is false.
+     */
+    singleLine: false,
+
+    /**
+     * APIProperty: align
+     * {String} Determines how scale bar will be aligned within the element -
+     * left, center, or right supported
+     */
+    align: 'left',
+    
+    /**
+     * APIProperty: div
+     * {Element} Optional DOM element to become the container for the scale
+     *     bar.  If not provided, one will be created.
+     */
+    div: null,
+    
+    /**
+     * Property: scaleText
+     * Text to prefix the scale denominator used as a title for the scale bar
+     *     element.  Default is "scale 1:".
+     */
+    scaleText: "scale 1:",
+    
+    /**
+     * Property: thousandsSeparator
+     * Thousands separator for formatted scale bar measures.  The title
+     *     attribute for the scale bar always uses
+     *     <OpenLayers.Number.thousandsSeparator> for number formatting.  To
+     *     conserve space on measures displayed with markers, the default
+     *     thousands separator for formatting is "" (no separator).
+     */
+    thousandsSeparator: "",
+
+    /**
+     * Property: measurementProperties
+     * {Object} Holds display units, abbreviations, and conversion to inches
+     * (since we're using dpi) per measurement sytem.
+     */
+    measurementProperties: {
+        english: {
+            units: ['miles', 'feet', 'inches'],
+            abbr: ['mi', 'ft', 'in'],
+            inches: [63360, 12, 1]
+        },
+        metric: {
+            units: ['kilometers', 'meters', 'centimeters'],
+            abbr: ['km', 'm', 'cm'],
+            inches: [39370.07874, 39.370079, 0.393701]
+        }
+    },
+
+    /**
+     * Property: limitedStyle
+     * {Boolean} For browsers with limited CSS support, limitedStyle will be
+     *     set to true.  In addition, this property can be set to true in the
+     *     options sent to the constructor.  If true scale bar element offsets
+     *     will be determined based on the <defaultStyles> object.
+     */
+    limitedStyle: false,
+    
+    /**
+     * Property: customStyle
+     * {Object} For cases where <limitedStyle> is true, a customStyle property
+     *     can be set on the options sent to the constructor.  The
+     *     <defaultStyles> object will be extended with this custom style
+     *     object.
+     */
+    customStyles: null,
+
+    /**
+     * Property: defaultStyles
+     * {Object} For cases where <limitedStyle> is true, default scale bar
+     *     element offsets are taken from this object.  Values correspond to
+     *     pixel dimensions given in the stylesheet.
+     */
+    defaultStyles: {
+        Bar: {
+            height: 11, top: 12,
+            borderLeftWidth: 0,
+            borderRightWidth: 0
+        },
+        BarAlt: {
+            height: 11, top: 12,
+            borderLeftWidth: 0,
+            borderRightWidth: 0
+        },
+        MarkerMajor: {
+            height: 13, width: 13, top: 12,
+            borderLeftWidth: 0,
+            borderRightWidth: 0
+        },
+        MarkerMinor: {
+            height: 13, width: 13, top: 12,
+            borderLeftWidth: 0,
+            borderRightWidth: 0
+        },
+        NumbersBox: {
+            height: 13, width: 40, top: 24
+        },
+        LabelBox: {
+            height: 15, top: -2
+        },
+        LabelBoxSingleLine: {
+            height: 15, width: 35, top: 5, left: 10
+        }
+    },
+    
+    /**
+     * Property: appliedStyles
+     * For cases where <limitedStyle> is true, scale bar element offsets will
+     *     be determined based on <defaultStyles> extended with any
+     *     <customStyles>.
+     */
+    appliedStyles: null,
+
+    /**
+     * Constructor: OpenLayers.Control.ScaleBar
+     * Create a new scale bar instance.
+     *
+     * Parameters: 
+     * options - {Object} Optional object whose properties will be set on this
+     *     object.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        if(!document.styleSheets) {
+            this.limitedStyle = true;
+        }
+        if(this.limitedStyle) {
+            this.appliedStyles = OpenLayers.Util.extend({}, this.defaultStyles);
+            OpenLayers.Util.extend(this.appliedStyles, this.customStyles);
+        }
+        // create scalebar DOM elements
+        this.element = document.createElement('div');
+        this.element.style.position = 'relative';
+        this.element.className = this.displayClass + 'Wrapper';
+        this.labelContainer = document.createElement('div');
+        this.labelContainer.className = this.displayClass + 'Units';
+        this.labelContainer.style.position = 'absolute';
+        this.graphicsContainer = document.createElement('div');
+        this.graphicsContainer.style.position = 'absolute';
+        this.graphicsContainer.className = this.displayClass + 'Graphics';
+        this.numbersContainer = document.createElement('div');
+        this.numbersContainer.style.position = 'absolute';
+        this.numbersContainer.className = this.displayClass + 'Numbers';
+        this.element.appendChild(this.graphicsContainer);
+        this.element.appendChild(this.labelContainer);
+        this.element.appendChild(this.numbersContainer);
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Destroy the control.
+     */
+    destroy: function() {
+        this.map.events.unregister('moveend', this, this.onMoveend);
+        this.div.innerHTML = "";
+        OpenLayers.Control.prototype.destroy.apply(this);
+    },
+
+    /**
+     * Method: draw
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        // determine offsets for graphic elements
+        this.dxMarkerMajor = (
+            this.styleValue('MarkerMajor', 'borderLeftWidth') +
+            this.styleValue('MarkerMajor', 'width') +
+            this.styleValue('MarkerMajor', 'borderRightWidth')
+        ) / 2;
+        this.dxMarkerMinor = (
+            this.styleValue('MarkerMinor', 'borderLeftWidth') +
+            this.styleValue('MarkerMinor', 'width') +
+            this.styleValue('MarkerMinor', 'borderRightWidth')
+        ) / 2;
+        this.dxBar = (
+            this.styleValue('Bar', 'borderLeftWidth') +
+            this.styleValue('Bar', 'borderRightWidth')
+        ) / 2;
+        this.dxBarAlt = (
+            this.styleValue('BarAlt', 'borderLeftWidth') +
+            this.styleValue('BarAlt', 'borderRightWidth')
+        ) / 2;
+        this.dxNumbersBox = this.styleValue('NumbersBox', 'width') / 2;
+        // set scale bar element height
+        var classNames = ['Bar', 'BarAlt', 'MarkerMajor', 'MarkerMinor'];
+        if(this.singleLine) {
+            classNames.push('LabelBoxSingleLine');
+        } else {
+            classNames.push('NumbersBox', 'LabelBox');
+        }
+        var vertDisp = 0;
+        for(var classIndex = 0; classIndex < classNames.length; ++classIndex) {
+            var cls = classNames[classIndex];
+            vertDisp = Math.max(
+                vertDisp,
+                this.styleValue(cls, 'top') + this.styleValue(cls, 'height')
+            );
+        }
+        this.element.style.height = vertDisp + 'px';
+        this.xOffsetSingleLine = this.styleValue('LabelBoxSingleLine', 'width') +
+                                 this.styleValue('LabelBoxSingleLine', 'left');
+        
+        this.div.appendChild(this.element);
+        this.map.events.register('moveend', this, this.onMoveend);
+        this.update();
+        return this.div;
+    },
+    
+    /**
+     * Method: onMoveend
+     * Registered as a listener for "moveend".
+     */
+    onMoveend: function() {
+        this.update();
+    },
+   
+    /**
+     * APIMethod: update
+     * Update the scale bar after modifying properties.
+     *
+     * Parameters:
+     * scale - {Float} Optional scale denominator.  If not specified, the
+     *     map scale will be used.
+     */
+    update: function(scale) {
+        if(this.map.baseLayer == null || !this.map.getScale()) {
+            return;
+        }
+        this.scale = (scale != undefined) ? scale : this.map.getScale();
+        // update the element title and width
+        this.element.title = this.scaleText + OpenLayers.Number.format(this.scale);
+        this.element.style.width = this.maxWidth + 'px';
+        // check each measurement unit in the display system
+        var comp = this.getComp();
+        // get the value (subdivision length) with the lowest cumulative score
+        this.setSubProps(comp);
+        // clean out any old content from containers
+        this.labelContainer.innerHTML = "";
+        this.graphicsContainer.innerHTML = "";
+        this.numbersContainer.innerHTML = "";
+        // create all divisions
+        var numDiv = this.divisions * this.subdivisions;
+        var alignmentOffset = {
+            left: 0 + (this.singleLine ? 0 : this.dxNumbersBox),
+            center: (this.maxWidth / 2) -
+                (numDiv * this.subProps.pixels / 2) -
+                (this.singleLine ? this.xOffsetSingleLine / 2 : 0),
+            right: this.maxWidth -
+                (numDiv *this.subProps.pixels) -
+                (this.singleLine ? this.xOffsetSingleLine : this.dxNumbersBox)
+        }
+        var xPos, measure, divNum, cls, left;
+        for(var di=0; di<this.divisions; ++di) {
+            // set xPos and measure to start of division
+            xPos = di * this.subdivisions * this.subProps.pixels +
+                   alignmentOffset[this.align];
+            // add major marker
+            this.graphicsContainer.appendChild(this.createElement(
+                "MarkerMajor", " ", xPos - this.dxMarkerMajor
+            ));
+            // add major measure
+            if(!this.singleLine) {
+                measure = (di == 0) ? 0 :
+                    OpenLayers.Number.format(
+                        (di * this.subdivisions) * this.subProps.length,
+                        this.subProps.dec, this.thousandsSeparator
+                    );
+                this.numbersContainer.appendChild(this.createElement(
+                    "NumbersBox", measure, xPos - this.dxNumbersBox
+                ));
+            }
+            // create all subdivisions
+            for(var si=0; si<this.subdivisions; ++si) {
+                if((si % 2) == 0) {
+                    cls = "Bar";
+                    left = xPos - this.dxBar;
+                } else {
+                    cls = "BarAlt";
+                    left = xPos - this.dxBarAlt;
+                }
+                this.graphicsContainer.appendChild(this.createElement(
+                    cls, " ", left, this.subProps.pixels
+                ));
+                // add minor marker if not the last subdivision
+                if(si < this.subdivisions - 1) {
+                    // set xPos and measure to end of subdivision
+                    divNum = (di * this.subdivisions) + si + 1;
+                    xPos = divNum * this.subProps.pixels +
+                           alignmentOffset[this.align];
+                    this.graphicsContainer.appendChild(this.createElement(
+                        "MarkerMinor", " ", xPos - this.dxMarkerMinor
+                    ));
+                    if(this.showMinorMeasures && !this.singleLine) {
+                        // add corresponding measure
+                        measure = divNum * this.subProps.length;
+                        this.numbersContainer.appendChild(this.createElement(
+                            "NumbersBox", measure, xPos - this.dxNumbersBox
+                        ));
+                    }
+                }
+            }
+        }
+        // set xPos and measure to end of divisions
+        xPos = numDiv * this.subProps.pixels;
+        xPos += alignmentOffset[this.align];
+        // add the final major marker
+        this.graphicsContainer.appendChild(this.createElement(
+            "MarkerMajor", " ", xPos - this.dxMarkerMajor
+        ));
+        // add final measure
+        measure = OpenLayers.Number.format(
+            numDiv * this.subProps.length,
+            this.subProps.dec, this.thousandsSeparator
+        );
+        if(!this.singleLine) {
+            this.numbersContainer.appendChild(this.createElement(
+                "NumbersBox", measure, xPos - this.dxNumbersBox
+            ));
+        }
+        // add content to the label element
+        var labelBox = document.createElement('div');
+        labelBox.style.position = 'absolute';
+        var labelText;
+        if(this.singleLine) {
+            labelText = measure;
+            labelBox.className = this.displayClass + 'LabelBoxSingleLine';
+            labelBox.style.left = Math.round(
+                xPos + this.styleValue('LabelBoxSingleLine', 'left')) + 'px';
+        } else {
+            labelText = '';
+            labelBox.className = this.displayClass + 'LabelBox';
+            labelBox.style.textAlign = 'center';
+            labelBox.style.width = Math.round(numDiv * this.subProps.pixels) + 'px'
+            labelBox.style.left = Math.round(alignmentOffset[this.align]) + 'px';
+            labelBox.style.overflow = 'hidden';
+        }
+        if(this.abbreviateLabel) {
+            labelText += ' ' + this.subProps.abbr;
+        } else {
+            labelText += ' ' + this.subProps.units;
+        }
+        labelBox.appendChild(document.createTextNode(labelText));
+        this.labelContainer.appendChild(labelBox);
+    },
+    
+    /**
+     * Method: createElement
+     * Create a scale bar element.  These are absolutely positioned with
+     *     hidden overflow and left offset.
+     *
+     * Parameters:
+     * cls - {String} Class name suffix.
+     * text - {String} Text for child node.
+     * left - {Float} Left offset.
+     * width - {Float} Optional width.
+     * 
+     * Returns:
+     * {Element} A scale bar element.
+     */
+    createElement: function(cls, text, left, width) {
+        var element = document.createElement("div");
+        element.className = this.displayClass + cls;
+        OpenLayers.Util.extend(element.style, {
+            position: "absolute",
+            textAlign: "center",
+            overflow: "hidden",
+            left: Math.round(left) + "px"
+        });
+        element.appendChild(document.createTextNode(text));
+        if(width) {
+            element.style.width = Math.round(width) + "px";
+        }
+        return element;
+    },
+    
+    /**
+     * Method: getComp
+     * Get comparison matrix.
+     */
+    getComp: function() {
+        var system = this.measurementProperties[this.displaySystem];
+        var numUnits = system.units.length;
+        var comp = new Array(numUnits);
+        var numDiv = this.divisions * this.subdivisions;
+        for(var unitIndex = 0; unitIndex < numUnits; ++unitIndex) {
+            comp[unitIndex] = {};
+            var ppdu = OpenLayers.DOTS_PER_INCH *
+                system.inches[unitIndex] / this.scale;
+            var minSDDisplayLength = ((this.minWidth - this.dxNumbersBox) /
+                                       ppdu) / numDiv;
+            var maxSDDisplayLength = ((this.maxWidth - this.dxNumbersBox) /
+                                       ppdu) / numDiv;
+            // add up scores for each marker (even if numbers aren't displayed)
+            for(var vi=0; vi<numDiv; ++vi) {
+                var minNumber = minSDDisplayLength * (vi + 1);
+                var maxNumber = maxSDDisplayLength * (vi + 1);
+                var num = this.getHandsomeNumber(minNumber, maxNumber);
+                var compNum = {
+                    value: (num.value / (vi + 1)),
+                    score: 0, tie: 0, dec: 0, displayed: 0
+                };
+                // tally up scores for all values given this subdivision length
+                for(var vi2=0; vi2<numDiv; ++vi2) {
+                    var position = num.value * (vi2 + 1) / (vi + 1);
+                    var num2 = this.getHandsomeNumber(position, position);
+                    var major = ((vi2 + 1) % this.subdivisions == 0);
+                    var last = ((vi2 + 1) == numDiv);
+                    if((this.singleLine && last) ||
+                       (!this.singleLine && (major || this.showMinorMeasures))) {
+                        // count scores for displayed marker measurements
+                        compNum.score += num2.score;
+                        compNum.tie += num2.tie;
+                        compNum.dec = Math.max(compNum.dec, num2.dec);
+                        compNum.displayed += 1;
+                    } else {
+                        // count scores for non-displayed marker measurements
+                        compNum.score += num2.score / this.subdivisions;
+                        compNum.tie += num2.tie / this.subdivisions;
+                    }
+                }
+                // adjust scores so numbers closer to 1 are preferred for display
+                compNum.score *= (unitIndex + 1) * compNum.tie / compNum.displayed;
+                comp[unitIndex][vi] = compNum;
+            }
+        }
+        return comp;
+    },
+    
+    /**
+     * Method: setSubProps
+     * Set subdivision properties based on comparison matrix.
+     */
+    setSubProps: function(comp) {
+        var system = this.measurementProperties[this.displaySystem];
+        var score = Number.POSITIVE_INFINITY;
+        var tie = Number.POSITIVE_INFINITY;
+        for(var unitIndex = 0; unitIndex < comp.length; ++unitIndex) {
+            var ppdu = OpenLayers.DOTS_PER_INCH *
+                system.inches[unitIndex] / this.scale;
+            for(var vi in comp[unitIndex]) {
+                var compNum = comp[unitIndex][vi];
+                if((compNum.score < score) ||
+                   ((compNum.score == score) && (compNum.tie < tie))) {
+                    this.subProps = {
+                        length: compNum.value,
+                        pixels: ppdu * compNum.value,
+                        units: system.units[unitIndex],
+                        abbr: system.abbr[unitIndex],
+                        dec: compNum.dec 
+                    };
+                    score = compNum.score;
+                    tie = compNum.tie;
+                }
+            }
+        }
+    },
+    
+    /**
+     * Method: styleValue
+     * Get an integer value associated with a particular selector and key.
+     *     Given a stylesheet with .displayClassSomeSelector {border: 2px solid red},
+     *     styleValue('SomeSelector', 'borderWidth') returns 2
+     *
+     * Returns:
+     * {Integer} A value associated with a style selector/key combo.
+     */
+    styleValue: function(selector, key) {
+        var value = 0;
+        if(this.limitedStyle) {
+            value = this.appliedStyles[selector][key];
+        } else {
+            selector = "." + this.displayClass + selector;
+            rules: 
+            for(var i = document.styleSheets.length - 1; i >= 0; --i) {
+                var sheet = document.styleSheets[i];
+                if(!sheet.disabled) {
+                    var allRules;
+                    try {
+                        if(typeof(sheet.cssRules) == 'undefined') {
+                            if(typeof(sheet.rules) == 'undefined') {
+                                // can't get rules, keep looking
+                                continue;
+                            } else {
+                                allRules = sheet.rules;
+                            }
+                        } else {
+                            allRules = sheet.cssRules;
+                        }
+                    } catch(err) {
+                        continue;
+                    }
+                    for(var ruleIndex = 0; ruleIndex < allRules.length; ++ruleIndex) {
+                        var rule = allRules[ruleIndex];
+                        if(rule.selectorText &&
+                           (rule.selectorText.toLowerCase() == selector.toLowerCase())) {
+                            if(rule.style[key] != '') {
+                                value = parseInt(rule.style[key]);
+                                break rules;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        // if the key was not found, the equivalent value is zero
+        return value ? value : 0;
+    },
+
+    /**
+     * Method: getHandsomeNumber
+     * Attempts to generate a nice looking positive number between two other
+     *     positive numbers.
+     *
+     * Parameters:
+     * small - {Float} Lower positive bound.
+     * big - {Float} Upper positive bound.
+     * sigFigs - {Integer} Number of significant figures to consider.  Default
+     *     is 10.
+     *
+     * Returns:
+     * {Object} Object representing a nice looking number.
+     */
+    getHandsomeNumber: function(small, big, sigFigs) {
+        sigFigs = (sigFigs == null) ? 10 : sigFigs;
+        // if all else fails, return a small ugly number
+        var num = {
+            value: small,
+            score: Number.POSITIVE_INFINITY,
+            tie: Number.POSITIVE_INFINITY,
+            dec: 3
+        };
+        // try the first three comely multiplicands (in order of comliness)
+        var cmult, max, dec, tmult, multiplier, score, tie;
+        for(var hexp = 0; hexp < 3; ++hexp) {
+            cmult = Math.pow(2, (-1 * hexp));
+            max = Math.floor(Math.log(big / cmult) / Math.LN10);
+            for(var texp = max; texp > (max - sigFigs + 1); --texp) {
+                dec = Math.max(hexp - texp, 0);
+                tmult = cmult * Math.pow(10, texp);
+                // check if there is an integer multiple of tmult
+                // between small and big
+                if((tmult * Math.floor(big / tmult)) >= small) {
+                    // check if small is an integer multiple of tmult
+                    if(small % tmult == 0) {
+                        multiplier = small / tmult;
+                    } else {
+                        // smallest integer multiple between small and big
+                        multiplier = Math.floor(small / tmult) + 1;
+                    }
+                    // test against the best (lower == better)
+                    score = multiplier + (2 * hexp);
+                    tie = (texp < 0) ? (Math.abs(texp) + 1) : texp;
+                    if((score < num.score) || ((score == num.score) &&
+                       (tie < num.tie))) {
+                        num.value = parseFloat((tmult * multiplier).toFixed(dec));
+                        num.score = score;
+                        num.tie = tie;
+                        num.dec = dec;
+                    }
+                }
+            }
+        }
+        return num;
+    },
+    
+    CLASS_NAME: "OpenLayers.Control.ScaleBar"
+    
+});



More information about the Commits mailing list