[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