From 22c707a6e23445b4650a92d5f9ca94f84e41d50c Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 26 Jun 2017 21:10:47 -0400 Subject: [PATCH] Make infobox a container for widgets, measurement code a widget --- data/core.yaml | 25 +- dist/locales/en.json | 26 +- modules/index.js | 1 + modules/ui/info.js | 234 ++++------------- modules/ui/info/index.js | 7 + modules/ui/info/measurement.js | 448 ++++++++++++++++----------------- modules/ui/init.js | 5 +- 7 files changed, 296 insertions(+), 450 deletions(-) create mode 100644 modules/ui/info/index.js diff --git a/data/core.yaml b/data/core.yaml index b26bb755b..3c81c9dda 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -274,17 +274,20 @@ en: truncated_list: "Edits by {users} and {count} others" infobox: key: I - selected: "{n} selected" - geometry: Geometry - closed: closed - center: Center - perimeter: Perimeter - length: Length - area: Area - centroid: Centroid - location: Location - metric: Metric - imperial: Imperial + measurement: + key: M + title: Measurement + selected: "{n} selected" + geometry: Geometry + closed: closed + center: Center + perimeter: Perimeter + length: Length + area: Area + centroid: Centroid + location: Location + metric: Metric + imperial: Imperial geometry: point: point vertex: vertex diff --git a/dist/locales/en.json b/dist/locales/en.json index de125c471..fd9719b20 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -350,17 +350,21 @@ }, "infobox": { "key": "I", - "selected": "{n} selected", - "geometry": "Geometry", - "closed": "closed", - "center": "Center", - "perimeter": "Perimeter", - "length": "Length", - "area": "Area", - "centroid": "Centroid", - "location": "Location", - "metric": "Metric", - "imperial": "Imperial" + "measurement": { + "key": "M", + "title": "Measurement", + "selected": "{n} selected", + "geometry": "Geometry", + "closed": "closed", + "center": "Center", + "perimeter": "Perimeter", + "length": "Length", + "area": "Area", + "centroid": "Centroid", + "location": "Location", + "metric": "Metric", + "imperial": "Imperial" + } }, "geometry": { "point": "point", diff --git a/modules/index.js b/modules/index.js index 9277223f4..d2885a685 100644 --- a/modules/index.js +++ b/modules/index.js @@ -11,6 +11,7 @@ export * from './renderer/index'; export * from './services/index'; export * from './svg/index'; export * from './ui/fields/index'; +export * from './ui/info/index'; export * from './ui/intro/index'; export * from './ui/index'; export * from './util/index'; diff --git a/modules/ui/info.js b/modules/ui/info.js index eee4d8613..0f0bfd8db 100644 --- a/modules/ui/info.js +++ b/modules/ui/info.js @@ -1,203 +1,40 @@ import * as d3 from 'd3'; -import _ from 'lodash'; import { d3keybinding } from '../lib/d3.keybinding.js'; import { t } from '../util/locale'; -import { geoExtent } from '../geo/index'; -import { utilDetect } from '../util/detect'; import { uiCmd } from './cmd'; - -import { - geoLength as d3GeoLength, - geoCentroid as d3GeoCentroid -} from 'd3'; +import { uiInfoWidgets } from './info/index'; export function uiInfo(context) { - var isImperial = (utilDetect().locale.toLowerCase() === 'en-us'), - isHidden = true; + var isHidden = true, + ids = Object.keys(uiInfoWidgets), + widgets = {}, + current; + + // create widgets + ids.forEach(function(k) { + current = current || k; + if (!widgets[k]) { + widgets[k] = uiInfoWidgets[k](context); + } + }); + function info(selection) { - function radiansToMeters(r) { - // using WGS84 authalic radius (6371007.1809 m) - return r * 6371007.1809; - } - - function steradiansToSqmeters(r) { - // http://gis.stackexchange.com/a/124857/40446 - return r / (4 * Math.PI) * 510065621724000; - } - - - function toLineString(feature) { - if (feature.type === 'LineString') return feature; - - var result = { type: 'LineString', coordinates: [] }; - if (feature.type === 'Polygon') { - result.coordinates = feature.coordinates[0]; - } else if (feature.type === 'MultiPolygon') { - result.coordinates = feature.coordinates[0][0]; - } - - return result; - } - - - function displayLength(m) { - var d = m * (isImperial ? 3.28084 : 1), - p, unit; - - if (isImperial) { - if (d >= 5280) { - d /= 5280; - unit = 'mi'; - } else { - unit = 'ft'; - } - } else { - if (d >= 1000) { - d /= 1000; - unit = 'km'; - } else { - unit = 'm'; - } - } - - // drop unnecessary precision - p = d > 1000 ? 0 : d > 100 ? 1 : 2; - - return String(d.toFixed(p)) + ' ' + unit; - } - - - function displayArea(m2) { - var d = m2 * (isImperial ? 10.7639111056 : 1), - d1, d2, p1, p2, unit1, unit2; - - if (isImperial) { - if (d >= 6969600) { // > 0.25mi² show mi² - d1 = d / 27878400; - unit1 = 'mi²'; - } else { - d1 = d; - unit1 = 'ft²'; - } - - if (d > 4356 && d < 43560000) { // 0.1 - 1000 acres - d2 = d / 43560; - unit2 = 'ac'; - } - - } else { - if (d >= 250000) { // > 0.25km² show km² - d1 = d / 1000000; - unit1 = 'km²'; - } else { - d1 = d; - unit1 = 'm²'; - } - - if (d > 1000 && d < 10000000) { // 0.1 - 1000 hectares - d2 = d / 10000; - unit2 = 'ha'; - } - } - - // drop unnecessary precision - p1 = d1 > 1000 ? 0 : d1 > 100 ? 1 : 2; - p2 = d2 > 1000 ? 0 : d2 > 100 ? 1 : 2; - - return String(d1.toFixed(p1)) + ' ' + unit1 + - (d2 ? ' (' + String(d2.toFixed(p2)) + ' ' + unit2 + ')' : ''); - } - - function redraw() { - if (isHidden) return; + if (isHidden || !current) return; + var widget = widgets[current]; - var resolver = context.graph(), - selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), - singular = selected.length === 1 ? selected[0] : null, - extent = geoExtent(), - entity; - - wrap.html(''); - wrap.append('h4') - .attr('class', 'infobox-heading fillD') - .text(singular || t('infobox.selected', { n: selected.length })); - - if (!selected.length) return; - - var center; - for (var i = 0; i < selected.length; i++) { - entity = context.entity(selected[i]); - extent._extend(entity.extent(resolver)); - } - center = extent.center(); - - - var list = wrap.append('ul'); - - // multiple features, just display extent center.. - if (!singular) { - list.append('li') - .text(t('infobox.center') + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); - return; - } - - // single feature, display details.. - if (!entity) return; - var geometry = entity.geometry(resolver); - - if (geometry === 'line' || geometry === 'area') { - var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()), - feature = entity.asGeoJSON(resolver), - length = radiansToMeters(d3GeoLength(toLineString(feature))), - lengthLabel = t('infobox.' + (closed ? 'perimeter' : 'length')), - centroid = d3GeoCentroid(feature); - - list.append('li') - .text(t('infobox.geometry') + ': ' + - (closed ? t('infobox.closed') + ' ' : '') + t('geometry.' + geometry) ); - - if (closed) { - var area = steradiansToSqmeters(entity.area(resolver)); - list.append('li') - .text(t('infobox.area') + ': ' + displayArea(area)); - } - - list.append('li') - .text(lengthLabel + ': ' + displayLength(length)); - - list.append('li') - .text(t('infobox.centroid') + ': ' + centroid[0].toFixed(5) + ', ' + centroid[1].toFixed(5)); - - - var toggle = isImperial ? 'imperial' : 'metric'; - wrap.append('a') - .text(t('infobox.' + toggle)) - .attr('href', '#') - .attr('class', 'button') - .on('click', function() { - d3.event.preventDefault(); - isImperial = !isImperial; - redraw(); - }); - - } else { - var centerLabel = t('infobox.' + (entity.type === 'node' ? 'location' : 'center')); - - list.append('li') - .text(t('infobox.geometry') + ': ' + t('geometry.' + geometry)); - - list.append('li') - .text(centerLabel + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); - } + var content = selection.selectAll('.widget-content-' + current); + content.call(widget.redraw); } - function toggle() { + function toggle(setCurrent) { + current = setCurrent || current; + if (d3.event) { d3.event.preventDefault(); } @@ -237,14 +74,39 @@ export function uiInfo(context) { .style('display', (isHidden ? 'none' : 'block')) .merge(wrap); - context.map() - .on('drawn.info', redraw); + + var containers = wrap.selectAll('.widget-container') + .data(ids); + + containers.exit() + .remove(); + + var enter = containers.enter() + .append('div') + .attr('class', function(d) { return 'widget-container widget-container-' + d; }); + + enter + .append('h4') + .attr('class', 'title') + .text(function(d) { return widgets[d].title; }); + + enter + .append('div') + .attr('class', function(d) { return 'widget-content widget-content-' + d; }); + redraw(); var keybinding = d3keybinding('info') .on(uiCmd('⌘' + t('infobox.key')), toggle); + ids.forEach(function(k) { + var key = t('infobox.' + k + '.key', { default: null }); + if (!key) return; + keybinding + .on(uiCmd('⌘' + key), function() { toggle(k); }); + }); + d3.select(document) .call(keybinding); } diff --git a/modules/ui/info/index.js b/modules/ui/info/index.js new file mode 100644 index 000000000..aaa40591c --- /dev/null +++ b/modules/ui/info/index.js @@ -0,0 +1,7 @@ +export * from './measurement'; + +import { uiInfoMeasurement } from './measurement'; + +export var uiInfoWidgets = { + measurement: uiInfoMeasurement, +}; diff --git a/modules/ui/info/measurement.js b/modules/ui/info/measurement.js index 74e2ceecd..3c250026d 100644 --- a/modules/ui/info/measurement.js +++ b/modules/ui/info/measurement.js @@ -1,10 +1,8 @@ import * as d3 from 'd3'; import _ from 'lodash'; -import { d3keybinding } from '../lib/d3.keybinding.js'; -import { t } from '../util/locale'; -import { geoExtent } from '../geo/index'; -import { utilDetect } from '../util/detect'; -import { uiCmd } from './cmd'; +import { t } from '../../util/locale'; +import { geoExtent } from '../../geo'; +import { utilDetect } from '../../util/detect'; import { geoLength as d3GeoLength, @@ -13,241 +11,215 @@ import { export function uiInfoMeasurement(context) { - var isImperial = (utilDetect().locale.toLowerCase() === 'en-us'), - isHidden = true; + var isImperial = (utilDetect().locale.toLowerCase() === 'en-us'); - function info(selection) { - - function radiansToMeters(r) { - // using WGS84 authalic radius (6371007.1809 m) - return r * 6371007.1809; - } - - function steradiansToSqmeters(r) { - // http://gis.stackexchange.com/a/124857/40446 - return r / (4 * Math.PI) * 510065621724000; - } - - - function toLineString(feature) { - if (feature.type === 'LineString') return feature; - - var result = { type: 'LineString', coordinates: [] }; - if (feature.type === 'Polygon') { - result.coordinates = feature.coordinates[0]; - } else if (feature.type === 'MultiPolygon') { - result.coordinates = feature.coordinates[0][0]; - } - - return result; - } - - - function displayLength(m) { - var d = m * (isImperial ? 3.28084 : 1), - p, unit; - - if (isImperial) { - if (d >= 5280) { - d /= 5280; - unit = 'mi'; - } else { - unit = 'ft'; - } - } else { - if (d >= 1000) { - d /= 1000; - unit = 'km'; - } else { - unit = 'm'; - } - } - - // drop unnecessary precision - p = d > 1000 ? 0 : d > 100 ? 1 : 2; - - return String(d.toFixed(p)) + ' ' + unit; - } - - - function displayArea(m2) { - var d = m2 * (isImperial ? 10.7639111056 : 1), - d1, d2, p1, p2, unit1, unit2; - - if (isImperial) { - if (d >= 6969600) { // > 0.25mi² show mi² - d1 = d / 27878400; - unit1 = 'mi²'; - } else { - d1 = d; - unit1 = 'ft²'; - } - - if (d > 4356 && d < 43560000) { // 0.1 - 1000 acres - d2 = d / 43560; - unit2 = 'ac'; - } - - } else { - if (d >= 250000) { // > 0.25km² show km² - d1 = d / 1000000; - unit1 = 'km²'; - } else { - d1 = d; - unit1 = 'm²'; - } - - if (d > 1000 && d < 10000000) { // 0.1 - 1000 hectares - d2 = d / 10000; - unit2 = 'ha'; - } - } - - // drop unnecessary precision - p1 = d1 > 1000 ? 0 : d1 > 100 ? 1 : 2; - p2 = d2 > 1000 ? 0 : d2 > 100 ? 1 : 2; - - return String(d1.toFixed(p1)) + ' ' + unit1 + - (d2 ? ' (' + String(d2.toFixed(p2)) + ' ' + unit2 + ')' : ''); - } - - - function redraw() { - if (isHidden) return; - - var resolver = context.graph(), - selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), - singular = selected.length === 1 ? selected[0] : null, - extent = geoExtent(), - entity; - - wrap.html(''); - wrap.append('h4') - .attr('class', 'infobox-heading fillD') - .text(singular || t('infobox.selected', { n: selected.length })); - - if (!selected.length) return; - - var center; - for (var i = 0; i < selected.length; i++) { - entity = context.entity(selected[i]); - extent._extend(entity.extent(resolver)); - } - center = extent.center(); - - - var list = wrap.append('ul'); - - // multiple features, just display extent center.. - if (!singular) { - list.append('li') - .text(t('infobox.center') + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); - return; - } - - // single feature, display details.. - if (!entity) return; - var geometry = entity.geometry(resolver); - - if (geometry === 'line' || geometry === 'area') { - var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()), - feature = entity.asGeoJSON(resolver), - length = radiansToMeters(d3GeoLength(toLineString(feature))), - lengthLabel = t('infobox.' + (closed ? 'perimeter' : 'length')), - centroid = d3GeoCentroid(feature); - - list.append('li') - .text(t('infobox.geometry') + ': ' + - (closed ? t('infobox.closed') + ' ' : '') + t('geometry.' + geometry) ); - - if (closed) { - var area = steradiansToSqmeters(entity.area(resolver)); - list.append('li') - .text(t('infobox.area') + ': ' + displayArea(area)); - } - - list.append('li') - .text(lengthLabel + ': ' + displayLength(length)); - - list.append('li') - .text(t('infobox.centroid') + ': ' + centroid[0].toFixed(5) + ', ' + centroid[1].toFixed(5)); - - - var toggle = isImperial ? 'imperial' : 'metric'; - wrap.append('a') - .text(t('infobox.' + toggle)) - .attr('href', '#') - .attr('class', 'button') - .on('click', function() { - d3.event.preventDefault(); - isImperial = !isImperial; - redraw(); - }); - - } else { - var centerLabel = t('infobox.' + (entity.type === 'node' ? 'location' : 'center')); - - list.append('li') - .text(t('infobox.geometry') + ': ' + t('geometry.' + geometry)); - - list.append('li') - .text(centerLabel + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); - } - } - - - function toggle() { - if (d3.event) { - d3.event.preventDefault(); - } - - isHidden = !isHidden; - - if (isHidden) { - wrap - .style('display', 'block') - .style('opacity', 1) - .transition() - .duration(200) - .style('opacity', 0) - .on('end', function() { - d3.select(this).style('display', 'none'); - }); - } else { - wrap - .style('display', 'block') - .style('opacity', 0) - .transition() - .duration(200) - .style('opacity', 1) - .on('end', function() { - redraw(); - }); - } - } - - - var wrap = selection.selectAll('.infobox') - .data([0]); - - wrap = wrap.enter() - .append('div') - .attr('class', 'infobox fillD2') - .style('display', (isHidden ? 'none' : 'block')) - .merge(wrap); - - context.map() - .on('drawn.info', redraw); - - redraw(); - - var keybinding = d3keybinding('info') - .on(uiCmd('⌘' + t('infobox.key')), toggle); - - d3.select(document) - .call(keybinding); + function radiansToMeters(r) { + // using WGS84 authalic radius (6371007.1809 m) + return r * 6371007.1809; } - return info; + function steradiansToSqmeters(r) { + // http://gis.stackexchange.com/a/124857/40446 + return r / (4 * Math.PI) * 510065621724000; + } + + + function toLineString(feature) { + if (feature.type === 'LineString') return feature; + + var result = { type: 'LineString', coordinates: [] }; + if (feature.type === 'Polygon') { + result.coordinates = feature.coordinates[0]; + } else if (feature.type === 'MultiPolygon') { + result.coordinates = feature.coordinates[0][0]; + } + + return result; + } + + + function displayLength(m) { + var d = m * (isImperial ? 3.28084 : 1), + p, unit; + + if (isImperial) { + if (d >= 5280) { + d /= 5280; + unit = 'mi'; + } else { + unit = 'ft'; + } + } else { + if (d >= 1000) { + d /= 1000; + unit = 'km'; + } else { + unit = 'm'; + } + } + + // drop unnecessary precision + p = d > 1000 ? 0 : d > 100 ? 1 : 2; + + return String(d.toFixed(p)) + ' ' + unit; + } + + + function displayArea(m2) { + var d = m2 * (isImperial ? 10.7639111056 : 1), + d1, d2, p1, p2, unit1, unit2; + + if (isImperial) { + if (d >= 6969600) { // > 0.25mi² show mi² + d1 = d / 27878400; + unit1 = 'mi²'; + } else { + d1 = d; + unit1 = 'ft²'; + } + + if (d > 4356 && d < 43560000) { // 0.1 - 1000 acres + d2 = d / 43560; + unit2 = 'ac'; + } + + } else { + if (d >= 250000) { // > 0.25km² show km² + d1 = d / 1000000; + unit1 = 'km²'; + } else { + d1 = d; + unit1 = 'm²'; + } + + if (d > 1000 && d < 10000000) { // 0.1 - 1000 hectares + d2 = d / 10000; + unit2 = 'ha'; + } + } + + // drop unnecessary precision + p1 = d1 > 1000 ? 0 : d1 > 100 ? 1 : 2; + p2 = d2 > 1000 ? 0 : d2 > 100 ? 1 : 2; + + return String(d1.toFixed(p1)) + ' ' + unit1 + + (d2 ? ' (' + String(d2.toFixed(p2)) + ' ' + unit2 + ')' : ''); + } + + + function redraw(selection) { + + // check if visible + + var resolver = context.graph(), + selected = _.filter(context.selectedIDs(), function(e) { return context.hasEntity(e); }), + singular = selected.length === 1 ? selected[0] : null, + extent = geoExtent(), + entity; + + selection.html(''); + + selection + .append('h4') + .attr('class', 'infobox-heading fillD') + .text(singular || t('infobox.measurement.selected', { n: selected.length })); + + if (!selected.length) return; + + var center; + for (var i = 0; i < selected.length; i++) { + entity = context.entity(selected[i]); + extent._extend(entity.extent(resolver)); + } + center = extent.center(); + + + var list = selection + .append('ul'); + + // multiple features, just display extent center.. + if (!singular) { + list + .append('li') + .text(t('infobox.measurement.center') + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); + return; + } + + // single feature, display details.. + if (!entity) return; + var geometry = entity.geometry(resolver); + + if (geometry === 'line' || geometry === 'area') { + var closed = (entity.type === 'relation') || (entity.isClosed() && !entity.isDegenerate()), + feature = entity.asGeoJSON(resolver), + length = radiansToMeters(d3GeoLength(toLineString(feature))), + lengthLabel = t('infobox.measurement.' + (closed ? 'perimeter' : 'length')), + centroid = d3GeoCentroid(feature); + + list + .append('li') + .text(t('infobox.measurement.geometry') + ': ' + + (closed ? t('infobox.measurement.closed') + ' ' : '') + t('geometry.' + geometry) ); + + if (closed) { + var area = steradiansToSqmeters(entity.area(resolver)); + list + .append('li') + .text(t('infobox.measurement.area') + ': ' + displayArea(area)); + } + + list + .append('li') + .text(lengthLabel + ': ' + displayLength(length)); + + list + .append('li') + .text(t('infobox.measurement.centroid') + ': ' + centroid[0].toFixed(5) + ', ' + centroid[1].toFixed(5)); + + + var toggle = isImperial ? 'imperial' : 'metric'; + + selection + .append('a') + .text(t('infobox.measurement.' + toggle)) + .attr('href', '#') + .attr('class', 'button') + .on('click', function() { + d3.event.preventDefault(); + isImperial = !isImperial; + redraw(); + }); + + } else { + var centerLabel = t('infobox.measurement.' + (entity.type === 'node' ? 'location' : 'center')); + + list + .append('li') + .text(t('infobox.measurement.geometry') + ': ' + t('geometry.' + geometry)); + + list + .append('li') + .text(centerLabel + ': ' + center[0].toFixed(5) + ', ' + center[1].toFixed(5)); + } + } + + + + var widget = {}; + + widget.id = 'measurement'; + widget.title = t('infobox.measurement.title'); + widget.key = t('infobox.measurement.key'); + + widget.redraw = function(selection) { + selection.call(redraw); + + context.map() + .on('drawn.info-measurement', function() { + selection.call(redraw); + }); + }; + + return widget; } diff --git a/modules/ui/init.js b/modules/ui/init.js index cdfb5c6d4..5a88f51cd 100644 --- a/modules/ui/init.js +++ b/modules/ui/init.js @@ -81,10 +81,7 @@ export function uiInit(context) { .call(map); content - .call(uiMapInMap(context)); - - content - .append('div') + .call(uiMapInMap(context)) .call(uiInfo(context)); bar