diff --git a/Makefile b/Makefile
index 88c0d4853..109706bda 100644
--- a/Makefile
+++ b/Makefile
@@ -44,7 +44,6 @@ $(BUILDJS_TARGETS): $(BUILDJS_SOURCES) build.js
MODULE_TARGETS = \
js/lib/id/index.js \
- js/lib/id/presets.js \
js/lib/id/renderer.js \
js/lib/id/services.js \
js/lib/id/ui/index.js \
@@ -61,10 +60,6 @@ js/lib/id/modes.js: $(shell find modules/modes -type f)
@rm -f $@
node_modules/.bin/rollup -f umd -n iD.modes modules/modes/index.js --no-strict -o $@
-js/lib/id/presets.js: $(shell find modules/presets -type f)
- @rm -f $@
- node_modules/.bin/rollup -f umd -n iD.presets modules/presets/index.js --no-strict -o $@
-
js/lib/id/renderer.js: $(shell find modules/renderer -type f)
@rm -f $@
node_modules/.bin/rollup -f umd -n iD modules/renderer/index.js --no-strict -o $@
diff --git a/index.html b/index.html
index 555552818..6ede12354 100644
--- a/index.html
+++ b/index.html
@@ -37,7 +37,6 @@
-
diff --git a/js/lib/id/index.js b/js/lib/id/index.js
index fbdfd4f6d..597dec459 100644
--- a/js/lib/id/index.js
+++ b/js/lib/id/index.js
@@ -12364,6 +12364,482 @@
Tail: Tail
});
+ function Collection(collection) {
+ var maxSearchResults = 50,
+ maxSuggestionResults = 10;
+
+ var presets = {
+
+ collection: collection,
+
+ item: function(id) {
+ return _.find(collection, function(d) {
+ return d.id === id;
+ });
+ },
+
+ matchGeometry: function(geometry) {
+ return Collection(collection.filter(function(d) {
+ return d.matchGeometry(geometry);
+ }));
+ },
+
+ search: function(value, geometry) {
+ if (!value) return this;
+
+ value = value.toLowerCase();
+
+ var searchable = _.filter(collection, function(a) {
+ return a.searchable !== false && a.suggestion !== true;
+ }),
+ suggestions = _.filter(collection, function(a) {
+ return a.suggestion === true;
+ });
+
+ function leading(a) {
+ var index = a.indexOf(value);
+ return index === 0 || a[index - 1] === ' ';
+ }
+
+ // matches value to preset.name
+ var leading_name = _.filter(searchable, function(a) {
+ return leading(a.name().toLowerCase());
+ }).sort(function(a, b) {
+ var i = a.name().toLowerCase().indexOf(value) - b.name().toLowerCase().indexOf(value);
+ if (i === 0) return a.name().length - b.name().length;
+ else return i;
+ });
+
+ // matches value to preset.terms values
+ var leading_terms = _.filter(searchable, function(a) {
+ return _.some(a.terms() || [], leading);
+ });
+
+ // matches value to preset.tags values
+ var leading_tag_values = _.filter(searchable, function(a) {
+ return _.some(_.without(_.values(a.tags || {}), '*'), leading);
+ });
+
+
+ // finds close matches to value in preset.name
+ var levenstein_name = searchable.map(function(a) {
+ return {
+ preset: a,
+ dist: editDistance(value, a.name().toLowerCase())
+ };
+ }).filter(function(a) {
+ return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3;
+ }).sort(function(a, b) {
+ return a.dist - b.dist;
+ }).map(function(a) {
+ return a.preset;
+ });
+
+ // finds close matches to value in preset.terms
+ var leventstein_terms = _.filter(searchable, function(a) {
+ return _.some(a.terms() || [], function(b) {
+ return editDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
+ });
+ });
+
+ function suggestionName(name) {
+ var nameArray = name.split(' - ');
+ if (nameArray.length > 1) {
+ name = nameArray.slice(0, nameArray.length-1).join(' - ');
+ }
+ return name.toLowerCase();
+ }
+
+ var leading_suggestions = _.filter(suggestions, function(a) {
+ return leading(suggestionName(a.name()));
+ }).sort(function(a, b) {
+ a = suggestionName(a.name());
+ b = suggestionName(b.name());
+ var i = a.indexOf(value) - b.indexOf(value);
+ if (i === 0) return a.length - b.length;
+ else return i;
+ });
+
+ var leven_suggestions = suggestions.map(function(a) {
+ return {
+ preset: a,
+ dist: editDistance(value, suggestionName(a.name()))
+ };
+ }).filter(function(a) {
+ return a.dist + Math.min(value.length - suggestionName(a.preset.name()).length, 0) < 1;
+ }).sort(function(a, b) {
+ return a.dist - b.dist;
+ }).map(function(a) {
+ return a.preset;
+ });
+
+ var other = presets.item(geometry);
+
+ var results = leading_name.concat(
+ leading_terms,
+ leading_tag_values,
+ leading_suggestions.slice(0, maxSuggestionResults+5),
+ levenstein_name,
+ leventstein_terms,
+ leven_suggestions.slice(0, maxSuggestionResults)
+ ).slice(0, maxSearchResults-1);
+
+ return Collection(_.uniq(
+ results.concat(other)
+ ));
+ }
+ };
+
+ return presets;
+ }
+
+ function Category(id, category, all) {
+ category = _.clone(category);
+
+ category.id = id;
+
+ category.members = Collection(category.members.map(function(id) {
+ return all.item(id);
+ }));
+
+ category.matchGeometry = function(geometry) {
+ return category.geometry.indexOf(geometry) >= 0;
+ };
+
+ category.matchScore = function() { return -1; };
+
+ category.name = function() {
+ return t('presets.categories.' + id + '.name', {'default': id});
+ };
+
+ category.terms = function() {
+ return [];
+ };
+
+ return category;
+ }
+
+ function Field(id, field) {
+ field = _.clone(field);
+
+ field.id = id;
+
+ field.matchGeometry = function(geometry) {
+ return !field.geometry || field.geometry === geometry;
+ };
+
+ field.t = function(scope, options) {
+ return t('presets.fields.' + id + '.' + scope, options);
+ };
+
+ field.label = function() {
+ return field.t('label', {'default': id});
+ };
+
+ var placeholder = field.placeholder;
+ field.placeholder = function() {
+ return field.t('placeholder', {'default': placeholder});
+ };
+
+ return field;
+ }
+
+ function Preset(id, preset, fields) {
+ preset = _.clone(preset);
+
+ preset.id = id;
+ preset.fields = (preset.fields || []).map(getFields);
+ preset.geometry = (preset.geometry || []);
+
+ function getFields(f) {
+ return fields[f];
+ }
+
+ preset.matchGeometry = function(geometry) {
+ return preset.geometry.indexOf(geometry) >= 0;
+ };
+
+ var matchScore = preset.matchScore || 1;
+ preset.matchScore = function(entity) {
+ var tags = preset.tags,
+ score = 0;
+
+ for (var t in tags) {
+ if (entity.tags[t] === tags[t]) {
+ score += matchScore;
+ } else if (tags[t] === '*' && t in entity.tags) {
+ score += matchScore / 2;
+ } else {
+ return -1;
+ }
+ }
+
+ return score;
+ };
+
+ preset.t = function(scope, options) {
+ return t('presets.presets.' + id + '.' + scope, options);
+ };
+
+ var name = preset.name;
+ preset.name = function() {
+ if (preset.suggestion) {
+ id = id.split('/');
+ id = id[0] + '/' + id[1];
+ return name + ' - ' + t('presets.presets.' + id + '.name');
+ }
+ return preset.t('name', {'default': name});
+ };
+
+ preset.terms = function() {
+ return preset.t('terms', {'default': ''}).toLowerCase().trim().split(/\s*,+\s*/);
+ };
+
+ preset.isFallback = function() {
+ var tagCount = Object.keys(preset.tags).length;
+ return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
+ };
+
+ preset.reference = function(geometry) {
+ var key = Object.keys(preset.tags)[0],
+ value = preset.tags[key];
+
+ if (geometry === 'relation' && key === 'type') {
+ return { rtype: value };
+ } else if (value === '*') {
+ return { key: key };
+ } else {
+ return { key: key, value: value };
+ }
+ };
+
+ var removeTags = preset.removeTags || preset.tags;
+ preset.removeTags = function(tags, geometry) {
+ tags = _.omit(tags, _.keys(removeTags));
+
+ for (var f in preset.fields) {
+ var field = preset.fields[f];
+ if (field.matchGeometry(geometry) && field.default === tags[field.key]) {
+ delete tags[field.key];
+ }
+ }
+
+ delete tags.area;
+ return tags;
+ };
+
+ var applyTags = preset.addTags || preset.tags;
+ preset.applyTags = function(tags, geometry) {
+ var k;
+
+ tags = _.clone(tags);
+
+ for (k in applyTags) {
+ if (applyTags[k] === '*') {
+ tags[k] = 'yes';
+ } else {
+ tags[k] = applyTags[k];
+ }
+ }
+
+ // Add area=yes if necessary.
+ // This is necessary if the geometry is already an area (e.g. user drew an area) AND any of:
+ // 1. chosen preset could be either an area or a line (`barrier=city_wall`)
+ // 2. chosen preset doesn't have a key in areaKeys (`railway=station`)
+ if (geometry === 'area') {
+ var needsAreaTag = true;
+ if (preset.geometry.indexOf('line') === -1) {
+ for (k in applyTags) {
+ if (k in iD.areaKeys) {
+ needsAreaTag = false;
+ break;
+ }
+ }
+ }
+ if (needsAreaTag) {
+ tags.area = 'yes';
+ }
+ }
+
+ for (var f in preset.fields) {
+ var field = preset.fields[f];
+ if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field.default) {
+ tags[field.key] = field.default;
+ }
+ }
+
+ return tags;
+ };
+
+ return preset;
+ }
+
+ function presets$1() {
+ // an iD.presets.Collection with methods for
+ // loading new data and returning defaults
+
+ var all = Collection([]),
+ defaults = { area: all, line: all, point: all, vertex: all, relation: all },
+ fields = {},
+ universal = [],
+ recent = Collection([]);
+
+ // Index of presets by (geometry, tag key).
+ var index = {
+ point: {},
+ vertex: {},
+ line: {},
+ area: {},
+ relation: {}
+ };
+
+ all.match = function(entity, resolver) {
+ var geometry = entity.geometry(resolver),
+ geometryMatches = index[geometry],
+ best = -1,
+ match;
+
+ for (var k in entity.tags) {
+ var keyMatches = geometryMatches[k];
+ if (!keyMatches) continue;
+
+ for (var i = 0; i < keyMatches.length; i++) {
+ var score = keyMatches[i].matchScore(entity);
+ if (score > best) {
+ best = score;
+ match = keyMatches[i];
+ }
+ }
+ }
+
+ return match || all.item(geometry);
+ };
+
+ // Because of the open nature of tagging, iD will never have a complete
+ // list of tags used in OSM, so we want it to have logic like "assume
+ // that a closed way with an amenity tag is an area, unless the amenity
+ // is one of these specific types". This function computes a structure
+ // that allows testing of such conditions, based on the presets designated
+ // as as supporting (or not supporting) the area geometry.
+ //
+ // The returned object L is a whitelist/blacklist of tags. A closed way
+ // with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
+ // (see `Way#isArea()`). In other words, the keys of L form the whitelist,
+ // and the subkeys form the blacklist.
+ all.areaKeys = function() {
+ var areaKeys = {},
+ ignore = ['barrier', 'highway', 'footway', 'railway', 'type'],
+ presets = _.reject(all.collection, 'suggestion');
+
+ // whitelist
+ presets.forEach(function(d) {
+ for (var key in d.tags) break;
+ if (!key) return;
+ if (ignore.indexOf(key) !== -1) return;
+
+ if (d.geometry.indexOf('area') !== -1) {
+ areaKeys[key] = areaKeys[key] || {};
+ }
+ });
+
+ // blacklist
+ presets.forEach(function(d) {
+ for (var key in d.tags) break;
+ if (!key) return;
+ if (ignore.indexOf(key) !== -1) return;
+
+ var value = d.tags[key];
+ if (d.geometry.indexOf('area') === -1 &&
+ d.geometry.indexOf('line') !== -1 &&
+ key in areaKeys && value !== '*') {
+ areaKeys[key][value] = true;
+ }
+ });
+
+ return areaKeys;
+ };
+
+ all.load = function(d) {
+
+ if (d.fields) {
+ _.forEach(d.fields, function(d, id) {
+ fields[id] = Field(id, d);
+ if (d.universal) universal.push(fields[id]);
+ });
+ }
+
+ if (d.presets) {
+ _.forEach(d.presets, function(d, id) {
+ all.collection.push(Preset(id, d, fields));
+ });
+ }
+
+ if (d.categories) {
+ _.forEach(d.categories, function(d, id) {
+ all.collection.push(Category(id, d, all));
+ });
+ }
+
+ if (d.defaults) {
+ var getItem = _.bind(all.item, all);
+ defaults = {
+ area: Collection(d.defaults.area.map(getItem)),
+ line: Collection(d.defaults.line.map(getItem)),
+ point: Collection(d.defaults.point.map(getItem)),
+ vertex: Collection(d.defaults.vertex.map(getItem)),
+ relation: Collection(d.defaults.relation.map(getItem))
+ };
+ }
+
+ for (var i = 0; i < all.collection.length; i++) {
+ var preset = all.collection[i],
+ geometry = preset.geometry;
+
+ for (var j = 0; j < geometry.length; j++) {
+ var g = index[geometry[j]];
+ for (var k in preset.tags) {
+ (g[k] = g[k] || []).push(preset);
+ }
+ }
+ }
+
+ return all;
+ };
+
+ all.field = function(id) {
+ return fields[id];
+ };
+
+ all.universal = function() {
+ return universal;
+ };
+
+ all.defaults = function(geometry, n) {
+ var rec = recent.matchGeometry(geometry).collection.slice(0, 4),
+ def = _.uniq(rec.concat(defaults[geometry].collection)).slice(0, n - 1);
+ return Collection(_.uniq(rec.concat(def).concat(all.item(geometry))));
+ };
+
+ all.choose = function(preset) {
+ if (!preset.isFallback()) {
+ recent = Collection(_.uniq([preset].concat(recent.collection)));
+ }
+ return all;
+ };
+
+ return all;
+ }
+
+
+
+ var presets = Object.freeze({
+ Category: Category,
+ Collection: Collection,
+ Field: Field,
+ Preset: Preset,
+ presets: presets$1
+ });
+
exports.actions = actions;
exports.geo = geo;
exports.svg = svg;
@@ -12378,7 +12854,11 @@
=======
=======
exports.operations = Operations;
+<<<<<<< HEAD
>>>>>>> 422ffee... external modules for operations
+=======
+ exports.presets = presets;
+>>>>>>> ed34eb3... external modules for presets
exports.util = util;
>>>>>>> 42ce4cf... external modules for util
exports.Connection = Connection;
diff --git a/modules/index.js b/modules/index.js
index 62fb9f382..cf07941f0 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -4,6 +4,7 @@ import * as behavior from './behavior/index';
import * as modes from './modes/index';
import * as util from './util/index';
import * as operations from './operations/index';
+import * as presets from './presets/index';
export { Connection } from './core/connection';
export { Difference } from './core/difference';
@@ -22,5 +23,6 @@ export {
behavior,
modes,
operations,
+ presets,
util
};
diff --git a/modules/presets/collection.js b/modules/presets/collection.js
index e7532fb95..69966c20e 100644
--- a/modules/presets/collection.js
+++ b/modules/presets/collection.js
@@ -1,3 +1,4 @@
+import { editDistance } from '../util/index';
export function Collection(collection) {
var maxSearchResults = 50,
maxSuggestionResults = 10;
@@ -59,7 +60,7 @@ export function Collection(collection) {
var levenstein_name = searchable.map(function(a) {
return {
preset: a,
- dist: iD.util.editDistance(value, a.name().toLowerCase())
+ dist: editDistance(value, a.name().toLowerCase())
};
}).filter(function(a) {
return a.dist + Math.min(value.length - a.preset.name().length, 0) < 3;
@@ -72,7 +73,7 @@ export function Collection(collection) {
// finds close matches to value in preset.terms
var leventstein_terms = _.filter(searchable, function(a) {
return _.some(a.terms() || [], function(b) {
- return iD.util.editDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
+ return editDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
});
});
@@ -97,7 +98,7 @@ export function Collection(collection) {
var leven_suggestions = suggestions.map(function(a) {
return {
preset: a,
- dist: iD.util.editDistance(value, suggestionName(a.name()))
+ dist: editDistance(value, suggestionName(a.name()))
};
}).filter(function(a) {
return a.dist + Math.min(value.length - suggestionName(a.preset.name()).length, 0) < 1;
diff --git a/modules/presets/presets.js b/modules/presets/presets.js
index 4483e7d99..6765e6d29 100644
--- a/modules/presets/presets.js
+++ b/modules/presets/presets.js
@@ -53,7 +53,7 @@ export function presets() {
//
// The returned object L is a whitelist/blacklist of tags. A closed way
// with a tag (k, v) is considered to be an area if `k in L && !(v in L[k])`
- // (see `iD.Way#isArea()`). In other words, the keys of L form the whitelist,
+ // (see `Way#isArea()`). In other words, the keys of L form the whitelist,
// and the subkeys form the blacklist.
all.areaKeys = function() {
var areaKeys = {},
diff --git a/test/index.html b/test/index.html
index 340f59447..e371d79e0 100644
--- a/test/index.html
+++ b/test/index.html
@@ -42,7 +42,6 @@
-