diff --git a/modules/actions/copy_entities.js b/modules/actions/copy_entities.js index d47a0a3c7..d75a032b9 100644 --- a/modules/actions/copy_entities.js +++ b/modules/actions/copy_entities.js @@ -1,14 +1,14 @@ export function actionCopyEntities(ids, fromGraph) { - var copies = {}; + var _copies = {}; var action = function(graph) { ids.forEach(function(id) { - fromGraph.entity(id).copy(fromGraph, copies); + fromGraph.entity(id).copy(fromGraph, _copies); }); - for (var id in copies) { - graph = graph.replace(copies[id]); + for (var id in _copies) { + graph = graph.replace(_copies[id]); } return graph; @@ -16,7 +16,7 @@ export function actionCopyEntities(ids, fromGraph) { action.copies = function() { - return copies; + return _copies; }; diff --git a/modules/behavior/paste.js b/modules/behavior/paste.js index 596ed9c2c..806b34aa2 100644 --- a/modules/behavior/paste.js +++ b/modules/behavior/paste.js @@ -1,6 +1,3 @@ -import _invert from 'lodash-es/invert'; -import _mapValues from 'lodash-es/mapValues'; - import { event as d3_event } from 'd3-selection'; import { actionCopyEntities, actionMove } from '../actions'; @@ -32,7 +29,9 @@ export function behaviorPaste(context) { context.perform(action); var copies = action.copies(); - var originals = _invert(_mapValues(copies, 'id')); + var originals = new Set(); + Object.values(copies).forEach(function(entity) { originals.add(entity.id); }); + for (var id in copies) { var oldEntity = oldGraph.entity(id); var newEntity = copies[id]; @@ -41,13 +40,9 @@ export function behaviorPaste(context) { // Exclude child nodes from newIDs if their parent way was also copied. var parents = context.graph().parentWays(newEntity); - var parentCopied = false; - for (var i = 0; i < parents.length; i++) { - if (originals[parents[i].id]) { - parentCopied = true; - break; - } - } + var parentCopied = parents.some(function(parent) { + return originals.has(parent.id); + }); if (!parentCopied) { newIDs.push(newEntity.id); diff --git a/modules/operations/reflect.js b/modules/operations/reflect.js index 312313d8d..9ee35f25c 100644 --- a/modules/operations/reflect.js +++ b/modules/operations/reflect.js @@ -1,5 +1,3 @@ -import _uniqBy from 'lodash-es/uniqBy'; - import { t } from '../util/locale'; import { actionReflect } from '../actions'; import { behaviorOperation } from '../behavior'; @@ -19,10 +17,10 @@ export function operationReflectLong(selectedIDs, context) { export function operationReflect(selectedIDs, context, axis) { axis = axis || 'long'; - var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'), - extent = selectedIDs.reduce(function(extent, id) { - return extent.extend(context.entity(id).extent(context.graph())); - }, geoExtent()); + var multi = (selectedIDs.length === 1 ? 'single' : 'multiple'); + var extent = selectedIDs.reduce(function(extent, id) { + return extent.extend(context.entity(id).extent(context.graph())); + }, geoExtent()); var operation = function() { @@ -34,7 +32,11 @@ export function operationReflect(selectedIDs, context, axis) { operation.available = function() { var nodes = utilGetAllNodes(selectedIDs, context.graph()); - return _uniqBy(nodes, function(n) { return n.loc; }).length >= 3; + var uniqeLocs = nodes.reduce(function(acc, node) { + return acc.add(node.loc); + }, new Set()); + + return uniqeLocs.size >= 3; }; diff --git a/modules/operations/rotate.js b/modules/operations/rotate.js index 442eb1310..61335aaef 100644 --- a/modules/operations/rotate.js +++ b/modules/operations/rotate.js @@ -1,5 +1,3 @@ -import _uniqBy from 'lodash-es/uniqBy'; - import { t } from '../util/locale'; import { behaviorOperation } from '../behavior'; import { geoExtent } from '../geo'; @@ -21,7 +19,11 @@ export function operationRotate(selectedIDs, context) { operation.available = function() { var nodes = utilGetAllNodes(selectedIDs, context.graph()); - return _uniqBy(nodes, function(n) { return n.loc; }).length >= 2; + var uniqeLocs = nodes.reduce(function(acc, node) { + return acc.add(node.loc); + }, new Set()); + + return uniqeLocs.size >= 2; }; diff --git a/modules/osm/lanes.js b/modules/osm/lanes.js index 35b846862..941f602b6 100644 --- a/modules/osm/lanes.js +++ b/modules/osm/lanes.js @@ -1,7 +1,3 @@ -import _isNumber from 'lodash-es/isNumber'; -import _isString from 'lodash-es/isString'; -import _isNaN from 'lodash-es/isNaN'; - export function osmLanes(entity) { if (entity.type !== 'way') return null; @@ -122,12 +118,12 @@ function getLaneCount(tags, isOneWay) { function parseMaxspeed(tags) { var maxspeed = tags.maxspeed; - if (_isNumber(maxspeed)) return maxspeed; - if (_isString(maxspeed)) { - maxspeed = maxspeed.match(/^([0-9][\.0-9]+?)(?:[ ]?(?:km\/h|kmh|kph|mph|knots))?$/g); - if (!maxspeed) return; - return parseInt(maxspeed, 10); - } + if (!maxspeed) return; + + var maxspeedRegex = /^([0-9][\.0-9]+?)(?:[ ]?(?:km\/h|kmh|kph|mph|knots))?$/; + if (!maxspeedRegex.test(maxspeed)) return; + + return parseInt(maxspeed, 10); } @@ -146,17 +142,17 @@ function parseLaneDirections(tags, isOneWay, laneCount) { bothways = 0; backward = 0; } - else if (_isNaN(forward) && _isNaN(backward)) { + else if (isNaN(forward) && isNaN(backward)) { backward = Math.floor((laneCount - bothways) / 2); forward = laneCount - bothways - backward; } - else if (_isNaN(forward)) { + else if (isNaN(forward)) { if (backward > laneCount - bothways) { backward = laneCount - bothways; } forward = laneCount - bothways - backward; } - else if (_isNaN(backward)) { + else if (isNaN(backward)) { if (forward > laneCount - bothways) { forward = laneCount - bothways; } @@ -197,7 +193,7 @@ function parseMaxspeedLanes(tag, maxspeed) { if (s === 'none') return s; var m = parseInt(s, 10); if (s === '' || m === maxspeed) return null; - return _isNaN(m) ? 'unknown': m; + return isNaN(m) ? 'unknown': m; }); } diff --git a/modules/presets/index.js b/modules/presets/index.js index 4d12aacd0..35d77bcb6 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -1,6 +1,3 @@ -import _bind from 'lodash-es/bind'; -import _forEach from 'lodash-es/forEach'; - import { dispatch as d3_dispatch } from 'd3-dispatch'; import { json as d3_json } from 'd3-request'; @@ -161,9 +158,10 @@ export function presetIndex(context) { all.build = function(d, visible) { if (d.fields) { - _forEach(d.fields, function(d, id) { - _fields[id] = presetField(id, d); - if (d.universal) { + Object.keys(d.fields).forEach(function(id) { + var f = d.fields[id]; + _fields[id] = presetField(id, f); + if (f.universal) { _universal.push(_fields[id]); } }); @@ -171,29 +169,31 @@ export function presetIndex(context) { if (d.presets) { var rawPresets = d.presets; - _forEach(d.presets, function(d, id) { + Object.keys(d.presets).forEach(function(id) { + var p = d.presets[id]; var existing = all.index(id); if (existing !== -1) { - all.collection[existing] = presetPreset(id, d, _fields, visible, rawPresets); + all.collection[existing] = presetPreset(id, p, _fields, visible, rawPresets); } else { - all.collection.push(presetPreset(id, d, _fields, visible, rawPresets)); + all.collection.push(presetPreset(id, p, _fields, visible, rawPresets)); } }); } if (d.categories) { - _forEach(d.categories, function(d, id) { + Object.keys(d.categories).forEach(function(id) { + var c = d.categories[id]; var existing = all.index(id); if (existing !== -1) { - all.collection[existing] = presetCategory(id, d, all); + all.collection[existing] = presetCategory(id, c, all); } else { - all.collection.push(presetCategory(id, d, all)); + all.collection.push(presetCategory(id, c, all)); } }); } if (d.defaults) { - var getItem = _bind(all.item, all); + var getItem = (all.item).bind(all); _defaults = { area: presetCollection(d.defaults.area.map(getItem)), line: presetCollection(d.defaults.line.map(getItem)), diff --git a/modules/ui/changeset_editor.js b/modules/ui/changeset_editor.js index 643094a94..e2e8bb1cf 100644 --- a/modules/ui/changeset_editor.js +++ b/modules/ui/changeset_editor.js @@ -1,11 +1,9 @@ -import _uniqBy from 'lodash-es/uniqBy'; - import { dispatch as d3_dispatch } from 'd3-dispatch'; import { t } from '../util/locale'; import { svgIcon } from '../svg'; import { uiCombobox, uiField, uiFormFields } from './index'; -import { utilRebind, utilTriggerEvent } from '../util'; +import { utilArrayUniqBy, utilRebind, utilTriggerEvent } from '../util'; export function uiChangesetEditor(context) { @@ -72,15 +70,13 @@ export function uiChangesetEditor(context) { if (err) return; var comments = changesets.map(function(changeset) { - return { - title: changeset.tags.comment, - value: changeset.tags.comment - }; - }); + var comment = changeset.tags.comment; + return comment ? { title: comment, value: comment } : null; + }).filter(Boolean); commentField .call(commentCombo - .data(_uniqBy(comments, 'title')) + .data(utilArrayUniqBy(comments, 'title')) ); }); } diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index d60239f53..554006135 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -1,5 +1,3 @@ -import _uniqBy from 'lodash-es/uniqBy'; - import { dispatch as d3_dispatch } from 'd3-dispatch'; import { select as d3_select } from 'd3-selection'; @@ -7,7 +5,7 @@ import { dataAddressFormats } from '../../../data'; import { geoExtent, geoChooseEdge, geoSphericalDistance } from '../../geo'; import { services } from '../../services'; import { uiCombobox } from '../index'; -import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util'; +import { utilArrayUniqBy, utilGetSetValue, utilNoAuto, utilRebind } from '../../util'; export function uiFieldAddress(field, context) { @@ -41,7 +39,7 @@ export function uiFieldAddress(field, context) { return a.dist - b.dist; }); - return _uniqBy(streets, 'value'); + return utilArrayUniqBy(streets, 'value'); function isAddressable(d) { return d.tags.highway && d.tags.name && d.type === 'way'; @@ -67,7 +65,7 @@ export function uiFieldAddress(field, context) { return a.dist - b.dist; }); - return _uniqBy(cities, 'value'); + return utilArrayUniqBy(cities, 'value'); function isAddressable(d) { @@ -105,7 +103,7 @@ export function uiFieldAddress(field, context) { return a.dist - b.dist; }); - return _uniqBy(results, 'value'); + return utilArrayUniqBy(results, 'value'); } diff --git a/modules/ui/fields/input.js b/modules/ui/fields/input.js index f0c6d02ae..782875a95 100644 --- a/modules/ui/fields/input.js +++ b/modules/ui/fields/input.js @@ -8,12 +8,7 @@ import { t, textDirection } from '../../util/locale'; import { dataPhoneFormats } from '../../../data'; import { services } from '../../services'; import { tooltip } from '../../util/tooltip'; - -import { - utilGetSetValue, - utilNoAuto, - utilRebind -} from '../../util'; +import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util'; export { diff --git a/modules/util/array.js b/modules/util/array.js index 02d8dd827..fc189203f 100644 --- a/modules/util/array.js +++ b/modules/util/array.js @@ -45,6 +45,7 @@ export function utilArrayUniq(a) { return Array.from(new Set(a)); } + // Splits array into chunks of given chunk size // var a = [1,2,3,4,5,6,7]; // utilArrayChunk(a, 3); @@ -89,3 +90,39 @@ export function utilArrayGroupBy(a, key) { }, {}); } + +// Returns an Array with all the duplicates removed +// where uniqueness determined by the given key +// `key` can be passed as a property or as a key function +// +// var pets = [ +// { type: 'Dog', name: 'Spot' }, +// { type: 'Cat', name: 'Tiger' }, +// { type: 'Dog', name: 'Rover' }, +// { type: 'Cat', name: 'Leo' } +// ]; +// +// utilArrayUniqBy(pets, 'type') +// [ +// { type: 'Dog', name: 'Spot' }, +// { type: 'Cat', name: 'Tiger' } +// ] +// +// utilArrayUniqBy(pets, function(item) { return item.name.length; }) +// [ +// { type: 'Dog', name: 'Spot' }, +// { type: 'Cat', name: 'Tiger' }, +// { type: 'Cat', name: 'Leo' } +// } +export function utilArrayUniqBy(a, key) { + var seen = new Set(); + return a.reduce(function(acc, item) { + var val = (typeof key === 'function') ? key(item) : item[key]; + if (val && !seen.has(val)) { + seen.add(val); + acc.push(item); + } + return acc; + }, []); +} + diff --git a/modules/util/index.js b/modules/util/index.js index 992360c9c..c7431b503 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -4,6 +4,7 @@ export { utilArrayGroupBy } from './array'; export { utilArrayIntersection } from './array'; export { utilArrayUnion } from './array'; export { utilArrayUniq } from './array'; +export { utilArrayUniqBy } from './array'; export { utilAsyncMap } from './util'; export { utilCallWhenIdle } from './call_when_idle'; diff --git a/test/spec/util/array.js b/test/spec/util/array.js index d97062c54..d8e8c2b0c 100644 --- a/test/spec/util/array.js +++ b/test/spec/util/array.js @@ -81,4 +81,38 @@ describe('iD.utilArray', function() { }); }); + describe('utilArrayUniqBy', function() { + var pets = [ + { type: 'Dog', name: 'Spot' }, + { type: 'Cat', name: 'Tiger' }, + { type: 'Dog', name: 'Rover' }, + { type: 'Cat', name: 'Leo' } + ]; + + it('groups by key property', function() { + var expected = [ + { type: 'Dog', name: 'Spot' }, + { type: 'Cat', name: 'Tiger' } + //{ type: 'Dog', name: 'Rover' }, // not unique by type + //{ type: 'Cat', name: 'Leo' } // not unique by type + ]; + expect(iD.utilArrayUniqBy(pets, 'type')).to.eql(expected); + }); + + it('groups by key function', function() { + var expected = [ + { type: 'Dog', name: 'Spot' }, + { type: 'Cat', name: 'Tiger' }, + //{ type: 'Dog', name: 'Rover' }, // not unique by name length + { type: 'Cat', name: 'Leo' } + ]; + var keyFn = function(item) { return item.name.length; }; + expect(iD.utilArrayUniqBy(pets, keyFn)).to.eql(expected); + }); + + it('undefined key function', function() { + expect(iD.utilArrayUniqBy(pets)).to.eql([]); + }); + }); + });