Add a formatUnit method to the scaleWidget.
Make the formatting units utility available.
MODIFY
CHANGELOG.md
CHANGELOG.md
- The mapInteractor cancelOnMove option can now take a movement threshold (#1058)
- GCS can now be specified in pointSearch, boxSearch, and polygonSearch (#1051)
- Added a track feature (#1040)
+- Added a geo.gui.scaleWidget.formatUnit utility function (#1048)
## Version 0.19.8

MODIFY
examples/measure/main.js
examples/measure/main.js
@@ -49,36 +49,6 @@ geo.osmLayer.tileSources['false'] = {
url: '/data/white.jpg',
attribution: ''
};
-// Define a decimal miles unit
-geo.gui.scaleWidget.unitsTable['decmiles'] = [
- {unit: 'mi', scale: 1609.344}
-];
-// Define area units
-var areaUnitsTable = {
- si: [
- {unit: 'nm\xB2', scale: 1e-18},
- {unit: '\u03BCm\xB2', scale: 1e-12},
- {unit: 'mm\xB2', scale: 1e-6},
- {unit: 'm\xB2', scale: 1},
- {unit: 'km\xB2', scale: 1e6}
- ],
- hectares: [
- {unit: 'ha', scale: 1e4}
- ],
- decmiles: [
- {unit: 'mi\xB2', scale: 1609.344 * 1609.344}
- ],
- miles: [
- {unit: 'in\xB2', scale: 0.0254 * 0.0254},
- {unit: 'ft\xB2', scale: 0.3048 * 0.3048},
- {unit: 'mi\xB2', scale: 1609.344 * 1609.344}
- ],
- acres: [
- {unit: 'pl', scale: 0.3048 * 0.3048 * 16.5 * 16.5},
- {unit: 'rd', scale: 1609.344 * 1609.344 / 640 / 4},
- {unit: 'ac', scale: 1609.344 * 1609.344 / 640}
- ]
-};
var map, mapLayer, layer, fromButtonSelect, fromGeojsonUpdate;
@@ -418,47 +388,6 @@ function annotationArea(annotation) {
return Math.abs(area);
}
-/**
- * Format a unit with at least three siginfiicant figures.
- *
- * @param {number} val The value. A valance or area in base units.
- * @param {string} unit The name of the unit system.
- * @param {object[]} [table] The table of the unit system.
- * @returns {string} A formatted string or `undefined`.
- */
-function formatUnit(val, unit, table) {
- if (val === undefined || val === null) {
- return;
- }
- table = table || geo.gui.scaleWidget.unitsTable;
- if (!table || !table[unit]) {
- return;
- }
- unit = table[unit];
- let pos;
- for (pos = 0; pos < unit.length - 1; pos += 1) {
- if (val < unit[pos + 1].scale) {
- break;
- }
- }
- unit = unit[pos];
- val /= unit.scale;
- let digits = Math.max(0, -Math.ceil(Math.log10(val)) + 3);
- if (digits > 10) {
- return;
- }
- let result = val.toFixed(digits);
- if (digits) {
- while (result.substr(result.length - 1) === '0') {
- result = result.substr(0, result.length - 1);
- }
- if (result.substr(result.length - 1) === '.') {
- result = result.substr(0, result.length - 1);
- }
- }
- return result + ' ' + unit.unit;
-}
-
/**
* Add the units to the name of the annotation if appropriate.
*
@@ -477,11 +406,11 @@ function modifyAnnotation(annotation) {
area = annotationArea(annotation);
var result = s_labelRecord();
if (result) {
- dist = formatUnit(dist, query.distunit || 'decmiles');
+ dist = geo.gui.scaleWidget.formatUnit(dist, query.distunit || 'decmiles');
if (dist) {
result.text += ' - ' + dist;
}
- area = formatUnit(area, query.areaunit || 'decmiles', areaUnitsTable);
+ area = geo.gui.scaleWidget.formatUnit(area, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
result.text += ' - ' + area;
}
@@ -527,20 +456,20 @@ function labelVertices(annotation, annotationIndex, labels) {
pts.forEach((p, i) => {
if (i) {
labels.push({
- text: formatUnit(tally[i], query.distunit || 'decmiles') || '',
+ text: geo.gui.scaleWidget.formatUnit(tally[i], query.distunit || 'decmiles') || '',
position: p,
style: Object.assign({}, style, {offset: {x: 12, y: 0}, textAlign: 'left'})
});
}
if (i !== pts.length - 1) {
labels.push({
- text: formatUnit(tally[tally.length - 1] - tally[i], query.distunit || 'decmiles') || '',
+ text: geo.gui.scaleWidget.formatUnit(tally[tally.length - 1] - tally[i], query.distunit || 'decmiles') || '',
position: p,
style: Object.assign({}, style, {offset: {x: -12, y: 0}, textAlign: 'right'})
});
let p1 = {x: (pts[i + 1].x + p.x) / 2, y: (pts[i + 1].y + p.y) / 2};
labels.push({
- text: formatUnit(dist[i], query.distunit || 'decmiles') || '',
+ text: geo.gui.scaleWidget.formatUnit(dist[i], query.distunit || 'decmiles') || '',
position: p1,
style: Object.assign({}, style, {offset: {x: 10, y: 0}, textAlign: 'left'})
});
@@ -582,8 +511,8 @@ function handleAnnotationChange(evt) {
area = annotationArea(annotation);
if (dist) { totaldist += dist; }
if (area) { totalarea += area; }
- dist = formatUnit(dist, query.distunit || 'decmiles') || '';
- area = formatUnit(area, query.areaunit || 'decmiles', areaUnitsTable);
+ dist = geo.gui.scaleWidget.formatUnit(dist, query.distunit || 'decmiles') || '';
+ area = geo.gui.scaleWidget.formatUnit(area, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
dist = (dist ? dist + ' - ' : '') + area;
}
@@ -603,8 +532,8 @@ function handleAnnotationChange(evt) {
entry.find('.entry-dist').text(dist);
$('#annotationlist').append(entry);
});
- let dist = formatUnit(totaldist || undefined, query.distunit || 'decmiles') || '';
- let area = formatUnit(totalarea || undefined, query.areaunit || 'decmiles', areaUnitsTable);
+ let dist = geo.gui.scaleWidget.formatUnit(totaldist || undefined, query.distunit || 'decmiles') || '';
+ let area = geo.gui.scaleWidget.formatUnit(totalarea || undefined, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
dist = (dist ? dist + ' - ' : '') + area;
}

MODIFY
src/ui/scaleWidget.js
src/ui/scaleWidget.js
@@ -52,6 +52,24 @@ require('./scaleWidget.styl');
* `mutliple`.
*/
+/**
+ * For a unit table, the records are ordered smallest scale to largest scale.
+ * The smallest unit can be repeated to have different rounding behavior for
+ * values less than 1 and values greater than or equal to 1.
+ *
+ * @typedef {object} geo.gui.scaleWidget.unitTableRecord
+ * @property {string} unit The display name of the unit.
+ * @property {number} scale The size of the unit in base unit.
+ * @property {number} [basis=10] The number of units in the next greater unit
+ * if not a power of 10.
+ * @property {object[]} [multiples] A list of multiplier values to round to
+ * when rounding is used. The list should probably include a multiple of 1.
+ * Default is 1, 1.5, 2, 3, 5, 8.
+ * @property {number} multiples.multiple A factor to round to.
+ * @property {number} multiples.digit The number of digits to preserve when
+ * rounding.
+ */
+
/**
* Create a new instance of class geo.gui.scaleWidget.
*
@@ -314,12 +332,21 @@ var scaleWidget = function (arg) {
inherit(scaleWidget, svgWidget);
-/* The unitsTable has predefined unit sets. Each entry is an array that must
- * be in ascending order. */
+/**
+ * The unitsTable has predefined unit sets for a base unit of one meter. Each
+ * entry is an array that must be in ascending order. Use unicode in strings,
+ * not html entities. It makes it more reusable.
+ * @name unitsTable
+ * @property unitsTable {object} The key names are the names of unit systems,
+ * such as `si`.
+ * @property unitsTable.unit {geo.gui.scaleWidget.unitTableRecord[]} A list of
+ * units within the unit system from smallest to largest.
+ * @memberof geo.gui.scaleWidget
+ */
scaleWidget.unitsTable = {
si: [
{unit: 'nm', scale: 1e-9},
- {unit: '&mu;m', scale: 1e-6},
+ {unit: '\u03BCm', scale: 1e-6},
{unit: 'mm', scale: 0.001},
{unit: 'm', scale: 1},
{unit: 'km', scale: 1000}
@@ -346,8 +373,98 @@ scaleWidget.unitsTable = {
},
{unit: 'ft', scale: 0.3048},
{unit: 'mi', scale: 1609.344}
+ ],
+ decmiles: [ // decimal miles
+ {unit: 'mi', scale: 1609.344}
+ ]
+};
+
+/**
+ * The areaUnitsTable has predefined unit sets for a base unit of one square
+ * meter. Each entry is an array that must be in ascending order. This table
+ * can be passed to formatUnit.
+ * @name areaUnitsTable
+ * @property areaUnitsTable {object} The key names are the names of unit
+ * systems, such as `si`.
+ * @property areaUnitsTable.unit {geo.gui.scaleWidget.unitTableRecord[]} A list
+ * of units within the unit system from smallest to largest.
+ * @memberof geo.gui.scaleWidget
+ */
+scaleWidget.areaUnitsTable = {
+ si: [
+ {unit: 'nm\xB2', scale: 1e-18},
+ {unit: '\u03BCm\xB2', scale: 1e-12},
+ {unit: 'mm\xB2', scale: 1e-6},
+ {unit: 'm\xB2', scale: 1},
+ {unit: 'km\xB2', scale: 1e6}
+ ],
+ hectares: [
+ {unit: 'ha', scale: 1e4}
+ ],
+ decmiles: [ // decimal square miles
+ {unit: 'mi\xB2', scale: 1609.344 * 1609.344}
+ ],
+ miles: [
+ {unit: 'in\xB2', scale: 0.0254 * 0.0254},
+ {unit: 'ft\xB2', scale: 0.3048 * 0.3048},
+ {unit: 'mi\xB2', scale: 1609.344 * 1609.344}
+ ],
+ acres: [
+ {unit: 'pl', scale: 0.3048 * 0.3048 * 16.5 * 16.5},
+ {unit: 'rd', scale: 1609.344 * 1609.344 / 640 / 4},
+ {unit: 'ac', scale: 1609.344 * 1609.344 / 640}
]
};
+/**
+ * Format a unit with a specified number of significant figures. Given a value
+ * in base units, such as meters, this will return a string with appropriate
+ * units. For instance, `formatUnit(0.345)` will return `345 mm`.
+ *
+ * @param {number} val The value. A length or area in base units. With the
+ * default unit table, this is in meters. With the `areaUnitsTable`, this
+ * is square meters.
+ * @param {string|object[]} [unit='si'] The name of the unit system or a unit
+ * table.
+ * @param {object} [table=unitTable] The table of the unit system. Ignored if
+ * `unit` is a unit table.
+ * @param {number} [digits=3] The minimum number of significant figures.
+ * @returns {string} A formatted string or `undefined`.
+ */
+scaleWidget.formatUnit = function (val, unit, table, digits) {
+ if (val === undefined || val === null) {
+ return;
+ }
+ if (!Array.isArray(unit)) {
+ table = table || scaleWidget.unitsTable;
+ if (!table || !table[unit || 'si']) {
+ return;
+ }
+ unit = table[unit || 'si'];
+ }
+ let pos;
+ for (pos = 0; pos < unit.length - 1; pos += 1) {
+ if (val < unit[pos + 1].scale) {
+ break;
+ }
+ }
+ unit = unit[pos];
+ val /= unit.scale;
+ digits = Math.max(0, -Math.ceil(Math.log10(val)) + (digits === undefined || digits < 0 ? 3 : digits));
+ if (digits > 10) {
+ return;
+ }
+ let result = val.toFixed(digits);
+ if (digits) {
+ while (result.substr(result.length - 1) === '0') {
+ result = result.substr(0, result.length - 1);
+ }
+ if (result.substr(result.length - 1) === '.') {
+ result = result.substr(0, result.length - 1);
+ }
+ }
+ return result + ' ' + unit.unit;
+};
+
registerWidget('dom', 'scale', scaleWidget);
module.exports = scaleWidget;

MODIFY
src/util/color.js
src/util/color.js
@@ -267,7 +267,7 @@ var colorUtils = {
* standard, `rgb` and `rgba` are aliases of each other, as are `hsl` and
* `hsla`.
* @name cssColorConversions
- * @property cssColorConversions
+ * @property cssColorConversions {geo.util.cssColorConversionRecord[]}
* @memberof geo.util
*/
colorUtils.cssColorConversions = [{

MODIFY
tests/cases/scaleWidget.js
tests/cases/scaleWidget.js
@@ -56,7 +56,7 @@ describe('geo.gui.scaleWidget', function () {
expect(result.pixels).toBe(150);
result = widget._scaleValue(1.6e-5, 200);
expect(result.value).toBe(1.5e-5);
- expect(result.html).toBe('15 &mu;m');
+ expect(result.html).toBe('15 \u03BCm');
expect(widget._scaleValue(1, 200, 'miles').html).toBe('3 ft');
expect(widget._scaleValue(0.2, 200, 'miles').html).toBe('6 in');
expect(widget._scaleValue(0.01, 200, 'miles').html).toBe('0.3 in');
@@ -132,4 +132,28 @@ describe('geo.gui.scaleWidget', function () {
expect(widget.options('orientation')).toBe('right');
});
});
+
+ describe('Check class utility methods', function () {
+ it('formatUnit', function () {
+ var formatUnit = geo.gui.scaleWidget.formatUnit,
+ areaUnitsTable = geo.gui.scaleWidget.areaUnitsTable;
+ expect(formatUnit(3, 'si')).toBe('3 m');
+ expect(formatUnit(3e-4, 'si')).toBe('300 \u03BCm');
+ expect(formatUnit(3.4567, 'si')).toBe('3.46 m');
+ expect(formatUnit(3.4567)).toBe('3.46 m');
+ expect(formatUnit('3.4567', 'si')).toBe('3.46 m');
+ expect(formatUnit(3.4567, 'si', undefined, 2)).toBe('3.5 m');
+ expect(formatUnit(3.4567, 'si', undefined, 0)).toBe('3 m');
+ expect(formatUnit(3.4567, 'si', undefined, -2)).toBe('3.46 m');
+ expect(formatUnit(34567, 'si')).toBe('34.6 km');
+ expect(formatUnit(3.4567, 'si', areaUnitsTable)).toBe('3.46 m\xB2');
+ expect(formatUnit(null, 'si')).toBe(undefined);
+ expect(formatUnit(3e-100, 'si')).toBe(undefined);
+ expect(formatUnit(3.4567, 'nosuchunits')).toBe(undefined);
+ expect(formatUnit(3.4567, 'miles')).toBe('11.3 ft');
+ expect(formatUnit(3.4567e-3, 'miles')).toBe('0.136 in');
+ expect(formatUnit(3.4567e3, 'miles')).toBe('2.15 mi');
+ expect(formatUnit(3.4567e3, [{unit: 'NM', scale: 1852}])).toBe('1.87 NM');
+ });
+ });
});

Add a formatUnit method to the scaleWidget.

Make the formatting units utility available.