/g, '.');
+ }).reverse();
+ var stringsKey = locale; // US English is the default
+ if (stringsKey.toLowerCase() === 'en-us') stringsKey = 'en';
+ var result = _localeStrings && _localeStrings[scopeId] && _localeStrings[scopeId][stringsKey];
- function resolve(which) {
- var fieldIDs = which === 'fields' ? _this.originalFields : _this.originalMoreFields;
- var resolved = [];
- fieldIDs.forEach(function (fieldID) {
- var match = fieldID.match(/\{(.*)\}/);
+ while (result !== undefined && path.length) {
+ result = result[path.pop()];
+ }
- if (match !== null) {
- // a presetID wrapped in braces {}
- resolved = resolved.concat(inheritFields(match[1], which));
- } else if (allFields[fieldID]) {
- // a normal fieldID
- resolved.push(allFields[fieldID]);
- } else {
- console.log("Cannot resolve \"".concat(fieldID, "\" found in ").concat(_this.id, ".").concat(which)); // eslint-disable-line no-console
- }
- }); // no fields resolved, so use the parent's if possible
+ if (result !== undefined) {
+ if (replacements) {
+ if (_typeof(result) === 'object' && Object.keys(result).length) {
+ // If plural forms are provided, dig one level deeper based on the
+ // first numeric token replacement provided.
+ var number = Object.values(replacements).find(function (value) {
+ return typeof value === 'number';
+ });
- if (!resolved.length) {
- var endIndex = _this.id.lastIndexOf('/');
+ if (number !== undefined) {
+ var rule = pluralRule(number, locale);
- var parentID = endIndex && _this.id.substring(0, endIndex);
+ if (result[rule]) {
+ result = result[rule];
+ } else {
+ // We're pretty sure this should be a plural but no string
+ // could be found for the given rule. Just pick the first
+ // string and hope it makes sense.
+ result = Object.values(result)[0];
+ }
+ }
+ }
- if (parentID) {
- resolved = inheritFields(parentID, which);
- }
- }
+ if (typeof result === 'string') {
+ for (var key in replacements) {
+ var value = replacements[key];
- return utilArrayUniq(resolved); // returns an array of fields to inherit from the given presetID, if found
+ if (typeof value === 'number') {
+ if (value.toLocaleString) {
+ // format numbers for the locale
+ value = value.toLocaleString(locale, {
+ style: 'decimal',
+ useGrouping: true,
+ minimumFractionDigits: 0
+ });
+ } else {
+ value = value.toString();
+ }
+ }
- function inheritFields(presetID, which) {
- var parent = allPresets[presetID];
- if (!parent) return [];
+ var token = "{".concat(key, "}");
+ var regex = new RegExp(token, 'g');
+ result = result.replace(regex, value);
+ }
+ }
+ }
- if (which === 'fields') {
- return parent.fields().filter(shouldInherit);
- } else if (which === 'moreFields') {
- return parent.moreFields();
- } else {
- return [];
+ if (typeof result === 'string') {
+ // found a localized string!
+ return {
+ text: result,
+ locale: locale
+ };
}
- } // Skip `fields` for the keys which define the preset.
- // These are usually `typeCombo` fields like `shop=*`
+ } // no localized string found...
+ // attempt to fallback to a lower-priority language
- function shouldInherit(f) {
- if (f.key && _this.tags[f.key] !== undefined && // inherit anyway if multiple values are allowed or just a checkbox
- f.type !== 'multiCombo' && f.type !== 'semiCombo' && f.type !== 'manyCombo' && f.type !== 'check') return false;
- return true;
+ var index = _localeCodes.indexOf(locale);
+
+ if (index >= 0 && index < _localeCodes.length - 1) {
+ // eventually this will be 'en' or another locale with 100% coverage
+ var fallback = _localeCodes[index + 1];
+ return localizer.tInfo(origStringId, replacements, fallback);
}
- }
- return _this;
- }
+ if (replacements && 'default' in replacements) {
+ // Fallback to a default value if one is specified in `replacements`
+ return {
+ text: replacements["default"],
+ locale: null
+ };
+ }
- var _mainPresetIndex = presetIndex(); // singleton
- // `presetIndex` wraps a `presetCollection`
- // with methods for loading new data and returning defaults
- //
+ var missing = "Missing ".concat(locale, " translation: ").concat(origStringId);
+ if (typeof console !== 'undefined') console.error(missing); // eslint-disable-line
- function presetIndex() {
- var dispatch = dispatch$8('favoritePreset', 'recentsChange');
- var MAXRECENTS = 30; // seed the preset lists with geometry fallbacks
+ return {
+ text: missing,
+ locale: 'en'
+ };
+ };
- var POINT = presetPreset('point', {
- name: 'Point',
- tags: {},
- geometry: ['point', 'vertex'],
- matchScore: 0.1
- });
- var LINE = presetPreset('line', {
- name: 'Line',
- tags: {},
- geometry: ['line'],
- matchScore: 0.1
- });
- var AREA = presetPreset('area', {
- name: 'Area',
- tags: {
- area: 'yes'
- },
- geometry: ['area'],
- matchScore: 0.1
- });
- var RELATION = presetPreset('relation', {
- name: 'Relation',
- tags: {},
- geometry: ['relation'],
- matchScore: 0.1
- });
+ localizer.hasTextForStringId = function (stringId) {
+ return !!localizer.tInfo(stringId, {
+ "default": 'nothing found'
+ }).locale;
+ }; // Returns only the localized text, discarding the locale info
- var _this = presetCollection([POINT, LINE, AREA, RELATION]);
- var _presets = {
- point: POINT,
- line: LINE,
- area: AREA,
- relation: RELATION
- };
- var _defaults = {
- point: presetCollection([POINT]),
- vertex: presetCollection([POINT]),
- line: presetCollection([LINE]),
- area: presetCollection([AREA]),
- relation: presetCollection([RELATION])
- };
- var _fields = {};
- var _categories = {};
- var _universal = [];
- var _addablePresetIDs = null; // Set of preset IDs that the user can add
+ localizer.t = function (stringId, replacements, locale) {
+ return localizer.tInfo(stringId, replacements, locale).text;
+ }; // Returns the localized text wrapped in an HTML element encoding the locale info
- var _recents;
+ /**
+ * @deprecated This method is considered deprecated. Instead, use the direct DOM manipulating
+ * method `t.append`.
+ */
- var _favorites; // Index of presets by (geometry, tag key).
+ localizer.t.html = function (stringId, replacements, locale) {
+ // replacement string might be html unsafe, so we need to escape it except if it is explicitly marked as html code
+ replacements = Object.assign({}, replacements);
- var _geometryIndex = {
- point: {},
- vertex: {},
- line: {},
- area: {},
- relation: {}
- };
+ for (var k in replacements) {
+ if (typeof replacements[k] === 'string') {
+ replacements[k] = escape$4(replacements[k]);
+ }
- var _loadPromise;
+ if (_typeof(replacements[k]) === 'object' && typeof replacements[k].html === 'string') {
+ replacements[k] = replacements[k].html;
+ }
+ }
- _this.ensureLoaded = function () {
- if (_loadPromise) return _loadPromise;
- return _loadPromise = Promise.all([_mainFileFetcher.get('preset_categories'), _mainFileFetcher.get('preset_defaults'), _mainFileFetcher.get('preset_presets'), _mainFileFetcher.get('preset_fields')]).then(function (vals) {
- _this.merge({
- categories: vals[0],
- defaults: vals[1],
- presets: vals[2],
- fields: vals[3]
- });
+ var info = localizer.tInfo(stringId, replacements, locale); // text may be empty or undefined if `replacements.default` is
- osmSetAreaKeys(_this.areaKeys());
- osmSetPointTags(_this.pointTags());
- osmSetVertexTags(_this.vertexTags());
- });
- }; // `merge` accepts an object containing new preset data (all properties optional):
- // {
- // fields: {},
- // presets: {},
- // categories: {},
- // defaults: {},
- // featureCollection: {}
- //}
+ if (info.text) {
+ return "").concat(info.text, "");
+ } else {
+ return '';
+ }
+ }; // Adds localized text wrapped as an HTML span element with locale info to the DOM
- _this.merge = function (d) {
- var newLocationSets = []; // Merge Fields
+ localizer.t.append = function (stringId, replacements, locale) {
+ return function (selection) {
+ var info = localizer.tInfo(stringId, replacements, locale);
+ return selection.append('span').attr('class', 'localized-text').attr('lang', info.locale || 'und').text((replacements && replacements.prefix || '') + info.text + (replacements && replacements.suffix || ''));
+ };
+ };
- if (d.fields) {
- Object.keys(d.fields).forEach(function (fieldID) {
- var f = d.fields[fieldID];
+ localizer.languageName = function (code, options) {
+ if (_languageNames[code]) {
+ // name in locale language
+ // e.g. "German"
+ return _languageNames[code];
+ } // sometimes we only want the local name
- if (f) {
- // add or replace
- f = presetField(fieldID, f);
- if (f.locationSet) newLocationSets.push(f);
- _fields[fieldID] = f;
- } else {
- // remove
- delete _fields[fieldID];
- }
- });
- } // Merge Presets
+ if (options && options.localOnly) return null;
+ var langInfo = _dataLanguages[code];
- if (d.presets) {
- Object.keys(d.presets).forEach(function (presetID) {
- var p = d.presets[presetID];
-
- if (p) {
- // add or replace
- var isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
+ if (langInfo) {
+ if (langInfo.nativeName) {
+ // name in native language
+ // e.g. "Deutsch (de)"
+ return localizer.t('translate.language_and_code', {
+ language: langInfo.nativeName,
+ code: code
+ });
+ } else if (langInfo.base && langInfo.script) {
+ var base = langInfo.base; // the code of the language this is based on
- p = presetPreset(presetID, p, isAddable, _fields, _presets);
- if (p.locationSet) newLocationSets.push(p);
- _presets[presetID] = p;
- } else {
- // remove (but not if it's a fallback)
- var existing = _presets[presetID];
+ if (_languageNames[base]) {
+ // base language name in locale language
+ var scriptCode = langInfo.script;
+ var script = _scriptNames[scriptCode] || scriptCode; // e.g. "Serbian (Cyrillic)"
- if (existing && !existing.isFallback()) {
- delete _presets[presetID];
- }
+ return localizer.t('translate.language_and_code', {
+ language: _languageNames[base],
+ code: script
+ });
+ } else if (_dataLanguages[base] && _dataLanguages[base].nativeName) {
+ // e.g. "ÑÑпÑки (sr-Cyrl)"
+ return localizer.t('translate.language_and_code', {
+ language: _dataLanguages[base].nativeName,
+ code: code
+ });
}
- });
- } // Merge Categories
+ }
+ }
+ return code; // if not found, use the code
+ };
- if (d.categories) {
- Object.keys(d.categories).forEach(function (categoryID) {
- var c = d.categories[categoryID];
+ return localizer;
+ }
- if (c) {
- // add or replace
- c = presetCategory(categoryID, c, _presets);
- if (c.locationSet) newLocationSets.push(c);
- _categories[categoryID] = c;
- } else {
- // remove
- delete _categories[categoryID];
- }
- });
- } // Rebuild _this.collection after changing presets and categories
+ // `presetCollection` is a wrapper around an `Array` of presets `collection`,
+ // and decorated with some extra methods for searching and matching geometry
+ //
+ function presetCollection(collection) {
+ var MAXRESULTS = 50;
+ var _this = {};
+ var _memo = {};
+ _this.collection = collection;
- _this.collection = Object.values(_presets).concat(Object.values(_categories)); // Merge Defaults
+ _this.item = function (id) {
+ if (_memo[id]) return _memo[id];
- if (d.defaults) {
- Object.keys(d.defaults).forEach(function (geometry) {
- var def = d.defaults[geometry];
+ var found = _this.collection.find(function (d) {
+ return d.id === id;
+ });
- if (Array.isArray(def)) {
- // add or replace
- _defaults[geometry] = presetCollection(def.map(function (id) {
- return _presets[id] || _categories[id];
- }).filter(Boolean));
- } else {
- // remove
- delete _defaults[geometry];
- }
- });
- } // Rebuild universal fields array
+ if (found) _memo[id] = found;
+ return found;
+ };
+ _this.index = function (id) {
+ return _this.collection.findIndex(function (d) {
+ return d.id === id;
+ });
+ };
- _universal = Object.values(_fields).filter(function (field) {
- return field.universal;
- }); // Reset all the preset fields - they'll need to be resolved again
+ _this.matchGeometry = function (geometry) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return d.matchGeometry(geometry);
+ }));
+ };
- Object.values(_presets).forEach(function (preset) {
- return preset.resetFields();
- }); // Rebuild geometry index
+ _this.matchAllGeometry = function (geometries) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return d && d.matchAllGeometry(geometries);
+ }));
+ };
- _geometryIndex = {
- point: {},
- vertex: {},
- line: {},
- area: {},
- relation: {}
- };
+ _this.matchAnyGeometry = function (geometries) {
+ return presetCollection(_this.collection.filter(function (d) {
+ return geometries.some(function (geom) {
+ return d.matchGeometry(geom);
+ });
+ }));
+ };
- _this.collection.forEach(function (preset) {
- (preset.geometry || []).forEach(function (geometry) {
- var g = _geometryIndex[geometry];
+ _this.fallback = function (geometry) {
+ var id = geometry;
+ if (id === 'vertex') id = 'point';
+ return _this.item(id);
+ };
- for (var key in preset.tags) {
- (g[key] = g[key] || []).push(preset);
- }
- });
- }); // Merge Custom Features
+ _this.search = function (value, geometry, loc) {
+ if (!value) return _this; // don't remove diacritical characters since we're assuming the user is being intentional
+ value = value.toLowerCase().trim(); // match at name beginning or just after a space (e.g. "office" -> match "Law Office")
- if (d.featureCollection && Array.isArray(d.featureCollection.features)) {
- _mainLocations.mergeCustomGeoJSON(d.featureCollection);
- } // Resolve all locationSet features.
+ function leading(a) {
+ var index = a.indexOf(value);
+ return index === 0 || a[index - 1] === ' ';
+ } // match at name beginning only
- if (newLocationSets.length) {
- _mainLocations.mergeLocationSets(newLocationSets);
+ function leadingStrict(a) {
+ var index = a.indexOf(value);
+ return index === 0;
}
- return _this;
- };
+ function sortPresets(nameProp) {
+ return function sortNames(a, b) {
+ var aCompare = a[nameProp]();
+ var bCompare = b[nameProp](); // priority if search string matches preset name exactly - #4325
- _this.match = function (entity, resolver) {
- return resolver["transient"](entity, 'presetMatch', function () {
- var geometry = entity.geometry(resolver); // Treat entities on addr:interpolation lines as points, not vertices - #3241
+ if (value === aCompare) return -1;
+ if (value === bCompare) return 1; // priority for higher matchScore
- if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
- geometry = 'point';
- }
+ var i = b.originalScore - a.originalScore;
+ if (i !== 0) return i; // priority if search string appears earlier in preset name
- var entityExtent = entity.extent(resolver);
- return _this.matchTags(entity.tags, geometry, entityExtent.center());
- });
- };
+ i = aCompare.indexOf(value) - bCompare.indexOf(value);
+ if (i !== 0) return i; // priority for shorter preset names
- _this.matchTags = function (tags, geometry, loc) {
- var geometryMatches = _geometryIndex[geometry];
- var address;
- var best = -1;
- var match;
- var validLocations;
+ return aCompare.length - bCompare.length;
+ };
+ }
+
+ var pool = _this.collection;
if (Array.isArray(loc)) {
- validLocations = _mainLocations.locationsAt(loc);
+ var validLocations = _mainLocations.locationsAt(loc);
+ pool = pool.filter(function (a) {
+ return !a.locationSetID || validLocations[a.locationSetID];
+ });
}
- for (var k in tags) {
- // If any part of an address is present, allow fallback to "Address" preset - #4353
- if (/^addr:/.test(k) && geometryMatches['addr:*']) {
- address = geometryMatches['addr:*'][0];
- }
+ var searchable = pool.filter(function (a) {
+ return a.searchable !== false && a.suggestion !== true;
+ });
+ var suggestions = pool.filter(function (a) {
+ return a.suggestion === true;
+ }); // matches value to preset.name
- var keyMatches = geometryMatches[k];
- if (!keyMatches) continue;
+ var leadingNames = searchable.filter(function (a) {
+ return leading(a.searchName());
+ }).sort(sortPresets('searchName')); // matches value to preset suggestion name
- for (var i = 0; i < keyMatches.length; i++) {
- var candidate = keyMatches[i]; // discard candidate preset if location is not valid at `loc`
+ var leadingSuggestions = suggestions.filter(function (a) {
+ return leadingStrict(a.searchName());
+ }).sort(sortPresets('searchName'));
+ var leadingNamesStripped = searchable.filter(function (a) {
+ return leading(a.searchNameStripped());
+ }).sort(sortPresets('searchNameStripped'));
+ var leadingSuggestionsStripped = suggestions.filter(function (a) {
+ return leadingStrict(a.searchNameStripped());
+ }).sort(sortPresets('searchNameStripped')); // matches value to preset.terms values
- if (validLocations && candidate.locationSetID) {
- if (!validLocations[candidate.locationSetID]) continue;
- }
+ var leadingTerms = searchable.filter(function (a) {
+ return (a.terms() || []).some(leading);
+ });
+ var leadingSuggestionTerms = suggestions.filter(function (a) {
+ return (a.terms() || []).some(leading);
+ }); // matches value to preset.tags values
- var score = candidate.matchScore(tags);
+ var leadingTagValues = searchable.filter(function (a) {
+ return Object.values(a.tags || {}).filter(function (val) {
+ return val !== '*';
+ }).some(leading);
+ }); // finds close matches to value in preset.name
- if (score > best) {
- best = score;
- match = candidate;
- }
- }
- }
+ var similarName = searchable.map(function (a) {
+ return {
+ preset: a,
+ dist: utilEditDistance(value, a.searchName())
+ };
+ }).filter(function (a) {
+ return a.dist + Math.min(value.length - a.preset.searchName().length, 0) < 3;
+ }).sort(function (a, b) {
+ return a.dist - b.dist;
+ }).map(function (a) {
+ return a.preset;
+ }); // finds close matches to value to preset suggestion name
+
+ var similarSuggestions = suggestions.map(function (a) {
+ return {
+ preset: a,
+ dist: utilEditDistance(value, a.searchName())
+ };
+ }).filter(function (a) {
+ return a.dist + Math.min(value.length - a.preset.searchName().length, 0) < 1;
+ }).sort(function (a, b) {
+ return a.dist - b.dist;
+ }).map(function (a) {
+ return a.preset;
+ }); // finds close matches to value in preset.terms
- if (address && (!match || match.isFallback())) {
- match = address;
+ var similarTerms = searchable.filter(function (a) {
+ return (a.terms() || []).some(function (b) {
+ return utilEditDistance(value, b) + Math.min(value.length - b.length, 0) < 3;
+ });
+ });
+ var results = leadingNames.concat(leadingSuggestions, leadingNamesStripped, leadingSuggestionsStripped, leadingTerms, leadingSuggestionTerms, leadingTagValues, similarName, similarSuggestions, similarTerms).slice(0, MAXRESULTS - 1);
+
+ if (geometry) {
+ if (typeof geometry === 'string') {
+ results.push(_this.fallback(geometry));
+ } else {
+ geometry.forEach(function (geom) {
+ return results.push(_this.fallback(geom));
+ });
+ }
}
- return match || _this.fallback(geometry);
+ return presetCollection(utilArrayUniq(results));
};
- _this.allowsVertex = function (entity, resolver) {
- if (entity.type !== 'node') return false;
- if (Object.keys(entity.tags).length === 0) return true;
- return resolver["transient"](entity, 'vertexMatch', function () {
- // address lines allow vertices to act as standalone points
- if (entity.isOnAddressLine(resolver)) return true;
- var geometries = osmNodeGeometriesForTags(entity.tags);
- if (geometries.vertex) return true;
- if (geometries.point) return false; // allow vertices for unspecified points
+ return _this;
+ }
- return true;
- });
- }; // 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 keeplist/discardlist 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 keeplist,
- // and the subkeys form the discardlist.
+ // `presetCategory` builds a `presetCollection` of member presets,
+ // decorated with some extra methods for searching and matching geometry
+ //
+ function presetCategory(categoryID, category, allPresets) {
+ var _this = Object.assign({}, category); // shallow copy
- _this.areaKeys = function () {
- // The ignore list is for keys that imply lines. (We always add `area=yes` for exceptions)
- var ignore = ['barrier', 'highway', 'footway', 'railway', 'junction', 'type'];
- var areaKeys = {}; // ignore name-suggestion-index and deprecated presets
- var presets = _this.collection.filter(function (p) {
- return !p.suggestion && !p.replacement;
- }); // keeplist
+ var _searchName; // cache
- presets.forEach(function (p) {
- var keys = p.tags && Object.keys(p.tags);
- var key = keys && keys.length && keys[0]; // pick the first tag
+ var _searchNameStripped; // cache
- if (!key) return;
- if (ignore.indexOf(key) !== -1) return;
- if (p.geometry.indexOf('area') !== -1) {
- // probably an area..
- areaKeys[key] = areaKeys[key] || {};
+ _this.id = categoryID;
+ _this.members = presetCollection((category.members || []).map(function (presetID) {
+ return allPresets[presetID];
+ }).filter(Boolean));
+ _this.geometry = _this.members.collection.reduce(function (acc, preset) {
+ for (var i in preset.geometry) {
+ var geometry = preset.geometry[i];
+
+ if (acc.indexOf(geometry) === -1) {
+ acc.push(geometry);
}
- }); // discardlist
+ }
- presets.forEach(function (p) {
- var key;
+ return acc;
+ }, []);
- for (key in p.addTags) {
- // examine all addTags to get a better sense of what can be tagged on lines - #6800
- var value = p.addTags[key];
+ _this.matchGeometry = function (geom) {
+ return _this.geometry.indexOf(geom) >= 0;
+ };
- if (key in areaKeys && // probably an area...
- p.geometry.indexOf('line') !== -1 && // but sometimes a line
- value !== '*') {
- areaKeys[key][value] = true;
- }
- }
+ _this.matchAllGeometry = function (geometries) {
+ return _this.members.collection.some(function (preset) {
+ return preset.matchAllGeometry(geometries);
});
- return areaKeys;
};
- _this.pointTags = function () {
- return _this.collection.reduce(function (pointTags, d) {
- // ignore name-suggestion-index, deprecated, and generic presets
- if (d.suggestion || d.replacement || d.searchable === false) return pointTags; // only care about the primary tag
-
- var keys = d.tags && Object.keys(d.tags);
- var key = keys && keys.length && keys[0]; // pick the first tag
-
- if (!key) return pointTags; // if this can be a point
-
- if (d.geometry.indexOf('point') !== -1) {
- pointTags[key] = pointTags[key] || {};
- pointTags[key][d.tags[key]] = true;
- }
-
- return pointTags;
- }, {});
+ _this.matchScore = function () {
+ return -1;
};
- _this.vertexTags = function () {
- return _this.collection.reduce(function (vertexTags, d) {
- // ignore name-suggestion-index, deprecated, and generic presets
- if (d.suggestion || d.replacement || d.searchable === false) return vertexTags; // only care about the primary tag
-
- var keys = d.tags && Object.keys(d.tags);
- var key = keys && keys.length && keys[0]; // pick the first tag
-
- if (!key) return vertexTags; // if this can be a vertex
-
- if (d.geometry.indexOf('vertex') !== -1) {
- vertexTags[key] = vertexTags[key] || {};
- vertexTags[key][d.tags[key]] = true;
- }
-
- return vertexTags;
- }, {});
+ _this.name = function () {
+ return _t("_tagging.presets.categories.".concat(categoryID, ".name"), {
+ 'default': categoryID
+ });
};
- _this.field = function (id) {
- return _fields[id];
+ _this.nameLabel = function () {
+ return _t.html("_tagging.presets.categories.".concat(categoryID, ".name"), {
+ 'default': categoryID
+ });
};
- _this.universal = function () {
- return _universal;
+ _this.terms = function () {
+ return [];
};
- _this.defaults = function (geometry, n, startWithRecents, loc) {
- var recents = [];
-
- if (startWithRecents) {
- recents = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
+ _this.searchName = function () {
+ if (!_searchName) {
+ _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
}
- var defaults;
+ return _searchName;
+ };
- if (_addablePresetIDs) {
- defaults = Array.from(_addablePresetIDs).map(function (id) {
- var preset = _this.item(id);
+ _this.searchNameStripped = function () {
+ if (!_searchNameStripped) {
+ _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
- if (preset && preset.matchGeometry(geometry)) return preset;
- return null;
- }).filter(Boolean);
- } else {
- defaults = _defaults[geometry].collection.concat(_this.fallback(geometry));
+ if (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
+
+ _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
}
- var result = presetCollection(utilArrayUniq(recents.concat(defaults)).slice(0, n - 1));
+ return _searchNameStripped;
+ };
- if (Array.isArray(loc)) {
- var validLocations = _mainLocations.locationsAt(loc);
- result.collection = result.collection.filter(function (a) {
- return !a.locationSetID || validLocations[a.locationSetID];
- });
- }
+ return _this;
+ }
- return result;
- }; // pass a Set of addable preset ids
+ // `presetField` decorates a given `field` Object
+ // with some extra methods for searching and matching geometry
+ //
+ function presetField(fieldID, field) {
+ var _this = Object.assign({}, field); // shallow copy
- _this.addablePresetIDs = function (val) {
- if (!arguments.length) return _addablePresetIDs; // accept and convert arrays
- if (Array.isArray(val)) val = new Set(val);
- _addablePresetIDs = val;
+ _this.id = fieldID; // for use in classes, element ids, css selectors
- if (_addablePresetIDs) {
- // reset all presets
- _this.collection.forEach(function (p) {
- // categories aren't addable
- if (p.addable) p.addable(_addablePresetIDs.has(p.id));
- });
- } else {
- _this.collection.forEach(function (p) {
- if (p.addable) p.addable(true);
- });
- }
+ _this.safeid = utilSafeClassName(fieldID);
- return _this;
+ _this.matchGeometry = function (geom) {
+ return !_this.geometry || _this.geometry.indexOf(geom) !== -1;
};
- _this.recent = function () {
- return presetCollection(utilArrayUniq(_this.getRecents().map(function (d) {
- return d.preset;
- })));
+ _this.matchAllGeometry = function (geometries) {
+ return !_this.geometry || geometries.every(function (geom) {
+ return _this.geometry.indexOf(geom) !== -1;
+ });
};
- function RibbonItem(preset, source) {
- var item = {};
- item.preset = preset;
- item.source = source;
+ _this.t = function (scope, options) {
+ return _t("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ };
- item.isFavorite = function () {
- return item.source === 'favorite';
- };
+ _this.t.html = function (scope, options) {
+ return _t.html("_tagging.presets.fields.".concat(fieldID, ".").concat(scope), options);
+ };
- item.isRecent = function () {
- return item.source === 'recent';
- };
+ _this.hasTextForStringId = function (scope) {
+ return _mainLocalizer.hasTextForStringId("_tagging.presets.fields.".concat(fieldID, ".").concat(scope));
+ };
- item.matches = function (preset) {
- return item.preset.id === preset.id;
- };
+ _this.title = function () {
+ return _this.overrideLabel || _this.t('label', {
+ 'default': fieldID
+ });
+ };
- item.minified = function () {
- return {
- pID: item.preset.id
- };
- };
+ _this.label = function () {
+ return _this.overrideLabel || _this.t.html('label', {
+ 'default': fieldID
+ });
+ };
- return item;
- }
+ var _placeholder = _this.placeholder;
- function ribbonItemForMinified(d, source) {
- if (d && d.pID) {
- var preset = _this.item(d.pID);
+ _this.placeholder = function () {
+ return _this.t('placeholder', {
+ 'default': _placeholder
+ });
+ };
- if (!preset) return null;
- return RibbonItem(preset, source);
- }
-
- return null;
- }
+ _this.originalTerms = (_this.terms || []).join();
- _this.getGenericRibbonItems = function () {
- return ['point', 'line', 'area'].map(function (id) {
- return RibbonItem(_this.item(id), 'generic');
- });
+ _this.terms = function () {
+ return _this.t('terms', {
+ 'default': _this.originalTerms
+ }).toLowerCase().trim().split(/\s*,+\s*/);
};
- _this.getAddable = function () {
- if (!_addablePresetIDs) return [];
- return _addablePresetIDs.map(function (id) {
- var preset = _this.item(id);
+ _this.increment = _this.type === 'number' ? _this.increment || 1 : undefined;
+ return _this;
+ }
- if (preset) return RibbonItem(preset, 'addable');
- return null;
- }).filter(Boolean);
- };
+ // `presetPreset` decorates a given `preset` Object
+ // with some extra methods for searching and matching geometry
+ //
- function setRecents(items) {
- _recents = items;
- var minifiedItems = items.map(function (d) {
- return d.minified();
- });
- corePreferences('preset_recents', JSON.stringify(minifiedItems));
- dispatch.call('recentsChange');
- }
+ function presetPreset(presetID, preset, addable, allFields, allPresets) {
+ allFields = allFields || {};
+ allPresets = allPresets || {};
- _this.getRecents = function () {
- if (!_recents) {
- // fetch from local storage
- _recents = (JSON.parse(corePreferences('preset_recents')) || []).reduce(function (acc, d) {
- var item = ribbonItemForMinified(d, 'recent');
- if (item && item.preset.addable()) acc.push(item);
- return acc;
- }, []);
- }
+ var _this = Object.assign({}, preset); // shallow copy
- return _recents;
- };
- _this.addRecent = function (preset, besidePreset, after) {
- var recents = _this.getRecents();
+ var _addable = addable || false;
- var beforeItem = _this.recentMatching(besidePreset);
+ var _resolvedFields; // cache
- var toIndex = recents.indexOf(beforeItem);
- if (after) toIndex += 1;
- var newItem = RibbonItem(preset, 'recent');
- recents.splice(toIndex, 0, newItem);
- setRecents(recents);
- };
- _this.removeRecent = function (preset) {
- var item = _this.recentMatching(preset);
+ var _resolvedMoreFields; // cache
- if (item) {
- var items = _this.getRecents();
- items.splice(items.indexOf(item), 1);
- setRecents(items);
- }
- };
+ var _searchName; // cache
- _this.recentMatching = function (preset) {
- var items = _this.getRecents();
- for (var i in items) {
- if (items[i].matches(preset)) {
- return items[i];
- }
- }
+ var _searchNameStripped; // cache
- return null;
+
+ _this.id = presetID;
+ _this.safeid = utilSafeClassName(presetID); // for use in css classes, selectors, element ids
+
+ _this.originalTerms = (_this.terms || []).join();
+ _this.originalName = _this.name || '';
+ _this.originalScore = _this.matchScore || 1;
+ _this.originalReference = _this.reference || {};
+ _this.originalFields = _this.fields || [];
+ _this.originalMoreFields = _this.moreFields || [];
+
+ _this.fields = function () {
+ return _resolvedFields || (_resolvedFields = resolve('fields'));
};
- _this.moveItem = function (items, fromIndex, toIndex) {
- if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= items.length || toIndex >= items.length) return null;
- items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);
- return items;
+ _this.moreFields = function () {
+ return _resolvedMoreFields || (_resolvedMoreFields = resolve('moreFields'));
};
- _this.moveRecent = function (item, beforeItem) {
- var recents = _this.getRecents();
+ _this.resetFields = function () {
+ return _resolvedFields = _resolvedMoreFields = null;
+ };
- var fromIndex = recents.indexOf(item);
- var toIndex = recents.indexOf(beforeItem);
+ _this.tags = _this.tags || {};
+ _this.addTags = _this.addTags || _this.tags;
+ _this.removeTags = _this.removeTags || _this.addTags;
+ _this.geometry = _this.geometry || [];
- var items = _this.moveItem(recents, fromIndex, toIndex);
+ _this.matchGeometry = function (geom) {
+ return _this.geometry.indexOf(geom) >= 0;
+ };
- if (items) setRecents(items);
+ _this.matchAllGeometry = function (geoms) {
+ return geoms.every(_this.matchGeometry);
};
- _this.setMostRecent = function (preset) {
- if (preset.searchable === false) return;
+ _this.matchScore = function (entityTags) {
+ var tags = _this.tags;
+ var seen = {};
+ var score = 0; // match on tags
- var items = _this.getRecents();
+ for (var k in tags) {
+ seen[k] = true;
- var item = _this.recentMatching(preset);
+ if (entityTags[k] === tags[k]) {
+ score += _this.originalScore;
+ } else if (tags[k] === '*' && k in entityTags) {
+ score += _this.originalScore / 2;
+ } else {
+ return -1;
+ }
+ } // boost score for additional matches in addTags - #6802
- if (item) {
- items.splice(items.indexOf(item), 1);
- } else {
- item = RibbonItem(preset, 'recent');
- } // remove the last recent (first in, first out)
+ var addTags = _this.addTags;
- while (items.length >= MAXRECENTS) {
- items.pop();
- } // prepend array
+ for (var _k in addTags) {
+ if (!seen[_k] && entityTags[_k] === addTags[_k]) {
+ score += _this.originalScore;
+ }
+ }
+ return score;
+ };
- items.unshift(item);
- setRecents(items);
+ _this.t = function (scope, options) {
+ var textID = "_tagging.presets.presets.".concat(presetID, ".").concat(scope);
+ return _t(textID, options);
};
- function setFavorites(items) {
- _favorites = items;
- var minifiedItems = items.map(function (d) {
- return d.minified();
+ _this.t.html = function (scope, options) {
+ var textID = "_tagging.presets.presets.".concat(presetID, ".").concat(scope);
+ return _t.html(textID, options);
+ };
+
+ _this.name = function () {
+ return _this.t('name', {
+ 'default': _this.originalName
});
- corePreferences('preset_favorites', JSON.stringify(minifiedItems)); // call update
+ };
- dispatch.call('favoritePreset');
- }
+ _this.nameLabel = function () {
+ return _this.t.html('name', {
+ 'default': _this.originalName
+ });
+ };
- _this.addFavorite = function (preset, besidePreset, after) {
- var favorites = _this.getFavorites();
+ _this.subtitle = function () {
+ if (_this.suggestion) {
+ var path = presetID.split('/');
+ path.pop(); // remove brand name
- var beforeItem = _this.favoriteMatching(besidePreset);
+ return _t('_tagging.presets.presets.' + path.join('/') + '.name');
+ }
- var toIndex = favorites.indexOf(beforeItem);
- if (after) toIndex += 1;
- var newItem = RibbonItem(preset, 'favorite');
- favorites.splice(toIndex, 0, newItem);
- setFavorites(favorites);
+ return null;
};
- _this.toggleFavorite = function (preset) {
- var favs = _this.getFavorites();
+ _this.subtitleLabel = function () {
+ if (_this.suggestion) {
+ var path = presetID.split('/');
+ path.pop(); // remove brand name
- var favorite = _this.favoriteMatching(preset);
+ return _t.html('_tagging.presets.presets.' + path.join('/') + '.name');
+ }
- if (favorite) {
- favs.splice(favs.indexOf(favorite), 1);
- } else {
- // only allow 10 favorites
- if (favs.length === 10) {
- // remove the last favorite (last in, first out)
- favs.pop();
- } // append array
+ return null;
+ };
+ _this.terms = function () {
+ return _this.t('terms', {
+ 'default': _this.originalTerms
+ }).toLowerCase().trim().split(/\s*,+\s*/);
+ };
- favs.push(RibbonItem(preset, 'favorite'));
+ _this.searchName = function () {
+ if (!_searchName) {
+ _searchName = (_this.suggestion ? _this.originalName : _this.name()).toLowerCase();
}
- setFavorites(favs);
+ return _searchName;
};
- _this.removeFavorite = function (preset) {
- var item = _this.favoriteMatching(preset);
+ _this.searchNameStripped = function () {
+ if (!_searchNameStripped) {
+ _searchNameStripped = _this.searchName(); // split combined diacritical characters into their parts
- if (item) {
- var items = _this.getFavorites();
+ if (_searchNameStripped.normalize) _searchNameStripped = _searchNameStripped.normalize('NFD'); // remove diacritics
- items.splice(items.indexOf(item), 1);
- setFavorites(items);
+ _searchNameStripped = _searchNameStripped.replace(/[\u0300-\u036f]/g, '');
}
+
+ return _searchNameStripped;
};
- _this.getFavorites = function () {
- if (!_favorites) {
- // fetch from local storage
- var rawFavorites = JSON.parse(corePreferences('preset_favorites'));
+ _this.isFallback = function () {
+ var tagCount = Object.keys(_this.tags).length;
+ return tagCount === 0 || tagCount === 1 && _this.tags.hasOwnProperty('area');
+ };
- if (!rawFavorites) {
- rawFavorites = [];
- corePreferences('preset_favorites', JSON.stringify(rawFavorites));
- }
+ _this.addable = function (val) {
+ if (!arguments.length) return _addable;
+ _addable = val;
+ return _this;
+ };
- _favorites = rawFavorites.reduce(function (output, d) {
- var item = ribbonItemForMinified(d, 'favorite');
- if (item && item.preset.addable()) output.push(item);
- return output;
- }, []);
- }
+ _this.reference = function () {
+ // Lookup documentation on Wikidata...
+ var qid = _this.tags.wikidata || _this.tags['flag:wikidata'] || _this.tags['brand:wikidata'] || _this.tags['network:wikidata'] || _this.tags['operator:wikidata'];
- return _favorites;
+ if (qid) {
+ return {
+ qid: qid
+ };
+ } // Lookup documentation on OSM Wikibase...
+
+
+ var key = _this.originalReference.key || Object.keys(utilObjectOmit(_this.tags, 'name'))[0];
+ var value = _this.originalReference.value || _this.tags[key];
+
+ if (value === '*') {
+ return {
+ key: key
+ };
+ } else {
+ return {
+ key: key,
+ value: value
+ };
+ }
};
- _this.favoriteMatching = function (preset) {
- var favs = _this.getFavorites();
+ _this.unsetTags = function (tags, geometry, ignoringKeys, skipFieldDefaults) {
+ // allow manually keeping some tags
+ var removeTags = ignoringKeys ? utilObjectOmit(_this.removeTags, ignoringKeys) : _this.removeTags;
+ tags = utilObjectOmit(tags, Object.keys(removeTags));
- for (var index in favs) {
- if (favs[index].matches(preset)) {
- return favs[index];
- }
+ if (geometry && !skipFieldDefaults) {
+ _this.fields().forEach(function (field) {
+ if (field.matchGeometry(geometry) && field.key && field["default"] === tags[field.key]) {
+ delete tags[field.key];
+ }
+ });
}
- return null;
+ delete tags.area;
+ return tags;
};
- return utilRebind(_this, dispatch, 'on');
- }
+ _this.setTags = function (tags, geometry, skipFieldDefaults) {
+ var addTags = _this.addTags;
+ tags = Object.assign({}, tags); // shallow copy
- function utilTagText(entity) {
- var obj = entity && entity.tags || {};
- return Object.keys(obj).map(function (k) {
- return k + '=' + obj[k];
- }).join(', ');
- }
- function utilTotalExtent(array, graph) {
- var extent = geoExtent();
- var val, entity;
+ for (var k in addTags) {
+ if (addTags[k] === '*') {
+ // if this tag is ancillary, don't override an existing value since any value is okay
+ if (_this.tags[k] || !tags[k] || tags[k] === 'no') {
+ tags[k] = 'yes';
+ }
+ } else {
+ tags[k] = addTags[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 osmAreaKeys (`railway=station`)
- for (var i = 0; i < array.length; i++) {
- val = array[i];
- entity = typeof val === 'string' ? graph.hasEntity(val) : val;
- if (entity) {
- extent._extend(entity.extent(graph));
- }
- }
+ if (!addTags.hasOwnProperty('area')) {
+ delete tags.area;
- return extent;
- }
- function utilTagDiff(oldTags, newTags) {
- var tagDiff = [];
- var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
- keys.forEach(function (k) {
- var oldVal = oldTags[k];
- var newVal = newTags[k];
+ if (geometry === 'area') {
+ var needsAreaTag = true;
- if ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
- tagDiff.push({
- type: '-',
- key: k,
- oldVal: oldVal,
- newVal: newVal,
- display: '- ' + k + '=' + oldVal
- });
+ if (_this.geometry.indexOf('line') === -1) {
+ for (var _k2 in addTags) {
+ if (_k2 in osmAreaKeys) {
+ needsAreaTag = false;
+ break;
+ }
+ }
+ }
+
+ if (needsAreaTag) {
+ tags.area = 'yes';
+ }
+ }
}
- if ((newVal || newVal === '') && (oldVal === undefined || newVal !== oldVal)) {
- tagDiff.push({
- type: '+',
- key: k,
- oldVal: oldVal,
- newVal: newVal,
- display: '+ ' + k + '=' + newVal
+ if (geometry && !skipFieldDefaults) {
+ _this.fields().forEach(function (field) {
+ if (field.matchGeometry(geometry) && field.key && !tags[field.key] && field["default"]) {
+ tags[field.key] = field["default"];
+ }
});
}
- });
- return tagDiff;
- }
- function utilEntitySelector(ids) {
- return ids.length ? '.' + ids.join(',.') : 'nothing';
- } // returns an selector to select entity ids for:
- // - entityIDs passed in
- // - shallow descendant entityIDs for any of those entities that are relations
-
- function utilEntityOrMemberSelector(ids, graph) {
- var seen = new Set(ids);
- ids.forEach(collectShallowDescendants);
- return utilEntitySelector(Array.from(seen));
- function collectShallowDescendants(id) {
- var entity = graph.hasEntity(id);
- if (!entity || entity.type !== 'relation') return;
- entity.members.map(function (member) {
- return member.id;
- }).forEach(function (id) {
- seen.add(id);
- });
- }
- } // returns an selector to select entity ids for:
- // - entityIDs passed in
- // - deep descendant entityIDs for any of those entities that are relations
+ return tags;
+ }; // For a preset without fields, use the fields of the parent preset.
+ // Replace {preset} placeholders with the fields of the specified presets.
- function utilEntityOrDeepMemberSelector(ids, graph) {
- return utilEntitySelector(utilEntityAndDeepMemberIDs(ids, graph));
- } // returns an selector to select entity ids for:
- // - entityIDs passed in
- // - deep descendant entityIDs for any of those entities that are relations
- function utilEntityAndDeepMemberIDs(ids, graph) {
- var seen = new Set();
- ids.forEach(collectDeepDescendants);
- return Array.from(seen);
+ function resolve(which) {
+ var fieldIDs = which === 'fields' ? _this.originalFields : _this.originalMoreFields;
+ var resolved = [];
+ fieldIDs.forEach(function (fieldID) {
+ var match = fieldID.match(/\{(.*)\}/);
- function collectDeepDescendants(id) {
- if (seen.has(id)) return;
- seen.add(id);
- var entity = graph.hasEntity(id);
- if (!entity || entity.type !== 'relation') return;
- entity.members.map(function (member) {
- return member.id;
- }).forEach(collectDeepDescendants); // recurse
- }
- } // returns an selector to select entity ids for:
- // - deep descendant entityIDs for any of those entities that are relations
+ if (match !== null) {
+ // a presetID wrapped in braces {}
+ resolved = resolved.concat(inheritFields(match[1], which));
+ } else if (allFields[fieldID]) {
+ // a normal fieldID
+ resolved.push(allFields[fieldID]);
+ } else {
+ console.log("Cannot resolve \"".concat(fieldID, "\" found in ").concat(_this.id, ".").concat(which)); // eslint-disable-line no-console
+ }
+ }); // no fields resolved, so use the parent's if possible
- function utilDeepMemberSelector(ids, graph, skipMultipolgonMembers) {
- var idsSet = new Set(ids);
- var seen = new Set();
- var returners = new Set();
- ids.forEach(collectDeepDescendants);
- return utilEntitySelector(Array.from(returners));
+ if (!resolved.length) {
+ var endIndex = _this.id.lastIndexOf('/');
- function collectDeepDescendants(id) {
- if (seen.has(id)) return;
- seen.add(id);
+ var parentID = endIndex && _this.id.substring(0, endIndex);
- if (!idsSet.has(id)) {
- returners.add(id);
+ if (parentID) {
+ resolved = inheritFields(parentID, which);
+ }
}
- var entity = graph.hasEntity(id);
- if (!entity || entity.type !== 'relation') return;
- if (skipMultipolgonMembers && entity.isMultipolygon()) return;
- entity.members.map(function (member) {
- return member.id;
- }).forEach(collectDeepDescendants); // recurse
- }
- } // Adds or removes highlight styling for the specified entities
+ return utilArrayUniq(resolved); // returns an array of fields to inherit from the given presetID, if found
- function utilHighlightEntities(ids, highlighted, context) {
- context.surface().selectAll(utilEntityOrDeepMemberSelector(ids, context.graph())).classed('highlighted', highlighted);
- } // returns an Array that is the union of:
- // - nodes for any nodeIDs passed in
- // - child nodes of any wayIDs passed in
- // - descendant member and child nodes of relationIDs passed in
+ function inheritFields(presetID, which) {
+ var parent = allPresets[presetID];
+ if (!parent) return [];
- function utilGetAllNodes(ids, graph) {
- var seen = new Set();
- var nodes = new Set();
- ids.forEach(collectNodes);
- return Array.from(nodes);
+ if (which === 'fields') {
+ return parent.fields().filter(shouldInherit);
+ } else if (which === 'moreFields') {
+ return parent.moreFields();
+ } else {
+ return [];
+ }
+ } // Skip `fields` for the keys which define the preset.
+ // These are usually `typeCombo` fields like `shop=*`
- function collectNodes(id) {
- if (seen.has(id)) return;
- seen.add(id);
- var entity = graph.hasEntity(id);
- if (!entity) return;
- if (entity.type === 'node') {
- nodes.add(entity);
- } else if (entity.type === 'way') {
- entity.nodes.forEach(collectNodes);
- } else {
- entity.members.map(function (member) {
- return member.id;
- }).forEach(collectNodes); // recurse
+ function shouldInherit(f) {
+ if (f.key && _this.tags[f.key] !== undefined && // inherit anyway if multiple values are allowed or just a checkbox
+ f.type !== 'multiCombo' && f.type !== 'semiCombo' && f.type !== 'manyCombo' && f.type !== 'check') return false;
+ return true;
}
}
+
+ return _this;
}
- function utilDisplayName(entity) {
- var localizedNameKey = 'name:' + _mainLocalizer.languageCode().toLowerCase();
- var name = entity.tags[localizedNameKey] || entity.tags.name || '';
- if (name) return name;
- var tags = {
- direction: entity.tags.direction,
- from: entity.tags.from,
- network: entity.tags.cycle_network || entity.tags.network,
- ref: entity.tags.ref,
- to: entity.tags.to,
- via: entity.tags.via
- };
- var keyComponents = [];
- if (tags.network) {
- keyComponents.push('network');
- }
+ var _mainPresetIndex = presetIndex(); // singleton
+ // `presetIndex` wraps a `presetCollection`
+ // with methods for loading new data and returning defaults
+ //
- if (tags.ref) {
- keyComponents.push('ref');
- } // Routes may need more disambiguation based on direction or destination
+ function presetIndex() {
+ var dispatch = dispatch$8('favoritePreset', 'recentsChange');
+ var MAXRECENTS = 30; // seed the preset lists with geometry fallbacks
+ var POINT = presetPreset('point', {
+ name: 'Point',
+ tags: {},
+ geometry: ['point', 'vertex'],
+ matchScore: 0.1
+ });
+ var LINE = presetPreset('line', {
+ name: 'Line',
+ tags: {},
+ geometry: ['line'],
+ matchScore: 0.1
+ });
+ var AREA = presetPreset('area', {
+ name: 'Area',
+ tags: {
+ area: 'yes'
+ },
+ geometry: ['area'],
+ matchScore: 0.1
+ });
+ var RELATION = presetPreset('relation', {
+ name: 'Relation',
+ tags: {},
+ geometry: ['relation'],
+ matchScore: 0.1
+ });
- if (entity.tags.route) {
- if (tags.direction) {
- keyComponents.push('direction');
- } else if (tags.from && tags.to) {
- keyComponents.push('from');
- keyComponents.push('to');
+ var _this = presetCollection([POINT, LINE, AREA, RELATION]);
- if (tags.via) {
- keyComponents.push('via');
- }
- }
- }
+ var _presets = {
+ point: POINT,
+ line: LINE,
+ area: AREA,
+ relation: RELATION
+ };
+ var _defaults = {
+ point: presetCollection([POINT]),
+ vertex: presetCollection([POINT]),
+ line: presetCollection([LINE]),
+ area: presetCollection([AREA]),
+ relation: presetCollection([RELATION])
+ };
+ var _fields = {};
+ var _categories = {};
+ var _universal = [];
+ var _addablePresetIDs = null; // Set of preset IDs that the user can add
- if (keyComponents.length) {
- name = _t('inspector.display_name.' + keyComponents.join('_'), tags);
- }
+ var _recents;
- return name;
- }
- function utilDisplayNameForPath(entity) {
- var name = utilDisplayName(entity);
- var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;
+ var _favorites; // Index of presets by (geometry, tag key).
- if (!isFirefox && name && rtlRegex.test(name)) {
- name = fixRTLTextForSvg(name);
- }
- return name;
- }
- function utilDisplayType(id) {
- return {
- n: _t('inspector.node'),
- w: _t('inspector.way'),
- r: _t('inspector.relation')
- }[id.charAt(0)];
- } // `utilDisplayLabel`
- // Returns a string suitable for display
- // By default returns something like name/ref, fallback to preset type, fallback to OSM type
- // "Main Street" or "Tertiary Road"
- // If `verbose=true`, include both preset name and feature name.
- // "Tertiary Road Main Street"
- //
+ var _geometryIndex = {
+ point: {},
+ vertex: {},
+ line: {},
+ area: {},
+ relation: {}
+ };
- function utilDisplayLabel(entity, graphOrGeometry, verbose) {
- var result;
- var displayName = utilDisplayName(entity);
- var preset = typeof graphOrGeometry === 'string' ? _mainPresetIndex.matchTags(entity.tags, graphOrGeometry) : _mainPresetIndex.match(entity, graphOrGeometry);
- var presetName = preset && (preset.suggestion ? preset.subtitle() : preset.name());
+ var _loadPromise;
- if (verbose) {
- result = [presetName, displayName].filter(Boolean).join(' ');
- } else {
- result = displayName || presetName;
- } // Fallback to the OSM type (node/way/relation)
+ _this.ensureLoaded = function () {
+ if (_loadPromise) return _loadPromise;
+ return _loadPromise = Promise.all([_mainFileFetcher.get('preset_categories'), _mainFileFetcher.get('preset_defaults'), _mainFileFetcher.get('preset_presets'), _mainFileFetcher.get('preset_fields')]).then(function (vals) {
+ _this.merge({
+ categories: vals[0],
+ defaults: vals[1],
+ presets: vals[2],
+ fields: vals[3]
+ });
+ osmSetAreaKeys(_this.areaKeys());
+ osmSetPointTags(_this.pointTags());
+ osmSetVertexTags(_this.vertexTags());
+ });
+ }; // `merge` accepts an object containing new preset data (all properties optional):
+ // {
+ // fields: {},
+ // presets: {},
+ // categories: {},
+ // defaults: {},
+ // featureCollection: {}
+ //}
- return result || utilDisplayType(entity.id);
- }
- function utilEntityRoot(entityType) {
- return {
- node: 'n',
- way: 'w',
- relation: 'r'
- }[entityType];
- } // Returns a single object containing the tags of all the given entities.
- // Example:
- // {
- // highway: 'service',
- // service: 'parking_aisle'
- // }
- // +
- // {
- // highway: 'service',
- // service: 'driveway',
- // width: '3'
- // }
- // =
- // {
- // highway: 'service',
- // service: [ 'driveway', 'parking_aisle' ],
- // width: [ '3', undefined ]
- // }
- function utilCombinedTags(entityIDs, graph) {
- var tags = {};
- var tagCounts = {};
- var allKeys = new Set();
- var entities = entityIDs.map(function (entityID) {
- return graph.hasEntity(entityID);
- }).filter(Boolean); // gather the aggregate keys
+ _this.merge = function (d) {
+ var newLocationSets = []; // Merge Fields
- entities.forEach(function (entity) {
- var keys = Object.keys(entity.tags).filter(Boolean);
- keys.forEach(function (key) {
- allKeys.add(key);
- });
- });
- entities.forEach(function (entity) {
- allKeys.forEach(function (key) {
- var value = entity.tags[key]; // purposely allow `undefined`
+ if (d.fields) {
+ Object.keys(d.fields).forEach(function (fieldID) {
+ var f = d.fields[fieldID];
- if (!tags.hasOwnProperty(key)) {
- // first value, set as raw
- tags[key] = value;
- } else {
- if (!Array.isArray(tags[key])) {
- if (tags[key] !== value) {
- // first alternate value, replace single value with array
- tags[key] = [tags[key], value];
- }
+ if (f) {
+ // add or replace
+ f = presetField(fieldID, f);
+ if (f.locationSet) newLocationSets.push(f);
+ _fields[fieldID] = f;
} else {
- // type is array
- if (tags[key].indexOf(value) === -1) {
- // subsequent alternate value, add to array
- tags[key].push(value);
- }
+ // remove
+ delete _fields[fieldID];
}
- }
+ });
+ } // Merge Presets
- var tagHash = key + '=' + value;
- if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
- tagCounts[tagHash] += 1;
- });
- });
- for (var key in tags) {
- if (!Array.isArray(tags[key])) continue; // sort values by frequency then alphabetically
+ if (d.presets) {
+ Object.keys(d.presets).forEach(function (presetID) {
+ var p = d.presets[presetID];
- tags[key] = tags[key].sort(function (val1, val2) {
- var key = key; // capture
+ if (p) {
+ // add or replace
+ var isAddable = !_addablePresetIDs || _addablePresetIDs.has(presetID);
- var count2 = tagCounts[key + '=' + val2];
- var count1 = tagCounts[key + '=' + val1];
+ p = presetPreset(presetID, p, isAddable, _fields, _presets);
+ if (p.locationSet) newLocationSets.push(p);
+ _presets[presetID] = p;
+ } else {
+ // remove (but not if it's a fallback)
+ var existing = _presets[presetID];
- if (count2 !== count1) {
- return count2 - count1;
- }
+ if (existing && !existing.isFallback()) {
+ delete _presets[presetID];
+ }
+ }
+ });
+ } // Merge Categories
- if (val2 && val1) {
- return val1.localeCompare(val2);
- }
- return val1 ? 1 : -1;
- });
- }
+ if (d.categories) {
+ Object.keys(d.categories).forEach(function (categoryID) {
+ var c = d.categories[categoryID];
- return tags;
- }
- function utilStringQs(str) {
- var i = 0; // advance past any leading '?' or '#' characters
+ if (c) {
+ // add or replace
+ c = presetCategory(categoryID, c, _presets);
+ if (c.locationSet) newLocationSets.push(c);
+ _categories[categoryID] = c;
+ } else {
+ // remove
+ delete _categories[categoryID];
+ }
+ });
+ } // Rebuild _this.collection after changing presets and categories
- while (i < str.length && (str[i] === '?' || str[i] === '#')) {
- i++;
- }
- str = str.slice(i);
- return str.split('&').reduce(function (obj, pair) {
- var parts = pair.split('=');
+ _this.collection = Object.values(_presets).concat(Object.values(_categories)); // Merge Defaults
- if (parts.length === 2) {
- obj[parts[0]] = null === parts[1] ? '' : decodeURIComponent(parts[1]);
- }
+ if (d.defaults) {
+ Object.keys(d.defaults).forEach(function (geometry) {
+ var def = d.defaults[geometry];
- return obj;
- }, {});
- }
- function utilQsString(obj, noencode) {
- // encode everything except special characters used in certain hash parameters:
- // "/" in map states, ":", ",", {" and "}" in background
- function softEncode(s) {
- return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
- }
+ if (Array.isArray(def)) {
+ // add or replace
+ _defaults[geometry] = presetCollection(def.map(function (id) {
+ return _presets[id] || _categories[id];
+ }).filter(Boolean));
+ } else {
+ // remove
+ delete _defaults[geometry];
+ }
+ });
+ } // Rebuild universal fields array
- return Object.keys(obj).sort().map(function (key) {
- return encodeURIComponent(key) + '=' + (noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
- }).join('&');
- }
- function utilPrefixDOMProperty(property) {
- var prefixes = ['webkit', 'ms', 'moz', 'o'];
- var i = -1;
- var n = prefixes.length;
- var s = document.body;
- if (property in s) return property;
- property = property.substr(0, 1).toUpperCase() + property.substr(1);
- while (++i < n) {
- if (prefixes[i] + property in s) {
- return prefixes[i] + property;
- }
- }
+ _universal = Object.values(_fields).filter(function (field) {
+ return field.universal;
+ }); // Reset all the preset fields - they'll need to be resolved again
- return false;
- }
- function utilPrefixCSSProperty(property) {
- var prefixes = ['webkit', 'ms', 'Moz', 'O'];
- var i = -1;
- var n = prefixes.length;
- var s = document.body.style;
+ Object.values(_presets).forEach(function (preset) {
+ return preset.resetFields();
+ }); // Rebuild geometry index
- if (property.toLowerCase() in s) {
- return property.toLowerCase();
- }
+ _geometryIndex = {
+ point: {},
+ vertex: {},
+ line: {},
+ area: {},
+ relation: {}
+ };
- while (++i < n) {
- if (prefixes[i] + property in s) {
- return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
- }
- }
+ _this.collection.forEach(function (preset) {
+ (preset.geometry || []).forEach(function (geometry) {
+ var g = _geometryIndex[geometry];
- return false;
- }
- var transformProperty;
- function utilSetTransform(el, x, y, scale) {
- var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
- var translate = utilDetect().opera ? 'translate(' + x + 'px,' + y + 'px)' : 'translate3d(' + x + 'px,' + y + 'px,0)';
- return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
- } // Calculates Levenshtein distance between two strings
- // see: https://en.wikipedia.org/wiki/Levenshtein_distance
- // first converts the strings to lowercase and replaces diacritic marks with ascii equivalents.
+ for (var key in preset.tags) {
+ g[key] = g[key] || {};
+ var value = preset.tags[key];
+ (g[key][value] = g[key][value] || []).push(preset);
+ }
+ });
+ }); // Merge Custom Features
- function utilEditDistance(a, b) {
- a = remove$6(a.toLowerCase());
- b = remove$6(b.toLowerCase());
- if (a.length === 0) return b.length;
- if (b.length === 0) return a.length;
- var matrix = [];
- var i, j;
- for (i = 0; i <= b.length; i++) {
- matrix[i] = [i];
- }
+ if (d.featureCollection && Array.isArray(d.featureCollection.features)) {
+ _mainLocations.mergeCustomGeoJSON(d.featureCollection);
+ } // Resolve all locationSet features.
- for (j = 0; j <= a.length; j++) {
- matrix[0][j] = j;
- }
- for (i = 1; i <= b.length; i++) {
- for (j = 1; j <= a.length; j++) {
- if (b.charAt(i - 1) === a.charAt(j - 1)) {
- matrix[i][j] = matrix[i - 1][j - 1];
- } else {
- matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
- Math.min(matrix[i][j - 1] + 1, // insertion
- matrix[i - 1][j] + 1)); // deletion
- }
+ if (newLocationSets.length) {
+ _mainLocations.mergeLocationSets(newLocationSets);
}
- }
-
- return matrix[b.length][a.length];
- } // a d3.mouse-alike which
- // 1. Only works on HTML elements, not SVG
- // 2. Does not cause style recalculation
- function utilFastMouse(container) {
- var rect = container.getBoundingClientRect();
- var rectLeft = rect.left;
- var rectTop = rect.top;
- var clientLeft = +container.clientLeft;
- var clientTop = +container.clientTop;
- return function (e) {
- return [e.clientX - rectLeft - clientLeft, e.clientY - rectTop - clientTop];
+ return _this;
};
- }
- function utilAsyncMap(inputs, func, callback) {
- var remaining = inputs.length;
- var results = [];
- var errors = [];
- inputs.forEach(function (d, i) {
- func(d, function done(err, data) {
- errors[i] = err;
- results[i] = data;
- remaining--;
- if (!remaining) callback(errors, results);
- });
- });
- } // wraps an index to an interval [0..length-1]
- function utilWrap(index, length) {
- if (index < 0) {
- index += Math.ceil(-index / length) * length;
- }
+ _this.match = function (entity, resolver) {
+ return resolver["transient"](entity, 'presetMatch', function () {
+ var geometry = entity.geometry(resolver); // Treat entities on addr:interpolation lines as points, not vertices - #3241
- return index % length;
- }
- /**
- * a replacement for functor
- *
- * @param {*} value any value
- * @returns {Function} a function that returns that value or the value if it's a function
- */
+ if (geometry === 'vertex' && entity.isOnAddressLine(resolver)) {
+ geometry = 'point';
+ }
- function utilFunctor(value) {
- if (typeof value === 'function') return value;
- return function () {
- return value;
+ var entityExtent = entity.extent(resolver);
+ return _this.matchTags(entity.tags, geometry, entityExtent.center());
+ });
};
- }
- function utilNoAuto(selection) {
- var isText = selection.size() && selection.node().tagName.toLowerCase() === 'textarea';
- return selection // assign 'new-password' even for non-password fields to prevent browsers (Chrome) ignoring 'off'
- .attr('autocomplete', 'new-password').attr('autocorrect', 'off').attr('autocapitalize', 'off').attr('spellcheck', isText ? 'true' : 'false');
- } // https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
- // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
- function utilHashcode(str) {
- var hash = 0;
+ _this.matchTags = function (tags, geometry, loc) {
+ var keyIndex = _geometryIndex[geometry];
+ var bestScore = -1;
+ var bestMatch;
+ var matchCandidates = [];
- if (str.length === 0) {
- return hash;
- }
+ for (var k in tags) {
+ var indexMatches = [];
+ var valueIndex = keyIndex[k];
+ if (!valueIndex) continue;
+ var keyValueMatches = valueIndex[tags[k]];
+ if (keyValueMatches) indexMatches.push.apply(indexMatches, _toConsumableArray(keyValueMatches));
+ var keyStarMatches = valueIndex['*'];
+ if (keyStarMatches) indexMatches.push.apply(indexMatches, _toConsumableArray(keyStarMatches));
+ if (indexMatches.length === 0) continue;
+
+ for (var i = 0; i < indexMatches.length; i++) {
+ var candidate = indexMatches[i];
+ var score = candidate.matchScore(tags);
- for (var i = 0; i < str.length; i++) {
- var _char = str.charCodeAt(i);
+ if (score === -1) {
+ continue;
+ }
- hash = (hash << 5) - hash + _char;
- hash = hash & hash; // Convert to 32bit integer
- }
+ matchCandidates.push({
+ score: score,
+ candidate: candidate
+ });
- return hash;
- } // Returns version of `str` with all runs of special characters replaced by `_`;
- // suitable for HTML ids, classes, selectors, etc.
+ if (score > bestScore) {
+ bestScore = score;
+ bestMatch = candidate;
+ }
+ }
+ }
- function utilSafeClassName(str) {
- return str.toLowerCase().replace(/[^a-z0-9]+/g, '_');
- } // Returns string based on `val` that is highly unlikely to collide with an id
- // used previously or that's present elsewhere in the document. Useful for preventing
- // browser-provided autofills or when embedding iD on pages with unknown elements.
+ if (bestMatch && bestMatch.locationSetID && bestMatch.locationSetID !== '+[Q2]' && Array.isArray(loc)) {
+ var validLocations = _mainLocations.locationsAt(loc);
- function utilUniqueDomId(val) {
- return 'ideditor-' + utilSafeClassName(val.toString()) + '-' + new Date().getTime().toString();
- } // Returns the length of `str` in unicode characters. This can be less than
- // `String.length()` since a single unicode character can be composed of multiple
- // JavaScript UTF-16 code units.
+ if (!validLocations[bestMatch.locationSetID]) {
+ matchCandidates.sort(function (a, b) {
+ return a.score < b.score ? 1 : -1;
+ });
- function utilUnicodeCharsCount(str) {
- // Native ES2015 implementations of `Array.from` split strings into unicode characters
- return Array.from(str).length;
- } // Returns a new string representing `str` cut from its start to `limit` length
- // in unicode characters. Note that this runs the risk of splitting graphemes.
+ for (var _i = 0; _i < matchCandidates.length; _i++) {
+ var candidateScore = matchCandidates[_i];
- function utilUnicodeCharsTruncated(str, limit) {
- return Array.from(str).slice(0, limit).join('');
- } // Variation of d3.json (https://github.com/d3/d3-fetch/blob/master/src/json.js)
+ if (!candidateScore.candidate.locationSetID || validLocations[candidateScore.candidate.locationSetID]) {
+ bestMatch = candidateScore.candidate;
+ bestScore = candidateScore.score;
+ break;
+ }
+ }
+ }
+ } // If any part of an address is present, allow fallback to "Address" preset - #4353
- function utilFetchJson(resourse, init) {
- return fetch(resourse, init).then(function (response) {
- // fetch in PhantomJS tests may return ok=false and status=0 even if it's okay
- if (!response.ok && response.status !== 0 || !response.json) throw new Error(response.status + ' ' + response.statusText);
- if (response.status === 204 || response.status === 205) return;
- return response.json();
- });
- }
- function osmEntity(attrs) {
- // For prototypal inheritance.
- if (this instanceof osmEntity) return; // Create the appropriate subtype.
-
- if (attrs && attrs.type) {
- return osmEntity[attrs.type].apply(this, arguments);
- } else if (attrs && attrs.id) {
- return osmEntity[osmEntity.id.type(attrs.id)].apply(this, arguments);
- } // Initialize a generic Entity (used only in tests).
+ if (!bestMatch || bestMatch.isFallback()) {
+ for (var _k in tags) {
+ if (/^addr:/.test(_k) && keyIndex['addr:*'] && keyIndex['addr:*']['*']) {
+ bestMatch = keyIndex['addr:*']['*'][0];
+ break;
+ }
+ }
+ }
+ return bestMatch || _this.fallback(geometry);
+ };
- return new osmEntity().initialize(arguments);
- }
+ _this.allowsVertex = function (entity, resolver) {
+ if (entity.type !== 'node') return false;
+ if (Object.keys(entity.tags).length === 0) return true;
+ return resolver["transient"](entity, 'vertexMatch', function () {
+ // address lines allow vertices to act as standalone points
+ if (entity.isOnAddressLine(resolver)) return true;
+ var geometries = osmNodeGeometriesForTags(entity.tags);
+ if (geometries.vertex) return true;
+ if (geometries.point) return false; // allow vertices for unspecified points
- osmEntity.id = function (type) {
- return osmEntity.id.fromOSM(type, osmEntity.id.next[type]--);
- };
+ return true;
+ });
+ }; // 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 keeplist/discardlist 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 keeplist,
+ // and the subkeys form the discardlist.
- osmEntity.id.next = {
- changeset: -1,
- node: -1,
- way: -1,
- relation: -1
- };
- osmEntity.id.fromOSM = function (type, id) {
- return type[0] + id;
- };
+ _this.areaKeys = function () {
+ // The ignore list is for keys that imply lines. (We always add `area=yes` for exceptions)
+ var ignore = ['barrier', 'highway', 'footway', 'railway', 'junction', 'type'];
+ var areaKeys = {}; // ignore name-suggestion-index and deprecated presets
- osmEntity.id.toOSM = function (id) {
- return id.slice(1);
- };
+ var presets = _this.collection.filter(function (p) {
+ return !p.suggestion && !p.replacement;
+ }); // keeplist
- osmEntity.id.type = function (id) {
- return {
- 'c': 'changeset',
- 'n': 'node',
- 'w': 'way',
- 'r': 'relation'
- }[id[0]];
- }; // A function suitable for use as the second argument to d3.selection#data().
+ presets.forEach(function (p) {
+ var keys = p.tags && Object.keys(p.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- osmEntity.key = function (entity) {
- return entity.id + 'v' + (entity.v || 0);
- };
+ if (!key) return;
+ if (ignore.indexOf(key) !== -1) return;
- var _deprecatedTagValuesByKey;
+ if (p.geometry.indexOf('area') !== -1) {
+ // probably an area..
+ areaKeys[key] = areaKeys[key] || {};
+ }
+ }); // discardlist
- osmEntity.deprecatedTagValuesByKey = function (dataDeprecated) {
- if (!_deprecatedTagValuesByKey) {
- _deprecatedTagValuesByKey = {};
- dataDeprecated.forEach(function (d) {
- var oldKeys = Object.keys(d.old);
+ presets.forEach(function (p) {
+ var key;
- if (oldKeys.length === 1) {
- var oldKey = oldKeys[0];
- var oldValue = d.old[oldKey];
+ for (key in p.addTags) {
+ // examine all addTags to get a better sense of what can be tagged on lines - #6800
+ var value = p.addTags[key];
- if (oldValue !== '*') {
- if (!_deprecatedTagValuesByKey[oldKey]) {
- _deprecatedTagValuesByKey[oldKey] = [oldValue];
- } else {
- _deprecatedTagValuesByKey[oldKey].push(oldValue);
- }
+ if (key in areaKeys && // probably an area...
+ p.geometry.indexOf('line') !== -1 && // but sometimes a line
+ value !== '*') {
+ areaKeys[key][value] = true;
}
}
});
- }
+ return areaKeys;
+ };
- return _deprecatedTagValuesByKey;
- };
+ _this.pointTags = function () {
+ return _this.collection.reduce(function (pointTags, d) {
+ // ignore name-suggestion-index, deprecated, and generic presets
+ if (d.suggestion || d.replacement || d.searchable === false) return pointTags; // only care about the primary tag
- osmEntity.prototype = {
- tags: {},
- initialize: function initialize(sources) {
- for (var i = 0; i < sources.length; ++i) {
- var source = sources[i];
+ var keys = d.tags && Object.keys(d.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- for (var prop in source) {
- if (Object.prototype.hasOwnProperty.call(source, prop)) {
- if (source[prop] === undefined) {
- delete this[prop];
- } else {
- this[prop] = source[prop];
- }
- }
+ if (!key) return pointTags; // if this can be a point
+
+ if (d.geometry.indexOf('point') !== -1) {
+ pointTags[key] = pointTags[key] || {};
+ pointTags[key][d.tags[key]] = true;
}
- }
- if (!this.id && this.type) {
- this.id = osmEntity.id(this.type);
- }
+ return pointTags;
+ }, {});
+ };
- if (!this.hasOwnProperty('visible')) {
- this.visible = true;
- }
+ _this.vertexTags = function () {
+ return _this.collection.reduce(function (vertexTags, d) {
+ // ignore name-suggestion-index, deprecated, and generic presets
+ if (d.suggestion || d.replacement || d.searchable === false) return vertexTags; // only care about the primary tag
- if (debug) {
- Object.freeze(this);
- Object.freeze(this.tags);
- if (this.loc) Object.freeze(this.loc);
- if (this.nodes) Object.freeze(this.nodes);
- if (this.members) Object.freeze(this.members);
- }
+ var keys = d.tags && Object.keys(d.tags);
+ var key = keys && keys.length && keys[0]; // pick the first tag
- return this;
- },
- copy: function copy(resolver, copies) {
- if (copies[this.id]) return copies[this.id];
- var copy = osmEntity(this, {
- id: undefined,
- user: undefined,
- version: undefined
- });
- copies[this.id] = copy;
- return copy;
- },
- osmId: function osmId() {
- return osmEntity.id.toOSM(this.id);
- },
- isNew: function isNew() {
- return this.osmId() < 0;
- },
- update: function update(attrs) {
- return osmEntity(this, attrs, {
- v: 1 + (this.v || 0)
- });
- },
- mergeTags: function mergeTags(tags) {
- var merged = Object.assign({}, this.tags); // shallow copy
+ if (!key) return vertexTags; // if this can be a vertex
- var changed = false;
+ if (d.geometry.indexOf('vertex') !== -1) {
+ vertexTags[key] = vertexTags[key] || {};
+ vertexTags[key][d.tags[key]] = true;
+ }
- for (var k in tags) {
- var t1 = merged[k];
- var t2 = tags[k];
+ return vertexTags;
+ }, {});
+ };
- if (!t1) {
- changed = true;
- merged[k] = t2;
- } else if (t1 !== t2) {
- changed = true;
- merged[k] = utilUnicodeCharsTruncated(utilArrayUnion(t1.split(/;\s*/), t2.split(/;\s*/)).join(';'), 255 // avoid exceeding character limit; see also services/osm.js -> maxCharsForTagValue()
- );
- }
- }
+ _this.field = function (id) {
+ return _fields[id];
+ };
- return changed ? this.update({
- tags: merged
- }) : this;
- },
- intersects: function intersects(extent, resolver) {
- return this.extent(resolver).intersects(extent);
- },
- hasNonGeometryTags: function hasNonGeometryTags() {
- return Object.keys(this.tags).some(function (k) {
- return k !== 'area';
- });
- },
- hasParentRelations: function hasParentRelations(resolver) {
- return resolver.parentRelations(this).length > 0;
- },
- hasInterestingTags: function hasInterestingTags() {
- return Object.keys(this.tags).some(osmIsInterestingTag);
- },
- isHighwayIntersection: function isHighwayIntersection() {
- return false;
- },
- isDegenerate: function isDegenerate() {
- return true;
- },
- deprecatedTags: function deprecatedTags(dataDeprecated) {
- var tags = this.tags; // if there are no tags, none can be deprecated
+ _this.universal = function () {
+ return _universal;
+ };
- if (Object.keys(tags).length === 0) return [];
- var deprecated = [];
- dataDeprecated.forEach(function (d) {
- var oldKeys = Object.keys(d.old);
+ _this.defaults = function (geometry, n, startWithRecents, loc) {
+ var recents = [];
- if (d.replace) {
- var hasExistingValues = Object.keys(d.replace).some(function (replaceKey) {
- if (!tags[replaceKey] || d.old[replaceKey]) return false;
- var replaceValue = d.replace[replaceKey];
- if (replaceValue === '*') return false;
- if (replaceValue === tags[replaceKey]) return false;
- return true;
- }); // don't flag deprecated tags if the upgrade path would overwrite existing data - #7843
+ if (startWithRecents) {
+ recents = _this.recent().matchGeometry(geometry).collection.slice(0, 4);
+ }
- if (hasExistingValues) return;
- }
+ var defaults;
- var matchesDeprecatedTags = oldKeys.every(function (oldKey) {
- if (!tags[oldKey]) return false;
- if (d.old[oldKey] === '*') return true;
- if (d.old[oldKey] === tags[oldKey]) return true;
- var vals = tags[oldKey].split(';').filter(Boolean);
+ if (_addablePresetIDs) {
+ defaults = Array.from(_addablePresetIDs).map(function (id) {
+ var preset = _this.item(id);
- if (vals.length === 0) {
- return false;
- } else if (vals.length > 1) {
- return vals.indexOf(d.old[oldKey]) !== -1;
- } else {
- if (tags[oldKey] === d.old[oldKey]) {
- if (d.replace && d.old[oldKey] === d.replace[oldKey]) {
- var replaceKeys = Object.keys(d.replace);
- return !replaceKeys.every(function (replaceKey) {
- return tags[replaceKey] === d.replace[replaceKey];
- });
- } else {
- return true;
- }
- }
- }
+ if (preset && preset.matchGeometry(geometry)) return preset;
+ return null;
+ }).filter(Boolean);
+ } else {
+ defaults = _defaults[geometry].collection.concat(_this.fallback(geometry));
+ }
- return false;
+ var result = presetCollection(utilArrayUniq(recents.concat(defaults)).slice(0, n - 1));
+
+ if (Array.isArray(loc)) {
+ var validLocations = _mainLocations.locationsAt(loc);
+ result.collection = result.collection.filter(function (a) {
+ return !a.locationSetID || validLocations[a.locationSetID];
});
+ }
- if (matchesDeprecatedTags) {
- deprecated.push(d);
- }
- });
- return deprecated;
- }
- };
+ return result;
+ }; // pass a Set of addable preset ids
- function osmLanes(entity) {
- if (entity.type !== 'way') return null;
- if (!entity.tags.highway) return null;
- var tags = entity.tags;
- var isOneWay = entity.isOneWay();
- var laneCount = getLaneCount(tags, isOneWay);
- var maxspeed = parseMaxspeed(tags);
- var laneDirections = parseLaneDirections(tags, isOneWay, laneCount);
- var forward = laneDirections.forward;
- var backward = laneDirections.backward;
- var bothways = laneDirections.bothways; // parse the piped string 'x|y|z' format
- var turnLanes = {};
- turnLanes.unspecified = parseTurnLanes(tags['turn:lanes']);
- turnLanes.forward = parseTurnLanes(tags['turn:lanes:forward']);
- turnLanes.backward = parseTurnLanes(tags['turn:lanes:backward']);
- var maxspeedLanes = {};
- maxspeedLanes.unspecified = parseMaxspeedLanes(tags['maxspeed:lanes'], maxspeed);
- maxspeedLanes.forward = parseMaxspeedLanes(tags['maxspeed:lanes:forward'], maxspeed);
- maxspeedLanes.backward = parseMaxspeedLanes(tags['maxspeed:lanes:backward'], maxspeed);
- var psvLanes = {};
- psvLanes.unspecified = parseMiscLanes(tags['psv:lanes']);
- psvLanes.forward = parseMiscLanes(tags['psv:lanes:forward']);
- psvLanes.backward = parseMiscLanes(tags['psv:lanes:backward']);
- var busLanes = {};
- busLanes.unspecified = parseMiscLanes(tags['bus:lanes']);
- busLanes.forward = parseMiscLanes(tags['bus:lanes:forward']);
- busLanes.backward = parseMiscLanes(tags['bus:lanes:backward']);
- var taxiLanes = {};
- taxiLanes.unspecified = parseMiscLanes(tags['taxi:lanes']);
- taxiLanes.forward = parseMiscLanes(tags['taxi:lanes:forward']);
- taxiLanes.backward = parseMiscLanes(tags['taxi:lanes:backward']);
- var hovLanes = {};
- hovLanes.unspecified = parseMiscLanes(tags['hov:lanes']);
- hovLanes.forward = parseMiscLanes(tags['hov:lanes:forward']);
- hovLanes.backward = parseMiscLanes(tags['hov:lanes:backward']);
- var hgvLanes = {};
- hgvLanes.unspecified = parseMiscLanes(tags['hgv:lanes']);
- hgvLanes.forward = parseMiscLanes(tags['hgv:lanes:forward']);
- hgvLanes.backward = parseMiscLanes(tags['hgv:lanes:backward']);
- var bicyclewayLanes = {};
- bicyclewayLanes.unspecified = parseBicycleWay(tags['bicycleway:lanes']);
- bicyclewayLanes.forward = parseBicycleWay(tags['bicycleway:lanes:forward']);
- bicyclewayLanes.backward = parseBicycleWay(tags['bicycleway:lanes:backward']);
- var lanesObj = {
- forward: [],
- backward: [],
- unspecified: []
- }; // map forward/backward/unspecified of each lane type to lanesObj
+ _this.addablePresetIDs = function (val) {
+ if (!arguments.length) return _addablePresetIDs; // accept and convert arrays
- mapToLanesObj(lanesObj, turnLanes, 'turnLane');
- mapToLanesObj(lanesObj, maxspeedLanes, 'maxspeed');
- mapToLanesObj(lanesObj, psvLanes, 'psv');
- mapToLanesObj(lanesObj, busLanes, 'bus');
- mapToLanesObj(lanesObj, taxiLanes, 'taxi');
- mapToLanesObj(lanesObj, hovLanes, 'hov');
- mapToLanesObj(lanesObj, hgvLanes, 'hgv');
- mapToLanesObj(lanesObj, bicyclewayLanes, 'bicycleway');
- return {
- metadata: {
- count: laneCount,
- oneway: isOneWay,
- forward: forward,
- backward: backward,
- bothways: bothways,
- turnLanes: turnLanes,
- maxspeed: maxspeed,
- maxspeedLanes: maxspeedLanes,
- psvLanes: psvLanes,
- busLanes: busLanes,
- taxiLanes: taxiLanes,
- hovLanes: hovLanes,
- hgvLanes: hgvLanes,
- bicyclewayLanes: bicyclewayLanes
- },
- lanes: lanesObj
- };
- }
+ if (Array.isArray(val)) val = new Set(val);
+ _addablePresetIDs = val;
- function getLaneCount(tags, isOneWay) {
- var count;
+ if (_addablePresetIDs) {
+ // reset all presets
+ _this.collection.forEach(function (p) {
+ // categories aren't addable
+ if (p.addable) p.addable(_addablePresetIDs.has(p.id));
+ });
+ } else {
+ _this.collection.forEach(function (p) {
+ if (p.addable) p.addable(true);
+ });
+ }
- if (tags.lanes) {
- count = parseInt(tags.lanes, 10);
+ return _this;
+ };
- if (count > 0) {
- return count;
- }
- }
+ _this.recent = function () {
+ return presetCollection(utilArrayUniq(_this.getRecents().map(function (d) {
+ return d.preset;
+ })));
+ };
- switch (tags.highway) {
- case 'trunk':
- case 'motorway':
- count = isOneWay ? 2 : 4;
- break;
+ function RibbonItem(preset, source) {
+ var item = {};
+ item.preset = preset;
+ item.source = source;
- default:
- count = isOneWay ? 1 : 2;
- break;
- }
+ item.isFavorite = function () {
+ return item.source === 'favorite';
+ };
- return count;
- }
+ item.isRecent = function () {
+ return item.source === 'recent';
+ };
- function parseMaxspeed(tags) {
- var maxspeed = tags.maxspeed;
- if (!maxspeed) return;
- var maxspeedRegex = /^([0-9][\.0-9]+?)(?:[ ]?(?:km\/h|kmh|kph|mph|knots))?$/;
- if (!maxspeedRegex.test(maxspeed)) return;
- return parseInt(maxspeed, 10);
- }
+ item.matches = function (preset) {
+ return item.preset.id === preset.id;
+ };
- function parseLaneDirections(tags, isOneWay, laneCount) {
- var forward = parseInt(tags['lanes:forward'], 10);
- var backward = parseInt(tags['lanes:backward'], 10);
- var bothways = parseInt(tags['lanes:both_ways'], 10) > 0 ? 1 : 0;
+ item.minified = function () {
+ return {
+ pID: item.preset.id
+ };
+ };
- if (parseInt(tags.oneway, 10) === -1) {
- forward = 0;
- bothways = 0;
- backward = laneCount;
- } else if (isOneWay) {
- forward = laneCount;
- bothways = 0;
- backward = 0;
- } else if (isNaN(forward) && isNaN(backward)) {
- backward = Math.floor((laneCount - bothways) / 2);
- forward = laneCount - bothways - backward;
- } else if (isNaN(forward)) {
- if (backward > laneCount - bothways) {
- backward = laneCount - bothways;
- }
+ return item;
+ }
- forward = laneCount - bothways - backward;
- } else if (isNaN(backward)) {
- if (forward > laneCount - bothways) {
- forward = laneCount - bothways;
+ function ribbonItemForMinified(d, source) {
+ if (d && d.pID) {
+ var preset = _this.item(d.pID);
+
+ if (!preset) return null;
+ return RibbonItem(preset, source);
}
- backward = laneCount - bothways - forward;
+ return null;
}
- return {
- forward: forward,
- backward: backward,
- bothways: bothways
- };
- }
-
- function parseTurnLanes(tag) {
- if (!tag) return;
- var validValues = ['left', 'slight_left', 'sharp_left', 'through', 'right', 'slight_right', 'sharp_right', 'reverse', 'merge_to_left', 'merge_to_right', 'none'];
- return tag.split('|').map(function (s) {
- if (s === '') s = 'none';
- return s.split(';').map(function (d) {
- return validValues.indexOf(d) === -1 ? 'unknown' : d;
+ _this.getGenericRibbonItems = function () {
+ return ['point', 'line', 'area'].map(function (id) {
+ return RibbonItem(_this.item(id), 'generic');
});
- });
- }
+ };
- function parseMaxspeedLanes(tag, maxspeed) {
- if (!tag) return;
- return tag.split('|').map(function (s) {
- if (s === 'none') return s;
- var m = parseInt(s, 10);
- if (s === '' || m === maxspeed) return null;
- return isNaN(m) ? 'unknown' : m;
- });
- }
+ _this.getAddable = function () {
+ if (!_addablePresetIDs) return [];
+ return _addablePresetIDs.map(function (id) {
+ var preset = _this.item(id);
- function parseMiscLanes(tag) {
- if (!tag) return;
- var validValues = ['yes', 'no', 'designated'];
- return tag.split('|').map(function (s) {
- if (s === '') s = 'no';
- return validValues.indexOf(s) === -1 ? 'unknown' : s;
- });
- }
+ if (preset) return RibbonItem(preset, 'addable');
+ return null;
+ }).filter(Boolean);
+ };
- function parseBicycleWay(tag) {
- if (!tag) return;
- var validValues = ['yes', 'no', 'designated', 'lane'];
- return tag.split('|').map(function (s) {
- if (s === '') s = 'no';
- return validValues.indexOf(s) === -1 ? 'unknown' : s;
- });
- }
-
- function mapToLanesObj(lanesObj, data, key) {
- if (data.forward) {
- data.forward.forEach(function (l, i) {
- if (!lanesObj.forward[i]) lanesObj.forward[i] = {};
- lanesObj.forward[i][key] = l;
- });
- }
-
- if (data.backward) {
- data.backward.forEach(function (l, i) {
- if (!lanesObj.backward[i]) lanesObj.backward[i] = {};
- lanesObj.backward[i][key] = l;
+ function setRecents(items) {
+ _recents = items;
+ var minifiedItems = items.map(function (d) {
+ return d.minified();
});
+ corePreferences('preset_recents', JSON.stringify(minifiedItems));
+ dispatch.call('recentsChange');
}
- if (data.unspecified) {
- data.unspecified.forEach(function (l, i) {
- if (!lanesObj.unspecified[i]) lanesObj.unspecified[i] = {};
- lanesObj.unspecified[i][key] = l;
- });
- }
- }
+ _this.getRecents = function () {
+ if (!_recents) {
+ // fetch from local storage
+ _recents = (JSON.parse(corePreferences('preset_recents')) || []).reduce(function (acc, d) {
+ var item = ribbonItemForMinified(d, 'recent');
+ if (item && item.preset.addable()) acc.push(item);
+ return acc;
+ }, []);
+ }
- function osmWay() {
- if (!(this instanceof osmWay)) {
- return new osmWay().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
- }
- }
- osmEntity.way = osmWay;
- osmWay.prototype = Object.create(osmEntity.prototype);
- Object.assign(osmWay.prototype, {
- type: 'way',
- nodes: [],
- copy: function copy(resolver, copies) {
- if (copies[this.id]) return copies[this.id];
- var copy = osmEntity.prototype.copy.call(this, resolver, copies);
- var nodes = this.nodes.map(function (id) {
- return resolver.entity(id).copy(resolver, copies).id;
- });
- copy = copy.update({
- nodes: nodes
- });
- copies[this.id] = copy;
- return copy;
- },
- extent: function extent(resolver) {
- return resolver["transient"](this, 'extent', function () {
- var extent = geoExtent();
+ return _recents;
+ };
- for (var i = 0; i < this.nodes.length; i++) {
- var node = resolver.hasEntity(this.nodes[i]);
+ _this.addRecent = function (preset, besidePreset, after) {
+ var recents = _this.getRecents();
- if (node) {
- extent._extend(node.extent());
- }
- }
+ var beforeItem = _this.recentMatching(besidePreset);
- return extent;
- });
- },
- first: function first() {
- return this.nodes[0];
- },
- last: function last() {
- return this.nodes[this.nodes.length - 1];
- },
- contains: function contains(node) {
- return this.nodes.indexOf(node) >= 0;
- },
- affix: function affix(node) {
- if (this.nodes[0] === node) return 'prefix';
- if (this.nodes[this.nodes.length - 1] === node) return 'suffix';
- },
- layer: function layer() {
- // explicit layer tag, clamp between -10, 10..
- if (isFinite(this.tags.layer)) {
- return Math.max(-10, Math.min(+this.tags.layer, 10));
- } // implied layer tag..
+ var toIndex = recents.indexOf(beforeItem);
+ if (after) toIndex += 1;
+ var newItem = RibbonItem(preset, 'recent');
+ recents.splice(toIndex, 0, newItem);
+ setRecents(recents);
+ };
+ _this.removeRecent = function (preset) {
+ var item = _this.recentMatching(preset);
- if (this.tags.covered === 'yes') return -1;
- if (this.tags.location === 'overground') return 1;
- if (this.tags.location === 'underground') return -1;
- if (this.tags.location === 'underwater') return -10;
- if (this.tags.power === 'line') return 10;
- if (this.tags.power === 'minor_line') return 10;
- if (this.tags.aerialway) return 10;
- if (this.tags.bridge) return 1;
- if (this.tags.cutting) return -1;
- if (this.tags.tunnel) return -1;
- if (this.tags.waterway) return -1;
- if (this.tags.man_made === 'pipeline') return -10;
- if (this.tags.boundary) return -10;
- return 0;
- },
- // the approximate width of the line based on its tags except its `width` tag
- impliedLineWidthMeters: function impliedLineWidthMeters() {
- var averageWidths = {
- highway: {
- // width is for single lane
- motorway: 5,
- motorway_link: 5,
- trunk: 4.5,
- trunk_link: 4.5,
- primary: 4,
- secondary: 4,
- tertiary: 4,
- primary_link: 4,
- secondary_link: 4,
- tertiary_link: 4,
- unclassified: 4,
- road: 4,
- living_street: 4,
- bus_guideway: 4,
- pedestrian: 4,
- residential: 3.5,
- service: 3.5,
- track: 3,
- cycleway: 2.5,
- bridleway: 2,
- corridor: 2,
- steps: 2,
- path: 1.5,
- footway: 1.5
- },
- railway: {
- // width includes ties and rail bed, not just track gauge
- rail: 2.5,
- light_rail: 2.5,
- tram: 2.5,
- subway: 2.5,
- monorail: 2.5,
- funicular: 2.5,
- disused: 2.5,
- preserved: 2.5,
- miniature: 1.5,
- narrow_gauge: 1.5
- },
- waterway: {
- river: 50,
- canal: 25,
- stream: 5,
- tidal_channel: 5,
- fish_pass: 2.5,
- drain: 2.5,
- ditch: 1.5
- }
- };
+ if (item) {
+ var items = _this.getRecents();
- for (var key in averageWidths) {
- if (this.tags[key] && averageWidths[key][this.tags[key]]) {
- var width = averageWidths[key][this.tags[key]];
+ items.splice(items.indexOf(item), 1);
+ setRecents(items);
+ }
+ };
- if (key === 'highway') {
- var laneCount = this.tags.lanes && parseInt(this.tags.lanes, 10);
- if (!laneCount) laneCount = this.isOneWay() ? 1 : 2;
- return width * laneCount;
- }
+ _this.recentMatching = function (preset) {
+ var items = _this.getRecents();
- return width;
+ for (var i in items) {
+ if (items[i].matches(preset)) {
+ return items[i];
}
}
return null;
- },
- isOneWay: function isOneWay() {
- // explicit oneway tag..
- var values = {
- 'yes': true,
- '1': true,
- '-1': true,
- 'reversible': true,
- 'alternating': true,
- 'no': false,
- '0': false
- };
-
- if (values[this.tags.oneway] !== undefined) {
- return values[this.tags.oneway];
- } // implied oneway tag..
-
-
- for (var key in this.tags) {
- if (key in osmOneWayTags && this.tags[key] in osmOneWayTags[key]) {
- return true;
- }
- }
+ };
- return false;
- },
- // Some identifier for tag that implies that this way is "sided",
- // i.e. the right side is the 'inside' (e.g. the right side of a
- // natural=cliff is lower).
- sidednessIdentifier: function sidednessIdentifier() {
- for (var key in this.tags) {
- var value = this.tags[key];
+ _this.moveItem = function (items, fromIndex, toIndex) {
+ if (fromIndex === toIndex || fromIndex < 0 || toIndex < 0 || fromIndex >= items.length || toIndex >= items.length) return null;
+ items.splice(toIndex, 0, items.splice(fromIndex, 1)[0]);
+ return items;
+ };
- if (key in osmRightSideIsInsideTags && value in osmRightSideIsInsideTags[key]) {
- if (osmRightSideIsInsideTags[key][value] === true) {
- return key;
- } else {
- // if the map's value is something other than a
- // literal true, we should use it so we can
- // special case some keys (e.g. natural=coastline
- // is handled differently to other naturals).
- return osmRightSideIsInsideTags[key][value];
- }
- }
- }
+ _this.moveRecent = function (item, beforeItem) {
+ var recents = _this.getRecents();
- return null;
- },
- isSided: function isSided() {
- if (this.tags.two_sided === 'yes') {
- return false;
- }
+ var fromIndex = recents.indexOf(item);
+ var toIndex = recents.indexOf(beforeItem);
- return this.sidednessIdentifier() !== null;
- },
- lanes: function lanes() {
- return osmLanes(this);
- },
- isClosed: function isClosed() {
- return this.nodes.length > 1 && this.first() === this.last();
- },
- isConvex: function isConvex(resolver) {
- if (!this.isClosed() || this.isDegenerate()) return null;
- var nodes = utilArrayUniq(resolver.childNodes(this));
- var coords = nodes.map(function (n) {
- return n.loc;
- });
- var curr = 0;
- var prev = 0;
+ var items = _this.moveItem(recents, fromIndex, toIndex);
- for (var i = 0; i < coords.length; i++) {
- var o = coords[(i + 1) % coords.length];
- var a = coords[i];
- var b = coords[(i + 2) % coords.length];
- var res = geoVecCross(a, b, o);
- curr = res > 0 ? 1 : res < 0 ? -1 : 0;
+ if (items) setRecents(items);
+ };
- if (curr === 0) {
- continue;
- } else if (prev && curr !== prev) {
- return false;
- }
+ _this.setMostRecent = function (preset) {
+ if (preset.searchable === false) return;
- prev = curr;
- }
+ var items = _this.getRecents();
- return true;
- },
- // returns an object with the tag that implies this is an area, if any
- tagSuggestingArea: function tagSuggestingArea() {
- return osmTagSuggestingArea(this.tags);
- },
- isArea: function isArea() {
- if (this.tags.area === 'yes') return true;
- if (!this.isClosed() || this.tags.area === 'no') return false;
- return this.tagSuggestingArea() !== null;
- },
- isDegenerate: function isDegenerate() {
- return new Set(this.nodes).size < (this.isArea() ? 3 : 2);
- },
- areAdjacent: function areAdjacent(n1, n2) {
- for (var i = 0; i < this.nodes.length; i++) {
- if (this.nodes[i] === n1) {
- if (this.nodes[i - 1] === n2) return true;
- if (this.nodes[i + 1] === n2) return true;
- }
- }
+ var item = _this.recentMatching(preset);
- return false;
- },
- geometry: function geometry(graph) {
- return graph["transient"](this, 'geometry', function () {
- return this.isArea() ? 'area' : 'line';
- });
- },
- // returns an array of objects representing the segments between the nodes in this way
- segments: function segments(graph) {
- function segmentExtent(graph) {
- var n1 = graph.hasEntity(this.nodes[0]);
- var n2 = graph.hasEntity(this.nodes[1]);
- return n1 && n2 && geoExtent([[Math.min(n1.loc[0], n2.loc[0]), Math.min(n1.loc[1], n2.loc[1])], [Math.max(n1.loc[0], n2.loc[0]), Math.max(n1.loc[1], n2.loc[1])]]);
- }
+ if (item) {
+ items.splice(items.indexOf(item), 1);
+ } else {
+ item = RibbonItem(preset, 'recent');
+ } // remove the last recent (first in, first out)
- return graph["transient"](this, 'segments', function () {
- var segments = [];
- for (var i = 0; i < this.nodes.length - 1; i++) {
- segments.push({
- id: this.id + '-' + i,
- wayId: this.id,
- index: i,
- nodes: [this.nodes[i], this.nodes[i + 1]],
- extent: segmentExtent
- });
- }
+ while (items.length >= MAXRECENTS) {
+ items.pop();
+ } // prepend array
- return segments;
- });
- },
- // If this way is not closed, append the beginning node to the end of the nodelist to close it.
- close: function close() {
- if (this.isClosed() || !this.nodes.length) return this;
- var nodes = this.nodes.slice();
- nodes = nodes.filter(noRepeatNodes);
- nodes.push(nodes[0]);
- return this.update({
- nodes: nodes
- });
- },
- // If this way is closed, remove any connector nodes from the end of the nodelist to unclose it.
- unclose: function unclose() {
- if (!this.isClosed()) return this;
- var nodes = this.nodes.slice();
- var connector = this.first();
- var i = nodes.length - 1; // remove trailing connectors..
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- i = nodes.length - 1;
- }
+ items.unshift(item);
+ setRecents(items);
+ };
- nodes = nodes.filter(noRepeatNodes);
- return this.update({
- nodes: nodes
+ function setFavorites(items) {
+ _favorites = items;
+ var minifiedItems = items.map(function (d) {
+ return d.minified();
});
- },
- // Adds a node (id) in front of the node which is currently at position index.
- // If index is undefined, the node will be added to the end of the way for linear ways,
- // or just before the final connecting node for circular ways.
- // Consecutive duplicates are eliminated including existing ones.
- // Circularity is always preserved when adding a node.
- addNode: function addNode(id, index) {
- var nodes = this.nodes.slice();
- var isClosed = this.isClosed();
- var max = isClosed ? nodes.length - 1 : nodes.length;
-
- if (index === undefined) {
- index = max;
- }
-
- if (index < 0 || index > max) {
- throw new RangeError('index ' + index + ' out of range 0..' + max);
- } // If this is a closed way, remove all connector nodes except the first one
- // (there may be duplicates) and adjust index if necessary..
+ corePreferences('preset_favorites', JSON.stringify(minifiedItems)); // call update
+ dispatch.call('favoritePreset');
+ }
- if (isClosed) {
- var connector = this.first(); // leading connectors..
+ _this.addFavorite = function (preset, besidePreset, after) {
+ var favorites = _this.getFavorites();
- var i = 1;
+ var beforeItem = _this.favoriteMatching(besidePreset);
- while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- } // trailing connectors..
+ var toIndex = favorites.indexOf(beforeItem);
+ if (after) toIndex += 1;
+ var newItem = RibbonItem(preset, 'favorite');
+ favorites.splice(toIndex, 0, newItem);
+ setFavorites(favorites);
+ };
+ _this.toggleFavorite = function (preset) {
+ var favs = _this.getFavorites();
- i = nodes.length - 1;
+ var favorite = _this.favoriteMatching(preset);
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- i = nodes.length - 1;
- }
- }
+ if (favorite) {
+ favs.splice(favs.indexOf(favorite), 1);
+ } else {
+ // only allow 10 favorites
+ if (favs.length === 10) {
+ // remove the last favorite (last in, first out)
+ favs.pop();
+ } // append array
- nodes.splice(index, 0, id);
- nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
+ favs.push(RibbonItem(preset, 'favorite'));
}
- return this.update({
- nodes: nodes
- });
- },
- // Replaces the node which is currently at position index with the given node (id).
- // Consecutive duplicates are eliminated including existing ones.
- // Circularity is preserved when updating a node.
- updateNode: function updateNode(id, index) {
- var nodes = this.nodes.slice();
- var isClosed = this.isClosed();
- var max = nodes.length - 1;
+ setFavorites(favs);
+ };
- if (index === undefined || index < 0 || index > max) {
- throw new RangeError('index ' + index + ' out of range 0..' + max);
- } // If this is a closed way, remove all connector nodes except the first one
- // (there may be duplicates) and adjust index if necessary..
+ _this.removeFavorite = function (preset) {
+ var item = _this.favoriteMatching(preset);
+ if (item) {
+ var items = _this.getFavorites();
- if (isClosed) {
- var connector = this.first(); // leading connectors..
+ items.splice(items.indexOf(item), 1);
+ setFavorites(items);
+ }
+ };
- var i = 1;
+ _this.getFavorites = function () {
+ if (!_favorites) {
+ // fetch from local storage
+ var rawFavorites = JSON.parse(corePreferences('preset_favorites'));
- while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index > i) index--;
- } // trailing connectors..
+ if (!rawFavorites) {
+ rawFavorites = [];
+ corePreferences('preset_favorites', JSON.stringify(rawFavorites));
+ }
+ _favorites = rawFavorites.reduce(function (output, d) {
+ var item = ribbonItemForMinified(d, 'favorite');
+ if (item && item.preset.addable()) output.push(item);
+ return output;
+ }, []);
+ }
- i = nodes.length - 1;
+ return _favorites;
+ };
- while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
- nodes.splice(i, 1);
- if (index === i) index = 0; // update leading connector instead
+ _this.favoriteMatching = function (preset) {
+ var favs = _this.getFavorites();
- i = nodes.length - 1;
+ for (var index in favs) {
+ if (favs[index].matches(preset)) {
+ return favs[index];
}
}
- nodes.splice(index, 1, id);
- nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+ return null;
+ };
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
- }
+ return utilRebind(_this, dispatch, 'on');
+ }
- return this.update({
- nodes: nodes
- });
- },
- // Replaces each occurrence of node id needle with replacement.
- // Consecutive duplicates are eliminated including existing ones.
- // Circularity is preserved.
- replaceNode: function replaceNode(needleID, replacementID) {
- var nodes = this.nodes.slice();
- var isClosed = this.isClosed();
-
- for (var i = 0; i < nodes.length; i++) {
- if (nodes[i] === needleID) {
- nodes[i] = replacementID;
- }
- }
+ function utilTagText(entity) {
+ var obj = entity && entity.tags || {};
+ return Object.keys(obj).map(function (k) {
+ return k + '=' + obj[k];
+ }).join(', ');
+ }
+ function utilTotalExtent(array, graph) {
+ var extent = geoExtent();
+ var val, entity;
- nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+ for (var i = 0; i < array.length; i++) {
+ val = array[i];
+ entity = typeof val === 'string' ? graph.hasEntity(val) : val;
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
+ if (entity) {
+ extent._extend(entity.extent(graph));
}
+ }
- return this.update({
- nodes: nodes
- });
- },
- // Removes each occurrence of node id.
- // Consecutive duplicates are eliminated including existing ones.
- // Circularity is preserved.
- removeNode: function removeNode(id) {
- var nodes = this.nodes.slice();
- var isClosed = this.isClosed();
- nodes = nodes.filter(function (node) {
- return node !== id;
- }).filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+ return extent;
+ }
+ function utilTagDiff(oldTags, newTags) {
+ var tagDiff = [];
+ var keys = utilArrayUnion(Object.keys(oldTags), Object.keys(newTags)).sort();
+ keys.forEach(function (k) {
+ var oldVal = oldTags[k];
+ var newVal = newTags[k];
- if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
- nodes.push(nodes[0]);
+ if ((oldVal || oldVal === '') && (newVal === undefined || newVal !== oldVal)) {
+ tagDiff.push({
+ type: '-',
+ key: k,
+ oldVal: oldVal,
+ newVal: newVal,
+ display: '- ' + k + '=' + oldVal
+ });
}
- return this.update({
- nodes: nodes
- });
- },
- asJXON: function asJXON(changeset_id) {
- var r = {
- way: {
- '@id': this.osmId(),
- '@version': this.version || 0,
- nd: this.nodes.map(function (id) {
- return {
- keyAttributes: {
- ref: osmEntity.id.toOSM(id)
- }
- };
- }, this),
- tag: Object.keys(this.tags).map(function (k) {
- return {
- keyAttributes: {
- k: k,
- v: this.tags[k]
- }
- };
- }, this)
- }
- };
-
- if (changeset_id) {
- r.way['@changeset'] = changeset_id;
+ if ((newVal || newVal === '') && (oldVal === undefined || newVal !== oldVal)) {
+ tagDiff.push({
+ type: '+',
+ key: k,
+ oldVal: oldVal,
+ newVal: newVal,
+ display: '+ ' + k + '=' + newVal
+ });
}
+ });
+ return tagDiff;
+ }
+ function utilEntitySelector(ids) {
+ return ids.length ? '.' + ids.join(',.') : 'nothing';
+ } // returns an selector to select entity ids for:
+ // - entityIDs passed in
+ // - shallow descendant entityIDs for any of those entities that are relations
- return r;
- },
- asGeoJSON: function asGeoJSON(resolver) {
- return resolver["transient"](this, 'GeoJSON', function () {
- var coordinates = resolver.childNodes(this).map(function (n) {
- return n.loc;
- });
+ function utilEntityOrMemberSelector(ids, graph) {
+ var seen = new Set(ids);
+ ids.forEach(collectShallowDescendants);
+ return utilEntitySelector(Array.from(seen));
- if (this.isArea() && this.isClosed()) {
- return {
- type: 'Polygon',
- coordinates: [coordinates]
- };
- } else {
- return {
- type: 'LineString',
- coordinates: coordinates
- };
- }
+ function collectShallowDescendants(id) {
+ var entity = graph.hasEntity(id);
+ if (!entity || entity.type !== 'relation') return;
+ entity.members.map(function (member) {
+ return member.id;
+ }).forEach(function (id) {
+ seen.add(id);
});
- },
- area: function area(resolver) {
- return resolver["transient"](this, 'area', function () {
- var nodes = resolver.childNodes(this);
- var json = {
- type: 'Polygon',
- coordinates: [nodes.map(function (n) {
- return n.loc;
- })]
- };
-
- if (!this.isClosed() && nodes.length) {
- json.coordinates[0].push(nodes[0].loc);
- }
+ }
+ } // returns an selector to select entity ids for:
+ // - entityIDs passed in
+ // - deep descendant entityIDs for any of those entities that are relations
- var area = d3_geoArea(json); // Heuristic for detecting counterclockwise winding order. Assumes
- // that OpenStreetMap polygons are not hemisphere-spanning.
+ function utilEntityOrDeepMemberSelector(ids, graph) {
+ return utilEntitySelector(utilEntityAndDeepMemberIDs(ids, graph));
+ } // returns an selector to select entity ids for:
+ // - entityIDs passed in
+ // - deep descendant entityIDs for any of those entities that are relations
- if (area > 2 * Math.PI) {
- json.coordinates[0] = json.coordinates[0].reverse();
- area = d3_geoArea(json);
- }
+ function utilEntityAndDeepMemberIDs(ids, graph) {
+ var seen = new Set();
+ ids.forEach(collectDeepDescendants);
+ return Array.from(seen);
- return isNaN(area) ? 0 : area;
- });
+ function collectDeepDescendants(id) {
+ if (seen.has(id)) return;
+ seen.add(id);
+ var entity = graph.hasEntity(id);
+ if (!entity || entity.type !== 'relation') return;
+ entity.members.map(function (member) {
+ return member.id;
+ }).forEach(collectDeepDescendants); // recurse
}
- }); // Filter function to eliminate consecutive duplicates.
+ } // returns an selector to select entity ids for:
+ // - deep descendant entityIDs for any of those entities that are relations
- function noRepeatNodes(node, i, arr) {
- return i === 0 || node !== arr[i - 1];
- }
+ function utilDeepMemberSelector(ids, graph, skipMultipolgonMembers) {
+ var idsSet = new Set(ids);
+ var seen = new Set();
+ var returners = new Set();
+ ids.forEach(collectDeepDescendants);
+ return utilEntitySelector(Array.from(returners));
- //
- // 1. Relation tagged with `type=multipolygon` and no interesting tags.
- // 2. One and only one member with the `outer` role. Must be a way with interesting tags.
- // 3. No members without a role.
- //
- // Old multipolygons are no longer recommended but are still rendered as areas by iD.
+ function collectDeepDescendants(id) {
+ if (seen.has(id)) return;
+ seen.add(id);
- function osmOldMultipolygonOuterMemberOfRelation(entity, graph) {
- if (entity.type !== 'relation' || !entity.isMultipolygon() || Object.keys(entity.tags).filter(osmIsInterestingTag).length > 1) {
- return false;
+ if (!idsSet.has(id)) {
+ returners.add(id);
+ }
+
+ var entity = graph.hasEntity(id);
+ if (!entity || entity.type !== 'relation') return;
+ if (skipMultipolgonMembers && entity.isMultipolygon()) return;
+ entity.members.map(function (member) {
+ return member.id;
+ }).forEach(collectDeepDescendants); // recurse
}
+ } // Adds or removes highlight styling for the specified entities
- var outerMember;
+ function utilHighlightEntities(ids, highlighted, context) {
+ context.surface().selectAll(utilEntityOrDeepMemberSelector(ids, context.graph())).classed('highlighted', highlighted);
+ } // returns an Array that is the union of:
+ // - nodes for any nodeIDs passed in
+ // - child nodes of any wayIDs passed in
+ // - descendant member and child nodes of relationIDs passed in
- for (var memberIndex in entity.members) {
- var member = entity.members[memberIndex];
+ function utilGetAllNodes(ids, graph) {
+ var seen = new Set();
+ var nodes = new Set();
+ ids.forEach(collectNodes);
+ return Array.from(nodes);
- if (!member.role || member.role === 'outer') {
- if (outerMember) return false;
- if (member.type !== 'way') return false;
- if (!graph.hasEntity(member.id)) return false;
- outerMember = graph.entity(member.id);
+ function collectNodes(id) {
+ if (seen.has(id)) return;
+ seen.add(id);
+ var entity = graph.hasEntity(id);
+ if (!entity) return;
- if (Object.keys(outerMember.tags).filter(osmIsInterestingTag).length === 0) {
- return false;
- }
+ if (entity.type === 'node') {
+ nodes.add(entity);
+ } else if (entity.type === 'way') {
+ entity.nodes.forEach(collectNodes);
+ } else {
+ entity.members.map(function (member) {
+ return member.id;
+ }).forEach(collectNodes); // recurse
}
}
+ }
+ function utilDisplayName(entity) {
+ var localizedNameKey = 'name:' + _mainLocalizer.languageCode().toLowerCase();
+ var name = entity.tags[localizedNameKey] || entity.tags.name || '';
+ if (name) return name;
+ var tags = {
+ direction: entity.tags.direction,
+ from: entity.tags.from,
+ network: entity.tags.cycle_network || entity.tags.network,
+ ref: entity.tags.ref,
+ to: entity.tags.to,
+ via: entity.tags.via
+ };
+ var keyComponents = [];
- return outerMember;
- } // For fixing up rendering of multipolygons with tags on the outer member.
- // https://github.com/openstreetmap/iD/issues/613
-
- function osmIsOldMultipolygonOuterMember(entity, graph) {
- if (entity.type !== 'way' || Object.keys(entity.tags).filter(osmIsInterestingTag).length === 0) {
- return false;
+ if (tags.network) {
+ keyComponents.push('network');
}
- var parents = graph.parentRelations(entity);
- if (parents.length !== 1) return false;
- var parent = parents[0];
-
- if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
- return false;
- }
+ if (tags.ref) {
+ keyComponents.push('ref');
+ } // Routes may need more disambiguation based on direction or destination
- var members = parent.members,
- member;
- for (var i = 0; i < members.length; i++) {
- member = members[i];
+ if (entity.tags.route) {
+ if (tags.direction) {
+ keyComponents.push('direction');
+ } else if (tags.from && tags.to) {
+ keyComponents.push('from');
+ keyComponents.push('to');
- if (member.id === entity.id && member.role && member.role !== 'outer') {
- // Not outer member
- return false;
+ if (tags.via) {
+ keyComponents.push('via');
+ }
}
+ }
- if (member.id !== entity.id && (!member.role || member.role === 'outer')) {
- // Not a simple multipolygon
- return false;
- }
+ if (keyComponents.length) {
+ name = _t('inspector.display_name.' + keyComponents.join('_'), tags);
}
- return parent;
+ return name;
}
- function osmOldMultipolygonOuterMember(entity, graph) {
- if (entity.type !== 'way') return false;
- var parents = graph.parentRelations(entity);
- if (parents.length !== 1) return false;
- var parent = parents[0];
+ function utilDisplayNameForPath(entity) {
+ var name = utilDisplayName(entity);
+ var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1;
+ var isNewChromium = Number(utilDetect().version.split('.')[0]) >= 96.0;
- if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
- return false;
+ if (!isFirefox && !isNewChromium && name && rtlRegex.test(name)) {
+ name = fixRTLTextForSvg(name);
}
- var members = parent.members,
- member,
- outerMember;
+ return name;
+ }
+ function utilDisplayType(id) {
+ return {
+ n: _t('inspector.node'),
+ w: _t('inspector.way'),
+ r: _t('inspector.relation')
+ }[id.charAt(0)];
+ } // `utilDisplayLabel`
+ // Returns a string suitable for display
+ // By default returns something like name/ref, fallback to preset type, fallback to OSM type
+ // "Main Street" or "Tertiary Road"
+ // If `verbose=true`, include both preset name and feature name.
+ // "Tertiary Road Main Street"
+ //
- for (var i = 0; i < members.length; i++) {
- member = members[i];
+ function utilDisplayLabel(entity, graphOrGeometry, verbose) {
+ var result;
+ var displayName = utilDisplayName(entity);
+ var preset = typeof graphOrGeometry === 'string' ? _mainPresetIndex.matchTags(entity.tags, graphOrGeometry) : _mainPresetIndex.match(entity, graphOrGeometry);
+ var presetName = preset && (preset.suggestion ? preset.subtitle() : preset.name());
- if (!member.role || member.role === 'outer') {
- if (outerMember) return false; // Not a simple multipolygon
+ if (verbose) {
+ result = [presetName, displayName].filter(Boolean).join(' ');
+ } else {
+ result = displayName || presetName;
+ } // Fallback to the OSM type (node/way/relation)
- outerMember = member;
- }
- }
- if (!outerMember) return false;
- var outerEntity = graph.hasEntity(outerMember.id);
+ return result || utilDisplayType(entity.id);
+ }
+ function utilEntityRoot(entityType) {
+ return {
+ node: 'n',
+ way: 'w',
+ relation: 'r'
+ }[entityType];
+ } // Returns a single object containing the tags of all the given entities.
+ // Example:
+ // {
+ // highway: 'service',
+ // service: 'parking_aisle'
+ // }
+ // +
+ // {
+ // highway: 'service',
+ // service: 'driveway',
+ // width: '3'
+ // }
+ // =
+ // {
+ // highway: 'service',
+ // service: [ 'driveway', 'parking_aisle' ],
+ // width: [ '3', undefined ]
+ // }
- if (!outerEntity || !Object.keys(outerEntity.tags).filter(osmIsInterestingTag).length) {
- return false;
- }
+ function utilCombinedTags(entityIDs, graph) {
+ var tags = {};
+ var tagCounts = {};
+ var allKeys = new Set();
+ var entities = entityIDs.map(function (entityID) {
+ return graph.hasEntity(entityID);
+ }).filter(Boolean); // gather the aggregate keys
- return outerEntity;
- } // Join `toJoin` array into sequences of connecting ways.
- // Segments which share identical start/end nodes will, as much as possible,
- // be connected with each other.
- //
- // The return value is a nested array. Each constituent array contains elements
- // of `toJoin` which have been determined to connect.
- //
- // Each consitituent array also has a `nodes` property whose value is an
- // ordered array of member nodes, with appropriate order reversal and
- // start/end coordinate de-duplication.
- //
- // Members of `toJoin` must have, at minimum, `type` and `id` properties.
- // Thus either an array of `osmWay`s or a relation member array may be used.
- //
- // If an member is an `osmWay`, its tags and childnodes may be reversed via
- // `actionReverse` in the output.
- //
- // The returned sequences array also has an `actions` array property, containing
- // any reversal actions that should be applied to the graph, should the calling
- // code attempt to actually join the given ways.
- //
- // Incomplete members (those for which `graph.hasEntity(element.id)` returns
- // false) and non-way members are ignored.
- //
+ entities.forEach(function (entity) {
+ var keys = Object.keys(entity.tags).filter(Boolean);
+ keys.forEach(function (key) {
+ allKeys.add(key);
+ });
+ });
+ entities.forEach(function (entity) {
+ allKeys.forEach(function (key) {
+ var value = entity.tags[key]; // purposely allow `undefined`
- function osmJoinWays(toJoin, graph) {
- function resolve(member) {
- return graph.childNodes(graph.entity(member.id));
- }
+ if (!tags.hasOwnProperty(key)) {
+ // first value, set as raw
+ tags[key] = value;
+ } else {
+ if (!Array.isArray(tags[key])) {
+ if (tags[key] !== value) {
+ // first alternate value, replace single value with array
+ tags[key] = [tags[key], value];
+ }
+ } else {
+ // type is array
+ if (tags[key].indexOf(value) === -1) {
+ // subsequent alternate value, add to array
+ tags[key].push(value);
+ }
+ }
+ }
- function reverse(item) {
- var action = actionReverse(item.id, {
- reverseOneway: true
+ var tagHash = key + '=' + value;
+ if (!tagCounts[tagHash]) tagCounts[tagHash] = 0;
+ tagCounts[tagHash] += 1;
});
- sequences.actions.push(action);
- return item instanceof osmWay ? action(graph).entity(item.id) : item;
- } // make a copy containing only the items to join
+ });
+ for (var key in tags) {
+ if (!Array.isArray(tags[key])) continue; // sort values by frequency then alphabetically
- toJoin = toJoin.filter(function (member) {
- return member.type === 'way' && graph.hasEntity(member.id);
- }); // Are the things we are joining relation members or `osmWays`?
- // If `osmWays`, skip the "prefer a forward path" code below (see #4872)
+ tags[key] = tags[key].sort(function (val1, val2) {
+ var key = key; // capture
- var i;
- var joinAsMembers = true;
+ var count2 = tagCounts[key + '=' + val2];
+ var count1 = tagCounts[key + '=' + val1];
- for (i = 0; i < toJoin.length; i++) {
- if (toJoin[i] instanceof osmWay) {
- joinAsMembers = false;
- break;
- }
- }
+ if (count2 !== count1) {
+ return count2 - count1;
+ }
- var sequences = [];
- sequences.actions = [];
+ if (val2 && val1) {
+ return val1.localeCompare(val2);
+ }
- while (toJoin.length) {
- // start a new sequence
- var item = toJoin.shift();
- var currWays = [item];
- var currNodes = resolve(item).slice(); // add to it
+ return val1 ? 1 : -1;
+ });
+ }
- while (toJoin.length) {
- var start = currNodes[0];
- var end = currNodes[currNodes.length - 1];
- var fn = null;
- var nodes = null; // Find the next way/member to join.
+ return tags;
+ }
+ function utilStringQs(str) {
+ var i = 0; // advance past any leading '?' or '#' characters
- for (i = 0; i < toJoin.length; i++) {
- item = toJoin[i];
- nodes = resolve(item); // (for member ordering only, not way ordering - see #4872)
- // Strongly prefer to generate a forward path that preserves the order
- // of the members array. For multipolygons and most relations, member
- // order does not matter - but for routes, it does. (see #4589)
- // If we started this sequence backwards (i.e. next member way attaches to
- // the start node and not the end node), reverse the initial way before continuing.
+ while (i < str.length && (str[i] === '?' || str[i] === '#')) {
+ i++;
+ }
- if (joinAsMembers && currWays.length === 1 && nodes[0] !== end && nodes[nodes.length - 1] !== end && (nodes[nodes.length - 1] === start || nodes[0] === start)) {
- currWays[0] = reverse(currWays[0]);
- currNodes.reverse();
- start = currNodes[0];
- end = currNodes[currNodes.length - 1];
- }
-
- if (nodes[0] === end) {
- fn = currNodes.push; // join to end
-
- nodes = nodes.slice(1);
- break;
- } else if (nodes[nodes.length - 1] === end) {
- fn = currNodes.push; // join to end
-
- nodes = nodes.slice(0, -1).reverse();
- item = reverse(item);
- break;
- } else if (nodes[nodes.length - 1] === start) {
- fn = currNodes.unshift; // join to beginning
-
- nodes = nodes.slice(0, -1);
- break;
- } else if (nodes[0] === start) {
- fn = currNodes.unshift; // join to beginning
-
- nodes = nodes.slice(1).reverse();
- item = reverse(item);
- break;
- } else {
- fn = nodes = null;
- }
- }
-
- if (!nodes) {
- // couldn't find a joinable way/member
- break;
- }
+ str = str.slice(i);
+ return str.split('&').reduce(function (obj, pair) {
+ var parts = pair.split('=');
- fn.apply(currWays, [item]);
- fn.apply(currNodes, nodes);
- toJoin.splice(i, 1);
+ if (parts.length === 2) {
+ obj[parts[0]] = null === parts[1] ? '' : decodeURIComponent(parts[1]);
}
- currWays.nodes = currNodes;
- sequences.push(currWays);
+ return obj;
+ }, {});
+ }
+ function utilQsString(obj, noencode) {
+ // encode everything except special characters used in certain hash parameters:
+ // "/" in map states, ":", ",", {" and "}" in background
+ function softEncode(s) {
+ return encodeURIComponent(s).replace(/(%2F|%3A|%2C|%7B|%7D)/g, decodeURIComponent);
}
- return sequences;
+ return Object.keys(obj).sort().map(function (key) {
+ return encodeURIComponent(key) + '=' + (noencode ? softEncode(obj[key]) : encodeURIComponent(obj[key]));
+ }).join('&');
}
+ function utilPrefixDOMProperty(property) {
+ var prefixes = ['webkit', 'ms', 'moz', 'o'];
+ var i = -1;
+ var n = prefixes.length;
+ var s = document.body;
+ if (property in s) return property;
+ property = property.substr(0, 1).toUpperCase() + property.substr(1);
- function actionAddMember(relationId, member, memberIndex, insertPair) {
- return function action(graph) {
- var relation = graph.entity(relationId); // There are some special rules for Public Transport v2 routes.
+ while (++i < n) {
+ if (prefixes[i] + property in s) {
+ return prefixes[i] + property;
+ }
+ }
- var isPTv2 = /stop|platform/.test(member.role);
+ return false;
+ }
+ function utilPrefixCSSProperty(property) {
+ var prefixes = ['webkit', 'ms', 'Moz', 'O'];
+ var i = -1;
+ var n = prefixes.length;
+ var s = document.body.style;
- if ((isNaN(memberIndex) || insertPair) && member.type === 'way' && !isPTv2) {
- // Try to perform sensible inserts based on how the ways join together
- graph = addWayMember(relation, graph);
- } else {
- // see https://wiki.openstreetmap.org/wiki/Public_transport#Service_routes
- // Stops and Platforms for PTv2 should be ordered first.
- // hack: We do not currently have the ability to place them in the exactly correct order.
- if (isPTv2 && isNaN(memberIndex)) {
- memberIndex = 0;
- }
+ if (property.toLowerCase() in s) {
+ return property.toLowerCase();
+ }
- graph = graph.replace(relation.addMember(member, memberIndex));
+ while (++i < n) {
+ if (prefixes[i] + property in s) {
+ return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase();
}
+ }
- return graph;
- }; // Add a way member into the relation "wherever it makes sense".
- // In this situation we were not supplied a memberIndex.
+ return false;
+ }
+ var transformProperty;
+ function utilSetTransform(el, x, y, scale) {
+ var prop = transformProperty = transformProperty || utilPrefixCSSProperty('Transform');
+ var translate = utilDetect().opera ? 'translate(' + x + 'px,' + y + 'px)' : 'translate3d(' + x + 'px,' + y + 'px,0)';
+ return el.style(prop, translate + (scale ? ' scale(' + scale + ')' : ''));
+ } // Calculates Levenshtein distance between two strings
+ // see: https://en.wikipedia.org/wiki/Levenshtein_distance
+ // first converts the strings to lowercase and replaces diacritic marks with ascii equivalents.
- function addWayMember(relation, graph) {
- var groups, tempWay, item, i, j, k; // remove PTv2 stops and platforms before doing anything.
+ function utilEditDistance(a, b) {
+ a = remove$6(a.toLowerCase());
+ b = remove$6(b.toLowerCase());
+ if (a.length === 0) return b.length;
+ if (b.length === 0) return a.length;
+ var matrix = [];
+ var i, j;
- var PTv2members = [];
- var members = [];
+ for (i = 0; i <= b.length; i++) {
+ matrix[i] = [i];
+ }
- for (i = 0; i < relation.members.length; i++) {
- var m = relation.members[i];
+ for (j = 0; j <= a.length; j++) {
+ matrix[0][j] = j;
+ }
- if (/stop|platform/.test(m.role)) {
- PTv2members.push(m);
+ for (i = 1; i <= b.length; i++) {
+ for (j = 1; j <= a.length; j++) {
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
+ matrix[i][j] = matrix[i - 1][j - 1];
} else {
- members.push(m);
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, // substitution
+ Math.min(matrix[i][j - 1] + 1, // insertion
+ matrix[i - 1][j] + 1)); // deletion
}
}
+ }
- relation = relation.update({
- members: members
+ return matrix[b.length][a.length];
+ } // a d3.mouse-alike which
+ // 1. Only works on HTML elements, not SVG
+ // 2. Does not cause style recalculation
+
+ function utilFastMouse(container) {
+ var rect = container.getBoundingClientRect();
+ var rectLeft = rect.left;
+ var rectTop = rect.top;
+ var clientLeft = +container.clientLeft;
+ var clientTop = +container.clientTop;
+ return function (e) {
+ return [e.clientX - rectLeft - clientLeft, e.clientY - rectTop - clientTop];
+ };
+ }
+ function utilAsyncMap(inputs, func, callback) {
+ var remaining = inputs.length;
+ var results = [];
+ var errors = [];
+ inputs.forEach(function (d, i) {
+ func(d, function done(err, data) {
+ errors[i] = err;
+ results[i] = data;
+ remaining--;
+ if (!remaining) callback(errors, results);
});
+ });
+ } // wraps an index to an interval [0..length-1]
- if (insertPair) {
- // We're adding a member that must stay paired with an existing member.
- // (This feature is used by `actionSplit`)
- //
- // This is tricky because the members may exist multiple times in the
- // member list, and with different A-B/B-A ordering and different roles.
- // (e.g. a bus route that loops out and back - #4589).
- //
- // Replace the existing member with a temporary way,
- // so that `osmJoinWays` can treat the pair like a single way.
- tempWay = osmWay({
- id: 'wTemp',
- nodes: insertPair.nodes
- });
- graph = graph.replace(tempWay);
- var tempMember = {
- id: tempWay.id,
- type: 'way',
- role: member.role
- };
- var tempRelation = relation.replaceMember({
- id: insertPair.originalID
- }, tempMember, true);
- groups = utilArrayGroupBy(tempRelation.members, 'type');
- groups.way = groups.way || [];
- } else {
- // Add the member anywhere, one time. Just push and let `osmJoinWays` decide where to put it.
- groups = utilArrayGroupBy(relation.members, 'type');
- groups.way = groups.way || [];
- groups.way.push(member);
- }
+ function utilWrap(index, length) {
+ if (index < 0) {
+ index += Math.ceil(-index / length) * length;
+ }
- members = withIndex(groups.way);
- var joined = osmJoinWays(members, graph); // `joined` might not contain all of the way members,
- // But will contain only the completed (downloaded) members
+ return index % length;
+ }
+ /**
+ * a replacement for functor
+ *
+ * @param {*} value any value
+ * @returns {Function} a function that returns that value or the value if it's a function
+ */
- for (i = 0; i < joined.length; i++) {
- var segment = joined[i];
- var nodes = segment.nodes.slice();
- var startIndex = segment[0].index; // j = array index in `members` where this segment starts
+ function utilFunctor(value) {
+ if (typeof value === 'function') return value;
+ return function () {
+ return value;
+ };
+ }
+ function utilNoAuto(selection) {
+ var isText = selection.size() && selection.node().tagName.toLowerCase() === 'textarea';
+ return selection // assign 'new-password' even for non-password fields to prevent browsers (Chrome) ignoring 'off'
+ .attr('autocomplete', 'new-password').attr('autocorrect', 'off').attr('autocapitalize', 'off').attr('spellcheck', isText ? 'true' : 'false');
+ } // https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
+ // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
- for (j = 0; j < members.length; j++) {
- if (members[j].index === startIndex) {
- break;
- }
- } // k = each member in segment
+ function utilHashcode(str) {
+ var hash = 0;
+ if (str.length === 0) {
+ return hash;
+ }
- for (k = 0; k < segment.length; k++) {
- item = segment[k];
- var way = graph.entity(item.id); // If this is a paired item, generate members in correct order and role
+ for (var i = 0; i < str.length; i++) {
+ var _char = str.charCodeAt(i);
- if (tempWay && item.id === tempWay.id) {
- if (nodes[0].id === insertPair.nodes[0]) {
- item.pair = [{
- id: insertPair.originalID,
- type: 'way',
- role: item.role
- }, {
- id: insertPair.insertedID,
- type: 'way',
- role: item.role
- }];
- } else {
- item.pair = [{
- id: insertPair.insertedID,
- type: 'way',
- role: item.role
- }, {
- id: insertPair.originalID,
- type: 'way',
- role: item.role
- }];
- }
- } // reorder `members` if necessary
+ hash = (hash << 5) - hash + _char;
+ hash = hash & hash; // Convert to 32bit integer
+ }
+ return hash;
+ } // Returns version of `str` with all runs of special characters replaced by `_`;
+ // suitable for HTML ids, classes, selectors, etc.
- if (k > 0) {
- if (j + k >= members.length || item.index !== members[j + k].index) {
- moveMember(members, item.index, j + k);
- }
- }
+ function utilSafeClassName(str) {
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, '_');
+ } // Returns string based on `val` that is highly unlikely to collide with an id
+ // used previously or that's present elsewhere in the document. Useful for preventing
+ // browser-provided autofills or when embedding iD on pages with unknown elements.
- nodes.splice(0, way.nodes.length - 1);
- }
- }
+ function utilUniqueDomId(val) {
+ return 'ideditor-' + utilSafeClassName(val.toString()) + '-' + new Date().getTime().toString();
+ } // Returns the length of `str` in unicode characters. This can be less than
+ // `String.length()` since a single unicode character can be composed of multiple
+ // JavaScript UTF-16 code units.
- if (tempWay) {
- graph = graph.remove(tempWay);
- } // Final pass: skip dead items, split pairs, remove index properties
+ function utilUnicodeCharsCount(str) {
+ // Native ES2015 implementations of `Array.from` split strings into unicode characters
+ return Array.from(str).length;
+ } // Returns a new string representing `str` cut from its start to `limit` length
+ // in unicode characters. Note that this runs the risk of splitting graphemes.
+ function utilUnicodeCharsTruncated(str, limit) {
+ return Array.from(str).slice(0, limit).join('');
+ }
- var wayMembers = [];
+ function toNumericID(id) {
+ var match = id.match(/^[cnwr](-?\d+)$/);
- for (i = 0; i < members.length; i++) {
- item = members[i];
- if (item.index === -1) continue;
+ if (match) {
+ return parseInt(match[1], 10);
+ }
- if (item.pair) {
- wayMembers.push(item.pair[0]);
- wayMembers.push(item.pair[1]);
- } else {
- wayMembers.push(utilObjectOmit(item, ['index']));
- }
- } // Put stops and platforms first, then nodes, ways, relations
- // This is recommended for Public Transport v2 routes:
- // see https://wiki.openstreetmap.org/wiki/Public_transport#Service_routes
+ return NaN;
+ }
+ function compareNumericIDs(left, right) {
+ if (isNaN(left) && isNaN(right)) return -1;
+ if (isNaN(left)) return 1;
+ if (isNaN(right)) return -1;
+ if (Math.sign(left) !== Math.sign(right)) return -Math.sign(left);
+ if (Math.sign(left) < 0) return Math.sign(right - left);
+ return Math.sign(left - right);
+ } // Returns -1 if the first parameter ID is older than the second,
+ // 1 if the second parameter is older, 0 if they are the same.
+ // If both IDs are test IDs, the function returns -1.
- var newMembers = PTv2members.concat(groups.node || [], wayMembers, groups.relation || []);
- return graph.replace(relation.update({
- members: newMembers
- })); // `moveMember()` changes the `members` array in place by splicing
- // the item with `.index = findIndex` to where it belongs,
- // and marking the old position as "dead" with `.index = -1`
- //
- // j=5, k=0 jk
- // segment 5 4 7 6
- // members 0 1 2 3 4 5 6 7 8 9 keep 5 in j+k
- //
- // j=5, k=1 j k
- // segment 5 4 7 6
- // members 0 1 2 3 4 5 6 7 8 9 move 4 to j+k
- // members 0 1 2 3 x 5 4 6 7 8 9 moved
- //
- // j=5, k=2 j k
- // segment 5 4 7 6
- // members 0 1 2 3 x 5 4 6 7 8 9 move 7 to j+k
- // members 0 1 2 3 x 5 4 7 6 x 8 9 moved
- //
- // j=5, k=3 j k
- // segment 5 4 7 6
- // members 0 1 2 3 x 5 4 7 6 x 8 9 keep 6 in j+k
- //
- function moveMember(arr, findIndex, toIndex) {
- var i;
+ function utilCompareIDs(left, right) {
+ return compareNumericIDs(toNumericID(left), toNumericID(right));
+ } // Returns the chronologically oldest ID in the list.
+ // Database IDs (with positive numbers) before editor ones (with negative numbers).
+ // Among each category, the closest number to 0 is the oldest.
+ // Test IDs (any string that does not conform to OSM's ID scheme) are taken last.
- for (i = 0; i < arr.length; i++) {
- if (arr[i].index === findIndex) {
- break;
- }
- }
+ function utilOldestID(ids) {
+ if (ids.length === 0) {
+ return undefined;
+ }
- var item = Object.assign({}, arr[i]); // shallow copy
+ var oldestIDIndex = 0;
+ var oldestID = toNumericID(ids[0]);
- arr[i].index = -1; // mark as dead
+ for (var i = 1; i < ids.length; i++) {
+ var num = toNumericID(ids[i]);
- item.index = toIndex;
- arr.splice(toIndex, 0, item);
- } // This is the same as `Relation.indexedMembers`,
- // Except we don't want to index all the members, only the ways
+ if (compareNumericIDs(oldestID, num) === 1) {
+ oldestIDIndex = i;
+ oldestID = num;
+ }
+ }
+ return ids[oldestIDIndex];
+ }
- function withIndex(arr) {
- var result = new Array(arr.length);
+ function osmEntity(attrs) {
+ // For prototypal inheritance.
+ if (this instanceof osmEntity) return; // Create the appropriate subtype.
- for (var i = 0; i < arr.length; i++) {
- result[i] = Object.assign({}, arr[i]); // shallow copy
+ if (attrs && attrs.type) {
+ return osmEntity[attrs.type].apply(this, arguments);
+ } else if (attrs && attrs.id) {
+ return osmEntity[osmEntity.id.type(attrs.id)].apply(this, arguments);
+ } // Initialize a generic Entity (used only in tests).
- result[i].index = i;
- }
- return result;
- }
- }
+ return new osmEntity().initialize(arguments);
}
- function actionAddMidpoint(midpoint, node) {
- return function (graph) {
- graph = graph.replace(node.move(midpoint.loc));
- var parents = utilArrayIntersection(graph.parentWays(graph.entity(midpoint.edge[0])), graph.parentWays(graph.entity(midpoint.edge[1])));
- parents.forEach(function (way) {
- for (var i = 0; i < way.nodes.length - 1; i++) {
- if (geoEdgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) {
- graph = graph.replace(graph.entity(way.id).addNode(node.id, i + 1)); // Add only one midpoint on doubled-back segments,
- // turning them into self-intersections.
+ osmEntity.id = function (type) {
+ return osmEntity.id.fromOSM(type, osmEntity.id.next[type]--);
+ };
- return;
- }
- }
- });
- return graph;
- };
- }
+ osmEntity.id.next = {
+ changeset: -1,
+ node: -1,
+ way: -1,
+ relation: -1
+ };
- // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
- function actionAddVertex(wayId, nodeId, index) {
- return function (graph) {
- return graph.replace(graph.entity(wayId).addNode(nodeId, index));
- };
- }
+ osmEntity.id.fromOSM = function (type, id) {
+ return type[0] + id;
+ };
- function actionChangeMember(relationId, member, memberIndex) {
- return function (graph) {
- return graph.replace(graph.entity(relationId).updateMember(member, memberIndex));
- };
- }
+ osmEntity.id.toOSM = function (id) {
+ var match = id.match(/^[cnwr](-?\d+)$/);
- function actionChangePreset(entityID, oldPreset, newPreset, skipFieldDefaults) {
- return function action(graph) {
- var entity = graph.entity(entityID);
- var geometry = entity.geometry(graph);
- var tags = entity.tags; // preserve tags that the new preset might care about, if any
+ if (match) {
+ return match[1];
+ }
- if (oldPreset) tags = oldPreset.unsetTags(tags, geometry, newPreset && newPreset.addTags ? Object.keys(newPreset.addTags) : null);
- if (newPreset) tags = newPreset.setTags(tags, geometry, skipFieldDefaults);
- return graph.replace(entity.update({
- tags: tags
- }));
- };
- }
+ return '';
+ };
- function actionChangeTags(entityId, tags) {
- return function (graph) {
- var entity = graph.entity(entityId);
- return graph.replace(entity.update({
- tags: tags
- }));
- };
- }
+ osmEntity.id.type = function (id) {
+ return {
+ 'c': 'changeset',
+ 'n': 'node',
+ 'w': 'way',
+ 'r': 'relation'
+ }[id[0]];
+ }; // A function suitable for use as the second argument to d3.selection#data().
- function osmNode() {
- if (!(this instanceof osmNode)) {
- return new osmNode().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
- }
- }
- osmEntity.node = osmNode;
- osmNode.prototype = Object.create(osmEntity.prototype);
- Object.assign(osmNode.prototype, {
- type: 'node',
- loc: [9999, 9999],
- extent: function extent() {
- return new geoExtent(this.loc);
- },
- geometry: function geometry(graph) {
- return graph["transient"](this, 'geometry', function () {
- return graph.isPoi(this) ? 'point' : 'vertex';
- });
- },
- move: function move(loc) {
- return this.update({
- loc: loc
- });
- },
- isDegenerate: function isDegenerate() {
- return !(Array.isArray(this.loc) && this.loc.length === 2 && this.loc[0] >= -180 && this.loc[0] <= 180 && this.loc[1] >= -90 && this.loc[1] <= 90);
- },
- // Inspect tags and geometry to determine which direction(s) this node/vertex points
- directions: function directions(resolver, projection) {
- var val;
- var i; // which tag to use?
- if (this.isHighwayIntersection(resolver) && (this.tags.stop || '').toLowerCase() === 'all') {
- // all-way stop tag on a highway intersection
- val = 'all';
- } else {
- // generic direction tag
- val = (this.tags.direction || '').toLowerCase(); // better suffix-style direction tag
+ osmEntity.key = function (entity) {
+ return entity.id + 'v' + (entity.v || 0);
+ };
- var re = /:direction$/i;
- var keys = Object.keys(this.tags);
+ var _deprecatedTagValuesByKey;
- for (i = 0; i < keys.length; i++) {
- if (re.test(keys[i])) {
- val = this.tags[keys[i]].toLowerCase();
- break;
+ osmEntity.deprecatedTagValuesByKey = function (dataDeprecated) {
+ if (!_deprecatedTagValuesByKey) {
+ _deprecatedTagValuesByKey = {};
+ dataDeprecated.forEach(function (d) {
+ var oldKeys = Object.keys(d.old);
+
+ if (oldKeys.length === 1) {
+ var oldKey = oldKeys[0];
+ var oldValue = d.old[oldKey];
+
+ if (oldValue !== '*') {
+ if (!_deprecatedTagValuesByKey[oldKey]) {
+ _deprecatedTagValuesByKey[oldKey] = [oldValue];
+ } else {
+ _deprecatedTagValuesByKey[oldKey].push(oldValue);
+ }
}
}
- }
+ });
+ }
- if (val === '') return [];
- var cardinal = {
- north: 0,
- n: 0,
- northnortheast: 22,
- nne: 22,
- northeast: 45,
- ne: 45,
- eastnortheast: 67,
- ene: 67,
- east: 90,
- e: 90,
- eastsoutheast: 112,
- ese: 112,
- southeast: 135,
- se: 135,
- southsoutheast: 157,
- sse: 157,
- south: 180,
- s: 180,
- southsouthwest: 202,
- ssw: 202,
- southwest: 225,
- sw: 225,
- westsouthwest: 247,
- wsw: 247,
- west: 270,
- w: 270,
- westnorthwest: 292,
- wnw: 292,
- northwest: 315,
- nw: 315,
- northnorthwest: 337,
- nnw: 337
- };
- var values = val.split(';');
- var results = [];
- values.forEach(function (v) {
- // swap cardinal for numeric directions
- if (cardinal[v] !== undefined) {
- v = cardinal[v];
- } // numeric direction - just add to results
+ return _deprecatedTagValuesByKey;
+ };
+ osmEntity.prototype = {
+ tags: {},
+ initialize: function initialize(sources) {
+ for (var i = 0; i < sources.length; ++i) {
+ var source = sources[i];
- if (v !== '' && !isNaN(+v)) {
- results.push(+v);
- return;
- } // string direction - inspect parent ways
+ for (var prop in source) {
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
+ if (source[prop] === undefined) {
+ delete this[prop];
+ } else {
+ this[prop] = source[prop];
+ }
+ }
+ }
+ }
+ if (!this.id && this.type) {
+ this.id = osmEntity.id(this.type);
+ }
- var lookBackward = this.tags['traffic_sign:backward'] || v === 'backward' || v === 'both' || v === 'all';
- var lookForward = this.tags['traffic_sign:forward'] || v === 'forward' || v === 'both' || v === 'all';
- if (!lookForward && !lookBackward) return;
- var nodeIds = {};
- resolver.parentWays(this).forEach(function (parent) {
- var nodes = parent.nodes;
+ if (!this.hasOwnProperty('visible')) {
+ this.visible = true;
+ }
- for (i = 0; i < nodes.length; i++) {
- if (nodes[i] === this.id) {
- // match current entity
- if (lookForward && i > 0) {
- nodeIds[nodes[i - 1]] = true; // look back to prev node
- }
+ if (debug) {
+ Object.freeze(this);
+ Object.freeze(this.tags);
+ if (this.loc) Object.freeze(this.loc);
+ if (this.nodes) Object.freeze(this.nodes);
+ if (this.members) Object.freeze(this.members);
+ }
- if (lookBackward && i < nodes.length - 1) {
- nodeIds[nodes[i + 1]] = true; // look ahead to next node
- }
- }
- }
- }, this);
- Object.keys(nodeIds).forEach(function (nodeId) {
- // +90 because geoAngle returns angle from X axis, not Y (north)
- results.push(geoAngle(this, resolver.entity(nodeId), projection) * (180 / Math.PI) + 90);
- }, this);
- }, this);
- return utilArrayUniq(results);
+ return this;
},
- isEndpoint: function isEndpoint(resolver) {
- return resolver["transient"](this, 'isEndpoint', function () {
- var id = this.id;
- return resolver.parentWays(this).filter(function (parent) {
- return !parent.isClosed() && !!parent.affix(id);
- }).length > 0;
+ copy: function copy(resolver, copies) {
+ if (copies[this.id]) return copies[this.id];
+ var copy = osmEntity(this, {
+ id: undefined,
+ user: undefined,
+ version: undefined
});
+ copies[this.id] = copy;
+ return copy;
},
- isConnected: function isConnected(resolver) {
- return resolver["transient"](this, 'isConnected', function () {
- var parents = resolver.parentWays(this);
-
- if (parents.length > 1) {
- // vertex is connected to multiple parent ways
- for (var i in parents) {
- if (parents[i].geometry(resolver) === 'line' && parents[i].hasInterestingTags()) return true;
- }
- } else if (parents.length === 1) {
- var way = parents[0];
- var nodes = way.nodes.slice();
+ osmId: function osmId() {
+ return osmEntity.id.toOSM(this.id);
+ },
+ isNew: function isNew() {
+ var osmId = osmEntity.id.toOSM(this.id);
+ return osmId.length === 0 || osmId[0] === '-';
+ },
+ update: function update(attrs) {
+ return osmEntity(this, attrs, {
+ v: 1 + (this.v || 0)
+ });
+ },
+ mergeTags: function mergeTags(tags) {
+ var merged = Object.assign({}, this.tags); // shallow copy
- if (way.isClosed()) {
- nodes.pop();
- } // ignore connecting node if closed
- // return true if vertex appears multiple times (way is self intersecting)
+ var changed = false;
+ for (var k in tags) {
+ var t1 = merged[k];
+ var t2 = tags[k];
- return nodes.indexOf(this.id) !== nodes.lastIndexOf(this.id);
+ if (!t1) {
+ changed = true;
+ merged[k] = t2;
+ } else if (t1 !== t2) {
+ changed = true;
+ merged[k] = utilUnicodeCharsTruncated(utilArrayUnion(t1.split(/;\s*/), t2.split(/;\s*/)).join(';'), 255 // avoid exceeding character limit; see also services/osm.js -> maxCharsForTagValue()
+ );
}
+ }
- return false;
- });
+ return changed ? this.update({
+ tags: merged
+ }) : this;
},
- parentIntersectionWays: function parentIntersectionWays(resolver) {
- return resolver["transient"](this, 'parentIntersectionWays', function () {
- return resolver.parentWays(this).filter(function (parent) {
- return (parent.tags.highway || parent.tags.waterway || parent.tags.railway || parent.tags.aeroway) && parent.geometry(resolver) === 'line';
- });
+ intersects: function intersects(extent, resolver) {
+ return this.extent(resolver).intersects(extent);
+ },
+ hasNonGeometryTags: function hasNonGeometryTags() {
+ return Object.keys(this.tags).some(function (k) {
+ return k !== 'area';
});
},
- isIntersection: function isIntersection(resolver) {
- return this.parentIntersectionWays(resolver).length > 1;
+ hasParentRelations: function hasParentRelations(resolver) {
+ return resolver.parentRelations(this).length > 0;
},
- isHighwayIntersection: function isHighwayIntersection(resolver) {
- return resolver["transient"](this, 'isHighwayIntersection', function () {
- return resolver.parentWays(this).filter(function (parent) {
- return parent.tags.highway && parent.geometry(resolver) === 'line';
- }).length > 1;
- });
+ hasInterestingTags: function hasInterestingTags() {
+ return Object.keys(this.tags).some(osmIsInterestingTag);
},
- isOnAddressLine: function isOnAddressLine(resolver) {
- return resolver["transient"](this, 'isOnAddressLine', function () {
- return resolver.parentWays(this).filter(function (parent) {
- return parent.tags.hasOwnProperty('addr:interpolation') && parent.geometry(resolver) === 'line';
- }).length > 0;
- });
+ isHighwayIntersection: function isHighwayIntersection() {
+ return false;
},
- asJXON: function asJXON(changeset_id) {
- var r = {
- node: {
- '@id': this.osmId(),
- '@lon': this.loc[0],
- '@lat': this.loc[1],
- '@version': this.version || 0,
- tag: Object.keys(this.tags).map(function (k) {
- return {
- keyAttributes: {
- k: k,
- v: this.tags[k]
- }
- };
- }, this)
- }
- };
- if (changeset_id) r.node['@changeset'] = changeset_id;
- return r;
+ isDegenerate: function isDegenerate() {
+ return true;
},
- asGeoJSON: function asGeoJSON() {
- return {
- type: 'Point',
- coordinates: this.loc
- };
- }
- });
-
- function actionCircularize(wayId, projection, maxAngle) {
- maxAngle = (maxAngle || 20) * Math.PI / 180;
+ deprecatedTags: function deprecatedTags(dataDeprecated) {
+ var tags = this.tags; // if there are no tags, none can be deprecated
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var way = graph.entity(wayId);
- var origNodes = {};
- graph.childNodes(way).forEach(function (node) {
- if (!origNodes[node.id]) origNodes[node.id] = node;
- });
+ if (Object.keys(tags).length === 0) return [];
+ var deprecated = [];
+ dataDeprecated.forEach(function (d) {
+ var oldKeys = Object.keys(d.old);
- if (!way.isConvex(graph)) {
- graph = action.makeConvex(graph);
- }
+ if (d.replace) {
+ var hasExistingValues = Object.keys(d.replace).some(function (replaceKey) {
+ if (!tags[replaceKey] || d.old[replaceKey]) return false;
+ var replaceValue = d.replace[replaceKey];
+ if (replaceValue === '*') return false;
+ if (replaceValue === tags[replaceKey]) return false;
+ return true;
+ }); // don't flag deprecated tags if the upgrade path would overwrite existing data - #7843
- var nodes = utilArrayUniq(graph.childNodes(way));
- var keyNodes = nodes.filter(function (n) {
- return graph.parentWays(n).length !== 1;
- });
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var keyPoints = keyNodes.map(function (n) {
- return projection(n.loc);
- });
- var centroid = points.length === 2 ? geoVecInterp(points[0], points[1], 0.5) : d3_polygonCentroid(points);
- var radius = d3_median(points, function (p) {
- return geoVecLength(centroid, p);
- });
- var sign = d3_polygonArea(points) > 0 ? 1 : -1;
- var ids, i, j, k; // we need at least two key nodes for the algorithm to work
+ if (hasExistingValues) return;
+ }
- if (!keyNodes.length) {
- keyNodes = [nodes[0]];
- keyPoints = [points[0]];
- }
+ var matchesDeprecatedTags = oldKeys.every(function (oldKey) {
+ if (!tags[oldKey]) return false;
+ if (d.old[oldKey] === '*') return true;
+ if (d.old[oldKey] === tags[oldKey]) return true;
+ var vals = tags[oldKey].split(';').filter(Boolean);
- if (keyNodes.length === 1) {
- var index = nodes.indexOf(keyNodes[0]);
- var oppositeIndex = Math.floor((index + nodes.length / 2) % nodes.length);
- keyNodes.push(nodes[oppositeIndex]);
- keyPoints.push(points[oppositeIndex]);
- } // key points and nodes are those connected to the ways,
- // they are projected onto the circle, in between nodes are moved
- // to constant intervals between key nodes, extra in between nodes are
- // added if necessary.
+ if (vals.length === 0) {
+ return false;
+ } else if (vals.length > 1) {
+ return vals.indexOf(d.old[oldKey]) !== -1;
+ } else {
+ if (tags[oldKey] === d.old[oldKey]) {
+ if (d.replace && d.old[oldKey] === d.replace[oldKey]) {
+ var replaceKeys = Object.keys(d.replace);
+ return !replaceKeys.every(function (replaceKey) {
+ return tags[replaceKey] === d.replace[replaceKey];
+ });
+ } else {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
- for (i = 0; i < keyPoints.length; i++) {
- var nextKeyNodeIndex = (i + 1) % keyNodes.length;
- var startNode = keyNodes[i];
- var endNode = keyNodes[nextKeyNodeIndex];
- var startNodeIndex = nodes.indexOf(startNode);
- var endNodeIndex = nodes.indexOf(endNode);
- var numberNewPoints = -1;
- var indexRange = endNodeIndex - startNodeIndex;
- var nearNodes = {};
- var inBetweenNodes = [];
- var startAngle, endAngle, totalAngle, eachAngle;
- var angle, loc, node, origNode;
+ if (matchesDeprecatedTags) {
+ deprecated.push(d);
+ }
+ });
+ return deprecated;
+ }
+ };
- if (indexRange < 0) {
- indexRange += nodes.length;
- } // position this key node
+ function osmLanes(entity) {
+ if (entity.type !== 'way') return null;
+ if (!entity.tags.highway) return null;
+ var tags = entity.tags;
+ var isOneWay = entity.isOneWay();
+ var laneCount = getLaneCount(tags, isOneWay);
+ var maxspeed = parseMaxspeed(tags);
+ var laneDirections = parseLaneDirections(tags, isOneWay, laneCount);
+ var forward = laneDirections.forward;
+ var backward = laneDirections.backward;
+ var bothways = laneDirections.bothways; // parse the piped string 'x|y|z' format
+ var turnLanes = {};
+ turnLanes.unspecified = parseTurnLanes(tags['turn:lanes']);
+ turnLanes.forward = parseTurnLanes(tags['turn:lanes:forward']);
+ turnLanes.backward = parseTurnLanes(tags['turn:lanes:backward']);
+ var maxspeedLanes = {};
+ maxspeedLanes.unspecified = parseMaxspeedLanes(tags['maxspeed:lanes'], maxspeed);
+ maxspeedLanes.forward = parseMaxspeedLanes(tags['maxspeed:lanes:forward'], maxspeed);
+ maxspeedLanes.backward = parseMaxspeedLanes(tags['maxspeed:lanes:backward'], maxspeed);
+ var psvLanes = {};
+ psvLanes.unspecified = parseMiscLanes(tags['psv:lanes']);
+ psvLanes.forward = parseMiscLanes(tags['psv:lanes:forward']);
+ psvLanes.backward = parseMiscLanes(tags['psv:lanes:backward']);
+ var busLanes = {};
+ busLanes.unspecified = parseMiscLanes(tags['bus:lanes']);
+ busLanes.forward = parseMiscLanes(tags['bus:lanes:forward']);
+ busLanes.backward = parseMiscLanes(tags['bus:lanes:backward']);
+ var taxiLanes = {};
+ taxiLanes.unspecified = parseMiscLanes(tags['taxi:lanes']);
+ taxiLanes.forward = parseMiscLanes(tags['taxi:lanes:forward']);
+ taxiLanes.backward = parseMiscLanes(tags['taxi:lanes:backward']);
+ var hovLanes = {};
+ hovLanes.unspecified = parseMiscLanes(tags['hov:lanes']);
+ hovLanes.forward = parseMiscLanes(tags['hov:lanes:forward']);
+ hovLanes.backward = parseMiscLanes(tags['hov:lanes:backward']);
+ var hgvLanes = {};
+ hgvLanes.unspecified = parseMiscLanes(tags['hgv:lanes']);
+ hgvLanes.forward = parseMiscLanes(tags['hgv:lanes:forward']);
+ hgvLanes.backward = parseMiscLanes(tags['hgv:lanes:backward']);
+ var bicyclewayLanes = {};
+ bicyclewayLanes.unspecified = parseBicycleWay(tags['bicycleway:lanes']);
+ bicyclewayLanes.forward = parseBicycleWay(tags['bicycleway:lanes:forward']);
+ bicyclewayLanes.backward = parseBicycleWay(tags['bicycleway:lanes:backward']);
+ var lanesObj = {
+ forward: [],
+ backward: [],
+ unspecified: []
+ }; // map forward/backward/unspecified of each lane type to lanesObj
- var distance = geoVecLength(centroid, keyPoints[i]) || 1e-4;
- keyPoints[i] = [centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, centroid[1] + (keyPoints[i][1] - centroid[1]) / distance * radius];
- loc = projection.invert(keyPoints[i]);
- node = keyNodes[i];
- origNode = origNodes[node.id];
- node = node.move(geoVecInterp(origNode.loc, loc, t));
- graph = graph.replace(node); // figure out the between delta angle we want to match to
+ mapToLanesObj(lanesObj, turnLanes, 'turnLane');
+ mapToLanesObj(lanesObj, maxspeedLanes, 'maxspeed');
+ mapToLanesObj(lanesObj, psvLanes, 'psv');
+ mapToLanesObj(lanesObj, busLanes, 'bus');
+ mapToLanesObj(lanesObj, taxiLanes, 'taxi');
+ mapToLanesObj(lanesObj, hovLanes, 'hov');
+ mapToLanesObj(lanesObj, hgvLanes, 'hgv');
+ mapToLanesObj(lanesObj, bicyclewayLanes, 'bicycleway');
+ return {
+ metadata: {
+ count: laneCount,
+ oneway: isOneWay,
+ forward: forward,
+ backward: backward,
+ bothways: bothways,
+ turnLanes: turnLanes,
+ maxspeed: maxspeed,
+ maxspeedLanes: maxspeedLanes,
+ psvLanes: psvLanes,
+ busLanes: busLanes,
+ taxiLanes: taxiLanes,
+ hovLanes: hovLanes,
+ hgvLanes: hgvLanes,
+ bicyclewayLanes: bicyclewayLanes
+ },
+ lanes: lanesObj
+ };
+ }
- startAngle = Math.atan2(keyPoints[i][1] - centroid[1], keyPoints[i][0] - centroid[0]);
- endAngle = Math.atan2(keyPoints[nextKeyNodeIndex][1] - centroid[1], keyPoints[nextKeyNodeIndex][0] - centroid[0]);
- totalAngle = endAngle - startAngle; // detects looping around -pi/pi
+ function getLaneCount(tags, isOneWay) {
+ var count;
- if (totalAngle * sign > 0) {
- totalAngle = -sign * (2 * Math.PI - Math.abs(totalAngle));
- }
+ if (tags.lanes) {
+ count = parseInt(tags.lanes, 10);
- do {
- numberNewPoints++;
- eachAngle = totalAngle / (indexRange + numberNewPoints);
- } while (Math.abs(eachAngle) > maxAngle); // move existing nodes
+ if (count > 0) {
+ return count;
+ }
+ }
+ switch (tags.highway) {
+ case 'trunk':
+ case 'motorway':
+ count = isOneWay ? 2 : 4;
+ break;
- for (j = 1; j < indexRange; j++) {
- angle = startAngle + j * eachAngle;
- loc = projection.invert([centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]);
- node = nodes[(j + startNodeIndex) % nodes.length];
- origNode = origNodes[node.id];
- nearNodes[node.id] = angle;
- node = node.move(geoVecInterp(origNode.loc, loc, t));
- graph = graph.replace(node);
- } // add new in between nodes if necessary
+ default:
+ count = isOneWay ? 1 : 2;
+ break;
+ }
+ return count;
+ }
- for (j = 0; j < numberNewPoints; j++) {
- angle = startAngle + (indexRange + j) * eachAngle;
- loc = projection.invert([centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]); // choose a nearnode to use as the original
+ function parseMaxspeed(tags) {
+ var maxspeed = tags.maxspeed;
+ if (!maxspeed) return;
+ var maxspeedRegex = /^([0-9][\.0-9]+?)(?:[ ]?(?:km\/h|kmh|kph|mph|knots))?$/;
+ if (!maxspeedRegex.test(maxspeed)) return;
+ return parseInt(maxspeed, 10);
+ }
- var min = Infinity;
+ function parseLaneDirections(tags, isOneWay, laneCount) {
+ var forward = parseInt(tags['lanes:forward'], 10);
+ var backward = parseInt(tags['lanes:backward'], 10);
+ var bothways = parseInt(tags['lanes:both_ways'], 10) > 0 ? 1 : 0;
- for (var nodeId in nearNodes) {
- var nearAngle = nearNodes[nodeId];
- var dist = Math.abs(nearAngle - angle);
+ if (parseInt(tags.oneway, 10) === -1) {
+ forward = 0;
+ bothways = 0;
+ backward = laneCount;
+ } else if (isOneWay) {
+ forward = laneCount;
+ bothways = 0;
+ backward = 0;
+ } else if (isNaN(forward) && isNaN(backward)) {
+ backward = Math.floor((laneCount - bothways) / 2);
+ forward = laneCount - bothways - backward;
+ } else if (isNaN(forward)) {
+ if (backward > laneCount - bothways) {
+ backward = laneCount - bothways;
+ }
- if (dist < min) {
- min = dist;
- origNode = origNodes[nodeId];
- }
- }
+ forward = laneCount - bothways - backward;
+ } else if (isNaN(backward)) {
+ if (forward > laneCount - bothways) {
+ forward = laneCount - bothways;
+ }
- node = osmNode({
- loc: geoVecInterp(origNode.loc, loc, t)
- });
- graph = graph.replace(node);
- nodes.splice(endNodeIndex + j, 0, node);
- inBetweenNodes.push(node.id);
- } // Check for other ways that share these keyNodes..
- // If keyNodes are adjacent in both ways,
- // we can add inBetweenNodes to that shared way too..
+ backward = laneCount - bothways - forward;
+ }
+ return {
+ forward: forward,
+ backward: backward,
+ bothways: bothways
+ };
+ }
- if (indexRange === 1 && inBetweenNodes.length) {
- var startIndex1 = way.nodes.lastIndexOf(startNode.id);
- var endIndex1 = way.nodes.lastIndexOf(endNode.id);
- var wayDirection1 = endIndex1 - startIndex1;
+ function parseTurnLanes(tag) {
+ if (!tag) return;
+ var validValues = ['left', 'slight_left', 'sharp_left', 'through', 'right', 'slight_right', 'sharp_right', 'reverse', 'merge_to_left', 'merge_to_right', 'none'];
+ return tag.split('|').map(function (s) {
+ if (s === '') s = 'none';
+ return s.split(';').map(function (d) {
+ return validValues.indexOf(d) === -1 ? 'unknown' : d;
+ });
+ });
+ }
- if (wayDirection1 < -1) {
- wayDirection1 = 1;
- }
+ function parseMaxspeedLanes(tag, maxspeed) {
+ if (!tag) return;
+ return tag.split('|').map(function (s) {
+ if (s === 'none') return s;
+ var m = parseInt(s, 10);
+ if (s === '' || m === maxspeed) return null;
+ return isNaN(m) ? 'unknown' : m;
+ });
+ }
- var parentWays = graph.parentWays(keyNodes[i]);
+ function parseMiscLanes(tag) {
+ if (!tag) return;
+ var validValues = ['yes', 'no', 'designated'];
+ return tag.split('|').map(function (s) {
+ if (s === '') s = 'no';
+ return validValues.indexOf(s) === -1 ? 'unknown' : s;
+ });
+ }
- for (j = 0; j < parentWays.length; j++) {
- var sharedWay = parentWays[j];
- if (sharedWay === way) continue;
+ function parseBicycleWay(tag) {
+ if (!tag) return;
+ var validValues = ['yes', 'no', 'designated', 'lane'];
+ return tag.split('|').map(function (s) {
+ if (s === '') s = 'no';
+ return validValues.indexOf(s) === -1 ? 'unknown' : s;
+ });
+ }
- if (sharedWay.areAdjacent(startNode.id, endNode.id)) {
- var startIndex2 = sharedWay.nodes.lastIndexOf(startNode.id);
- var endIndex2 = sharedWay.nodes.lastIndexOf(endNode.id);
- var wayDirection2 = endIndex2 - startIndex2;
- var insertAt = endIndex2;
+ function mapToLanesObj(lanesObj, data, key) {
+ if (data.forward) {
+ data.forward.forEach(function (l, i) {
+ if (!lanesObj.forward[i]) lanesObj.forward[i] = {};
+ lanesObj.forward[i][key] = l;
+ });
+ }
- if (wayDirection2 < -1) {
- wayDirection2 = 1;
- }
+ if (data.backward) {
+ data.backward.forEach(function (l, i) {
+ if (!lanesObj.backward[i]) lanesObj.backward[i] = {};
+ lanesObj.backward[i][key] = l;
+ });
+ }
- if (wayDirection1 !== wayDirection2) {
- inBetweenNodes.reverse();
- insertAt = startIndex2;
- }
+ if (data.unspecified) {
+ data.unspecified.forEach(function (l, i) {
+ if (!lanesObj.unspecified[i]) lanesObj.unspecified[i] = {};
+ lanesObj.unspecified[i][key] = l;
+ });
+ }
+ }
- for (k = 0; k < inBetweenNodes.length; k++) {
- sharedWay = sharedWay.addNode(inBetweenNodes[k], insertAt + k);
- }
+ function osmWay() {
+ if (!(this instanceof osmWay)) {
+ return new osmWay().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
+ osmEntity.way = osmWay;
+ osmWay.prototype = Object.create(osmEntity.prototype);
+ Object.assign(osmWay.prototype, {
+ type: 'way',
+ nodes: [],
+ copy: function copy(resolver, copies) {
+ if (copies[this.id]) return copies[this.id];
+ var copy = osmEntity.prototype.copy.call(this, resolver, copies);
+ var nodes = this.nodes.map(function (id) {
+ return resolver.entity(id).copy(resolver, copies).id;
+ });
+ copy = copy.update({
+ nodes: nodes
+ });
+ copies[this.id] = copy;
+ return copy;
+ },
+ extent: function extent(resolver) {
+ return resolver["transient"](this, 'extent', function () {
+ var extent = geoExtent();
- graph = graph.replace(sharedWay);
- }
+ for (var i = 0; i < this.nodes.length; i++) {
+ var node = resolver.hasEntity(this.nodes[i]);
+
+ if (node) {
+ extent._extend(node.extent());
}
}
- } // update the way to have all the new nodes
-
- ids = nodes.map(function (n) {
- return n.id;
- });
- ids.push(ids[0]);
- way = way.update({
- nodes: ids
- });
- graph = graph.replace(way);
- return graph;
- };
-
- action.makeConvex = function (graph) {
- var way = graph.entity(wayId);
- var nodes = utilArrayUniq(graph.childNodes(way));
- var points = nodes.map(function (n) {
- return projection(n.loc);
+ return extent;
});
- var sign = d3_polygonArea(points) > 0 ? 1 : -1;
- var hull = d3_polygonHull(points);
- var i, j; // D3 convex hulls go counterclockwise..
+ },
+ first: function first() {
+ return this.nodes[0];
+ },
+ last: function last() {
+ return this.nodes[this.nodes.length - 1];
+ },
+ contains: function contains(node) {
+ return this.nodes.indexOf(node) >= 0;
+ },
+ affix: function affix(node) {
+ if (this.nodes[0] === node) return 'prefix';
+ if (this.nodes[this.nodes.length - 1] === node) return 'suffix';
+ },
+ layer: function layer() {
+ // explicit layer tag, clamp between -10, 10..
+ if (isFinite(this.tags.layer)) {
+ return Math.max(-10, Math.min(+this.tags.layer, 10));
+ } // implied layer tag..
- if (sign === -1) {
- nodes.reverse();
- points.reverse();
- }
- for (i = 0; i < hull.length - 1; i++) {
- var startIndex = points.indexOf(hull[i]);
- var endIndex = points.indexOf(hull[i + 1]);
- var indexRange = endIndex - startIndex;
+ if (this.tags.covered === 'yes') return -1;
+ if (this.tags.location === 'overground') return 1;
+ if (this.tags.location === 'underground') return -1;
+ if (this.tags.location === 'underwater') return -10;
+ if (this.tags.power === 'line') return 10;
+ if (this.tags.power === 'minor_line') return 10;
+ if (this.tags.aerialway) return 10;
+ if (this.tags.bridge) return 1;
+ if (this.tags.cutting) return -1;
+ if (this.tags.tunnel) return -1;
+ if (this.tags.waterway) return -1;
+ if (this.tags.man_made === 'pipeline') return -10;
+ if (this.tags.boundary) return -10;
+ return 0;
+ },
+ // the approximate width of the line based on its tags except its `width` tag
+ impliedLineWidthMeters: function impliedLineWidthMeters() {
+ var averageWidths = {
+ highway: {
+ // width is for single lane
+ motorway: 5,
+ motorway_link: 5,
+ trunk: 4.5,
+ trunk_link: 4.5,
+ primary: 4,
+ secondary: 4,
+ tertiary: 4,
+ primary_link: 4,
+ secondary_link: 4,
+ tertiary_link: 4,
+ unclassified: 4,
+ road: 4,
+ living_street: 4,
+ bus_guideway: 4,
+ pedestrian: 4,
+ residential: 3.5,
+ service: 3.5,
+ track: 3,
+ cycleway: 2.5,
+ bridleway: 2,
+ corridor: 2,
+ steps: 2,
+ path: 1.5,
+ footway: 1.5
+ },
+ railway: {
+ // width includes ties and rail bed, not just track gauge
+ rail: 2.5,
+ light_rail: 2.5,
+ tram: 2.5,
+ subway: 2.5,
+ monorail: 2.5,
+ funicular: 2.5,
+ disused: 2.5,
+ preserved: 2.5,
+ miniature: 1.5,
+ narrow_gauge: 1.5
+ },
+ waterway: {
+ river: 50,
+ canal: 25,
+ stream: 5,
+ tidal_channel: 5,
+ fish_pass: 2.5,
+ drain: 2.5,
+ ditch: 1.5
+ }
+ };
- if (indexRange < 0) {
- indexRange += nodes.length;
- } // move interior nodes to the surface of the convex hull..
+ for (var key in averageWidths) {
+ if (this.tags[key] && averageWidths[key][this.tags[key]]) {
+ var width = averageWidths[key][this.tags[key]];
+ if (key === 'highway') {
+ var laneCount = this.tags.lanes && parseInt(this.tags.lanes, 10);
+ if (!laneCount) laneCount = this.isOneWay() ? 1 : 2;
+ return width * laneCount;
+ }
- for (j = 1; j < indexRange; j++) {
- var point = geoVecInterp(hull[i], hull[i + 1], j / indexRange);
- var node = nodes[(j + startIndex) % nodes.length].move(projection.invert(point));
- graph = graph.replace(node);
+ return width;
}
}
- return graph;
- };
+ return null;
+ },
+ isOneWay: function isOneWay() {
+ // explicit oneway tag..
+ var values = {
+ 'yes': true,
+ '1': true,
+ '-1': true,
+ 'reversible': true,
+ 'alternating': true,
+ 'no': false,
+ '0': false
+ };
- action.disabled = function (graph) {
- if (!graph.entity(wayId).isClosed()) {
- return 'not_closed';
- } //disable when already circular
+ if (values[this.tags.oneway] !== undefined) {
+ return values[this.tags.oneway];
+ } // implied oneway tag..
- var way = graph.entity(wayId);
- var nodes = utilArrayUniq(graph.childNodes(way));
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var hull = d3_polygonHull(points);
- var epsilonAngle = Math.PI / 180;
+ for (var key in this.tags) {
+ if (key in osmOneWayTags && this.tags[key] in osmOneWayTags[key]) {
+ return true;
+ }
+ }
- if (hull.length !== points.length || hull.length < 3) {
+ return false;
+ },
+ // Some identifier for tag that implies that this way is "sided",
+ // i.e. the right side is the 'inside' (e.g. the right side of a
+ // natural=cliff is lower).
+ sidednessIdentifier: function sidednessIdentifier() {
+ for (var key in this.tags) {
+ var value = this.tags[key];
+
+ if (key in osmRightSideIsInsideTags && value in osmRightSideIsInsideTags[key]) {
+ if (osmRightSideIsInsideTags[key][value] === true) {
+ return key;
+ } else {
+ // if the map's value is something other than a
+ // literal true, we should use it so we can
+ // special case some keys (e.g. natural=coastline
+ // is handled differently to other naturals).
+ return osmRightSideIsInsideTags[key][value];
+ }
+ }
+ }
+
+ return null;
+ },
+ isSided: function isSided() {
+ if (this.tags.two_sided === 'yes') {
return false;
}
- var centroid = d3_polygonCentroid(points);
- var radius = geoVecLengthSquare(centroid, points[0]);
- var i, actualPoint; // compare distances between centroid and points
+ return this.sidednessIdentifier() !== null;
+ },
+ lanes: function lanes() {
+ return osmLanes(this);
+ },
+ isClosed: function isClosed() {
+ return this.nodes.length > 1 && this.first() === this.last();
+ },
+ isConvex: function isConvex(resolver) {
+ if (!this.isClosed() || this.isDegenerate()) return null;
+ var nodes = utilArrayUniq(resolver.childNodes(this));
+ var coords = nodes.map(function (n) {
+ return n.loc;
+ });
+ var curr = 0;
+ var prev = 0;
- for (i = 0; i < hull.length; i++) {
- actualPoint = hull[i];
- var actualDist = geoVecLengthSquare(actualPoint, centroid);
- var diff = Math.abs(actualDist - radius); //compare distances with epsilon-error (5%)
+ for (var i = 0; i < coords.length; i++) {
+ var o = coords[(i + 1) % coords.length];
+ var a = coords[i];
+ var b = coords[(i + 2) % coords.length];
+ var res = geoVecCross(a, b, o);
+ curr = res > 0 ? 1 : res < 0 ? -1 : 0;
- if (diff > 0.05 * radius) {
+ if (curr === 0) {
+ continue;
+ } else if (prev && curr !== prev) {
return false;
}
- } //check if central angles are smaller than maxAngle
-
- for (i = 0; i < hull.length; i++) {
- actualPoint = hull[i];
- var nextPoint = hull[(i + 1) % hull.length];
- var startAngle = Math.atan2(actualPoint[1] - centroid[1], actualPoint[0] - centroid[0]);
- var endAngle = Math.atan2(nextPoint[1] - centroid[1], nextPoint[0] - centroid[0]);
- var angle = endAngle - startAngle;
+ prev = curr;
+ }
- if (angle < 0) {
- angle = -angle;
+ return true;
+ },
+ // returns an object with the tag that implies this is an area, if any
+ tagSuggestingArea: function tagSuggestingArea() {
+ return osmTagSuggestingArea(this.tags);
+ },
+ isArea: function isArea() {
+ if (this.tags.area === 'yes') return true;
+ if (!this.isClosed() || this.tags.area === 'no') return false;
+ return this.tagSuggestingArea() !== null;
+ },
+ isDegenerate: function isDegenerate() {
+ return new Set(this.nodes).size < (this.isArea() ? 3 : 2);
+ },
+ areAdjacent: function areAdjacent(n1, n2) {
+ for (var i = 0; i < this.nodes.length; i++) {
+ if (this.nodes[i] === n1) {
+ if (this.nodes[i - 1] === n2) return true;
+ if (this.nodes[i + 1] === n2) return true;
}
+ }
- if (angle > Math.PI) {
- angle = 2 * Math.PI - angle;
- }
+ return false;
+ },
+ geometry: function geometry(graph) {
+ return graph["transient"](this, 'geometry', function () {
+ return this.isArea() ? 'area' : 'line';
+ });
+ },
+ // returns an array of objects representing the segments between the nodes in this way
+ segments: function segments(graph) {
+ function segmentExtent(graph) {
+ var n1 = graph.hasEntity(this.nodes[0]);
+ var n2 = graph.hasEntity(this.nodes[1]);
+ return n1 && n2 && geoExtent([[Math.min(n1.loc[0], n2.loc[0]), Math.min(n1.loc[1], n2.loc[1])], [Math.max(n1.loc[0], n2.loc[0]), Math.max(n1.loc[1], n2.loc[1])]]);
+ }
- if (angle > maxAngle + epsilonAngle) {
- return false;
+ return graph["transient"](this, 'segments', function () {
+ var segments = [];
+
+ for (var i = 0; i < this.nodes.length - 1; i++) {
+ segments.push({
+ id: this.id + '-' + i,
+ wayId: this.id,
+ index: i,
+ nodes: [this.nodes[i], this.nodes[i + 1]],
+ extent: segmentExtent
+ });
}
- }
- return 'already_circular';
- };
+ return segments;
+ });
+ },
+ // If this way is not closed, append the beginning node to the end of the nodelist to close it.
+ close: function close() {
+ if (this.isClosed() || !this.nodes.length) return this;
+ var nodes = this.nodes.slice();
+ nodes = nodes.filter(noRepeatNodes);
+ nodes.push(nodes[0]);
+ return this.update({
+ nodes: nodes
+ });
+ },
+ // If this way is closed, remove any connector nodes from the end of the nodelist to unclose it.
+ unclose: function unclose() {
+ if (!this.isClosed()) return this;
+ var nodes = this.nodes.slice();
+ var connector = this.first();
+ var i = nodes.length - 1; // remove trailing connectors..
- action.transitionable = true;
- return action;
- }
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ i = nodes.length - 1;
+ }
- function actionDeleteWay(wayID) {
- function canDeleteNode(node, graph) {
- // don't delete nodes still attached to ways or relations
- if (graph.parentWays(node).length || graph.parentRelations(node).length) return false;
- var geometries = osmNodeGeometriesForTags(node.tags); // don't delete if this node can be a standalone point
+ nodes = nodes.filter(noRepeatNodes);
+ return this.update({
+ nodes: nodes
+ });
+ },
+ // Adds a node (id) in front of the node which is currently at position index.
+ // If index is undefined, the node will be added to the end of the way for linear ways,
+ // or just before the final connecting node for circular ways.
+ // Consecutive duplicates are eliminated including existing ones.
+ // Circularity is always preserved when adding a node.
+ addNode: function addNode(id, index) {
+ var nodes = this.nodes.slice();
+ var isClosed = this.isClosed();
+ var max = isClosed ? nodes.length - 1 : nodes.length;
- if (geometries.point) return false; // delete if this node only be a vertex
+ if (index === undefined) {
+ index = max;
+ }
- if (geometries.vertex) return true; // iD doesn't know if this should be a point or vertex,
- // so only delete if there are no interesting tags
+ if (index < 0 || index > max) {
+ throw new RangeError('index ' + index + ' out of range 0..' + max);
+ } // If this is a closed way, remove all connector nodes except the first one
+ // (there may be duplicates) and adjust index if necessary..
- return !node.hasInterestingTags();
- }
- var action = function action(graph) {
- var way = graph.entity(wayID);
- graph.parentRelations(way).forEach(function (parent) {
- parent = parent.removeMembersWithID(wayID);
- graph = graph.replace(parent);
+ if (isClosed) {
+ var connector = this.first(); // leading connectors..
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(graph);
- }
- });
- new Set(way.nodes).forEach(function (nodeID) {
- graph = graph.replace(way.removeNode(nodeID));
- var node = graph.entity(nodeID);
+ var i = 1;
- if (canDeleteNode(node, graph)) {
- graph = graph.remove(node);
- }
- });
- return graph.remove(way);
- };
+ while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ } // trailing connectors..
- return action;
- }
- function actionDeleteMultiple(ids) {
- var actions = {
- way: actionDeleteWay,
- node: actionDeleteNode,
- relation: actionDeleteRelation
- };
+ i = nodes.length - 1;
- var action = function action(graph) {
- ids.forEach(function (id) {
- if (graph.hasEntity(id)) {
- // It may have been deleted already.
- graph = actions[graph.entity(id).type](id)(graph);
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ i = nodes.length - 1;
}
- });
- return graph;
- };
+ }
- return action;
- }
+ nodes.splice(index, 0, id);
+ nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
- function actionDeleteRelation(relationID, allowUntaggedMembers) {
- function canDeleteEntity(entity, graph) {
- return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags() && !allowUntaggedMembers;
- }
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
- var action = function action(graph) {
- var relation = graph.entity(relationID);
- graph.parentRelations(relation).forEach(function (parent) {
- parent = parent.removeMembersWithID(relationID);
- graph = graph.replace(parent);
+ return this.update({
+ nodes: nodes
+ });
+ },
+ // Replaces the node which is currently at position index with the given node (id).
+ // Consecutive duplicates are eliminated including existing ones.
+ // Circularity is preserved when updating a node.
+ updateNode: function updateNode(id, index) {
+ var nodes = this.nodes.slice();
+ var isClosed = this.isClosed();
+ var max = nodes.length - 1;
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(graph);
+ if (index === undefined || index < 0 || index > max) {
+ throw new RangeError('index ' + index + ' out of range 0..' + max);
+ } // If this is a closed way, remove all connector nodes except the first one
+ // (there may be duplicates) and adjust index if necessary..
+
+
+ if (isClosed) {
+ var connector = this.first(); // leading connectors..
+
+ var i = 1;
+
+ while (i < nodes.length && nodes.length > 2 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index > i) index--;
+ } // trailing connectors..
+
+
+ i = nodes.length - 1;
+
+ while (i > 0 && nodes.length > 1 && nodes[i] === connector) {
+ nodes.splice(i, 1);
+ if (index === i) index = 0; // update leading connector instead
+
+ i = nodes.length - 1;
}
+ }
+
+ nodes.splice(index, 1, id);
+ nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
+
+ return this.update({
+ nodes: nodes
});
- var memberIDs = utilArrayUniq(relation.members.map(function (m) {
- return m.id;
- }));
- memberIDs.forEach(function (memberID) {
- graph = graph.replace(relation.removeMembersWithID(memberID));
- var entity = graph.entity(memberID);
+ },
+ // Replaces each occurrence of node id needle with replacement.
+ // Consecutive duplicates are eliminated including existing ones.
+ // Circularity is preserved.
+ replaceNode: function replaceNode(needleID, replacementID) {
+ var nodes = this.nodes.slice();
+ var isClosed = this.isClosed();
- if (canDeleteEntity(entity, graph)) {
- graph = actionDeleteMultiple([memberID])(graph);
+ for (var i = 0; i < nodes.length; i++) {
+ if (nodes[i] === needleID) {
+ nodes[i] = replacementID;
}
+ }
+
+ nodes = nodes.filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
+
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
+
+ return this.update({
+ nodes: nodes
});
- return graph.remove(relation);
- };
+ },
+ // Removes each occurrence of node id.
+ // Consecutive duplicates are eliminated including existing ones.
+ // Circularity is preserved.
+ removeNode: function removeNode(id) {
+ var nodes = this.nodes.slice();
+ var isClosed = this.isClosed();
+ nodes = nodes.filter(function (node) {
+ return node !== id;
+ }).filter(noRepeatNodes); // If the way was closed before, append a connector node to keep it closed..
- return action;
- }
+ if (isClosed && (nodes.length === 1 || nodes[0] !== nodes[nodes.length - 1])) {
+ nodes.push(nodes[0]);
+ }
- function actionDeleteNode(nodeId) {
- var action = function action(graph) {
- var node = graph.entity(nodeId);
- graph.parentWays(node).forEach(function (parent) {
- parent = parent.removeNode(nodeId);
- graph = graph.replace(parent);
+ return this.update({
+ nodes: nodes
+ });
+ },
+ asJXON: function asJXON(changeset_id) {
+ var r = {
+ way: {
+ '@id': this.osmId(),
+ '@version': this.version || 0,
+ nd: this.nodes.map(function (id) {
+ return {
+ keyAttributes: {
+ ref: osmEntity.id.toOSM(id)
+ }
+ };
+ }, this),
+ tag: Object.keys(this.tags).map(function (k) {
+ return {
+ keyAttributes: {
+ k: k,
+ v: this.tags[k]
+ }
+ };
+ }, this)
+ }
+ };
- if (parent.isDegenerate()) {
- graph = actionDeleteWay(parent.id)(graph);
+ if (changeset_id) {
+ r.way['@changeset'] = changeset_id;
+ }
+
+ return r;
+ },
+ asGeoJSON: function asGeoJSON(resolver) {
+ return resolver["transient"](this, 'GeoJSON', function () {
+ var coordinates = resolver.childNodes(this).map(function (n) {
+ return n.loc;
+ });
+
+ if (this.isArea() && this.isClosed()) {
+ return {
+ type: 'Polygon',
+ coordinates: [coordinates]
+ };
+ } else {
+ return {
+ type: 'LineString',
+ coordinates: coordinates
+ };
}
});
- graph.parentRelations(node).forEach(function (parent) {
- parent = parent.removeMembersWithID(nodeId);
- graph = graph.replace(parent);
+ },
+ area: function area(resolver) {
+ return resolver["transient"](this, 'area', function () {
+ var nodes = resolver.childNodes(this);
+ var json = {
+ type: 'Polygon',
+ coordinates: [nodes.map(function (n) {
+ return n.loc;
+ })]
+ };
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(graph);
+ if (!this.isClosed() && nodes.length) {
+ json.coordinates[0].push(nodes[0].loc);
+ }
+
+ var area = d3_geoArea(json); // Heuristic for detecting counterclockwise winding order. Assumes
+ // that OpenStreetMap polygons are not hemisphere-spanning.
+
+ if (area > 2 * Math.PI) {
+ json.coordinates[0] = json.coordinates[0].reverse();
+ area = d3_geoArea(json);
}
+
+ return isNaN(area) ? 0 : area;
});
- return graph.remove(node);
- };
+ }
+ }); // Filter function to eliminate consecutive duplicates.
- return action;
+ function noRepeatNodes(node, i, arr) {
+ return i === 0 || node !== arr[i - 1];
}
//
- // First choose a node to be the survivor, with preference given
- // to an existing (not new) node.
- //
- // Tags and relation memberships of of non-surviving nodes are merged
- // to the survivor.
- //
- // This is the inverse of `iD.actionDisconnect`.
- //
- // Reference:
- // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as
- // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java
+ // 1. Relation tagged with `type=multipolygon` and no interesting tags.
+ // 2. One and only one member with the `outer` role. Must be a way with interesting tags.
+ // 3. No members without a role.
//
+ // Old multipolygons are no longer recommended but are still rendered as areas by iD.
- function actionConnect(nodeIDs) {
- var action = function action(graph) {
- var survivor;
- var node;
- var parents;
- var i, j; // Choose a survivor node, prefer an existing (not new) node - #4974
+ function osmOldMultipolygonOuterMemberOfRelation(entity, graph) {
+ if (entity.type !== 'relation' || !entity.isMultipolygon() || Object.keys(entity.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
+ }
- for (i = 0; i < nodeIDs.length; i++) {
- survivor = graph.entity(nodeIDs[i]);
- if (survivor.version) break; // found one
- } // Replace all non-surviving nodes with the survivor and merge tags.
+ var outerMember;
+ for (var memberIndex in entity.members) {
+ var member = entity.members[memberIndex];
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- if (node.id === survivor.id) continue;
- parents = graph.parentWays(node);
+ if (!member.role || member.role === 'outer') {
+ if (outerMember) return false;
+ if (member.type !== 'way') return false;
+ if (!graph.hasEntity(member.id)) return false;
+ outerMember = graph.entity(member.id);
- for (j = 0; j < parents.length; j++) {
- graph = graph.replace(parents[j].replaceNode(node.id, survivor.id));
+ if (Object.keys(outerMember.tags).filter(osmIsInterestingTag).length === 0) {
+ return false;
}
+ }
+ }
- parents = graph.parentRelations(node);
+ return outerMember;
+ } // For fixing up rendering of multipolygons with tags on the outer member.
+ // https://github.com/openstreetmap/iD/issues/613
- for (j = 0; j < parents.length; j++) {
- graph = graph.replace(parents[j].replaceMember(node, survivor));
- }
+ function osmIsOldMultipolygonOuterMember(entity, graph) {
+ if (entity.type !== 'way' || Object.keys(entity.tags).filter(osmIsInterestingTag).length === 0) {
+ return false;
+ }
- survivor = survivor.mergeTags(node.tags);
- graph = actionDeleteNode(node.id)(graph);
- }
+ var parents = graph.parentRelations(entity);
+ if (parents.length !== 1) return false;
+ var parent = parents[0];
- graph = graph.replace(survivor); // find and delete any degenerate ways created by connecting adjacent vertices
+ if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
+ }
- parents = graph.parentWays(survivor);
+ var members = parent.members,
+ member;
- for (i = 0; i < parents.length; i++) {
- if (parents[i].isDegenerate()) {
- graph = actionDeleteWay(parents[i].id)(graph);
- }
+ for (var i = 0; i < members.length; i++) {
+ member = members[i];
+
+ if (member.id === entity.id && member.role && member.role !== 'outer') {
+ // Not outer member
+ return false;
}
- return graph;
- };
+ if (member.id !== entity.id && (!member.role || member.role === 'outer')) {
+ // Not a simple multipolygon
+ return false;
+ }
+ }
- action.disabled = function (graph) {
- var seen = {};
- var restrictionIDs = [];
- var survivor;
- var node, way;
- var relations, relation, role;
- var i, j, k; // Choose a survivor node, prefer an existing (not new) node - #4974
+ return parent;
+ }
+ function osmOldMultipolygonOuterMember(entity, graph) {
+ if (entity.type !== 'way') return false;
+ var parents = graph.parentRelations(entity);
+ if (parents.length !== 1) return false;
+ var parent = parents[0];
- for (i = 0; i < nodeIDs.length; i++) {
- survivor = graph.entity(nodeIDs[i]);
- if (survivor.version) break; // found one
- } // 1. disable if the nodes being connected have conflicting relation roles
+ if (!parent.isMultipolygon() || Object.keys(parent.tags).filter(osmIsInterestingTag).length > 1) {
+ return false;
+ }
+ var members = parent.members,
+ member,
+ outerMember;
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- relations = graph.parentRelations(node);
+ for (var i = 0; i < members.length; i++) {
+ member = members[i];
- for (j = 0; j < relations.length; j++) {
- relation = relations[j];
- role = relation.memberById(node.id).role || ''; // if this node is a via node in a restriction, remember for later
+ if (!member.role || member.role === 'outer') {
+ if (outerMember) return false; // Not a simple multipolygon
- if (relation.hasFromViaTo()) {
- restrictionIDs.push(relation.id);
- }
+ outerMember = member;
+ }
+ }
- if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
- return 'relation';
- } else {
- seen[relation.id] = role;
- }
- }
- } // gather restrictions for parent ways
+ if (!outerMember) return false;
+ var outerEntity = graph.hasEntity(outerMember.id);
+ if (!outerEntity || !Object.keys(outerEntity.tags).filter(osmIsInterestingTag).length) {
+ return false;
+ }
- for (i = 0; i < nodeIDs.length; i++) {
- node = graph.entity(nodeIDs[i]);
- var parents = graph.parentWays(node);
+ return outerEntity;
+ } // Join `toJoin` array into sequences of connecting ways.
+ // Segments which share identical start/end nodes will, as much as possible,
+ // be connected with each other.
+ //
+ // The return value is a nested array. Each constituent array contains elements
+ // of `toJoin` which have been determined to connect.
+ //
+ // Each consitituent array also has a `nodes` property whose value is an
+ // ordered array of member nodes, with appropriate order reversal and
+ // start/end coordinate de-duplication.
+ //
+ // Members of `toJoin` must have, at minimum, `type` and `id` properties.
+ // Thus either an array of `osmWay`s or a relation member array may be used.
+ //
+ // If an member is an `osmWay`, its tags and childnodes may be reversed via
+ // `actionReverse` in the output.
+ //
+ // The returned sequences array also has an `actions` array property, containing
+ // any reversal actions that should be applied to the graph, should the calling
+ // code attempt to actually join the given ways.
+ //
+ // Incomplete members (those for which `graph.hasEntity(element.id)` returns
+ // false) and non-way members are ignored.
+ //
- for (j = 0; j < parents.length; j++) {
- var parent = parents[j];
- relations = graph.parentRelations(parent);
+ function osmJoinWays(toJoin, graph) {
+ function resolve(member) {
+ return graph.childNodes(graph.entity(member.id));
+ }
- for (k = 0; k < relations.length; k++) {
- relation = relations[k];
+ function reverse(item) {
+ var action = actionReverse(item.id, {
+ reverseOneway: true
+ });
+ sequences.actions.push(action);
+ return item instanceof osmWay ? action(graph).entity(item.id) : item;
+ } // make a copy containing only the items to join
- if (relation.hasFromViaTo()) {
- restrictionIDs.push(relation.id);
- }
- }
- }
- } // test restrictions
+ toJoin = toJoin.filter(function (member) {
+ return member.type === 'way' && graph.hasEntity(member.id);
+ }); // Are the things we are joining relation members or `osmWays`?
+ // If `osmWays`, skip the "prefer a forward path" code below (see #4872)
- restrictionIDs = utilArrayUniq(restrictionIDs);
+ var i;
+ var joinAsMembers = true;
- for (i = 0; i < restrictionIDs.length; i++) {
- relation = graph.entity(restrictionIDs[i]);
- if (!relation.isComplete(graph)) continue;
- var memberWays = relation.members.filter(function (m) {
- return m.type === 'way';
- }).map(function (m) {
- return graph.entity(m.id);
- });
- memberWays = utilArrayUniq(memberWays);
- var f = relation.memberByRole('from');
- var t = relation.memberByRole('to');
- var isUturn = f.id === t.id; // 2a. disable if connection would damage a restriction
- // (a key node is a node at the junction of ways)
+ for (i = 0; i < toJoin.length; i++) {
+ if (toJoin[i] instanceof osmWay) {
+ joinAsMembers = false;
+ break;
+ }
+ }
- var nodes = {
- from: [],
- via: [],
- to: [],
- keyfrom: [],
- keyto: []
- };
+ var sequences = [];
+ sequences.actions = [];
- for (j = 0; j < relation.members.length; j++) {
- collectNodes(relation.members[j], nodes);
- }
+ while (toJoin.length) {
+ // start a new sequence
+ var item = toJoin.shift();
+ var currWays = [item];
+ var currNodes = resolve(item).slice(); // add to it
- nodes.keyfrom = utilArrayUniq(nodes.keyfrom.filter(hasDuplicates));
- nodes.keyto = utilArrayUniq(nodes.keyto.filter(hasDuplicates));
- var filter = keyNodeFilter(nodes.keyfrom, nodes.keyto);
- nodes.from = nodes.from.filter(filter);
- nodes.via = nodes.via.filter(filter);
- nodes.to = nodes.to.filter(filter);
- var connectFrom = false;
- var connectVia = false;
- var connectTo = false;
- var connectKeyFrom = false;
- var connectKeyTo = false;
+ while (toJoin.length) {
+ var start = currNodes[0];
+ var end = currNodes[currNodes.length - 1];
+ var fn = null;
+ var nodes = null; // Find the next way/member to join.
- for (j = 0; j < nodeIDs.length; j++) {
- var n = nodeIDs[j];
+ for (i = 0; i < toJoin.length; i++) {
+ item = toJoin[i];
+ nodes = resolve(item); // (for member ordering only, not way ordering - see #4872)
+ // Strongly prefer to generate a forward path that preserves the order
+ // of the members array. For multipolygons and most relations, member
+ // order does not matter - but for routes, it does. (see #4589)
+ // If we started this sequence backwards (i.e. next member way attaches to
+ // the start node and not the end node), reverse the initial way before continuing.
- if (nodes.from.indexOf(n) !== -1) {
- connectFrom = true;
+ if (joinAsMembers && currWays.length === 1 && nodes[0] !== end && nodes[nodes.length - 1] !== end && (nodes[nodes.length - 1] === start || nodes[0] === start)) {
+ currWays[0] = reverse(currWays[0]);
+ currNodes.reverse();
+ start = currNodes[0];
+ end = currNodes[currNodes.length - 1];
}
- if (nodes.via.indexOf(n) !== -1) {
- connectVia = true;
- }
+ if (nodes[0] === end) {
+ fn = currNodes.push; // join to end
- if (nodes.to.indexOf(n) !== -1) {
- connectTo = true;
- }
+ nodes = nodes.slice(1);
+ break;
+ } else if (nodes[nodes.length - 1] === end) {
+ fn = currNodes.push; // join to end
- if (nodes.keyfrom.indexOf(n) !== -1) {
- connectKeyFrom = true;
- }
+ nodes = nodes.slice(0, -1).reverse();
+ item = reverse(item);
+ break;
+ } else if (nodes[nodes.length - 1] === start) {
+ fn = currNodes.unshift; // join to beginning
- if (nodes.keyto.indexOf(n) !== -1) {
- connectKeyTo = true;
+ nodes = nodes.slice(0, -1);
+ break;
+ } else if (nodes[0] === start) {
+ fn = currNodes.unshift; // join to beginning
+
+ nodes = nodes.slice(1).reverse();
+ item = reverse(item);
+ break;
+ } else {
+ fn = nodes = null;
}
}
- if (connectFrom && connectTo && !isUturn) {
- return 'restriction';
+ if (!nodes) {
+ // couldn't find a joinable way/member
+ break;
}
- if (connectFrom && connectVia) {
- return 'restriction';
- }
+ fn.apply(currWays, [item]);
+ fn.apply(currNodes, nodes);
+ toJoin.splice(i, 1);
+ }
- if (connectTo && connectVia) {
- return 'restriction';
- } // connecting to a key node -
- // if both nodes are on a member way (i.e. part of the turn restriction),
- // the connecting node must be adjacent to the key node.
+ currWays.nodes = currNodes;
+ sequences.push(currWays);
+ }
+ return sequences;
+ }
- if (connectKeyFrom || connectKeyTo) {
- if (nodeIDs.length !== 2) {
- return 'restriction';
- }
+ function actionAddMember(relationId, member, memberIndex, insertPair) {
+ return function action(graph) {
+ var relation = graph.entity(relationId); // There are some special rules for Public Transport v2 routes.
- var n0 = null;
- var n1 = null;
-
- for (j = 0; j < memberWays.length; j++) {
- way = memberWays[j];
-
- if (way.contains(nodeIDs[0])) {
- n0 = nodeIDs[0];
- }
-
- if (way.contains(nodeIDs[1])) {
- n1 = nodeIDs[1];
- }
- }
-
- if (n0 && n1) {
- // both nodes are part of the restriction
- var ok = false;
-
- for (j = 0; j < memberWays.length; j++) {
- way = memberWays[j];
+ var isPTv2 = /stop|platform/.test(member.role);
- if (way.areAdjacent(n0, n1)) {
- ok = true;
- break;
- }
- }
+ if ((isNaN(memberIndex) || insertPair) && member.type === 'way' && !isPTv2) {
+ // Try to perform sensible inserts based on how the ways join together
+ graph = addWayMember(relation, graph);
+ } else {
+ // see https://wiki.openstreetmap.org/wiki/Public_transport#Service_routes
+ // Stops and Platforms for PTv2 should be ordered first.
+ // hack: We do not currently have the ability to place them in the exactly correct order.
+ if (isPTv2 && isNaN(memberIndex)) {
+ memberIndex = 0;
+ }
- if (!ok) {
- return 'restriction';
- }
- }
- } // 2b. disable if nodes being connected will destroy a member way in a restriction
- // (to test, make a copy and try actually connecting the nodes)
+ graph = graph.replace(relation.addMember(member, memberIndex));
+ }
+ return graph;
+ }; // Add a way member into the relation "wherever it makes sense".
+ // In this situation we were not supplied a memberIndex.
- for (j = 0; j < memberWays.length; j++) {
- way = memberWays[j].update({}); // make copy
+ function addWayMember(relation, graph) {
+ var groups, tempWay, insertPairIsReversed, item, i, j, k; // remove PTv2 stops and platforms before doing anything.
- for (k = 0; k < nodeIDs.length; k++) {
- if (nodeIDs[k] === survivor.id) continue;
+ var PTv2members = [];
+ var members = [];
- if (way.areAdjacent(nodeIDs[k], survivor.id)) {
- way = way.removeNode(nodeIDs[k]);
- } else {
- way = way.replaceNode(nodeIDs[k], survivor.id);
- }
- }
+ for (i = 0; i < relation.members.length; i++) {
+ var m = relation.members[i];
- if (way.isDegenerate()) {
- return 'restriction';
- }
+ if (/stop|platform/.test(m.role)) {
+ PTv2members.push(m);
+ } else {
+ members.push(m);
}
}
- return false; // if a key node appears multiple times (indexOf !== lastIndexOf) it's a FROM-VIA or TO-VIA junction
-
- function hasDuplicates(n, i, arr) {
- return arr.indexOf(n) !== arr.lastIndexOf(n);
- }
+ relation = relation.update({
+ members: members
+ });
- function keyNodeFilter(froms, tos) {
- return function (n) {
- return froms.indexOf(n) === -1 && tos.indexOf(n) === -1;
+ if (insertPair) {
+ // We're adding a member that must stay paired with an existing member.
+ // (This feature is used by `actionSplit`)
+ //
+ // This is tricky because the members may exist multiple times in the
+ // member list, and with different A-B/B-A ordering and different roles.
+ // (e.g. a bus route that loops out and back - #4589).
+ //
+ // Replace the existing member with a temporary way,
+ // so that `osmJoinWays` can treat the pair like a single way.
+ tempWay = osmWay({
+ id: 'wTemp',
+ nodes: insertPair.nodes
+ });
+ graph = graph.replace(tempWay);
+ var tempMember = {
+ id: tempWay.id,
+ type: 'way',
+ role: member.role
};
- }
+ var tempRelation = relation.replaceMember({
+ id: insertPair.originalID
+ }, tempMember, true);
+ groups = utilArrayGroupBy(tempRelation.members, 'type');
+ groups.way = groups.way || []; // Insert pair is reversed if the inserted way comes before the original one.
+ // (Except when they form a loop.)
- function collectNodes(member, collection) {
- var entity = graph.hasEntity(member.id);
- if (!entity) return;
- var role = member.role || '';
+ var originalWay = graph.entity(insertPair.originalID);
+ var insertedWay = graph.entity(insertPair.insertedID);
+ insertPairIsReversed = originalWay.nodes.length > 0 && insertedWay.nodes.length > 0 && insertedWay.nodes[insertedWay.nodes.length - 1] === originalWay.nodes[0] && originalWay.nodes[originalWay.nodes.length - 1] !== insertedWay.nodes[0];
+ } else {
+ // Add the member anywhere, one time. Just push and let `osmJoinWays` decide where to put it.
+ groups = utilArrayGroupBy(relation.members, 'type');
+ groups.way = groups.way || [];
+ groups.way.push(member);
+ }
- if (!collection[role]) {
- collection[role] = [];
- }
+ members = withIndex(groups.way);
+ var joined = osmJoinWays(members, graph); // `joined` might not contain all of the way members,
+ // But will contain only the completed (downloaded) members
- if (member.type === 'node') {
- collection[role].push(member.id);
+ for (i = 0; i < joined.length; i++) {
+ var segment = joined[i];
+ var nodes = segment.nodes.slice();
+ var startIndex = segment[0].index; // j = array index in `members` where this segment starts
- if (role === 'via') {
- collection.keyfrom.push(member.id);
- collection.keyto.push(member.id);
+ for (j = 0; j < members.length; j++) {
+ if (members[j].index === startIndex) {
+ break;
}
- } else if (member.type === 'way') {
- collection[role].push.apply(collection[role], entity.nodes);
+ } // k = each member in segment
- if (role === 'from' || role === 'via') {
- collection.keyfrom.push(entity.first());
- collection.keyfrom.push(entity.last());
- }
- if (role === 'to' || role === 'via') {
- collection.keyto.push(entity.first());
- collection.keyto.push(entity.last());
- }
- }
- }
- };
+ for (k = 0; k < segment.length; k++) {
+ item = segment[k];
+ var way = graph.entity(item.id); // If this is a paired item, generate members in correct order and role
- return action;
- }
+ if (tempWay && item.id === tempWay.id) {
+ var reverse = nodes[0].id !== insertPair.nodes[0] ^ insertPairIsReversed;
- function actionCopyEntities(ids, fromGraph) {
- var _copies = {};
+ if (reverse) {
+ item.pair = [{
+ id: insertPair.insertedID,
+ type: 'way',
+ role: item.role
+ }, {
+ id: insertPair.originalID,
+ type: 'way',
+ role: item.role
+ }];
+ } else {
+ item.pair = [{
+ id: insertPair.originalID,
+ type: 'way',
+ role: item.role
+ }, {
+ id: insertPair.insertedID,
+ type: 'way',
+ role: item.role
+ }];
+ }
+ } // reorder `members` if necessary
- var action = function action(graph) {
- ids.forEach(function (id) {
- fromGraph.entity(id).copy(fromGraph, _copies);
- });
- for (var id in _copies) {
- graph = graph.replace(_copies[id]);
- }
+ if (k > 0) {
+ if (j + k >= members.length || item.index !== members[j + k].index) {
+ moveMember(members, item.index, j + k);
+ }
+ }
- return graph;
- };
+ nodes.splice(0, way.nodes.length - 1);
+ }
+ }
- action.copies = function () {
- return _copies;
- };
+ if (tempWay) {
+ graph = graph.remove(tempWay);
+ } // Final pass: skip dead items, split pairs, remove index properties
- return action;
- }
- function actionDeleteMember(relationId, memberIndex) {
- return function (graph) {
- var relation = graph.entity(relationId).removeMember(memberIndex);
- graph = graph.replace(relation);
+ var wayMembers = [];
- if (relation.isDegenerate()) {
- graph = actionDeleteRelation(relation.id)(graph);
- }
+ for (i = 0; i < members.length; i++) {
+ item = members[i];
+ if (item.index === -1) continue;
- return graph;
- };
- }
+ if (item.pair) {
+ wayMembers.push(item.pair[0]);
+ wayMembers.push(item.pair[1]);
+ } else {
+ wayMembers.push(utilObjectOmit(item, ['index']));
+ }
+ } // Put stops and platforms first, then nodes, ways, relations
+ // This is recommended for Public Transport v2 routes:
+ // see https://wiki.openstreetmap.org/wiki/Public_transport#Service_routes
- function actionDiscardTags(difference, discardTags) {
- discardTags = discardTags || {};
- return function (graph) {
- difference.modified().forEach(checkTags);
- difference.created().forEach(checkTags);
- return graph;
- function checkTags(entity) {
- var keys = Object.keys(entity.tags);
- var didDiscard = false;
- var tags = {};
+ var newMembers = PTv2members.concat(groups.node || [], wayMembers, groups.relation || []);
+ return graph.replace(relation.update({
+ members: newMembers
+ })); // `moveMember()` changes the `members` array in place by splicing
+ // the item with `.index = findIndex` to where it belongs,
+ // and marking the old position as "dead" with `.index = -1`
+ //
+ // j=5, k=0 jk
+ // segment 5 4 7 6
+ // members 0 1 2 3 4 5 6 7 8 9 keep 5 in j+k
+ //
+ // j=5, k=1 j k
+ // segment 5 4 7 6
+ // members 0 1 2 3 4 5 6 7 8 9 move 4 to j+k
+ // members 0 1 2 3 x 5 4 6 7 8 9 moved
+ //
+ // j=5, k=2 j k
+ // segment 5 4 7 6
+ // members 0 1 2 3 x 5 4 6 7 8 9 move 7 to j+k
+ // members 0 1 2 3 x 5 4 7 6 x 8 9 moved
+ //
+ // j=5, k=3 j k
+ // segment 5 4 7 6
+ // members 0 1 2 3 x 5 4 7 6 x 8 9 keep 6 in j+k
+ //
- for (var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ function moveMember(arr, findIndex, toIndex) {
+ var i;
- if (discardTags[k] || !entity.tags[k]) {
- didDiscard = true;
- } else {
- tags[k] = entity.tags[k];
+ for (i = 0; i < arr.length; i++) {
+ if (arr[i].index === findIndex) {
+ break;
}
}
- if (didDiscard) {
- graph = graph.replace(entity.update({
- tags: tags
- }));
- }
- }
- };
- }
-
- //
- // Optionally, disconnect only the given ways.
- //
- // For testing convenience, accepts an ID to assign to the (first) new node.
- // Normally, this will be undefined and the way will automatically
- // be assigned a new ID.
- //
- // This is the inverse of `iD.actionConnect`.
- //
- // Reference:
- // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
- // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
- //
+ var item = Object.assign({}, arr[i]); // shallow copy
- function actionDisconnect(nodeId, newNodeId) {
- var wayIds;
+ arr[i].index = -1; // mark as dead
- var action = function action(graph) {
- var node = graph.entity(nodeId);
- var connections = action.connections(graph);
- connections.forEach(function (connection) {
- var way = graph.entity(connection.wayID);
- var newNode = osmNode({
- id: newNodeId,
- loc: node.loc,
- tags: node.tags
- });
- graph = graph.replace(newNode);
+ item.index = toIndex;
+ arr.splice(toIndex, 0, item);
+ } // This is the same as `Relation.indexedMembers`,
+ // Except we don't want to index all the members, only the ways
- if (connection.index === 0 && way.isArea()) {
- // replace shared node with shared node..
- graph = graph.replace(way.replaceNode(way.nodes[0], newNode.id));
- } else if (way.isClosed() && connection.index === way.nodes.length - 1) {
- // replace closing node with new new node..
- graph = graph.replace(way.unclose().addNode(newNode.id));
- } else {
- // replace shared node with multiple new nodes..
- graph = graph.replace(way.updateNode(newNode.id, connection.index));
- }
- });
- return graph;
- };
- action.connections = function (graph) {
- var candidates = [];
- var keeping = false;
- var parentWays = graph.parentWays(graph.entity(nodeId));
- var way, waynode;
+ function withIndex(arr) {
+ var result = new Array(arr.length);
- for (var i = 0; i < parentWays.length; i++) {
- way = parentWays[i];
+ for (var i = 0; i < arr.length; i++) {
+ result[i] = Object.assign({}, arr[i]); // shallow copy
- if (wayIds && wayIds.indexOf(way.id) === -1) {
- keeping = true;
- continue;
+ result[i].index = i;
}
- if (way.isArea() && way.nodes[0] === nodeId) {
- candidates.push({
- wayID: way.id,
- index: 0
- });
- } else {
- for (var j = 0; j < way.nodes.length; j++) {
- waynode = way.nodes[j];
-
- if (waynode === nodeId) {
- if (way.isClosed() && parentWays.length > 1 && wayIds && wayIds.indexOf(way.id) !== -1 && j === way.nodes.length - 1) {
- continue;
- }
-
- candidates.push({
- wayID: way.id,
- index: j
- });
- }
- }
- }
+ return result;
}
+ }
+ }
- return keeping ? candidates : candidates.slice(1);
- };
+ function actionAddMidpoint(midpoint, node) {
+ return function (graph) {
+ graph = graph.replace(node.move(midpoint.loc));
+ var parents = utilArrayIntersection(graph.parentWays(graph.entity(midpoint.edge[0])), graph.parentWays(graph.entity(midpoint.edge[1])));
+ parents.forEach(function (way) {
+ for (var i = 0; i < way.nodes.length - 1; i++) {
+ if (geoEdgeEqual([way.nodes[i], way.nodes[i + 1]], midpoint.edge)) {
+ graph = graph.replace(graph.entity(way.id).addNode(node.id, i + 1)); // Add only one midpoint on doubled-back segments,
+ // turning them into self-intersections.
- action.disabled = function (graph) {
- var connections = action.connections(graph);
- if (connections.length === 0) return 'not_connected';
- var parentWays = graph.parentWays(graph.entity(nodeId));
- var seenRelationIds = {};
- var sharedRelation;
- parentWays.forEach(function (way) {
- var relations = graph.parentRelations(way);
- relations.forEach(function (relation) {
- if (relation.id in seenRelationIds) {
- if (wayIds) {
- if (wayIds.indexOf(way.id) !== -1 || wayIds.indexOf(seenRelationIds[relation.id]) !== -1) {
- sharedRelation = relation;
- }
- } else {
- sharedRelation = relation;
- }
- } else {
- seenRelationIds[relation.id] = way.id;
+ return;
}
- });
+ }
});
- if (sharedRelation) return 'relation';
+ return graph;
};
+ }
- action.limitWays = function (val) {
- if (!arguments.length) return wayIds;
- wayIds = val;
- return action;
+ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
+ function actionAddVertex(wayId, nodeId, index) {
+ return function (graph) {
+ return graph.replace(graph.entity(wayId).addNode(nodeId, index));
};
-
- return action;
}
- function actionExtract(entityID, projection) {
- var extractedNodeID;
+ function actionChangeMember(relationId, member, memberIndex) {
+ return function (graph) {
+ return graph.replace(graph.entity(relationId).updateMember(member, memberIndex));
+ };
+ }
- var action = function action(graph) {
+ function actionChangePreset(entityID, oldPreset, newPreset, skipFieldDefaults) {
+ return function action(graph) {
var entity = graph.entity(entityID);
+ var geometry = entity.geometry(graph);
+ var tags = entity.tags; // preserve tags that the new preset might care about, if any
- if (entity.type === 'node') {
- return extractFromNode(entity, graph);
- }
-
- return extractFromWayOrRelation(entity, graph);
+ if (oldPreset) tags = oldPreset.unsetTags(tags, geometry, newPreset && newPreset.addTags ? Object.keys(newPreset.addTags) : null);
+ if (newPreset) tags = newPreset.setTags(tags, geometry, skipFieldDefaults);
+ return graph.replace(entity.update({
+ tags: tags
+ }));
};
+ }
- function extractFromNode(node, graph) {
- extractedNodeID = node.id; // Create a new node to replace the one we will detach
+ function actionChangeTags(entityId, tags) {
+ return function (graph) {
+ var entity = graph.entity(entityId);
+ return graph.replace(entity.update({
+ tags: tags
+ }));
+ };
+ }
- var replacement = osmNode({
- loc: node.loc
+ function osmNode() {
+ if (!(this instanceof osmNode)) {
+ return new osmNode().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
+ osmEntity.node = osmNode;
+ osmNode.prototype = Object.create(osmEntity.prototype);
+ Object.assign(osmNode.prototype, {
+ type: 'node',
+ loc: [9999, 9999],
+ extent: function extent() {
+ return new geoExtent(this.loc);
+ },
+ geometry: function geometry(graph) {
+ return graph["transient"](this, 'geometry', function () {
+ return graph.isPoi(this) ? 'point' : 'vertex';
});
- graph = graph.replace(replacement); // Process each way in turn, updating the graph as we go
-
- graph = graph.parentWays(node).reduce(function (accGraph, parentWay) {
- return accGraph.replace(parentWay.replaceNode(entityID, replacement.id));
- }, graph); // Process any relations too
+ },
+ move: function move(loc) {
+ return this.update({
+ loc: loc
+ });
+ },
+ isDegenerate: function isDegenerate() {
+ return !(Array.isArray(this.loc) && this.loc.length === 2 && this.loc[0] >= -180 && this.loc[0] <= 180 && this.loc[1] >= -90 && this.loc[1] <= 90);
+ },
+ // Inspect tags and geometry to determine which direction(s) this node/vertex points
+ directions: function directions(resolver, projection) {
+ var val;
+ var i; // which tag to use?
- return graph.parentRelations(node).reduce(function (accGraph, parentRel) {
- return accGraph.replace(parentRel.replaceMember(node, replacement));
- }, graph);
- }
+ if (this.isHighwayIntersection(resolver) && (this.tags.stop || '').toLowerCase() === 'all') {
+ // all-way stop tag on a highway intersection
+ val = 'all';
+ } else {
+ // generic direction tag
+ val = (this.tags.direction || '').toLowerCase(); // better suffix-style direction tag
- function extractFromWayOrRelation(entity, graph) {
- var fromGeometry = entity.geometry(graph);
- var keysToCopyAndRetain = ['source', 'wheelchair'];
- var keysToRetain = ['area'];
- var buildingKeysToRetain = ['architect', 'building', 'height', 'layer'];
- var extractedLoc = d3_geoPath(projection).centroid(entity.asGeoJSON(graph));
- extractedLoc = extractedLoc && projection.invert(extractedLoc);
+ var re = /:direction$/i;
+ var keys = Object.keys(this.tags);
- if (!extractedLoc || !isFinite(extractedLoc[0]) || !isFinite(extractedLoc[1])) {
- extractedLoc = entity.extent(graph).center();
+ for (i = 0; i < keys.length; i++) {
+ if (re.test(keys[i])) {
+ val = this.tags[keys[i]].toLowerCase();
+ break;
+ }
+ }
}
- var indoorAreaValues = {
- area: true,
- corridor: true,
- elevator: true,
- level: true,
- room: true
+ if (val === '') return [];
+ var cardinal = {
+ north: 0,
+ n: 0,
+ northnortheast: 22,
+ nne: 22,
+ northeast: 45,
+ ne: 45,
+ eastnortheast: 67,
+ ene: 67,
+ east: 90,
+ e: 90,
+ eastsoutheast: 112,
+ ese: 112,
+ southeast: 135,
+ se: 135,
+ southsoutheast: 157,
+ sse: 157,
+ south: 180,
+ s: 180,
+ southsouthwest: 202,
+ ssw: 202,
+ southwest: 225,
+ sw: 225,
+ westsouthwest: 247,
+ wsw: 247,
+ west: 270,
+ w: 270,
+ westnorthwest: 292,
+ wnw: 292,
+ northwest: 315,
+ nw: 315,
+ northnorthwest: 337,
+ nnw: 337
};
- var isBuilding = entity.tags.building && entity.tags.building !== 'no' || entity.tags['building:part'] && entity.tags['building:part'] !== 'no';
- var isIndoorArea = fromGeometry === 'area' && entity.tags.indoor && indoorAreaValues[entity.tags.indoor];
- var entityTags = Object.assign({}, entity.tags); // shallow copy
-
- var pointTags = {};
-
- for (var key in entityTags) {
- if (entity.type === 'relation' && key === 'type') {
- continue;
- }
+ var values = val.split(';');
+ var results = [];
+ values.forEach(function (v) {
+ // swap cardinal for numeric directions
+ if (cardinal[v] !== undefined) {
+ v = cardinal[v];
+ } // numeric direction - just add to results
- if (keysToRetain.indexOf(key) !== -1) {
- continue;
- }
- if (isBuilding) {
- // don't transfer building-related tags
- if (buildingKeysToRetain.indexOf(key) !== -1 || key.match(/^building:.{1,}/) || key.match(/^roof:.{1,}/)) continue;
- } // leave `indoor` tag on the area
+ if (v !== '' && !isNaN(+v)) {
+ results.push(+v);
+ return;
+ } // string direction - inspect parent ways
- if (isIndoorArea && key === 'indoor') {
- continue;
- } // copy the tag from the entity to the point
+ var lookBackward = this.tags['traffic_sign:backward'] || v === 'backward' || v === 'both' || v === 'all';
+ var lookForward = this.tags['traffic_sign:forward'] || v === 'forward' || v === 'both' || v === 'all';
+ if (!lookForward && !lookBackward) return;
+ var nodeIds = {};
+ resolver.parentWays(this).forEach(function (parent) {
+ var nodes = parent.nodes;
+ for (i = 0; i < nodes.length; i++) {
+ if (nodes[i] === this.id) {
+ // match current entity
+ if (lookForward && i > 0) {
+ nodeIds[nodes[i - 1]] = true; // look back to prev node
+ }
- pointTags[key] = entityTags[key]; // leave addresses and some other tags so they're on both features
+ if (lookBackward && i < nodes.length - 1) {
+ nodeIds[nodes[i + 1]] = true; // look ahead to next node
+ }
+ }
+ }
+ }, this);
+ Object.keys(nodeIds).forEach(function (nodeId) {
+ // +90 because geoAngle returns angle from X axis, not Y (north)
+ results.push(geoAngle(this, resolver.entity(nodeId), projection) * (180 / Math.PI) + 90);
+ }, this);
+ }, this);
+ return utilArrayUniq(results);
+ },
+ isCrossing: function isCrossing() {
+ return this.tags.highway === 'crossing' || this.tags.railway && this.tags.railway.indexOf('crossing') !== -1;
+ },
+ isEndpoint: function isEndpoint(resolver) {
+ return resolver["transient"](this, 'isEndpoint', function () {
+ var id = this.id;
+ return resolver.parentWays(this).filter(function (parent) {
+ return !parent.isClosed() && !!parent.affix(id);
+ }).length > 0;
+ });
+ },
+ isConnected: function isConnected(resolver) {
+ return resolver["transient"](this, 'isConnected', function () {
+ var parents = resolver.parentWays(this);
- if (keysToCopyAndRetain.indexOf(key) !== -1 || key.match(/^addr:.{1,}/)) {
- continue;
- } else if (isIndoorArea && key === 'level') {
- // leave `level` on both features
- continue;
- } // remove the tag from the entity
+ if (parents.length > 1) {
+ // vertex is connected to multiple parent ways
+ for (var i in parents) {
+ if (parents[i].geometry(resolver) === 'line' && parents[i].hasInterestingTags()) return true;
+ }
+ } else if (parents.length === 1) {
+ var way = parents[0];
+ var nodes = way.nodes.slice();
+ if (way.isClosed()) {
+ nodes.pop();
+ } // ignore connecting node if closed
+ // return true if vertex appears multiple times (way is self intersecting)
- delete entityTags[key];
- }
- if (!isBuilding && !isIndoorArea && fromGeometry === 'area') {
- // ensure that areas keep area geometry
- entityTags.area = 'yes';
- }
+ return nodes.indexOf(this.id) !== nodes.lastIndexOf(this.id);
+ }
- var replacement = osmNode({
- loc: extractedLoc,
- tags: pointTags
+ return false;
});
- graph = graph.replace(replacement);
- extractedNodeID = replacement.id;
- return graph.replace(entity.update({
- tags: entityTags
- }));
+ },
+ parentIntersectionWays: function parentIntersectionWays(resolver) {
+ return resolver["transient"](this, 'parentIntersectionWays', function () {
+ return resolver.parentWays(this).filter(function (parent) {
+ return (parent.tags.highway || parent.tags.waterway || parent.tags.railway || parent.tags.aeroway) && parent.geometry(resolver) === 'line';
+ });
+ });
+ },
+ isIntersection: function isIntersection(resolver) {
+ return this.parentIntersectionWays(resolver).length > 1;
+ },
+ isHighwayIntersection: function isHighwayIntersection(resolver) {
+ return resolver["transient"](this, 'isHighwayIntersection', function () {
+ return resolver.parentWays(this).filter(function (parent) {
+ return parent.tags.highway && parent.geometry(resolver) === 'line';
+ }).length > 1;
+ });
+ },
+ isOnAddressLine: function isOnAddressLine(resolver) {
+ return resolver["transient"](this, 'isOnAddressLine', function () {
+ return resolver.parentWays(this).filter(function (parent) {
+ return parent.tags.hasOwnProperty('addr:interpolation') && parent.geometry(resolver) === 'line';
+ }).length > 0;
+ });
+ },
+ asJXON: function asJXON(changeset_id) {
+ var r = {
+ node: {
+ '@id': this.osmId(),
+ '@lon': this.loc[0],
+ '@lat': this.loc[1],
+ '@version': this.version || 0,
+ tag: Object.keys(this.tags).map(function (k) {
+ return {
+ keyAttributes: {
+ k: k,
+ v: this.tags[k]
+ }
+ };
+ }, this)
+ }
+ };
+ if (changeset_id) r.node['@changeset'] = changeset_id;
+ return r;
+ },
+ asGeoJSON: function asGeoJSON() {
+ return {
+ type: 'Point',
+ coordinates: this.loc
+ };
}
+ });
- action.getExtractedNodeID = function () {
- return extractedNodeID;
- };
+ function actionCircularize(wayId, projection, maxAngle) {
+ maxAngle = (maxAngle || 20) * Math.PI / 180;
- return action;
- }
+ var action = function action(graph, t) {
+ if (t === null || !isFinite(t)) t = 1;
+ t = Math.min(Math.max(+t, 0), 1);
+ var way = graph.entity(wayId);
+ var origNodes = {};
+ graph.childNodes(way).forEach(function (node) {
+ if (!origNodes[node.id]) origNodes[node.id] = node;
+ });
- //
- // This is the inverse of `iD.actionSplit`.
- //
- // Reference:
- // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as
- // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
- //
+ if (!way.isConvex(graph)) {
+ graph = action.makeConvex(graph);
+ }
- function actionJoin(ids) {
- function groupEntitiesByGeometry(graph) {
- var entities = ids.map(function (id) {
- return graph.entity(id);
+ var nodes = utilArrayUniq(graph.childNodes(way));
+ var keyNodes = nodes.filter(function (n) {
+ return graph.parentWays(n).length !== 1;
});
- return Object.assign({
- line: []
- }, utilArrayGroupBy(entities, function (entity) {
- return entity.geometry(graph);
- }));
- }
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var keyPoints = keyNodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var centroid = points.length === 2 ? geoVecInterp(points[0], points[1], 0.5) : d3_polygonCentroid(points);
+ var radius = d3_median(points, function (p) {
+ return geoVecLength(centroid, p);
+ });
+ var sign = d3_polygonArea(points) > 0 ? 1 : -1;
+ var ids, i, j, k; // we need at least two key nodes for the algorithm to work
- var action = function action(graph) {
- var ways = ids.map(graph.entity, graph);
- var survivorID = ways[0].id; // if any of the ways are sided (e.g. coastline, cliff, kerb)
- // sort them first so they establish the overall order - #6033
+ if (!keyNodes.length) {
+ keyNodes = [nodes[0]];
+ keyPoints = [points[0]];
+ }
- ways.sort(function (a, b) {
- var aSided = a.isSided();
- var bSided = b.isSided();
- return aSided && !bSided ? -1 : bSided && !aSided ? 1 : 0;
- }); // Prefer to keep an existing way.
+ if (keyNodes.length === 1) {
+ var index = nodes.indexOf(keyNodes[0]);
+ var oppositeIndex = Math.floor((index + nodes.length / 2) % nodes.length);
+ keyNodes.push(nodes[oppositeIndex]);
+ keyPoints.push(points[oppositeIndex]);
+ } // key points and nodes are those connected to the ways,
+ // they are projected onto the circle, in between nodes are moved
+ // to constant intervals between key nodes, extra in between nodes are
+ // added if necessary.
- for (var i = 0; i < ways.length; i++) {
- if (!ways[i].isNew()) {
- survivorID = ways[i].id;
- break;
- }
- }
- var sequences = osmJoinWays(ways, graph);
- var joined = sequences[0]; // We might need to reverse some of these ways before joining them. #4688
- // `joined.actions` property will contain any actions we need to apply.
+ for (i = 0; i < keyPoints.length; i++) {
+ var nextKeyNodeIndex = (i + 1) % keyNodes.length;
+ var startNode = keyNodes[i];
+ var endNode = keyNodes[nextKeyNodeIndex];
+ var startNodeIndex = nodes.indexOf(startNode);
+ var endNodeIndex = nodes.indexOf(endNode);
+ var numberNewPoints = -1;
+ var indexRange = endNodeIndex - startNodeIndex;
+ var nearNodes = {};
+ var inBetweenNodes = [];
+ var startAngle, endAngle, totalAngle, eachAngle;
+ var angle, loc, node, origNode;
- graph = sequences.actions.reduce(function (g, action) {
- return action(g);
- }, graph);
- var survivor = graph.entity(survivorID);
- survivor = survivor.update({
- nodes: joined.nodes.map(function (n) {
- return n.id;
- })
- });
- graph = graph.replace(survivor);
- joined.forEach(function (way) {
- if (way.id === survivorID) return;
- graph.parentRelations(way).forEach(function (parent) {
- graph = graph.replace(parent.replaceMember(way, survivor));
- });
- survivor = survivor.mergeTags(way.tags);
- graph = graph.replace(survivor);
- graph = actionDeleteWay(way.id)(graph);
- }); // Finds if the join created a single-member multipolygon,
- // and if so turns it into a basic area instead
+ if (indexRange < 0) {
+ indexRange += nodes.length;
+ } // position this key node
- function checkForSimpleMultipolygon() {
- if (!survivor.isClosed()) return;
- var multipolygons = graph.parentMultipolygons(survivor).filter(function (multipolygon) {
- // find multipolygons where the survivor is the only member
- return multipolygon.members.length === 1;
- }); // skip if this is the single member of multiple multipolygons
- if (multipolygons.length !== 1) return;
- var multipolygon = multipolygons[0];
+ var distance = geoVecLength(centroid, keyPoints[i]) || 1e-4;
+ keyPoints[i] = [centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, centroid[1] + (keyPoints[i][1] - centroid[1]) / distance * radius];
+ loc = projection.invert(keyPoints[i]);
+ node = keyNodes[i];
+ origNode = origNodes[node.id];
+ node = node.move(geoVecInterp(origNode.loc, loc, t));
+ graph = graph.replace(node); // figure out the between delta angle we want to match to
- for (var key in survivor.tags) {
- if (multipolygon.tags[key] && // don't collapse if tags cannot be cleanly merged
- multipolygon.tags[key] !== survivor.tags[key]) return;
+ startAngle = Math.atan2(keyPoints[i][1] - centroid[1], keyPoints[i][0] - centroid[0]);
+ endAngle = Math.atan2(keyPoints[nextKeyNodeIndex][1] - centroid[1], keyPoints[nextKeyNodeIndex][0] - centroid[0]);
+ totalAngle = endAngle - startAngle; // detects looping around -pi/pi
+
+ if (totalAngle * sign > 0) {
+ totalAngle = -sign * (2 * Math.PI - Math.abs(totalAngle));
}
- survivor = survivor.mergeTags(multipolygon.tags);
- graph = graph.replace(survivor);
- graph = actionDeleteRelation(multipolygon.id, true
- /* allow untagged members */
- )(graph);
- var tags = Object.assign({}, survivor.tags);
+ do {
+ numberNewPoints++;
+ eachAngle = totalAngle / (indexRange + numberNewPoints);
+ } while (Math.abs(eachAngle) > maxAngle); // move existing nodes
- if (survivor.geometry(graph) !== 'area') {
- // ensure the feature persists as an area
- tags.area = 'yes';
- }
- delete tags.type; // remove type=multipolygon
+ for (j = 1; j < indexRange; j++) {
+ angle = startAngle + j * eachAngle;
+ loc = projection.invert([centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]);
+ node = nodes[(j + startNodeIndex) % nodes.length];
+ origNode = origNodes[node.id];
+ nearNodes[node.id] = angle;
+ node = node.move(geoVecInterp(origNode.loc, loc, t));
+ graph = graph.replace(node);
+ } // add new in between nodes if necessary
- survivor = survivor.update({
- tags: tags
- });
- graph = graph.replace(survivor);
- }
- checkForSimpleMultipolygon();
- return graph;
- }; // Returns the number of nodes the resultant way is expected to have
+ for (j = 0; j < numberNewPoints; j++) {
+ angle = startAngle + (indexRange + j) * eachAngle;
+ loc = projection.invert([centroid[0] + Math.cos(angle) * radius, centroid[1] + Math.sin(angle) * radius]); // choose a nearnode to use as the original
+ var min = Infinity;
- action.resultingWayNodesLength = function (graph) {
- return ids.reduce(function (count, id) {
- return count + graph.entity(id).nodes.length;
- }, 0) - ids.length - 1;
- };
+ for (var nodeId in nearNodes) {
+ var nearAngle = nearNodes[nodeId];
+ var dist = Math.abs(nearAngle - angle);
- action.disabled = function (graph) {
- var geometries = groupEntitiesByGeometry(graph);
+ if (dist < min) {
+ min = dist;
+ origNode = origNodes[nodeId];
+ }
+ }
- if (ids.length < 2 || ids.length !== geometries.line.length) {
- return 'not_eligible';
- }
+ node = osmNode({
+ loc: geoVecInterp(origNode.loc, loc, t)
+ });
+ graph = graph.replace(node);
+ nodes.splice(endNodeIndex + j, 0, node);
+ inBetweenNodes.push(node.id);
+ } // Check for other ways that share these keyNodes..
+ // If keyNodes are adjacent in both ways,
+ // we can add inBetweenNodes to that shared way too..
- var joined = osmJoinWays(ids.map(graph.entity, graph), graph);
- if (joined.length > 1) {
- return 'not_adjacent';
- } // Loop through all combinations of path-pairs
- // to check potential intersections between all pairs
+ if (indexRange === 1 && inBetweenNodes.length) {
+ var startIndex1 = way.nodes.lastIndexOf(startNode.id);
+ var endIndex1 = way.nodes.lastIndexOf(endNode.id);
+ var wayDirection1 = endIndex1 - startIndex1;
+ if (wayDirection1 < -1) {
+ wayDirection1 = 1;
+ }
- for (var i = 0; i < ids.length - 1; i++) {
- for (var j = i + 1; j < ids.length; j++) {
- var path1 = graph.childNodes(graph.entity(ids[i])).map(function (e) {
- return e.loc;
- });
- var path2 = graph.childNodes(graph.entity(ids[j])).map(function (e) {
- return e.loc;
- });
- var intersections = geoPathIntersections(path1, path2); // Check if intersections are just nodes lying on top of
- // each other/the line, as opposed to crossing it
+ var parentWays = graph.parentWays(keyNodes[i]);
- var common = utilArrayIntersection(joined[0].nodes.map(function (n) {
- return n.loc.toString();
- }), intersections.map(function (n) {
- return n.toString();
- }));
+ for (j = 0; j < parentWays.length; j++) {
+ var sharedWay = parentWays[j];
+ if (sharedWay === way) continue;
- if (common.length !== intersections.length) {
- return 'paths_intersect';
+ if (sharedWay.areAdjacent(startNode.id, endNode.id)) {
+ var startIndex2 = sharedWay.nodes.lastIndexOf(startNode.id);
+ var endIndex2 = sharedWay.nodes.lastIndexOf(endNode.id);
+ var wayDirection2 = endIndex2 - startIndex2;
+ var insertAt = endIndex2;
+
+ if (wayDirection2 < -1) {
+ wayDirection2 = 1;
+ }
+
+ if (wayDirection1 !== wayDirection2) {
+ inBetweenNodes.reverse();
+ insertAt = startIndex2;
+ }
+
+ for (k = 0; k < inBetweenNodes.length; k++) {
+ sharedWay = sharedWay.addNode(inBetweenNodes[k], insertAt + k);
+ }
+
+ graph = graph.replace(sharedWay);
+ }
}
}
- }
+ } // update the way to have all the new nodes
- var nodeIds = joined[0].nodes.map(function (n) {
+
+ ids = nodes.map(function (n) {
return n.id;
- }).slice(1, -1);
- var relation;
- var tags = {};
- var conflicting = false;
- joined[0].forEach(function (way) {
- var parents = graph.parentRelations(way);
- parents.forEach(function (parent) {
- if (parent.isRestriction() && parent.members.some(function (m) {
- return nodeIds.indexOf(m.id) >= 0;
- })) {
- relation = parent;
- }
- });
+ });
+ ids.push(ids[0]);
+ way = way.update({
+ nodes: ids
+ });
+ graph = graph.replace(way);
+ return graph;
+ };
- for (var k in way.tags) {
- if (!(k in tags)) {
- tags[k] = way.tags[k];
- } else if (tags[k] && osmIsInterestingTag(k) && tags[k] !== way.tags[k]) {
- conflicting = true;
- }
- }
+ action.makeConvex = function (graph) {
+ var way = graph.entity(wayId);
+ var nodes = utilArrayUniq(graph.childNodes(way));
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
});
+ var sign = d3_polygonArea(points) > 0 ? 1 : -1;
+ var hull = d3_polygonHull(points);
+ var i, j; // D3 convex hulls go counterclockwise..
- if (relation) {
- return 'restriction';
+ if (sign === -1) {
+ nodes.reverse();
+ points.reverse();
}
- if (conflicting) {
- return 'conflicting_tags';
+ for (i = 0; i < hull.length - 1; i++) {
+ var startIndex = points.indexOf(hull[i]);
+ var endIndex = points.indexOf(hull[i + 1]);
+ var indexRange = endIndex - startIndex;
+
+ if (indexRange < 0) {
+ indexRange += nodes.length;
+ } // move interior nodes to the surface of the convex hull..
+
+
+ for (j = 1; j < indexRange; j++) {
+ var point = geoVecInterp(hull[i], hull[i + 1], j / indexRange);
+ var node = nodes[(j + startIndex) % nodes.length].move(projection.invert(point));
+ graph = graph.replace(node);
+ }
}
+
+ return graph;
};
- return action;
- }
+ action.disabled = function (graph) {
+ if (!graph.entity(wayId).isClosed()) {
+ return 'not_closed';
+ } //disable when already circular
- function actionMerge(ids) {
- function groupEntitiesByGeometry(graph) {
- var entities = ids.map(function (id) {
- return graph.entity(id);
- });
- return Object.assign({
- point: [],
- area: [],
- line: [],
- relation: []
- }, utilArrayGroupBy(entities, function (entity) {
- return entity.geometry(graph);
- }));
- }
- var action = function action(graph) {
- var geometries = groupEntitiesByGeometry(graph);
- var target = geometries.area[0] || geometries.line[0];
- var points = geometries.point;
- points.forEach(function (point) {
- target = target.mergeTags(point.tags);
- graph = graph.replace(target);
- graph.parentRelations(point).forEach(function (parent) {
- graph = graph.replace(parent.replaceMember(point, target));
- });
- var nodes = utilArrayUniq(graph.childNodes(target));
- var removeNode = point;
+ var way = graph.entity(wayId);
+ var nodes = utilArrayUniq(graph.childNodes(way));
+ var points = nodes.map(function (n) {
+ return projection(n.loc);
+ });
+ var hull = d3_polygonHull(points);
+ var epsilonAngle = Math.PI / 180;
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
+ if (hull.length !== points.length || hull.length < 3) {
+ return false;
+ }
- if (graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags()) {
- continue;
- } // Found an uninteresting child node on the target way.
- // Move orig point into its place to preserve point's history. #3683
+ var centroid = d3_polygonCentroid(points);
+ var radius = geoVecLengthSquare(centroid, points[0]);
+ var i, actualPoint; // compare distances between centroid and points
+ for (i = 0; i < hull.length; i++) {
+ actualPoint = hull[i];
+ var actualDist = geoVecLengthSquare(actualPoint, centroid);
+ var diff = Math.abs(actualDist - radius); //compare distances with epsilon-error (5%)
- graph = graph.replace(point.update({
- tags: {},
- loc: node.loc
- }));
- target = target.replaceNode(node.id, point.id);
- graph = graph.replace(target);
- removeNode = node;
- break;
+ if (diff > 0.05 * radius) {
+ return false;
}
+ } //check if central angles are smaller than maxAngle
- graph = graph.remove(removeNode);
- });
-
- if (target.tags.area === 'yes') {
- var tags = Object.assign({}, target.tags); // shallow copy
- delete tags.area;
+ for (i = 0; i < hull.length; i++) {
+ actualPoint = hull[i];
+ var nextPoint = hull[(i + 1) % hull.length];
+ var startAngle = Math.atan2(actualPoint[1] - centroid[1], actualPoint[0] - centroid[0]);
+ var endAngle = Math.atan2(nextPoint[1] - centroid[1], nextPoint[0] - centroid[0]);
+ var angle = endAngle - startAngle;
- if (osmTagSuggestingArea(tags)) {
- // remove the `area` tag if area geometry is now implied - #3851
- target = target.update({
- tags: tags
- });
- graph = graph.replace(target);
+ if (angle < 0) {
+ angle = -angle;
}
- }
-
- return graph;
- };
- action.disabled = function (graph) {
- var geometries = groupEntitiesByGeometry(graph);
+ if (angle > Math.PI) {
+ angle = 2 * Math.PI - angle;
+ }
- if (geometries.point.length === 0 || geometries.area.length + geometries.line.length !== 1 || geometries.relation.length !== 0) {
- return 'not_eligible';
+ if (angle > maxAngle + epsilonAngle) {
+ return false;
+ }
}
+
+ return 'already_circular';
};
+ action.transitionable = true;
return action;
}
- //
- // 1. move all the nodes to a common location
- // 2. `actionConnect` them
-
- function actionMergeNodes(nodeIDs, loc) {
- // If there is a single "interesting" node, use that as the location.
- // Otherwise return the average location of all the nodes.
- function chooseLoc(graph) {
- if (!nodeIDs.length) return null;
- var sum = [0, 0];
- var interestingCount = 0;
- var interestingLoc;
-
- for (var i = 0; i < nodeIDs.length; i++) {
- var node = graph.entity(nodeIDs[i]);
+ function actionDeleteWay(wayID) {
+ function canDeleteNode(node, graph) {
+ // don't delete nodes still attached to ways or relations
+ if (graph.parentWays(node).length || graph.parentRelations(node).length) return false;
+ var geometries = osmNodeGeometriesForTags(node.tags); // don't delete if this node can be a standalone point
- if (node.hasInterestingTags()) {
- interestingLoc = ++interestingCount === 1 ? node.loc : null;
- }
+ if (geometries.point) return false; // delete if this node only be a vertex
- sum = geoVecAdd(sum, node.loc);
- }
+ if (geometries.vertex) return true; // iD doesn't know if this should be a point or vertex,
+ // so only delete if there are no interesting tags
- return interestingLoc || geoVecScale(sum, 1 / nodeIDs.length);
+ return !node.hasInterestingTags();
}
var action = function action(graph) {
- if (nodeIDs.length < 2) return graph;
- var toLoc = loc;
-
- if (!toLoc) {
- toLoc = chooseLoc(graph);
- }
-
- for (var i = 0; i < nodeIDs.length; i++) {
- var node = graph.entity(nodeIDs[i]);
+ var way = graph.entity(wayID);
+ graph.parentRelations(way).forEach(function (parent) {
+ parent = parent.removeMembersWithID(wayID);
+ graph = graph.replace(parent);
- if (node.loc !== toLoc) {
- graph = graph.replace(node.move(toLoc));
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
}
- }
+ });
+ new Set(way.nodes).forEach(function (nodeID) {
+ graph = graph.replace(way.removeNode(nodeID));
+ var node = graph.entity(nodeID);
- return actionConnect(nodeIDs)(graph);
+ if (canDeleteNode(node, graph)) {
+ graph = graph.remove(node);
+ }
+ });
+ return graph.remove(way);
};
- action.disabled = function (graph) {
- if (nodeIDs.length < 2) return 'not_eligible';
+ return action;
+ }
- for (var i = 0; i < nodeIDs.length; i++) {
- var entity = graph.entity(nodeIDs[i]);
- if (entity.type !== 'node') return 'not_eligible';
- }
+ function actionDeleteMultiple(ids) {
+ var actions = {
+ way: actionDeleteWay,
+ node: actionDeleteNode,
+ relation: actionDeleteRelation
+ };
- return actionConnect(nodeIDs).disabled(graph);
+ var action = function action(graph) {
+ ids.forEach(function (id) {
+ if (graph.hasEntity(id)) {
+ // It may have been deleted already.
+ graph = actions[graph.entity(id).type](id)(graph);
+ }
+ });
+ return graph;
};
return action;
}
- function osmChangeset() {
- if (!(this instanceof osmChangeset)) {
- return new osmChangeset().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
+ function actionDeleteRelation(relationID, allowUntaggedMembers) {
+ function canDeleteEntity(entity, graph) {
+ return !graph.parentWays(entity).length && !graph.parentRelations(entity).length && !entity.hasInterestingTags() && !allowUntaggedMembers;
}
- }
- osmEntity.changeset = osmChangeset;
- osmChangeset.prototype = Object.create(osmEntity.prototype);
- Object.assign(osmChangeset.prototype, {
- type: 'changeset',
- extent: function extent() {
- return new geoExtent();
- },
- geometry: function geometry() {
- return 'changeset';
- },
- asJXON: function asJXON() {
- return {
- osm: {
- changeset: {
- tag: Object.keys(this.tags).map(function (k) {
- return {
- '@k': k,
- '@v': this.tags[k]
- };
- }, this),
- '@version': 0.6,
- '@generator': 'iD'
- }
- }
- };
- },
- // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
- // XML. Returns a string.
- osmChangeJXON: function osmChangeJXON(changes) {
- var changeset_id = this.id;
- function nest(x, order) {
- var groups = {};
+ var action = function action(graph) {
+ var relation = graph.entity(relationID);
+ graph.parentRelations(relation).forEach(function (parent) {
+ parent = parent.removeMembersWithID(relationID);
+ graph = graph.replace(parent);
- for (var i = 0; i < x.length; i++) {
- var tagName = Object.keys(x[i])[0];
- if (!groups[tagName]) groups[tagName] = [];
- groups[tagName].push(x[i][tagName]);
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
}
+ });
+ var memberIDs = utilArrayUniq(relation.members.map(function (m) {
+ return m.id;
+ }));
+ memberIDs.forEach(function (memberID) {
+ graph = graph.replace(relation.removeMembersWithID(memberID));
+ var entity = graph.entity(memberID);
- var ordered = {};
- order.forEach(function (o) {
- if (groups[o]) ordered[o] = groups[o];
- });
- return ordered;
- } // sort relations in a changeset by dependencies
+ if (canDeleteEntity(entity, graph)) {
+ graph = actionDeleteMultiple([memberID])(graph);
+ }
+ });
+ return graph.remove(relation);
+ };
+ return action;
+ }
- function sort(changes) {
- // find a referenced relation in the current changeset
- function resolve(item) {
- return relations.find(function (relation) {
- return item.keyAttributes.type === 'relation' && item.keyAttributes.ref === relation['@id'];
- });
- } // a new item is an item that has not been already processed
+ function actionDeleteNode(nodeId) {
+ var action = function action(graph) {
+ var node = graph.entity(nodeId);
+ graph.parentWays(node).forEach(function (parent) {
+ parent = parent.removeNode(nodeId);
+ graph = graph.replace(parent);
+ if (parent.isDegenerate()) {
+ graph = actionDeleteWay(parent.id)(graph);
+ }
+ });
+ graph.parentRelations(node).forEach(function (parent) {
+ parent = parent.removeMembersWithID(nodeId);
+ graph = graph.replace(parent);
- function isNew(item) {
- return !sorted[item['@id']] && !processing.find(function (proc) {
- return proc['@id'] === item['@id'];
- });
+ if (parent.isDegenerate()) {
+ graph = actionDeleteRelation(parent.id)(graph);
}
+ });
+ return graph.remove(node);
+ };
- var processing = [];
- var sorted = {};
- var relations = changes.relation;
- if (!relations) return changes;
+ return action;
+ }
- for (var i = 0; i < relations.length; i++) {
- var relation = relations[i]; // skip relation if already sorted
+ //
+ // First choose a node to be the survivor, with preference given
+ // to the oldest existing (not new) and "interesting" node.
+ //
+ // Tags and relation memberships of of non-surviving nodes are merged
+ // to the survivor.
+ //
+ // This is the inverse of `iD.actionDisconnect`.
+ //
+ // Reference:
+ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeNodesAction.as
+ // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/MergeNodesAction.java
+ //
- if (!sorted[relation['@id']]) {
- processing.push(relation);
- }
+ function actionConnect(nodeIDs) {
+ var action = function action(graph) {
+ var survivor;
+ var node;
+ var parents;
+ var i, j; // Select the node with the ID passed as parameter if it is in the list,
+ // otherwise select the node with the oldest ID as the survivor, or the
+ // last one if there are only new nodes.
- while (processing.length > 0) {
- var next = processing[0],
- deps = next.member.map(resolve).filter(Boolean).filter(isNew);
+ nodeIDs.reverse();
+ var interestingIDs = [];
- if (deps.length === 0) {
- sorted[next['@id']] = next;
- processing.shift();
- } else {
- processing = deps.concat(processing);
- }
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+
+ if (node.hasInterestingTags()) {
+ if (!node.isNew()) {
+ interestingIDs.push(node.id);
}
}
-
- changes.relation = Object.values(sorted);
- return changes;
}
- function rep(entity) {
- return entity.asJXON(changeset_id);
- }
+ survivor = graph.entity(utilOldestID(interestingIDs.length > 0 ? interestingIDs : nodeIDs)); // Replace all non-surviving nodes with the survivor and merge tags.
- return {
- osmChange: {
- '@version': 0.6,
- '@generator': 'iD',
- 'create': sort(nest(changes.created.map(rep), ['node', 'way', 'relation'])),
- 'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
- 'delete': Object.assign(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {
- '@if-unused': true
- })
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ if (node.id === survivor.id) continue;
+ parents = graph.parentWays(node);
+
+ for (j = 0; j < parents.length; j++) {
+ graph = graph.replace(parents[j].replaceNode(node.id, survivor.id));
}
- };
- },
- asGeoJSON: function asGeoJSON() {
- return {};
- }
- });
- function osmNote() {
- if (!(this instanceof osmNote)) {
- return new osmNote().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
- }
- }
+ parents = graph.parentRelations(node);
- osmNote.id = function () {
- return osmNote.id.next--;
- };
+ for (j = 0; j < parents.length; j++) {
+ graph = graph.replace(parents[j].replaceMember(node, survivor));
+ }
- osmNote.id.next = -1;
- Object.assign(osmNote.prototype, {
- type: 'note',
- initialize: function initialize(sources) {
- for (var i = 0; i < sources.length; ++i) {
- var source = sources[i];
+ survivor = survivor.mergeTags(node.tags);
+ graph = actionDeleteNode(node.id)(graph);
+ }
- for (var prop in source) {
- if (Object.prototype.hasOwnProperty.call(source, prop)) {
- if (source[prop] === undefined) {
- delete this[prop];
- } else {
- this[prop] = source[prop];
- }
- }
+ graph = graph.replace(survivor); // find and delete any degenerate ways created by connecting adjacent vertices
+
+ parents = graph.parentWays(survivor);
+
+ for (i = 0; i < parents.length; i++) {
+ if (parents[i].isDegenerate()) {
+ graph = actionDeleteWay(parents[i].id)(graph);
}
}
- if (!this.id) {
- this.id = osmNote.id().toString();
- }
+ return graph;
+ };
- return this;
- },
- extent: function extent() {
- return new geoExtent(this.loc);
- },
- update: function update(attrs) {
- return osmNote(this, attrs); // {v: 1 + (this.v || 0)}
- },
- isNew: function isNew() {
- return this.id < 0;
- },
- move: function move(loc) {
- return this.update({
- loc: loc
- });
- }
- });
+ action.disabled = function (graph) {
+ var seen = {};
+ var restrictionIDs = [];
+ var survivor;
+ var node, way;
+ var relations, relation, role;
+ var i, j, k; // Select the node with the oldest ID as the survivor.
- function osmRelation() {
- if (!(this instanceof osmRelation)) {
- return new osmRelation().initialize(arguments);
- } else if (arguments.length) {
- this.initialize(arguments);
- }
- }
- osmEntity.relation = osmRelation;
- osmRelation.prototype = Object.create(osmEntity.prototype);
+ survivor = graph.entity(utilOldestID(nodeIDs)); // 1. disable if the nodes being connected have conflicting relation roles
- osmRelation.creationOrder = function (a, b) {
- var aId = parseInt(osmEntity.id.toOSM(a.id), 10);
- var bId = parseInt(osmEntity.id.toOSM(b.id), 10);
- if (aId < 0 || bId < 0) return aId - bId;
- return bId - aId;
- };
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ relations = graph.parentRelations(node);
- Object.assign(osmRelation.prototype, {
- type: 'relation',
- members: [],
- copy: function copy(resolver, copies) {
- if (copies[this.id]) return copies[this.id];
- var copy = osmEntity.prototype.copy.call(this, resolver, copies);
- var members = this.members.map(function (member) {
- return Object.assign({}, member, {
- id: resolver.entity(member.id).copy(resolver, copies).id
- });
- });
- copy = copy.update({
- members: members
- });
- copies[this.id] = copy;
- return copy;
- },
- extent: function extent(resolver, memo) {
- return resolver["transient"](this, 'extent', function () {
- if (memo && memo[this.id]) return geoExtent();
- memo = memo || {};
- memo[this.id] = true;
- var extent = geoExtent();
+ for (j = 0; j < relations.length; j++) {
+ relation = relations[j];
+ role = relation.memberById(node.id).role || ''; // if this node is a via node in a restriction, remember for later
- for (var i = 0; i < this.members.length; i++) {
- var member = resolver.hasEntity(this.members[i].id);
+ if (relation.hasFromViaTo()) {
+ restrictionIDs.push(relation.id);
+ }
- if (member) {
- extent._extend(member.extent(resolver, memo));
+ if (seen[relation.id] !== undefined && seen[relation.id] !== role) {
+ return 'relation';
+ } else {
+ seen[relation.id] = role;
}
}
+ } // gather restrictions for parent ways
- return extent;
- });
- },
- geometry: function geometry(graph) {
- return graph["transient"](this, 'geometry', function () {
- return this.isMultipolygon() ? 'area' : 'relation';
- });
- },
- isDegenerate: function isDegenerate() {
- return this.members.length === 0;
- },
- // Return an array of members, each extended with an 'index' property whose value
- // is the member index.
- indexedMembers: function indexedMembers() {
- var result = new Array(this.members.length);
- for (var i = 0; i < this.members.length; i++) {
- result[i] = Object.assign({}, this.members[i], {
- index: i
- });
- }
+ for (i = 0; i < nodeIDs.length; i++) {
+ node = graph.entity(nodeIDs[i]);
+ var parents = graph.parentWays(node);
- return result;
- },
- // Return the first member with the given role. A copy of the member object
- // is returned, extended with an 'index' property whose value is the member index.
- memberByRole: function memberByRole(role) {
- for (var i = 0; i < this.members.length; i++) {
- if (this.members[i].role === role) {
- return Object.assign({}, this.members[i], {
- index: i
- });
- }
- }
- },
- // Same as memberByRole, but returns all members with the given role
- membersByRole: function membersByRole(role) {
- var result = [];
+ for (j = 0; j < parents.length; j++) {
+ var parent = parents[j];
+ relations = graph.parentRelations(parent);
- for (var i = 0; i < this.members.length; i++) {
- if (this.members[i].role === role) {
- result.push(Object.assign({}, this.members[i], {
- index: i
- }));
- }
- }
+ for (k = 0; k < relations.length; k++) {
+ relation = relations[k];
- return result;
- },
- // Return the first member with the given id. A copy of the member object
- // is returned, extended with an 'index' property whose value is the member index.
- memberById: function memberById(id) {
- for (var i = 0; i < this.members.length; i++) {
- if (this.members[i].id === id) {
- return Object.assign({}, this.members[i], {
- index: i
- });
- }
- }
- },
- // Return the first member with the given id and role. A copy of the member object
- // is returned, extended with an 'index' property whose value is the member index.
- memberByIdAndRole: function memberByIdAndRole(id, role) {
- for (var i = 0; i < this.members.length; i++) {
- if (this.members[i].id === id && this.members[i].role === role) {
- return Object.assign({}, this.members[i], {
- index: i
- });
- }
- }
- },
- addMember: function addMember(member, index) {
- var members = this.members.slice();
- members.splice(index === undefined ? members.length : index, 0, member);
- return this.update({
- members: members
- });
- },
- updateMember: function updateMember(member, index) {
- var members = this.members.slice();
- members.splice(index, 1, Object.assign({}, members[index], member));
- return this.update({
- members: members
- });
- },
- removeMember: function removeMember(index) {
- var members = this.members.slice();
- members.splice(index, 1);
- return this.update({
- members: members
- });
- },
- removeMembersWithID: function removeMembersWithID(id) {
- var members = this.members.filter(function (m) {
- return m.id !== id;
- });
- return this.update({
- members: members
- });
- },
- moveMember: function moveMember(fromIndex, toIndex) {
- var members = this.members.slice();
- members.splice(toIndex, 0, members.splice(fromIndex, 1)[0]);
- return this.update({
- members: members
- });
- },
- // Wherever a member appears with id `needle.id`, replace it with a member
- // with id `replacement.id`, type `replacement.type`, and the original role,
- // By default, adding a duplicate member (by id and role) is prevented.
- // Return an updated relation.
- replaceMember: function replaceMember(needle, replacement, keepDuplicates) {
- if (!this.memberById(needle.id)) return this;
- var members = [];
-
- for (var i = 0; i < this.members.length; i++) {
- var member = this.members[i];
-
- if (member.id !== needle.id) {
- members.push(member);
- } else if (keepDuplicates || !this.memberByIdAndRole(replacement.id, member.role)) {
- members.push({
- id: replacement.id,
- type: replacement.type,
- role: member.role
- });
+ if (relation.hasFromViaTo()) {
+ restrictionIDs.push(relation.id);
+ }
+ }
}
- }
+ } // test restrictions
- return this.update({
- members: members
- });
- },
- asJXON: function asJXON(changeset_id) {
- var r = {
- relation: {
- '@id': this.osmId(),
- '@version': this.version || 0,
- member: this.members.map(function (member) {
- return {
- keyAttributes: {
- type: member.type,
- role: member.role,
- ref: osmEntity.id.toOSM(member.id)
- }
- };
- }, this),
- tag: Object.keys(this.tags).map(function (k) {
- return {
- keyAttributes: {
- k: k,
- v: this.tags[k]
- }
- };
- }, this)
- }
- };
- if (changeset_id) {
- r.relation['@changeset'] = changeset_id;
- }
+ restrictionIDs = utilArrayUniq(restrictionIDs);
- return r;
- },
- asGeoJSON: function asGeoJSON(resolver) {
- return resolver["transient"](this, 'GeoJSON', function () {
- if (this.isMultipolygon()) {
- return {
- type: 'MultiPolygon',
- coordinates: this.multipolygon(resolver)
- };
- } else {
- return {
- type: 'FeatureCollection',
- properties: this.tags,
- features: this.members.map(function (member) {
- return Object.assign({
- role: member.role
- }, resolver.entity(member.id).asGeoJSON(resolver));
- })
- };
- }
- });
- },
- area: function area(resolver) {
- return resolver["transient"](this, 'area', function () {
- return d3_geoArea(this.asGeoJSON(resolver));
- });
- },
- isMultipolygon: function isMultipolygon() {
- return this.tags.type === 'multipolygon';
- },
- isComplete: function isComplete(resolver) {
- for (var i = 0; i < this.members.length; i++) {
- if (!resolver.hasEntity(this.members[i].id)) {
- return false;
- }
- }
+ for (i = 0; i < restrictionIDs.length; i++) {
+ relation = graph.entity(restrictionIDs[i]);
+ if (!relation.isComplete(graph)) continue;
+ var memberWays = relation.members.filter(function (m) {
+ return m.type === 'way';
+ }).map(function (m) {
+ return graph.entity(m.id);
+ });
+ memberWays = utilArrayUniq(memberWays);
+ var f = relation.memberByRole('from');
+ var t = relation.memberByRole('to');
+ var isUturn = f.id === t.id; // 2a. disable if connection would damage a restriction
+ // (a key node is a node at the junction of ways)
- return true;
- },
- hasFromViaTo: function hasFromViaTo() {
- return this.members.some(function (m) {
- return m.role === 'from';
- }) && this.members.some(function (m) {
- return m.role === 'via';
- }) && this.members.some(function (m) {
- return m.role === 'to';
- });
- },
- isRestriction: function isRestriction() {
- return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
- },
- isValidRestriction: function isValidRestriction() {
- if (!this.isRestriction()) return false;
- var froms = this.members.filter(function (m) {
- return m.role === 'from';
- });
- var vias = this.members.filter(function (m) {
- return m.role === 'via';
- });
- var tos = this.members.filter(function (m) {
- return m.role === 'to';
- });
- if (froms.length !== 1 && this.tags.restriction !== 'no_entry') return false;
- if (froms.some(function (m) {
- return m.type !== 'way';
- })) return false;
- if (tos.length !== 1 && this.tags.restriction !== 'no_exit') return false;
- if (tos.some(function (m) {
- return m.type !== 'way';
- })) return false;
- if (vias.length === 0) return false;
- if (vias.length > 1 && vias.some(function (m) {
- return m.type !== 'way';
- })) return false;
- return true;
- },
- // Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm],
- // where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings.
- //
- // This corresponds to the structure needed for rendering a multipolygon path using a
- // `evenodd` fill rule, as well as the structure of a GeoJSON MultiPolygon geometry.
- //
- // In the case of invalid geometries, this function will still return a result which
- // includes the nodes of all way members, but some Nds may be unclosed and some inner
- // rings not matched with the intended outer ring.
- //
- multipolygon: function multipolygon(resolver) {
- var outers = this.members.filter(function (m) {
- return 'outer' === (m.role || 'outer');
- });
- var inners = this.members.filter(function (m) {
- return 'inner' === m.role;
- });
- outers = osmJoinWays(outers, resolver);
- inners = osmJoinWays(inners, resolver);
+ var nodes = {
+ from: [],
+ via: [],
+ to: [],
+ keyfrom: [],
+ keyto: []
+ };
- var sequenceToLineString = function sequenceToLineString(sequence) {
- if (sequence.nodes.length > 2 && sequence.nodes[0] !== sequence.nodes[sequence.nodes.length - 1]) {
- // close unclosed parts to ensure correct area rendering - #2945
- sequence.nodes.push(sequence.nodes[0]);
+ for (j = 0; j < relation.members.length; j++) {
+ collectNodes(relation.members[j], nodes);
}
- return sequence.nodes.map(function (node) {
- return node.loc;
- });
- };
+ nodes.keyfrom = utilArrayUniq(nodes.keyfrom.filter(hasDuplicates));
+ nodes.keyto = utilArrayUniq(nodes.keyto.filter(hasDuplicates));
+ var filter = keyNodeFilter(nodes.keyfrom, nodes.keyto);
+ nodes.from = nodes.from.filter(filter);
+ nodes.via = nodes.via.filter(filter);
+ nodes.to = nodes.to.filter(filter);
+ var connectFrom = false;
+ var connectVia = false;
+ var connectTo = false;
+ var connectKeyFrom = false;
+ var connectKeyTo = false;
- outers = outers.map(sequenceToLineString);
- inners = inners.map(sequenceToLineString);
- var result = outers.map(function (o) {
- // Heuristic for detecting counterclockwise winding order. Assumes
- // that OpenStreetMap polygons are not hemisphere-spanning.
- return [d3_geoArea({
- type: 'Polygon',
- coordinates: [o]
- }) > 2 * Math.PI ? o.reverse() : o];
- });
+ for (j = 0; j < nodeIDs.length; j++) {
+ var n = nodeIDs[j];
- function findOuter(inner) {
- var o, outer;
+ if (nodes.from.indexOf(n) !== -1) {
+ connectFrom = true;
+ }
- for (o = 0; o < outers.length; o++) {
- outer = outers[o];
+ if (nodes.via.indexOf(n) !== -1) {
+ connectVia = true;
+ }
- if (geoPolygonContainsPolygon(outer, inner)) {
- return o;
+ if (nodes.to.indexOf(n) !== -1) {
+ connectTo = true;
}
- }
- for (o = 0; o < outers.length; o++) {
- outer = outers[o];
+ if (nodes.keyfrom.indexOf(n) !== -1) {
+ connectKeyFrom = true;
+ }
- if (geoPolygonIntersectsPolygon(outer, inner, false)) {
- return o;
+ if (nodes.keyto.indexOf(n) !== -1) {
+ connectKeyTo = true;
}
}
- }
-
- for (var i = 0; i < inners.length; i++) {
- var inner = inners[i];
- if (d3_geoArea({
- type: 'Polygon',
- coordinates: [inner]
- }) < 2 * Math.PI) {
- inner = inner.reverse();
+ if (connectFrom && connectTo && !isUturn) {
+ return 'restriction';
}
- var o = findOuter(inners[i]);
-
- if (o !== undefined) {
- result[o].push(inners[i]);
- } else {
- result.push([inners[i]]); // Invalid geometry
+ if (connectFrom && connectVia) {
+ return 'restriction';
}
- }
-
- return result;
- }
- });
-
- var QAItem = /*#__PURE__*/function () {
- function QAItem(loc, service, itemType, id, props) {
- _classCallCheck$1(this, QAItem);
-
- // Store required properties
- this.loc = loc;
- this.service = service.title;
- this.itemType = itemType; // All issues must have an ID for selection, use generic if none specified
-
- this.id = id ? id : "".concat(QAItem.id());
- this.update(props); // Some QA services have marker icons to differentiate issues
- if (service && typeof service.getIcon === 'function') {
- this.icon = service.getIcon(itemType);
- }
- }
+ if (connectTo && connectVia) {
+ return 'restriction';
+ } // connecting to a key node -
+ // if both nodes are on a member way (i.e. part of the turn restriction),
+ // the connecting node must be adjacent to the key node.
- _createClass$1(QAItem, [{
- key: "update",
- value: function update(props) {
- var _this = this;
- // You can't override this initial information
- var loc = this.loc,
- service = this.service,
- itemType = this.itemType,
- id = this.id;
- Object.keys(props).forEach(function (prop) {
- return _this[prop] = props[prop];
- });
- this.loc = loc;
- this.service = service;
- this.itemType = itemType;
- this.id = id;
- return this;
- } // Generic handling for newly created QAItems
+ if (connectKeyFrom || connectKeyTo) {
+ if (nodeIDs.length !== 2) {
+ return 'restriction';
+ }
- }], [{
- key: "id",
- value: function id() {
- return this.nextId--;
- }
- }]);
+ var n0 = null;
+ var n1 = null;
- return QAItem;
- }();
- QAItem.nextId = -1;
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j];
- //
- // Optionally, split only the given ways, if multiple ways share
- // the given node.
- //
- // This is the inverse of `iD.actionJoin`.
- //
- // For testing convenience, accepts an ID to assign to the new way.
- // Normally, this will be undefined and the way will automatically
- // be assigned a new ID.
- //
- // Reference:
- // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
- //
+ if (way.contains(nodeIDs[0])) {
+ n0 = nodeIDs[0];
+ }
- function actionSplit(nodeIds, newWayIds) {
- // accept single ID for backwards-compatiblity
- if (typeof nodeIds === 'string') nodeIds = [nodeIds];
+ if (way.contains(nodeIDs[1])) {
+ n1 = nodeIDs[1];
+ }
+ }
- var _wayIDs; // the strategy for picking which way will have a new version and which way is newly created
+ if (n0 && n1) {
+ // both nodes are part of the restriction
+ var ok = false;
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j];
- var _keepHistoryOn = 'longest'; // 'longest', 'first'
- // The IDs of the ways actually created by running this action
+ if (way.areAdjacent(n0, n1)) {
+ ok = true;
+ break;
+ }
+ }
- var _createdWayIDs = [];
+ if (!ok) {
+ return 'restriction';
+ }
+ }
+ } // 2b. disable if nodes being connected will destroy a member way in a restriction
+ // (to test, make a copy and try actually connecting the nodes)
- function dist(graph, nA, nB) {
- var locA = graph.entity(nA).loc;
- var locB = graph.entity(nB).loc;
- var epsilon = 1e-6;
- return locA && locB ? geoSphericalDistance(locA, locB) : epsilon;
- } // If the way is closed, we need to search for a partner node
- // to split the way at.
- //
- // The following looks for a node that is both far away from
- // the initial node in terms of way segment length and nearby
- // in terms of beeline-distance. This assures that areas get
- // split on the most "natural" points (independent of the number
- // of nodes).
- // For example: bone-shaped areas get split across their waist
- // line, circles across the diameter.
+ for (j = 0; j < memberWays.length; j++) {
+ way = memberWays[j].update({}); // make copy
- function splitArea(nodes, idxA, graph) {
- var lengths = new Array(nodes.length);
- var length;
- var i;
- var best = 0;
- var idxB;
+ for (k = 0; k < nodeIDs.length; k++) {
+ if (nodeIDs[k] === survivor.id) continue;
- function wrap(index) {
- return utilWrap(index, nodes.length);
- } // calculate lengths
+ if (way.areAdjacent(nodeIDs[k], survivor.id)) {
+ way = way.removeNode(nodeIDs[k]);
+ } else {
+ way = way.replaceNode(nodeIDs[k], survivor.id);
+ }
+ }
+ if (way.isDegenerate()) {
+ return 'restriction';
+ }
+ }
+ }
- length = 0;
+ return false; // if a key node appears multiple times (indexOf !== lastIndexOf) it's a FROM-VIA or TO-VIA junction
- for (i = wrap(idxA + 1); i !== idxA; i = wrap(i + 1)) {
- length += dist(graph, nodes[i], nodes[wrap(i - 1)]);
- lengths[i] = length;
+ function hasDuplicates(n, i, arr) {
+ return arr.indexOf(n) !== arr.lastIndexOf(n);
}
- length = 0;
+ function keyNodeFilter(froms, tos) {
+ return function (n) {
+ return froms.indexOf(n) === -1 && tos.indexOf(n) === -1;
+ };
+ }
- for (i = wrap(idxA - 1); i !== idxA; i = wrap(i - 1)) {
- length += dist(graph, nodes[i], nodes[wrap(i + 1)]);
+ function collectNodes(member, collection) {
+ var entity = graph.hasEntity(member.id);
+ if (!entity) return;
+ var role = member.role || '';
- if (length < lengths[i]) {
- lengths[i] = length;
+ if (!collection[role]) {
+ collection[role] = [];
}
- } // determine best opposite node to split
+ if (member.type === 'node') {
+ collection[role].push(member.id);
- for (i = 0; i < nodes.length; i++) {
- var cost = lengths[i] / dist(graph, nodes[idxA], nodes[i]);
+ if (role === 'via') {
+ collection.keyfrom.push(member.id);
+ collection.keyto.push(member.id);
+ }
+ } else if (member.type === 'way') {
+ collection[role].push.apply(collection[role], entity.nodes);
- if (cost > best) {
- idxB = i;
- best = cost;
+ if (role === 'from' || role === 'via') {
+ collection.keyfrom.push(entity.first());
+ collection.keyfrom.push(entity.last());
+ }
+
+ if (role === 'to' || role === 'via') {
+ collection.keyto.push(entity.first());
+ collection.keyto.push(entity.last());
+ }
}
}
+ };
- return idxB;
- }
+ return action;
+ }
- function totalLengthBetweenNodes(graph, nodes) {
- var totalLength = 0;
+ function actionCopyEntities(ids, fromGraph) {
+ var _copies = {};
- for (var i = 0; i < nodes.length - 1; i++) {
- totalLength += dist(graph, nodes[i], nodes[i + 1]);
+ var action = function action(graph) {
+ ids.forEach(function (id) {
+ fromGraph.entity(id).copy(fromGraph, _copies);
+ });
+
+ for (var id in _copies) {
+ graph = graph.replace(_copies[id]);
}
- return totalLength;
- }
-
- function split(graph, nodeId, wayA, newWayId) {
- var wayB = osmWay({
- id: newWayId,
- tags: wayA.tags
- }); // `wayB` is the NEW way
-
- var origNodes = wayA.nodes.slice();
- var nodesA;
- var nodesB;
- var isArea = wayA.isArea();
- var isOuter = osmIsOldMultipolygonOuterMember(wayA, graph);
-
- if (wayA.isClosed()) {
- var nodes = wayA.nodes.slice(0, -1);
- var idxA = nodes.indexOf(nodeId);
- var idxB = splitArea(nodes, idxA, graph);
-
- if (idxB < idxA) {
- nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1));
- nodesB = nodes.slice(idxB, idxA + 1);
- } else {
- nodesA = nodes.slice(idxA, idxB + 1);
- nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1));
- }
- } else {
- var idx = wayA.nodes.indexOf(nodeId, 1);
- nodesA = wayA.nodes.slice(0, idx + 1);
- nodesB = wayA.nodes.slice(idx);
- }
+ return graph;
+ };
- var lengthA = totalLengthBetweenNodes(graph, nodesA);
- var lengthB = totalLengthBetweenNodes(graph, nodesB);
+ action.copies = function () {
+ return _copies;
+ };
- if (_keepHistoryOn === 'longest' && lengthB > lengthA) {
- // keep the history on the longer way, regardless of the node count
- wayA = wayA.update({
- nodes: nodesB
- });
- wayB = wayB.update({
- nodes: nodesA
- });
- var temp = lengthA;
- lengthA = lengthB;
- lengthB = temp;
- } else {
- wayA = wayA.update({
- nodes: nodesA
- });
- wayB = wayB.update({
- nodes: nodesB
- });
- }
+ return action;
+ }
- if (wayA.tags.step_count) {
- // divide up the the step count proportionally between the two ways
- var stepCount = parseFloat(wayA.tags.step_count);
+ function actionDeleteMember(relationId, memberIndex) {
+ return function (graph) {
+ var relation = graph.entity(relationId).removeMember(memberIndex);
+ graph = graph.replace(relation);
- if (stepCount && // ensure a number
- isFinite(stepCount) && // ensure positive
- stepCount > 0 && // ensure integer
- Math.round(stepCount) === stepCount) {
- var tagsA = Object.assign({}, wayA.tags);
- var tagsB = Object.assign({}, wayB.tags);
- var ratioA = lengthA / (lengthA + lengthB);
- var countA = Math.round(stepCount * ratioA);
- tagsA.step_count = countA.toString();
- tagsB.step_count = (stepCount - countA).toString();
- wayA = wayA.update({
- tags: tagsA
- });
- wayB = wayB.update({
- tags: tagsB
- });
- }
+ if (relation.isDegenerate()) {
+ graph = actionDeleteRelation(relation.id)(graph);
}
- graph = graph.replace(wayA);
- graph = graph.replace(wayB);
- graph.parentRelations(wayA).forEach(function (relation) {
- var member; // Turn restrictions - make sure:
- // 1. Splitting a FROM/TO way - only `wayA` OR `wayB` remains in relation
- // (whichever one is connected to the VIA node/ways)
- // 2. Splitting a VIA way - `wayB` remains in relation as a VIA way
-
- if (relation.hasFromViaTo()) {
- var f = relation.memberByRole('from');
- var v = relation.membersByRole('via');
- var t = relation.memberByRole('to');
- var i; // 1. split a FROM/TO
-
- if (f.id === wayA.id || t.id === wayA.id) {
- var keepB = false;
+ return graph;
+ };
+ }
- if (v.length === 1 && v[0].type === 'node') {
- // check via node
- keepB = wayB.contains(v[0].id);
- } else {
- // check via way(s)
- for (i = 0; i < v.length; i++) {
- if (v[i].type === 'way') {
- var wayVia = graph.hasEntity(v[i].id);
+ function actionDiscardTags(difference, discardTags) {
+ discardTags = discardTags || {};
+ return function (graph) {
+ difference.modified().forEach(checkTags);
+ difference.created().forEach(checkTags);
+ return graph;
- if (wayVia && utilArrayIntersection(wayB.nodes, wayVia.nodes).length) {
- keepB = true;
- break;
- }
- }
- }
- }
+ function checkTags(entity) {
+ var keys = Object.keys(entity.tags);
+ var didDiscard = false;
+ var tags = {};
- if (keepB) {
- relation = relation.replaceMember(wayA, wayB);
- graph = graph.replace(relation);
- } // 2. split a VIA
+ for (var i = 0; i < keys.length; i++) {
+ var k = keys[i];
+ if (discardTags[k] || !entity.tags[k]) {
+ didDiscard = true;
} else {
- for (i = 0; i < v.length; i++) {
- if (v[i].type === 'way' && v[i].id === wayA.id) {
- member = {
- id: wayB.id,
- type: 'way',
- role: 'via'
- };
- graph = actionAddMember(relation.id, member, v[i].index + 1)(graph);
- break;
- }
- }
- } // All other relations (Routes, Multipolygons, etc):
- // 1. Both `wayA` and `wayB` remain in the relation
- // 2. But must be inserted as a pair (see `actionAddMember` for details)
-
- } else {
- if (relation === isOuter) {
- graph = graph.replace(relation.mergeTags(wayA.tags));
- graph = graph.replace(wayA.update({
- tags: {}
- }));
- graph = graph.replace(wayB.update({
- tags: {}
- }));
+ tags[k] = entity.tags[k];
}
-
- member = {
- id: wayB.id,
- type: 'way',
- role: relation.memberById(wayA.id).role
- };
- var insertPair = {
- originalID: wayA.id,
- insertedID: wayB.id,
- nodes: origNodes
- };
- graph = actionAddMember(relation.id, member, undefined, insertPair)(graph);
}
- });
- if (!isOuter && isArea) {
- var multipolygon = osmRelation({
- tags: Object.assign({}, wayA.tags, {
- type: 'multipolygon'
- }),
- members: [{
- id: wayA.id,
- role: 'outer',
- type: 'way'
- }, {
- id: wayB.id,
- role: 'outer',
- type: 'way'
- }]
- });
- graph = graph.replace(multipolygon);
- graph = graph.replace(wayA.update({
- tags: {}
- }));
- graph = graph.replace(wayB.update({
- tags: {}
- }));
+ if (didDiscard) {
+ graph = graph.replace(entity.update({
+ tags: tags
+ }));
+ }
}
+ };
+ }
- _createdWayIDs.push(wayB.id);
+ //
+ // Optionally, disconnect only the given ways.
+ //
+ // For testing convenience, accepts an ID to assign to the (first) new node.
+ // Normally, this will be undefined and the way will automatically
+ // be assigned a new ID.
+ //
+ // This is the inverse of `iD.actionConnect`.
+ //
+ // Reference:
+ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
+ // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
+ //
- return graph;
- }
+ function actionDisconnect(nodeId, newNodeId) {
+ var wayIds;
+ var disconnectableRelationTypes = {
+ 'associatedStreet': true,
+ 'enforcement': true,
+ 'site': true
+ };
var action = function action(graph) {
- _createdWayIDs = [];
- var newWayIndex = 0;
-
- for (var i = 0; i < nodeIds.length; i++) {
- var nodeId = nodeIds[i];
- var candidates = action.waysForNode(nodeId, graph);
+ var node = graph.entity(nodeId);
+ var connections = action.connections(graph);
+ connections.forEach(function (connection) {
+ var way = graph.entity(connection.wayID);
+ var newNode = osmNode({
+ id: newNodeId,
+ loc: node.loc,
+ tags: node.tags
+ });
+ graph = graph.replace(newNode);
- for (var j = 0; j < candidates.length; j++) {
- graph = split(graph, nodeId, candidates[j], newWayIds && newWayIds[newWayIndex]);
- newWayIndex += 1;
+ if (connection.index === 0 && way.isArea()) {
+ // replace shared node with shared node..
+ graph = graph.replace(way.replaceNode(way.nodes[0], newNode.id));
+ } else if (way.isClosed() && connection.index === way.nodes.length - 1) {
+ // replace closing node with new new node..
+ graph = graph.replace(way.unclose().addNode(newNode.id));
+ } else {
+ // replace shared node with multiple new nodes..
+ graph = graph.replace(way.updateNode(newNode.id, connection.index));
}
- }
-
+ });
return graph;
};
- action.getCreatedWayIDs = function () {
- return _createdWayIDs;
- };
-
- action.waysForNode = function (nodeId, graph) {
- var node = graph.entity(nodeId);
- var splittableParents = graph.parentWays(node).filter(isSplittable);
+ action.connections = function (graph) {
+ var candidates = [];
+ var keeping = false;
+ var parentWays = graph.parentWays(graph.entity(nodeId));
+ var way, waynode;
- if (!_wayIDs) {
- // If the ways to split aren't specified, only split the lines.
- // If there are no lines to split, split the areas.
- var hasLine = splittableParents.some(function (parent) {
- return parent.geometry(graph) === 'line';
- });
+ for (var i = 0; i < parentWays.length; i++) {
+ way = parentWays[i];
- if (hasLine) {
- return splittableParents.filter(function (parent) {
- return parent.geometry(graph) === 'line';
- });
+ if (wayIds && wayIds.indexOf(way.id) === -1) {
+ keeping = true;
+ continue;
}
- }
-
- return splittableParents;
- function isSplittable(parent) {
- // If the ways to split are specified, ignore everything else.
- if (_wayIDs && _wayIDs.indexOf(parent.id) === -1) return false; // We can fake splitting closed ways at their endpoints...
+ if (way.isArea() && way.nodes[0] === nodeId) {
+ candidates.push({
+ wayID: way.id,
+ index: 0
+ });
+ } else {
+ for (var j = 0; j < way.nodes.length; j++) {
+ waynode = way.nodes[j];
- if (parent.isClosed()) return true; // otherwise, we can't split nodes at their endpoints.
+ if (waynode === nodeId) {
+ if (way.isClosed() && parentWays.length > 1 && wayIds && wayIds.indexOf(way.id) !== -1 && j === way.nodes.length - 1) {
+ continue;
+ }
- for (var i = 1; i < parent.nodes.length - 1; i++) {
- if (parent.nodes[i] === nodeId) return true;
+ candidates.push({
+ wayID: way.id,
+ index: j
+ });
+ }
+ }
}
-
- return false;
}
- };
- action.ways = function (graph) {
- return utilArrayUniq([].concat.apply([], nodeIds.map(function (nodeId) {
- return action.waysForNode(nodeId, graph);
- })));
+ return keeping ? candidates : candidates.slice(1);
};
action.disabled = function (graph) {
- for (var i = 0; i < nodeIds.length; i++) {
- var nodeId = nodeIds[i];
- var candidates = action.waysForNode(nodeId, graph);
-
- if (candidates.length === 0 || _wayIDs && _wayIDs.length !== candidates.length) {
- return 'not_eligible';
- }
- }
+ var connections = action.connections(graph);
+ if (connections.length === 0) return 'not_connected';
+ var parentWays = graph.parentWays(graph.entity(nodeId));
+ var seenRelationIds = {};
+ var sharedRelation;
+ parentWays.forEach(function (way) {
+ var relations = graph.parentRelations(way);
+ relations.filter(function (relation) {
+ return !disconnectableRelationTypes[relation.tags.type];
+ }).forEach(function (relation) {
+ if (relation.id in seenRelationIds) {
+ if (wayIds) {
+ if (wayIds.indexOf(way.id) !== -1 || wayIds.indexOf(seenRelationIds[relation.id]) !== -1) {
+ sharedRelation = relation;
+ }
+ } else {
+ sharedRelation = relation;
+ }
+ } else {
+ seenRelationIds[relation.id] = way.id;
+ }
+ });
+ });
+ if (sharedRelation) return 'relation';
};
action.limitWays = function (val) {
- if (!arguments.length) return _wayIDs;
- _wayIDs = val;
- return action;
- };
-
- action.keepHistoryOn = function (val) {
- if (!arguments.length) return _keepHistoryOn;
- _keepHistoryOn = val;
+ if (!arguments.length) return wayIds;
+ wayIds = val;
return action;
};
return action;
}
- function coreGraph(other, mutable) {
- if (!(this instanceof coreGraph)) return new coreGraph(other, mutable);
-
- if (other instanceof coreGraph) {
- var base = other.base();
- this.entities = Object.assign(Object.create(base.entities), other.entities);
- this._parentWays = Object.assign(Object.create(base.parentWays), other._parentWays);
- this._parentRels = Object.assign(Object.create(base.parentRels), other._parentRels);
- } else {
- this.entities = Object.create({});
- this._parentWays = Object.create({});
- this._parentRels = Object.create({});
- this.rebase(other || [], [this]);
- }
+ function actionExtract(entityID, projection) {
+ var extractedNodeID;
- this.transients = {};
- this._childNodes = {};
- this.frozen = !mutable;
- }
- coreGraph.prototype = {
- hasEntity: function hasEntity(id) {
- return this.entities[id];
- },
- entity: function entity(id) {
- var entity = this.entities[id]; //https://github.com/openstreetmap/iD/issues/3973#issuecomment-307052376
+ var action = function action(graph) {
+ var entity = graph.entity(entityID);
- if (!entity) {
- entity = this.entities.__proto__[id]; // eslint-disable-line no-proto
+ if (entity.type === 'node') {
+ return extractFromNode(entity, graph);
}
- if (!entity) {
- throw new Error('entity ' + id + ' not found');
- }
+ return extractFromWayOrRelation(entity, graph);
+ };
- return entity;
- },
- geometry: function geometry(id) {
- return this.entity(id).geometry(this);
- },
- "transient": function transient(entity, key, fn) {
- var id = entity.id;
- var transients = this.transients[id] || (this.transients[id] = {});
+ function extractFromNode(node, graph) {
+ extractedNodeID = node.id; // Create a new node to replace the one we will detach
- if (transients[key] !== undefined) {
- return transients[key];
- }
+ var replacement = osmNode({
+ loc: node.loc
+ });
+ graph = graph.replace(replacement); // Process each way in turn, updating the graph as we go
- transients[key] = fn.call(entity);
- return transients[key];
- },
- parentWays: function parentWays(entity) {
- var parents = this._parentWays[entity.id];
- var result = [];
+ graph = graph.parentWays(node).reduce(function (accGraph, parentWay) {
+ return accGraph.replace(parentWay.replaceNode(entityID, replacement.id));
+ }, graph); // Process any relations too
- if (parents) {
- parents.forEach(function (id) {
- result.push(this.entity(id));
- }, this);
- }
+ return graph.parentRelations(node).reduce(function (accGraph, parentRel) {
+ return accGraph.replace(parentRel.replaceMember(node, replacement));
+ }, graph);
+ }
- return result;
- },
- isPoi: function isPoi(entity) {
- var parents = this._parentWays[entity.id];
- return !parents || parents.size === 0;
- },
- isShared: function isShared(entity) {
- var parents = this._parentWays[entity.id];
- return parents && parents.size > 1;
- },
- parentRelations: function parentRelations(entity) {
- var parents = this._parentRels[entity.id];
- var result = [];
+ function extractFromWayOrRelation(entity, graph) {
+ var fromGeometry = entity.geometry(graph);
+ var keysToCopyAndRetain = ['source', 'wheelchair'];
+ var keysToRetain = ['area'];
+ var buildingKeysToRetain = ['architect', 'building', 'height', 'layer'];
+ var extractedLoc = d3_geoPath(projection).centroid(entity.asGeoJSON(graph));
+ extractedLoc = extractedLoc && projection.invert(extractedLoc);
- if (parents) {
- parents.forEach(function (id) {
- result.push(this.entity(id));
- }, this);
+ if (!extractedLoc || !isFinite(extractedLoc[0]) || !isFinite(extractedLoc[1])) {
+ extractedLoc = entity.extent(graph).center();
}
- return result;
- },
- parentMultipolygons: function parentMultipolygons(entity) {
- return this.parentRelations(entity).filter(function (relation) {
- return relation.isMultipolygon();
- });
- },
- childNodes: function childNodes(entity) {
- if (this._childNodes[entity.id]) return this._childNodes[entity.id];
- if (!entity.nodes) return [];
- var nodes = [];
-
- for (var i = 0; i < entity.nodes.length; i++) {
- nodes[i] = this.entity(entity.nodes[i]);
- }
- this._childNodes[entity.id] = nodes;
- return this._childNodes[entity.id];
- },
- base: function base() {
- return {
- 'entities': Object.getPrototypeOf(this.entities),
- 'parentWays': Object.getPrototypeOf(this._parentWays),
- 'parentRels': Object.getPrototypeOf(this._parentRels)
+ var indoorAreaValues = {
+ area: true,
+ corridor: true,
+ elevator: true,
+ level: true,
+ room: true
};
- },
- // Unlike other graph methods, rebase mutates in place. This is because it
- // is used only during the history operation that merges newly downloaded
- // data into each state. To external consumers, it should appear as if the
- // graph always contained the newly downloaded data.
- rebase: function rebase(entities, stack, force) {
- var base = this.base();
- var i, j, k, id;
+ var isBuilding = entity.tags.building && entity.tags.building !== 'no' || entity.tags['building:part'] && entity.tags['building:part'] !== 'no';
+ var isIndoorArea = fromGeometry === 'area' && entity.tags.indoor && indoorAreaValues[entity.tags.indoor];
+ var entityTags = Object.assign({}, entity.tags); // shallow copy
- for (i = 0; i < entities.length; i++) {
- var entity = entities[i];
- if (!entity.visible || !force && base.entities[entity.id]) continue; // Merging data into the base graph
+ var pointTags = {};
- base.entities[entity.id] = entity;
+ for (var key in entityTags) {
+ if (entity.type === 'relation' && key === 'type') {
+ continue;
+ }
- this._updateCalculated(undefined, entity, base.parentWays, base.parentRels); // Restore provisionally-deleted nodes that are discovered to have an extant parent
+ if (keysToRetain.indexOf(key) !== -1) {
+ continue;
+ }
+ if (isBuilding) {
+ // don't transfer building-related tags
+ if (buildingKeysToRetain.indexOf(key) !== -1 || key.match(/^building:.{1,}/) || key.match(/^roof:.{1,}/)) continue;
+ } // leave `indoor` tag on the area
- if (entity.type === 'way') {
- for (j = 0; j < entity.nodes.length; j++) {
- id = entity.nodes[j];
- for (k = 1; k < stack.length; k++) {
- var ents = stack[k].entities;
-
- if (ents.hasOwnProperty(id) && ents[id] === undefined) {
- delete ents[id];
- }
- }
- }
- }
- }
-
- for (i = 0; i < stack.length; i++) {
- stack[i]._updateRebased();
- }
- },
- _updateRebased: function _updateRebased() {
- var base = this.base();
- Object.keys(this._parentWays).forEach(function (child) {
- if (base.parentWays[child]) {
- base.parentWays[child].forEach(function (id) {
- if (!this.entities.hasOwnProperty(id)) {
- this._parentWays[child].add(id);
- }
- }, this);
- }
- }, this);
- Object.keys(this._parentRels).forEach(function (child) {
- if (base.parentRels[child]) {
- base.parentRels[child].forEach(function (id) {
- if (!this.entities.hasOwnProperty(id)) {
- this._parentRels[child].add(id);
- }
- }, this);
- }
- }, this);
- this.transients = {}; // this._childNodes is not updated, under the assumption that
- // ways are always downloaded with their child nodes.
- },
- // Updates calculated properties (parentWays, parentRels) for the specified change
- _updateCalculated: function _updateCalculated(oldentity, entity, parentWays, parentRels) {
- parentWays = parentWays || this._parentWays;
- parentRels = parentRels || this._parentRels;
- var type = entity && entity.type || oldentity && oldentity.type;
- var removed, added, i;
+ if (isIndoorArea && key === 'indoor') {
+ continue;
+ } // copy the tag from the entity to the point
- if (type === 'way') {
- // Update parentWays
- if (oldentity && entity) {
- removed = utilArrayDifference(oldentity.nodes, entity.nodes);
- added = utilArrayDifference(entity.nodes, oldentity.nodes);
- } else if (oldentity) {
- removed = oldentity.nodes;
- added = [];
- } else if (entity) {
- removed = [];
- added = entity.nodes;
- }
- for (i = 0; i < removed.length; i++) {
- // make a copy of prototype property, store as own property, and update..
- parentWays[removed[i]] = new Set(parentWays[removed[i]]);
- parentWays[removed[i]]["delete"](oldentity.id);
- }
+ pointTags[key] = entityTags[key]; // leave addresses and some other tags so they're on both features
- for (i = 0; i < added.length; i++) {
- // make a copy of prototype property, store as own property, and update..
- parentWays[added[i]] = new Set(parentWays[added[i]]);
- parentWays[added[i]].add(entity.id);
- }
- } else if (type === 'relation') {
- // Update parentRels
- // diff only on the IDs since the same entity can be a member multiple times with different roles
- var oldentityMemberIDs = oldentity ? oldentity.members.map(function (m) {
- return m.id;
- }) : [];
- var entityMemberIDs = entity ? entity.members.map(function (m) {
- return m.id;
- }) : [];
+ if (keysToCopyAndRetain.indexOf(key) !== -1 || key.match(/^addr:.{1,}/)) {
+ continue;
+ } else if (isIndoorArea && key === 'level') {
+ // leave `level` on both features
+ continue;
+ } // remove the tag from the entity
- if (oldentity && entity) {
- removed = utilArrayDifference(oldentityMemberIDs, entityMemberIDs);
- added = utilArrayDifference(entityMemberIDs, oldentityMemberIDs);
- } else if (oldentity) {
- removed = oldentityMemberIDs;
- added = [];
- } else if (entity) {
- removed = [];
- added = entityMemberIDs;
- }
- for (i = 0; i < removed.length; i++) {
- // make a copy of prototype property, store as own property, and update..
- parentRels[removed[i]] = new Set(parentRels[removed[i]]);
- parentRels[removed[i]]["delete"](oldentity.id);
- }
+ delete entityTags[key];
+ }
- for (i = 0; i < added.length; i++) {
- // make a copy of prototype property, store as own property, and update..
- parentRels[added[i]] = new Set(parentRels[added[i]]);
- parentRels[added[i]].add(entity.id);
- }
+ if (!isBuilding && !isIndoorArea && fromGeometry === 'area') {
+ // ensure that areas keep area geometry
+ entityTags.area = 'yes';
}
- },
- replace: function replace(entity) {
- if (this.entities[entity.id] === entity) return this;
- return this.update(function () {
- this._updateCalculated(this.entities[entity.id], entity);
- this.entities[entity.id] = entity;
+ var replacement = osmNode({
+ loc: extractedLoc,
+ tags: pointTags
});
- },
- remove: function remove(entity) {
- return this.update(function () {
- this._updateCalculated(entity, undefined);
+ graph = graph.replace(replacement);
+ extractedNodeID = replacement.id;
+ return graph.replace(entity.update({
+ tags: entityTags
+ }));
+ }
- this.entities[entity.id] = undefined;
- });
- },
- revert: function revert(id) {
- var baseEntity = this.base().entities[id];
- var headEntity = this.entities[id];
- if (headEntity === baseEntity) return this;
- return this.update(function () {
- this._updateCalculated(headEntity, baseEntity);
+ action.getExtractedNodeID = function () {
+ return extractedNodeID;
+ };
- delete this.entities[id];
+ return action;
+ }
+
+ //
+ // This is the inverse of `iD.actionSplit`.
+ //
+ // Reference:
+ // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as
+ // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java
+ //
+
+ function actionJoin(ids) {
+ function groupEntitiesByGeometry(graph) {
+ var entities = ids.map(function (id) {
+ return graph.entity(id);
});
- },
- update: function update() {
- var graph = this.frozen ? coreGraph(this, true) : this;
+ return Object.assign({
+ line: []
+ }, utilArrayGroupBy(entities, function (entity) {
+ return entity.geometry(graph);
+ }));
+ }
- for (var i = 0; i < arguments.length; i++) {
- arguments[i].call(graph, graph);
- }
+ var action = function action(graph) {
+ var ways = ids.map(graph.entity, graph); // Prefer to keep an existing way.
+ // if there are multiple existing ways, keep the oldest one
+ // the oldest way is determined by the ID of the way.
- if (this.frozen) graph.frozen = true;
- return graph;
- },
- // Obliterates any existing entities
- load: function load(entities) {
- var base = this.base();
- this.entities = Object.create(base.entities);
+ var survivorID = utilOldestID(ways.map(function (way) {
+ return way.id;
+ })); // if any of the ways are sided (e.g. coastline, cliff, kerb)
+ // sort them first so they establish the overall order - #6033
- for (var i in entities) {
- this.entities[i] = entities[i];
+ ways.sort(function (a, b) {
+ var aSided = a.isSided();
+ var bSided = b.isSided();
+ return aSided && !bSided ? -1 : bSided && !aSided ? 1 : 0;
+ });
+ var sequences = osmJoinWays(ways, graph);
+ var joined = sequences[0]; // We might need to reverse some of these ways before joining them. #4688
+ // `joined.actions` property will contain any actions we need to apply.
- this._updateCalculated(base.entities[i], this.entities[i]);
- }
+ graph = sequences.actions.reduce(function (g, action) {
+ return action(g);
+ }, graph);
+ var survivor = graph.entity(survivorID);
+ survivor = survivor.update({
+ nodes: joined.nodes.map(function (n) {
+ return n.id;
+ })
+ });
+ graph = graph.replace(survivor);
+ joined.forEach(function (way) {
+ if (way.id === survivorID) return;
+ graph.parentRelations(way).forEach(function (parent) {
+ graph = graph.replace(parent.replaceMember(way, survivor));
+ });
+ survivor = survivor.mergeTags(way.tags);
+ graph = graph.replace(survivor);
+ graph = actionDeleteWay(way.id)(graph);
+ }); // Finds if the join created a single-member multipolygon,
+ // and if so turns it into a basic area instead
- return this;
- }
- };
+ function checkForSimpleMultipolygon() {
+ if (!survivor.isClosed()) return;
+ var multipolygons = graph.parentMultipolygons(survivor).filter(function (multipolygon) {
+ // find multipolygons where the survivor is the only member
+ return multipolygon.members.length === 1;
+ }); // skip if this is the single member of multiple multipolygons
- function osmTurn(turn) {
- if (!(this instanceof osmTurn)) {
- return new osmTurn(turn);
- }
+ if (multipolygons.length !== 1) return;
+ var multipolygon = multipolygons[0];
- Object.assign(this, turn);
- }
- function osmIntersection(graph, startVertexId, maxDistance) {
- maxDistance = maxDistance || 30; // in meters
+ for (var key in survivor.tags) {
+ if (multipolygon.tags[key] && // don't collapse if tags cannot be cleanly merged
+ multipolygon.tags[key] !== survivor.tags[key]) return;
+ }
- var vgraph = coreGraph(); // virtual graph
+ survivor = survivor.mergeTags(multipolygon.tags);
+ graph = graph.replace(survivor);
+ graph = actionDeleteRelation(multipolygon.id, true
+ /* allow untagged members */
+ )(graph);
+ var tags = Object.assign({}, survivor.tags);
- var i, j, k;
+ if (survivor.geometry(graph) !== 'area') {
+ // ensure the feature persists as an area
+ tags.area = 'yes';
+ }
- function memberOfRestriction(entity) {
- return graph.parentRelations(entity).some(function (r) {
- return r.isRestriction();
- });
- }
+ delete tags.type; // remove type=multipolygon
- function isRoad(way) {
- if (way.isArea() || way.isDegenerate()) return false;
- var roads = {
- 'motorway': true,
- 'motorway_link': true,
- 'trunk': true,
- 'trunk_link': true,
- 'primary': true,
- 'primary_link': true,
- 'secondary': true,
- 'secondary_link': true,
- 'tertiary': true,
- 'tertiary_link': true,
- 'residential': true,
- 'unclassified': true,
- 'living_street': true,
- 'service': true,
- 'road': true,
- 'track': true
- };
- return roads[way.tags.highway];
- }
+ survivor = survivor.update({
+ tags: tags
+ });
+ graph = graph.replace(survivor);
+ }
- var startNode = graph.entity(startVertexId);
- var checkVertices = [startNode];
- var checkWays;
- var vertices = [];
- var vertexIds = [];
- var vertex;
- var ways = [];
- var wayIds = [];
- var way;
- var nodes = [];
- var node;
- var parents = [];
- var parent; // `actions` will store whatever actions must be performed to satisfy
- // preconditions for adding a turn restriction to this intersection.
- // - Remove any existing degenerate turn restrictions (missing from/to, etc)
- // - Reverse oneways so that they are drawn in the forward direction
- // - Split ways on key vertices
+ checkForSimpleMultipolygon();
+ return graph;
+ }; // Returns the number of nodes the resultant way is expected to have
- var actions = []; // STEP 1: walk the graph outwards from starting vertex to search
- // for more key vertices and ways to include in the intersection..
- while (checkVertices.length) {
- vertex = checkVertices.pop(); // check this vertex for parent ways that are roads
+ action.resultingWayNodesLength = function (graph) {
+ return ids.reduce(function (count, id) {
+ return count + graph.entity(id).nodes.length;
+ }, 0) - ids.length - 1;
+ };
- checkWays = graph.parentWays(vertex);
- var hasWays = false;
+ action.disabled = function (graph) {
+ var geometries = groupEntitiesByGeometry(graph);
- for (i = 0; i < checkWays.length; i++) {
- way = checkWays[i];
- if (!isRoad(way) && !memberOfRestriction(way)) continue;
- ways.push(way); // it's a road, or it's already in a turn restriction
+ if (ids.length < 2 || ids.length !== geometries.line.length) {
+ return 'not_eligible';
+ }
- hasWays = true; // check the way's children for more key vertices
+ var joined = osmJoinWays(ids.map(graph.entity, graph), graph);
- nodes = utilArrayUniq(graph.childNodes(way));
+ if (joined.length > 1) {
+ return 'not_adjacent';
+ }
- for (j = 0; j < nodes.length; j++) {
- node = nodes[j];
- if (node === vertex) continue; // same thing
+ var i; // All joined ways must belong to the same set of (non-restriction) relations.
+ // Restriction relations have different logic, below, which allows some cases
+ // this prohibits, and prohibits some cases this allows.
- if (vertices.indexOf(node) !== -1) continue; // seen it already
+ var sortedParentRelations = function sortedParentRelations(id) {
+ return graph.parentRelations(graph.entity(id)).filter(function (rel) {
+ return !rel.isRestriction() && !rel.isConnectivity();
+ }).sort(function (a, b) {
+ return a.id - b.id;
+ });
+ };
- if (geoSphericalDistance(node.loc, startNode.loc) > maxDistance) continue; // too far from start
- // a key vertex will have parents that are also roads
+ var relsA = sortedParentRelations(ids[0]);
- var hasParents = false;
- parents = graph.parentWays(node);
+ for (i = 1; i < ids.length; i++) {
+ var relsB = sortedParentRelations(ids[i]);
- for (k = 0; k < parents.length; k++) {
- parent = parents[k];
- if (parent === way) continue; // same thing
+ if (!utilArrayIdentical(relsA, relsB)) {
+ return 'conflicting_relations';
+ }
+ } // Loop through all combinations of path-pairs
+ // to check potential intersections between all pairs
- if (ways.indexOf(parent) !== -1) continue; // seen it already
- if (!isRoad(parent)) continue; // not a road
+ for (i = 0; i < ids.length - 1; i++) {
+ for (var j = i + 1; j < ids.length; j++) {
+ var path1 = graph.childNodes(graph.entity(ids[i])).map(function (e) {
+ return e.loc;
+ });
+ var path2 = graph.childNodes(graph.entity(ids[j])).map(function (e) {
+ return e.loc;
+ });
+ var intersections = geoPathIntersections(path1, path2); // Check if intersections are just nodes lying on top of
+ // each other/the line, as opposed to crossing it
- hasParents = true;
- break;
- }
+ var common = utilArrayIntersection(joined[0].nodes.map(function (n) {
+ return n.loc.toString();
+ }), intersections.map(function (n) {
+ return n.toString();
+ }));
- if (hasParents) {
- checkVertices.push(node);
+ if (common.length !== intersections.length) {
+ return 'paths_intersect';
}
}
}
- if (hasWays) {
- vertices.push(vertex);
- }
- }
-
- vertices = utilArrayUniq(vertices);
- ways = utilArrayUniq(ways); // STEP 2: Build a virtual graph containing only the entities in the intersection..
- // Everything done after this step should act on the virtual graph
- // Any actions that must be performed later to the main graph go in `actions` array
+ var nodeIds = joined[0].nodes.map(function (n) {
+ return n.id;
+ }).slice(1, -1);
+ var relation;
+ var tags = {};
+ var conflicting = false;
+ joined[0].forEach(function (way) {
+ var parents = graph.parentRelations(way);
+ parents.forEach(function (parent) {
+ if ((parent.isRestriction() || parent.isConnectivity()) && parent.members.some(function (m) {
+ return nodeIds.indexOf(m.id) >= 0;
+ })) {
+ relation = parent;
+ }
+ });
- ways.forEach(function (way) {
- graph.childNodes(way).forEach(function (node) {
- vgraph = vgraph.replace(node);
- });
- vgraph = vgraph.replace(way);
- graph.parentRelations(way).forEach(function (relation) {
- if (relation.isRestriction()) {
- if (relation.isValidRestriction(graph)) {
- vgraph = vgraph.replace(relation);
- } else if (relation.isComplete(graph)) {
- actions.push(actionDeleteRelation(relation.id));
+ for (var k in way.tags) {
+ if (!(k in tags)) {
+ tags[k] = way.tags[k];
+ } else if (tags[k] && osmIsInterestingTag(k) && tags[k] !== way.tags[k]) {
+ conflicting = true;
}
}
});
- }); // STEP 3: Force all oneways to be drawn in the forward direction
- ways.forEach(function (w) {
- var way = vgraph.entity(w.id);
+ if (relation) {
+ return relation.isRestriction() ? 'restriction' : 'connectivity';
+ }
- if (way.tags.oneway === '-1') {
- var action = actionReverse(way.id, {
- reverseOneway: true
- });
- actions.push(action);
- vgraph = action(vgraph);
+ if (conflicting) {
+ return 'conflicting_tags';
}
- }); // STEP 4: Split ways on key vertices
+ };
- var origCount = osmEntity.id.next.way;
- vertices.forEach(function (v) {
- // This is an odd way to do it, but we need to find all the ways that
- // will be split here, then split them one at a time to ensure that these
- // actions can be replayed on the main graph exactly in the same order.
- // (It is unintuitive, but the order of ways returned from graph.parentWays()
- // is arbitrary, depending on how the main graph and vgraph were built)
- var splitAll = actionSplit([v.id]).keepHistoryOn('first');
+ return action;
+ }
- if (!splitAll.disabled(vgraph)) {
- splitAll.ways(vgraph).forEach(function (way) {
- var splitOne = actionSplit([v.id]).limitWays([way.id]).keepHistoryOn('first');
- actions.push(splitOne);
- vgraph = splitOne(vgraph);
+ function actionMerge(ids) {
+ function groupEntitiesByGeometry(graph) {
+ var entities = ids.map(function (id) {
+ return graph.entity(id);
+ });
+ return Object.assign({
+ point: [],
+ area: [],
+ line: [],
+ relation: []
+ }, utilArrayGroupBy(entities, function (entity) {
+ return entity.geometry(graph);
+ }));
+ }
+
+ var action = function action(graph) {
+ var geometries = groupEntitiesByGeometry(graph);
+ var target = geometries.area[0] || geometries.line[0];
+ var points = geometries.point;
+ points.forEach(function (point) {
+ target = target.mergeTags(point.tags);
+ graph = graph.replace(target);
+ graph.parentRelations(point).forEach(function (parent) {
+ graph = graph.replace(parent.replaceMember(point, target));
});
- }
- }); // In here is where we should also split the intersection at nearby junction.
- // for https://github.com/mapbox/iD-internal/issues/31
- // nearbyVertices.forEach(function(v) {
- // });
- // Reasons why we reset the way id count here:
- // 1. Continuity with way ids created by the splits so that we can replay
- // these actions later if the user decides to create a turn restriction
- // 2. Avoids churning way ids just by hovering over a vertex
- // and displaying the turn restriction editor
+ var nodes = utilArrayUniq(graph.childNodes(target));
+ var removeNode = point;
- osmEntity.id.next.way = origCount; // STEP 5: Update arrays to point to vgraph entities
+ if (!point.isNew()) {
+ // Try to preserve the original point if it already has
+ // an ID in the database.
+ var inserted = false;
- vertexIds = vertices.map(function (v) {
- return v.id;
- });
- vertices = [];
- ways = [];
- vertexIds.forEach(function (id) {
- var vertex = vgraph.entity(id);
- var parents = vgraph.parentWays(vertex);
- vertices.push(vertex);
- ways = ways.concat(parents);
- });
- vertices = utilArrayUniq(vertices);
- ways = utilArrayUniq(ways);
- vertexIds = vertices.map(function (v) {
- return v.id;
- });
- wayIds = ways.map(function (w) {
- return w.id;
- }); // STEP 6: Update the ways with some metadata that will be useful for
- // walking the intersection graph later and rendering turn arrows.
+ var canBeReplaced = function canBeReplaced(node) {
+ return !(graph.parentWays(node).length > 1 || graph.parentRelations(node).length);
+ };
- function withMetadata(way, vertexIds) {
- var __oneWay = way.isOneWay(); // which affixes are key vertices?
+ var replaceNode = function replaceNode(node) {
+ graph = graph.replace(point.update({
+ tags: node.tags,
+ loc: node.loc
+ }));
+ target = target.replaceNode(node.id, point.id);
+ graph = graph.replace(target);
+ removeNode = node;
+ inserted = true;
+ };
+ var i;
+ var node; // First, try to replace a new child node on the target way.
- var __first = vertexIds.indexOf(way.first()) !== -1;
+ for (i = 0; i < nodes.length; i++) {
+ node = nodes[i];
- var __last = vertexIds.indexOf(way.last()) !== -1; // what roles is this way eligible for?
+ if (canBeReplaced(node) && node.isNew()) {
+ replaceNode(node);
+ break;
+ }
+ }
+ if (!inserted && point.hasInterestingTags()) {
+ // No new child node found, try to find an existing, but
+ // uninteresting child node instead.
+ for (i = 0; i < nodes.length; i++) {
+ node = nodes[i];
- var __via = __first && __last;
+ if (canBeReplaced(node) && !node.hasInterestingTags()) {
+ replaceNode(node);
+ break;
+ }
+ }
- var __from = __first && !__oneWay || __last;
+ if (!inserted) {
+ // Still not inserted, try to find an existing, interesting,
+ // but more recent child node.
+ for (i = 0; i < nodes.length; i++) {
+ node = nodes[i];
- var __to = __first || __last && !__oneWay;
+ if (canBeReplaced(node) && utilCompareIDs(point.id, node.id) < 0) {
+ replaceNode(node);
+ break;
+ }
+ }
+ } // If the point still hasn't been inserted, we give up.
+ // There are more interesting or older nodes on the way.
- return way.update({
- __first: __first,
- __last: __last,
- __from: __from,
- __via: __via,
- __to: __to,
- __oneWay: __oneWay
+ }
+ }
+
+ graph = graph.remove(removeNode);
});
- }
- ways = [];
- wayIds.forEach(function (id) {
- var way = withMetadata(vgraph.entity(id), vertexIds);
- vgraph = vgraph.replace(way);
- ways.push(way);
- }); // STEP 7: Simplify - This is an iterative process where we:
- // 1. Find trivial vertices with only 2 parents
- // 2. trim off the leaf way from those vertices and remove from vgraph
+ if (target.tags.area === 'yes') {
+ var tags = Object.assign({}, target.tags); // shallow copy
- var keepGoing;
- var removeWayIds = [];
- var removeVertexIds = [];
+ delete tags.area;
- do {
- keepGoing = false;
- checkVertices = vertexIds.slice();
+ if (osmTagSuggestingArea(tags)) {
+ // remove the `area` tag if area geometry is now implied - #3851
+ target = target.update({
+ tags: tags
+ });
+ graph = graph.replace(target);
+ }
+ }
- for (i = 0; i < checkVertices.length; i++) {
- var vertexId = checkVertices[i];
- vertex = vgraph.hasEntity(vertexId);
+ return graph;
+ };
- if (!vertex) {
- if (vertexIds.indexOf(vertexId) !== -1) {
- vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
- }
+ action.disabled = function (graph) {
+ var geometries = groupEntitiesByGeometry(graph);
- removeVertexIds.push(vertexId);
- continue;
- }
+ if (geometries.point.length === 0 || geometries.area.length + geometries.line.length !== 1 || geometries.relation.length !== 0) {
+ return 'not_eligible';
+ }
+ };
- parents = vgraph.parentWays(vertex);
+ return action;
+ }
- if (parents.length < 3) {
- if (vertexIds.indexOf(vertexId) !== -1) {
- vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
- }
- }
+ //
+ // 1. move all the nodes to a common location
+ // 2. `actionConnect` them
- if (parents.length === 2) {
- // vertex with 2 parents is trivial
- var a = parents[0];
- var b = parents[1];
- var aIsLeaf = a && !a.__via;
- var bIsLeaf = b && !b.__via;
- var leaf, survivor;
+ function actionMergeNodes(nodeIDs, loc) {
+ // If there is a single "interesting" node, use that as the location.
+ // Otherwise return the average location of all the nodes.
+ function chooseLoc(graph) {
+ if (!nodeIDs.length) return null;
+ var sum = [0, 0];
+ var interestingCount = 0;
+ var interestingLoc;
- if (aIsLeaf && !bIsLeaf) {
- leaf = a;
- survivor = b;
- } else if (!aIsLeaf && bIsLeaf) {
- leaf = b;
- survivor = a;
- }
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var node = graph.entity(nodeIDs[i]);
- if (leaf && survivor) {
- survivor = withMetadata(survivor, vertexIds); // update survivor way
+ if (node.hasInterestingTags()) {
+ interestingLoc = ++interestingCount === 1 ? node.loc : null;
+ }
- vgraph = vgraph.replace(survivor).remove(leaf); // update graph
+ sum = geoVecAdd(sum, node.loc);
+ }
- removeWayIds.push(leaf.id);
- keepGoing = true;
- }
- }
+ return interestingLoc || geoVecScale(sum, 1 / nodeIDs.length);
+ }
- parents = vgraph.parentWays(vertex);
+ var action = function action(graph) {
+ if (nodeIDs.length < 2) return graph;
+ var toLoc = loc;
- if (parents.length < 2) {
- // vertex is no longer a key vertex
- if (vertexIds.indexOf(vertexId) !== -1) {
- vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
- }
+ if (!toLoc) {
+ toLoc = chooseLoc(graph);
+ }
- removeVertexIds.push(vertexId);
- keepGoing = true;
- }
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var node = graph.entity(nodeIDs[i]);
- if (parents.length < 1) {
- // vertex is no longer attached to anything
- vgraph = vgraph.remove(vertex);
+ if (node.loc !== toLoc) {
+ graph = graph.replace(node.move(toLoc));
}
}
- } while (keepGoing);
- vertices = vertices.filter(function (vertex) {
- return removeVertexIds.indexOf(vertex.id) === -1;
- }).map(function (vertex) {
- return vgraph.entity(vertex.id);
- });
- ways = ways.filter(function (way) {
- return removeWayIds.indexOf(way.id) === -1;
- }).map(function (way) {
- return vgraph.entity(way.id);
- }); // OK! Here is our intersection..
+ return actionConnect(nodeIDs)(graph);
+ };
- var intersection = {
- graph: vgraph,
- actions: actions,
- vertices: vertices,
- ways: ways
- }; // Get all the valid turns through this intersection given a starting way id.
- // This operates on the virtual graph for everything.
- //
- // Basically, walk through all possible paths from starting way,
- // honoring the existing turn restrictions as we go (watch out for loops!)
- //
- // For each path found, generate and return a `osmTurn` datastructure.
- //
+ action.disabled = function (graph) {
+ if (nodeIDs.length < 2) return 'not_eligible';
- intersection.turns = function (fromWayId, maxViaWay) {
- if (!fromWayId) return [];
- if (!maxViaWay) maxViaWay = 0;
- var vgraph = intersection.graph;
- var keyVertexIds = intersection.vertices.map(function (v) {
- return v.id;
- });
- var start = vgraph.entity(fromWayId);
- if (!start || !(start.__from || start.__via)) return []; // maxViaWay=0 from-*-to (0 vias)
- // maxViaWay=1 from-*-via-*-to (1 via max)
- // maxViaWay=2 from-*-via-*-via-*-to (2 vias max)
+ for (var i = 0; i < nodeIDs.length; i++) {
+ var entity = graph.entity(nodeIDs[i]);
+ if (entity.type !== 'node') return 'not_eligible';
+ }
- var maxPathLength = maxViaWay * 2 + 3;
- var turns = [];
- step(start);
- return turns; // traverse the intersection graph and find all the valid paths
+ return actionConnect(nodeIDs).disabled(graph);
+ };
- function step(entity, currPath, currRestrictions, matchedRestriction) {
- currPath = (currPath || []).slice(); // shallow copy
+ return action;
+ }
- if (currPath.length >= maxPathLength) return;
- currPath.push(entity.id);
- currRestrictions = (currRestrictions || []).slice(); // shallow copy
+ function osmChangeset() {
+ if (!(this instanceof osmChangeset)) {
+ return new osmChangeset().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
+ osmEntity.changeset = osmChangeset;
+ osmChangeset.prototype = Object.create(osmEntity.prototype);
+ Object.assign(osmChangeset.prototype, {
+ type: 'changeset',
+ extent: function extent() {
+ return new geoExtent();
+ },
+ geometry: function geometry() {
+ return 'changeset';
+ },
+ asJXON: function asJXON() {
+ return {
+ osm: {
+ changeset: {
+ tag: Object.keys(this.tags).map(function (k) {
+ return {
+ '@k': k,
+ '@v': this.tags[k]
+ };
+ }, this),
+ '@version': 0.6,
+ '@generator': 'iD'
+ }
+ }
+ };
+ },
+ // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
+ // XML. Returns a string.
+ osmChangeJXON: function osmChangeJXON(changes) {
+ var changeset_id = this.id;
- var i, j;
+ function nest(x, order) {
+ var groups = {};
- if (entity.type === 'node') {
- var parents = vgraph.parentWays(entity);
- var nextWays = []; // which ways can we step into?
+ for (var i = 0; i < x.length; i++) {
+ var tagName = Object.keys(x[i])[0];
+ if (!groups[tagName]) groups[tagName] = [];
+ groups[tagName].push(x[i][tagName]);
+ }
- for (i = 0; i < parents.length; i++) {
- var way = parents[i]; // if next way is a oneway incoming to this vertex, skip
+ var ordered = {};
+ order.forEach(function (o) {
+ if (groups[o]) ordered[o] = groups[o];
+ });
+ return ordered;
+ } // sort relations in a changeset by dependencies
- if (way.__oneWay && way.nodes[0] !== entity.id) continue; // if we have seen it before (allowing for an initial u-turn), skip
- if (currPath.indexOf(way.id) !== -1 && currPath.length >= 3) continue; // Check all "current" restrictions (where we've already walked the `FROM`)
+ function sort(changes) {
+ // find a referenced relation in the current changeset
+ function resolve(item) {
+ return relations.find(function (relation) {
+ return item.keyAttributes.type === 'relation' && item.keyAttributes.ref === relation['@id'];
+ });
+ } // a new item is an item that has not been already processed
- var restrict = null;
- for (j = 0; j < currRestrictions.length; j++) {
- var restriction = currRestrictions[j];
- var f = restriction.memberByRole('from');
- var v = restriction.membersByRole('via');
- var t = restriction.memberByRole('to');
- var isOnly = /^only_/.test(restriction.tags.restriction); // Does the current path match this turn restriction?
+ function isNew(item) {
+ return !sorted[item['@id']] && !processing.find(function (proc) {
+ return proc['@id'] === item['@id'];
+ });
+ }
- var matchesFrom = f.id === fromWayId;
- var matchesViaTo = false;
- var isAlongOnlyPath = false;
+ var processing = [];
+ var sorted = {};
+ var relations = changes.relation;
+ if (!relations) return changes;
- if (t.id === way.id) {
- // match TO
- if (v.length === 1 && v[0].type === 'node') {
- // match VIA node
- matchesViaTo = v[0].id === entity.id && (matchesFrom && currPath.length === 2 || !matchesFrom && currPath.length > 2);
- } else {
- // match all VIA ways
- var pathVias = [];
+ for (var i = 0; i < relations.length; i++) {
+ var relation = relations[i]; // skip relation if already sorted
- for (k = 2; k < currPath.length; k += 2) {
- // k = 2 skips FROM
- pathVias.push(currPath[k]); // (path goes way-node-way...)
- }
+ if (!sorted[relation['@id']]) {
+ processing.push(relation);
+ }
- var restrictionVias = [];
+ while (processing.length > 0) {
+ var next = processing[0],
+ deps = next.member.map(resolve).filter(Boolean).filter(isNew);
- for (k = 0; k < v.length; k++) {
- if (v[k].type === 'way') {
- restrictionVias.push(v[k].id);
- }
- }
+ if (deps.length === 0) {
+ sorted[next['@id']] = next;
+ processing.shift();
+ } else {
+ processing = deps.concat(processing);
+ }
+ }
+ }
- var diff = utilArrayDifference(pathVias, restrictionVias);
- matchesViaTo = !diff.length;
- }
- } else if (isOnly) {
- for (k = 0; k < v.length; k++) {
- // way doesn't match TO, but is one of the via ways along the path of an "only"
- if (v[k].type === 'way' && v[k].id === way.id) {
- isAlongOnlyPath = true;
- break;
- }
- }
- }
+ changes.relation = Object.values(sorted);
+ return changes;
+ }
- if (matchesViaTo) {
- if (isOnly) {
- restrict = {
- id: restriction.id,
- direct: matchesFrom,
- from: f.id,
- only: true,
- end: true
- };
- } else {
- restrict = {
- id: restriction.id,
- direct: matchesFrom,
- from: f.id,
- no: true,
- end: true
- };
- }
- } else {
- // indirect - caused by a different nearby restriction
- if (isAlongOnlyPath) {
- restrict = {
- id: restriction.id,
- direct: false,
- from: f.id,
- only: true,
- end: false
- };
- } else if (isOnly) {
- restrict = {
- id: restriction.id,
- direct: false,
- from: f.id,
- no: true,
- end: true
- };
- }
- } // stop looking if we find a "direct" restriction (matching FROM, VIA, TO)
+ function rep(entity) {
+ return entity.asJXON(changeset_id);
+ }
+ return {
+ osmChange: {
+ '@version': 0.6,
+ '@generator': 'iD',
+ 'create': sort(nest(changes.created.map(rep), ['node', 'way', 'relation'])),
+ 'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
+ 'delete': Object.assign(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {
+ '@if-unused': true
+ })
+ }
+ };
+ },
+ asGeoJSON: function asGeoJSON() {
+ return {};
+ }
+ });
- if (restrict && restrict.direct) break;
- }
+ function osmNote() {
+ if (!(this instanceof osmNote)) {
+ return new osmNote().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
- nextWays.push({
- way: way,
- restrict: restrict
- });
- }
+ osmNote.id = function () {
+ return osmNote.id.next--;
+ };
- nextWays.forEach(function (nextWay) {
- step(nextWay.way, currPath, currRestrictions, nextWay.restrict);
- });
- } else {
- // entity.type === 'way'
- if (currPath.length >= 3) {
- // this is a "complete" path..
- var turnPath = currPath.slice(); // shallow copy
- // an indirect restriction - only include the partial path (starting at FROM)
+ osmNote.id.next = -1;
+ Object.assign(osmNote.prototype, {
+ type: 'note',
+ initialize: function initialize(sources) {
+ for (var i = 0; i < sources.length; ++i) {
+ var source = sources[i];
- if (matchedRestriction && matchedRestriction.direct === false) {
- for (i = 0; i < turnPath.length; i++) {
- if (turnPath[i] === matchedRestriction.from) {
- turnPath = turnPath.slice(i);
- break;
- }
- }
+ for (var prop in source) {
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
+ if (source[prop] === undefined) {
+ delete this[prop];
+ } else {
+ this[prop] = source[prop];
}
+ }
+ }
+ }
- var turn = pathToTurn(turnPath);
-
- if (turn) {
- if (matchedRestriction) {
- turn.restrictionID = matchedRestriction.id;
- turn.no = matchedRestriction.no;
- turn.only = matchedRestriction.only;
- turn.direct = matchedRestriction.direct;
- }
-
- turns.push(osmTurn(turn));
- }
+ if (!this.id) {
+ this.id = osmNote.id().toString();
+ }
- if (currPath[0] === currPath[2]) return; // if we made a u-turn - stop here
- }
+ return this;
+ },
+ extent: function extent() {
+ return new geoExtent(this.loc);
+ },
+ update: function update(attrs) {
+ return osmNote(this, attrs); // {v: 1 + (this.v || 0)}
+ },
+ isNew: function isNew() {
+ return this.id < 0;
+ },
+ move: function move(loc) {
+ return this.update({
+ loc: loc
+ });
+ }
+ });
- if (matchedRestriction && matchedRestriction.end) return; // don't advance any further
- // which nodes can we step into?
+ function osmRelation() {
+ if (!(this instanceof osmRelation)) {
+ return new osmRelation().initialize(arguments);
+ } else if (arguments.length) {
+ this.initialize(arguments);
+ }
+ }
+ osmEntity.relation = osmRelation;
+ osmRelation.prototype = Object.create(osmEntity.prototype);
- var n1 = vgraph.entity(entity.first());
- var n2 = vgraph.entity(entity.last());
- var dist = geoSphericalDistance(n1.loc, n2.loc);
- var nextNodes = [];
+ osmRelation.creationOrder = function (a, b) {
+ var aId = parseInt(osmEntity.id.toOSM(a.id), 10);
+ var bId = parseInt(osmEntity.id.toOSM(b.id), 10);
+ if (aId < 0 || bId < 0) return aId - bId;
+ return bId - aId;
+ };
- if (currPath.length > 1) {
- if (dist > maxDistance) return; // the next node is too far
+ Object.assign(osmRelation.prototype, {
+ type: 'relation',
+ members: [],
+ copy: function copy(resolver, copies) {
+ if (copies[this.id]) return copies[this.id];
+ var copy = osmEntity.prototype.copy.call(this, resolver, copies);
+ var members = this.members.map(function (member) {
+ return Object.assign({}, member, {
+ id: resolver.entity(member.id).copy(resolver, copies).id
+ });
+ });
+ copy = copy.update({
+ members: members
+ });
+ copies[this.id] = copy;
+ return copy;
+ },
+ extent: function extent(resolver, memo) {
+ return resolver["transient"](this, 'extent', function () {
+ if (memo && memo[this.id]) return geoExtent();
+ memo = memo || {};
+ memo[this.id] = true;
+ var extent = geoExtent();
- if (!entity.__via) return; // this way is a leaf / can't be a via
- }
+ for (var i = 0; i < this.members.length; i++) {
+ var member = resolver.hasEntity(this.members[i].id);
- if (!entity.__oneWay && // bidirectional..
- keyVertexIds.indexOf(n1.id) !== -1 && // key vertex..
- currPath.indexOf(n1.id) === -1) {
- // haven't seen it yet..
- nextNodes.push(n1); // can advance to first node
+ if (member) {
+ extent._extend(member.extent(resolver, memo));
}
+ }
- if (keyVertexIds.indexOf(n2.id) !== -1 && // key vertex..
- currPath.indexOf(n2.id) === -1) {
- // haven't seen it yet..
- nextNodes.push(n2); // can advance to last node
- }
+ return extent;
+ });
+ },
+ geometry: function geometry(graph) {
+ return graph["transient"](this, 'geometry', function () {
+ return this.isMultipolygon() ? 'area' : 'relation';
+ });
+ },
+ isDegenerate: function isDegenerate() {
+ return this.members.length === 0;
+ },
+ // Return an array of members, each extended with an 'index' property whose value
+ // is the member index.
+ indexedMembers: function indexedMembers() {
+ var result = new Array(this.members.length);
- nextNodes.forEach(function (nextNode) {
- // gather restrictions FROM this way
- var fromRestrictions = vgraph.parentRelations(entity).filter(function (r) {
- if (!r.isRestriction()) return false;
- var f = r.memberByRole('from');
- if (!f || f.id !== entity.id) return false;
- var isOnly = /^only_/.test(r.tags.restriction);
- if (!isOnly) return true; // `only_` restrictions only matter along the direction of the VIA - #4849
+ for (var i = 0; i < this.members.length; i++) {
+ result[i] = Object.assign({}, this.members[i], {
+ index: i
+ });
+ }
- var isOnlyVia = false;
- var v = r.membersByRole('via');
+ return result;
+ },
+ // Return the first member with the given role. A copy of the member object
+ // is returned, extended with an 'index' property whose value is the member index.
+ memberByRole: function memberByRole(role) {
+ for (var i = 0; i < this.members.length; i++) {
+ if (this.members[i].role === role) {
+ return Object.assign({}, this.members[i], {
+ index: i
+ });
+ }
+ }
+ },
+ // Same as memberByRole, but returns all members with the given role
+ membersByRole: function membersByRole(role) {
+ var result = [];
- if (v.length === 1 && v[0].type === 'node') {
- // via node
- isOnlyVia = v[0].id === nextNode.id;
- } else {
- // via way(s)
- for (var i = 0; i < v.length; i++) {
- if (v[i].type !== 'way') continue;
- var viaWay = vgraph.entity(v[i].id);
+ for (var i = 0; i < this.members.length; i++) {
+ if (this.members[i].role === role) {
+ result.push(Object.assign({}, this.members[i], {
+ index: i
+ }));
+ }
+ }
- if (viaWay.first() === nextNode.id || viaWay.last() === nextNode.id) {
- isOnlyVia = true;
- break;
- }
- }
- }
+ return result;
+ },
+ // Return the first member with the given id. A copy of the member object
+ // is returned, extended with an 'index' property whose value is the member index.
+ memberById: function memberById(id) {
+ for (var i = 0; i < this.members.length; i++) {
+ if (this.members[i].id === id) {
+ return Object.assign({}, this.members[i], {
+ index: i
+ });
+ }
+ }
+ },
+ // Return the first member with the given id and role. A copy of the member object
+ // is returned, extended with an 'index' property whose value is the member index.
+ memberByIdAndRole: function memberByIdAndRole(id, role) {
+ for (var i = 0; i < this.members.length; i++) {
+ if (this.members[i].id === id && this.members[i].role === role) {
+ return Object.assign({}, this.members[i], {
+ index: i
+ });
+ }
+ }
+ },
+ addMember: function addMember(member, index) {
+ var members = this.members.slice();
+ members.splice(index === undefined ? members.length : index, 0, member);
+ return this.update({
+ members: members
+ });
+ },
+ updateMember: function updateMember(member, index) {
+ var members = this.members.slice();
+ members.splice(index, 1, Object.assign({}, members[index], member));
+ return this.update({
+ members: members
+ });
+ },
+ removeMember: function removeMember(index) {
+ var members = this.members.slice();
+ members.splice(index, 1);
+ return this.update({
+ members: members
+ });
+ },
+ removeMembersWithID: function removeMembersWithID(id) {
+ var members = this.members.filter(function (m) {
+ return m.id !== id;
+ });
+ return this.update({
+ members: members
+ });
+ },
+ moveMember: function moveMember(fromIndex, toIndex) {
+ var members = this.members.slice();
+ members.splice(toIndex, 0, members.splice(fromIndex, 1)[0]);
+ return this.update({
+ members: members
+ });
+ },
+ // Wherever a member appears with id `needle.id`, replace it with a member
+ // with id `replacement.id`, type `replacement.type`, and the original role,
+ // By default, adding a duplicate member (by id and role) is prevented.
+ // Return an updated relation.
+ replaceMember: function replaceMember(needle, replacement, keepDuplicates) {
+ if (!this.memberById(needle.id)) return this;
+ var members = [];
- return isOnlyVia;
- });
- step(nextNode, currPath, currRestrictions.concat(fromRestrictions), false);
+ for (var i = 0; i < this.members.length; i++) {
+ var member = this.members[i];
+
+ if (member.id !== needle.id) {
+ members.push(member);
+ } else if (keepDuplicates || !this.memberByIdAndRole(replacement.id, member.role)) {
+ members.push({
+ id: replacement.id,
+ type: replacement.type,
+ role: member.role
});
}
- } // assumes path is alternating way-node-way of odd length
+ }
+ return this.update({
+ members: members
+ });
+ },
+ asJXON: function asJXON(changeset_id) {
+ var r = {
+ relation: {
+ '@id': this.osmId(),
+ '@version': this.version || 0,
+ member: this.members.map(function (member) {
+ return {
+ keyAttributes: {
+ type: member.type,
+ role: member.role,
+ ref: osmEntity.id.toOSM(member.id)
+ }
+ };
+ }, this),
+ tag: Object.keys(this.tags).map(function (k) {
+ return {
+ keyAttributes: {
+ k: k,
+ v: this.tags[k]
+ }
+ };
+ }, this)
+ }
+ };
- function pathToTurn(path) {
- if (path.length < 3) return;
- var fromWayId, fromNodeId, fromVertexId;
- var toWayId, toNodeId, toVertexId;
- var viaWayIds, viaNodeId, isUturn;
- fromWayId = path[0];
- toWayId = path[path.length - 1];
+ if (changeset_id) {
+ r.relation['@changeset'] = changeset_id;
+ }
- if (path.length === 3 && fromWayId === toWayId) {
- // u turn
- var way = vgraph.entity(fromWayId);
- if (way.__oneWay) return null;
- isUturn = true;
- viaNodeId = fromVertexId = toVertexId = path[1];
- fromNodeId = toNodeId = adjacentNode(fromWayId, viaNodeId);
+ return r;
+ },
+ asGeoJSON: function asGeoJSON(resolver) {
+ return resolver["transient"](this, 'GeoJSON', function () {
+ if (this.isMultipolygon()) {
+ return {
+ type: 'MultiPolygon',
+ coordinates: this.multipolygon(resolver)
+ };
} else {
- isUturn = false;
- fromVertexId = path[1];
- fromNodeId = adjacentNode(fromWayId, fromVertexId);
- toVertexId = path[path.length - 2];
- toNodeId = adjacentNode(toWayId, toVertexId);
-
- if (path.length === 3) {
- viaNodeId = path[1];
- } else {
- viaWayIds = path.filter(function (entityId) {
- return entityId[0] === 'w';
- });
- viaWayIds = viaWayIds.slice(1, viaWayIds.length - 1); // remove first, last
- }
+ return {
+ type: 'FeatureCollection',
+ properties: this.tags,
+ features: this.members.map(function (member) {
+ return Object.assign({
+ role: member.role
+ }, resolver.entity(member.id).asGeoJSON(resolver));
+ })
+ };
+ }
+ });
+ },
+ area: function area(resolver) {
+ return resolver["transient"](this, 'area', function () {
+ return d3_geoArea(this.asGeoJSON(resolver));
+ });
+ },
+ isMultipolygon: function isMultipolygon() {
+ return this.tags.type === 'multipolygon';
+ },
+ isComplete: function isComplete(resolver) {
+ for (var i = 0; i < this.members.length; i++) {
+ if (!resolver.hasEntity(this.members[i].id)) {
+ return false;
}
+ }
- return {
- key: path.join('_'),
- path: path,
- from: {
- node: fromNodeId,
- way: fromWayId,
- vertex: fromVertexId
- },
- via: {
- node: viaNodeId,
- ways: viaWayIds
- },
- to: {
- node: toNodeId,
- way: toWayId,
- vertex: toVertexId
- },
- u: isUturn
- };
+ return true;
+ },
+ hasFromViaTo: function hasFromViaTo() {
+ return this.members.some(function (m) {
+ return m.role === 'from';
+ }) && this.members.some(function (m) {
+ return m.role === 'via';
+ }) && this.members.some(function (m) {
+ return m.role === 'to';
+ });
+ },
+ isRestriction: function isRestriction() {
+ return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
+ },
+ isValidRestriction: function isValidRestriction() {
+ if (!this.isRestriction()) return false;
+ var froms = this.members.filter(function (m) {
+ return m.role === 'from';
+ });
+ var vias = this.members.filter(function (m) {
+ return m.role === 'via';
+ });
+ var tos = this.members.filter(function (m) {
+ return m.role === 'to';
+ });
+ if (froms.length !== 1 && this.tags.restriction !== 'no_entry') return false;
+ if (froms.some(function (m) {
+ return m.type !== 'way';
+ })) return false;
+ if (tos.length !== 1 && this.tags.restriction !== 'no_exit') return false;
+ if (tos.some(function (m) {
+ return m.type !== 'way';
+ })) return false;
+ if (vias.length === 0) return false;
+ if (vias.length > 1 && vias.some(function (m) {
+ return m.type !== 'way';
+ })) return false;
+ return true;
+ },
+ isConnectivity: function isConnectivity() {
+ return !!(this.tags.type && this.tags.type.match(/^connectivity:?/));
+ },
+ // Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm],
+ // where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings.
+ //
+ // This corresponds to the structure needed for rendering a multipolygon path using a
+ // `evenodd` fill rule, as well as the structure of a GeoJSON MultiPolygon geometry.
+ //
+ // In the case of invalid geometries, this function will still return a result which
+ // includes the nodes of all way members, but some Nds may be unclosed and some inner
+ // rings not matched with the intended outer ring.
+ //
+ multipolygon: function multipolygon(resolver) {
+ var outers = this.members.filter(function (m) {
+ return 'outer' === (m.role || 'outer');
+ });
+ var inners = this.members.filter(function (m) {
+ return 'inner' === m.role;
+ });
+ outers = osmJoinWays(outers, resolver);
+ inners = osmJoinWays(inners, resolver);
- function adjacentNode(wayId, affixId) {
- var nodes = vgraph.entity(wayId).nodes;
- return affixId === nodes[0] ? nodes[1] : nodes[nodes.length - 2];
+ var sequenceToLineString = function sequenceToLineString(sequence) {
+ if (sequence.nodes.length > 2 && sequence.nodes[0] !== sequence.nodes[sequence.nodes.length - 1]) {
+ // close unclosed parts to ensure correct area rendering - #2945
+ sequence.nodes.push(sequence.nodes[0]);
}
- }
- };
- return intersection;
- }
- function osmInferRestriction(graph, turn, projection) {
- var fromWay = graph.entity(turn.from.way);
- var fromNode = graph.entity(turn.from.node);
- var fromVertex = graph.entity(turn.from.vertex);
- var toWay = graph.entity(turn.to.way);
- var toNode = graph.entity(turn.to.node);
- var toVertex = graph.entity(turn.to.vertex);
- var fromOneWay = fromWay.tags.oneway === 'yes';
- var toOneWay = toWay.tags.oneway === 'yes';
- var angle = (geoAngle(fromVertex, fromNode, projection) - geoAngle(toVertex, toNode, projection)) * 180 / Math.PI;
+ return sequence.nodes.map(function (node) {
+ return node.loc;
+ });
+ };
- while (angle < 0) {
- angle += 360;
- }
+ outers = outers.map(sequenceToLineString);
+ inners = inners.map(sequenceToLineString);
+ var result = outers.map(function (o) {
+ // Heuristic for detecting counterclockwise winding order. Assumes
+ // that OpenStreetMap polygons are not hemisphere-spanning.
+ return [d3_geoArea({
+ type: 'Polygon',
+ coordinates: [o]
+ }) > 2 * Math.PI ? o.reverse() : o];
+ });
- if (fromNode === toNode) {
- return 'no_u_turn';
- }
+ function findOuter(inner) {
+ var o, outer;
- if ((angle < 23 || angle > 336) && fromOneWay && toOneWay) {
- return 'no_u_turn'; // wider tolerance for u-turn if both ways are oneway
- }
+ for (o = 0; o < outers.length; o++) {
+ outer = outers[o];
- if ((angle < 40 || angle > 319) && fromOneWay && toOneWay && turn.from.vertex !== turn.to.vertex) {
- return 'no_u_turn'; // even wider tolerance for u-turn if there is a via way (from !== to)
- }
+ if (geoPolygonContainsPolygon(outer, inner)) {
+ return o;
+ }
+ }
- if (angle < 158) {
- return 'no_right_turn';
- }
+ for (o = 0; o < outers.length; o++) {
+ outer = outers[o];
- if (angle > 202) {
- return 'no_left_turn';
- }
+ if (geoPolygonIntersectsPolygon(outer, inner, false)) {
+ return o;
+ }
+ }
+ }
- return 'no_straight_on';
- }
+ for (var i = 0; i < inners.length; i++) {
+ var inner = inners[i];
- function actionMergePolygon(ids, newRelationId) {
- function groupEntities(graph) {
- var entities = ids.map(function (id) {
- return graph.entity(id);
- });
- var geometryGroups = utilArrayGroupBy(entities, function (entity) {
- if (entity.type === 'way' && entity.isClosed()) {
- return 'closedWay';
- } else if (entity.type === 'relation' && entity.isMultipolygon()) {
- return 'multipolygon';
+ if (d3_geoArea({
+ type: 'Polygon',
+ coordinates: [inner]
+ }) < 2 * Math.PI) {
+ inner = inner.reverse();
+ }
+
+ var o = findOuter(inners[i]);
+
+ if (o !== undefined) {
+ result[o].push(inners[i]);
} else {
- return 'other';
+ result.push([inners[i]]); // Invalid geometry
}
- });
- return Object.assign({
- closedWay: [],
- multipolygon: [],
- other: []
- }, geometryGroups);
- }
+ }
- var action = function action(graph) {
- var entities = groupEntities(graph); // An array representing all the polygons that are part of the multipolygon.
- //
- // Each element is itself an array of objects with an id property, and has a
- // locs property which is an array of the locations forming the polygon.
+ return result;
+ }
+ });
- var polygons = entities.multipolygon.reduce(function (polygons, m) {
- return polygons.concat(osmJoinWays(m.members, graph));
- }, []).concat(entities.closedWay.map(function (d) {
- var member = [{
- id: d.id
- }];
- member.nodes = graph.childNodes(d);
- return member;
- })); // contained is an array of arrays of boolean values,
- // where contained[j][k] is true iff the jth way is
- // contained by the kth way.
+ var QAItem = /*#__PURE__*/function () {
+ function QAItem(loc, service, itemType, id, props) {
+ _classCallCheck$1(this, QAItem);
- var contained = polygons.map(function (w, i) {
- return polygons.map(function (d, n) {
- if (i === n) return null;
- return geoPolygonContainsPolygon(d.nodes.map(function (n) {
- return n.loc;
- }), w.nodes.map(function (n) {
- return n.loc;
- }));
- });
- }); // Sort all polygons as either outer or inner ways
+ // Store required properties
+ this.loc = loc;
+ this.service = service.title;
+ this.itemType = itemType; // All issues must have an ID for selection, use generic if none specified
- var members = [];
- var outer = true;
+ this.id = id ? id : "".concat(QAItem.id());
+ this.update(props); // Some QA services have marker icons to differentiate issues
- while (polygons.length) {
- extractUncontained(polygons);
- polygons = polygons.filter(isContained);
- contained = contained.filter(isContained).map(filterContained);
+ if (service && typeof service.getIcon === 'function') {
+ this.icon = service.getIcon(itemType);
}
+ }
- function isContained(d, i) {
- return contained[i].some(function (val) {
- return val;
+ _createClass$1(QAItem, [{
+ key: "update",
+ value: function update(props) {
+ var _this = this;
+
+ // You can't override this initial information
+ var loc = this.loc,
+ service = this.service,
+ itemType = this.itemType,
+ id = this.id;
+ Object.keys(props).forEach(function (prop) {
+ return _this[prop] = props[prop];
});
- }
+ this.loc = loc;
+ this.service = service;
+ this.itemType = itemType;
+ this.id = id;
+ return this;
+ } // Generic handling for newly created QAItems
- function filterContained(d) {
- return d.filter(isContained);
+ }], [{
+ key: "id",
+ value: function id() {
+ return this.nextId--;
}
+ }]);
- function extractUncontained(polygons) {
- polygons.forEach(function (d, i) {
- if (!isContained(d, i)) {
- d.forEach(function (member) {
- members.push({
- type: 'way',
- id: member.id,
- role: outer ? 'outer' : 'inner'
- });
- });
- }
- });
- outer = !outer;
- } // Move all tags to one relation
+ return QAItem;
+ }();
+ QAItem.nextId = -1;
+ //
+ // Optionally, split only the given ways, if multiple ways share
+ // the given node.
+ //
+ // This is the inverse of `iD.actionJoin`.
+ //
+ // For testing convenience, accepts an ID to assign to the new way.
+ // Normally, this will be undefined and the way will automatically
+ // be assigned a new ID.
+ //
+ // Reference:
+ // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as
+ //
- var relation = entities.multipolygon[0] || osmRelation({
- id: newRelationId,
- tags: {
- type: 'multipolygon'
- }
- });
- entities.multipolygon.slice(1).forEach(function (m) {
- relation = relation.mergeTags(m.tags);
- graph = graph.remove(m);
- });
- entities.closedWay.forEach(function (way) {
- function isThisOuter(m) {
- return m.id === way.id && m.role !== 'inner';
- }
+ function actionSplit(nodeIds, newWayIds) {
+ // accept single ID for backwards-compatiblity
+ if (typeof nodeIds === 'string') nodeIds = [nodeIds];
- if (members.some(isThisOuter)) {
- relation = relation.mergeTags(way.tags);
- graph = graph.replace(way.update({
- tags: {}
- }));
- }
- });
- return graph.replace(relation.update({
- members: members,
- tags: utilObjectOmit(relation.tags, ['area'])
- }));
- };
+ var _wayIDs; // the strategy for picking which way will have a new version and which way is newly created
- action.disabled = function (graph) {
- var entities = groupEntities(graph);
- if (entities.other.length > 0 || entities.closedWay.length + entities.multipolygon.length < 2) {
- return 'not_eligible';
- }
+ var _keepHistoryOn = 'longest'; // 'longest', 'first'
+ // The IDs of the ways actually created by running this action
- if (!entities.multipolygon.every(function (r) {
- return r.isComplete(graph);
- })) {
- return 'incomplete_relation';
- }
+ var _createdWayIDs = [];
- if (!entities.multipolygon.length) {
- var sharedMultipolygons = [];
- entities.closedWay.forEach(function (way, i) {
- if (i === 0) {
- sharedMultipolygons = graph.parentMultipolygons(way);
- } else {
- sharedMultipolygons = utilArrayIntersection(sharedMultipolygons, graph.parentMultipolygons(way));
- }
- });
- sharedMultipolygons = sharedMultipolygons.filter(function (relation) {
- return relation.members.length === entities.closedWay.length;
- });
+ function dist(graph, nA, nB) {
+ var locA = graph.entity(nA).loc;
+ var locB = graph.entity(nB).loc;
+ var epsilon = 1e-6;
+ return locA && locB ? geoSphericalDistance(locA, locB) : epsilon;
+ } // If the way is closed, we need to search for a partner node
+ // to split the way at.
+ //
+ // The following looks for a node that is both far away from
+ // the initial node in terms of way segment length and nearby
+ // in terms of beeline-distance. This assures that areas get
+ // split on the most "natural" points (independent of the number
+ // of nodes).
+ // For example: bone-shaped areas get split across their waist
+ // line, circles across the diameter.
- if (sharedMultipolygons.length) {
- // don't create a new multipolygon if it'd be redundant
- return 'not_eligible';
- }
- } else if (entities.closedWay.some(function (way) {
- return utilArrayIntersection(graph.parentMultipolygons(way), entities.multipolygon).length;
- })) {
- // don't add a way to a multipolygon again if it's already a member
- return 'not_eligible';
- }
- };
- return action;
- }
+ function splitArea(nodes, idxA, graph) {
+ var lengths = new Array(nodes.length);
+ var length;
+ var i;
+ var best = 0;
+ var idxB;
- var FORCED$2 = descriptors && fails(function () {
- // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
- return Object.getOwnPropertyDescriptor(RegExp.prototype, 'flags').get.call({ dotAll: true, sticky: true }) !== 'sy';
- });
+ function wrap(index) {
+ return utilWrap(index, nodes.length);
+ } // calculate lengths
- // `RegExp.prototype.flags` getter
- // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
- if (FORCED$2) objectDefineProperty.f(RegExp.prototype, 'flags', {
- configurable: true,
- get: regexpFlags
- });
- var fastDeepEqual = function equal(a, b) {
- if (a === b) return true;
+ length = 0;
- if (a && b && _typeof(a) == 'object' && _typeof(b) == 'object') {
- if (a.constructor !== b.constructor) return false;
- var length, i, keys;
+ for (i = wrap(idxA + 1); i !== idxA; i = wrap(i + 1)) {
+ length += dist(graph, nodes[i], nodes[wrap(i - 1)]);
+ lengths[i] = length;
+ }
- if (Array.isArray(a)) {
- length = a.length;
- if (length != b.length) return false;
+ length = 0;
- for (i = length; i-- !== 0;) {
- if (!equal(a[i], b[i])) return false;
+ for (i = wrap(idxA - 1); i !== idxA; i = wrap(i - 1)) {
+ length += dist(graph, nodes[i], nodes[wrap(i + 1)]);
+
+ if (length < lengths[i]) {
+ lengths[i] = length;
}
+ } // determine best opposite node to split
- return true;
- }
- if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
- if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
- if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
- keys = Object.keys(a);
- length = keys.length;
- if (length !== Object.keys(b).length) return false;
+ for (i = 0; i < nodes.length; i++) {
+ var cost = lengths[i] / dist(graph, nodes[idxA], nodes[i]);
- for (i = length; i-- !== 0;) {
- if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
+ if (cost > best) {
+ idxB = i;
+ best = cost;
+ }
}
- for (i = length; i-- !== 0;) {
- var key = keys[i];
- if (!equal(a[key], b[key])) return false;
- }
+ return idxB;
+ }
- return true;
- } // true if both NaN, false otherwise
+ function totalLengthBetweenNodes(graph, nodes) {
+ var totalLength = 0;
+ for (var i = 0; i < nodes.length - 1; i++) {
+ totalLength += dist(graph, nodes[i], nodes[i + 1]);
+ }
- return a !== a && b !== b;
- };
+ return totalLength;
+ }
- // J. W. Hunt and M. D. McIlroy, An algorithm for differential buffer
- // comparison, Bell Telephone Laboratories CSTR #41 (1976)
- // http://www.cs.dartmouth.edu/~doug/
- // https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
- //
- // Expects two arrays, finds longest common sequence
+ function split(graph, nodeId, wayA, newWayId) {
+ var wayB = osmWay({
+ id: newWayId,
+ tags: wayA.tags
+ }); // `wayB` is the NEW way
- function LCS(buffer1, buffer2) {
- var equivalenceClasses = {};
+ var origNodes = wayA.nodes.slice();
+ var nodesA;
+ var nodesB;
+ var isArea = wayA.isArea();
+ var isOuter = osmIsOldMultipolygonOuterMember(wayA, graph);
- for (var j = 0; j < buffer2.length; j++) {
- var item = buffer2[j];
+ if (wayA.isClosed()) {
+ var nodes = wayA.nodes.slice(0, -1);
+ var idxA = nodes.indexOf(nodeId);
+ var idxB = splitArea(nodes, idxA, graph);
- if (equivalenceClasses[item]) {
- equivalenceClasses[item].push(j);
+ if (idxB < idxA) {
+ nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1));
+ nodesB = nodes.slice(idxB, idxA + 1);
+ } else {
+ nodesA = nodes.slice(idxA, idxB + 1);
+ nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1));
+ }
} else {
- equivalenceClasses[item] = [j];
+ var idx = wayA.nodes.indexOf(nodeId, 1);
+ nodesA = wayA.nodes.slice(0, idx + 1);
+ nodesB = wayA.nodes.slice(idx);
}
- }
- var NULLRESULT = {
- buffer1index: -1,
- buffer2index: -1,
- chain: null
- };
- var candidates = [NULLRESULT];
+ var lengthA = totalLengthBetweenNodes(graph, nodesA);
+ var lengthB = totalLengthBetweenNodes(graph, nodesB);
- for (var i = 0; i < buffer1.length; i++) {
- var _item = buffer1[i];
- var buffer2indices = equivalenceClasses[_item] || [];
- var r = 0;
- var c = candidates[0];
+ if (_keepHistoryOn === 'longest' && lengthB > lengthA) {
+ // keep the history on the longer way, regardless of the node count
+ wayA = wayA.update({
+ nodes: nodesB
+ });
+ wayB = wayB.update({
+ nodes: nodesA
+ });
+ var temp = lengthA;
+ lengthA = lengthB;
+ lengthB = temp;
+ } else {
+ wayA = wayA.update({
+ nodes: nodesA
+ });
+ wayB = wayB.update({
+ nodes: nodesB
+ });
+ }
- for (var jx = 0; jx < buffer2indices.length; jx++) {
- var _j = buffer2indices[jx];
- var s = void 0;
+ if (wayA.tags.step_count) {
+ // divide up the the step count proportionally between the two ways
+ var stepCount = parseFloat(wayA.tags.step_count);
- for (s = r; s < candidates.length; s++) {
- if (candidates[s].buffer2index < _j && (s === candidates.length - 1 || candidates[s + 1].buffer2index > _j)) {
- break;
- }
+ if (stepCount && // ensure a number
+ isFinite(stepCount) && // ensure positive
+ stepCount > 0 && // ensure integer
+ Math.round(stepCount) === stepCount) {
+ var tagsA = Object.assign({}, wayA.tags);
+ var tagsB = Object.assign({}, wayB.tags);
+ var ratioA = lengthA / (lengthA + lengthB);
+ var countA = Math.round(stepCount * ratioA);
+ tagsA.step_count = countA.toString();
+ tagsB.step_count = (stepCount - countA).toString();
+ wayA = wayA.update({
+ tags: tagsA
+ });
+ wayB = wayB.update({
+ tags: tagsB
+ });
}
+ }
- if (s < candidates.length) {
- var newCandidate = {
- buffer1index: i,
- buffer2index: _j,
- chain: candidates[s]
- };
-
- if (r === candidates.length) {
- candidates.push(c);
- } else {
- candidates[r] = c;
- }
+ graph = graph.replace(wayA);
+ graph = graph.replace(wayB);
+ graph.parentRelations(wayA).forEach(function (relation) {
+ var member; // Turn restrictions - make sure:
+ // 1. Splitting a FROM/TO way - only `wayA` OR `wayB` remains in relation
+ // (whichever one is connected to the VIA node/ways)
+ // 2. Splitting a VIA way - `wayB` remains in relation as a VIA way
- r = s + 1;
- c = newCandidate;
+ if (relation.hasFromViaTo()) {
+ var f = relation.memberByRole('from');
+ var v = relation.membersByRole('via');
+ var t = relation.memberByRole('to');
+ var i; // 1. split a FROM/TO
- if (r === candidates.length) {
- break; // no point in examining further (j)s
- }
- }
- }
+ if (f.id === wayA.id || t.id === wayA.id) {
+ var keepB = false;
- candidates[r] = c;
- } // At this point, we know the LCS: it's in the reverse of the
- // linked-list through .chain of candidates[candidates.length - 1].
+ if (v.length === 1 && v[0].type === 'node') {
+ // check via node
+ keepB = wayB.contains(v[0].id);
+ } else {
+ // check via way(s)
+ for (i = 0; i < v.length; i++) {
+ if (v[i].type === 'way') {
+ var wayVia = graph.hasEntity(v[i].id);
+ if (wayVia && utilArrayIntersection(wayB.nodes, wayVia.nodes).length) {
+ keepB = true;
+ break;
+ }
+ }
+ }
+ }
- return candidates[candidates.length - 1];
- } // We apply the LCS to build a 'comm'-style picture of the
- // offsets and lengths of mismatched chunks in the input
- // buffers. This is used by diff3MergeRegions.
+ if (keepB) {
+ relation = relation.replaceMember(wayA, wayB);
+ graph = graph.replace(relation);
+ } // 2. split a VIA
+ } else {
+ for (i = 0; i < v.length; i++) {
+ if (v[i].type === 'way' && v[i].id === wayA.id) {
+ member = {
+ id: wayB.id,
+ type: 'way',
+ role: 'via'
+ };
+ graph = actionAddMember(relation.id, member, v[i].index + 1)(graph);
+ break;
+ }
+ }
+ } // All other relations (Routes, Multipolygons, etc):
+ // 1. Both `wayA` and `wayB` remain in the relation
+ // 2. But must be inserted as a pair (see `actionAddMember` for details)
- function diffIndices(buffer1, buffer2) {
- var lcs = LCS(buffer1, buffer2);
- var result = [];
- var tail1 = buffer1.length;
- var tail2 = buffer2.length;
+ } else {
+ if (relation === isOuter) {
+ graph = graph.replace(relation.mergeTags(wayA.tags));
+ graph = graph.replace(wayA.update({
+ tags: {}
+ }));
+ graph = graph.replace(wayB.update({
+ tags: {}
+ }));
+ }
- for (var candidate = lcs; candidate !== null; candidate = candidate.chain) {
- var mismatchLength1 = tail1 - candidate.buffer1index - 1;
- var mismatchLength2 = tail2 - candidate.buffer2index - 1;
- tail1 = candidate.buffer1index;
- tail2 = candidate.buffer2index;
+ member = {
+ id: wayB.id,
+ type: 'way',
+ role: relation.memberById(wayA.id).role
+ };
+ var insertPair = {
+ originalID: wayA.id,
+ insertedID: wayB.id,
+ nodes: origNodes
+ };
+ graph = actionAddMember(relation.id, member, undefined, insertPair)(graph);
+ }
+ });
- if (mismatchLength1 || mismatchLength2) {
- result.push({
- buffer1: [tail1 + 1, mismatchLength1],
- buffer1Content: buffer1.slice(tail1 + 1, tail1 + 1 + mismatchLength1),
- buffer2: [tail2 + 1, mismatchLength2],
- buffer2Content: buffer2.slice(tail2 + 1, tail2 + 1 + mismatchLength2)
+ if (!isOuter && isArea) {
+ var multipolygon = osmRelation({
+ tags: Object.assign({}, wayA.tags, {
+ type: 'multipolygon'
+ }),
+ members: [{
+ id: wayA.id,
+ role: 'outer',
+ type: 'way'
+ }, {
+ id: wayB.id,
+ role: 'outer',
+ type: 'way'
+ }]
});
+ graph = graph.replace(multipolygon);
+ graph = graph.replace(wayA.update({
+ tags: {}
+ }));
+ graph = graph.replace(wayB.update({
+ tags: {}
+ }));
}
+
+ _createdWayIDs.push(wayB.id);
+
+ return graph;
}
- result.reverse();
- return result;
- } // We apply the LCS to build a JSON representation of a
- // independently derived from O, returns a fairly complicated
- // internal representation of merge decisions it's taken. The
- // interested reader may wish to consult
- //
- // Sanjeev Khanna, Keshav Kunal, and Benjamin C. Pierce.
- // 'A Formal Investigation of ' In Arvind and Prasad,
- // editors, Foundations of Software Technology and Theoretical
- // Computer Science (FSTTCS), December 2007.
- //
- // (http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf)
- //
+ var action = function action(graph) {
+ _createdWayIDs = [];
+ var newWayIndex = 0;
+ for (var i = 0; i < nodeIds.length; i++) {
+ var nodeId = nodeIds[i];
+ var candidates = action.waysForNode(nodeId, graph);
- function diff3MergeRegions(a, o, b) {
- // "hunks" are array subsets where `a` or `b` are different from `o`
- // https://www.gnu.org/software/diffutils/manual/html_node/diff3-Hunks.html
- var hunks = [];
+ for (var j = 0; j < candidates.length; j++) {
+ graph = split(graph, nodeId, candidates[j], newWayIds && newWayIds[newWayIndex]);
+ newWayIndex += 1;
+ }
+ }
- function addHunk(h, ab) {
- hunks.push({
- ab: ab,
- oStart: h.buffer1[0],
- oLength: h.buffer1[1],
- // length of o to remove
- abStart: h.buffer2[0],
- abLength: h.buffer2[1] // length of a/b to insert
- // abContent: (ab === 'a' ? a : b).slice(h.buffer2[0], h.buffer2[0] + h.buffer2[1])
+ return graph;
+ };
- });
- }
+ action.getCreatedWayIDs = function () {
+ return _createdWayIDs;
+ };
- diffIndices(o, a).forEach(function (item) {
- return addHunk(item, 'a');
- });
- diffIndices(o, b).forEach(function (item) {
- return addHunk(item, 'b');
- });
- hunks.sort(function (x, y) {
- return x.oStart - y.oStart;
- });
- var results = [];
- var currOffset = 0;
+ action.waysForNode = function (nodeId, graph) {
+ var node = graph.entity(nodeId);
+ var splittableParents = graph.parentWays(node).filter(isSplittable);
- function advanceTo(endOffset) {
- if (endOffset > currOffset) {
- results.push({
- stable: true,
- buffer: 'o',
- bufferStart: currOffset,
- bufferLength: endOffset - currOffset,
- bufferContent: o.slice(currOffset, endOffset)
+ if (!_wayIDs) {
+ // If the ways to split aren't specified, only split the lines.
+ // If there are no lines to split, split the areas.
+ var hasLine = splittableParents.some(function (parent) {
+ return parent.geometry(graph) === 'line';
});
- currOffset = endOffset;
+
+ if (hasLine) {
+ return splittableParents.filter(function (parent) {
+ return parent.geometry(graph) === 'line';
+ });
+ }
}
- }
- while (hunks.length) {
- var hunk = hunks.shift();
- var regionStart = hunk.oStart;
- var regionEnd = hunk.oStart + hunk.oLength;
- var regionHunks = [hunk];
- advanceTo(regionStart); // Try to pull next overlapping hunk into this region
+ return splittableParents;
- while (hunks.length) {
- var nextHunk = hunks[0];
- var nextHunkStart = nextHunk.oStart;
- if (nextHunkStart > regionEnd) break; // no overlap
+ function isSplittable(parent) {
+ // If the ways to split are specified, ignore everything else.
+ if (_wayIDs && _wayIDs.indexOf(parent.id) === -1) return false; // We can fake splitting closed ways at their endpoints...
- regionEnd = Math.max(regionEnd, nextHunkStart + nextHunk.oLength);
- regionHunks.push(hunks.shift());
+ if (parent.isClosed()) return true; // otherwise, we can't split nodes at their endpoints.
+
+ for (var i = 1; i < parent.nodes.length - 1; i++) {
+ if (parent.nodes[i] === nodeId) return true;
+ }
+
+ return false;
}
+ };
- if (regionHunks.length === 1) {
- // Only one hunk touches this region, meaning that there is no conflict here.
- // Either `a` or `b` is inserting into a region of `o` unchanged by the other.
- if (hunk.abLength > 0) {
- var buffer = hunk.ab === 'a' ? a : b;
- results.push({
- stable: true,
- buffer: hunk.ab,
- bufferStart: hunk.abStart,
- bufferLength: hunk.abLength,
- bufferContent: buffer.slice(hunk.abStart, hunk.abStart + hunk.abLength)
- });
- }
- } else {
- // A true a/b conflict. Determine the bounds involved from `a`, `o`, and `b`.
- // Effectively merge all the `a` hunks into one giant hunk, then do the
- // same for the `b` hunks; then, correct for skew in the regions of `o`
- // that each side changed, and report appropriate spans for the three sides.
- var bounds = {
- a: [a.length, -1, o.length, -1],
- b: [b.length, -1, o.length, -1]
- };
+ action.ways = function (graph) {
+ return utilArrayUniq([].concat.apply([], nodeIds.map(function (nodeId) {
+ return action.waysForNode(nodeId, graph);
+ })));
+ };
- while (regionHunks.length) {
- hunk = regionHunks.shift();
- var oStart = hunk.oStart;
- var oEnd = oStart + hunk.oLength;
- var abStart = hunk.abStart;
- var abEnd = abStart + hunk.abLength;
- var _b = bounds[hunk.ab];
- _b[0] = Math.min(abStart, _b[0]);
- _b[1] = Math.max(abEnd, _b[1]);
- _b[2] = Math.min(oStart, _b[2]);
- _b[3] = Math.max(oEnd, _b[3]);
- }
+ action.disabled = function (graph) {
+ for (var i = 0; i < nodeIds.length; i++) {
+ var nodeId = nodeIds[i];
+ var candidates = action.waysForNode(nodeId, graph);
- var aStart = bounds.a[0] + (regionStart - bounds.a[2]);
- var aEnd = bounds.a[1] + (regionEnd - bounds.a[3]);
- var bStart = bounds.b[0] + (regionStart - bounds.b[2]);
- var bEnd = bounds.b[1] + (regionEnd - bounds.b[3]);
- var result = {
- stable: false,
- aStart: aStart,
- aLength: aEnd - aStart,
- aContent: a.slice(aStart, aEnd),
- oStart: regionStart,
- oLength: regionEnd - regionStart,
- oContent: o.slice(regionStart, regionEnd),
- bStart: bStart,
- bLength: bEnd - bStart,
- bContent: b.slice(bStart, bEnd)
- };
- results.push(result);
+ if (candidates.length === 0 || _wayIDs && _wayIDs.length !== candidates.length) {
+ return 'not_eligible';
+ }
}
-
- currOffset = regionEnd;
- }
-
- advanceTo(o.length);
- return results;
- } // Applies the output of diff3MergeRegions to actually
- // construct the merged buffer; the returned result alternates
- // between 'ok' and 'conflict' blocks.
- // A "false conflict" is where `a` and `b` both change the same from `o`
-
-
- function diff3Merge(a, o, b, options) {
- var defaults = {
- excludeFalseConflicts: true,
- stringSeparator: /\s+/
};
- options = Object.assign(defaults, options);
- var aString = typeof a === 'string';
- var oString = typeof o === 'string';
- var bString = typeof b === 'string';
- if (aString) a = a.split(options.stringSeparator);
- if (oString) o = o.split(options.stringSeparator);
- if (bString) b = b.split(options.stringSeparator);
- var results = [];
- var regions = diff3MergeRegions(a, o, b);
- var okBuffer = [];
- function flushOk() {
- if (okBuffer.length) {
- results.push({
- ok: okBuffer
- });
- }
+ action.limitWays = function (val) {
+ if (!arguments.length) return _wayIDs;
+ _wayIDs = val;
+ return action;
+ };
- okBuffer = [];
- }
+ action.keepHistoryOn = function (val) {
+ if (!arguments.length) return _keepHistoryOn;
+ _keepHistoryOn = val;
+ return action;
+ };
- function isFalseConflict(a, b) {
- if (a.length !== b.length) return false;
+ return action;
+ }
- for (var i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) return false;
- }
+ function coreGraph(other, mutable) {
+ if (!(this instanceof coreGraph)) return new coreGraph(other, mutable);
- return true;
+ if (other instanceof coreGraph) {
+ var base = other.base();
+ this.entities = Object.assign(Object.create(base.entities), other.entities);
+ this._parentWays = Object.assign(Object.create(base.parentWays), other._parentWays);
+ this._parentRels = Object.assign(Object.create(base.parentRels), other._parentRels);
+ } else {
+ this.entities = Object.create({});
+ this._parentWays = Object.create({});
+ this._parentRels = Object.create({});
+ this.rebase(other || [], [this]);
}
- regions.forEach(function (region) {
- if (region.stable) {
- var _okBuffer;
+ this.transients = {};
+ this._childNodes = {};
+ this.frozen = !mutable;
+ }
+ coreGraph.prototype = {
+ hasEntity: function hasEntity(id) {
+ return this.entities[id];
+ },
+ entity: function entity(id) {
+ var entity = this.entities[id]; //https://github.com/openstreetmap/iD/issues/3973#issuecomment-307052376
- (_okBuffer = okBuffer).push.apply(_okBuffer, _toConsumableArray(region.bufferContent));
- } else {
- if (options.excludeFalseConflicts && isFalseConflict(region.aContent, region.bContent)) {
- var _okBuffer2;
+ if (!entity) {
+ entity = this.entities.__proto__[id]; // eslint-disable-line no-proto
+ }
- (_okBuffer2 = okBuffer).push.apply(_okBuffer2, _toConsumableArray(region.aContent));
- } else {
- flushOk();
- results.push({
- conflict: {
- a: region.aContent,
- aIndex: region.aStart,
- o: region.oContent,
- oIndex: region.oStart,
- b: region.bContent,
- bIndex: region.bStart
- }
- });
- }
+ if (!entity) {
+ throw new Error('entity ' + id + ' not found');
}
- });
- flushOk();
- return results;
- }
- function actionMergeRemoteChanges(id, localGraph, remoteGraph, discardTags, formatUser) {
- discardTags = discardTags || {};
- var _option = 'safe'; // 'safe', 'force_local', 'force_remote'
+ return entity;
+ },
+ geometry: function geometry(id) {
+ return this.entity(id).geometry(this);
+ },
+ "transient": function transient(entity, key, fn) {
+ var id = entity.id;
+ var transients = this.transients[id] || (this.transients[id] = {});
- var _conflicts = [];
+ if (transients[key] !== undefined) {
+ return transients[key];
+ }
- function user(d) {
- return typeof formatUser === 'function' ? formatUser(d) : d;
- }
+ transients[key] = fn.call(entity);
+ return transients[key];
+ },
+ parentWays: function parentWays(entity) {
+ var parents = this._parentWays[entity.id];
+ var result = [];
- function mergeLocation(remote, target) {
- function pointEqual(a, b) {
- var epsilon = 1e-6;
- return Math.abs(a[0] - b[0]) < epsilon && Math.abs(a[1] - b[1]) < epsilon;
+ if (parents) {
+ parents.forEach(function (id) {
+ result.push(this.entity(id));
+ }, this);
}
- if (_option === 'force_local' || pointEqual(target.loc, remote.loc)) {
- return target;
- }
+ return result;
+ },
+ isPoi: function isPoi(entity) {
+ var parents = this._parentWays[entity.id];
+ return !parents || parents.size === 0;
+ },
+ isShared: function isShared(entity) {
+ var parents = this._parentWays[entity.id];
+ return parents && parents.size > 1;
+ },
+ parentRelations: function parentRelations(entity) {
+ var parents = this._parentRels[entity.id];
+ var result = [];
- if (_option === 'force_remote') {
- return target.update({
- loc: remote.loc
- });
+ if (parents) {
+ parents.forEach(function (id) {
+ result.push(this.entity(id));
+ }, this);
}
- _conflicts.push(_t('merge_remote_changes.conflict.location', {
- user: user(remote.user)
- }));
-
- return target;
- }
+ return result;
+ },
+ parentMultipolygons: function parentMultipolygons(entity) {
+ return this.parentRelations(entity).filter(function (relation) {
+ return relation.isMultipolygon();
+ });
+ },
+ childNodes: function childNodes(entity) {
+ if (this._childNodes[entity.id]) return this._childNodes[entity.id];
+ if (!entity.nodes) return [];
+ var nodes = [];
- function mergeNodes(base, remote, target) {
- if (_option === 'force_local' || fastDeepEqual(target.nodes, remote.nodes)) {
- return target;
+ for (var i = 0; i < entity.nodes.length; i++) {
+ nodes[i] = this.entity(entity.nodes[i]);
}
+ this._childNodes[entity.id] = nodes;
+ return this._childNodes[entity.id];
+ },
+ base: function base() {
+ return {
+ 'entities': Object.getPrototypeOf(this.entities),
+ 'parentWays': Object.getPrototypeOf(this._parentWays),
+ 'parentRels': Object.getPrototypeOf(this._parentRels)
+ };
+ },
+ // Unlike other graph methods, rebase mutates in place. This is because it
+ // is used only during the history operation that merges newly downloaded
+ // data into each state. To external consumers, it should appear as if the
+ // graph always contained the newly downloaded data.
+ rebase: function rebase(entities, stack, force) {
+ var base = this.base();
+ var i, j, k, id;
- if (_option === 'force_remote') {
- return target.update({
- nodes: remote.nodes
- });
- }
+ for (i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+ if (!entity.visible || !force && base.entities[entity.id]) continue; // Merging data into the base graph
- var ccount = _conflicts.length;
- var o = base.nodes || [];
- var a = target.nodes || [];
- var b = remote.nodes || [];
- var nodes = [];
- var hunks = diff3Merge(a, o, b, {
- excludeFalseConflicts: true
- });
+ base.entities[entity.id] = entity;
- for (var i = 0; i < hunks.length; i++) {
- var hunk = hunks[i];
+ this._updateCalculated(undefined, entity, base.parentWays, base.parentRels); // Restore provisionally-deleted nodes that are discovered to have an extant parent
- if (hunk.ok) {
- nodes.push.apply(nodes, hunk.ok);
- } else {
- // for all conflicts, we can assume c.a !== c.b
- // because `diff3Merge` called with `true` option to exclude false conflicts..
- var c = hunk.conflict;
- if (fastDeepEqual(c.o, c.a)) {
- // only changed remotely
- nodes.push.apply(nodes, c.b);
- } else if (fastDeepEqual(c.o, c.b)) {
- // only changed locally
- nodes.push.apply(nodes, c.a);
- } else {
- // changed both locally and remotely
- _conflicts.push(_t('merge_remote_changes.conflict.nodelist', {
- user: user(remote.user)
- }));
+ if (entity.type === 'way') {
+ for (j = 0; j < entity.nodes.length; j++) {
+ id = entity.nodes[j];
- break;
+ for (k = 1; k < stack.length; k++) {
+ var ents = stack[k].entities;
+
+ if (ents.hasOwnProperty(id) && ents[id] === undefined) {
+ delete ents[id];
+ }
+ }
}
}
}
- return _conflicts.length === ccount ? target.update({
- nodes: nodes
- }) : target;
- }
-
- function mergeChildren(targetWay, children, updates, graph) {
- function isUsed(node, targetWay) {
- var hasInterestingParent = graph.parentWays(node).some(function (way) {
- return way.id !== targetWay.id;
- });
- return node.hasInterestingTags() || hasInterestingParent || graph.parentRelations(node).length > 0;
+ for (i = 0; i < stack.length; i++) {
+ stack[i]._updateRebased();
}
+ },
+ _updateRebased: function _updateRebased() {
+ var base = this.base();
+ Object.keys(this._parentWays).forEach(function (child) {
+ if (base.parentWays[child]) {
+ base.parentWays[child].forEach(function (id) {
+ if (!this.entities.hasOwnProperty(id)) {
+ this._parentWays[child].add(id);
+ }
+ }, this);
+ }
+ }, this);
+ Object.keys(this._parentRels).forEach(function (child) {
+ if (base.parentRels[child]) {
+ base.parentRels[child].forEach(function (id) {
+ if (!this.entities.hasOwnProperty(id)) {
+ this._parentRels[child].add(id);
+ }
+ }, this);
+ }
+ }, this);
+ this.transients = {}; // this._childNodes is not updated, under the assumption that
+ // ways are always downloaded with their child nodes.
+ },
+ // Updates calculated properties (parentWays, parentRels) for the specified change
+ _updateCalculated: function _updateCalculated(oldentity, entity, parentWays, parentRels) {
+ parentWays = parentWays || this._parentWays;
+ parentRels = parentRels || this._parentRels;
+ var type = entity && entity.type || oldentity && oldentity.type;
+ var removed, added, i;
- var ccount = _conflicts.length;
-
- for (var i = 0; i < children.length; i++) {
- var id = children[i];
- var node = graph.hasEntity(id); // remove unused childNodes..
-
- if (targetWay.nodes.indexOf(id) === -1) {
- if (node && !isUsed(node, targetWay)) {
- updates.removeIds.push(id);
- }
-
- continue;
- } // restore used childNodes..
-
-
- var local = localGraph.hasEntity(id);
- var remote = remoteGraph.hasEntity(id);
- var target;
+ if (type === 'way') {
+ // Update parentWays
+ if (oldentity && entity) {
+ removed = utilArrayDifference(oldentity.nodes, entity.nodes);
+ added = utilArrayDifference(entity.nodes, oldentity.nodes);
+ } else if (oldentity) {
+ removed = oldentity.nodes;
+ added = [];
+ } else if (entity) {
+ removed = [];
+ added = entity.nodes;
+ }
- if (_option === 'force_remote' && remote && remote.visible) {
- updates.replacements.push(remote);
- } else if (_option === 'force_local' && local) {
- target = osmEntity(local);
+ for (i = 0; i < removed.length; i++) {
+ // make a copy of prototype property, store as own property, and update..
+ parentWays[removed[i]] = new Set(parentWays[removed[i]]);
+ parentWays[removed[i]]["delete"](oldentity.id);
+ }
- if (remote) {
- target = target.update({
- version: remote.version
- });
- }
+ for (i = 0; i < added.length; i++) {
+ // make a copy of prototype property, store as own property, and update..
+ parentWays[added[i]] = new Set(parentWays[added[i]]);
+ parentWays[added[i]].add(entity.id);
+ }
+ } else if (type === 'relation') {
+ // Update parentRels
+ // diff only on the IDs since the same entity can be a member multiple times with different roles
+ var oldentityMemberIDs = oldentity ? oldentity.members.map(function (m) {
+ return m.id;
+ }) : [];
+ var entityMemberIDs = entity ? entity.members.map(function (m) {
+ return m.id;
+ }) : [];
- updates.replacements.push(target);
- } else if (_option === 'safe' && local && remote && local.version !== remote.version) {
- target = osmEntity(local, {
- version: remote.version
- });
+ if (oldentity && entity) {
+ removed = utilArrayDifference(oldentityMemberIDs, entityMemberIDs);
+ added = utilArrayDifference(entityMemberIDs, oldentityMemberIDs);
+ } else if (oldentity) {
+ removed = oldentityMemberIDs;
+ added = [];
+ } else if (entity) {
+ removed = [];
+ added = entityMemberIDs;
+ }
- if (remote.visible) {
- target = mergeLocation(remote, target);
- } else {
- _conflicts.push(_t('merge_remote_changes.conflict.deleted', {
- user: user(remote.user)
- }));
- }
+ for (i = 0; i < removed.length; i++) {
+ // make a copy of prototype property, store as own property, and update..
+ parentRels[removed[i]] = new Set(parentRels[removed[i]]);
+ parentRels[removed[i]]["delete"](oldentity.id);
+ }
- if (_conflicts.length !== ccount) break;
- updates.replacements.push(target);
+ for (i = 0; i < added.length; i++) {
+ // make a copy of prototype property, store as own property, and update..
+ parentRels[added[i]] = new Set(parentRels[added[i]]);
+ parentRels[added[i]].add(entity.id);
}
}
+ },
+ replace: function replace(entity) {
+ if (this.entities[entity.id] === entity) return this;
+ return this.update(function () {
+ this._updateCalculated(this.entities[entity.id], entity);
- return targetWay;
- }
+ this.entities[entity.id] = entity;
+ });
+ },
+ remove: function remove(entity) {
+ return this.update(function () {
+ this._updateCalculated(entity, undefined);
- function updateChildren(updates, graph) {
- for (var i = 0; i < updates.replacements.length; i++) {
- graph = graph.replace(updates.replacements[i]);
- }
+ this.entities[entity.id] = undefined;
+ });
+ },
+ revert: function revert(id) {
+ var baseEntity = this.base().entities[id];
+ var headEntity = this.entities[id];
+ if (headEntity === baseEntity) return this;
+ return this.update(function () {
+ this._updateCalculated(headEntity, baseEntity);
- if (updates.removeIds.length) {
- graph = actionDeleteMultiple(updates.removeIds)(graph);
+ delete this.entities[id];
+ });
+ },
+ update: function update() {
+ var graph = this.frozen ? coreGraph(this, true) : this;
+
+ for (var i = 0; i < arguments.length; i++) {
+ arguments[i].call(graph, graph);
}
+ if (this.frozen) graph.frozen = true;
return graph;
- }
+ },
+ // Obliterates any existing entities
+ load: function load(entities) {
+ var base = this.base();
+ this.entities = Object.create(base.entities);
- function mergeMembers(remote, target) {
- if (_option === 'force_local' || fastDeepEqual(target.members, remote.members)) {
- return target;
- }
+ for (var i in entities) {
+ this.entities[i] = entities[i];
- if (_option === 'force_remote') {
- return target.update({
- members: remote.members
- });
+ this._updateCalculated(base.entities[i], this.entities[i]);
}
- _conflicts.push(_t('merge_remote_changes.conflict.memberlist', {
- user: user(remote.user)
- }));
+ return this;
+ }
+ };
- return target;
+ function osmTurn(turn) {
+ if (!(this instanceof osmTurn)) {
+ return new osmTurn(turn);
}
- function mergeTags(base, remote, target) {
- if (_option === 'force_local' || fastDeepEqual(target.tags, remote.tags)) {
- return target;
- }
+ Object.assign(this, turn);
+ }
+ function osmIntersection(graph, startVertexId, maxDistance) {
+ maxDistance = maxDistance || 30; // in meters
- if (_option === 'force_remote') {
- return target.update({
- tags: remote.tags
- });
- }
+ var vgraph = coreGraph(); // virtual graph
- var ccount = _conflicts.length;
- var o = base.tags || {};
- var a = target.tags || {};
- var b = remote.tags || {};
- var keys = utilArrayUnion(utilArrayUnion(Object.keys(o), Object.keys(a)), Object.keys(b)).filter(function (k) {
- return !discardTags[k];
- });
- var tags = Object.assign({}, a); // shallow copy
+ var i, j, k;
- var changed = false;
+ function memberOfRestriction(entity) {
+ return graph.parentRelations(entity).some(function (r) {
+ return r.isRestriction();
+ });
+ }
- for (var i = 0; i < keys.length; i++) {
- var k = keys[i];
+ function isRoad(way) {
+ if (way.isArea() || way.isDegenerate()) return false;
+ var roads = {
+ 'motorway': true,
+ 'motorway_link': true,
+ 'trunk': true,
+ 'trunk_link': true,
+ 'primary': true,
+ 'primary_link': true,
+ 'secondary': true,
+ 'secondary_link': true,
+ 'tertiary': true,
+ 'tertiary_link': true,
+ 'residential': true,
+ 'unclassified': true,
+ 'living_street': true,
+ 'service': true,
+ 'road': true,
+ 'track': true
+ };
+ return roads[way.tags.highway];
+ }
- if (o[k] !== b[k] && a[k] !== b[k]) {
- // changed remotely..
- if (o[k] !== a[k]) {
- // changed locally..
- _conflicts.push(_t('merge_remote_changes.conflict.tags', {
- tag: k,
- local: a[k],
- remote: b[k],
- user: user(remote.user)
- }));
- } else {
- // unchanged locally, accept remote change..
- if (b.hasOwnProperty(k)) {
- tags[k] = b[k];
- } else {
- delete tags[k];
- }
+ var startNode = graph.entity(startVertexId);
+ var checkVertices = [startNode];
+ var checkWays;
+ var vertices = [];
+ var vertexIds = [];
+ var vertex;
+ var ways = [];
+ var wayIds = [];
+ var way;
+ var nodes = [];
+ var node;
+ var parents = [];
+ var parent; // `actions` will store whatever actions must be performed to satisfy
+ // preconditions for adding a turn restriction to this intersection.
+ // - Remove any existing degenerate turn restrictions (missing from/to, etc)
+ // - Reverse oneways so that they are drawn in the forward direction
+ // - Split ways on key vertices
- changed = true;
- }
- }
- }
+ var actions = []; // STEP 1: walk the graph outwards from starting vertex to search
+ // for more key vertices and ways to include in the intersection..
- return changed && _conflicts.length === ccount ? target.update({
- tags: tags
- }) : target;
- } // `graph.base()` is the common ancestor of the two graphs.
- // `localGraph` contains user's edits up to saving
- // `remoteGraph` contains remote edits to modified nodes
- // `graph` must be a descendent of `localGraph` and may include
- // some conflict resolution actions performed on it.
- //
- // --- ... --- `localGraph` -- ... -- `graph`
- // /
- // `graph.base()` --- ... --- `remoteGraph`
- //
+ while (checkVertices.length) {
+ vertex = checkVertices.pop(); // check this vertex for parent ways that are roads
+ checkWays = graph.parentWays(vertex);
+ var hasWays = false;
- var action = function action(graph) {
- var updates = {
- replacements: [],
- removeIds: []
- };
- var base = graph.base().entities[id];
- var local = localGraph.entity(id);
- var remote = remoteGraph.entity(id);
- var target = osmEntity(local, {
- version: remote.version
- }); // delete/undelete
+ for (i = 0; i < checkWays.length; i++) {
+ way = checkWays[i];
+ if (!isRoad(way) && !memberOfRestriction(way)) continue;
+ ways.push(way); // it's a road, or it's already in a turn restriction
- if (!remote.visible) {
- if (_option === 'force_remote') {
- return actionDeleteMultiple([id])(graph);
- } else if (_option === 'force_local') {
- if (target.type === 'way') {
- target = mergeChildren(target, utilArrayUniq(local.nodes), updates, graph);
- graph = updateChildren(updates, graph);
- }
+ hasWays = true; // check the way's children for more key vertices
- return graph.replace(target);
- } else {
- _conflicts.push(_t('merge_remote_changes.conflict.deleted', {
- user: user(remote.user)
- }));
+ nodes = utilArrayUniq(graph.childNodes(way));
- return graph; // do nothing
- }
- } // merge
+ for (j = 0; j < nodes.length; j++) {
+ node = nodes[j];
+ if (node === vertex) continue; // same thing
+ if (vertices.indexOf(node) !== -1) continue; // seen it already
- if (target.type === 'node') {
- target = mergeLocation(remote, target);
- } else if (target.type === 'way') {
- // pull in any child nodes that may not be present locally..
- graph.rebase(remoteGraph.childNodes(remote), [graph], false);
- target = mergeNodes(base, remote, target);
- target = mergeChildren(target, utilArrayUnion(local.nodes, remote.nodes), updates, graph);
- } else if (target.type === 'relation') {
- target = mergeMembers(remote, target);
- }
+ if (geoSphericalDistance(node.loc, startNode.loc) > maxDistance) continue; // too far from start
+ // a key vertex will have parents that are also roads
- target = mergeTags(base, remote, target);
+ var hasParents = false;
+ parents = graph.parentWays(node);
- if (!_conflicts.length) {
- graph = updateChildren(updates, graph).replace(target);
- }
+ for (k = 0; k < parents.length; k++) {
+ parent = parents[k];
+ if (parent === way) continue; // same thing
- return graph;
- };
+ if (ways.indexOf(parent) !== -1) continue; // seen it already
- action.withOption = function (opt) {
- _option = opt;
- return action;
- };
+ if (!isRoad(parent)) continue; // not a road
- action.conflicts = function () {
- return _conflicts;
- };
+ hasParents = true;
+ break;
+ }
- return action;
- }
+ if (hasParents) {
+ checkVertices.push(node);
+ }
+ }
+ }
- // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MoveNodeAction.as
+ if (hasWays) {
+ vertices.push(vertex);
+ }
+ }
- function actionMove(moveIDs, tryDelta, projection, cache) {
- var _delta = tryDelta;
+ vertices = utilArrayUniq(vertices);
+ ways = utilArrayUniq(ways); // STEP 2: Build a virtual graph containing only the entities in the intersection..
+ // Everything done after this step should act on the virtual graph
+ // Any actions that must be performed later to the main graph go in `actions` array
- function setupCache(graph) {
- function canMove(nodeID) {
- // Allow movement of any node that is in the selectedIDs list..
- if (moveIDs.indexOf(nodeID) !== -1) return true; // Allow movement of a vertex where 2 ways meet..
+ ways.forEach(function (way) {
+ graph.childNodes(way).forEach(function (node) {
+ vgraph = vgraph.replace(node);
+ });
+ vgraph = vgraph.replace(way);
+ graph.parentRelations(way).forEach(function (relation) {
+ if (relation.isRestriction()) {
+ if (relation.isValidRestriction(graph)) {
+ vgraph = vgraph.replace(relation);
+ } else if (relation.isComplete(graph)) {
+ actions.push(actionDeleteRelation(relation.id));
+ }
+ }
+ });
+ }); // STEP 3: Force all oneways to be drawn in the forward direction
- var parents = graph.parentWays(graph.entity(nodeID));
- if (parents.length < 3) return true; // Restrict movement of a vertex where >2 ways meet, unless all parentWays are moving too..
+ ways.forEach(function (w) {
+ var way = vgraph.entity(w.id);
- var parentsMoving = parents.every(function (way) {
- return cache.moving[way.id];
+ if (way.tags.oneway === '-1') {
+ var action = actionReverse(way.id, {
+ reverseOneway: true
});
- if (!parentsMoving) delete cache.moving[nodeID];
- return parentsMoving;
+ actions.push(action);
+ vgraph = action(vgraph);
}
+ }); // STEP 4: Split ways on key vertices
- function cacheEntities(ids) {
- for (var i = 0; i < ids.length; i++) {
- var id = ids[i];
- if (cache.moving[id]) continue;
- cache.moving[id] = true;
- var entity = graph.hasEntity(id);
- if (!entity) continue;
+ var origCount = osmEntity.id.next.way;
+ vertices.forEach(function (v) {
+ // This is an odd way to do it, but we need to find all the ways that
+ // will be split here, then split them one at a time to ensure that these
+ // actions can be replayed on the main graph exactly in the same order.
+ // (It is unintuitive, but the order of ways returned from graph.parentWays()
+ // is arbitrary, depending on how the main graph and vgraph were built)
+ var splitAll = actionSplit([v.id]).keepHistoryOn('first');
- if (entity.type === 'node') {
- cache.nodes.push(id);
- cache.startLoc[id] = entity.loc;
- } else if (entity.type === 'way') {
- cache.ways.push(id);
- cacheEntities(entity.nodes);
- } else {
- cacheEntities(entity.members.map(function (member) {
- return member.id;
- }));
- }
- }
+ if (!splitAll.disabled(vgraph)) {
+ splitAll.ways(vgraph).forEach(function (way) {
+ var splitOne = actionSplit([v.id]).limitWays([way.id]).keepHistoryOn('first');
+ actions.push(splitOne);
+ vgraph = splitOne(vgraph);
+ });
}
+ }); // In here is where we should also split the intersection at nearby junction.
+ // for https://github.com/mapbox/iD-internal/issues/31
+ // nearbyVertices.forEach(function(v) {
+ // });
+ // Reasons why we reset the way id count here:
+ // 1. Continuity with way ids created by the splits so that we can replay
+ // these actions later if the user decides to create a turn restriction
+ // 2. Avoids churning way ids just by hovering over a vertex
+ // and displaying the turn restriction editor
- function cacheIntersections(ids) {
- function isEndpoint(way, id) {
- return !way.isClosed() && !!way.affix(id);
- }
-
- for (var i = 0; i < ids.length; i++) {
- var id = ids[i]; // consider only intersections with 1 moved and 1 unmoved way.
+ osmEntity.id.next.way = origCount; // STEP 5: Update arrays to point to vgraph entities
- var childNodes = graph.childNodes(graph.entity(id));
+ vertexIds = vertices.map(function (v) {
+ return v.id;
+ });
+ vertices = [];
+ ways = [];
+ vertexIds.forEach(function (id) {
+ var vertex = vgraph.entity(id);
+ var parents = vgraph.parentWays(vertex);
+ vertices.push(vertex);
+ ways = ways.concat(parents);
+ });
+ vertices = utilArrayUniq(vertices);
+ ways = utilArrayUniq(ways);
+ vertexIds = vertices.map(function (v) {
+ return v.id;
+ });
+ wayIds = ways.map(function (w) {
+ return w.id;
+ }); // STEP 6: Update the ways with some metadata that will be useful for
+ // walking the intersection graph later and rendering turn arrows.
- for (var j = 0; j < childNodes.length; j++) {
- var node = childNodes[j];
- var parents = graph.parentWays(node);
- if (parents.length !== 2) continue;
- var moved = graph.entity(id);
- var unmoved = null;
+ function withMetadata(way, vertexIds) {
+ var __oneWay = way.isOneWay(); // which affixes are key vertices?
- for (var k = 0; k < parents.length; k++) {
- var way = parents[k];
- if (!cache.moving[way.id]) {
- unmoved = way;
- break;
- }
- }
+ var __first = vertexIds.indexOf(way.first()) !== -1;
- if (!unmoved) continue; // exclude ways that are overly connected..
+ var __last = vertexIds.indexOf(way.last()) !== -1; // what roles is this way eligible for?
- if (utilArrayIntersection(moved.nodes, unmoved.nodes).length > 2) continue;
- if (moved.isArea() || unmoved.isArea()) continue;
- cache.intersections.push({
- nodeId: node.id,
- movedId: moved.id,
- unmovedId: unmoved.id,
- movedIsEP: isEndpoint(moved, node.id),
- unmovedIsEP: isEndpoint(unmoved, node.id)
- });
- }
- }
- }
- if (!cache) {
- cache = {};
- }
+ var __via = __first && __last;
- if (!cache.ok) {
- cache.moving = {};
- cache.intersections = [];
- cache.replacedVertex = {};
- cache.startLoc = {};
- cache.nodes = [];
- cache.ways = [];
- cacheEntities(moveIDs);
- cacheIntersections(cache.ways);
- cache.nodes = cache.nodes.filter(canMove);
- cache.ok = true;
- }
- } // Place a vertex where the moved vertex used to be, to preserve way shape..
- //
- // Start:
- // b ---- e
- // / \
- // / \
- // / \
- // a c
- //
- // * node '*' added to preserve shape
- // / \
- // / b ---- e way `b,e` moved here:
- // / \
- // a c
- //
- //
+ var __from = __first && !__oneWay || __last;
+ var __to = __first || __last && !__oneWay;
- function replaceMovedVertex(nodeId, wayId, graph, delta) {
- var way = graph.entity(wayId);
- var moved = graph.entity(nodeId);
- var movedIndex = way.nodes.indexOf(nodeId);
- var len, prevIndex, nextIndex;
+ return way.update({
+ __first: __first,
+ __last: __last,
+ __from: __from,
+ __via: __via,
+ __to: __to,
+ __oneWay: __oneWay
+ });
+ }
- if (way.isClosed()) {
- len = way.nodes.length - 1;
- prevIndex = (movedIndex + len - 1) % len;
- nextIndex = (movedIndex + len + 1) % len;
- } else {
- len = way.nodes.length;
- prevIndex = movedIndex - 1;
- nextIndex = movedIndex + 1;
- }
+ ways = [];
+ wayIds.forEach(function (id) {
+ var way = withMetadata(vgraph.entity(id), vertexIds);
+ vgraph = vgraph.replace(way);
+ ways.push(way);
+ }); // STEP 7: Simplify - This is an iterative process where we:
+ // 1. Find trivial vertices with only 2 parents
+ // 2. trim off the leaf way from those vertices and remove from vgraph
- var prev = graph.hasEntity(way.nodes[prevIndex]);
- var next = graph.hasEntity(way.nodes[nextIndex]); // Don't add orig vertex at endpoint..
+ var keepGoing;
+ var removeWayIds = [];
+ var removeVertexIds = [];
- if (!prev || !next) return graph;
- var key = wayId + '_' + nodeId;
- var orig = cache.replacedVertex[key];
+ do {
+ keepGoing = false;
+ checkVertices = vertexIds.slice();
- if (!orig) {
- orig = osmNode();
- cache.replacedVertex[key] = orig;
- cache.startLoc[orig.id] = cache.startLoc[nodeId];
- }
+ for (i = 0; i < checkVertices.length; i++) {
+ var vertexId = checkVertices[i];
+ vertex = vgraph.hasEntity(vertexId);
- var start, end;
+ if (!vertex) {
+ if (vertexIds.indexOf(vertexId) !== -1) {
+ vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
+ }
- if (delta) {
- start = projection(cache.startLoc[nodeId]);
- end = projection.invert(geoVecAdd(start, delta));
- } else {
- end = cache.startLoc[nodeId];
- }
+ removeVertexIds.push(vertexId);
+ continue;
+ }
- orig = orig.move(end);
- var angle = Math.abs(geoAngle(orig, prev, projection) - geoAngle(orig, next, projection)) * 180 / Math.PI; // Don't add orig vertex if it would just make a straight line..
+ parents = vgraph.parentWays(vertex);
- if (angle > 175 && angle < 185) return graph; // moving forward or backward along way?
+ if (parents.length < 3) {
+ if (vertexIds.indexOf(vertexId) !== -1) {
+ vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
+ }
+ }
- var p1 = [prev.loc, orig.loc, moved.loc, next.loc].map(projection);
- var p2 = [prev.loc, moved.loc, orig.loc, next.loc].map(projection);
- var d1 = geoPathLength(p1);
- var d2 = geoPathLength(p2);
- var insertAt = d1 <= d2 ? movedIndex : nextIndex; // moving around closed loop?
+ if (parents.length === 2) {
+ // vertex with 2 parents is trivial
+ var a = parents[0];
+ var b = parents[1];
+ var aIsLeaf = a && !a.__via;
+ var bIsLeaf = b && !b.__via;
+ var leaf, survivor;
- if (way.isClosed() && insertAt === 0) insertAt = len;
- way = way.addNode(orig.id, insertAt);
- return graph.replace(orig).replace(way);
- } // Remove duplicate vertex that might have been added by
- // replaceMovedVertex. This is done after the unzorro checks.
+ if (aIsLeaf && !bIsLeaf) {
+ leaf = a;
+ survivor = b;
+ } else if (!aIsLeaf && bIsLeaf) {
+ leaf = b;
+ survivor = a;
+ }
+ if (leaf && survivor) {
+ survivor = withMetadata(survivor, vertexIds); // update survivor way
- function removeDuplicateVertices(wayId, graph) {
- var way = graph.entity(wayId);
- var epsilon = 1e-6;
- var prev, curr;
+ vgraph = vgraph.replace(survivor).remove(leaf); // update graph
- function isInteresting(node, graph) {
- return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
- }
+ removeWayIds.push(leaf.id);
+ keepGoing = true;
+ }
+ }
- for (var i = 0; i < way.nodes.length; i++) {
- curr = graph.entity(way.nodes[i]);
+ parents = vgraph.parentWays(vertex);
- if (prev && curr && geoVecEqual(prev.loc, curr.loc, epsilon)) {
- if (!isInteresting(prev, graph)) {
- way = way.removeNode(prev.id);
- graph = graph.replace(way).remove(prev);
- } else if (!isInteresting(curr, graph)) {
- way = way.removeNode(curr.id);
- graph = graph.replace(way).remove(curr);
+ if (parents.length < 2) {
+ // vertex is no longer a key vertex
+ if (vertexIds.indexOf(vertexId) !== -1) {
+ vertexIds.splice(vertexIds.indexOf(vertexId), 1); // stop checking this one
}
+
+ removeVertexIds.push(vertexId);
+ keepGoing = true;
}
- prev = curr;
+ if (parents.length < 1) {
+ // vertex is no longer attached to anything
+ vgraph = vgraph.remove(vertex);
+ }
}
+ } while (keepGoing);
- return graph;
- } // Reorder nodes around intersections that have moved..
+ vertices = vertices.filter(function (vertex) {
+ return removeVertexIds.indexOf(vertex.id) === -1;
+ }).map(function (vertex) {
+ return vgraph.entity(vertex.id);
+ });
+ ways = ways.filter(function (way) {
+ return removeWayIds.indexOf(way.id) === -1;
+ }).map(function (way) {
+ return vgraph.entity(way.id);
+ }); // OK! Here is our intersection..
+
+ var intersection = {
+ graph: vgraph,
+ actions: actions,
+ vertices: vertices,
+ ways: ways
+ }; // Get all the valid turns through this intersection given a starting way id.
+ // This operates on the virtual graph for everything.
//
- // Start: way1.nodes: b,e (moving)
- // a - b - c ----- d way2.nodes: a,b,c,d (static)
- // | vertex: b
- // e isEP1: true, isEP2, false
+ // Basically, walk through all possible paths from starting way,
+ // honoring the existing turn restrictions as we go (watch out for loops!)
//
- // way1 `b,e` moved here:
- // a ----- c = b - d
- // |
- // e
+ // For each path found, generate and return a `osmTurn` datastructure.
//
- // reorder nodes way1.nodes: b,e
- // a ----- c - b - d way2.nodes: a,c,b,d
- // |
- // e
- //
-
-
- function unZorroIntersection(intersection, graph) {
- var vertex = graph.entity(intersection.nodeId);
- var way1 = graph.entity(intersection.movedId);
- var way2 = graph.entity(intersection.unmovedId);
- var isEP1 = intersection.movedIsEP;
- var isEP2 = intersection.unmovedIsEP; // don't move the vertex if it is the endpoint of both ways.
- if (isEP1 && isEP2) return graph;
- var nodes1 = graph.childNodes(way1).filter(function (n) {
- return n !== vertex;
- });
- var nodes2 = graph.childNodes(way2).filter(function (n) {
- return n !== vertex;
+ intersection.turns = function (fromWayId, maxViaWay) {
+ if (!fromWayId) return [];
+ if (!maxViaWay) maxViaWay = 0;
+ var vgraph = intersection.graph;
+ var keyVertexIds = intersection.vertices.map(function (v) {
+ return v.id;
});
- if (way1.isClosed() && way1.first() === vertex.id) nodes1.push(nodes1[0]);
- if (way2.isClosed() && way2.first() === vertex.id) nodes2.push(nodes2[0]);
- var edge1 = !isEP1 && geoChooseEdge(nodes1, projection(vertex.loc), projection);
- var edge2 = !isEP2 && geoChooseEdge(nodes2, projection(vertex.loc), projection);
- var loc; // snap vertex to nearest edge (or some point between them)..
-
- if (!isEP1 && !isEP2) {
- var epsilon = 1e-6,
- maxIter = 10;
+ var start = vgraph.entity(fromWayId);
+ if (!start || !(start.__from || start.__via)) return []; // maxViaWay=0 from-*-to (0 vias)
+ // maxViaWay=1 from-*-via-*-to (1 via max)
+ // maxViaWay=2 from-*-via-*-via-*-to (2 vias max)
- for (var i = 0; i < maxIter; i++) {
- loc = geoVecInterp(edge1.loc, edge2.loc, 0.5);
- edge1 = geoChooseEdge(nodes1, projection(loc), projection);
- edge2 = geoChooseEdge(nodes2, projection(loc), projection);
- if (Math.abs(edge1.distance - edge2.distance) < epsilon) break;
- }
- } else if (!isEP1) {
- loc = edge1.loc;
- } else {
- loc = edge2.loc;
- }
+ var maxPathLength = maxViaWay * 2 + 3;
+ var turns = [];
+ step(start);
+ return turns; // traverse the intersection graph and find all the valid paths
- graph = graph.replace(vertex.move(loc)); // if zorro happened, reorder nodes..
+ function step(entity, currPath, currRestrictions, matchedRestriction) {
+ currPath = (currPath || []).slice(); // shallow copy
- if (!isEP1 && edge1.index !== way1.nodes.indexOf(vertex.id)) {
- way1 = way1.removeNode(vertex.id).addNode(vertex.id, edge1.index);
- graph = graph.replace(way1);
- }
+ if (currPath.length >= maxPathLength) return;
+ currPath.push(entity.id);
+ currRestrictions = (currRestrictions || []).slice(); // shallow copy
- if (!isEP2 && edge2.index !== way2.nodes.indexOf(vertex.id)) {
- way2 = way2.removeNode(vertex.id).addNode(vertex.id, edge2.index);
- graph = graph.replace(way2);
- }
+ var i, j;
- return graph;
- }
+ if (entity.type === 'node') {
+ var parents = vgraph.parentWays(entity);
+ var nextWays = []; // which ways can we step into?
- function cleanupIntersections(graph) {
- for (var i = 0; i < cache.intersections.length; i++) {
- var obj = cache.intersections[i];
- graph = replaceMovedVertex(obj.nodeId, obj.movedId, graph, _delta);
- graph = replaceMovedVertex(obj.nodeId, obj.unmovedId, graph, null);
- graph = unZorroIntersection(obj, graph);
- graph = removeDuplicateVertices(obj.movedId, graph);
- graph = removeDuplicateVertices(obj.unmovedId, graph);
- }
+ for (i = 0; i < parents.length; i++) {
+ var way = parents[i]; // if next way is a oneway incoming to this vertex, skip
- return graph;
- } // check if moving way endpoint can cross an unmoved way, if so limit delta..
+ if (way.__oneWay && way.nodes[0] !== entity.id) continue; // if we have seen it before (allowing for an initial u-turn), skip
+ if (currPath.indexOf(way.id) !== -1 && currPath.length >= 3) continue; // Check all "current" restrictions (where we've already walked the `FROM`)
- function limitDelta(graph) {
- function moveNode(loc) {
- return geoVecAdd(projection(loc), _delta);
- }
+ var restrict = null;
- for (var i = 0; i < cache.intersections.length; i++) {
- var obj = cache.intersections[i]; // Don't limit movement if this is vertex joins 2 endpoints..
+ for (j = 0; j < currRestrictions.length; j++) {
+ var restriction = currRestrictions[j];
+ var f = restriction.memberByRole('from');
+ var v = restriction.membersByRole('via');
+ var t = restriction.memberByRole('to');
+ var isOnly = /^only_/.test(restriction.tags.restriction); // Does the current path match this turn restriction?
- if (obj.movedIsEP && obj.unmovedIsEP) continue; // Don't limit movement if this vertex is not an endpoint anyway..
+ var matchesFrom = f.id === fromWayId;
+ var matchesViaTo = false;
+ var isAlongOnlyPath = false;
- if (!obj.movedIsEP) continue;
- var node = graph.entity(obj.nodeId);
- var start = projection(node.loc);
- var end = geoVecAdd(start, _delta);
- var movedNodes = graph.childNodes(graph.entity(obj.movedId));
- var movedPath = movedNodes.map(function (n) {
- return moveNode(n.loc);
- });
- var unmovedNodes = graph.childNodes(graph.entity(obj.unmovedId));
- var unmovedPath = unmovedNodes.map(function (n) {
- return projection(n.loc);
- });
- var hits = geoPathIntersections(movedPath, unmovedPath);
+ if (t.id === way.id) {
+ // match TO
+ if (v.length === 1 && v[0].type === 'node') {
+ // match VIA node
+ matchesViaTo = v[0].id === entity.id && (matchesFrom && currPath.length === 2 || !matchesFrom && currPath.length > 2);
+ } else {
+ // match all VIA ways
+ var pathVias = [];
- for (var j = 0; i < hits.length; i++) {
- if (geoVecEqual(hits[j], end)) continue;
- var edge = geoChooseEdge(unmovedNodes, end, projection);
- _delta = geoVecSubtract(projection(edge.loc), start);
- }
- }
- }
+ for (k = 2; k < currPath.length; k += 2) {
+ // k = 2 skips FROM
+ pathVias.push(currPath[k]); // (path goes way-node-way...)
+ }
- var action = function action(graph) {
- if (_delta[0] === 0 && _delta[1] === 0) return graph;
- setupCache(graph);
+ var restrictionVias = [];
- if (cache.intersections.length) {
- limitDelta(graph);
- }
+ for (k = 0; k < v.length; k++) {
+ if (v[k].type === 'way') {
+ restrictionVias.push(v[k].id);
+ }
+ }
- for (var i = 0; i < cache.nodes.length; i++) {
- var node = graph.entity(cache.nodes[i]);
- var start = projection(node.loc);
- var end = geoVecAdd(start, _delta);
- graph = graph.replace(node.move(projection.invert(end)));
- }
+ var diff = utilArrayDifference(pathVias, restrictionVias);
+ matchesViaTo = !diff.length;
+ }
+ } else if (isOnly) {
+ for (k = 0; k < v.length; k++) {
+ // way doesn't match TO, but is one of the via ways along the path of an "only"
+ if (v[k].type === 'way' && v[k].id === way.id) {
+ isAlongOnlyPath = true;
+ break;
+ }
+ }
+ }
- if (cache.intersections.length) {
- graph = cleanupIntersections(graph);
- }
+ if (matchesViaTo) {
+ if (isOnly) {
+ restrict = {
+ id: restriction.id,
+ direct: matchesFrom,
+ from: f.id,
+ only: true,
+ end: true
+ };
+ } else {
+ restrict = {
+ id: restriction.id,
+ direct: matchesFrom,
+ from: f.id,
+ no: true,
+ end: true
+ };
+ }
+ } else {
+ // indirect - caused by a different nearby restriction
+ if (isAlongOnlyPath) {
+ restrict = {
+ id: restriction.id,
+ direct: false,
+ from: f.id,
+ only: true,
+ end: false
+ };
+ } else if (isOnly) {
+ restrict = {
+ id: restriction.id,
+ direct: false,
+ from: f.id,
+ no: true,
+ end: true
+ };
+ }
+ } // stop looking if we find a "direct" restriction (matching FROM, VIA, TO)
- return graph;
- };
- action.delta = function () {
- return _delta;
- };
+ if (restrict && restrict.direct) break;
+ }
- return action;
- }
+ nextWays.push({
+ way: way,
+ restrict: restrict
+ });
+ }
- function actionMoveMember(relationId, fromIndex, toIndex) {
- return function (graph) {
- return graph.replace(graph.entity(relationId).moveMember(fromIndex, toIndex));
- };
- }
+ nextWays.forEach(function (nextWay) {
+ step(nextWay.way, currPath, currRestrictions, nextWay.restrict);
+ });
+ } else {
+ // entity.type === 'way'
+ if (currPath.length >= 3) {
+ // this is a "complete" path..
+ var turnPath = currPath.slice(); // shallow copy
+ // an indirect restriction - only include the partial path (starting at FROM)
- function actionMoveNode(nodeID, toLoc) {
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var node = graph.entity(nodeID);
- return graph.replace(node.move(geoVecInterp(node.loc, toLoc, t)));
- };
+ if (matchedRestriction && matchedRestriction.direct === false) {
+ for (i = 0; i < turnPath.length; i++) {
+ if (turnPath[i] === matchedRestriction.from) {
+ turnPath = turnPath.slice(i);
+ break;
+ }
+ }
+ }
- action.transitionable = true;
- return action;
- }
+ var turn = pathToTurn(turnPath);
- function actionNoop() {
- return function (graph) {
- return graph;
- };
- }
+ if (turn) {
+ if (matchedRestriction) {
+ turn.restrictionID = matchedRestriction.id;
+ turn.no = matchedRestriction.no;
+ turn.only = matchedRestriction.only;
+ turn.direct = matchedRestriction.direct;
+ }
- function actionOrthogonalize(wayID, projection, vertexID, degThresh, ep) {
- var epsilon = ep || 1e-4;
- var threshold = degThresh || 13; // degrees within right or straight to alter
- // We test normalized dot products so we can compare as cos(angle)
+ turns.push(osmTurn(turn));
+ }
- var lowerThreshold = Math.cos((90 - threshold) * Math.PI / 180);
- var upperThreshold = Math.cos(threshold * Math.PI / 180);
+ if (currPath[0] === currPath[2]) return; // if we made a u-turn - stop here
+ }
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var way = graph.entity(wayID);
- way = way.removeNode(''); // sanity check - remove any consecutive duplicates
+ if (matchedRestriction && matchedRestriction.end) return; // don't advance any further
+ // which nodes can we step into?
- if (way.tags.nonsquare) {
- var tags = Object.assign({}, way.tags); // since we're squaring, remove indication that this is physically unsquare
+ var n1 = vgraph.entity(entity.first());
+ var n2 = vgraph.entity(entity.last());
+ var dist = geoSphericalDistance(n1.loc, n2.loc);
+ var nextNodes = [];
- delete tags.nonsquare;
- way = way.update({
- tags: tags
- });
- }
+ if (currPath.length > 1) {
+ if (dist > maxDistance) return; // the next node is too far
- graph = graph.replace(way);
- var isClosed = way.isClosed();
- var nodes = graph.childNodes(way).slice(); // shallow copy
+ if (!entity.__via) return; // this way is a leaf / can't be a via
+ }
- if (isClosed) nodes.pop();
+ if (!entity.__oneWay && // bidirectional..
+ keyVertexIds.indexOf(n1.id) !== -1 && // key vertex..
+ currPath.indexOf(n1.id) === -1) {
+ // haven't seen it yet..
+ nextNodes.push(n1); // can advance to first node
+ }
- if (vertexID !== undefined) {
- nodes = nodeSubset(nodes, vertexID, isClosed);
- if (nodes.length !== 3) return graph;
- } // note: all geometry functions here use the unclosed node/point/coord list
+ if (keyVertexIds.indexOf(n2.id) !== -1 && // key vertex..
+ currPath.indexOf(n2.id) === -1) {
+ // haven't seen it yet..
+ nextNodes.push(n2); // can advance to last node
+ }
+ nextNodes.forEach(function (nextNode) {
+ // gather restrictions FROM this way
+ var fromRestrictions = vgraph.parentRelations(entity).filter(function (r) {
+ if (!r.isRestriction()) return false;
+ var f = r.memberByRole('from');
+ if (!f || f.id !== entity.id) return false;
+ var isOnly = /^only_/.test(r.tags.restriction);
+ if (!isOnly) return true; // `only_` restrictions only matter along the direction of the VIA - #4849
- var nodeCount = {};
- var points = [];
- var corner = {
- i: 0,
- dotp: 1
- };
- var node, point, loc, score, motions, i, j;
+ var isOnlyVia = false;
+ var v = r.membersByRole('via');
- for (i = 0; i < nodes.length; i++) {
- node = nodes[i];
- nodeCount[node.id] = (nodeCount[node.id] || 0) + 1;
- points.push({
- id: node.id,
- coord: projection(node.loc)
- });
- }
+ if (v.length === 1 && v[0].type === 'node') {
+ // via node
+ isOnlyVia = v[0].id === nextNode.id;
+ } else {
+ // via way(s)
+ for (var i = 0; i < v.length; i++) {
+ if (v[i].type !== 'way') continue;
+ var viaWay = vgraph.entity(v[i].id);
- if (points.length === 3) {
- // move only one vertex for right triangle
- for (i = 0; i < 1000; i++) {
- motions = points.map(calcMotion);
- points[corner.i].coord = geoVecAdd(points[corner.i].coord, motions[corner.i]);
- score = corner.dotp;
+ if (viaWay.first() === nextNode.id || viaWay.last() === nextNode.id) {
+ isOnlyVia = true;
+ break;
+ }
+ }
+ }
- if (score < epsilon) {
- break;
- }
+ return isOnlyVia;
+ });
+ step(nextNode, currPath, currRestrictions.concat(fromRestrictions), false);
+ });
}
+ } // assumes path is alternating way-node-way of odd length
- node = graph.entity(nodes[corner.i].id);
- loc = projection.invert(points[corner.i].coord);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
- } else {
- var straights = [];
- var simplified = []; // Remove points from nearly straight sections..
- // This produces a simplified shape to orthogonalize
- for (i = 0; i < points.length; i++) {
- point = points[i];
- var dotp = 0;
+ function pathToTurn(path) {
+ if (path.length < 3) return;
+ var fromWayId, fromNodeId, fromVertexId;
+ var toWayId, toNodeId, toVertexId;
+ var viaWayIds, viaNodeId, isUturn;
+ fromWayId = path[0];
+ toWayId = path[path.length - 1];
- if (isClosed || i > 0 && i < points.length - 1) {
- var a = points[(i - 1 + points.length) % points.length];
- var b = points[(i + 1) % points.length];
- dotp = Math.abs(geoOrthoNormalizedDotProduct(a.coord, b.coord, point.coord));
- }
+ if (path.length === 3 && fromWayId === toWayId) {
+ // u turn
+ var way = vgraph.entity(fromWayId);
+ if (way.__oneWay) return null;
+ isUturn = true;
+ viaNodeId = fromVertexId = toVertexId = path[1];
+ fromNodeId = toNodeId = adjacentNode(fromWayId, viaNodeId);
+ } else {
+ isUturn = false;
+ fromVertexId = path[1];
+ fromNodeId = adjacentNode(fromWayId, fromVertexId);
+ toVertexId = path[path.length - 2];
+ toNodeId = adjacentNode(toWayId, toVertexId);
- if (dotp > upperThreshold) {
- straights.push(point);
+ if (path.length === 3) {
+ viaNodeId = path[1];
} else {
- simplified.push(point);
- }
- } // Orthogonalize the simplified shape
-
-
- var bestPoints = clonePoints(simplified);
- var originalPoints = clonePoints(simplified);
- score = Infinity;
-
- for (i = 0; i < 1000; i++) {
- motions = simplified.map(calcMotion);
-
- for (j = 0; j < motions.length; j++) {
- simplified[j].coord = geoVecAdd(simplified[j].coord, motions[j]);
+ viaWayIds = path.filter(function (entityId) {
+ return entityId[0] === 'w';
+ });
+ viaWayIds = viaWayIds.slice(1, viaWayIds.length - 1); // remove first, last
}
+ }
- var newScore = geoOrthoCalcScore(simplified, isClosed, epsilon, threshold);
-
- if (newScore < score) {
- bestPoints = clonePoints(simplified);
- score = newScore;
- }
+ return {
+ key: path.join('_'),
+ path: path,
+ from: {
+ node: fromNodeId,
+ way: fromWayId,
+ vertex: fromVertexId
+ },
+ via: {
+ node: viaNodeId,
+ ways: viaWayIds
+ },
+ to: {
+ node: toNodeId,
+ way: toWayId,
+ vertex: toVertexId
+ },
+ u: isUturn
+ };
- if (score < epsilon) {
- break;
- }
+ function adjacentNode(wayId, affixId) {
+ var nodes = vgraph.entity(wayId).nodes;
+ return affixId === nodes[0] ? nodes[1] : nodes[nodes.length - 2];
}
+ }
+ };
- var bestCoords = bestPoints.map(function (p) {
- return p.coord;
- });
- if (isClosed) bestCoords.push(bestCoords[0]); // move the nodes that should move
+ return intersection;
+ }
+ function osmInferRestriction(graph, turn, projection) {
+ var fromWay = graph.entity(turn.from.way);
+ var fromNode = graph.entity(turn.from.node);
+ var fromVertex = graph.entity(turn.from.vertex);
+ var toWay = graph.entity(turn.to.way);
+ var toNode = graph.entity(turn.to.node);
+ var toVertex = graph.entity(turn.to.vertex);
+ var fromOneWay = fromWay.tags.oneway === 'yes';
+ var toOneWay = toWay.tags.oneway === 'yes';
+ var angle = (geoAngle(fromVertex, fromNode, projection) - geoAngle(toVertex, toNode, projection)) * 180 / Math.PI;
- for (i = 0; i < bestPoints.length; i++) {
- point = bestPoints[i];
+ while (angle < 0) {
+ angle += 360;
+ }
- if (!geoVecEqual(originalPoints[i].coord, point.coord)) {
- node = graph.entity(point.id);
- loc = projection.invert(point.coord);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
- }
- } // move the nodes along straight segments
+ if (fromNode === toNode) {
+ return 'no_u_turn';
+ }
+ if ((angle < 23 || angle > 336) && fromOneWay && toOneWay) {
+ return 'no_u_turn'; // wider tolerance for u-turn if both ways are oneway
+ }
- for (i = 0; i < straights.length; i++) {
- point = straights[i];
- if (nodeCount[point.id] > 1) continue; // skip self-intersections
+ if ((angle < 40 || angle > 319) && fromOneWay && toOneWay && turn.from.vertex !== turn.to.vertex) {
+ return 'no_u_turn'; // even wider tolerance for u-turn if there is a via way (from !== to)
+ }
- node = graph.entity(point.id);
+ if (angle < 158) {
+ return 'no_right_turn';
+ }
- if (t === 1 && graph.parentWays(node).length === 1 && graph.parentRelations(node).length === 0 && !node.hasInterestingTags()) {
- // remove uninteresting points..
- graph = actionDeleteNode(node.id)(graph);
- } else {
- // move interesting points to the nearest edge..
- var choice = geoVecProject(point.coord, bestCoords);
+ if (angle > 202) {
+ return 'no_left_turn';
+ }
- if (choice) {
- loc = projection.invert(choice.target);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc, t)));
- }
- }
- }
- }
-
- return graph;
-
- function clonePoints(array) {
- return array.map(function (p) {
- return {
- id: p.id,
- coord: [p.coord[0], p.coord[1]]
- };
- });
- }
-
- function calcMotion(point, i, array) {
- // don't try to move the endpoints of a non-closed way.
- if (!isClosed && (i === 0 || i === array.length - 1)) return [0, 0]; // don't try to move a node that appears more than once (self intersection)
-
- if (nodeCount[array[i].id] > 1) return [0, 0];
- var a = array[(i - 1 + array.length) % array.length].coord;
- var origin = point.coord;
- var b = array[(i + 1) % array.length].coord;
- var p = geoVecSubtract(a, origin);
- var q = geoVecSubtract(b, origin);
- var scale = 2 * Math.min(geoVecLength(p), geoVecLength(q));
- p = geoVecNormalize(p);
- q = geoVecNormalize(q);
- var dotp = p[0] * q[0] + p[1] * q[1];
- var val = Math.abs(dotp);
-
- if (val < lowerThreshold) {
- // nearly orthogonal
- corner.i = i;
- corner.dotp = val;
- var vec = geoVecNormalize(geoVecAdd(p, q));
- return geoVecScale(vec, 0.1 * dotp * scale);
- }
-
- return [0, 0]; // do nothing
- }
- }; // if we are only orthogonalizing one vertex,
- // get that vertex and the previous and next
-
-
- function nodeSubset(nodes, vertexID, isClosed) {
- var first = isClosed ? 0 : 1;
- var last = isClosed ? nodes.length : nodes.length - 1;
+ return 'no_straight_on';
+ }
- for (var i = first; i < last; i++) {
- if (nodes[i].id === vertexID) {
- return [nodes[(i - 1 + nodes.length) % nodes.length], nodes[i], nodes[(i + 1) % nodes.length]];
+ function actionMergePolygon(ids, newRelationId) {
+ function groupEntities(graph) {
+ var entities = ids.map(function (id) {
+ return graph.entity(id);
+ });
+ var geometryGroups = utilArrayGroupBy(entities, function (entity) {
+ if (entity.type === 'way' && entity.isClosed()) {
+ return 'closedWay';
+ } else if (entity.type === 'relation' && entity.isMultipolygon()) {
+ return 'multipolygon';
+ } else {
+ return 'other';
}
- }
-
- return [];
+ });
+ return Object.assign({
+ closedWay: [],
+ multipolygon: [],
+ other: []
+ }, geometryGroups);
}
- action.disabled = function (graph) {
- var way = graph.entity(wayID);
- way = way.removeNode(''); // sanity check - remove any consecutive duplicates
+ var action = function action(graph) {
+ var entities = groupEntities(graph); // An array representing all the polygons that are part of the multipolygon.
+ //
+ // Each element is itself an array of objects with an id property, and has a
+ // locs property which is an array of the locations forming the polygon.
- graph = graph.replace(way);
- var isClosed = way.isClosed();
- var nodes = graph.childNodes(way).slice(); // shallow copy
+ var polygons = entities.multipolygon.reduce(function (polygons, m) {
+ return polygons.concat(osmJoinWays(m.members, graph));
+ }, []).concat(entities.closedWay.map(function (d) {
+ var member = [{
+ id: d.id
+ }];
+ member.nodes = graph.childNodes(d);
+ return member;
+ })); // contained is an array of arrays of boolean values,
+ // where contained[j][k] is true iff the jth way is
+ // contained by the kth way.
- if (isClosed) nodes.pop();
- var allowStraightAngles = false;
+ var contained = polygons.map(function (w, i) {
+ return polygons.map(function (d, n) {
+ if (i === n) return null;
+ return geoPolygonContainsPolygon(d.nodes.map(function (n) {
+ return n.loc;
+ }), w.nodes.map(function (n) {
+ return n.loc;
+ }));
+ });
+ }); // Sort all polygons as either outer or inner ways
- if (vertexID !== undefined) {
- allowStraightAngles = true;
- nodes = nodeSubset(nodes, vertexID, isClosed);
- if (nodes.length !== 3) return 'end_vertex';
+ var members = [];
+ var outer = true;
+
+ while (polygons.length) {
+ extractUncontained(polygons);
+ polygons = polygons.filter(isContained);
+ contained = contained.filter(isContained).map(filterContained);
}
- var coords = nodes.map(function (n) {
- return projection(n.loc);
- });
- var score = geoOrthoCanOrthogonalize(coords, isClosed, epsilon, threshold, allowStraightAngles);
+ function isContained(d, i) {
+ return contained[i].some(function (val) {
+ return val;
+ });
+ }
- if (score === null) {
- return 'not_squarish';
- } else if (score === 0) {
- return 'square_enough';
- } else {
- return false;
+ function filterContained(d) {
+ return d.filter(isContained);
}
- };
- action.transitionable = true;
- return action;
- }
+ function extractUncontained(polygons) {
+ polygons.forEach(function (d, i) {
+ if (!isContained(d, i)) {
+ d.forEach(function (member) {
+ members.push({
+ type: 'way',
+ id: member.id,
+ role: outer ? 'outer' : 'inner'
+ });
+ });
+ }
+ });
+ outer = !outer;
+ } // Move all tags to one relation.
+ // Keep the oldest multipolygon alive if it exists.
- //
- // `turn` must be an `osmTurn` object
- // see osm/intersection.js, pathToTurn()
- //
- // This specifies a restriction of type `restriction` when traveling from
- // `turn.from.way` toward `turn.to.way` via `turn.via.node` OR `turn.via.ways`.
- // (The action does not check that these entities form a valid intersection.)
- //
- // From, to, and via ways should be split before calling this action.
- // (old versions of the code would split the ways here, but we no longer do it)
- //
- // For testing convenience, accepts a restrictionID to assign to the new
- // relation. Normally, this will be undefined and the relation will
- // automatically be assigned a new ID.
- //
- function actionRestrictTurn(turn, restrictionType, restrictionID) {
- return function (graph) {
- var fromWay = graph.entity(turn.from.way);
- var toWay = graph.entity(turn.to.way);
- var viaNode = turn.via.node && graph.entity(turn.via.node);
- var viaWays = turn.via.ways && turn.via.ways.map(function (id) {
- return graph.entity(id);
- });
- var members = [];
- members.push({
- id: fromWay.id,
- type: 'way',
- role: 'from'
- });
+ var relation;
- if (viaNode) {
- members.push({
- id: viaNode.id,
- type: 'node',
- role: 'via'
+ if (entities.multipolygon.length > 0) {
+ var oldestID = utilOldestID(entities.multipolygon.map(function (entity) {
+ return entity.id;
+ }));
+ relation = entities.multipolygon.find(function (entity) {
+ return entity.id === oldestID;
});
- } else if (viaWays) {
- viaWays.forEach(function (viaWay) {
- members.push({
- id: viaWay.id,
- type: 'way',
- role: 'via'
- });
+ } else {
+ relation = osmRelation({
+ id: newRelationId,
+ tags: {
+ type: 'multipolygon'
+ }
});
}
- members.push({
- id: toWay.id,
- type: 'way',
- role: 'to'
+ entities.multipolygon.forEach(function (m) {
+ if (m.id !== relation.id) {
+ relation = relation.mergeTags(m.tags);
+ graph = graph.remove(m);
+ }
});
- return graph.replace(osmRelation({
- id: restrictionID,
- tags: {
- type: 'restriction',
- restriction: restrictionType
- },
- members: members
+ entities.closedWay.forEach(function (way) {
+ function isThisOuter(m) {
+ return m.id === way.id && m.role !== 'inner';
+ }
+
+ if (members.some(isThisOuter)) {
+ relation = relation.mergeTags(way.tags);
+ graph = graph.replace(way.update({
+ tags: {}
+ }));
+ }
+ });
+ return graph.replace(relation.update({
+ members: members,
+ tags: utilObjectOmit(relation.tags, ['area'])
}));
};
- }
- function actionRevert(id) {
- var action = function action(graph) {
- var entity = graph.hasEntity(id),
- base = graph.base().entities[id];
-
- if (entity && !base) {
- // entity will be removed..
- if (entity.type === 'node') {
- graph.parentWays(entity).forEach(function (parent) {
- parent = parent.removeNode(id);
- graph = graph.replace(parent);
+ action.disabled = function (graph) {
+ var entities = groupEntities(graph);
- if (parent.isDegenerate()) {
- graph = actionDeleteWay(parent.id)(graph);
- }
- });
- }
+ if (entities.other.length > 0 || entities.closedWay.length + entities.multipolygon.length < 2) {
+ return 'not_eligible';
+ }
- graph.parentRelations(entity).forEach(function (parent) {
- parent = parent.removeMembersWithID(id);
- graph = graph.replace(parent);
+ if (!entities.multipolygon.every(function (r) {
+ return r.isComplete(graph);
+ })) {
+ return 'incomplete_relation';
+ }
- if (parent.isDegenerate()) {
- graph = actionDeleteRelation(parent.id)(graph);
+ if (!entities.multipolygon.length) {
+ var sharedMultipolygons = [];
+ entities.closedWay.forEach(function (way, i) {
+ if (i === 0) {
+ sharedMultipolygons = graph.parentMultipolygons(way);
+ } else {
+ sharedMultipolygons = utilArrayIntersection(sharedMultipolygons, graph.parentMultipolygons(way));
}
});
- }
+ sharedMultipolygons = sharedMultipolygons.filter(function (relation) {
+ return relation.members.length === entities.closedWay.length;
+ });
- return graph.revert(id);
+ if (sharedMultipolygons.length) {
+ // don't create a new multipolygon if it'd be redundant
+ return 'not_eligible';
+ }
+ } else if (entities.closedWay.some(function (way) {
+ return utilArrayIntersection(graph.parentMultipolygons(way), entities.multipolygon).length;
+ })) {
+ // don't add a way to a multipolygon again if it's already a member
+ return 'not_eligible';
+ }
};
return action;
}
- function actionRotate(rotateIds, pivot, angle, projection) {
- var action = function action(graph) {
- return graph.update(function (graph) {
- utilGetAllNodes(rotateIds, graph).forEach(function (node) {
- var point = geoRotate([projection(node.loc)], angle, pivot)[0];
- graph = graph.replace(node.move(projection.invert(point)));
- });
- });
- };
+ var DESCRIPTORS$1 = descriptors;
+ var objectDefinePropertyModule = objectDefineProperty;
+ var regExpFlags = regexpFlags$1;
+ var fails$4 = fails$V;
- return action;
- }
+ var RegExpPrototype = RegExp.prototype;
- function actionScale(ids, pivotLoc, scaleFactor, projection) {
- return function (graph) {
- return graph.update(function (graph) {
- var point, radial;
- utilGetAllNodes(ids, graph).forEach(function (node) {
- point = projection(node.loc);
- radial = [point[0] - pivotLoc[0], point[1] - pivotLoc[1]];
- point = [pivotLoc[0] + scaleFactor * radial[0], pivotLoc[1] + scaleFactor * radial[1]];
- graph = graph.replace(node.move(projection.invert(point)));
- });
- });
- };
- }
+ var FORCED$2 = DESCRIPTORS$1 && fails$4(function () {
+ // eslint-disable-next-line es/no-object-getownpropertydescriptor -- safe
+ return Object.getOwnPropertyDescriptor(RegExpPrototype, 'flags').get.call({ dotAll: true, sticky: true }) !== 'sy';
+ });
- /* Align nodes along their common axis */
+ // `RegExp.prototype.flags` getter
+ // https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
+ if (FORCED$2) objectDefinePropertyModule.f(RegExpPrototype, 'flags', {
+ configurable: true,
+ get: regExpFlags
+ });
- function actionStraightenNodes(nodeIDs, projection) {
- function positionAlongWay(a, o, b) {
- return geoVecDot(a, b, o) / geoVecDot(b, b, o);
- } // returns the endpoints of the long axis of symmetry of the `points` bounding rect
+ var fastDeepEqual = function equal(a, b) {
+ if (a === b) return true;
+ if (a && b && _typeof(a) == 'object' && _typeof(b) == 'object') {
+ if (a.constructor !== b.constructor) return false;
+ var length, i, keys;
- function getEndpoints(points) {
- var ssr = geoGetSmallestSurroundingRectangle(points); // Choose line pq = axis of symmetry.
- // The shape's surrounding rectangle has 2 axes of symmetry.
- // Snap points to the long axis
+ if (Array.isArray(a)) {
+ length = a.length;
+ if (length != b.length) return false;
- var p1 = [(ssr.poly[0][0] + ssr.poly[1][0]) / 2, (ssr.poly[0][1] + ssr.poly[1][1]) / 2];
- var q1 = [(ssr.poly[2][0] + ssr.poly[3][0]) / 2, (ssr.poly[2][1] + ssr.poly[3][1]) / 2];
- var p2 = [(ssr.poly[3][0] + ssr.poly[4][0]) / 2, (ssr.poly[3][1] + ssr.poly[4][1]) / 2];
- var q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2];
- var isLong = geoVecLength(p1, q1) > geoVecLength(p2, q2);
+ for (i = length; i-- !== 0;) {
+ if (!equal(a[i], b[i])) return false;
+ }
- if (isLong) {
- return [p1, q1];
+ return true;
}
- return [p2, q2];
- }
+ if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags;
+ if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
+ if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
+ keys = Object.keys(a);
+ length = keys.length;
+ if (length !== Object.keys(b).length) return false;
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var nodes = nodeIDs.map(function (id) {
- return graph.entity(id);
- });
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var endpoints = getEndpoints(points);
- var startPoint = endpoints[0];
- var endPoint = endpoints[1]; // Move points onto the line connecting the endpoints
+ for (i = length; i-- !== 0;) {
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
+ }
- for (var i = 0; i < points.length; i++) {
- var node = nodes[i];
- var point = points[i];
- var u = positionAlongWay(point, startPoint, endPoint);
- var point2 = geoVecInterp(startPoint, endPoint, u);
- var loc2 = projection.invert(point2);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc2, t)));
+ for (i = length; i-- !== 0;) {
+ var key = keys[i];
+ if (!equal(a[key], b[key])) return false;
}
- return graph;
- };
+ return true;
+ } // true if both NaN, false otherwise
- action.disabled = function (graph) {
- var nodes = nodeIDs.map(function (id) {
- return graph.entity(id);
- });
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var endpoints = getEndpoints(points);
- var startPoint = endpoints[0];
- var endPoint = endpoints[1];
- var maxDistance = 0;
- for (var i = 0; i < points.length; i++) {
- var point = points[i];
- var u = positionAlongWay(point, startPoint, endPoint);
- var p = geoVecInterp(startPoint, endPoint, u);
- var dist = geoVecLength(p, point);
+ return a !== a && b !== b;
+ };
- if (!isNaN(dist) && dist > maxDistance) {
- maxDistance = dist;
- }
- }
+ // J. W. Hunt and M. D. McIlroy, An algorithm for differential buffer
+ // comparison, Bell Telephone Laboratories CSTR #41 (1976)
+ // http://www.cs.dartmouth.edu/~doug/
+ // https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
+ //
+ // Expects two arrays, finds longest common sequence
- if (maxDistance < 0.0001) {
- return 'straight_enough';
+ function LCS(buffer1, buffer2) {
+ var equivalenceClasses = {};
+
+ for (var j = 0; j < buffer2.length; j++) {
+ var item = buffer2[j];
+
+ if (equivalenceClasses[item]) {
+ equivalenceClasses[item].push(j);
+ } else {
+ equivalenceClasses[item] = [j];
}
+ }
+
+ var NULLRESULT = {
+ buffer1index: -1,
+ buffer2index: -1,
+ chain: null
};
+ var candidates = [NULLRESULT];
- action.transitionable = true;
- return action;
- }
+ for (var i = 0; i < buffer1.length; i++) {
+ var _item = buffer1[i];
+ var buffer2indices = equivalenceClasses[_item] || [];
+ var r = 0;
+ var c = candidates[0];
- /*
- * Based on https://github.com/openstreetmap/potlatch2/net/systemeD/potlatch2/tools/Straighten.as
- */
+ for (var jx = 0; jx < buffer2indices.length; jx++) {
+ var _j = buffer2indices[jx];
+ var s = void 0;
- function actionStraightenWay(selectedIDs, projection) {
- function positionAlongWay(a, o, b) {
- return geoVecDot(a, b, o) / geoVecDot(b, b, o);
- } // Return all selected ways as a continuous, ordered array of nodes
+ for (s = r; s < candidates.length; s++) {
+ if (candidates[s].buffer2index < _j && (s === candidates.length - 1 || candidates[s + 1].buffer2index > _j)) {
+ break;
+ }
+ }
+ if (s < candidates.length) {
+ var newCandidate = {
+ buffer1index: i,
+ buffer2index: _j,
+ chain: candidates[s]
+ };
- function allNodes(graph) {
- var nodes = [];
- var startNodes = [];
- var endNodes = [];
- var remainingWays = [];
- var selectedWays = selectedIDs.filter(function (w) {
- return graph.entity(w).type === 'way';
- });
- var selectedNodes = selectedIDs.filter(function (n) {
- return graph.entity(n).type === 'node';
- });
+ if (r === candidates.length) {
+ candidates.push(c);
+ } else {
+ candidates[r] = c;
+ }
- for (var i = 0; i < selectedWays.length; i++) {
- var way = graph.entity(selectedWays[i]);
- nodes = way.nodes.slice(0);
- remainingWays.push(nodes);
- startNodes.push(nodes[0]);
- endNodes.push(nodes[nodes.length - 1]);
- } // Remove duplicate end/startNodes (duplicate nodes cannot be at the line end,
- // and need to be removed so currNode difference calculation below works)
- // i.e. ["n-1", "n-1", "n-2"] => ["n-2"]
+ r = s + 1;
+ c = newCandidate;
+ if (r === candidates.length) {
+ break; // no point in examining further (j)s
+ }
+ }
+ }
- startNodes = startNodes.filter(function (n) {
- return startNodes.indexOf(n) === startNodes.lastIndexOf(n);
- });
- endNodes = endNodes.filter(function (n) {
- return endNodes.indexOf(n) === endNodes.lastIndexOf(n);
- }); // Choose the initial endpoint to start from
+ candidates[r] = c;
+ } // At this point, we know the LCS: it's in the reverse of the
+ // linked-list through .chain of candidates[candidates.length - 1].
- var currNode = utilArrayDifference(startNodes, endNodes).concat(utilArrayDifference(endNodes, startNodes))[0];
- var nextWay = [];
- nodes = []; // Create nested function outside of loop to avoid "function in loop" lint error
- var getNextWay = function getNextWay(currNode, remainingWays) {
- return remainingWays.filter(function (way) {
- return way[0] === currNode || way[way.length - 1] === currNode;
- })[0];
- }; // Add nodes to end of nodes array, until all ways are added
+ return candidates[candidates.length - 1];
+ } // We apply the LCS to build a 'comm'-style picture of the
+ // offsets and lengths of mismatched chunks in the input
+ // buffers. This is used by diff3MergeRegions.
- while (remainingWays.length) {
- nextWay = getNextWay(currNode, remainingWays);
- remainingWays = utilArrayDifference(remainingWays, [nextWay]);
-
- if (nextWay[0] !== currNode) {
- nextWay.reverse();
- }
-
- nodes = nodes.concat(nextWay);
- currNode = nodes[nodes.length - 1];
- } // If user selected 2 nodes to straighten between, then slice nodes array to those nodes
+ function diffIndices(buffer1, buffer2) {
+ var lcs = LCS(buffer1, buffer2);
+ var result = [];
+ var tail1 = buffer1.length;
+ var tail2 = buffer2.length;
+ for (var candidate = lcs; candidate !== null; candidate = candidate.chain) {
+ var mismatchLength1 = tail1 - candidate.buffer1index - 1;
+ var mismatchLength2 = tail2 - candidate.buffer2index - 1;
+ tail1 = candidate.buffer1index;
+ tail2 = candidate.buffer2index;
- if (selectedNodes.length === 2) {
- var startNodeIdx = nodes.indexOf(selectedNodes[0]);
- var endNodeIdx = nodes.indexOf(selectedNodes[1]);
- var sortedStartEnd = [startNodeIdx, endNodeIdx];
- sortedStartEnd.sort(function (a, b) {
- return a - b;
+ if (mismatchLength1 || mismatchLength2) {
+ result.push({
+ buffer1: [tail1 + 1, mismatchLength1],
+ buffer1Content: buffer1.slice(tail1 + 1, tail1 + 1 + mismatchLength1),
+ buffer2: [tail2 + 1, mismatchLength2],
+ buffer2Content: buffer2.slice(tail2 + 1, tail2 + 1 + mismatchLength2)
});
- nodes = nodes.slice(sortedStartEnd[0], sortedStartEnd[1] + 1);
}
-
- return nodes.map(function (n) {
- return graph.entity(n);
- });
- }
-
- function shouldKeepNode(node, graph) {
- return graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags();
}
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var nodes = allNodes(graph);
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var startPoint = points[0];
- var endPoint = points[points.length - 1];
- var toDelete = [];
- var i;
-
- for (i = 1; i < points.length - 1; i++) {
- var node = nodes[i];
- var point = points[i];
+ result.reverse();
+ return result;
+ } // We apply the LCS to build a JSON representation of a
+ // independently derived from O, returns a fairly complicated
+ // internal representation of merge decisions it's taken. The
+ // interested reader may wish to consult
+ //
+ // Sanjeev Khanna, Keshav Kunal, and Benjamin C. Pierce.
+ // 'A Formal Investigation of ' In Arvind and Prasad,
+ // editors, Foundations of Software Technology and Theoretical
+ // Computer Science (FSTTCS), December 2007.
+ //
+ // (http://www.cis.upenn.edu/~bcpierce/papers/diff3-short.pdf)
+ //
- if (t < 1 || shouldKeepNode(node, graph)) {
- var u = positionAlongWay(point, startPoint, endPoint);
- var p = geoVecInterp(startPoint, endPoint, u);
- var loc2 = projection.invert(p);
- graph = graph.replace(node.move(geoVecInterp(node.loc, loc2, t)));
- } else {
- // safe to delete
- if (toDelete.indexOf(node) === -1) {
- toDelete.push(node);
- }
- }
- }
- for (i = 0; i < toDelete.length; i++) {
- graph = actionDeleteNode(toDelete[i].id)(graph);
- }
+ function diff3MergeRegions(a, o, b) {
+ // "hunks" are array subsets where `a` or `b` are different from `o`
+ // https://www.gnu.org/software/diffutils/manual/html_node/diff3-Hunks.html
+ var hunks = [];
- return graph;
- };
+ function addHunk(h, ab) {
+ hunks.push({
+ ab: ab,
+ oStart: h.buffer1[0],
+ oLength: h.buffer1[1],
+ // length of o to remove
+ abStart: h.buffer2[0],
+ abLength: h.buffer2[1] // length of a/b to insert
+ // abContent: (ab === 'a' ? a : b).slice(h.buffer2[0], h.buffer2[0] + h.buffer2[1])
- action.disabled = function (graph) {
- // check way isn't too bendy
- var nodes = allNodes(graph);
- var points = nodes.map(function (n) {
- return projection(n.loc);
});
- var startPoint = points[0];
- var endPoint = points[points.length - 1];
- var threshold = 0.2 * geoVecLength(startPoint, endPoint);
- var i;
+ }
- if (threshold === 0) {
- return 'too_bendy';
+ diffIndices(o, a).forEach(function (item) {
+ return addHunk(item, 'a');
+ });
+ diffIndices(o, b).forEach(function (item) {
+ return addHunk(item, 'b');
+ });
+ hunks.sort(function (x, y) {
+ return x.oStart - y.oStart;
+ });
+ var results = [];
+ var currOffset = 0;
+
+ function advanceTo(endOffset) {
+ if (endOffset > currOffset) {
+ results.push({
+ stable: true,
+ buffer: 'o',
+ bufferStart: currOffset,
+ bufferLength: endOffset - currOffset,
+ bufferContent: o.slice(currOffset, endOffset)
+ });
+ currOffset = endOffset;
}
+ }
- var maxDistance = 0;
+ while (hunks.length) {
+ var hunk = hunks.shift();
+ var regionStart = hunk.oStart;
+ var regionEnd = hunk.oStart + hunk.oLength;
+ var regionHunks = [hunk];
+ advanceTo(regionStart); // Try to pull next overlapping hunk into this region
- for (i = 1; i < points.length - 1; i++) {
- var point = points[i];
- var u = positionAlongWay(point, startPoint, endPoint);
- var p = geoVecInterp(startPoint, endPoint, u);
- var dist = geoVecLength(p, point); // to bendy if point is off by 20% of total start/end distance in projected space
+ while (hunks.length) {
+ var nextHunk = hunks[0];
+ var nextHunkStart = nextHunk.oStart;
+ if (nextHunkStart > regionEnd) break; // no overlap
- if (isNaN(dist) || dist > threshold) {
- return 'too_bendy';
- } else if (dist > maxDistance) {
- maxDistance = dist;
- }
+ regionEnd = Math.max(regionEnd, nextHunkStart + nextHunk.oLength);
+ regionHunks.push(hunks.shift());
}
- var keepingAllNodes = nodes.every(function (node, i) {
- return i === 0 || i === nodes.length - 1 || shouldKeepNode(node, graph);
- });
+ if (regionHunks.length === 1) {
+ // Only one hunk touches this region, meaning that there is no conflict here.
+ // Either `a` or `b` is inserting into a region of `o` unchanged by the other.
+ if (hunk.abLength > 0) {
+ var buffer = hunk.ab === 'a' ? a : b;
+ results.push({
+ stable: true,
+ buffer: hunk.ab,
+ bufferStart: hunk.abStart,
+ bufferLength: hunk.abLength,
+ bufferContent: buffer.slice(hunk.abStart, hunk.abStart + hunk.abLength)
+ });
+ }
+ } else {
+ // A true a/b conflict. Determine the bounds involved from `a`, `o`, and `b`.
+ // Effectively merge all the `a` hunks into one giant hunk, then do the
+ // same for the `b` hunks; then, correct for skew in the regions of `o`
+ // that each side changed, and report appropriate spans for the three sides.
+ var bounds = {
+ a: [a.length, -1, o.length, -1],
+ b: [b.length, -1, o.length, -1]
+ };
- if (maxDistance < 0.0001 && // Allow straightening even if already straight in order to remove extraneous nodes
- keepingAllNodes) {
- return 'straight_enough';
+ while (regionHunks.length) {
+ hunk = regionHunks.shift();
+ var oStart = hunk.oStart;
+ var oEnd = oStart + hunk.oLength;
+ var abStart = hunk.abStart;
+ var abEnd = abStart + hunk.abLength;
+ var _b = bounds[hunk.ab];
+ _b[0] = Math.min(abStart, _b[0]);
+ _b[1] = Math.max(abEnd, _b[1]);
+ _b[2] = Math.min(oStart, _b[2]);
+ _b[3] = Math.max(oEnd, _b[3]);
+ }
+
+ var aStart = bounds.a[0] + (regionStart - bounds.a[2]);
+ var aEnd = bounds.a[1] + (regionEnd - bounds.a[3]);
+ var bStart = bounds.b[0] + (regionStart - bounds.b[2]);
+ var bEnd = bounds.b[1] + (regionEnd - bounds.b[3]);
+ var result = {
+ stable: false,
+ aStart: aStart,
+ aLength: aEnd - aStart,
+ aContent: a.slice(aStart, aEnd),
+ oStart: regionStart,
+ oLength: regionEnd - regionStart,
+ oContent: o.slice(regionStart, regionEnd),
+ bStart: bStart,
+ bLength: bEnd - bStart,
+ bContent: b.slice(bStart, bEnd)
+ };
+ results.push(result);
}
- };
- action.transitionable = true;
- return action;
- }
+ currOffset = regionEnd;
+ }
- //
- // `turn` must be an `osmTurn` object with a `restrictionID` property.
- // see osm/intersection.js, pathToTurn()
- //
+ advanceTo(o.length);
+ return results;
+ } // Applies the output of diff3MergeRegions to actually
+ // construct the merged buffer; the returned result alternates
+ // between 'ok' and 'conflict' blocks.
+ // A "false conflict" is where `a` and `b` both change the same from `o`
- function actionUnrestrictTurn(turn) {
- return function (graph) {
- return actionDeleteRelation(turn.restrictionID)(graph);
+
+ function diff3Merge(a, o, b, options) {
+ var defaults = {
+ excludeFalseConflicts: true,
+ stringSeparator: /\s+/
};
- }
+ options = Object.assign(defaults, options);
+ var aString = typeof a === 'string';
+ var oString = typeof o === 'string';
+ var bString = typeof b === 'string';
+ if (aString) a = a.split(options.stringSeparator);
+ if (oString) o = o.split(options.stringSeparator);
+ if (bString) b = b.split(options.stringSeparator);
+ var results = [];
+ var regions = diff3MergeRegions(a, o, b);
+ var okBuffer = [];
- /* Reflect the given area around its axis of symmetry */
+ function flushOk() {
+ if (okBuffer.length) {
+ results.push({
+ ok: okBuffer
+ });
+ }
- function actionReflect(reflectIds, projection) {
- var _useLongAxis = true;
+ okBuffer = [];
+ }
- var action = function action(graph, t) {
- if (t === null || !isFinite(t)) t = 1;
- t = Math.min(Math.max(+t, 0), 1);
- var nodes = utilGetAllNodes(reflectIds, graph);
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- var ssr = geoGetSmallestSurroundingRectangle(points); // Choose line pq = axis of symmetry.
- // The shape's surrounding rectangle has 2 axes of symmetry.
- // Reflect across the longer axis by default.
+ function isFalseConflict(a, b) {
+ if (a.length !== b.length) return false;
- var p1 = [(ssr.poly[0][0] + ssr.poly[1][0]) / 2, (ssr.poly[0][1] + ssr.poly[1][1]) / 2];
- var q1 = [(ssr.poly[2][0] + ssr.poly[3][0]) / 2, (ssr.poly[2][1] + ssr.poly[3][1]) / 2];
- var p2 = [(ssr.poly[3][0] + ssr.poly[4][0]) / 2, (ssr.poly[3][1] + ssr.poly[4][1]) / 2];
- var q2 = [(ssr.poly[1][0] + ssr.poly[2][0]) / 2, (ssr.poly[1][1] + ssr.poly[2][1]) / 2];
- var p, q;
- var isLong = geoVecLength(p1, q1) > geoVecLength(p2, q2);
+ for (var i = 0; i < a.length; i++) {
+ if (a[i] !== b[i]) return false;
+ }
- if (_useLongAxis && isLong || !_useLongAxis && !isLong) {
- p = p1;
- q = q1;
- } else {
- p = p2;
- q = q2;
- } // reflect c across pq
- // http://math.stackexchange.com/questions/65503/point-reflection-over-a-line
+ return true;
+ }
+ regions.forEach(function (region) {
+ if (region.stable) {
+ var _okBuffer;
- var dx = q[0] - p[0];
- var dy = q[1] - p[1];
- var a = (dx * dx - dy * dy) / (dx * dx + dy * dy);
- var b = 2 * dx * dy / (dx * dx + dy * dy);
+ (_okBuffer = okBuffer).push.apply(_okBuffer, _toConsumableArray(region.bufferContent));
+ } else {
+ if (options.excludeFalseConflicts && isFalseConflict(region.aContent, region.bContent)) {
+ var _okBuffer2;
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- var c = projection(node.loc);
- var c2 = [a * (c[0] - p[0]) + b * (c[1] - p[1]) + p[0], b * (c[0] - p[0]) - a * (c[1] - p[1]) + p[1]];
- var loc2 = projection.invert(c2);
- node = node.move(geoVecInterp(node.loc, loc2, t));
- graph = graph.replace(node);
+ (_okBuffer2 = okBuffer).push.apply(_okBuffer2, _toConsumableArray(region.aContent));
+ } else {
+ flushOk();
+ results.push({
+ conflict: {
+ a: region.aContent,
+ aIndex: region.aStart,
+ o: region.oContent,
+ oIndex: region.oStart,
+ b: region.bContent,
+ bIndex: region.bStart
+ }
+ });
+ }
}
+ });
+ flushOk();
+ return results;
+ }
- return graph;
- };
+ var lodash = {exports: {}};
- action.useLongAxis = function (val) {
- if (!arguments.length) return _useLongAxis;
- _useLongAxis = val;
- return action;
- };
+ (function (module, exports) {
+ (function () {
+ /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+ var undefined$1;
+ /** Used as the semantic version number. */
+
+ var VERSION = '4.17.21';
+ /** Used as the size to enable large array optimizations. */
+
+ var LARGE_ARRAY_SIZE = 200;
+ /** Error message constants. */
+
+ var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
+ FUNC_ERROR_TEXT = 'Expected a function',
+ INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`';
+ /** Used to stand-in for `undefined` hash values. */
+
+ var HASH_UNDEFINED = '__lodash_hash_undefined__';
+ /** Used as the maximum memoize cache size. */
+
+ var MAX_MEMOIZE_SIZE = 500;
+ /** Used as the internal argument placeholder. */
+
+ var PLACEHOLDER = '__lodash_placeholder__';
+ /** Used to compose bitmasks for cloning. */
+
+ var CLONE_DEEP_FLAG = 1,
+ CLONE_FLAT_FLAG = 2,
+ CLONE_SYMBOLS_FLAG = 4;
+ /** Used to compose bitmasks for value comparisons. */
+
+ var COMPARE_PARTIAL_FLAG = 1,
+ COMPARE_UNORDERED_FLAG = 2;
+ /** Used to compose bitmasks for function metadata. */
+
+ var WRAP_BIND_FLAG = 1,
+ WRAP_BIND_KEY_FLAG = 2,
+ WRAP_CURRY_BOUND_FLAG = 4,
+ WRAP_CURRY_FLAG = 8,
+ WRAP_CURRY_RIGHT_FLAG = 16,
+ WRAP_PARTIAL_FLAG = 32,
+ WRAP_PARTIAL_RIGHT_FLAG = 64,
+ WRAP_ARY_FLAG = 128,
+ WRAP_REARG_FLAG = 256,
+ WRAP_FLIP_FLAG = 512;
+ /** Used as default options for `_.truncate`. */
+
+ var DEFAULT_TRUNC_LENGTH = 30,
+ DEFAULT_TRUNC_OMISSION = '...';
+ /** Used to detect hot functions by number of calls within a span of milliseconds. */
+
+ var HOT_COUNT = 800,
+ HOT_SPAN = 16;
+ /** Used to indicate the type of lazy iteratees. */
+
+ var LAZY_FILTER_FLAG = 1,
+ LAZY_MAP_FLAG = 2,
+ LAZY_WHILE_FLAG = 3;
+ /** Used as references for various `Number` constants. */
+
+ var INFINITY = 1 / 0,
+ MAX_SAFE_INTEGER = 9007199254740991,
+ MAX_INTEGER = 1.7976931348623157e+308,
+ NAN = 0 / 0;
+ /** Used as references for the maximum length and index of an array. */
+
+ var MAX_ARRAY_LENGTH = 4294967295,
+ MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+ HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+ /** Used to associate wrap methods with their bit flags. */
+
+ var wrapFlags = [['ary', WRAP_ARY_FLAG], ['bind', WRAP_BIND_FLAG], ['bindKey', WRAP_BIND_KEY_FLAG], ['curry', WRAP_CURRY_FLAG], ['curryRight', WRAP_CURRY_RIGHT_FLAG], ['flip', WRAP_FLIP_FLAG], ['partial', WRAP_PARTIAL_FLAG], ['partialRight', WRAP_PARTIAL_RIGHT_FLAG], ['rearg', WRAP_REARG_FLAG]];
+ /** `Object#toString` result references. */
+
+ var argsTag = '[object Arguments]',
+ arrayTag = '[object Array]',
+ asyncTag = '[object AsyncFunction]',
+ boolTag = '[object Boolean]',
+ dateTag = '[object Date]',
+ domExcTag = '[object DOMException]',
+ errorTag = '[object Error]',
+ funcTag = '[object Function]',
+ genTag = '[object GeneratorFunction]',
+ mapTag = '[object Map]',
+ numberTag = '[object Number]',
+ nullTag = '[object Null]',
+ objectTag = '[object Object]',
+ promiseTag = '[object Promise]',
+ proxyTag = '[object Proxy]',
+ regexpTag = '[object RegExp]',
+ setTag = '[object Set]',
+ stringTag = '[object String]',
+ symbolTag = '[object Symbol]',
+ undefinedTag = '[object Undefined]',
+ weakMapTag = '[object WeakMap]',
+ weakSetTag = '[object WeakSet]';
+ var arrayBufferTag = '[object ArrayBuffer]',
+ dataViewTag = '[object DataView]',
+ float32Tag = '[object Float32Array]',
+ float64Tag = '[object Float64Array]',
+ int8Tag = '[object Int8Array]',
+ int16Tag = '[object Int16Array]',
+ int32Tag = '[object Int32Array]',
+ uint8Tag = '[object Uint8Array]',
+ uint8ClampedTag = '[object Uint8ClampedArray]',
+ uint16Tag = '[object Uint16Array]',
+ uint32Tag = '[object Uint32Array]';
+ /** Used to match empty string literals in compiled template source. */
+
+ var reEmptyStringLeading = /\b__p \+= '';/g,
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+ /** Used to match HTML entities and HTML characters. */
+
+ var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
+ reUnescapedHtml = /[&<>"']/g,
+ reHasEscapedHtml = RegExp(reEscapedHtml.source),
+ reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+ /** Used to match template delimiters. */
+
+ var reEscape = /<%-([\s\S]+?)%>/g,
+ reEvaluate = /<%([\s\S]+?)%>/g,
+ reInterpolate = /<%=([\s\S]+?)%>/g;
+ /** Used to match property names within property paths. */
+
+ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+ reIsPlainProp = /^\w*$/,
+ rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+ /**
+ * Used to match `RegExp`
+ * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+ */
- action.transitionable = true;
- return action;
- }
+ var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+ reHasRegExpChar = RegExp(reRegExpChar.source);
+ /** Used to match leading whitespace. */
- function actionUpgradeTags(entityId, oldTags, replaceTags) {
- return function (graph) {
- var entity = graph.entity(entityId);
- var tags = Object.assign({}, entity.tags); // shallow copy
+ var reTrimStart = /^\s+/;
+ /** Used to match a single whitespace character. */
- var transferValue;
- var semiIndex;
+ var reWhitespace = /\s/;
+ /** Used to match wrap detail comments. */
- for (var oldTagKey in oldTags) {
- if (!(oldTagKey in tags)) continue; // wildcard match
+ var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
+ reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
+ reSplitDetails = /,? & /;
+ /** Used to match words composed of alphanumeric characters. */
- if (oldTags[oldTagKey] === '*') {
- // note the value since we might need to transfer it
- transferValue = tags[oldTagKey];
- delete tags[oldTagKey]; // exact match
- } else if (oldTags[oldTagKey] === tags[oldTagKey]) {
- delete tags[oldTagKey]; // match is within semicolon-delimited values
- } else {
- var vals = tags[oldTagKey].split(';').filter(Boolean);
- var oldIndex = vals.indexOf(oldTags[oldTagKey]);
+ var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
+ /**
+ * Used to validate the `validate` option in `_.template` variable.
+ *
+ * Forbids characters which could potentially change the meaning of the function argument definition:
+ * - "()," (modification of function parameters)
+ * - "=" (default value)
+ * - "[]{}" (destructuring of function parameters)
+ * - "/" (beginning of a comment)
+ * - whitespace
+ */
- if (vals.length === 1 || oldIndex === -1) {
- delete tags[oldTagKey];
- } else {
- if (replaceTags && replaceTags[oldTagKey]) {
- // replacing a value within a semicolon-delimited value, note the index
- semiIndex = oldIndex;
- }
+ var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/;
+ /** Used to match backslashes in property paths. */
- vals.splice(oldIndex, 1);
- tags[oldTagKey] = vals.join(';');
- }
- }
- }
+ var reEscapeChar = /\\(\\)?/g;
+ /**
+ * Used to match
+ * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
+ */
- if (replaceTags) {
- for (var replaceKey in replaceTags) {
- var replaceValue = replaceTags[replaceKey];
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+ /** Used to match `RegExp` flags from their coerced string values. */
+
+ var reFlags = /\w*$/;
+ /** Used to detect bad signed hexadecimal string values. */
+
+ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+ /** Used to detect binary string values. */
+
+ var reIsBinary = /^0b[01]+$/i;
+ /** Used to detect host constructors (Safari). */
+
+ var reIsHostCtor = /^\[object .+?Constructor\]$/;
+ /** Used to detect octal string values. */
+
+ var reIsOctal = /^0o[0-7]+$/i;
+ /** Used to detect unsigned integer values. */
+
+ var reIsUint = /^(?:0|[1-9]\d*)$/;
+ /** Used to match Latin Unicode letters (excluding mathematical operators). */
+
+ var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
+ /** Used to ensure capturing order of template delimiters. */
+
+ var reNoMatch = /($^)/;
+ /** Used to match unescaped characters in compiled string literals. */
+
+ var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+ /** Used to compose unicode character classes. */
+
+ var rsAstralRange = "\\ud800-\\udfff",
+ rsComboMarksRange = "\\u0300-\\u036f",
+ reComboHalfMarksRange = "\\ufe20-\\ufe2f",
+ rsComboSymbolsRange = "\\u20d0-\\u20ff",
+ rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
+ rsDingbatRange = "\\u2700-\\u27bf",
+ rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+ rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+ rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+ rsPunctuationRange = "\\u2000-\\u206f",
+ rsSpaceRange = " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",
+ rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+ rsVarRange = "\\ufe0e\\ufe0f",
+ rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+ /** Used to compose unicode capture groups. */
+
+ var rsApos = "['\u2019]",
+ rsAstral = '[' + rsAstralRange + ']',
+ rsBreak = '[' + rsBreakRange + ']',
+ rsCombo = '[' + rsComboRange + ']',
+ rsDigits = '\\d+',
+ rsDingbat = '[' + rsDingbatRange + ']',
+ rsLower = '[' + rsLowerRange + ']',
+ rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+ rsFitz = "\\ud83c[\\udffb-\\udfff]",
+ rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+ rsNonAstral = '[^' + rsAstralRange + ']',
+ rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}",
+ rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]",
+ rsUpper = '[' + rsUpperRange + ']',
+ rsZWJ = "\\u200d";
+ /** Used to compose unicode regexes. */
+
+ var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
+ rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
+ rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+ rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+ reOptMod = rsModifier + '?',
+ rsOptVar = '[' + rsVarRange + ']?',
+ rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+ rsOrdLower = '\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])',
+ rsOrdUpper = '\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])',
+ rsSeq = rsOptVar + reOptMod + rsOptJoin,
+ rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+ rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+ /** Used to match apostrophes. */
+
+ var reApos = RegExp(rsApos, 'g');
+ /**
+ * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+ * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+ */
- if (replaceValue === '*') {
- if (tags[replaceKey] && tags[replaceKey] !== 'no') {
- // allow any pre-existing value except `no` (troll tag)
- continue;
- } else {
- // otherwise assume `yes` is okay
- tags[replaceKey] = 'yes';
- }
- } else if (replaceValue === '$1') {
- tags[replaceKey] = transferValue;
- } else {
- if (tags[replaceKey] && oldTags[replaceKey] && semiIndex !== undefined) {
- // don't override preexisting values
- var existingVals = tags[replaceKey].split(';').filter(Boolean);
+ var reComboMark = RegExp(rsCombo, 'g');
+ /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+
+ var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+ /** Used to match complex or compound words. */
+
+ var reUnicodeWord = RegExp([rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')', rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')', rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower, rsUpper + '+' + rsOptContrUpper, rsOrdUpper, rsOrdLower, rsDigits, rsEmoji].join('|'), 'g');
+ /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+
+ var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
+ /** Used to detect strings that need a more robust regexp to match words. */
+
+ var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+ /** Used to assign default `context` object properties. */
+
+ var contextProps = ['Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array', 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object', 'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap', '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'];
+ /** Used to make template sourceURLs easier to identify. */
+
+ var templateCounter = -1;
+ /** Used to identify `toStringTag` values of typed arrays. */
+
+ var typedArrayTags = {};
+ typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true;
+ typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
+ /** Used to identify `toStringTag` values supported by `_.clone`. */
+
+ var cloneableTags = {};
+ cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+ cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false;
+ /** Used to map Latin Unicode letters to basic Latin letters. */
+
+ var deburredLetters = {
+ // Latin-1 Supplement block.
+ '\xc0': 'A',
+ '\xc1': 'A',
+ '\xc2': 'A',
+ '\xc3': 'A',
+ '\xc4': 'A',
+ '\xc5': 'A',
+ '\xe0': 'a',
+ '\xe1': 'a',
+ '\xe2': 'a',
+ '\xe3': 'a',
+ '\xe4': 'a',
+ '\xe5': 'a',
+ '\xc7': 'C',
+ '\xe7': 'c',
+ '\xd0': 'D',
+ '\xf0': 'd',
+ '\xc8': 'E',
+ '\xc9': 'E',
+ '\xca': 'E',
+ '\xcb': 'E',
+ '\xe8': 'e',
+ '\xe9': 'e',
+ '\xea': 'e',
+ '\xeb': 'e',
+ '\xcc': 'I',
+ '\xcd': 'I',
+ '\xce': 'I',
+ '\xcf': 'I',
+ '\xec': 'i',
+ '\xed': 'i',
+ '\xee': 'i',
+ '\xef': 'i',
+ '\xd1': 'N',
+ '\xf1': 'n',
+ '\xd2': 'O',
+ '\xd3': 'O',
+ '\xd4': 'O',
+ '\xd5': 'O',
+ '\xd6': 'O',
+ '\xd8': 'O',
+ '\xf2': 'o',
+ '\xf3': 'o',
+ '\xf4': 'o',
+ '\xf5': 'o',
+ '\xf6': 'o',
+ '\xf8': 'o',
+ '\xd9': 'U',
+ '\xda': 'U',
+ '\xdb': 'U',
+ '\xdc': 'U',
+ '\xf9': 'u',
+ '\xfa': 'u',
+ '\xfb': 'u',
+ '\xfc': 'u',
+ '\xdd': 'Y',
+ '\xfd': 'y',
+ '\xff': 'y',
+ '\xc6': 'Ae',
+ '\xe6': 'ae',
+ '\xde': 'Th',
+ '\xfe': 'th',
+ '\xdf': 'ss',
+ // Latin Extended-A block.
+ "\u0100": 'A',
+ "\u0102": 'A',
+ "\u0104": 'A',
+ "\u0101": 'a',
+ "\u0103": 'a',
+ "\u0105": 'a',
+ "\u0106": 'C',
+ "\u0108": 'C',
+ "\u010A": 'C',
+ "\u010C": 'C',
+ "\u0107": 'c',
+ "\u0109": 'c',
+ "\u010B": 'c',
+ "\u010D": 'c',
+ "\u010E": 'D',
+ "\u0110": 'D',
+ "\u010F": 'd',
+ "\u0111": 'd',
+ "\u0112": 'E',
+ "\u0114": 'E',
+ "\u0116": 'E',
+ "\u0118": 'E',
+ "\u011A": 'E',
+ "\u0113": 'e',
+ "\u0115": 'e',
+ "\u0117": 'e',
+ "\u0119": 'e',
+ "\u011B": 'e',
+ "\u011C": 'G',
+ "\u011E": 'G',
+ "\u0120": 'G',
+ "\u0122": 'G',
+ "\u011D": 'g',
+ "\u011F": 'g',
+ "\u0121": 'g',
+ "\u0123": 'g',
+ "\u0124": 'H',
+ "\u0126": 'H',
+ "\u0125": 'h',
+ "\u0127": 'h',
+ "\u0128": 'I',
+ "\u012A": 'I',
+ "\u012C": 'I',
+ "\u012E": 'I',
+ "\u0130": 'I',
+ "\u0129": 'i',
+ "\u012B": 'i',
+ "\u012D": 'i',
+ "\u012F": 'i',
+ "\u0131": 'i',
+ "\u0134": 'J',
+ "\u0135": 'j',
+ "\u0136": 'K',
+ "\u0137": 'k',
+ "\u0138": 'k',
+ "\u0139": 'L',
+ "\u013B": 'L',
+ "\u013D": 'L',
+ "\u013F": 'L',
+ "\u0141": 'L',
+ "\u013A": 'l',
+ "\u013C": 'l',
+ "\u013E": 'l',
+ "\u0140": 'l',
+ "\u0142": 'l',
+ "\u0143": 'N',
+ "\u0145": 'N',
+ "\u0147": 'N',
+ "\u014A": 'N',
+ "\u0144": 'n',
+ "\u0146": 'n',
+ "\u0148": 'n',
+ "\u014B": 'n',
+ "\u014C": 'O',
+ "\u014E": 'O',
+ "\u0150": 'O',
+ "\u014D": 'o',
+ "\u014F": 'o',
+ "\u0151": 'o',
+ "\u0154": 'R',
+ "\u0156": 'R',
+ "\u0158": 'R',
+ "\u0155": 'r',
+ "\u0157": 'r',
+ "\u0159": 'r',
+ "\u015A": 'S',
+ "\u015C": 'S',
+ "\u015E": 'S',
+ "\u0160": 'S',
+ "\u015B": 's',
+ "\u015D": 's',
+ "\u015F": 's',
+ "\u0161": 's',
+ "\u0162": 'T',
+ "\u0164": 'T',
+ "\u0166": 'T',
+ "\u0163": 't',
+ "\u0165": 't',
+ "\u0167": 't',
+ "\u0168": 'U',
+ "\u016A": 'U',
+ "\u016C": 'U',
+ "\u016E": 'U',
+ "\u0170": 'U',
+ "\u0172": 'U',
+ "\u0169": 'u',
+ "\u016B": 'u',
+ "\u016D": 'u',
+ "\u016F": 'u',
+ "\u0171": 'u',
+ "\u0173": 'u',
+ "\u0174": 'W',
+ "\u0175": 'w',
+ "\u0176": 'Y',
+ "\u0177": 'y',
+ "\u0178": 'Y',
+ "\u0179": 'Z',
+ "\u017B": 'Z',
+ "\u017D": 'Z',
+ "\u017A": 'z',
+ "\u017C": 'z',
+ "\u017E": 'z',
+ "\u0132": 'IJ',
+ "\u0133": 'ij',
+ "\u0152": 'Oe',
+ "\u0153": 'oe',
+ "\u0149": "'n",
+ "\u017F": 's'
+ };
+ /** Used to map characters to HTML entities. */
+
+ var htmlEscapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+ };
+ /** Used to map HTML entities to characters. */
+
+ var htmlUnescapes = {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ ''': "'"
+ };
+ /** Used to escape characters for inclusion in compiled string literals. */
+
+ var stringEscapes = {
+ '\\': '\\',
+ "'": "'",
+ '\n': 'n',
+ '\r': 'r',
+ "\u2028": 'u2028',
+ "\u2029": 'u2029'
+ };
+ /** Built-in method references without a dependency on `root`. */
- if (existingVals.indexOf(replaceValue) === -1) {
- existingVals.splice(semiIndex, 0, replaceValue);
- tags[replaceKey] = existingVals.join(';');
- }
- } else {
- tags[replaceKey] = replaceValue;
- }
- }
- }
- }
+ var freeParseFloat = parseFloat,
+ freeParseInt = parseInt;
+ /** Detect free variable `global` from Node.js. */
- return graph.replace(entity.update({
- tags: tags
- }));
- };
- }
+ var freeGlobal = _typeof(commonjsGlobal) == 'object' && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal;
+ /** Detect free variable `self`. */
- function behaviorEdit(context) {
- function behavior() {
- context.map().minzoom(context.minEditableZoom());
- }
+ var freeSelf = (typeof self === "undefined" ? "undefined" : _typeof(self)) == 'object' && self && self.Object === Object && self;
+ /** Used as a reference to the global object. */
- behavior.off = function () {
- context.map().minzoom(0);
- };
+ var root = freeGlobal || freeSelf || Function('return this')();
+ /** Detect free variable `exports`. */
- return behavior;
- }
+ var freeExports = exports && !exports.nodeType && exports;
+ /** Detect free variable `module`. */
- /*
- The hover behavior adds the `.hover` class on pointerover to all elements to which
- the identical datum is bound, and removes it on pointerout.
+ var freeModule = freeExports && 'object' == 'object' && module && !module.nodeType && module;
+ /** Detect the popular CommonJS extension `module.exports`. */
- The :hover pseudo-class is insufficient for iD's purposes because a datum's visual
- representation may consist of several elements scattered throughout the DOM hierarchy.
- Only one of these elements can have the :hover pseudo-class, but all of them will
- have the .hover class.
- */
+ var moduleExports = freeModule && freeModule.exports === freeExports;
+ /** Detect free variable `process` from Node.js. */
- function behaviorHover(context) {
- var dispatch = dispatch$8('hover');
+ var freeProcess = moduleExports && freeGlobal.process;
+ /** Used to access faster Node.js helpers. */
- var _selection = select(null);
+ var nodeUtil = function () {
+ try {
+ // Use `util.types` for Node.js 10+.
+ var types = freeModule && freeModule.require && freeModule.require('util').types;
- var _newNodeId = null;
- var _initialNodeID = null;
+ if (types) {
+ return types;
+ } // Legacy `process.binding('util')` for Node.js < 10.
- var _altDisables;
- var _ignoreVertex;
+ return freeProcess && freeProcess.binding && freeProcess.binding('util');
+ } catch (e) {}
+ }();
+ /* Node.js helper references. */
- var _targets = []; // use pointer events on supported platforms; fallback to mouse events
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
+ nodeIsDate = nodeUtil && nodeUtil.isDate,
+ nodeIsMap = nodeUtil && nodeUtil.isMap,
+ nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
+ nodeIsSet = nodeUtil && nodeUtil.isSet,
+ nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
+ /*--------------------------------------------------------------------------*/
- function keydown(d3_event) {
- if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- _selection.selectAll('.hover').classed('hover-suppressed', true).classed('hover', false);
+ /**
+ * A faster alternative to `Function#apply`, this function invokes `func`
+ * with the `this` binding of `thisArg` and the arguments of `args`.
+ *
+ * @private
+ * @param {Function} func The function to invoke.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} args The arguments to invoke `func` with.
+ * @returns {*} Returns the result of `func`.
+ */
- _selection.classed('hover-disabled', true);
+ function apply(func, thisArg, args) {
+ switch (args.length) {
+ case 0:
+ return func.call(thisArg);
- dispatch.call('hover', this, null);
- }
- }
+ case 1:
+ return func.call(thisArg, args[0]);
- function keyup(d3_event) {
- if (_altDisables && d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false).classed('hover', true);
+ case 2:
+ return func.call(thisArg, args[0], args[1]);
- _selection.classed('hover-disabled', false);
+ case 3:
+ return func.call(thisArg, args[0], args[1], args[2]);
+ }
- dispatch.call('hover', this, _targets);
+ return func.apply(thisArg, args);
}
- }
+ /**
+ * A specialized version of `baseAggregator` for arrays.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
- function behavior(selection) {
- _selection = selection;
- _targets = [];
- if (_initialNodeID) {
- _newNodeId = _initialNodeID;
- _initialNodeID = null;
- } else {
- _newNodeId = null;
- }
+ function arrayAggregator(array, setter, iteratee, accumulator) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- _selection.on(_pointerPrefix + 'over.hover', pointerover).on(_pointerPrefix + 'out.hover', pointerout) // treat pointerdown as pointerover for touch devices
- .on(_pointerPrefix + 'down.hover', pointerover);
+ while (++index < length) {
+ var value = array[index];
+ setter(accumulator, value, iteratee(value), array);
+ }
- select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', pointerout, true).on('keydown.hover', keydown).on('keyup.hover', keyup);
+ return accumulator;
+ }
+ /**
+ * A specialized version of `_.forEach` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
- function eventTarget(d3_event) {
- var datum = d3_event.target && d3_event.target.__data__;
- if (_typeof(datum) !== 'object') return null;
- if (!(datum instanceof osmEntity) && datum.properties && datum.properties.entity instanceof osmEntity) {
- return datum.properties.entity;
+ function arrayEach(array, iteratee) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
+
+ while (++index < length) {
+ if (iteratee(array[index], index, array) === false) {
+ break;
+ }
}
- return datum;
+ return array;
}
+ /**
+ * A specialized version of `_.forEachRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns `array`.
+ */
- function pointerover(d3_event) {
- // ignore mouse hovers with buttons pressed unless dragging
- if (context.mode().id.indexOf('drag') === -1 && (!d3_event.pointerType || d3_event.pointerType === 'mouse') && d3_event.buttons) return;
- var target = eventTarget(d3_event);
- if (target && _targets.indexOf(target) === -1) {
- _targets.push(target);
+ function arrayEachRight(array, iteratee) {
+ var length = array == null ? 0 : array.length;
- updateHover(d3_event, _targets);
+ while (length--) {
+ if (iteratee(array[length], length, array) === false) {
+ break;
+ }
}
- }
- function pointerout(d3_event) {
- var target = eventTarget(d3_event);
+ return array;
+ }
+ /**
+ * A specialized version of `_.every` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`.
+ */
- var index = _targets.indexOf(target);
- if (index !== -1) {
- _targets.splice(index);
+ function arrayEvery(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- updateHover(d3_event, _targets);
+ while (++index < length) {
+ if (!predicate(array[index], index, array)) {
+ return false;
+ }
}
- }
- function allowsVertex(d) {
- return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
+ return true;
}
+ /**
+ * A specialized version of `_.filter` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
- function modeAllowsHover(target) {
- var mode = context.mode();
- if (mode.id === 'add-point') {
- return mode.preset.matchGeometry('vertex') || target.type !== 'way' && target.geometry(context.graph()) !== 'vertex';
+ function arrayFilter(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (predicate(value, index, array)) {
+ result[resIndex++] = value;
+ }
}
- return true;
+ return result;
}
+ /**
+ * A specialized version of `_.includes` for arrays without support for
+ * specifying an index to search from.
+ *
+ * @private
+ * @param {Array} [array] The array to inspect.
+ * @param {*} target The value to search for.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
- function updateHover(d3_event, targets) {
- _selection.selectAll('.hover').classed('hover', false);
- _selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
+ function arrayIncludes(array, value) {
+ var length = array == null ? 0 : array.length;
+ return !!length && baseIndexOf(array, value, 0) > -1;
+ }
+ /**
+ * This function is like `arrayIncludes` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} [array] The array to inspect.
+ * @param {*} target The value to search for.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {boolean} Returns `true` if `target` is found, else `false`.
+ */
- var mode = context.mode();
- if (!_newNodeId && (mode.id === 'draw-line' || mode.id === 'draw-area')) {
- var node = targets.find(function (target) {
- return target instanceof osmEntity && target.type === 'node';
- });
- _newNodeId = node && node.id;
- }
+ function arrayIncludesWith(array, value, comparator) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- targets = targets.filter(function (datum) {
- if (datum instanceof osmEntity) {
- // If drawing a way, don't hover on a node that was just placed. #3974
- return datum.id !== _newNodeId && (datum.type !== 'node' || !_ignoreVertex || allowsVertex(datum)) && modeAllowsHover(datum);
+ while (++index < length) {
+ if (comparator(value, array[index])) {
+ return true;
}
+ }
- return true;
- });
- var selector = '';
+ return false;
+ }
+ /**
+ * A specialized version of `_.map` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
- for (var i in targets) {
- var datum = targets[i]; // What are we hovering over?
- if (datum.__featurehash__) {
- // hovering custom data
- selector += ', .data' + datum.__featurehash__;
- } else if (datum instanceof QAItem) {
- selector += ', .' + datum.service + '.itemId-' + datum.id;
- } else if (datum instanceof osmNote) {
- selector += ', .note-' + datum.id;
- } else if (datum instanceof osmEntity) {
- selector += ', .' + datum.id;
+ function arrayMap(array, iteratee) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ result = Array(length);
- if (datum.type === 'relation') {
- for (var j in datum.members) {
- selector += ', .' + datum.members[j].id;
- }
- }
- }
+ while (++index < length) {
+ result[index] = iteratee(array[index], index, array);
}
- var suppressed = _altDisables && d3_event && d3_event.altKey;
+ return result;
+ }
+ /**
+ * Appends the elements of `values` to `array`.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to append.
+ * @returns {Array} Returns `array`.
+ */
- if (selector.trim().length) {
- // remove the first comma
- selector = selector.slice(1);
- _selection.selectAll(selector).classed(suppressed ? 'hover-suppressed' : 'hover', true);
+ function arrayPush(array, values) {
+ var index = -1,
+ length = values.length,
+ offset = array.length;
+
+ while (++index < length) {
+ array[offset + index] = values[index];
}
- dispatch.call('hover', this, !suppressed && targets);
+ return array;
}
- }
+ /**
+ * A specialized version of `_.reduce` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the first element of `array` as
+ * the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
- behavior.off = function (selection) {
- selection.selectAll('.hover').classed('hover', false);
- selection.selectAll('.hover-suppressed').classed('hover-suppressed', false);
- selection.classed('hover-disabled', false);
- selection.on(_pointerPrefix + 'over.hover', null).on(_pointerPrefix + 'out.hover', null).on(_pointerPrefix + 'down.hover', null);
- select(window).on(_pointerPrefix + 'up.hover pointercancel.hover', null, true).on('keydown.hover', null).on('keyup.hover', null);
- };
- behavior.altDisables = function (val) {
- if (!arguments.length) return _altDisables;
- _altDisables = val;
- return behavior;
- };
+ function arrayReduce(array, iteratee, accumulator, initAccum) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- behavior.ignoreVertex = function (val) {
- if (!arguments.length) return _ignoreVertex;
- _ignoreVertex = val;
- return behavior;
- };
+ if (initAccum && length) {
+ accumulator = array[++index];
+ }
- behavior.initialNodeID = function (nodeId) {
- _initialNodeID = nodeId;
- return behavior;
- };
+ while (++index < length) {
+ accumulator = iteratee(accumulator, array[index], index, array);
+ }
- return utilRebind(behavior, dispatch, 'on');
- }
+ return accumulator;
+ }
+ /**
+ * A specialized version of `_.reduceRight` for arrays without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @param {boolean} [initAccum] Specify using the last element of `array` as
+ * the initial value.
+ * @returns {*} Returns the accumulated value.
+ */
- var _disableSpace = false;
- var _lastSpace = null;
- function behaviorDraw(context) {
- var dispatch = dispatch$8('move', 'down', 'downcancel', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish');
- var keybinding = utilKeybinding('draw');
- var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true).on('hover', context.ui().sidebar.hover);
+ function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+ var length = array == null ? 0 : array.length;
- var _edit = behaviorEdit(context);
+ if (initAccum && length) {
+ accumulator = array[--length];
+ }
- var _closeTolerance = 4;
- var _tolerance = 12;
- var _mouseLeave = false;
- var _lastMouse = null;
+ while (length--) {
+ accumulator = iteratee(accumulator, array[length], length, array);
+ }
- var _lastPointerUpEvent;
+ return accumulator;
+ }
+ /**
+ * A specialized version of `_.some` for arrays without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} [array] The array to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ */
- var _downPointer; // use pointer events on supported platforms; fallback to mouse events
+ function arraySome(array, predicate) {
+ var index = -1,
+ length = array == null ? 0 : array.length;
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; // related code
- // - `mode/drag_node.js` `datum()`
+ while (++index < length) {
+ if (predicate(array[index], index, array)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ /**
+ * Gets the size of an ASCII `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
- function datum(d3_event) {
- var mode = context.mode();
- var isNote = mode && mode.id.indexOf('note') !== -1;
- if (d3_event.altKey || isNote) return {};
- var element;
- if (d3_event.type === 'keydown') {
- element = _lastMouse && _lastMouse.target;
- } else {
- element = d3_event.target;
- } // When drawing, snap only to touch targets..
- // (this excludes area fills and active drawing elements)
+ var asciiSize = baseProperty('length');
+ /**
+ * Converts an ASCII `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
+ function asciiToArray(string) {
+ return string.split('');
+ }
+ /**
+ * Splits an ASCII `string` into an array of its words.
+ *
+ * @private
+ * @param {string} The string to inspect.
+ * @returns {Array} Returns the words of `string`.
+ */
- var d = element.__data__;
- return d && d.properties && d.properties.target ? d : {};
- }
- function pointerdown(d3_event) {
- if (_downPointer) return;
- var pointerLocGetter = utilFastMouse(this);
- _downPointer = {
- id: d3_event.pointerId || 'mouse',
- pointerLocGetter: pointerLocGetter,
- downTime: +new Date(),
- downLoc: pointerLocGetter(d3_event)
- };
- dispatch.call('down', this, d3_event, datum(d3_event));
- }
+ function asciiWords(string) {
+ return string.match(reAsciiWord) || [];
+ }
+ /**
+ * The base implementation of methods like `_.findKey` and `_.findLastKey`,
+ * without support for iteratee shorthands, which iterates over `collection`
+ * using `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @returns {*} Returns the found element or its key, else `undefined`.
+ */
- function pointerup(d3_event) {
- if (!_downPointer || _downPointer.id !== (d3_event.pointerId || 'mouse')) return;
- var downPointer = _downPointer;
- _downPointer = null;
- _lastPointerUpEvent = d3_event;
- if (downPointer.isCancelled) return;
- var t2 = +new Date();
- var p2 = downPointer.pointerLocGetter(d3_event);
- var dist = geoVecLength(downPointer.downLoc, p2);
- if (dist < _closeTolerance || dist < _tolerance && t2 - downPointer.downTime < 500) {
- // Prevent a quick second click
- select(window).on('click.draw-block', function () {
- d3_event.stopPropagation();
- }, true);
- context.map().dblclickZoomEnable(false);
- window.setTimeout(function () {
- context.map().dblclickZoomEnable(true);
- select(window).on('click.draw-block', null);
- }, 500);
- click(d3_event, p2);
+ function baseFindKey(collection, predicate, eachFunc) {
+ var result;
+ eachFunc(collection, function (value, key, collection) {
+ if (predicate(value, key, collection)) {
+ result = key;
+ return false;
+ }
+ });
+ return result;
}
- }
+ /**
+ * The base implementation of `_.findIndex` and `_.findLastIndex` without
+ * support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {number} fromIndex The index to search from.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- function pointermove(d3_event) {
- if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse') && !_downPointer.isCancelled) {
- var p2 = _downPointer.pointerLocGetter(d3_event);
- var dist = geoVecLength(_downPointer.downLoc, p2);
+ function baseFindIndex(array, predicate, fromIndex, fromRight) {
+ var length = array.length,
+ index = fromIndex + (fromRight ? 1 : -1);
- if (dist >= _closeTolerance) {
- _downPointer.isCancelled = true;
- dispatch.call('downcancel', this);
+ while (fromRight ? index-- : ++index < length) {
+ if (predicate(array[index], index, array)) {
+ return index;
+ }
}
+
+ return -1;
}
+ /**
+ * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- if (d3_event.pointerType && d3_event.pointerType !== 'mouse' || d3_event.buttons || _downPointer) return; // HACK: Mobile Safari likes to send one or more `mouse` type pointermove
- // events immediately after non-mouse pointerup events; detect and ignore them.
- if (_lastPointerUpEvent && _lastPointerUpEvent.pointerType !== 'mouse' && d3_event.timeStamp - _lastPointerUpEvent.timeStamp < 100) return;
- _lastMouse = d3_event;
- dispatch.call('move', this, d3_event, datum(d3_event));
- }
+ function baseIndexOf(array, value, fromIndex) {
+ return value === value ? strictIndexOf(array, value, fromIndex) : baseFindIndex(array, baseIsNaN, fromIndex);
+ }
+ /**
+ * This function is like `baseIndexOf` except that it accepts a comparator.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @param {Function} comparator The comparator invoked per element.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- function pointercancel(d3_event) {
- if (_downPointer && _downPointer.id === (d3_event.pointerId || 'mouse')) {
- if (!_downPointer.isCancelled) {
- dispatch.call('downcancel', this);
+
+ function baseIndexOfWith(array, value, fromIndex, comparator) {
+ var index = fromIndex - 1,
+ length = array.length;
+
+ while (++index < length) {
+ if (comparator(array[index], value)) {
+ return index;
+ }
}
- _downPointer = null;
+ return -1;
}
- }
-
- function mouseenter() {
- _mouseLeave = false;
- }
+ /**
+ * The base implementation of `_.isNaN` without support for number objects.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+ */
- function mouseleave() {
- _mouseLeave = true;
- }
- function allowsVertex(d) {
- return d.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(d, context.graph());
- } // related code
- // - `mode/drag_node.js` `doMove()`
- // - `behavior/draw.js` `click()`
- // - `behavior/draw_way.js` `move()`
+ function baseIsNaN(value) {
+ return value !== value;
+ }
+ /**
+ * The base implementation of `_.mean` and `_.meanBy` without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the mean.
+ */
- function click(d3_event, loc) {
- var d = datum(d3_event);
- var target = d && d.properties && d.properties.entity;
- var mode = context.mode();
+ function baseMean(array, iteratee) {
+ var length = array == null ? 0 : array.length;
+ return length ? baseSum(array, iteratee) / length : NAN;
+ }
+ /**
+ * The base implementation of `_.property` without support for deep paths.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ */
- if (target && target.type === 'node' && allowsVertex(target)) {
- // Snap to a node
- dispatch.call('clickNode', this, target, d);
- return;
- } else if (target && target.type === 'way' && (mode.id !== 'add-point' || mode.preset.matchGeometry('vertex'))) {
- // Snap to a way
- var choice = geoChooseEdge(context.graph().childNodes(target), loc, context.projection, context.activeID());
- if (choice) {
- var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]];
- dispatch.call('clickWay', this, choice.loc, edge, d);
- return;
- }
- } else if (mode.id !== 'add-point' || mode.preset.matchGeometry('point')) {
- var locLatLng = context.projection.invert(loc);
- dispatch.call('click', this, locLatLng, d);
+ function baseProperty(key) {
+ return function (object) {
+ return object == null ? undefined$1 : object[key];
+ };
}
- } // treat a spacebar press like a click
+ /**
+ * The base implementation of `_.propertyOf` without support for deep paths.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Function} Returns the new accessor function.
+ */
- function space(d3_event) {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- var currSpace = context.map().mouse();
+ function basePropertyOf(object) {
+ return function (key) {
+ return object == null ? undefined$1 : object[key];
+ };
+ }
+ /**
+ * The base implementation of `_.reduce` and `_.reduceRight`, without support
+ * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {*} accumulator The initial value.
+ * @param {boolean} initAccum Specify using the first or last element of
+ * `collection` as the initial value.
+ * @param {Function} eachFunc The function to iterate over `collection`.
+ * @returns {*} Returns the accumulated value.
+ */
- if (_disableSpace && _lastSpace) {
- var dist = geoVecLength(_lastSpace, currSpace);
- if (dist > _tolerance) {
- _disableSpace = false;
- }
+ function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+ eachFunc(collection, function (value, index, collection) {
+ accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index, collection);
+ });
+ return accumulator;
}
+ /**
+ * The base implementation of `_.sortBy` which uses `comparer` to define the
+ * sort order of `array` and replaces criteria objects with their corresponding
+ * values.
+ *
+ * @private
+ * @param {Array} array The array to sort.
+ * @param {Function} comparer The function to define sort order.
+ * @returns {Array} Returns `array`.
+ */
- if (_disableSpace || _mouseLeave || !_lastMouse) return; // user must move mouse or release space bar to allow another click
-
- _lastSpace = currSpace;
- _disableSpace = true;
- select(window).on('keyup.space-block', function () {
- d3_event.preventDefault();
- d3_event.stopPropagation();
- _disableSpace = false;
- select(window).on('keyup.space-block', null);
- }); // get the current mouse position
- var loc = context.map().mouse() || // or the map center if the mouse has never entered the map
- context.projection(context.map().center());
- click(d3_event, loc);
- }
+ function baseSortBy(array, comparer) {
+ var length = array.length;
+ array.sort(comparer);
- function backspace(d3_event) {
- d3_event.preventDefault();
- dispatch.call('undo');
- }
+ while (length--) {
+ array[length] = array[length].value;
+ }
- function del(d3_event) {
- d3_event.preventDefault();
- dispatch.call('cancel');
- }
+ return array;
+ }
+ /**
+ * The base implementation of `_.sum` and `_.sumBy` without support for
+ * iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {number} Returns the sum.
+ */
- function ret(d3_event) {
- d3_event.preventDefault();
- dispatch.call('finish');
- }
- function behavior(selection) {
- context.install(_hover);
- context.install(_edit);
- _downPointer = null;
- keybinding.on('â«', backspace).on('â¦', del).on('â', ret).on('â©', ret).on('space', space).on('â¥space', space);
- selection.on('mouseenter.draw', mouseenter).on('mouseleave.draw', mouseleave).on(_pointerPrefix + 'down.draw', pointerdown).on(_pointerPrefix + 'move.draw', pointermove);
- select(window).on(_pointerPrefix + 'up.draw', pointerup, true).on('pointercancel.draw', pointercancel, true);
- select(document).call(keybinding);
- return behavior;
- }
+ function baseSum(array, iteratee) {
+ var result,
+ index = -1,
+ length = array.length;
- behavior.off = function (selection) {
- context.ui().sidebar.hover.cancel();
- context.uninstall(_hover);
- context.uninstall(_edit);
- selection.on('mouseenter.draw', null).on('mouseleave.draw', null).on(_pointerPrefix + 'down.draw', null).on(_pointerPrefix + 'move.draw', null);
- select(window).on(_pointerPrefix + 'up.draw', null).on('pointercancel.draw', null); // note: keyup.space-block, click.draw-block should remain
+ while (++index < length) {
+ var current = iteratee(array[index]);
- select(document).call(keybinding.unbind);
- };
+ if (current !== undefined$1) {
+ result = result === undefined$1 ? current : result + current;
+ }
+ }
- behavior.hover = function () {
- return _hover;
- };
+ return result;
+ }
+ /**
+ * The base implementation of `_.times` without support for iteratee shorthands
+ * or max array length checks.
+ *
+ * @private
+ * @param {number} n The number of times to invoke `iteratee`.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the array of results.
+ */
- return utilRebind(behavior, dispatch, 'on');
- }
- function initRange(domain, range) {
- switch (arguments.length) {
- case 0:
- break;
+ function baseTimes(n, iteratee) {
+ var index = -1,
+ result = Array(n);
- case 1:
- this.range(domain);
- break;
+ while (++index < n) {
+ result[index] = iteratee(index);
+ }
- default:
- this.range(range).domain(domain);
- break;
- }
+ return result;
+ }
+ /**
+ * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+ * of key-value pairs for `object` corresponding to the property names of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the key-value pairs.
+ */
- return this;
- }
- function constants(x) {
- return function () {
- return x;
- };
- }
+ function baseToPairs(object, props) {
+ return arrayMap(props, function (key) {
+ return [key, object[key]];
+ });
+ }
+ /**
+ * The base implementation of `_.trim`.
+ *
+ * @private
+ * @param {string} string The string to trim.
+ * @returns {string} Returns the trimmed string.
+ */
- function number(x) {
- return +x;
- }
- var unit = [0, 1];
- function identity$1(x) {
- return x;
- }
+ function baseTrim(string) {
+ return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string;
+ }
+ /**
+ * The base implementation of `_.unary` without support for storing metadata.
+ *
+ * @private
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new capped function.
+ */
- function normalize(a, b) {
- return (b -= a = +a) ? function (x) {
- return (x - a) / b;
- } : constants(isNaN(b) ? NaN : 0.5);
- }
- function clamper(a, b) {
- var t;
- if (a > b) t = a, a = b, b = t;
- return function (x) {
- return Math.max(a, Math.min(b, x));
- };
- } // normalize(a, b)(x) takes a domain value x in [a,b] and returns the corresponding parameter t in [0,1].
- // interpolate(a, b)(t) takes a parameter t in [0,1] and returns the corresponding range value x in [a,b].
+ function baseUnary(func) {
+ return function (value) {
+ return func(value);
+ };
+ }
+ /**
+ * The base implementation of `_.values` and `_.valuesIn` which creates an
+ * array of `object` property values corresponding to the property names
+ * of `props`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} props The property names to get values for.
+ * @returns {Object} Returns the array of property values.
+ */
- function bimap(domain, range, interpolate) {
- var d0 = domain[0],
- d1 = domain[1],
- r0 = range[0],
- r1 = range[1];
- if (d1 < d0) d0 = normalize(d1, d0), r0 = interpolate(r1, r0);else d0 = normalize(d0, d1), r0 = interpolate(r0, r1);
- return function (x) {
- return r0(d0(x));
- };
- }
+ function baseValues(object, props) {
+ return arrayMap(props, function (key) {
+ return object[key];
+ });
+ }
+ /**
+ * Checks if a `cache` value for `key` exists.
+ *
+ * @private
+ * @param {Object} cache The cache to query.
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
- function polymap(domain, range, interpolate) {
- var j = Math.min(domain.length, range.length) - 1,
- d = new Array(j),
- r = new Array(j),
- i = -1; // Reverse descending domains.
- if (domain[j] < domain[0]) {
- domain = domain.slice().reverse();
- range = range.slice().reverse();
- }
+ function cacheHas(cache, key) {
+ return cache.has(key);
+ }
+ /**
+ * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the first unmatched string symbol.
+ */
- while (++i < j) {
- d[i] = normalize(domain[i], domain[i + 1]);
- r[i] = interpolate(range[i], range[i + 1]);
- }
- return function (x) {
- var i = bisectRight(domain, x, 1, j) - 1;
- return r[i](d[i](x));
- };
- }
+ function charsStartIndex(strSymbols, chrSymbols) {
+ var index = -1,
+ length = strSymbols.length;
- function copy$1(source, target) {
- return target.domain(source.domain()).range(source.range()).interpolate(source.interpolate()).clamp(source.clamp()).unknown(source.unknown());
- }
- function transformer() {
- var domain = unit,
- range = unit,
- interpolate = interpolate$1,
- transform,
- untransform,
- unknown,
- clamp = identity$1,
- piecewise,
- output,
- input;
+ while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
- function rescale() {
- var n = Math.min(domain.length, range.length);
- if (clamp !== identity$1) clamp = clamper(domain[0], domain[n - 1]);
- piecewise = n > 2 ? polymap : bimap;
- output = input = null;
- return scale;
- }
+ return index;
+ }
+ /**
+ * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+ * that is not found in the character symbols.
+ *
+ * @private
+ * @param {Array} strSymbols The string symbols to inspect.
+ * @param {Array} chrSymbols The character symbols to find.
+ * @returns {number} Returns the index of the last unmatched string symbol.
+ */
- function scale(x) {
- return x == null || isNaN(x = +x) ? unknown : (output || (output = piecewise(domain.map(transform), range, interpolate)))(transform(clamp(x)));
- }
- scale.invert = function (y) {
- return clamp(untransform((input || (input = piecewise(range, domain.map(transform), d3_interpolateNumber)))(y)));
- };
+ function charsEndIndex(strSymbols, chrSymbols) {
+ var index = strSymbols.length;
- scale.domain = function (_) {
- return arguments.length ? (domain = Array.from(_, number), rescale()) : domain.slice();
- };
+ while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
- scale.range = function (_) {
- return arguments.length ? (range = Array.from(_), rescale()) : range.slice();
- };
+ return index;
+ }
+ /**
+ * Gets the number of `placeholder` occurrences in `array`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} placeholder The placeholder to search for.
+ * @returns {number} Returns the placeholder count.
+ */
- scale.rangeRound = function (_) {
- return range = Array.from(_), interpolate = interpolateRound, rescale();
- };
- scale.clamp = function (_) {
- return arguments.length ? (clamp = _ ? true : identity$1, rescale()) : clamp !== identity$1;
- };
+ function countHolders(array, placeholder) {
+ var length = array.length,
+ result = 0;
- scale.interpolate = function (_) {
- return arguments.length ? (interpolate = _, rescale()) : interpolate;
- };
+ while (length--) {
+ if (array[length] === placeholder) {
+ ++result;
+ }
+ }
- scale.unknown = function (_) {
- return arguments.length ? (unknown = _, scale) : unknown;
- };
+ return result;
+ }
+ /**
+ * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
+ * letters to basic Latin letters.
+ *
+ * @private
+ * @param {string} letter The matched letter to deburr.
+ * @returns {string} Returns the deburred letter.
+ */
- return function (t, u) {
- transform = t, untransform = u;
- return rescale();
- };
- }
- function continuous() {
- return transformer()(identity$1, identity$1);
- }
- function formatDecimal (x) {
- return Math.abs(x = Math.round(x)) >= 1e21 ? x.toLocaleString("en").replace(/,/g, "") : x.toString(10);
- } // Computes the decimal coefficient and exponent of the specified number x with
- // significant digits p, where x is positive and p is in [1, 21] or undefined.
- // For example, formatDecimalParts(1.23) returns ["123", 0].
+ var deburrLetter = basePropertyOf(deburredLetters);
+ /**
+ * Used by `_.escape` to convert characters to HTML entities.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
- function formatDecimalParts(x, p) {
- if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
+ var escapeHtmlChar = basePropertyOf(htmlEscapes);
+ /**
+ * Used by `_.template` to escape characters for inclusion in compiled string literals.
+ *
+ * @private
+ * @param {string} chr The matched character to escape.
+ * @returns {string} Returns the escaped character.
+ */
- var i,
- coefficient = x.slice(0, i); // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
- // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
+ function escapeStringChar(chr) {
+ return '\\' + stringEscapes[chr];
+ }
+ /**
+ * Gets the value at `key` of `object`.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {string} key The key of the property to get.
+ * @returns {*} Returns the property value.
+ */
- return [coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient, +x.slice(i + 1)];
- }
- function exponent (x) {
- return x = formatDecimalParts(Math.abs(x)), x ? x[1] : NaN;
- }
+ function getValue(object, key) {
+ return object == null ? undefined$1 : object[key];
+ }
+ /**
+ * Checks if `string` contains Unicode symbols.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {boolean} Returns `true` if a symbol is found, else `false`.
+ */
- function formatGroup (grouping, thousands) {
- return function (value, width) {
- var i = value.length,
- t = [],
- j = 0,
- g = grouping[0],
- length = 0;
- while (i > 0 && g > 0) {
- if (length + g + 1 > width) g = Math.max(1, width - length);
- t.push(value.substring(i -= g, i + g));
- if ((length += g + 1) > width) break;
- g = grouping[j = (j + 1) % grouping.length];
+ function hasUnicode(string) {
+ return reHasUnicode.test(string);
}
+ /**
+ * Checks if `string` contains a word composed of Unicode symbols.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {boolean} Returns `true` if a word is found, else `false`.
+ */
- return t.reverse().join(thousands);
- };
- }
- function formatNumerals (numerals) {
- return function (value) {
- return value.replace(/[0-9]/g, function (i) {
- return numerals[+i];
- });
- };
- }
+ function hasUnicodeWord(string) {
+ return reHasUnicodeWord.test(string);
+ }
+ /**
+ * Converts `iterator` to an array.
+ *
+ * @private
+ * @param {Object} iterator The iterator to convert.
+ * @returns {Array} Returns the converted array.
+ */
- // [[fill]align][sign][symbol][0][width][,][.precision][~][type]
- var re = /^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;
- function formatSpecifier(specifier) {
- if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier);
- var match;
- return new FormatSpecifier({
- fill: match[1],
- align: match[2],
- sign: match[3],
- symbol: match[4],
- zero: match[5],
- width: match[6],
- comma: match[7],
- precision: match[8] && match[8].slice(1),
- trim: match[9],
- type: match[10]
- });
- }
- formatSpecifier.prototype = FormatSpecifier.prototype; // instanceof
- function FormatSpecifier(specifier) {
- this.fill = specifier.fill === undefined ? " " : specifier.fill + "";
- this.align = specifier.align === undefined ? ">" : specifier.align + "";
- this.sign = specifier.sign === undefined ? "-" : specifier.sign + "";
- this.symbol = specifier.symbol === undefined ? "" : specifier.symbol + "";
- this.zero = !!specifier.zero;
- this.width = specifier.width === undefined ? undefined : +specifier.width;
- this.comma = !!specifier.comma;
- this.precision = specifier.precision === undefined ? undefined : +specifier.precision;
- this.trim = !!specifier.trim;
- this.type = specifier.type === undefined ? "" : specifier.type + "";
- }
+ function iteratorToArray(iterator) {
+ var data,
+ result = [];
- FormatSpecifier.prototype.toString = function () {
- return this.fill + this.align + this.sign + this.symbol + (this.zero ? "0" : "") + (this.width === undefined ? "" : Math.max(1, this.width | 0)) + (this.comma ? "," : "") + (this.precision === undefined ? "" : "." + Math.max(0, this.precision | 0)) + (this.trim ? "~" : "") + this.type;
- };
+ while (!(data = iterator.next()).done) {
+ result.push(data.value);
+ }
- // Trims insignificant zeros, e.g., replaces 1.2000k with 1.2k.
- function formatTrim (s) {
- out: for (var n = s.length, i = 1, i0 = -1, i1; i < n; ++i) {
- switch (s[i]) {
- case ".":
- i0 = i1 = i;
- break;
+ return result;
+ }
+ /**
+ * Converts `map` to its key-value pairs.
+ *
+ * @private
+ * @param {Object} map The map to convert.
+ * @returns {Array} Returns the key-value pairs.
+ */
- case "0":
- if (i0 === 0) i0 = i;
- i1 = i;
- break;
- default:
- if (!+s[i]) break out;
- if (i0 > 0) i0 = 0;
- break;
+ function mapToArray(map) {
+ var index = -1,
+ result = Array(map.size);
+ map.forEach(function (value, key) {
+ result[++index] = [key, value];
+ });
+ return result;
}
- }
-
- return i0 > 0 ? s.slice(0, i0) + s.slice(i1 + 1) : s;
- }
+ /**
+ * Creates a unary function that invokes `func` with its argument transformed.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {Function} transform The argument transform.
+ * @returns {Function} Returns the new function.
+ */
- var nativeToPrecision = 1.0.toPrecision;
- var FORCED$1 = fails(function () {
- // IE7-
- return nativeToPrecision.call(1, undefined) !== '1';
- }) || !fails(function () {
- // V8 ~ Android 4.3-
- nativeToPrecision.call({});
- });
+ function overArg(func, transform) {
+ return function (arg) {
+ return func(transform(arg));
+ };
+ }
+ /**
+ * Replaces all `placeholder` elements in `array` with an internal placeholder
+ * and returns an array of their indexes.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {*} placeholder The placeholder to replace.
+ * @returns {Array} Returns the new array of placeholder indexes.
+ */
- // `Number.prototype.toPrecision` method
- // https://tc39.es/ecma262/#sec-number.prototype.toprecision
- _export({ target: 'Number', proto: true, forced: FORCED$1 }, {
- toPrecision: function toPrecision(precision) {
- return precision === undefined
- ? nativeToPrecision.call(thisNumberValue(this))
- : nativeToPrecision.call(thisNumberValue(this), precision);
- }
- });
- var prefixExponent;
- function formatPrefixAuto (x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return x + "";
- var coefficient = d[0],
- exponent = d[1],
- i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1,
- n = coefficient.length;
- return i === n ? coefficient : i > n ? coefficient + new Array(i - n + 1).join("0") : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i) : "0." + new Array(1 - i).join("0") + formatDecimalParts(x, Math.max(0, p + i - 1))[0]; // less than 1y!
- }
+ function replaceHolders(array, placeholder) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
- function formatRounded (x, p) {
- var d = formatDecimalParts(x, p);
- if (!d) return x + "";
- var coefficient = d[0],
- exponent = d[1];
- return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1) : coefficient + new Array(exponent - coefficient.length + 2).join("0");
- }
+ while (++index < length) {
+ var value = array[index];
- var formatTypes = {
- "%": function _(x, p) {
- return (x * 100).toFixed(p);
- },
- "b": function b(x) {
- return Math.round(x).toString(2);
- },
- "c": function c(x) {
- return x + "";
- },
- "d": formatDecimal,
- "e": function e(x, p) {
- return x.toExponential(p);
- },
- "f": function f(x, p) {
- return x.toFixed(p);
- },
- "g": function g(x, p) {
- return x.toPrecision(p);
- },
- "o": function o(x) {
- return Math.round(x).toString(8);
- },
- "p": function p(x, _p) {
- return formatRounded(x * 100, _p);
- },
- "r": formatRounded,
- "s": formatPrefixAuto,
- "X": function X(x) {
- return Math.round(x).toString(16).toUpperCase();
- },
- "x": function x(_x) {
- return Math.round(_x).toString(16);
- }
- };
+ if (value === placeholder || value === PLACEHOLDER) {
+ array[index] = PLACEHOLDER;
+ result[resIndex++] = index;
+ }
+ }
- function identity (x) {
- return x;
- }
+ return result;
+ }
+ /**
+ * Converts `set` to an array of its values.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the values.
+ */
- var map$1 = Array.prototype.map,
- prefixes = ["y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"];
- function formatLocale (locale) {
- var group = locale.grouping === undefined || locale.thousands === undefined ? identity : formatGroup(map$1.call(locale.grouping, Number), locale.thousands + ""),
- currencyPrefix = locale.currency === undefined ? "" : locale.currency[0] + "",
- currencySuffix = locale.currency === undefined ? "" : locale.currency[1] + "",
- decimal = locale.decimal === undefined ? "." : locale.decimal + "",
- numerals = locale.numerals === undefined ? identity : formatNumerals(map$1.call(locale.numerals, String)),
- percent = locale.percent === undefined ? "%" : locale.percent + "",
- minus = locale.minus === undefined ? "â" : locale.minus + "",
- nan = locale.nan === undefined ? "NaN" : locale.nan + "";
- function newFormat(specifier) {
- specifier = formatSpecifier(specifier);
- var fill = specifier.fill,
- align = specifier.align,
- sign = specifier.sign,
- symbol = specifier.symbol,
- zero = specifier.zero,
- width = specifier.width,
- comma = specifier.comma,
- precision = specifier.precision,
- trim = specifier.trim,
- type = specifier.type; // The "n" type is an alias for ",g".
+ function setToArray(set) {
+ var index = -1,
+ result = Array(set.size);
+ set.forEach(function (value) {
+ result[++index] = value;
+ });
+ return result;
+ }
+ /**
+ * Converts `set` to its value-value pairs.
+ *
+ * @private
+ * @param {Object} set The set to convert.
+ * @returns {Array} Returns the value-value pairs.
+ */
- if (type === "n") comma = true, type = "g"; // The "" type, and any invalid type, is an alias for ".12~g".
- else if (!formatTypes[type]) precision === undefined && (precision = 12), trim = true, type = "g"; // If zero fill is specified, padding goes after sign and before digits.
- if (zero || fill === "0" && align === "=") zero = true, fill = "0", align = "="; // Compute the prefix and suffix.
- // For SI-prefix, the suffix is lazily computed.
+ function setToPairs(set) {
+ var index = -1,
+ result = Array(set.size);
+ set.forEach(function (value) {
+ result[++index] = [value, value];
+ });
+ return result;
+ }
+ /**
+ * A specialized version of `_.indexOf` which performs strict equality
+ * comparisons of values, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- var prefix = symbol === "$" ? currencyPrefix : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "",
- suffix = symbol === "$" ? currencySuffix : /[%p]/.test(type) ? percent : ""; // What format function should we use?
- // Is this an integer type?
- // Can this type generate exponential notation?
- var formatType = formatTypes[type],
- maybeSuffix = /[defgprs%]/.test(type); // Set the default precision if not specified,
- // or clamp the specified precision to the supported range.
- // For significant precision, it must be in [1, 21].
- // For fixed precision, it must be in [0, 20].
+ function strictIndexOf(array, value, fromIndex) {
+ var index = fromIndex - 1,
+ length = array.length;
- precision = precision === undefined ? 6 : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision)) : Math.max(0, Math.min(20, precision));
+ while (++index < length) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
- function format(value) {
- var valuePrefix = prefix,
- valueSuffix = suffix,
- i,
- n,
- c;
+ return -1;
+ }
+ /**
+ * A specialized version of `_.lastIndexOf` which performs strict equality
+ * comparisons of values, i.e. `===`.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} fromIndex The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- if (type === "c") {
- valueSuffix = formatType(value) + valueSuffix;
- value = "";
- } else {
- value = +value; // Determine the sign. -0 is not less than 0, but 1 / -0 is!
- var valueNegative = value < 0 || 1 / value < 0; // Perform the initial formatting.
+ function strictLastIndexOf(array, value, fromIndex) {
+ var index = fromIndex + 1;
- value = isNaN(value) ? nan : formatType(Math.abs(value), precision); // Trim insignificant zeros.
+ while (index--) {
+ if (array[index] === value) {
+ return index;
+ }
+ }
- if (trim) value = formatTrim(value); // If a negative value rounds to zero after formatting, and no explicit positive sign is requested, hide the sign.
+ return index;
+ }
+ /**
+ * Gets the number of symbols in `string`.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {number} Returns the string size.
+ */
- if (valueNegative && +value === 0 && sign !== "+") valueNegative = false; // Compute the prefix and suffix.
- valuePrefix = (valueNegative ? sign === "(" ? sign : minus : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
- valueSuffix = (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + valueSuffix + (valueNegative && sign === "(" ? ")" : ""); // Break the formatted value into the integer âvalueâ part that can be
- // grouped, and fractional or exponential âsuffixâ part that is not.
+ function stringSize(string) {
+ return hasUnicode(string) ? unicodeSize(string) : asciiSize(string);
+ }
+ /**
+ * Converts `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
- if (maybeSuffix) {
- i = -1, n = value.length;
- while (++i < n) {
- if (c = value.charCodeAt(i), 48 > c || c > 57) {
- valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;
- value = value.slice(0, i);
- break;
- }
- }
- }
- } // If the fill character is not "0", grouping is applied before padding.
+ function stringToArray(string) {
+ return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
+ }
+ /**
+ * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
+ * character of `string`.
+ *
+ * @private
+ * @param {string} string The string to inspect.
+ * @returns {number} Returns the index of the last non-whitespace character.
+ */
- if (comma && !zero) value = group(value, Infinity); // Compute the padding.
+ function trimmedEndIndex(string) {
+ var index = string.length;
- var length = valuePrefix.length + value.length + valueSuffix.length,
- padding = length < width ? new Array(width - length + 1).join(fill) : ""; // If the fill character is "0", grouping is applied after padding.
+ while (index-- && reWhitespace.test(string.charAt(index))) {}
- if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = ""; // Reconstruct the final output based on the desired alignment.
+ return index;
+ }
+ /**
+ * Used by `_.unescape` to convert HTML entities to characters.
+ *
+ * @private
+ * @param {string} chr The matched character to unescape.
+ * @returns {string} Returns the unescaped character.
+ */
- switch (align) {
- case "<":
- value = valuePrefix + value + valueSuffix + padding;
- break;
- case "=":
- value = valuePrefix + padding + value + valueSuffix;
- break;
+ var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
+ /**
+ * Gets the size of a Unicode `string`.
+ *
+ * @private
+ * @param {string} string The string inspect.
+ * @returns {number} Returns the string size.
+ */
- case "^":
- value = padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
- break;
+ function unicodeSize(string) {
+ var result = reUnicode.lastIndex = 0;
- default:
- value = padding + valuePrefix + value + valueSuffix;
- break;
+ while (reUnicode.test(string)) {
+ ++result;
}
- return numerals(value);
+ return result;
}
+ /**
+ * Converts a Unicode `string` to an array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the converted array.
+ */
- format.toString = function () {
- return specifier + "";
- };
- return format;
- }
+ function unicodeToArray(string) {
+ return string.match(reUnicode) || [];
+ }
+ /**
+ * Splits a Unicode `string` into an array of its words.
+ *
+ * @private
+ * @param {string} The string to inspect.
+ * @returns {Array} Returns the words of `string`.
+ */
- function formatPrefix(specifier, value) {
- var f = newFormat((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)),
- e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,
- k = Math.pow(10, -e),
- prefix = prefixes[8 + e / 3];
- return function (value) {
- return f(k * value) + prefix;
- };
- }
- return {
- format: newFormat,
- formatPrefix: formatPrefix
- };
- }
+ function unicodeWords(string) {
+ return string.match(reUnicodeWord) || [];
+ }
+ /*--------------------------------------------------------------------------*/
- var locale;
- var format;
- var formatPrefix;
- defaultLocale({
- thousands: ",",
- grouping: [3],
- currency: ["$", ""]
- });
- function defaultLocale(definition) {
- locale = formatLocale(definition);
- format = locale.format;
- formatPrefix = locale.formatPrefix;
- return locale;
- }
+ /**
+ * Create a new pristine `lodash` function using the `context` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Util
+ * @param {Object} [context=root] The context object.
+ * @returns {Function} Returns a new `lodash` function.
+ * @example
+ *
+ * _.mixin({ 'foo': _.constant('foo') });
+ *
+ * var lodash = _.runInContext();
+ * lodash.mixin({ 'bar': lodash.constant('bar') });
+ *
+ * _.isFunction(_.foo);
+ * // => true
+ * _.isFunction(_.bar);
+ * // => false
+ *
+ * lodash.isFunction(lodash.foo);
+ * // => false
+ * lodash.isFunction(lodash.bar);
+ * // => true
+ *
+ * // Create a suped-up `defer` in Node.js.
+ * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+ */
- function precisionFixed (step) {
- return Math.max(0, -exponent(Math.abs(step)));
- }
- function precisionPrefix (step, value) {
- return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));
- }
+ var runInContext = function runInContext(context) {
+ context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
+ /** Built-in constructor references. */
- function precisionRound (step, max) {
- step = Math.abs(step), max = Math.abs(max) - step;
- return Math.max(0, exponent(max) - exponent(step)) + 1;
- }
+ var Array = context.Array,
+ Date = context.Date,
+ Error = context.Error,
+ Function = context.Function,
+ Math = context.Math,
+ Object = context.Object,
+ RegExp = context.RegExp,
+ String = context.String,
+ TypeError = context.TypeError;
+ /** Used for built-in method references. */
- function tickFormat(start, stop, count, specifier) {
- var step = tickStep(start, stop, count),
- precision;
- specifier = formatSpecifier(specifier == null ? ",f" : specifier);
+ var arrayProto = Array.prototype,
+ funcProto = Function.prototype,
+ objectProto = Object.prototype;
+ /** Used to detect overreaching core-js shims. */
- switch (specifier.type) {
- case "s":
- {
- var value = Math.max(Math.abs(start), Math.abs(stop));
- if (specifier.precision == null && !isNaN(precision = precisionPrefix(step, value))) specifier.precision = precision;
- return formatPrefix(specifier, value);
- }
+ var coreJsData = context['__core-js_shared__'];
+ /** Used to resolve the decompiled source of functions. */
- case "":
- case "e":
- case "g":
- case "p":
- case "r":
- {
- if (specifier.precision == null && !isNaN(precision = precisionRound(step, Math.max(Math.abs(start), Math.abs(stop))))) specifier.precision = precision - (specifier.type === "e");
- break;
- }
+ var funcToString = funcProto.toString;
+ /** Used to check objects for own properties. */
- case "f":
- case "%":
- {
- if (specifier.precision == null && !isNaN(precision = precisionFixed(step))) specifier.precision = precision - (specifier.type === "%") * 2;
- break;
- }
- }
+ var hasOwnProperty = objectProto.hasOwnProperty;
+ /** Used to generate unique IDs. */
- return format(specifier);
- }
+ var idCounter = 0;
+ /** Used to detect methods masquerading as native. */
- function linearish(scale) {
- var domain = scale.domain;
+ var maskSrcKey = function () {
+ var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+ return uid ? 'Symbol(src)_1.' + uid : '';
+ }();
+ /**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
- scale.ticks = function (count) {
- var d = domain();
- return ticks(d[0], d[d.length - 1], count == null ? 10 : count);
- };
- scale.tickFormat = function (count, specifier) {
- var d = domain();
- return tickFormat(d[0], d[d.length - 1], count == null ? 10 : count, specifier);
- };
+ var nativeObjectToString = objectProto.toString;
+ /** Used to infer the `Object` constructor. */
- scale.nice = function (count) {
- if (count == null) count = 10;
- var d = domain();
- var i0 = 0;
- var i1 = d.length - 1;
- var start = d[i0];
- var stop = d[i1];
- var prestep;
- var step;
- var maxIter = 10;
+ var objectCtorString = funcToString.call(Object);
+ /** Used to restore the original `_` reference in `_.noConflict`. */
- if (stop < start) {
- step = start, start = stop, stop = step;
- step = i0, i0 = i1, i1 = step;
- }
+ var oldDash = root._;
+ /** Used to detect if a method is native. */
- while (maxIter-- > 0) {
- step = tickIncrement(start, stop, count);
+ var reIsNative = RegExp('^' + funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$');
+ /** Built-in value references. */
- if (step === prestep) {
- d[i0] = start;
- d[i1] = stop;
- return domain(d);
- } else if (step > 0) {
- start = Math.floor(start / step) * step;
- stop = Math.ceil(stop / step) * step;
- } else if (step < 0) {
- start = Math.ceil(start * step) / step;
- stop = Math.floor(stop * step) / step;
- } else {
- break;
- }
+ var Buffer = moduleExports ? context.Buffer : undefined$1,
+ _Symbol = context.Symbol,
+ Uint8Array = context.Uint8Array,
+ allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined$1,
+ getPrototype = overArg(Object.getPrototypeOf, Object),
+ objectCreate = Object.create,
+ propertyIsEnumerable = objectProto.propertyIsEnumerable,
+ splice = arrayProto.splice,
+ spreadableSymbol = _Symbol ? _Symbol.isConcatSpreadable : undefined$1,
+ symIterator = _Symbol ? _Symbol.iterator : undefined$1,
+ symToStringTag = _Symbol ? _Symbol.toStringTag : undefined$1;
- prestep = step;
- }
+ var defineProperty = function () {
+ try {
+ var func = getNative(Object, 'defineProperty');
+ func({}, '', {});
+ return func;
+ } catch (e) {}
+ }();
+ /** Mocked built-ins. */
+
+
+ var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
+ ctxNow = Date && Date.now !== root.Date.now && Date.now,
+ ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
+ /* Built-in method references for those with the same name as other `lodash` methods. */
+
+ var nativeCeil = Math.ceil,
+ nativeFloor = Math.floor,
+ nativeGetSymbols = Object.getOwnPropertySymbols,
+ nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined$1,
+ nativeIsFinite = context.isFinite,
+ nativeJoin = arrayProto.join,
+ nativeKeys = overArg(Object.keys, Object),
+ nativeMax = Math.max,
+ nativeMin = Math.min,
+ nativeNow = Date.now,
+ nativeParseInt = context.parseInt,
+ nativeRandom = Math.random,
+ nativeReverse = arrayProto.reverse;
+ /* Built-in method references that are verified to be native. */
+
+ var DataView = getNative(context, 'DataView'),
+ Map = getNative(context, 'Map'),
+ Promise = getNative(context, 'Promise'),
+ Set = getNative(context, 'Set'),
+ WeakMap = getNative(context, 'WeakMap'),
+ nativeCreate = getNative(Object, 'create');
+ /** Used to store function metadata. */
+
+ var metaMap = WeakMap && new WeakMap();
+ /** Used to lookup unminified function names. */
+
+ var realNames = {};
+ /** Used to detect maps, sets, and weakmaps. */
+
+ var dataViewCtorString = toSource(DataView),
+ mapCtorString = toSource(Map),
+ promiseCtorString = toSource(Promise),
+ setCtorString = toSource(Set),
+ weakMapCtorString = toSource(WeakMap);
+ /** Used to convert symbols to primitives and strings. */
+
+ var symbolProto = _Symbol ? _Symbol.prototype : undefined$1,
+ symbolValueOf = symbolProto ? symbolProto.valueOf : undefined$1,
+ symbolToString = symbolProto ? symbolProto.toString : undefined$1;
+ /*------------------------------------------------------------------------*/
- return scale;
- };
+ /**
+ * Creates a `lodash` object which wraps `value` to enable implicit method
+ * chain sequences. Methods that operate on and return arrays, collections,
+ * and functions can be chained together. Methods that retrieve a single value
+ * or may return a primitive value will automatically end the chain sequence
+ * and return the unwrapped value. Otherwise, the value must be unwrapped
+ * with `_#value`.
+ *
+ * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+ * enabled using `_.chain`.
+ *
+ * The execution of chained methods is lazy, that is, it's deferred until
+ * `_#value` is implicitly or explicitly called.
+ *
+ * Lazy evaluation allows several methods to support shortcut fusion.
+ * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+ * the creation of intermediate arrays and can greatly reduce the number of
+ * iteratee executions. Sections of a chain sequence qualify for shortcut
+ * fusion if the section is applied to an array and iteratees accept only
+ * one argument. The heuristic for whether a section qualifies for shortcut
+ * fusion is subject to change.
+ *
+ * Chaining is supported in custom builds as long as the `_#value` method is
+ * directly or indirectly included in the build.
+ *
+ * In addition to lodash methods, wrappers have `Array` and `String` methods.
+ *
+ * The wrapper `Array` methods are:
+ * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+ *
+ * The wrapper `String` methods are:
+ * `replace` and `split`
+ *
+ * The wrapper methods that support shortcut fusion are:
+ * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+ * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+ * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+ *
+ * The chainable wrapper methods are:
+ * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+ * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+ * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+ * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+ * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+ * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+ * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+ * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+ * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+ * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+ * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+ * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+ * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+ * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+ * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+ * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+ * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+ * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+ * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+ * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+ * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+ * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+ * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+ * `zipObject`, `zipObjectDeep`, and `zipWith`
+ *
+ * The wrapper methods that are **not** chainable by default are:
+ * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+ * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
+ * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
+ * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
+ * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
+ * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
+ * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
+ * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
+ * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
+ * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
+ * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
+ * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
+ * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
+ * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
+ * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
+ * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
+ * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
+ * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
+ * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
+ * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
+ * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
+ * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
+ * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
+ * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
+ * `upperFirst`, `value`, and `words`
+ *
+ * @name _
+ * @constructor
+ * @category Seq
+ * @param {*} value The value to wrap in a `lodash` instance.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2, 3]);
+ *
+ * // Returns an unwrapped value.
+ * wrapped.reduce(_.add);
+ * // => 6
+ *
+ * // Returns a wrapped value.
+ * var squares = wrapped.map(square);
+ *
+ * _.isArray(squares);
+ * // => false
+ *
+ * _.isArray(squares.value());
+ * // => true
+ */
- return scale;
- }
- function linear() {
- var scale = continuous();
+ function lodash(value) {
+ if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+ if (value instanceof LodashWrapper) {
+ return value;
+ }
- scale.copy = function () {
- return copy$1(scale, linear());
- };
+ if (hasOwnProperty.call(value, '__wrapped__')) {
+ return wrapperClone(value);
+ }
+ }
- initRange.apply(scale, arguments);
- return linearish(scale);
- }
+ return new LodashWrapper(value);
+ }
+ /**
+ * The base implementation of `_.create` without support for assigning
+ * properties to the created object.
+ *
+ * @private
+ * @param {Object} proto The object to inherit from.
+ * @returns {Object} Returns the new object.
+ */
- // eslint-disable-next-line es/no-math-expm1 -- safe
- var $expm1 = Math.expm1;
- var exp$1 = Math.exp;
- // `Math.expm1` method implementation
- // https://tc39.es/ecma262/#sec-math.expm1
- var mathExpm1 = (!$expm1
- // Old FF bug
- || $expm1(10) > 22025.465794806719 || $expm1(10) < 22025.4657948067165168
- // Tor Browser bug
- || $expm1(-2e-17) != -2e-17
- ) ? function expm1(x) {
- return (x = +x) == 0 ? x : x > -1e-6 && x < 1e-6 ? x + x * x / 2 : exp$1(x) - 1;
- } : $expm1;
-
- function quantize() {
- var x0 = 0,
- x1 = 1,
- n = 1,
- domain = [0.5],
- range = [0, 1],
- unknown;
-
- function scale(x) {
- return x != null && x <= x ? range[bisectRight(domain, x, 0, n)] : unknown;
- }
-
- function rescale() {
- var i = -1;
- domain = new Array(n);
-
- while (++i < n) {
- domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
- }
+ var baseCreate = function () {
+ function object() {}
- return scale;
- }
+ return function (proto) {
+ if (!isObject(proto)) {
+ return {};
+ }
- scale.domain = function (_) {
- var _ref, _ref2;
+ if (objectCreate) {
+ return objectCreate(proto);
+ }
- return arguments.length ? ((_ref = _, _ref2 = _slicedToArray(_ref, 2), x0 = _ref2[0], x1 = _ref2[1], _ref), x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
- };
+ object.prototype = proto;
+ var result = new object();
+ object.prototype = undefined$1;
+ return result;
+ };
+ }();
+ /**
+ * The function whose prototype chain sequence wrappers inherit from.
+ *
+ * @private
+ */
- scale.range = function (_) {
- return arguments.length ? (n = (range = Array.from(_)).length - 1, rescale()) : range.slice();
- };
- scale.invertExtent = function (y) {
- var i = range.indexOf(y);
- return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]];
- };
+ function baseLodash() {// No operation performed.
+ }
+ /**
+ * The base constructor for creating `lodash` wrapper objects.
+ *
+ * @private
+ * @param {*} value The value to wrap.
+ * @param {boolean} [chainAll] Enable explicit method chain sequences.
+ */
- scale.unknown = function (_) {
- return arguments.length ? (unknown = _, scale) : scale;
- };
- scale.thresholds = function () {
- return domain.slice();
- };
+ function LodashWrapper(value, chainAll) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__chain__ = !!chainAll;
+ this.__index__ = 0;
+ this.__values__ = undefined$1;
+ }
+ /**
+ * By default, the template delimiters used by lodash are like those in
+ * embedded Ruby (ERB) as well as ES2015 template strings. Change the
+ * following template settings to use alternative delimiters.
+ *
+ * @static
+ * @memberOf _
+ * @type {Object}
+ */
- scale.copy = function () {
- return quantize().domain([x0, x1]).range(range).unknown(unknown);
- };
- return initRange.apply(linearish(scale), arguments);
- }
+ lodash.templateSettings = {
+ /**
+ * Used to detect `data` property values to be HTML-escaped.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'escape': reEscape,
- // https://github.com/tc39/proposal-string-pad-start-end
+ /**
+ * Used to detect code to be evaluated.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'evaluate': reEvaluate,
+ /**
+ * Used to detect `data` property values to inject.
+ *
+ * @memberOf _.templateSettings
+ * @type {RegExp}
+ */
+ 'interpolate': reInterpolate,
+ /**
+ * Used to reference the data object in the template text.
+ *
+ * @memberOf _.templateSettings
+ * @type {string}
+ */
+ 'variable': '',
+ /**
+ * Used to import variables into the compiled template.
+ *
+ * @memberOf _.templateSettings
+ * @type {Object}
+ */
+ 'imports': {
+ /**
+ * A reference to the `lodash` function.
+ *
+ * @memberOf _.templateSettings.imports
+ * @type {Function}
+ */
+ '_': lodash
+ }
+ }; // Ensure wrappers are instances of `baseLodash`.
+
+ lodash.prototype = baseLodash.prototype;
+ lodash.prototype.constructor = lodash;
+ LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+ LodashWrapper.prototype.constructor = LodashWrapper;
+ /*------------------------------------------------------------------------*/
- var ceil = Math.ceil;
+ /**
+ * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+ *
+ * @private
+ * @constructor
+ * @param {*} value The value to wrap.
+ */
- // `String.prototype.{ padStart, padEnd }` methods implementation
- var createMethod = function (IS_END) {
- return function ($this, maxLength, fillString) {
- var S = String(requireObjectCoercible($this));
- var stringLength = S.length;
- var fillStr = fillString === undefined ? ' ' : String(fillString);
- var intMaxLength = toLength(maxLength);
- var fillLen, stringFiller;
- if (intMaxLength <= stringLength || fillStr == '') return S;
- fillLen = intMaxLength - stringLength;
- stringFiller = stringRepeat.call(fillStr, ceil(fillLen / fillStr.length));
- if (stringFiller.length > fillLen) stringFiller = stringFiller.slice(0, fillLen);
- return IS_END ? S + stringFiller : stringFiller + S;
- };
- };
+ function LazyWrapper(value) {
+ this.__wrapped__ = value;
+ this.__actions__ = [];
+ this.__dir__ = 1;
+ this.__filtered__ = false;
+ this.__iteratees__ = [];
+ this.__takeCount__ = MAX_ARRAY_LENGTH;
+ this.__views__ = [];
+ }
+ /**
+ * Creates a clone of the lazy wrapper object.
+ *
+ * @private
+ * @name clone
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the cloned `LazyWrapper` object.
+ */
- var stringPad = {
- // `String.prototype.padStart` method
- // https://tc39.es/ecma262/#sec-string.prototype.padstart
- start: createMethod(false),
- // `String.prototype.padEnd` method
- // https://tc39.es/ecma262/#sec-string.prototype.padend
- end: createMethod(true)
- };
- var padStart = stringPad.start;
+ function lazyClone() {
+ var result = new LazyWrapper(this.__wrapped__);
+ result.__actions__ = copyArray(this.__actions__);
+ result.__dir__ = this.__dir__;
+ result.__filtered__ = this.__filtered__;
+ result.__iteratees__ = copyArray(this.__iteratees__);
+ result.__takeCount__ = this.__takeCount__;
+ result.__views__ = copyArray(this.__views__);
+ return result;
+ }
+ /**
+ * Reverses the direction of lazy iteration.
+ *
+ * @private
+ * @name reverse
+ * @memberOf LazyWrapper
+ * @returns {Object} Returns the new reversed `LazyWrapper` object.
+ */
- var abs$1 = Math.abs;
- var DatePrototype = Date.prototype;
- var getTime = DatePrototype.getTime;
- var nativeDateToISOString = DatePrototype.toISOString;
- // `Date.prototype.toISOString` method implementation
- // https://tc39.es/ecma262/#sec-date.prototype.toisostring
- // PhantomJS / old WebKit fails here:
- var dateToIsoString = (fails(function () {
- return nativeDateToISOString.call(new Date(-5e13 - 1)) != '0385-07-25T07:06:39.999Z';
- }) || !fails(function () {
- nativeDateToISOString.call(new Date(NaN));
- })) ? function toISOString() {
- if (!isFinite(getTime.call(this))) throw RangeError('Invalid time value');
- var date = this;
- var year = date.getUTCFullYear();
- var milliseconds = date.getUTCMilliseconds();
- var sign = year < 0 ? '-' : year > 9999 ? '+' : '';
- return sign + padStart(abs$1(year), sign ? 6 : 4, 0) +
- '-' + padStart(date.getUTCMonth() + 1, 2, 0) +
- '-' + padStart(date.getUTCDate(), 2, 0) +
- 'T' + padStart(date.getUTCHours(), 2, 0) +
- ':' + padStart(date.getUTCMinutes(), 2, 0) +
- ':' + padStart(date.getUTCSeconds(), 2, 0) +
- '.' + padStart(milliseconds, 3, 0) +
- 'Z';
- } : nativeDateToISOString;
+ function lazyReverse() {
+ if (this.__filtered__) {
+ var result = new LazyWrapper(this);
+ result.__dir__ = -1;
+ result.__filtered__ = true;
+ } else {
+ result = this.clone();
+ result.__dir__ *= -1;
+ }
- // `Date.prototype.toISOString` method
- // https://tc39.es/ecma262/#sec-date.prototype.toisostring
- // PhantomJS / old WebKit has a broken implementations
- _export({ target: 'Date', proto: true, forced: Date.prototype.toISOString !== dateToIsoString }, {
- toISOString: dateToIsoString
- });
+ return result;
+ }
+ /**
+ * Extracts the unwrapped value from its lazy wrapper.
+ *
+ * @private
+ * @name value
+ * @memberOf LazyWrapper
+ * @returns {*} Returns the unwrapped value.
+ */
- function behaviorBreathe() {
- var duration = 800;
- var steps = 4;
- var selector = '.selected.shadow, .selected .shadow';
- var _selected = select(null);
+ function lazyValue() {
+ var array = this.__wrapped__.value(),
+ dir = this.__dir__,
+ isArr = isArray(array),
+ isRight = dir < 0,
+ arrLength = isArr ? array.length : 0,
+ view = getView(0, arrLength, this.__views__),
+ start = view.start,
+ end = view.end,
+ length = end - start,
+ index = isRight ? end : start - 1,
+ iteratees = this.__iteratees__,
+ iterLength = iteratees.length,
+ resIndex = 0,
+ takeCount = nativeMin(length, this.__takeCount__);
- var _classed = '';
- var _params = {};
- var _done = false;
+ if (!isArr || !isRight && arrLength == length && takeCount == length) {
+ return baseWrapperValue(array, this.__actions__);
+ }
- var _timer;
+ var result = [];
- function ratchetyInterpolator(a, b, steps, units) {
- a = parseFloat(a);
- b = parseFloat(b);
- var sample = quantize().domain([0, 1]).range(d3_quantize(d3_interpolateNumber(a, b), steps));
- return function (t) {
- return String(sample(t)) + (units || '');
- };
- }
+ outer: while (length-- && resIndex < takeCount) {
+ index += dir;
+ var iterIndex = -1,
+ value = array[index];
+
+ while (++iterIndex < iterLength) {
+ var data = iteratees[iterIndex],
+ iteratee = data.iteratee,
+ type = data.type,
+ computed = iteratee(value);
+
+ if (type == LAZY_MAP_FLAG) {
+ value = computed;
+ } else if (!computed) {
+ if (type == LAZY_FILTER_FLAG) {
+ continue outer;
+ } else {
+ break outer;
+ }
+ }
+ }
- function reset(selection) {
- selection.style('stroke-opacity', null).style('stroke-width', null).style('fill-opacity', null).style('r', null);
- }
+ result[resIndex++] = value;
+ }
- function setAnimationParams(transition, fromTo) {
- var toFrom = fromTo === 'from' ? 'to' : 'from';
- transition.styleTween('stroke-opacity', function (d) {
- return ratchetyInterpolator(_params[d.id][toFrom].opacity, _params[d.id][fromTo].opacity, steps);
- }).styleTween('stroke-width', function (d) {
- return ratchetyInterpolator(_params[d.id][toFrom].width, _params[d.id][fromTo].width, steps, 'px');
- }).styleTween('fill-opacity', function (d) {
- return ratchetyInterpolator(_params[d.id][toFrom].opacity, _params[d.id][fromTo].opacity, steps);
- }).styleTween('r', function (d) {
- return ratchetyInterpolator(_params[d.id][toFrom].width, _params[d.id][fromTo].width, steps, 'px');
- });
- }
+ return result;
+ } // Ensure `LazyWrapper` is an instance of `baseLodash`.
- function calcAnimationParams(selection) {
- selection.call(reset).each(function (d) {
- var s = select(this);
- var tag = s.node().tagName;
- var p = {
- 'from': {},
- 'to': {}
- };
- var opacity;
- var width; // determine base opacity and width
- if (tag === 'circle') {
- opacity = parseFloat(s.style('fill-opacity') || 0.5);
- width = parseFloat(s.style('r') || 15.5);
- } else {
- opacity = parseFloat(s.style('stroke-opacity') || 0.7);
- width = parseFloat(s.style('stroke-width') || 10);
- } // calculate from/to interpolation params..
+ LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+ LazyWrapper.prototype.constructor = LazyWrapper;
+ /*------------------------------------------------------------------------*/
+ /**
+ * Creates a hash object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- p.tag = tag;
- p.from.opacity = opacity * 0.6;
- p.to.opacity = opacity * 1.25;
- p.from.width = width * 0.7;
- p.to.width = width * (tag === 'circle' ? 1.5 : 1);
- _params[d.id] = p;
- });
- }
+ function Hash(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- function run(surface, fromTo) {
- var toFrom = fromTo === 'from' ? 'to' : 'from';
- var currSelected = surface.selectAll(selector);
- var currClassed = surface.attr('class');
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+ /**
+ * Removes all key-value entries from the hash.
+ *
+ * @private
+ * @name clear
+ * @memberOf Hash
+ */
- if (_done || currSelected.empty()) {
- _selected.call(reset);
- _selected = select(null);
- return;
- }
+ function hashClear() {
+ this.__data__ = nativeCreate ? nativeCreate(null) : {};
+ this.size = 0;
+ }
+ /**
+ * Removes `key` and its value from the hash.
+ *
+ * @private
+ * @name delete
+ * @memberOf Hash
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
- if (!fastDeepEqual(currSelected.data(), _selected.data()) || currClassed !== _classed) {
- _selected.call(reset);
- _classed = currClassed;
- _selected = currSelected.call(calcAnimationParams);
- }
+ function hashDelete(key) {
+ var result = this.has(key) && delete this.__data__[key];
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+ /**
+ * Gets the hash value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Hash
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
- var didCallNextRun = false;
- _selected.transition().duration(duration).call(setAnimationParams, fromTo).on('end', function () {
- // `end` event is called for each selected element, but we want
- // it to run only once
- if (!didCallNextRun) {
- surface.call(run, toFrom);
- didCallNextRun = true;
- } // if entity was deselected, remove breathe styling
+ function hashGet(key) {
+ var data = this.__data__;
+ if (nativeCreate) {
+ var result = data[key];
+ return result === HASH_UNDEFINED ? undefined$1 : result;
+ }
- if (!select(this).classed('selected')) {
- reset(select(this));
+ return hasOwnProperty.call(data, key) ? data[key] : undefined$1;
}
- });
- }
+ /**
+ * Checks if a hash value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Hash
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
- function behavior(surface) {
- _done = false;
- _timer = timer(function () {
- // wait for elements to actually become selected
- if (surface.selectAll(selector).empty()) {
- return false;
+
+ function hashHas(key) {
+ var data = this.__data__;
+ return nativeCreate ? data[key] !== undefined$1 : hasOwnProperty.call(data, key);
}
+ /**
+ * Sets the hash `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Hash
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the hash instance.
+ */
- surface.call(run, 'from');
- _timer.stop();
+ function hashSet(key, value) {
+ var data = this.__data__;
+ this.size += this.has(key) ? 0 : 1;
+ data[key] = nativeCreate && value === undefined$1 ? HASH_UNDEFINED : value;
+ return this;
+ } // Add methods to `Hash`.
- return true;
- }, 20);
- }
- behavior.restartIfNeeded = function (surface) {
- if (_selected.empty()) {
- surface.call(run, 'from');
+ Hash.prototype.clear = hashClear;
+ Hash.prototype['delete'] = hashDelete;
+ Hash.prototype.get = hashGet;
+ Hash.prototype.has = hashHas;
+ Hash.prototype.set = hashSet;
+ /*------------------------------------------------------------------------*/
- if (_timer) {
- _timer.stop();
- }
- }
- };
+ /**
+ * Creates an list cache object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- behavior.off = function () {
- _done = true;
+ function ListCache(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- if (_timer) {
- _timer.stop();
- }
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
+ }
+ }
+ /**
+ * Removes all key-value entries from the list cache.
+ *
+ * @private
+ * @name clear
+ * @memberOf ListCache
+ */
- _selected.interrupt().call(reset);
- };
- return behavior;
- }
+ function listCacheClear() {
+ this.__data__ = [];
+ this.size = 0;
+ }
+ /**
+ * Removes `key` and its value from the list cache.
+ *
+ * @private
+ * @name delete
+ * @memberOf ListCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
- /* Creates a keybinding behavior for an operation */
- function behaviorOperation(context) {
- var _operation;
- function keypress(d3_event) {
- // prevent operations during low zoom selection
- if (!context.map().withinEditableZoom()) return;
- if (_operation.availableForKeypress && !_operation.availableForKeypress()) return;
- d3_event.preventDefault();
+ function listCacheDelete(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
- var disabled = _operation.disabled();
+ if (index < 0) {
+ return false;
+ }
- if (disabled) {
- context.ui().flash.duration(4000).iconName('#iD-operation-' + _operation.id).iconClass('operation disabled').label(_operation.tooltip)();
- } else {
- context.ui().flash.duration(2000).iconName('#iD-operation-' + _operation.id).iconClass('operation').label(_operation.annotation() || _operation.title)();
- if (_operation.point) _operation.point(null);
+ var lastIndex = data.length - 1;
- _operation();
- }
- }
+ if (index == lastIndex) {
+ data.pop();
+ } else {
+ splice.call(data, index, 1);
+ }
- function behavior() {
- if (_operation && _operation.available()) {
- context.keybinding().on(_operation.keys, keypress);
- }
+ --this.size;
+ return true;
+ }
+ /**
+ * Gets the list cache value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf ListCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
- return behavior;
- }
- behavior.off = function () {
- context.keybinding().off(_operation.keys);
- };
+ function listCacheGet(key) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
+ return index < 0 ? undefined$1 : data[index][1];
+ }
+ /**
+ * Checks if a list cache value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf ListCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
- behavior.which = function (_) {
- if (!arguments.length) return _operation;
- _operation = _;
- return behavior;
- };
- return behavior;
- }
+ function listCacheHas(key) {
+ return assocIndexOf(this.__data__, key) > -1;
+ }
+ /**
+ * Sets the list cache `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf ListCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the list cache instance.
+ */
- function operationCircularize(context, selectedIDs) {
- var _extent;
- var _actions = selectedIDs.map(getAction).filter(Boolean);
+ function listCacheSet(key, value) {
+ var data = this.__data__,
+ index = assocIndexOf(data, key);
- var _amount = _actions.length === 1 ? 'single' : 'multiple';
+ if (index < 0) {
+ ++this.size;
+ data.push([key, value]);
+ } else {
+ data[index][1] = value;
+ }
- var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
- return n.loc;
- });
+ return this;
+ } // Add methods to `ListCache`.
- function getAction(entityID) {
- var entity = context.entity(entityID);
- if (entity.type !== 'way' || new Set(entity.nodes).size <= 1) return null;
- if (!_extent) {
- _extent = entity.extent(context.graph());
- } else {
- _extent = _extent.extend(entity.extent(context.graph()));
- }
+ ListCache.prototype.clear = listCacheClear;
+ ListCache.prototype['delete'] = listCacheDelete;
+ ListCache.prototype.get = listCacheGet;
+ ListCache.prototype.has = listCacheHas;
+ ListCache.prototype.set = listCacheSet;
+ /*------------------------------------------------------------------------*/
- return actionCircularize(entityID, context.projection);
- }
+ /**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- var operation = function operation() {
- if (!_actions.length) return;
+ function MapCache(entries) {
+ var index = -1,
+ length = entries == null ? 0 : entries.length;
+ this.clear();
- var combinedAction = function combinedAction(graph, t) {
- _actions.forEach(function (action) {
- if (!action.disabled(graph)) {
- graph = action(graph, t);
+ while (++index < length) {
+ var entry = entries[index];
+ this.set(entry[0], entry[1]);
}
- });
+ }
+ /**
+ * Removes all key-value entries from the map.
+ *
+ * @private
+ * @name clear
+ * @memberOf MapCache
+ */
- return graph;
- };
- combinedAction.transitionable = true;
- context.perform(combinedAction, operation.annotation());
- window.setTimeout(function () {
- context.validator().validate();
- }, 300); // after any transition
- };
+ function mapCacheClear() {
+ this.size = 0;
+ this.__data__ = {
+ 'hash': new Hash(),
+ 'map': new (Map || ListCache)(),
+ 'string': new Hash()
+ };
+ }
+ /**
+ * Removes `key` and its value from the map.
+ *
+ * @private
+ * @name delete
+ * @memberOf MapCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
- operation.available = function () {
- return _actions.length && selectedIDs.length === _actions.length;
- }; // don't cache this because the visible extent could change
+ function mapCacheDelete(key) {
+ var result = getMapData(this, key)['delete'](key);
+ this.size -= result ? 1 : 0;
+ return result;
+ }
+ /**
+ * Gets the map value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf MapCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
- operation.disabled = function () {
- if (!_actions.length) return '';
- var actionDisableds = _actions.map(function (action) {
- return action.disabled(context.graph());
- }).filter(Boolean);
+ function mapCacheGet(key) {
+ return getMapData(this, key).get(key);
+ }
+ /**
+ * Checks if a map value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf MapCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
- if (actionDisableds.length === _actions.length) {
- // none of the features can be circularized
- if (new Set(actionDisableds).size > 1) {
- return 'multiple_blockers';
+
+ function mapCacheHas(key) {
+ return getMapData(this, key).has(key);
}
+ /**
+ * Sets the map `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf MapCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the map cache instance.
+ */
- return actionDisableds[0];
- } else if (_extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- }
- return false;
+ function mapCacheSet(key, value) {
+ var data = getMapData(this, key),
+ size = data.size;
+ data.set(key, value);
+ this.size += data.size == size ? 0 : 1;
+ return this;
+ } // Add methods to `MapCache`.
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
- if (osm) {
- var missing = _coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ MapCache.prototype.clear = mapCacheClear;
+ MapCache.prototype['delete'] = mapCacheDelete;
+ MapCache.prototype.get = mapCacheGet;
+ MapCache.prototype.has = mapCacheHas;
+ MapCache.prototype.set = mapCacheSet;
+ /*------------------------------------------------------------------------*/
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ /**
+ *
+ * Creates an array cache object to store unique values.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [values] The values to cache.
+ */
+
+ function SetCache(values) {
+ var index = -1,
+ length = values == null ? 0 : values.length;
+ this.__data__ = new MapCache();
+
+ while (++index < length) {
+ this.add(values[index]);
}
}
+ /**
+ * Adds `value` to the array cache.
+ *
+ * @private
+ * @name add
+ * @memberOf SetCache
+ * @alias push
+ * @param {*} value The value to cache.
+ * @returns {Object} Returns the cache instance.
+ */
- return false;
- }
- };
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.circularize.' + disable + '.' + _amount) : _t('operations.circularize.description.' + _amount);
- };
+ function setCacheAdd(value) {
+ this.__data__.set(value, HASH_UNDEFINED);
- operation.annotation = function () {
- return _t('operations.circularize.annotation.feature', {
- n: _actions.length
- });
- };
+ return this;
+ }
+ /**
+ * Checks if `value` is in the array cache.
+ *
+ * @private
+ * @name has
+ * @memberOf SetCache
+ * @param {*} value The value to search for.
+ * @returns {number} Returns `true` if `value` is found, else `false`.
+ */
- operation.id = 'circularize';
- operation.keys = [_t('operations.circularize.key')];
- operation.title = _t('operations.circularize.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
- // For example, âZ -> Ctrl+Z
+ function setCacheHas(value) {
+ return this.__data__.has(value);
+ } // Add methods to `SetCache`.
- var uiCmd = function uiCmd(code) {
- var detected = utilDetect();
- if (detected.os === 'mac') {
- return code;
- }
+ SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
+ SetCache.prototype.has = setCacheHas;
+ /*------------------------------------------------------------------------*/
- if (detected.os === 'win') {
- if (code === 'ââ§Z') return 'Ctrl+Y';
- }
+ /**
+ * Creates a stack cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
- var result = '',
- replacements = {
- 'â': 'Ctrl',
- 'â§': 'Shift',
- 'â¥': 'Alt',
- 'â«': 'Backspace',
- 'â¦': 'Delete'
- };
+ function Stack(entries) {
+ var data = this.__data__ = new ListCache(entries);
+ this.size = data.size;
+ }
+ /**
+ * Removes all key-value entries from the stack.
+ *
+ * @private
+ * @name clear
+ * @memberOf Stack
+ */
- for (var i = 0; i < code.length; i++) {
- if (code[i] in replacements) {
- result += replacements[code[i]] + (i < code.length - 1 ? '+' : '');
- } else {
- result += code[i];
- }
- }
- return result;
- }; // return a display-focused string for a given keyboard code
+ function stackClear() {
+ this.__data__ = new ListCache();
+ this.size = 0;
+ }
+ /**
+ * Removes `key` and its value from the stack.
+ *
+ * @private
+ * @name delete
+ * @memberOf Stack
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
- uiCmd.display = function (code) {
- if (code.length !== 1) return code;
- var detected = utilDetect();
- var mac = detected.os === 'mac';
- var replacements = {
- 'â': mac ? 'â ' + _t('shortcuts.key.cmd') : _t('shortcuts.key.ctrl'),
- 'â§': mac ? '⧠' + _t('shortcuts.key.shift') : _t('shortcuts.key.shift'),
- 'â¥': mac ? '⥠' + _t('shortcuts.key.option') : _t('shortcuts.key.alt'),
- 'â': mac ? 'â ' + _t('shortcuts.key.ctrl') : _t('shortcuts.key.ctrl'),
- 'â«': mac ? 'â« ' + _t('shortcuts.key.delete') : _t('shortcuts.key.backspace'),
- 'â¦': mac ? '⦠' + _t('shortcuts.key.del') : _t('shortcuts.key.del'),
- 'â': mac ? 'â ' + _t('shortcuts.key.pgup') : _t('shortcuts.key.pgup'),
- 'â': mac ? 'â ' + _t('shortcuts.key.pgdn') : _t('shortcuts.key.pgdn'),
- 'â': mac ? 'â ' + _t('shortcuts.key.home') : _t('shortcuts.key.home'),
- 'â': mac ? 'â ' + _t('shortcuts.key.end') : _t('shortcuts.key.end'),
- 'âµ': mac ? 'â ' + _t('shortcuts.key.return') : _t('shortcuts.key.enter'),
- 'â': mac ? 'â ' + _t('shortcuts.key.esc') : _t('shortcuts.key.esc'),
- 'â°': mac ? 'â° ' + _t('shortcuts.key.menu') : _t('shortcuts.key.menu')
- };
- return replacements[code] || code;
- };
-
- function operationDelete(context, selectedIDs) {
- var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
- var action = actionDeleteMultiple(selectedIDs);
- var nodes = utilGetAllNodes(selectedIDs, context.graph());
- var coords = nodes.map(function (n) {
- return n.loc;
- });
- var extent = utilTotalExtent(selectedIDs, context.graph());
- var operation = function operation() {
- var nextSelectedID;
- var nextSelectedLoc;
+ function stackDelete(key) {
+ var data = this.__data__,
+ result = data['delete'](key);
+ this.size = data.size;
+ return result;
+ }
+ /**
+ * Gets the stack value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Stack
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
- if (selectedIDs.length === 1) {
- var id = selectedIDs[0];
- var entity = context.entity(id);
- var geometry = entity.geometry(context.graph());
- var parents = context.graph().parentWays(entity);
- var parent = parents[0]; // Select the next closest node in the way.
- if (geometry === 'vertex') {
- var nodes = parent.nodes;
- var i = nodes.indexOf(id);
+ function stackGet(key) {
+ return this.__data__.get(key);
+ }
+ /**
+ * Checks if a stack value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Stack
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
- if (i === 0) {
- i++;
- } else if (i === nodes.length - 1) {
- i--;
- } else {
- var a = geoSphericalDistance(entity.loc, context.entity(nodes[i - 1]).loc);
- var b = geoSphericalDistance(entity.loc, context.entity(nodes[i + 1]).loc);
- i = a < b ? i - 1 : i + 1;
- }
- nextSelectedID = nodes[i];
- nextSelectedLoc = context.entity(nextSelectedID).loc;
+ function stackHas(key) {
+ return this.__data__.has(key);
}
- }
+ /**
+ * Sets the stack `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Stack
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the stack cache instance.
+ */
- context.perform(action, operation.annotation());
- context.validator().validate();
- if (nextSelectedID && nextSelectedLoc) {
- if (context.hasEntity(nextSelectedID)) {
- context.enter(modeSelect(context, [nextSelectedID]).follow(true));
- } else {
- context.map().centerEase(nextSelectedLoc);
- context.enter(modeBrowse(context));
- }
- } else {
- context.enter(modeBrowse(context));
- }
- };
+ function stackSet(key, value) {
+ var data = this.__data__;
- operation.available = function () {
- return true;
- };
+ if (data instanceof ListCache) {
+ var pairs = data.__data__;
- operation.disabled = function () {
- if (extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- } else if (selectedIDs.some(protectedMember)) {
- return 'part_of_relation';
- } else if (selectedIDs.some(incompleteRelation)) {
- return 'incomplete_relation';
- } else if (selectedIDs.some(hasWikidataTag)) {
- return 'has_wikidata_tag';
- }
+ if (!Map || pairs.length < LARGE_ARRAY_SIZE - 1) {
+ pairs.push([key, value]);
+ this.size = ++data.size;
+ return this;
+ }
- return false;
+ data = this.__data__ = new MapCache(pairs);
+ }
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ data.set(key, value);
+ this.size = data.size;
+ return this;
+ } // Add methods to `Stack`.
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ Stack.prototype.clear = stackClear;
+ Stack.prototype['delete'] = stackDelete;
+ Stack.prototype.get = stackGet;
+ Stack.prototype.has = stackHas;
+ Stack.prototype.set = stackSet;
+ /*------------------------------------------------------------------------*/
+
+ /**
+ * Creates an array of the enumerable property names of the array-like `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @param {boolean} inherited Specify returning inherited property names.
+ * @returns {Array} Returns the array of property names.
+ */
+
+ function arrayLikeKeys(value, inherited) {
+ var isArr = isArray(value),
+ isArg = !isArr && isArguments(value),
+ isBuff = !isArr && !isArg && isBuffer(value),
+ isType = !isArr && !isArg && !isBuff && isTypedArray(value),
+ skipIndexes = isArr || isArg || isBuff || isType,
+ result = skipIndexes ? baseTimes(value.length, String) : [],
+ length = result.length;
+
+ for (var key in value) {
+ if ((inherited || hasOwnProperty.call(value, key)) && !(skipIndexes && ( // Safari 9 has enumerable `arguments.length` in strict mode.
+ key == 'length' || // Node.js 0.10 has enumerable non-index properties on buffers.
+ isBuff && (key == 'offset' || key == 'parent') || // PhantomJS 2 has enumerable non-index properties on typed arrays.
+ isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset') || // Skip index properties.
+ isIndex(key, length)))) {
+ result.push(key);
+ }
}
+
+ return result;
}
+ /**
+ * A specialized version of `_.sample` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to sample.
+ * @returns {*} Returns the random element.
+ */
- return false;
- }
- function hasWikidataTag(id) {
- var entity = context.entity(id);
- return entity.tags.wikidata && entity.tags.wikidata.trim().length > 0;
- }
+ function arraySample(array) {
+ var length = array.length;
+ return length ? array[baseRandom(0, length - 1)] : undefined$1;
+ }
+ /**
+ * A specialized version of `_.sampleSize` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to sample.
+ * @param {number} n The number of elements to sample.
+ * @returns {Array} Returns the random elements.
+ */
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- function protectedMember(id) {
- var entity = context.entity(id);
- if (entity.type !== 'way') return false;
- var parents = context.graph().parentRelations(entity);
+ function arraySampleSize(array, n) {
+ return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
+ }
+ /**
+ * A specialized version of `_.shuffle` for arrays.
+ *
+ * @private
+ * @param {Array} array The array to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ */
- for (var i = 0; i < parents.length; i++) {
- var parent = parents[i];
- var type = parent.tags.type;
- var role = parent.memberById(id).role || 'outer';
- if (type === 'route' || type === 'boundary' || type === 'multipolygon' && role === 'outer') {
- return true;
+ function arrayShuffle(array) {
+ return shuffleSelf(copyArray(array));
+ }
+ /**
+ * This function is like `assignValue` except that it doesn't assign
+ * `undefined` values.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
+
+
+ function assignMergeValue(object, key, value) {
+ if (value !== undefined$1 && !eq(object[key], value) || value === undefined$1 && !(key in object)) {
+ baseAssignValue(object, key, value);
}
}
+ /**
+ * Assigns `value` to `key` of `object` if the existing value is not equivalent
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
- return false;
- }
- };
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.delete.' + disable + '.' + multi) : _t('operations.delete.description.' + multi);
- };
+ function assignValue(object, key, value) {
+ var objValue = object[key];
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.delete.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.delete.annotation.feature', {
- n: selectedIDs.length
- });
- };
+ if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || value === undefined$1 && !(key in object)) {
+ baseAssignValue(object, key, value);
+ }
+ }
+ /**
+ * Gets the index at which the `key` is found in `array` of key-value pairs.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} key The key to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
- operation.id = 'delete';
- operation.keys = [uiCmd('ââ«'), uiCmd('ââ¦'), uiCmd('â¦')];
- operation.title = _t('operations.delete.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
- function operationOrthogonalize(context, selectedIDs) {
- var _extent;
+ function assocIndexOf(array, key) {
+ var length = array.length;
- var _type;
+ while (length--) {
+ if (eq(array[length][0], key)) {
+ return length;
+ }
+ }
- var _actions = selectedIDs.map(chooseAction).filter(Boolean);
+ return -1;
+ }
+ /**
+ * Aggregates elements of `collection` on `accumulator` with keys transformed
+ * by `iteratee` and values set by `setter`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform keys.
+ * @param {Object} accumulator The initial aggregated object.
+ * @returns {Function} Returns `accumulator`.
+ */
- var _amount = _actions.length === 1 ? 'single' : 'multiple';
- var _coords = utilGetAllNodes(selectedIDs, context.graph()).map(function (n) {
- return n.loc;
- });
+ function baseAggregator(collection, setter, iteratee, accumulator) {
+ baseEach(collection, function (value, key, collection) {
+ setter(accumulator, value, iteratee(value), collection);
+ });
+ return accumulator;
+ }
+ /**
+ * The base implementation of `_.assign` without support for multiple sources
+ * or `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @returns {Object} Returns `object`.
+ */
- function chooseAction(entityID) {
- var entity = context.entity(entityID);
- var geometry = entity.geometry(context.graph());
- if (!_extent) {
- _extent = entity.extent(context.graph());
- } else {
- _extent = _extent.extend(entity.extent(context.graph()));
- } // square a line/area
+ function baseAssign(object, source) {
+ return object && copyObject(source, keys(source), object);
+ }
+ /**
+ * The base implementation of `_.assignIn` without support for multiple sources
+ * or `customizer` functions.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @returns {Object} Returns `object`.
+ */
- if (entity.type === 'way' && new Set(entity.nodes).size > 2) {
- if (_type && _type !== 'feature') return null;
- _type = 'feature';
- return actionOrthogonalize(entityID, context.projection); // square a single vertex
- } else if (geometry === 'vertex') {
- if (_type && _type !== 'corner') return null;
- _type = 'corner';
- var graph = context.graph();
- var parents = graph.parentWays(entity);
+ function baseAssignIn(object, source) {
+ return object && copyObject(source, keysIn(source), object);
+ }
+ /**
+ * The base implementation of `assignValue` and `assignMergeValue` without
+ * value checks.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {string} key The key of the property to assign.
+ * @param {*} value The value to assign.
+ */
- if (parents.length === 1) {
- var way = parents[0];
- if (way.nodes.indexOf(entityID) !== -1) {
- return actionOrthogonalize(way.id, context.projection, entityID);
+ function baseAssignValue(object, key, value) {
+ if (key == '__proto__' && defineProperty) {
+ defineProperty(object, key, {
+ 'configurable': true,
+ 'enumerable': true,
+ 'value': value,
+ 'writable': true
+ });
+ } else {
+ object[key] = value;
}
}
- }
+ /**
+ * The base implementation of `_.at` without support for individual paths.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {string[]} paths The property paths to pick.
+ * @returns {Array} Returns the picked elements.
+ */
- return null;
- }
- var operation = function operation() {
- if (!_actions.length) return;
+ function baseAt(object, paths) {
+ var index = -1,
+ length = paths.length,
+ result = Array(length),
+ skip = object == null;
- var combinedAction = function combinedAction(graph, t) {
- _actions.forEach(function (action) {
- if (!action.disabled(graph)) {
- graph = action(graph, t);
+ while (++index < length) {
+ result[index] = skip ? undefined$1 : get(object, paths[index]);
}
- });
-
- return graph;
- };
-
- combinedAction.transitionable = true;
- context.perform(combinedAction, operation.annotation());
- window.setTimeout(function () {
- context.validator().validate();
- }, 300); // after any transition
- };
- operation.available = function () {
- return _actions.length && selectedIDs.length === _actions.length;
- }; // don't cache this because the visible extent could change
+ return result;
+ }
+ /**
+ * The base implementation of `_.clamp` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ */
- operation.disabled = function () {
- if (!_actions.length) return '';
+ function baseClamp(number, lower, upper) {
+ if (number === number) {
+ if (upper !== undefined$1) {
+ number = number <= upper ? number : upper;
+ }
- var actionDisableds = _actions.map(function (action) {
- return action.disabled(context.graph());
- }).filter(Boolean);
+ if (lower !== undefined$1) {
+ number = number >= lower ? number : lower;
+ }
+ }
- if (actionDisableds.length === _actions.length) {
- // none of the features can be squared
- if (new Set(actionDisableds).size > 1) {
- return 'multiple_blockers';
+ return number;
}
+ /**
+ * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+ * traversed objects.
+ *
+ * @private
+ * @param {*} value The value to clone.
+ * @param {boolean} bitmask The bitmask flags.
+ * 1 - Deep clone
+ * 2 - Flatten inherited properties
+ * 4 - Clone symbols
+ * @param {Function} [customizer] The function to customize cloning.
+ * @param {string} [key] The key of `value`.
+ * @param {Object} [object] The parent object of `value`.
+ * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+ * @returns {*} Returns the cloned value.
+ */
- return actionDisableds[0];
- } else if (_extent && _extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- }
-
- return false;
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ function baseClone(value, bitmask, customizer, key, object, stack) {
+ var result,
+ isDeep = bitmask & CLONE_DEEP_FLAG,
+ isFlat = bitmask & CLONE_FLAT_FLAG,
+ isFull = bitmask & CLONE_SYMBOLS_FLAG;
- if (osm) {
- var missing = _coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ if (customizer) {
+ result = object ? customizer(value, key, object, stack) : customizer(value);
+ }
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ if (result !== undefined$1) {
+ return result;
}
- }
- return false;
- }
- };
+ if (!isObject(value)) {
+ return value;
+ }
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.orthogonalize.' + disable + '.' + _amount) : _t('operations.orthogonalize.description.' + _type + '.' + _amount);
- };
+ var isArr = isArray(value);
- operation.annotation = function () {
- return _t('operations.orthogonalize.annotation.' + _type, {
- n: _actions.length
- });
- };
+ if (isArr) {
+ result = initCloneArray(value);
- operation.id = 'orthogonalize';
- operation.keys = [_t('operations.orthogonalize.key')];
- operation.title = _t('operations.orthogonalize.title');
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
+ if (!isDeep) {
+ return copyArray(value, result);
+ }
+ } else {
+ var tag = getTag(value),
+ isFunc = tag == funcTag || tag == genTag;
- function operationReflectShort(context, selectedIDs) {
- return operationReflect(context, selectedIDs, 'short');
- }
- function operationReflectLong(context, selectedIDs) {
- return operationReflect(context, selectedIDs, 'long');
- }
- function operationReflect(context, selectedIDs, axis) {
- axis = axis || 'long';
- var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
- var nodes = utilGetAllNodes(selectedIDs, context.graph());
- var coords = nodes.map(function (n) {
- return n.loc;
- });
- var extent = utilTotalExtent(selectedIDs, context.graph());
+ if (isBuffer(value)) {
+ return cloneBuffer(value, isDeep);
+ }
- var operation = function operation() {
- var action = actionReflect(selectedIDs, context.projection).useLongAxis(Boolean(axis === 'long'));
- context.perform(action, operation.annotation());
- window.setTimeout(function () {
- context.validator().validate();
- }, 300); // after any transition
- };
+ if (tag == objectTag || tag == argsTag || isFunc && !object) {
+ result = isFlat || isFunc ? {} : initCloneObject(value);
- operation.available = function () {
- return nodes.length >= 3;
- }; // don't cache this because the visible extent could change
+ if (!isDeep) {
+ return isFlat ? copySymbolsIn(value, baseAssignIn(result, value)) : copySymbols(value, baseAssign(result, value));
+ }
+ } else {
+ if (!cloneableTags[tag]) {
+ return object ? value : {};
+ }
+ result = initCloneByTag(value, tag, isDeep);
+ }
+ } // Check for circular references and return its corresponding clone.
- operation.disabled = function () {
- if (extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- } else if (selectedIDs.some(incompleteRelation)) {
- return 'incomplete_relation';
- }
- return false;
+ stack || (stack = new Stack());
+ var stacked = stack.get(value);
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ if (stacked) {
+ return stacked;
+ }
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ stack.set(value, result);
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
+ if (isSet(value)) {
+ value.forEach(function (subValue) {
+ result.add(baseClone(subValue, bitmask, customizer, subValue, value, stack));
+ });
+ } else if (isMap(value)) {
+ value.forEach(function (subValue, key) {
+ result.set(key, baseClone(subValue, bitmask, customizer, key, value, stack));
});
- return true;
}
- }
-
- return false;
- }
-
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.reflect.' + disable + '.' + multi) : _t('operations.reflect.description.' + axis + '.' + multi);
- };
+ var keysFunc = isFull ? isFlat ? getAllKeysIn : getAllKeys : isFlat ? keysIn : keys;
+ var props = isArr ? undefined$1 : keysFunc(value);
+ arrayEach(props || value, function (subValue, key) {
+ if (props) {
+ key = subValue;
+ subValue = value[key];
+ } // Recursively populate clone (susceptible to call stack limits).
- operation.annotation = function () {
- return _t('operations.reflect.annotation.' + axis + '.feature', {
- n: selectedIDs.length
- });
- };
- operation.id = 'reflect-' + axis;
- operation.keys = [_t('operations.reflect.key.' + axis)];
- operation.title = _t('operations.reflect.title.' + axis);
- operation.behavior = behaviorOperation(context).which(operation);
- return operation;
- }
+ assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
+ });
+ return result;
+ }
+ /**
+ * The base implementation of `_.conforms` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {Function} Returns the new spec function.
+ */
- function operationMove(context, selectedIDs) {
- var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
- var nodes = utilGetAllNodes(selectedIDs, context.graph());
- var coords = nodes.map(function (n) {
- return n.loc;
- });
- var extent = utilTotalExtent(selectedIDs, context.graph());
- var operation = function operation() {
- context.enter(modeMove(context, selectedIDs));
- };
+ function baseConforms(source) {
+ var props = keys(source);
+ return function (object) {
+ return baseConformsTo(object, source, props);
+ };
+ }
+ /**
+ * The base implementation of `_.conformsTo` which accepts `props` to check.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+ */
- operation.available = function () {
- return selectedIDs.length > 0;
- };
- operation.disabled = function () {
- if (extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- } else if (selectedIDs.some(incompleteRelation)) {
- return 'incomplete_relation';
- }
+ function baseConformsTo(object, source, props) {
+ var length = props.length;
- return false;
+ if (object == null) {
+ return !length;
+ }
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
+ object = Object(object);
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ while (length--) {
+ var key = props[length],
+ predicate = source[key],
+ value = object[key];
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
+ if (value === undefined$1 && !(key in object) || !predicate(value)) {
+ return false;
+ }
}
+
+ return true;
}
+ /**
+ * The base implementation of `_.delay` and `_.defer` which accepts `args`
+ * to provide to `func`.
+ *
+ * @private
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {Array} args The arguments to provide to `func`.
+ * @returns {number|Object} Returns the timer id or timeout object.
+ */
- return false;
- }
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
+ function baseDelay(func, wait, args) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.move.' + disable + '.' + multi) : _t('operations.move.description.' + multi);
- };
+ return setTimeout(function () {
+ func.apply(undefined$1, args);
+ }, wait);
+ }
+ /**
+ * The base implementation of methods like `_.difference` without support
+ * for excluding multiple arrays or iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Array} values The values to exclude.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ */
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.move.annotation.feature', {
- n: selectedIDs.length
- });
- };
- operation.id = 'move';
- operation.keys = [_t('operations.move.key')];
- operation.title = _t('operations.move.title');
- operation.behavior = behaviorOperation(context).which(operation);
- operation.mouseOnly = true;
- return operation;
- }
+ function baseDifference(array, values, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ isCommon = true,
+ length = array.length,
+ result = [],
+ valuesLength = values.length;
- function modeRotate(context, entityIDs) {
- var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeMove
+ if (!length) {
+ return result;
+ }
- var mode = {
- id: 'rotate',
- button: 'browse'
- };
- var keybinding = utilKeybinding('rotate');
- var behaviors = [behaviorEdit(context), operationCircularize(context, entityIDs).behavior, operationDelete(context, entityIDs).behavior, operationMove(context, entityIDs).behavior, operationOrthogonalize(context, entityIDs).behavior, operationReflectLong(context, entityIDs).behavior, operationReflectShort(context, entityIDs).behavior];
- var annotation = entityIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(entityIDs[0])) : _t('operations.rotate.annotation.feature', {
- n: entityIDs.length
- });
+ if (iteratee) {
+ values = arrayMap(values, baseUnary(iteratee));
+ }
- var _prevGraph;
+ if (comparator) {
+ includes = arrayIncludesWith;
+ isCommon = false;
+ } else if (values.length >= LARGE_ARRAY_SIZE) {
+ includes = cacheHas;
+ isCommon = false;
+ values = new SetCache(values);
+ }
- var _prevAngle;
+ outer: while (++index < length) {
+ var value = array[index],
+ computed = iteratee == null ? value : iteratee(value);
+ value = comparator || value !== 0 ? value : 0;
- var _prevTransform;
+ if (isCommon && computed === computed) {
+ var valuesIndex = valuesLength;
- var _pivot; // use pointer events on supported platforms; fallback to mouse events
+ while (valuesIndex--) {
+ if (values[valuesIndex] === computed) {
+ continue outer;
+ }
+ }
+ result.push(value);
+ } else if (!includes(values, computed, comparator)) {
+ result.push(value);
+ }
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ return result;
+ }
+ /**
+ * The base implementation of `_.forEach` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
- function doRotate(d3_event) {
- var fn;
- if (context.graph() !== _prevGraph) {
- fn = context.perform;
- } else {
- fn = context.replace;
- } // projection changed, recalculate _pivot
+ var baseEach = createBaseEach(baseForOwn);
+ /**
+ * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ */
+ var baseEachRight = createBaseEach(baseForOwnRight, true);
+ /**
+ * The base implementation of `_.every` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`
+ */
- var projection = context.projection;
- var currTransform = projection.transform();
+ function baseEvery(collection, predicate) {
+ var result = true;
+ baseEach(collection, function (value, index, collection) {
+ result = !!predicate(value, index, collection);
+ return result;
+ });
+ return result;
+ }
+ /**
+ * The base implementation of methods like `_.max` and `_.min` which accepts a
+ * `comparator` to determine the extremum value.
+ *
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} iteratee The iteratee invoked per iteration.
+ * @param {Function} comparator The comparator used to compare values.
+ * @returns {*} Returns the extremum value.
+ */
- if (!_prevTransform || currTransform.k !== _prevTransform.k || currTransform.x !== _prevTransform.x || currTransform.y !== _prevTransform.y) {
- var nodes = utilGetAllNodes(entityIDs, context.graph());
- var points = nodes.map(function (n) {
- return projection(n.loc);
- });
- _pivot = getPivot(points);
- _prevAngle = undefined;
- }
- var currMouse = context.map().mouse(d3_event);
- var currAngle = Math.atan2(currMouse[1] - _pivot[1], currMouse[0] - _pivot[0]);
- if (typeof _prevAngle === 'undefined') _prevAngle = currAngle;
- var delta = currAngle - _prevAngle;
- fn(actionRotate(entityIDs, _pivot, delta, projection));
- _prevTransform = currTransform;
- _prevAngle = currAngle;
- _prevGraph = context.graph();
- }
+ function baseExtremum(array, iteratee, comparator) {
+ var index = -1,
+ length = array.length;
- function getPivot(points) {
- var _pivot;
+ while (++index < length) {
+ var value = array[index],
+ current = iteratee(value);
- if (points.length === 1) {
- _pivot = points[0];
- } else if (points.length === 2) {
- _pivot = geoVecInterp(points[0], points[1], 0.5);
- } else {
- var polygonHull = d3_polygonHull(points);
+ if (current != null && (computed === undefined$1 ? current === current && !isSymbol(current) : comparator(current, computed))) {
+ var computed = current,
+ result = value;
+ }
+ }
- if (polygonHull.length === 2) {
- _pivot = geoVecInterp(points[0], points[1], 0.5);
- } else {
- _pivot = d3_polygonCentroid(d3_polygonHull(points));
+ return result;
}
- }
+ /**
+ * The base implementation of `_.fill` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ */
- return _pivot;
- }
- function finish(d3_event) {
- d3_event.stopPropagation();
- context.replace(actionNoop(), annotation);
- context.enter(modeSelect(context, entityIDs));
- }
+ function baseFill(array, value, start, end) {
+ var length = array.length;
+ start = toInteger(start);
- function cancel() {
- if (_prevGraph) context.pop(); // remove the rotate
+ if (start < 0) {
+ start = -start > length ? 0 : length + start;
+ }
- context.enter(modeSelect(context, entityIDs));
- }
+ end = end === undefined$1 || end > length ? length : toInteger(end);
- function undone() {
- context.enter(modeBrowse(context));
- }
+ if (end < 0) {
+ end += length;
+ }
- mode.enter = function () {
- _prevGraph = null;
- context.features().forceVisible(entityIDs);
- behaviors.forEach(context.install);
- var downEvent;
- context.surface().on(_pointerPrefix + 'down.modeRotate', function (d3_event) {
- downEvent = d3_event;
- });
- select(window).on(_pointerPrefix + 'move.modeRotate', doRotate, true).on(_pointerPrefix + 'up.modeRotate', function (d3_event) {
- if (!downEvent) return;
- var mapNode = context.container().select('.main-map').node();
- var pointGetter = utilFastMouse(mapNode);
- var p1 = pointGetter(downEvent);
- var p2 = pointGetter(d3_event);
- var dist = geoVecLength(p1, p2);
- if (dist <= _tolerancePx) finish(d3_event);
- downEvent = null;
- }, true);
- context.history().on('undone.modeRotate', undone);
- keybinding.on('â', cancel).on('â©', finish);
- select(document).call(keybinding);
- };
+ end = start > end ? 0 : toLength(end);
- mode.exit = function () {
- behaviors.forEach(context.uninstall);
- context.surface().on(_pointerPrefix + 'down.modeRotate', null);
- select(window).on(_pointerPrefix + 'move.modeRotate', null, true).on(_pointerPrefix + 'up.modeRotate', null, true);
- context.history().on('undone.modeRotate', null);
- select(document).call(keybinding.unbind);
- context.features().forceVisible([]);
- };
+ while (start < end) {
+ array[start++] = value;
+ }
- mode.selectedIDs = function () {
- if (!arguments.length) return entityIDs; // no assign
+ return array;
+ }
+ /**
+ * The base implementation of `_.filter` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ */
- return mode;
- };
- return mode;
- }
+ function baseFilter(collection, predicate) {
+ var result = [];
+ baseEach(collection, function (value, index, collection) {
+ if (predicate(value, index, collection)) {
+ result.push(value);
+ }
+ });
+ return result;
+ }
+ /**
+ * The base implementation of `_.flatten` with support for restricting flattening.
+ *
+ * @private
+ * @param {Array} array The array to flatten.
+ * @param {number} depth The maximum recursion depth.
+ * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+ * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+ * @param {Array} [result=[]] The initial result value.
+ * @returns {Array} Returns the new flattened array.
+ */
- function operationRotate(context, selectedIDs) {
- var multi = selectedIDs.length === 1 ? 'single' : 'multiple';
- var nodes = utilGetAllNodes(selectedIDs, context.graph());
- var coords = nodes.map(function (n) {
- return n.loc;
- });
- var extent = utilTotalExtent(selectedIDs, context.graph());
- var operation = function operation() {
- context.enter(modeRotate(context, selectedIDs));
- };
+ function baseFlatten(array, depth, predicate, isStrict, result) {
+ var index = -1,
+ length = array.length;
+ predicate || (predicate = isFlattenable);
+ result || (result = []);
- operation.available = function () {
- return nodes.length >= 2;
- };
+ while (++index < length) {
+ var value = array[index];
- operation.disabled = function () {
- if (extent.percentContainedIn(context.map().extent()) < 0.8) {
- return 'too_large';
- } else if (someMissing()) {
- return 'not_downloaded';
- } else if (selectedIDs.some(context.hasHiddenConnections)) {
- return 'connected_to_hidden';
- } else if (selectedIDs.some(incompleteRelation)) {
- return 'incomplete_relation';
- }
+ if (depth > 0 && predicate(value)) {
+ if (depth > 1) {
+ // Recursively flatten arrays (susceptible to call stack limits).
+ baseFlatten(value, depth - 1, predicate, isStrict, result);
+ } else {
+ arrayPush(result, value);
+ }
+ } else if (!isStrict) {
+ result[result.length] = value;
+ }
+ }
- return false;
+ return result;
+ }
+ /**
+ * The base implementation of `baseForOwn` which iterates over `object`
+ * properties returned by `keysFunc` and invokes `iteratee` for each property.
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
- function someMissing() {
- if (context.inIntro()) return false;
- var osm = context.connection();
- if (osm) {
- var missing = coords.filter(function (loc) {
- return !osm.isDataLoaded(loc);
- });
+ var baseFor = createBaseFor();
+ /**
+ * This function is like `baseFor` except that it iterates over properties
+ * in the opposite order.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @returns {Object} Returns `object`.
+ */
- if (missing.length) {
- missing.forEach(function (loc) {
- context.loadTileAtLoc(loc);
- });
- return true;
- }
+ var baseForRight = createBaseFor(true);
+ /**
+ * The base implementation of `_.forOwn` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
+
+ function baseForOwn(object, iteratee) {
+ return object && baseFor(object, iteratee, keys);
}
+ /**
+ * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ */
- return false;
- }
- function incompleteRelation(id) {
- var entity = context.entity(id);
- return entity.type === 'relation' && !entity.isComplete(context.graph());
- }
- };
+ function baseForOwnRight(object, iteratee) {
+ return object && baseForRight(object, iteratee, keys);
+ }
+ /**
+ * The base implementation of `_.functions` which creates an array of
+ * `object` function property names filtered from `props`.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Array} props The property names to filter.
+ * @returns {Array} Returns the function names.
+ */
- operation.tooltip = function () {
- var disable = operation.disabled();
- return disable ? _t('operations.rotate.' + disable + '.' + multi) : _t('operations.rotate.description.' + multi);
- };
- operation.annotation = function () {
- return selectedIDs.length === 1 ? _t('operations.rotate.annotation.' + context.graph().geometry(selectedIDs[0])) : _t('operations.rotate.annotation.feature', {
- n: selectedIDs.length
- });
- };
+ function baseFunctions(object, props) {
+ return arrayFilter(props, function (key) {
+ return isFunction(object[key]);
+ });
+ }
+ /**
+ * The base implementation of `_.get` without support for default values.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @returns {*} Returns the resolved value.
+ */
- operation.id = 'rotate';
- operation.keys = [_t('operations.rotate.key')];
- operation.title = _t('operations.rotate.title');
- operation.behavior = behaviorOperation(context).which(operation);
- operation.mouseOnly = true;
- return operation;
- }
- function modeMove(context, entityIDs, baseGraph) {
- var _tolerancePx = 4; // see also behaviorDrag, behaviorSelect, modeRotate
+ function baseGet(object, path) {
+ path = castPath(path, object);
+ var index = 0,
+ length = path.length;
- var mode = {
- id: 'move',
- button: 'browse'
- };
- var keybinding = utilKeybinding('move');
- var behaviors = [behaviorEdit(context), operationCircularize(context, entityIDs).behavior, operationDelete(context, entityIDs).behavior, operationOrthogonalize(context, entityIDs).behavior, operationReflectLong(context, entityIDs).behavior, operationReflectShort(context, entityIDs).behavior, operationRotate(context, entityIDs).behavior];
- var annotation = entityIDs.length === 1 ? _t('operations.move.annotation.' + context.graph().geometry(entityIDs[0])) : _t('operations.move.annotation.feature', {
- n: entityIDs.length
- });
+ while (object != null && index < length) {
+ object = object[toKey(path[index++])];
+ }
- var _prevGraph;
+ return index && index == length ? object : undefined$1;
+ }
+ /**
+ * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+ * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+ * symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Function} keysFunc The function to get the keys of `object`.
+ * @param {Function} symbolsFunc The function to get the symbols of `object`.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
- var _cache;
- var _origin;
+ function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+ var result = keysFunc(object);
+ return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
+ }
+ /**
+ * The base implementation of `getTag` without fallbacks for buggy environments.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
- var _nudgeInterval; // use pointer events on supported platforms; fallback to mouse events
+ function baseGetTag(value) {
+ if (value == null) {
+ return value === undefined$1 ? undefinedTag : nullTag;
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ return symToStringTag && symToStringTag in Object(value) ? getRawTag(value) : objectToString(value);
+ }
+ /**
+ * The base implementation of `_.gt` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than `other`,
+ * else `false`.
+ */
- function doMove(nudge) {
- nudge = nudge || [0, 0];
- var fn;
- if (_prevGraph !== context.graph()) {
- _cache = {};
- _origin = context.map().mouseCoordinates();
- fn = context.perform;
- } else {
- fn = context.overwrite;
- }
+ function baseGt(value, other) {
+ return value > other;
+ }
+ /**
+ * The base implementation of `_.has` without support for deep paths.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
- var currMouse = context.map().mouse();
- var origMouse = context.projection(_origin);
- var delta = geoVecSubtract(geoVecSubtract(currMouse, origMouse), nudge);
- fn(actionMove(entityIDs, delta, context.projection, _cache));
- _prevGraph = context.graph();
- }
- function startNudge(nudge) {
- if (_nudgeInterval) window.clearInterval(_nudgeInterval);
- _nudgeInterval = window.setInterval(function () {
- context.map().pan(nudge);
- doMove(nudge);
- }, 50);
- }
+ function baseHas(object, key) {
+ return object != null && hasOwnProperty.call(object, key);
+ }
+ /**
+ * The base implementation of `_.hasIn` without support for deep paths.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {Array|string} key The key to check.
+ * @returns {boolean} Returns `true` if `key` exists, else `false`.
+ */
- function stopNudge() {
- if (_nudgeInterval) {
- window.clearInterval(_nudgeInterval);
- _nudgeInterval = null;
- }
- }
- function move() {
- doMove();
- var nudge = geoViewportEdge(context.map().mouse(), context.map().dimensions());
+ function baseHasIn(object, key) {
+ return object != null && key in Object(object);
+ }
+ /**
+ * The base implementation of `_.inRange` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {number} number The number to check.
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ */
- if (nudge) {
- startNudge(nudge);
- } else {
- stopNudge();
- }
- }
- function finish(d3_event) {
- d3_event.stopPropagation();
- context.replace(actionNoop(), annotation);
- context.enter(modeSelect(context, entityIDs));
- stopNudge();
- }
+ function baseInRange(number, start, end) {
+ return number >= nativeMin(start, end) && number < nativeMax(start, end);
+ }
+ /**
+ * The base implementation of methods like `_.intersection`, without support
+ * for iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of shared values.
+ */
- function cancel() {
- if (baseGraph) {
- while (context.graph() !== baseGraph) {
- context.pop();
- } // reset to baseGraph
+ function baseIntersection(arrays, iteratee, comparator) {
+ var includes = comparator ? arrayIncludesWith : arrayIncludes,
+ length = arrays[0].length,
+ othLength = arrays.length,
+ othIndex = othLength,
+ caches = Array(othLength),
+ maxLength = Infinity,
+ result = [];
- context.enter(modeBrowse(context));
- } else {
- if (_prevGraph) context.pop(); // remove the move
+ while (othIndex--) {
+ var array = arrays[othIndex];
- context.enter(modeSelect(context, entityIDs));
- }
+ if (othIndex && iteratee) {
+ array = arrayMap(array, baseUnary(iteratee));
+ }
- stopNudge();
- }
+ maxLength = nativeMin(array.length, maxLength);
+ caches[othIndex] = !comparator && (iteratee || length >= 120 && array.length >= 120) ? new SetCache(othIndex && array) : undefined$1;
+ }
- function undone() {
- context.enter(modeBrowse(context));
- }
+ array = arrays[0];
+ var index = -1,
+ seen = caches[0];
- mode.enter = function () {
- _origin = context.map().mouseCoordinates();
- _prevGraph = null;
- _cache = {};
- context.features().forceVisible(entityIDs);
- behaviors.forEach(context.install);
- var downEvent;
- context.surface().on(_pointerPrefix + 'down.modeMove', function (d3_event) {
- downEvent = d3_event;
- });
- select(window).on(_pointerPrefix + 'move.modeMove', move, true).on(_pointerPrefix + 'up.modeMove', function (d3_event) {
- if (!downEvent) return;
- var mapNode = context.container().select('.main-map').node();
- var pointGetter = utilFastMouse(mapNode);
- var p1 = pointGetter(downEvent);
- var p2 = pointGetter(d3_event);
- var dist = geoVecLength(p1, p2);
- if (dist <= _tolerancePx) finish(d3_event);
- downEvent = null;
- }, true);
- context.history().on('undone.modeMove', undone);
- keybinding.on('â', cancel).on('â©', finish);
- select(document).call(keybinding);
- };
+ outer: while (++index < length && result.length < maxLength) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+ value = comparator || value !== 0 ? value : 0;
- mode.exit = function () {
- stopNudge();
- behaviors.forEach(function (behavior) {
- context.uninstall(behavior);
- });
- context.surface().on(_pointerPrefix + 'down.modeMove', null);
- select(window).on(_pointerPrefix + 'move.modeMove', null, true).on(_pointerPrefix + 'up.modeMove', null, true);
- context.history().on('undone.modeMove', null);
- select(document).call(keybinding.unbind);
- context.features().forceVisible([]);
- };
+ if (!(seen ? cacheHas(seen, computed) : includes(result, computed, comparator))) {
+ othIndex = othLength;
- mode.selectedIDs = function () {
- if (!arguments.length) return entityIDs; // no assign
+ while (--othIndex) {
+ var cache = caches[othIndex];
- return mode;
- };
+ if (!(cache ? cacheHas(cache, computed) : includes(arrays[othIndex], computed, comparator))) {
+ continue outer;
+ }
+ }
- return mode;
- }
+ if (seen) {
+ seen.push(computed);
+ }
- function behaviorPaste(context) {
- function doPaste(d3_event) {
- // prevent paste during low zoom selection
- if (!context.map().withinEditableZoom()) return;
- d3_event.preventDefault();
- var baseGraph = context.graph();
- var mouse = context.map().mouse();
- var projection = context.projection;
- var viewport = geoExtent(projection.clipExtent()).polygon();
- if (!geoPointInPolygon(mouse, viewport)) return;
- var oldIDs = context.copyIDs();
- if (!oldIDs.length) return;
- var extent = geoExtent();
- var oldGraph = context.copyGraph();
- var newIDs = [];
- var action = actionCopyEntities(oldIDs, oldGraph);
- context.perform(action);
- var copies = action.copies();
- var originals = new Set();
- Object.values(copies).forEach(function (entity) {
- originals.add(entity.id);
- });
+ result.push(value);
+ }
+ }
- for (var id in copies) {
- var oldEntity = oldGraph.entity(id);
- var newEntity = copies[id];
+ return result;
+ }
+ /**
+ * The base implementation of `_.invert` and `_.invertBy` which inverts
+ * `object` with values transformed by `iteratee` and set by `setter`.
+ *
+ * @private
+ * @param {Object} object The object to iterate over.
+ * @param {Function} setter The function to set `accumulator` values.
+ * @param {Function} iteratee The iteratee to transform values.
+ * @param {Object} accumulator The initial inverted object.
+ * @returns {Function} Returns `accumulator`.
+ */
- extent._extend(oldEntity.extent(oldGraph)); // Exclude child nodes from newIDs if their parent way was also copied.
+ function baseInverter(object, setter, iteratee, accumulator) {
+ baseForOwn(object, function (value, key, object) {
+ setter(accumulator, iteratee(value), key, object);
+ });
+ return accumulator;
+ }
+ /**
+ * The base implementation of `_.invoke` without support for individual
+ * method arguments.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {Array} args The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ */
- var parents = context.graph().parentWays(newEntity);
- var parentCopied = parents.some(function (parent) {
- return originals.has(parent.id);
- });
- if (!parentCopied) {
- newIDs.push(newEntity.id);
+ function baseInvoke(object, path, args) {
+ path = castPath(path, object);
+ object = parent(object, path);
+ var func = object == null ? object : object[toKey(last(path))];
+ return func == null ? undefined$1 : apply(func, object, args);
}
- } // Put pasted objects where mouse pointer is..
+ /**
+ * The base implementation of `_.isArguments`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ */
- var copyPoint = context.copyLonLat() && projection(context.copyLonLat()) || projection(extent.center());
- var delta = geoVecSubtract(mouse, copyPoint);
- context.perform(actionMove(newIDs, delta, projection));
- context.enter(modeMove(context, newIDs, baseGraph));
- }
+ function baseIsArguments(value) {
+ return isObjectLike(value) && baseGetTag(value) == argsTag;
+ }
+ /**
+ * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+ */
- function behavior() {
- context.keybinding().on(uiCmd('âV'), doPaste);
- return behavior;
- }
- behavior.off = function () {
- context.keybinding().off(uiCmd('âV'));
- };
+ function baseIsArrayBuffer(value) {
+ return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
+ }
+ /**
+ * The base implementation of `_.isDate` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+ */
- return behavior;
- }
- // `String.prototype.repeat` method
- // https://tc39.es/ecma262/#sec-string.prototype.repeat
- _export({ target: 'String', proto: true }, {
- repeat: stringRepeat
- });
+ function baseIsDate(value) {
+ return isObjectLike(value) && baseGetTag(value) == dateTag;
+ }
+ /**
+ * The base implementation of `_.isEqual` which supports partial comparisons
+ * and tracks traversed objects.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {boolean} bitmask The bitmask flags.
+ * 1 - Unordered comparison
+ * 2 - Partial comparison
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ */
- /*
- `behaviorDrag` is like `d3_behavior.drag`, with the following differences:
- * The `origin` function is expected to return an [x, y] tuple rather than an
- {x, y} object.
- * The events are `start`, `move`, and `end`.
- (https://github.com/mbostock/d3/issues/563)
- * The `start` event is not dispatched until the first cursor movement occurs.
- (https://github.com/mbostock/d3/pull/368)
- * The `move` event has a `point` and `delta` [x, y] tuple properties rather
- than `x`, `y`, `dx`, and `dy` properties.
- * The `end` event is not dispatched if no movement occurs.
- * An `off` function is available that unbinds the drag's internal event handlers.
- */
+ function baseIsEqual(value, other, bitmask, customizer, stack) {
+ if (value === other) {
+ return true;
+ }
- function behaviorDrag() {
- var dispatch = dispatch$8('start', 'move', 'end'); // see also behaviorSelect
+ if (value == null || other == null || !isObjectLike(value) && !isObjectLike(other)) {
+ return value !== value && other !== other;
+ }
- var _tolerancePx = 1; // keep this low to facilitate pixel-perfect micromapping
+ return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
+ }
+ /**
+ * A specialized version of `baseIsEqual` for arrays and objects which performs
+ * deep comparisons and tracks traversed objects enabling objects with circular
+ * references to be compared.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
- var _penTolerancePx = 4; // styluses can be touchy so require greater movement - #1981
- var _origin = null;
- var _selector = '';
+ function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
+ var objIsArr = isArray(object),
+ othIsArr = isArray(other),
+ objTag = objIsArr ? arrayTag : getTag(object),
+ othTag = othIsArr ? arrayTag : getTag(other);
+ objTag = objTag == argsTag ? objectTag : objTag;
+ othTag = othTag == argsTag ? objectTag : othTag;
+ var objIsObj = objTag == objectTag,
+ othIsObj = othTag == objectTag,
+ isSameTag = objTag == othTag;
- var _targetNode;
+ if (isSameTag && isBuffer(object)) {
+ if (!isBuffer(other)) {
+ return false;
+ }
- var _targetEntity;
+ objIsArr = true;
+ objIsObj = false;
+ }
- var _surface;
+ if (isSameTag && !objIsObj) {
+ stack || (stack = new Stack());
+ return objIsArr || isTypedArray(object) ? equalArrays(object, other, bitmask, customizer, equalFunc, stack) : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
+ }
- var _pointerId; // use pointer events on supported platforms; fallback to mouse events
+ if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
+ var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+ othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+ if (objIsWrapped || othIsWrapped) {
+ var objUnwrapped = objIsWrapped ? object.value() : object,
+ othUnwrapped = othIsWrapped ? other.value() : other;
+ stack || (stack = new Stack());
+ return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
+ }
+ }
- var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse';
+ if (!isSameTag) {
+ return false;
+ }
- var d3_event_userSelectProperty = utilPrefixCSSProperty('UserSelect');
+ stack || (stack = new Stack());
+ return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
+ }
+ /**
+ * The base implementation of `_.isMap` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+ */
- var d3_event_userSelectSuppress = function d3_event_userSelectSuppress() {
- var selection$1 = selection();
- var select = selection$1.style(d3_event_userSelectProperty);
- selection$1.style(d3_event_userSelectProperty, 'none');
- return function () {
- selection$1.style(d3_event_userSelectProperty, select);
- };
- };
- function pointerdown(d3_event) {
- if (_pointerId) return;
- _pointerId = d3_event.pointerId || 'mouse';
- _targetNode = this; // only force reflow once per drag
+ function baseIsMap(value) {
+ return isObjectLike(value) && getTag(value) == mapTag;
+ }
+ /**
+ * The base implementation of `_.isMatch` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Array} matchData The property names, values, and compare flags to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ */
- var pointerLocGetter = utilFastMouse(_surface || _targetNode.parentNode);
- var offset;
- var startOrigin = pointerLocGetter(d3_event);
- var started = false;
- var selectEnable = d3_event_userSelectSuppress();
- select(window).on(_pointerPrefix + 'move.drag', pointermove).on(_pointerPrefix + 'up.drag pointercancel.drag', pointerup, true);
- if (_origin) {
- offset = _origin.call(_targetNode, _targetEntity);
- offset = [offset[0] - startOrigin[0], offset[1] - startOrigin[1]];
- } else {
- offset = [0, 0];
- }
+ function baseIsMatch(object, source, matchData, customizer) {
+ var index = matchData.length,
+ length = index,
+ noCustomizer = !customizer;
- d3_event.stopPropagation();
+ if (object == null) {
+ return !length;
+ }
- function pointermove(d3_event) {
- if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
- var p = pointerLocGetter(d3_event);
+ object = Object(object);
- if (!started) {
- var dist = geoVecLength(startOrigin, p);
- var tolerance = d3_event.pointerType === 'pen' ? _penTolerancePx : _tolerancePx; // don't start until the drag has actually moved somewhat
+ while (index--) {
+ var data = matchData[index];
- if (dist < tolerance) return;
- started = true;
- dispatch.call('start', this, d3_event, _targetEntity); // Don't send a `move` event in the same cycle as `start` since dragging
- // a midpoint will convert the target to a node.
- } else {
- startOrigin = p;
- d3_event.stopPropagation();
- d3_event.preventDefault();
- var dx = p[0] - startOrigin[0];
- var dy = p[1] - startOrigin[1];
- dispatch.call('move', this, d3_event, _targetEntity, [p[0] + offset[0], p[1] + offset[1]], [dx, dy]);
- }
- }
+ if (noCustomizer && data[2] ? data[1] !== object[data[0]] : !(data[0] in object)) {
+ return false;
+ }
+ }
- function pointerup(d3_event) {
- if (_pointerId !== (d3_event.pointerId || 'mouse')) return;
- _pointerId = null;
+ while (++index < length) {
+ data = matchData[index];
+ var key = data[0],
+ objValue = object[key],
+ srcValue = data[1];
- if (started) {
- dispatch.call('end', this, d3_event, _targetEntity);
- d3_event.preventDefault();
- }
+ if (noCustomizer && data[2]) {
+ if (objValue === undefined$1 && !(key in object)) {
+ return false;
+ }
+ } else {
+ var stack = new Stack();
- select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
- selectEnable();
- }
- }
+ if (customizer) {
+ var result = customizer(objValue, srcValue, key, object, source, stack);
+ }
- function behavior(selection) {
- var matchesSelector = utilPrefixDOMProperty('matchesSelector');
- var delegate = pointerdown;
+ if (!(result === undefined$1 ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack) : result)) {
+ return false;
+ }
+ }
+ }
- if (_selector) {
- delegate = function delegate(d3_event) {
- var root = this;
- var target = d3_event.target;
+ return true;
+ }
+ /**
+ * The base implementation of `_.isNative` without bad shim checks.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ * else `false`.
+ */
- for (; target && target !== root; target = target.parentNode) {
- var datum = target.__data__;
- _targetEntity = datum instanceof osmNote ? datum : datum && datum.properties && datum.properties.entity;
- if (_targetEntity && target[matchesSelector](_selector)) {
- return pointerdown.call(target, d3_event);
- }
+ function baseIsNative(value) {
+ if (!isObject(value) || isMasked(value)) {
+ return false;
}
- };
- }
- selection.on(_pointerPrefix + 'down.drag' + _selector, delegate);
- }
+ var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
+ return pattern.test(toSource(value));
+ }
+ /**
+ * The base implementation of `_.isRegExp` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+ */
- behavior.off = function (selection) {
- selection.on(_pointerPrefix + 'down.drag' + _selector, null);
- };
- behavior.selector = function (_) {
- if (!arguments.length) return _selector;
- _selector = _;
- return behavior;
- };
+ function baseIsRegExp(value) {
+ return isObjectLike(value) && baseGetTag(value) == regexpTag;
+ }
+ /**
+ * The base implementation of `_.isSet` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+ */
- behavior.origin = function (_) {
- if (!arguments.length) return _origin;
- _origin = _;
- return behavior;
- };
- behavior.cancel = function () {
- select(window).on(_pointerPrefix + 'move.drag', null).on(_pointerPrefix + 'up.drag pointercancel.drag', null);
- return behavior;
- };
+ function baseIsSet(value) {
+ return isObjectLike(value) && getTag(value) == setTag;
+ }
+ /**
+ * The base implementation of `_.isTypedArray` without Node.js optimizations.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ */
- behavior.targetNode = function (_) {
- if (!arguments.length) return _targetNode;
- _targetNode = _;
- return behavior;
- };
- behavior.targetEntity = function (_) {
- if (!arguments.length) return _targetEntity;
- _targetEntity = _;
- return behavior;
- };
+ function baseIsTypedArray(value) {
+ return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
+ }
+ /**
+ * The base implementation of `_.iteratee`.
+ *
+ * @private
+ * @param {*} [value=_.identity] The value to convert to an iteratee.
+ * @returns {Function} Returns the iteratee.
+ */
- behavior.surface = function (_) {
- if (!arguments.length) return _surface;
- _surface = _;
- return behavior;
- };
- return utilRebind(behavior, dispatch, 'on');
- }
+ function baseIteratee(value) {
+ // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+ // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+ if (typeof value == 'function') {
+ return value;
+ }
- function modeDragNode(context) {
- var mode = {
- id: 'drag-node',
- button: 'browse'
- };
- var hover = behaviorHover(context).altDisables(true).on('hover', context.ui().sidebar.hover);
- var edit = behaviorEdit(context);
+ if (value == null) {
+ return identity;
+ }
- var _nudgeInterval;
+ if (_typeof(value) == 'object') {
+ return isArray(value) ? baseMatchesProperty(value[0], value[1]) : baseMatches(value);
+ }
- var _restoreSelectedIDs = [];
- var _wasMidpoint = false;
- var _isCancelled = false;
+ return property(value);
+ }
+ /**
+ * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
- var _activeEntity;
- var _startLoc;
+ function baseKeys(object) {
+ if (!isPrototype(object)) {
+ return nativeKeys(object);
+ }
- var _lastLoc;
+ var result = [];
- function startNudge(d3_event, entity, nudge) {
- if (_nudgeInterval) window.clearInterval(_nudgeInterval);
- _nudgeInterval = window.setInterval(function () {
- context.map().pan(nudge);
- doMove(d3_event, entity, nudge);
- }, 50);
- }
+ for (var key in Object(object)) {
+ if (hasOwnProperty.call(object, key) && key != 'constructor') {
+ result.push(key);
+ }
+ }
- function stopNudge() {
- if (_nudgeInterval) {
- window.clearInterval(_nudgeInterval);
- _nudgeInterval = null;
- }
- }
+ return result;
+ }
+ /**
+ * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
- function moveAnnotation(entity) {
- return _t('operations.move.annotation.' + entity.geometry(context.graph()));
- }
- function connectAnnotation(nodeEntity, targetEntity) {
- var nodeGeometry = nodeEntity.geometry(context.graph());
- var targetGeometry = targetEntity.geometry(context.graph());
+ function baseKeysIn(object) {
+ if (!isObject(object)) {
+ return nativeKeysIn(object);
+ }
- if (nodeGeometry === 'vertex' && targetGeometry === 'vertex') {
- var nodeParentWayIDs = context.graph().parentWays(nodeEntity);
- var targetParentWayIDs = context.graph().parentWays(targetEntity);
- var sharedParentWays = utilArrayIntersection(nodeParentWayIDs, targetParentWayIDs); // if both vertices are part of the same way
+ var isProto = isPrototype(object),
+ result = [];
- if (sharedParentWays.length !== 0) {
- // if the nodes are next to each other, they are merged
- if (sharedParentWays[0].areAdjacent(nodeEntity.id, targetEntity.id)) {
- return _t('operations.connect.annotation.from_vertex.to_adjacent_vertex');
+ for (var key in object) {
+ if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+ result.push(key);
+ }
}
- return _t('operations.connect.annotation.from_vertex.to_sibling_vertex');
+ return result;
}
- }
+ /**
+ * The base implementation of `_.lt` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than `other`,
+ * else `false`.
+ */
- return _t('operations.connect.annotation.from_' + nodeGeometry + '.to_' + targetGeometry);
- }
- function shouldSnapToNode(target) {
- if (!_activeEntity) return false;
- return _activeEntity.geometry(context.graph()) !== 'vertex' || target.geometry(context.graph()) === 'vertex' || _mainPresetIndex.allowsVertex(target, context.graph());
- }
+ function baseLt(value, other) {
+ return value < other;
+ }
+ /**
+ * The base implementation of `_.map` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} iteratee The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ */
- function origin(entity) {
- return context.projection(entity.loc);
- }
- function keydown(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope')) {
- context.surface().classed('nope-suppressed', true);
+ function baseMap(collection, iteratee) {
+ var index = -1,
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+ baseEach(collection, function (value, key, collection) {
+ result[++index] = iteratee(value, key, collection);
+ });
+ return result;
}
+ /**
+ * The base implementation of `_.matches` which doesn't clone `source`.
+ *
+ * @private
+ * @param {Object} source The object of property values to match.
+ * @returns {Function} Returns the new spec function.
+ */
- context.surface().classed('nope', false).classed('nope-disabled', true);
- }
- }
- function keyup(d3_event) {
- if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) {
- if (context.surface().classed('nope-suppressed')) {
- context.surface().classed('nope', true);
+ function baseMatches(source) {
+ var matchData = getMatchData(source);
+
+ if (matchData.length == 1 && matchData[0][2]) {
+ return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+ }
+
+ return function (object) {
+ return object === source || baseIsMatch(object, source, matchData);
+ };
}
+ /**
+ * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+ *
+ * @private
+ * @param {string} path The path of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ */
- context.surface().classed('nope-suppressed', false).classed('nope-disabled', false);
- }
- }
- function start(d3_event, entity) {
- _wasMidpoint = entity.type === 'midpoint';
- var hasHidden = context.features().hasHiddenConnections(entity, context.graph());
- _isCancelled = !context.editable() || d3_event.shiftKey || hasHidden;
+ function baseMatchesProperty(path, srcValue) {
+ if (isKey(path) && isStrictComparable(srcValue)) {
+ return matchesStrictComparable(toKey(path), srcValue);
+ }
- if (_isCancelled) {
- if (hasHidden) {
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('modes.drag_node.connected_to_hidden'))();
+ return function (object) {
+ var objValue = get(object, path);
+ return objValue === undefined$1 && objValue === srcValue ? hasIn(object, path) : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
+ };
}
+ /**
+ * The base implementation of `_.merge` without support for multiple sources.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} [customizer] The function to customize merged values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
- return drag.cancel();
- }
- if (_wasMidpoint) {
- var midpoint = entity;
- entity = osmNode();
- context.perform(actionAddMidpoint(midpoint, entity));
- entity = context.entity(entity.id); // get post-action entity
+ function baseMerge(object, source, srcIndex, customizer, stack) {
+ if (object === source) {
+ return;
+ }
- var vertex = context.surface().selectAll('.' + entity.id);
- drag.targetNode(vertex.node()).targetEntity(entity);
- } else {
- context.perform(actionNoop());
- }
+ baseFor(source, function (srcValue, key) {
+ stack || (stack = new Stack());
- _activeEntity = entity;
- _startLoc = entity.loc;
- hover.ignoreVertex(entity.geometry(context.graph()) === 'vertex');
- context.surface().selectAll('.' + _activeEntity.id).classed('active', true);
- context.enter(mode);
- } // related code
- // - `behavior/draw.js` `datum()`
+ if (isObject(srcValue)) {
+ baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+ } else {
+ var newValue = customizer ? customizer(safeGet(object, key), srcValue, key + '', object, source, stack) : undefined$1;
+ if (newValue === undefined$1) {
+ newValue = srcValue;
+ }
- function datum(d3_event) {
- if (!d3_event || d3_event.altKey) {
- return {};
- } else {
- // When dragging, snap only to touch targets..
- // (this excludes area fills and active drawing elements)
- var d = d3_event.target.__data__;
- return d && d.properties && d.properties.target ? d : {};
- }
- }
+ assignMergeValue(object, key, newValue);
+ }
+ }, keysIn);
+ }
+ /**
+ * A specialized version of `baseMerge` for arrays and objects which performs
+ * deep merges and tracks traversed objects enabling objects with circular
+ * references to be merged.
+ *
+ * @private
+ * @param {Object} object The destination object.
+ * @param {Object} source The source object.
+ * @param {string} key The key of the value to merge.
+ * @param {number} srcIndex The index of `source`.
+ * @param {Function} mergeFunc The function to merge values.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ */
- function doMove(d3_event, entity, nudge) {
- nudge = nudge || [0, 0];
- var currPoint = d3_event && d3_event.point || context.projection(_lastLoc);
- var currMouse = geoVecSubtract(currPoint, nudge);
- var loc = context.projection.invert(currMouse);
- var target, edge;
- if (!_nudgeInterval) {
- // If not nudging at the edge of the viewport, try to snap..
- // related code
- // - `mode/drag_node.js` `doMove()`
- // - `behavior/draw.js` `click()`
- // - `behavior/draw_way.js` `move()`
- var d = datum(d3_event);
- target = d && d.properties && d.properties.entity;
- var targetLoc = target && target.loc;
- var targetNodes = d && d.properties && d.properties.nodes;
+ function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+ var objValue = safeGet(object, key),
+ srcValue = safeGet(source, key),
+ stacked = stack.get(srcValue);
- if (targetLoc) {
- // snap to node/vertex - a point target with `.loc`
- if (shouldSnapToNode(target)) {
- loc = targetLoc;
+ if (stacked) {
+ assignMergeValue(object, key, stacked);
+ return;
}
- } else if (targetNodes) {
- // snap to way - a line target with `.nodes`
- edge = geoChooseEdge(targetNodes, context.map().mouse(), context.projection, end.id);
- if (edge) {
- loc = edge.loc;
+ var newValue = customizer ? customizer(objValue, srcValue, key + '', object, source, stack) : undefined$1;
+ var isCommon = newValue === undefined$1;
+
+ if (isCommon) {
+ var isArr = isArray(srcValue),
+ isBuff = !isArr && isBuffer(srcValue),
+ isTyped = !isArr && !isBuff && isTypedArray(srcValue);
+ newValue = srcValue;
+
+ if (isArr || isBuff || isTyped) {
+ if (isArray(objValue)) {
+ newValue = objValue;
+ } else if (isArrayLikeObject(objValue)) {
+ newValue = copyArray(objValue);
+ } else if (isBuff) {
+ isCommon = false;
+ newValue = cloneBuffer(srcValue, true);
+ } else if (isTyped) {
+ isCommon = false;
+ newValue = cloneTypedArray(srcValue, true);
+ } else {
+ newValue = [];
+ }
+ } else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+ newValue = objValue;
+
+ if (isArguments(objValue)) {
+ newValue = toPlainObject(objValue);
+ } else if (!isObject(objValue) || isFunction(objValue)) {
+ newValue = initCloneObject(srcValue);
+ }
+ } else {
+ isCommon = false;
+ }
}
+
+ if (isCommon) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ stack.set(srcValue, newValue);
+ mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+ stack['delete'](srcValue);
+ }
+
+ assignMergeValue(object, key, newValue);
}
- }
+ /**
+ * The base implementation of `_.nth` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {number} n The index of the element to return.
+ * @returns {*} Returns the nth element of `array`.
+ */
- context.replace(actionMoveNode(entity.id, loc)); // Below here: validations
- var isInvalid = false; // Check if this connection to `target` could cause relations to break..
+ function baseNth(array, n) {
+ var length = array.length;
- if (target) {
- isInvalid = hasRelationConflict(entity, target, edge, context.graph());
- } // Check if this drag causes the geometry to break..
+ if (!length) {
+ return;
+ }
+ n += n < 0 ? length : 0;
+ return isIndex(n, length) ? array[n] : undefined$1;
+ }
+ /**
+ * The base implementation of `_.orderBy` without param guards.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+ * @param {string[]} orders The sort orders of `iteratees`.
+ * @returns {Array} Returns the new sorted array.
+ */
- if (!isInvalid) {
- isInvalid = hasInvalidGeometry(entity, context.graph());
- }
- var nope = context.surface().classed('nope');
+ function baseOrderBy(collection, iteratees, orders) {
+ if (iteratees.length) {
+ iteratees = arrayMap(iteratees, function (iteratee) {
+ if (isArray(iteratee)) {
+ return function (value) {
+ return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee);
+ };
+ }
- if (isInvalid === 'relation' || isInvalid === 'restriction') {
- if (!nope) {
- // about to nope - show hint
- context.ui().flash.duration(4000).iconName('#iD-icon-no').label(_t('operations.connect.' + isInvalid, {
- relation: _mainPresetIndex.item('type/restriction').name()
- }))();
- }
- } else if (isInvalid) {
- var errorID = isInvalid === 'line' ? 'lines' : 'areas';
- context.ui().flash.duration(3000).iconName('#iD-icon-no').label(_t('self_intersection.error.' + errorID))();
- } else {
- if (nope) {
- // about to un-nope, remove hint
- context.ui().flash.duration(1).label('')();
+ return iteratee;
+ });
+ } else {
+ iteratees = [identity];
+ }
+
+ var index = -1;
+ iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
+ var result = baseMap(collection, function (value, key, collection) {
+ var criteria = arrayMap(iteratees, function (iteratee) {
+ return iteratee(value);
+ });
+ return {
+ 'criteria': criteria,
+ 'index': ++index,
+ 'value': value
+ };
+ });
+ return baseSortBy(result, function (object, other) {
+ return compareMultiple(object, other, orders);
+ });
}
- }
+ /**
+ * The base implementation of `_.pick` without support for individual
+ * property identifiers.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {string[]} paths The property paths to pick.
+ * @returns {Object} Returns the new object.
+ */
- var nopeDisabled = context.surface().classed('nope-disabled');
- if (nopeDisabled) {
- context.surface().classed('nope', false).classed('nope-suppressed', isInvalid);
- } else {
- context.surface().classed('nope', isInvalid).classed('nope-suppressed', false);
- }
+ function basePick(object, paths) {
+ return basePickBy(object, paths, function (value, path) {
+ return hasIn(object, path);
+ });
+ }
+ /**
+ * The base implementation of `_.pickBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Object} object The source object.
+ * @param {string[]} paths The property paths to pick.
+ * @param {Function} predicate The function invoked per property.
+ * @returns {Object} Returns the new object.
+ */
- _lastLoc = loc;
- } // Uses `actionConnect.disabled()` to know whether this connection is ok..
+ function basePickBy(object, paths, predicate) {
+ var index = -1,
+ length = paths.length,
+ result = {};
- function hasRelationConflict(entity, target, edge, graph) {
- var testGraph = graph.update(); // copy
- // if snapping to way - add midpoint there and consider that the target..
+ while (++index < length) {
+ var path = paths[index],
+ value = baseGet(object, path);
- if (edge) {
- var midpoint = osmNode();
- var action = actionAddMidpoint({
- loc: edge.loc,
- edge: [target.nodes[edge.index - 1], target.nodes[edge.index]]
- }, midpoint);
- testGraph = action(testGraph);
- target = midpoint;
- } // can we connect to it?
+ if (predicate(value, path)) {
+ baseSet(result, castPath(path, object), value);
+ }
+ }
+ return result;
+ }
+ /**
+ * A specialized version of `baseProperty` which supports deep paths.
+ *
+ * @private
+ * @param {Array|string} path The path of the property to get.
+ * @returns {Function} Returns the new accessor function.
+ */
- var ids = [entity.id, target.id];
- return actionConnect(ids).disabled(testGraph);
- }
- function hasInvalidGeometry(entity, graph) {
- var parents = graph.parentWays(entity);
- var i, j, k;
+ function basePropertyDeep(path) {
+ return function (object) {
+ return baseGet(object, path);
+ };
+ }
+ /**
+ * The base implementation of `_.pullAllBy` without support for iteratee
+ * shorthands.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ */
- for (i = 0; i < parents.length; i++) {
- var parent = parents[i];
- var nodes = [];
- var activeIndex = null; // which multipolygon ring contains node being dragged
- // test any parent multipolygons for valid geometry
- var relations = graph.parentRelations(parent);
+ function basePullAll(array, values, iteratee, comparator) {
+ var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+ index = -1,
+ length = values.length,
+ seen = array;
- for (j = 0; j < relations.length; j++) {
- if (!relations[j].isMultipolygon()) continue;
- var rings = osmJoinWays(relations[j].members, graph); // find active ring and test it for self intersections
+ if (array === values) {
+ values = copyArray(values);
+ }
- for (k = 0; k < rings.length; k++) {
- nodes = rings[k].nodes;
+ if (iteratee) {
+ seen = arrayMap(array, baseUnary(iteratee));
+ }
- if (nodes.find(function (n) {
- return n.id === entity.id;
- })) {
- activeIndex = k;
+ while (++index < length) {
+ var fromIndex = 0,
+ value = values[index],
+ computed = iteratee ? iteratee(value) : value;
- if (geoHasSelfIntersections(nodes, entity.id)) {
- return 'multipolygonMember';
+ while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+ if (seen !== array) {
+ splice.call(seen, fromIndex, 1);
}
+
+ splice.call(array, fromIndex, 1);
}
+ }
- rings[k].coords = nodes.map(function (n) {
- return n.loc;
- });
- } // test active ring for intersections with other rings in the multipolygon
+ return array;
+ }
+ /**
+ * The base implementation of `_.pullAt` without support for individual
+ * indexes or capturing the removed elements.
+ *
+ * @private
+ * @param {Array} array The array to modify.
+ * @param {number[]} indexes The indexes of elements to remove.
+ * @returns {Array} Returns `array`.
+ */
- for (k = 0; k < rings.length; k++) {
- if (k === activeIndex) continue; // make sure active ring doesn't cross passive rings
+ function basePullAt(array, indexes) {
+ var length = array ? indexes.length : 0,
+ lastIndex = length - 1;
- if (geoHasLineIntersections(rings[activeIndex].nodes, rings[k].nodes, entity.id)) {
- return 'multipolygonRing';
+ while (length--) {
+ var index = indexes[length];
+
+ if (length == lastIndex || index !== previous) {
+ var previous = index;
+
+ if (isIndex(index)) {
+ splice.call(array, index, 1);
+ } else {
+ baseUnset(array, index);
+ }
}
}
- } // If we still haven't tested this node's parent way for self-intersections.
- // (because it's not a member of a multipolygon), test it now.
+ return array;
+ }
+ /**
+ * The base implementation of `_.random` without support for returning
+ * floating-point numbers.
+ *
+ * @private
+ * @param {number} lower The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the random number.
+ */
- if (activeIndex === null) {
- nodes = parent.nodes.map(function (nodeID) {
- return graph.entity(nodeID);
- });
- if (nodes.length && geoHasSelfIntersections(nodes, entity.id)) {
- return parent.geometry(graph);
- }
+ function baseRandom(lower, upper) {
+ return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
}
- }
+ /**
+ * The base implementation of `_.range` and `_.rangeRight` which doesn't
+ * coerce arguments.
+ *
+ * @private
+ * @param {number} start The start of the range.
+ * @param {number} end The end of the range.
+ * @param {number} step The value to increment or decrement by.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the range of numbers.
+ */
- return false;
- }
- function move(d3_event, entity, point) {
- if (_isCancelled) return;
- d3_event.stopPropagation();
- context.surface().classed('nope-disabled', d3_event.altKey);
- _lastLoc = context.projection.invert(point);
- doMove(d3_event, entity);
- var nudge = geoViewportEdge(point, context.map().dimensions());
+ function baseRange(start, end, step, fromRight) {
+ var index = -1,
+ length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+ result = Array(length);
- if (nudge) {
- startNudge(d3_event, entity, nudge);
- } else {
- stopNudge();
- }
- }
+ while (length--) {
+ result[fromRight ? length : ++index] = start;
+ start += step;
+ }
- function end(d3_event, entity) {
- if (_isCancelled) return;
- var wasPoint = entity.geometry(context.graph()) === 'point';
- var d = datum(d3_event);
- var nope = d && d.properties && d.properties.nope || context.surface().classed('nope');
- var target = d && d.properties && d.properties.entity; // entity to snap to
+ return result;
+ }
+ /**
+ * The base implementation of `_.repeat` which doesn't coerce arguments.
+ *
+ * @private
+ * @param {string} string The string to repeat.
+ * @param {number} n The number of times to repeat the string.
+ * @returns {string} Returns the repeated string.
+ */
- if (nope) {
- // bounce back
- context.perform(_actionBounceBack(entity.id, _startLoc));
- } else if (target && target.type === 'way') {
- var choice = geoChooseEdge(context.graph().childNodes(target), context.map().mouse(), context.projection, entity.id);
- context.replace(actionAddMidpoint({
- loc: choice.loc,
- edge: [target.nodes[choice.index - 1], target.nodes[choice.index]]
- }, entity), connectAnnotation(entity, target));
- } else if (target && target.type === 'node' && shouldSnapToNode(target)) {
- context.replace(actionConnect([target.id, entity.id]), connectAnnotation(entity, target));
- } else if (_wasMidpoint) {
- context.replace(actionNoop(), _t('operations.add.annotation.vertex'));
- } else {
- context.replace(actionNoop(), moveAnnotation(entity));
- }
- if (wasPoint) {
- context.enter(modeSelect(context, [entity.id]));
- } else {
- var reselection = _restoreSelectedIDs.filter(function (id) {
- return context.graph().hasEntity(id);
- });
+ function baseRepeat(string, n) {
+ var result = '';
- if (reselection.length) {
- context.enter(modeSelect(context, reselection));
- } else {
- context.enter(modeBrowse(context));
- }
- }
- }
+ if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+ return result;
+ } // Leverage the exponentiation by squaring algorithm for a faster repeat.
+ // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
- function _actionBounceBack(nodeID, toLoc) {
- var moveNode = actionMoveNode(nodeID, toLoc);
- var action = function action(graph, t) {
- // last time through, pop off the bounceback perform.
- // it will then overwrite the initial perform with a moveNode that does nothing
- if (t === 1) context.pop();
- return moveNode(graph, t);
- };
+ do {
+ if (n % 2) {
+ result += string;
+ }
- action.transitionable = true;
- return action;
- }
+ n = nativeFloor(n / 2);
- function cancel() {
- drag.cancel();
- context.enter(modeBrowse(context));
- }
+ if (n) {
+ string += string;
+ }
+ } while (n);
- var drag = behaviorDrag().selector('.layer-touch.points .target').surface(context.container().select('.main-map').node()).origin(origin).on('start', start).on('move', move).on('end', end);
+ return result;
+ }
+ /**
+ * The base implementation of `_.rest` which doesn't validate or coerce arguments.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ */
- mode.enter = function () {
- context.install(hover);
- context.install(edit);
- select(window).on('keydown.dragNode', keydown).on('keyup.dragNode', keyup);
- context.history().on('undone.drag-node', cancel);
- };
- mode.exit = function () {
- context.ui().sidebar.hover.cancel();
- context.uninstall(hover);
- context.uninstall(edit);
- select(window).on('keydown.dragNode', null).on('keyup.dragNode', null);
- context.history().on('undone.drag-node', null);
- _activeEntity = null;
- context.surface().classed('nope', false).classed('nope-suppressed', false).classed('nope-disabled', false).selectAll('.active').classed('active', false);
- stopNudge();
- };
+ function baseRest(func, start) {
+ return setToString(overRest(func, start, identity), func + '');
+ }
+ /**
+ * The base implementation of `_.sample`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to sample.
+ * @returns {*} Returns the random element.
+ */
- mode.selectedIDs = function () {
- if (!arguments.length) return _activeEntity ? [_activeEntity.id] : []; // no assign
- return mode;
- };
+ function baseSample(collection) {
+ return arraySample(values(collection));
+ }
+ /**
+ * The base implementation of `_.sampleSize` without param guards.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to sample.
+ * @param {number} n The number of elements to sample.
+ * @returns {Array} Returns the random elements.
+ */
- mode.activeID = function () {
- if (!arguments.length) return _activeEntity && _activeEntity.id; // no assign
- return mode;
- };
+ function baseSampleSize(collection, n) {
+ var array = values(collection);
+ return shuffleSelf(array, baseClamp(n, 0, array.length));
+ }
+ /**
+ * The base implementation of `_.set`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
- mode.restoreSelectedIDs = function (_) {
- if (!arguments.length) return _restoreSelectedIDs;
- _restoreSelectedIDs = _;
- return mode;
- };
- mode.behavior = drag;
- return mode;
- }
+ function baseSet(object, path, value, customizer) {
+ if (!isObject(object)) {
+ return object;
+ }
- // Safari bug https://bugs.webkit.org/show_bug.cgi?id=200829
- var NON_GENERIC = !!nativePromiseConstructor && fails(function () {
- nativePromiseConstructor.prototype['finally'].call({ then: function () { /* empty */ } }, function () { /* empty */ });
- });
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length,
+ lastIndex = length - 1,
+ nested = object;
- // `Promise.prototype.finally` method
- // https://tc39.es/ecma262/#sec-promise.prototype.finally
- _export({ target: 'Promise', proto: true, real: true, forced: NON_GENERIC }, {
- 'finally': function (onFinally) {
- var C = speciesConstructor(this, getBuiltIn('Promise'));
- var isFunction = typeof onFinally == 'function';
- return this.then(
- isFunction ? function (x) {
- return promiseResolve(C, onFinally()).then(function () { return x; });
- } : onFinally,
- isFunction ? function (e) {
- return promiseResolve(C, onFinally()).then(function () { throw e; });
- } : onFinally
- );
- }
- });
+ while (nested != null && ++index < length) {
+ var key = toKey(path[index]),
+ newValue = value;
- // makes sure that native promise-based APIs `Promise#finally` properly works with patched `Promise#then`
- if (typeof nativePromiseConstructor == 'function') {
- var method = getBuiltIn('Promise').prototype['finally'];
- if (nativePromiseConstructor.prototype['finally'] !== method) {
- redefine(nativePromiseConstructor.prototype, 'finally', method, { unsafe: true });
- }
- }
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
+ return object;
+ }
- function quickselect(arr, k, left, right, compare) {
- quickselectStep(arr, k, left || 0, right || arr.length - 1, compare || defaultCompare);
- }
+ if (index != lastIndex) {
+ var objValue = nested[key];
+ newValue = customizer ? customizer(objValue, key, nested) : undefined$1;
- function quickselectStep(arr, k, left, right, compare) {
- while (right > left) {
- if (right - left > 600) {
- var n = right - left + 1;
- var m = k - left + 1;
- var z = Math.log(n);
- var s = 0.5 * Math.exp(2 * z / 3);
- var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
- var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
- var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
- quickselectStep(arr, k, newLeft, newRight, compare);
- }
+ if (newValue === undefined$1) {
+ newValue = isObject(objValue) ? objValue : isIndex(path[index + 1]) ? [] : {};
+ }
+ }
- var t = arr[k];
- var i = left;
- var j = right;
- swap(arr, left, k);
- if (compare(arr[right], t) > 0) swap(arr, left, right);
+ assignValue(nested, key, newValue);
+ nested = nested[key];
+ }
- while (i < j) {
- swap(arr, i, j);
- i++;
- j--;
+ return object;
+ }
+ /**
+ * The base implementation of `setData` without support for hot loop shorting.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
- while (compare(arr[i], t) < 0) {
- i++;
+
+ var baseSetData = !metaMap ? identity : function (func, data) {
+ metaMap.set(func, data);
+ return func;
+ };
+ /**
+ * The base implementation of `setToString` without support for hot loop shorting.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
+
+ var baseSetToString = !defineProperty ? identity : function (func, string) {
+ return defineProperty(func, 'toString', {
+ 'configurable': true,
+ 'enumerable': false,
+ 'value': constant(string),
+ 'writable': true
+ });
+ };
+ /**
+ * The base implementation of `_.shuffle`.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ */
+
+ function baseShuffle(collection) {
+ return shuffleSelf(values(collection));
}
+ /**
+ * The base implementation of `_.slice` without an iteratee call guard.
+ *
+ * @private
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
- while (compare(arr[j], t) > 0) {
- j--;
+
+ function baseSlice(array, start, end) {
+ var index = -1,
+ length = array.length;
+
+ if (start < 0) {
+ start = -start > length ? 0 : length + start;
+ }
+
+ end = end > length ? length : end;
+
+ if (end < 0) {
+ end += length;
+ }
+
+ length = start > end ? 0 : end - start >>> 0;
+ start >>>= 0;
+ var result = Array(length);
+
+ while (++index < length) {
+ result[index] = array[index + start];
+ }
+
+ return result;
}
- }
+ /**
+ * The base implementation of `_.some` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} predicate The function invoked per iteration.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ */
- if (compare(arr[left], t) === 0) swap(arr, left, j);else {
- j++;
- swap(arr, j, right);
- }
- if (j <= k) left = j + 1;
- if (k <= j) right = j - 1;
- }
- }
- function swap(arr, i, j) {
- var tmp = arr[i];
- arr[i] = arr[j];
- arr[j] = tmp;
- }
+ function baseSome(collection, predicate) {
+ var result;
+ baseEach(collection, function (value, index, collection) {
+ result = predicate(value, index, collection);
+ return !result;
+ });
+ return !!result;
+ }
+ /**
+ * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+ * performs a binary search of `array` to determine the index at which `value`
+ * should be inserted into `array` in order to maintain its sort order.
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ */
- function defaultCompare(a, b) {
- return a < b ? -1 : a > b ? 1 : 0;
- }
- var RBush = /*#__PURE__*/function () {
- function RBush() {
- var maxEntries = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 9;
+ function baseSortedIndex(array, value, retHighest) {
+ var low = 0,
+ high = array == null ? low : array.length;
- _classCallCheck$1(this, RBush);
+ if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+ while (low < high) {
+ var mid = low + high >>> 1,
+ computed = array[mid];
- // max entries in a node is 9 by default; min node fill is 40% for best performance
- this._maxEntries = Math.max(4, maxEntries);
- this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
- this.clear();
- }
+ if (computed !== null && !isSymbol(computed) && (retHighest ? computed <= value : computed < value)) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
- _createClass$1(RBush, [{
- key: "all",
- value: function all() {
- return this._all(this.data, []);
- }
- }, {
- key: "search",
- value: function search(bbox) {
- var node = this.data;
- var result = [];
- if (!intersects(bbox, node)) return result;
- var toBBox = this.toBBox;
- var nodesToSearch = [];
+ return high;
+ }
- while (node) {
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var childBBox = node.leaf ? toBBox(child) : child;
+ return baseSortedIndexBy(array, value, identity, retHighest);
+ }
+ /**
+ * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+ * which invokes `iteratee` for `value` and each element of `array` to compute
+ * their sort ranking. The iteratee is invoked with one argument; (value).
+ *
+ * @private
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} iteratee The iteratee invoked per element.
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ */
- if (intersects(bbox, childBBox)) {
- if (node.leaf) result.push(child);else if (contains(bbox, childBBox)) this._all(child, result);else nodesToSearch.push(child);
+
+ function baseSortedIndexBy(array, value, iteratee, retHighest) {
+ var low = 0,
+ high = array == null ? 0 : array.length;
+
+ if (high === 0) {
+ return 0;
+ }
+
+ value = iteratee(value);
+ var valIsNaN = value !== value,
+ valIsNull = value === null,
+ valIsSymbol = isSymbol(value),
+ valIsUndefined = value === undefined$1;
+
+ while (low < high) {
+ var mid = nativeFloor((low + high) / 2),
+ computed = iteratee(array[mid]),
+ othIsDefined = computed !== undefined$1,
+ othIsNull = computed === null,
+ othIsReflexive = computed === computed,
+ othIsSymbol = isSymbol(computed);
+
+ if (valIsNaN) {
+ var setLow = retHighest || othIsReflexive;
+ } else if (valIsUndefined) {
+ setLow = othIsReflexive && (retHighest || othIsDefined);
+ } else if (valIsNull) {
+ setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+ } else if (valIsSymbol) {
+ setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+ } else if (othIsNull || othIsSymbol) {
+ setLow = false;
+ } else {
+ setLow = retHighest ? computed <= value : computed < value;
+ }
+
+ if (setLow) {
+ low = mid + 1;
+ } else {
+ high = mid;
}
}
- node = nodesToSearch.pop();
+ return nativeMin(high, MAX_ARRAY_INDEX);
}
+ /**
+ * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+ * support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
- return result;
- }
- }, {
- key: "collides",
- value: function collides(bbox) {
- var node = this.data;
- if (!intersects(bbox, node)) return false;
- var nodesToSearch = [];
- while (node) {
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var childBBox = node.leaf ? this.toBBox(child) : child;
+ function baseSortedUniq(array, iteratee) {
+ var index = -1,
+ length = array.length,
+ resIndex = 0,
+ result = [];
- if (intersects(bbox, childBBox)) {
- if (node.leaf || contains(bbox, childBBox)) return true;
- nodesToSearch.push(child);
+ while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+
+ if (!index || !eq(computed, seen)) {
+ var seen = computed;
+ result[resIndex++] = value === 0 ? 0 : value;
}
}
- node = nodesToSearch.pop();
+ return result;
}
+ /**
+ * The base implementation of `_.toNumber` which doesn't ensure correct
+ * conversions of binary, hexadecimal, or octal string values.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ */
- return false;
- }
- }, {
- key: "load",
- value: function load(data) {
- if (!(data && data.length)) return this;
- if (data.length < this._minEntries) {
- for (var i = 0; i < data.length; i++) {
- this.insert(data[i]);
+ function baseToNumber(value) {
+ if (typeof value == 'number') {
+ return value;
}
- return this;
- } // recursively build the tree with the given data from scratch using OMT algorithm
+ if (isSymbol(value)) {
+ return NAN;
+ }
+
+ return +value;
+ }
+ /**
+ * The base implementation of `_.toString` which doesn't convert nullish
+ * values to empty strings.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ */
- var node = this._build(data.slice(), 0, data.length - 1, 0);
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
- if (!this.data.children.length) {
- // save as is if tree is empty
- this.data = node;
- } else if (this.data.height === node.height) {
- // split root if trees have the same height
- this._splitRoot(this.data, node);
- } else {
- if (this.data.height < node.height) {
- // swap trees if inserted one is bigger
- var tmpNode = this.data;
- this.data = node;
- node = tmpNode;
- } // insert the small tree into the large tree at appropriate level
+ if (isArray(value)) {
+ // Recursively convert values (susceptible to call stack limits).
+ return arrayMap(value, baseToString) + '';
+ }
+ if (isSymbol(value)) {
+ return symbolToString ? symbolToString.call(value) : '';
+ }
- this._insert(node, this.data.height - node.height - 1, true);
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
}
+ /**
+ * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ */
- return this;
- }
- }, {
- key: "insert",
- value: function insert(item) {
- if (item) this._insert(item, this.data.height - 1);
- return this;
- }
- }, {
- key: "clear",
- value: function clear() {
- this.data = createNode([]);
- return this;
- }
- }, {
- key: "remove",
- value: function remove(item, equalsFn) {
- if (!item) return this;
- var node = this.data;
- var bbox = this.toBBox(item);
- var path = [];
- var indexes = [];
- var i, parent, goingUp; // depth-first iterative tree traversal
- while (node || path.length) {
- if (!node) {
- // go up
- node = path.pop();
- parent = path[path.length - 1];
- i = indexes.pop();
- goingUp = true;
+ function baseUniq(array, iteratee, comparator) {
+ var index = -1,
+ includes = arrayIncludes,
+ length = array.length,
+ isCommon = true,
+ result = [],
+ seen = result;
+
+ if (comparator) {
+ isCommon = false;
+ includes = arrayIncludesWith;
+ } else if (length >= LARGE_ARRAY_SIZE) {
+ var set = iteratee ? null : createSet(array);
+
+ if (set) {
+ return setToArray(set);
+ }
+
+ isCommon = false;
+ includes = cacheHas;
+ seen = new SetCache();
+ } else {
+ seen = iteratee ? [] : result;
}
- if (node.leaf) {
- // check current node
- var index = findItem(item, node.children, equalsFn);
+ outer: while (++index < length) {
+ var value = array[index],
+ computed = iteratee ? iteratee(value) : value;
+ value = comparator || value !== 0 ? value : 0;
- if (index !== -1) {
- // item found, remove the item and condense tree upwards
- node.children.splice(index, 1);
- path.push(node);
+ if (isCommon && computed === computed) {
+ var seenIndex = seen.length;
- this._condense(path);
+ while (seenIndex--) {
+ if (seen[seenIndex] === computed) {
+ continue outer;
+ }
+ }
- return this;
+ if (iteratee) {
+ seen.push(computed);
+ }
+
+ result.push(value);
+ } else if (!includes(seen, computed, comparator)) {
+ if (seen !== result) {
+ seen.push(computed);
+ }
+
+ result.push(value);
}
}
- if (!goingUp && !node.leaf && contains(node, bbox)) {
- // go down
- path.push(node);
- indexes.push(i);
- i = 0;
- parent = node;
- node = node.children[0];
- } else if (parent) {
- // go right
- i++;
- node = parent.children[i];
- goingUp = false;
- } else node = null; // nothing found
-
+ return result;
}
+ /**
+ * The base implementation of `_.unset`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The property path to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ */
- return this;
- }
- }, {
- key: "toBBox",
- value: function toBBox(item) {
- return item;
- }
- }, {
- key: "compareMinX",
- value: function compareMinX(a, b) {
- return a.minX - b.minX;
- }
- }, {
- key: "compareMinY",
- value: function compareMinY(a, b) {
- return a.minY - b.minY;
- }
- }, {
- key: "toJSON",
- value: function toJSON() {
- return this.data;
- }
- }, {
- key: "fromJSON",
- value: function fromJSON(data) {
- this.data = data;
- return this;
- }
- }, {
- key: "_all",
- value: function _all(node, result) {
- var nodesToSearch = [];
- while (node) {
- if (node.leaf) result.push.apply(result, _toConsumableArray(node.children));else nodesToSearch.push.apply(nodesToSearch, _toConsumableArray(node.children));
- node = nodesToSearch.pop();
+ function baseUnset(object, path) {
+ path = castPath(path, object);
+ object = parent(object, path);
+ return object == null || delete object[toKey(last(path))];
}
+ /**
+ * The base implementation of `_.update`.
+ *
+ * @private
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to update.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize path creation.
+ * @returns {Object} Returns `object`.
+ */
- return result;
- }
- }, {
- key: "_build",
- value: function _build(items, left, right, height) {
- var N = right - left + 1;
- var M = this._maxEntries;
- var node;
- if (N <= M) {
- // reached leaf level; return leaf
- node = createNode(items.slice(left, right + 1));
- calcBBox(node, this.toBBox);
- return node;
+ function baseUpdate(object, path, updater, customizer) {
+ return baseSet(object, path, updater(baseGet(object, path)), customizer);
}
+ /**
+ * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+ * without support for iteratee shorthands.
+ *
+ * @private
+ * @param {Array} array The array to query.
+ * @param {Function} predicate The function invoked per iteration.
+ * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Array} Returns the slice of `array`.
+ */
- if (!height) {
- // target height of the bulk-loaded tree
- height = Math.ceil(Math.log(N) / Math.log(M)); // target number of root entries to maximize storage utilization
- M = Math.ceil(N / Math.pow(M, height - 1));
- }
+ function baseWhile(array, predicate, isDrop, fromRight) {
+ var length = array.length,
+ index = fromRight ? length : -1;
- node = createNode([]);
- node.leaf = false;
- node.height = height; // split the items into M mostly square tiles
+ while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
- var N2 = Math.ceil(N / M);
- var N1 = N2 * Math.ceil(Math.sqrt(M));
- multiSelect(items, left, right, N1, this.compareMinX);
+ return isDrop ? baseSlice(array, fromRight ? 0 : index, fromRight ? index + 1 : length) : baseSlice(array, fromRight ? index + 1 : 0, fromRight ? length : index);
+ }
+ /**
+ * The base implementation of `wrapperValue` which returns the result of
+ * performing a sequence of actions on the unwrapped `value`, where each
+ * successive action is supplied the return value of the previous.
+ *
+ * @private
+ * @param {*} value The unwrapped value.
+ * @param {Array} actions Actions to perform to resolve the unwrapped value.
+ * @returns {*} Returns the resolved value.
+ */
- for (var i = left; i <= right; i += N1) {
- var right2 = Math.min(i + N1 - 1, right);
- multiSelect(items, i, right2, N2, this.compareMinY);
- for (var j = i; j <= right2; j += N2) {
- var right3 = Math.min(j + N2 - 1, right2); // pack each entry recursively
+ function baseWrapperValue(value, actions) {
+ var result = value;
- node.children.push(this._build(items, j, right3, height - 1));
+ if (result instanceof LazyWrapper) {
+ result = result.value();
}
+
+ return arrayReduce(actions, function (result, action) {
+ return action.func.apply(action.thisArg, arrayPush([result], action.args));
+ }, result);
}
+ /**
+ * The base implementation of methods like `_.xor`, without support for
+ * iteratee shorthands, that accepts an array of arrays to inspect.
+ *
+ * @private
+ * @param {Array} arrays The arrays to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of values.
+ */
- calcBBox(node, this.toBBox);
- return node;
- }
- }, {
- key: "_chooseSubtree",
- value: function _chooseSubtree(bbox, node, level, path) {
- while (true) {
- path.push(node);
- if (node.leaf || path.length - 1 === level) break;
- var minArea = Infinity;
- var minEnlargement = Infinity;
- var targetNode = void 0;
- for (var i = 0; i < node.children.length; i++) {
- var child = node.children[i];
- var area = bboxArea(child);
- var enlargement = enlargedArea(bbox, child) - area; // choose entry with the least area enlargement
+ function baseXor(arrays, iteratee, comparator) {
+ var length = arrays.length;
- if (enlargement < minEnlargement) {
- minEnlargement = enlargement;
- minArea = area < minArea ? area : minArea;
- targetNode = child;
- } else if (enlargement === minEnlargement) {
- // otherwise choose one with the smallest area
- if (area < minArea) {
- minArea = area;
- targetNode = child;
+ if (length < 2) {
+ return length ? baseUniq(arrays[0]) : [];
+ }
+
+ var index = -1,
+ result = Array(length);
+
+ while (++index < length) {
+ var array = arrays[index],
+ othIndex = -1;
+
+ while (++othIndex < length) {
+ if (othIndex != index) {
+ result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
}
}
}
- node = targetNode || node.children[0];
+ return baseUniq(baseFlatten(result, 1), iteratee, comparator);
}
+ /**
+ * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+ *
+ * @private
+ * @param {Array} props The property identifiers.
+ * @param {Array} values The property values.
+ * @param {Function} assignFunc The function to assign values.
+ * @returns {Object} Returns the new object.
+ */
- return node;
- }
- }, {
- key: "_insert",
- value: function _insert(item, level, isNode) {
- var bbox = isNode ? item : this.toBBox(item);
- var insertPath = []; // find the best node for accommodating the item, saving all nodes along the path too
- var node = this._chooseSubtree(bbox, this.data, level, insertPath); // put the item into the node
+ function baseZipObject(props, values, assignFunc) {
+ var index = -1,
+ length = props.length,
+ valsLength = values.length,
+ result = {};
+ while (++index < length) {
+ var value = index < valsLength ? values[index] : undefined$1;
+ assignFunc(result, props[index], value);
+ }
- node.children.push(item);
- extend$1(node, bbox); // split on node overflow; propagate upwards if necessary
+ return result;
+ }
+ /**
+ * Casts `value` to an empty array if it's not an array like object.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array|Object} Returns the cast array-like object.
+ */
- while (level >= 0) {
- if (insertPath[level].children.length > this._maxEntries) {
- this._split(insertPath, level);
- level--;
- } else break;
- } // adjust bboxes along the insertion path
+ function castArrayLikeObject(value) {
+ return isArrayLikeObject(value) ? value : [];
+ }
+ /**
+ * Casts `value` to `identity` if it's not a function.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Function} Returns cast function.
+ */
- this._adjustParentBBoxes(bbox, insertPath, level);
- } // split overflowed node into two
+ function castFunction(value) {
+ return typeof value == 'function' ? value : identity;
+ }
+ /**
+ * Casts `value` to a path array if it's not one.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {Array} Returns the cast property path array.
+ */
- }, {
- key: "_split",
- value: function _split(insertPath, level) {
- var node = insertPath[level];
- var M = node.children.length;
- var m = this._minEntries;
- this._chooseSplitAxis(node, m, M);
+ function castPath(value, object) {
+ if (isArray(value)) {
+ return value;
+ }
- var splitIndex = this._chooseSplitIndex(node, m, M);
+ return isKey(value, object) ? [value] : stringToPath(toString(value));
+ }
+ /**
+ * A `baseRest` alias which can be replaced with `identity` by module
+ * replacement plugins.
+ *
+ * @private
+ * @type {Function}
+ * @param {Function} func The function to apply a rest parameter to.
+ * @returns {Function} Returns the new function.
+ */
- var newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
- newNode.height = node.height;
- newNode.leaf = node.leaf;
- calcBBox(node, this.toBBox);
- calcBBox(newNode, this.toBBox);
- if (level) insertPath[level - 1].children.push(newNode);else this._splitRoot(node, newNode);
- }
- }, {
- key: "_splitRoot",
- value: function _splitRoot(node, newNode) {
- // split root node
- this.data = createNode([node, newNode]);
- this.data.height = node.height + 1;
- this.data.leaf = false;
- calcBBox(this.data, this.toBBox);
- }
- }, {
- key: "_chooseSplitIndex",
- value: function _chooseSplitIndex(node, m, M) {
- var index;
- var minOverlap = Infinity;
- var minArea = Infinity;
- for (var i = m; i <= M - m; i++) {
- var bbox1 = distBBox(node, 0, i, this.toBBox);
- var bbox2 = distBBox(node, i, M, this.toBBox);
- var overlap = intersectionArea(bbox1, bbox2);
- var area = bboxArea(bbox1) + bboxArea(bbox2); // choose distribution with minimum overlap
+ var castRest = baseRest;
+ /**
+ * Casts `array` to a slice if it's needed.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {number} start The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the cast slice.
+ */
- if (overlap < minOverlap) {
- minOverlap = overlap;
- index = i;
- minArea = area < minArea ? area : minArea;
- } else if (overlap === minOverlap) {
- // otherwise choose distribution with minimum area
- if (area < minArea) {
- minArea = area;
- index = i;
- }
- }
+ function castSlice(array, start, end) {
+ var length = array.length;
+ end = end === undefined$1 ? length : end;
+ return !start && end >= length ? array : baseSlice(array, start, end);
}
+ /**
+ * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
+ *
+ * @private
+ * @param {number|Object} id The timer id or timeout object of the timer to clear.
+ */
- return index || M - m;
- } // sorts node children by the best axis for split
-
- }, {
- key: "_chooseSplitAxis",
- value: function _chooseSplitAxis(node, m, M) {
- var compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
- var compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
- var xMargin = this._allDistMargin(node, m, M, compareMinX);
+ var clearTimeout = ctxClearTimeout || function (id) {
+ return root.clearTimeout(id);
+ };
+ /**
+ * Creates a clone of `buffer`.
+ *
+ * @private
+ * @param {Buffer} buffer The buffer to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Buffer} Returns the cloned buffer.
+ */
- var yMargin = this._allDistMargin(node, m, M, compareMinY); // if total distributions margin value is minimal for x, sort by minX,
- // otherwise it's already sorted by minY
+ function cloneBuffer(buffer, isDeep) {
+ if (isDeep) {
+ return buffer.slice();
+ }
- if (xMargin < yMargin) node.children.sort(compareMinX);
- } // total margin of all possible split distributions where each node is at least m full
+ var length = buffer.length,
+ result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
+ buffer.copy(result);
+ return result;
+ }
+ /**
+ * Creates a clone of `arrayBuffer`.
+ *
+ * @private
+ * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
+ */
- }, {
- key: "_allDistMargin",
- value: function _allDistMargin(node, m, M, compare) {
- node.children.sort(compare);
- var toBBox = this.toBBox;
- var leftBBox = distBBox(node, 0, m, toBBox);
- var rightBBox = distBBox(node, M - m, M, toBBox);
- var margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
- for (var i = m; i < M - m; i++) {
- var child = node.children[i];
- extend$1(leftBBox, node.leaf ? toBBox(child) : child);
- margin += bboxMargin(leftBBox);
+ function cloneArrayBuffer(arrayBuffer) {
+ var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+ new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+ return result;
}
+ /**
+ * Creates a clone of `dataView`.
+ *
+ * @private
+ * @param {Object} dataView The data view to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned data view.
+ */
- for (var _i = M - m - 1; _i >= m; _i--) {
- var _child = node.children[_i];
- extend$1(rightBBox, node.leaf ? toBBox(_child) : _child);
- margin += bboxMargin(rightBBox);
+
+ function cloneDataView(dataView, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+ return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
}
+ /**
+ * Creates a clone of `regexp`.
+ *
+ * @private
+ * @param {Object} regexp The regexp to clone.
+ * @returns {Object} Returns the cloned regexp.
+ */
- return margin;
- }
- }, {
- key: "_adjustParentBBoxes",
- value: function _adjustParentBBoxes(bbox, path, level) {
- // adjust bboxes along the given tree path
- for (var i = level; i >= 0; i--) {
- extend$1(path[i], bbox);
+
+ function cloneRegExp(regexp) {
+ var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+ result.lastIndex = regexp.lastIndex;
+ return result;
}
- }
- }, {
- key: "_condense",
- value: function _condense(path) {
- // go through the path, removing empty nodes and updating bboxes
- for (var i = path.length - 1, siblings; i >= 0; i--) {
- if (path[i].children.length === 0) {
- if (i > 0) {
- siblings = path[i - 1].children;
- siblings.splice(siblings.indexOf(path[i]), 1);
- } else this.clear();
- } else calcBBox(path[i], this.toBBox);
+ /**
+ * Creates a clone of the `symbol` object.
+ *
+ * @private
+ * @param {Object} symbol The symbol object to clone.
+ * @returns {Object} Returns the cloned symbol object.
+ */
+
+
+ function cloneSymbol(symbol) {
+ return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
}
- }
- }]);
+ /**
+ * Creates a clone of `typedArray`.
+ *
+ * @private
+ * @param {Object} typedArray The typed array to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the cloned typed array.
+ */
- return RBush;
- }();
- function findItem(item, items, equalsFn) {
- if (!equalsFn) return items.indexOf(item);
+ function cloneTypedArray(typedArray, isDeep) {
+ var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+ return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+ }
+ /**
+ * Compares values to sort them in ascending order.
+ *
+ * @private
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {number} Returns the sort order indicator for `value`.
+ */
- for (var i = 0; i < items.length; i++) {
- if (equalsFn(item, items[i])) return i;
- }
- return -1;
- } // calculate node's bbox from bboxes of its children
+ function compareAscending(value, other) {
+ if (value !== other) {
+ var valIsDefined = value !== undefined$1,
+ valIsNull = value === null,
+ valIsReflexive = value === value,
+ valIsSymbol = isSymbol(value);
+ var othIsDefined = other !== undefined$1,
+ othIsNull = other === null,
+ othIsReflexive = other === other,
+ othIsSymbol = isSymbol(other);
+ if (!othIsNull && !othIsSymbol && !valIsSymbol && value > other || valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol || valIsNull && othIsDefined && othIsReflexive || !valIsDefined && othIsReflexive || !valIsReflexive) {
+ return 1;
+ }
- function calcBBox(node, toBBox) {
- distBBox(node, 0, node.children.length, toBBox, node);
- } // min bounding rectangle of node children from k to p-1
+ if (!valIsNull && !valIsSymbol && !othIsSymbol && value < other || othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol || othIsNull && valIsDefined && valIsReflexive || !othIsDefined && valIsReflexive || !othIsReflexive) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ /**
+ * Used by `_.orderBy` to compare multiple properties of a value to another
+ * and stable sort them.
+ *
+ * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+ * specify an order of "desc" for descending or "asc" for ascending sort order
+ * of corresponding values.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {boolean[]|string[]} orders The order to sort by for each property.
+ * @returns {number} Returns the sort order indicator for `object`.
+ */
- function distBBox(node, k, p, toBBox, destNode) {
- if (!destNode) destNode = createNode(null);
- destNode.minX = Infinity;
- destNode.minY = Infinity;
- destNode.maxX = -Infinity;
- destNode.maxY = -Infinity;
- for (var i = k; i < p; i++) {
- var child = node.children[i];
- extend$1(destNode, node.leaf ? toBBox(child) : child);
- }
+ function compareMultiple(object, other, orders) {
+ var index = -1,
+ objCriteria = object.criteria,
+ othCriteria = other.criteria,
+ length = objCriteria.length,
+ ordersLength = orders.length;
- return destNode;
- }
+ while (++index < length) {
+ var result = compareAscending(objCriteria[index], othCriteria[index]);
- function extend$1(a, b) {
- a.minX = Math.min(a.minX, b.minX);
- a.minY = Math.min(a.minY, b.minY);
- a.maxX = Math.max(a.maxX, b.maxX);
- a.maxY = Math.max(a.maxY, b.maxY);
- return a;
- }
+ if (result) {
+ if (index >= ordersLength) {
+ return result;
+ }
- function compareNodeMinX(a, b) {
- return a.minX - b.minX;
- }
+ var order = orders[index];
+ return result * (order == 'desc' ? -1 : 1);
+ }
+ } // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+ // that causes it, under certain circumstances, to provide the same value for
+ // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+ // for more details.
+ //
+ // This also ensures a stable sort in V8 and other engines.
+ // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
- function compareNodeMinY(a, b) {
- return a.minY - b.minY;
- }
- function bboxArea(a) {
- return (a.maxX - a.minX) * (a.maxY - a.minY);
- }
+ return object.index - other.index;
+ }
+ /**
+ * Creates an array that is the composition of partially applied arguments,
+ * placeholders, and provided arguments into a single array of arguments.
+ *
+ * @private
+ * @param {Array} args The provided arguments.
+ * @param {Array} partials The arguments to prepend to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
- function bboxMargin(a) {
- return a.maxX - a.minX + (a.maxY - a.minY);
- }
- function enlargedArea(a, b) {
- return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) * (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
- }
+ function composeArgs(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersLength = holders.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(leftLength + rangeLength),
+ isUncurried = !isCurried;
- function intersectionArea(a, b) {
- var minX = Math.max(a.minX, b.minX);
- var minY = Math.max(a.minY, b.minY);
- var maxX = Math.min(a.maxX, b.maxX);
- var maxY = Math.min(a.maxY, b.maxY);
- return Math.max(0, maxX - minX) * Math.max(0, maxY - minY);
- }
+ while (++leftIndex < leftLength) {
+ result[leftIndex] = partials[leftIndex];
+ }
- function contains(a, b) {
- return a.minX <= b.minX && a.minY <= b.minY && b.maxX <= a.maxX && b.maxY <= a.maxY;
- }
+ while (++argsIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[holders[argsIndex]] = args[argsIndex];
+ }
+ }
- function intersects(a, b) {
- return b.minX <= a.maxX && b.minY <= a.maxY && b.maxX >= a.minX && b.maxY >= a.minY;
- }
+ while (rangeLength--) {
+ result[leftIndex++] = args[argsIndex++];
+ }
- function createNode(children) {
- return {
- children: children,
- height: 1,
- leaf: true,
- minX: Infinity,
- minY: Infinity,
- maxX: -Infinity,
- maxY: -Infinity
- };
- } // sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
- // combines selection algorithm with binary divide & conquer approach
+ return result;
+ }
+ /**
+ * This function is like `composeArgs` except that the arguments composition
+ * is tailored for `_.partialRight`.
+ *
+ * @private
+ * @param {Array} args The provided arguments.
+ * @param {Array} partials The arguments to append to those provided.
+ * @param {Array} holders The `partials` placeholder indexes.
+ * @params {boolean} [isCurried] Specify composing for a curried function.
+ * @returns {Array} Returns the new array of composed arguments.
+ */
- function multiSelect(arr, left, right, n, compare) {
- var stack = [left, right];
+ function composeArgsRight(args, partials, holders, isCurried) {
+ var argsIndex = -1,
+ argsLength = args.length,
+ holdersIndex = -1,
+ holdersLength = holders.length,
+ rightIndex = -1,
+ rightLength = partials.length,
+ rangeLength = nativeMax(argsLength - holdersLength, 0),
+ result = Array(rangeLength + rightLength),
+ isUncurried = !isCurried;
- while (stack.length) {
- right = stack.pop();
- left = stack.pop();
- if (right - left <= n) continue;
- var mid = left + Math.ceil((right - left) / n / 2) * n;
- quickselect(arr, mid, left, right, compare);
- stack.push(left, mid, mid, right);
- }
- }
+ while (++argsIndex < rangeLength) {
+ result[argsIndex] = args[argsIndex];
+ }
- function responseText(response) {
- if (!response.ok) throw new Error(response.status + " " + response.statusText);
- return response.text();
- }
+ var offset = argsIndex;
- function d3_text (input, init) {
- return fetch(input, init).then(responseText);
- }
+ while (++rightIndex < rightLength) {
+ result[offset + rightIndex] = partials[rightIndex];
+ }
- function responseJson(response) {
- if (!response.ok) throw new Error(response.status + " " + response.statusText);
- if (response.status === 204 || response.status === 205) return;
- return response.json();
- }
+ while (++holdersIndex < holdersLength) {
+ if (isUncurried || argsIndex < argsLength) {
+ result[offset + holders[holdersIndex]] = args[argsIndex++];
+ }
+ }
- function d3_json (input, init) {
- return fetch(input, init).then(responseJson);
- }
+ return result;
+ }
+ /**
+ * Copies the values of `source` to `array`.
+ *
+ * @private
+ * @param {Array} source The array to copy values from.
+ * @param {Array} [array=[]] The array to copy values to.
+ * @returns {Array} Returns `array`.
+ */
- function parser(type) {
- return function (input, init) {
- return d3_text(input, init).then(function (text) {
- return new DOMParser().parseFromString(text, type);
- });
- };
- }
- var d3_xml = parser("application/xml");
- var svg = parser("image/svg+xml");
+ function copyArray(source, array) {
+ var index = -1,
+ length = source.length;
+ array || (array = Array(length));
- var tiler$6 = utilTiler();
- var dispatch$7 = dispatch$8('loaded');
- var _tileZoom$3 = 14;
- var _krUrlRoot = 'https://www.keepright.at';
- var _krData = {
- errorTypes: {},
- localizeStrings: {}
- }; // This gets reassigned if reset
+ while (++index < length) {
+ array[index] = source[index];
+ }
- var _cache$2;
+ return array;
+ }
+ /**
+ * Copies properties of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy properties from.
+ * @param {Array} props The property identifiers to copy.
+ * @param {Object} [object={}] The object to copy properties to.
+ * @param {Function} [customizer] The function to customize copied values.
+ * @returns {Object} Returns `object`.
+ */
- var _krRuleset = [// no 20 - multiple node on same spot - these are mostly boundaries overlapping roads
- 30, 40, 50, 60, 70, 90, 100, 110, 120, 130, 150, 160, 170, 180, 190, 191, 192, 193, 194, 195, 196, 197, 198, 200, 201, 202, 203, 204, 205, 206, 207, 208, 210, 220, 230, 231, 232, 270, 280, 281, 282, 283, 284, 285, 290, 291, 292, 293, 294, 295, 296, 297, 298, 300, 310, 311, 312, 313, 320, 350, 360, 370, 380, 390, 400, 401, 402, 410, 411, 412, 413];
- function abortRequest$6(controller) {
- if (controller) {
- controller.abort();
- }
- }
+ function copyObject(source, props, object, customizer) {
+ var isNew = !object;
+ object || (object = {});
+ var index = -1,
+ length = props.length;
- function abortUnwantedRequests$3(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ while (++index < length) {
+ var key = props[index];
+ var newValue = customizer ? customizer(object[key], source[key], key, object, source) : undefined$1;
- if (!wanted) {
- abortRequest$6(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
+ if (newValue === undefined$1) {
+ newValue = source[key];
+ }
- function encodeIssueRtree$2(d) {
- return {
- minX: d.loc[0],
- minY: d.loc[1],
- maxX: d.loc[0],
- maxY: d.loc[1],
- data: d
- };
- } // Replace or remove QAItem from rtree
+ if (isNew) {
+ baseAssignValue(object, key, newValue);
+ } else {
+ assignValue(object, key, newValue);
+ }
+ }
+ return object;
+ }
+ /**
+ * Copies own symbols of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy symbols from.
+ * @param {Object} [object={}] The object to copy symbols to.
+ * @returns {Object} Returns `object`.
+ */
- function updateRtree$3(item, replace) {
- _cache$2.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
- if (replace) {
- _cache$2.rtree.insert(item);
- }
- }
+ function copySymbols(source, object) {
+ return copyObject(source, getSymbols(source), object);
+ }
+ /**
+ * Copies own and inherited symbols of `source` to `object`.
+ *
+ * @private
+ * @param {Object} source The object to copy symbols from.
+ * @param {Object} [object={}] The object to copy symbols to.
+ * @returns {Object} Returns `object`.
+ */
- function tokenReplacements(d) {
- if (!(d instanceof QAItem)) return;
- var htmlRegex = new RegExp(/<\/[a-z][\s\S]*>/);
- var replacements = {};
- var issueTemplate = _krData.errorTypes[d.whichType];
- if (!issueTemplate) {
- /* eslint-disable no-console */
- console.log('No Template: ', d.whichType);
- console.log(' ', d.description);
- /* eslint-enable no-console */
+ function copySymbolsIn(source, object) {
+ return copyObject(source, getSymbolsIn(source), object);
+ }
+ /**
+ * Creates a function like `_.groupBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} [initializer] The accumulator object initializer.
+ * @returns {Function} Returns the new aggregator function.
+ */
- return;
- } // some descriptions are just fixed text
+ function createAggregator(setter, initializer) {
+ return function (collection, iteratee) {
+ var func = isArray(collection) ? arrayAggregator : baseAggregator,
+ accumulator = initializer ? initializer() : {};
+ return func(collection, setter, getIteratee(iteratee, 2), accumulator);
+ };
+ }
+ /**
+ * Creates a function like `_.assign`.
+ *
+ * @private
+ * @param {Function} assigner The function to assign values.
+ * @returns {Function} Returns the new assigner function.
+ */
- if (!issueTemplate.regex) return; // regex pattern should match description with variable details captured
- var errorRegex = new RegExp(issueTemplate.regex, 'i');
- var errorMatch = errorRegex.exec(d.description);
+ function createAssigner(assigner) {
+ return baseRest(function (object, sources) {
+ var index = -1,
+ length = sources.length,
+ customizer = length > 1 ? sources[length - 1] : undefined$1,
+ guard = length > 2 ? sources[2] : undefined$1;
+ customizer = assigner.length > 3 && typeof customizer == 'function' ? (length--, customizer) : undefined$1;
- if (!errorMatch) {
- /* eslint-disable no-console */
- console.log('Unmatched: ', d.whichType);
- console.log(' ', d.description);
- console.log(' ', errorRegex);
- /* eslint-enable no-console */
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ customizer = length < 3 ? undefined$1 : customizer;
+ length = 1;
+ }
- return;
- }
+ object = Object(object);
- for (var i = 1; i < errorMatch.length; i++) {
- // skip first
- var capture = errorMatch[i];
- var idType = void 0;
- idType = 'IDs' in issueTemplate ? issueTemplate.IDs[i - 1] : '';
+ while (++index < length) {
+ var source = sources[index];
- if (idType && capture) {
- // link IDs if present in the capture
- capture = parseError(capture, idType);
- } else if (htmlRegex.test(capture)) {
- // escape any html in non-IDs
- capture = '\\' + capture + '\\';
- } else {
- var compare = capture.toLowerCase();
+ if (source) {
+ assigner(object, source, index, customizer);
+ }
+ }
- if (_krData.localizeStrings[compare]) {
- // some replacement strings can be localized
- capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
+ return object;
+ });
}
- }
+ /**
+ * Creates a `baseEach` or `baseEachRight` function.
+ *
+ * @private
+ * @param {Function} eachFunc The function to iterate over a collection.
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
- replacements['var' + i] = capture;
- }
- return replacements;
- }
+ function createBaseEach(eachFunc, fromRight) {
+ return function (collection, iteratee) {
+ if (collection == null) {
+ return collection;
+ }
- function parseError(capture, idType) {
- var compare = capture.toLowerCase();
+ if (!isArrayLike(collection)) {
+ return eachFunc(collection, iteratee);
+ }
- if (_krData.localizeStrings[compare]) {
- // some replacement strings can be localized
- capture = _t('QA.keepRight.error_parts.' + _krData.localizeStrings[compare]);
- }
+ var length = collection.length,
+ index = fromRight ? length : -1,
+ iterable = Object(collection);
- switch (idType) {
- // link a string like "this node"
- case 'this':
- capture = linkErrorObject(capture);
- break;
+ while (fromRight ? index-- : ++index < length) {
+ if (iteratee(iterable[index], index, iterable) === false) {
+ break;
+ }
+ }
- case 'url':
- capture = linkURL(capture);
- break;
- // link an entity ID
+ return collection;
+ };
+ }
+ /**
+ * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new base function.
+ */
- case 'n':
- case 'w':
- case 'r':
- capture = linkEntity(idType + capture);
- break;
- // some errors have more complex ID lists/variance
- case '20':
- capture = parse20(capture);
- break;
+ function createBaseFor(fromRight) {
+ return function (object, iteratee, keysFunc) {
+ var index = -1,
+ iterable = Object(object),
+ props = keysFunc(object),
+ length = props.length;
- case '211':
- capture = parse211(capture);
- break;
+ while (length--) {
+ var key = props[fromRight ? length : ++index];
- case '231':
- capture = parse231(capture);
- break;
+ if (iteratee(iterable[key], key, iterable) === false) {
+ break;
+ }
+ }
- case '294':
- capture = parse294(capture);
- break;
+ return object;
+ };
+ }
+ /**
+ * Creates a function that wraps `func` to invoke it with the optional `this`
+ * binding of `thisArg`.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
- case '370':
- capture = parse370(capture);
- break;
- }
- return capture;
+ function createBind(func, bitmask, thisArg) {
+ var isBind = bitmask & WRAP_BIND_FLAG,
+ Ctor = createCtor(func);
- function linkErrorObject(d) {
- return "".concat(d, "");
- }
+ function wrapper() {
+ var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
+ return fn.apply(isBind ? thisArg : this, arguments);
+ }
- function linkEntity(d) {
- return "".concat(d, "");
- }
+ return wrapper;
+ }
+ /**
+ * Creates a function like `_.lowerFirst`.
+ *
+ * @private
+ * @param {string} methodName The name of the `String` case method to use.
+ * @returns {Function} Returns the new case function.
+ */
- function linkURL(d) {
- return "").concat(d, "");
- } // arbitrary node list of form: #ID, #ID, #ID...
+ function createCaseFirst(methodName) {
+ return function (string) {
+ string = toString(string);
+ var strSymbols = hasUnicode(string) ? stringToArray(string) : undefined$1;
+ var chr = strSymbols ? strSymbols[0] : string.charAt(0);
+ var trailing = strSymbols ? castSlice(strSymbols, 1).join('') : string.slice(1);
+ return chr[methodName]() + trailing;
+ };
+ }
+ /**
+ * Creates a function like `_.camelCase`.
+ *
+ * @private
+ * @param {Function} callback The function to combine each word.
+ * @returns {Function} Returns the new compounder function.
+ */
- function parse211(capture) {
- var newList = [];
- var items = capture.split(', ');
- items.forEach(function (item) {
- // ID has # at the front
- var id = linkEntity('n' + item.slice(1));
- newList.push(id);
- });
- return newList.join(', ');
- } // arbitrary way list of form: #ID(layer),#ID(layer),#ID(layer)...
+ function createCompounder(callback) {
+ return function (string) {
+ return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+ };
+ }
+ /**
+ * Creates a function that produces an instance of `Ctor` regardless of
+ * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+ *
+ * @private
+ * @param {Function} Ctor The constructor to wrap.
+ * @returns {Function} Returns the new wrapped function.
+ */
- function parse231(capture) {
- var newList = []; // unfortunately 'layer' can itself contain commas, so we split on '),'
- var items = capture.split('),');
- items.forEach(function (item) {
- var match = item.match(/\#(\d+)\((.+)\)?/);
+ function createCtor(Ctor) {
+ return function () {
+ // Use a `switch` statement to work with class constructors. See
+ // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+ // for more details.
+ var args = arguments;
- if (match !== null && match.length > 2) {
- newList.push(linkEntity('w' + match[1]) + ' ' + _t('QA.keepRight.errorTypes.231.layer', {
- layer: match[2]
- }));
- }
- });
- return newList.join(', ');
- } // arbitrary node/relation list of form: from node #ID,to relation #ID,to node #ID...
+ switch (args.length) {
+ case 0:
+ return new Ctor();
+ case 1:
+ return new Ctor(args[0]);
- function parse294(capture) {
- var newList = [];
- var items = capture.split(',');
- items.forEach(function (item) {
- // item of form "from/to node/relation #ID"
- item = item.split(' '); // to/from role is more clear in quotes
+ case 2:
+ return new Ctor(args[0], args[1]);
- var role = "\"".concat(item[0], "\""); // first letter of node/relation provides the type
+ case 3:
+ return new Ctor(args[0], args[1], args[2]);
- var idType = item[1].slice(0, 1); // ID has # at the front
+ case 4:
+ return new Ctor(args[0], args[1], args[2], args[3]);
- var id = item[2].slice(1);
- id = linkEntity(idType + id);
- newList.push("".concat(role, " ").concat(item[1], " ").concat(id));
- });
- return newList.join(', ');
- } // may or may not include the string "(including the name 'name')"
+ case 5:
+ return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+ case 6:
+ return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
- function parse370(capture) {
- if (!capture) return '';
- var match = capture.match(/\(including the name (\'.+\')\)/);
+ case 7:
+ return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ }
- if (match && match.length) {
- return _t('QA.keepRight.errorTypes.370.including_the_name', {
- name: match[1]
- });
- }
+ var thisBinding = baseCreate(Ctor.prototype),
+ result = Ctor.apply(thisBinding, args); // Mimic the constructor's `return` behavior.
+ // See https://es5.github.io/#x13.2.2 for more details.
- return '';
- } // arbitrary node list of form: #ID,#ID,#ID...
+ return isObject(result) ? result : thisBinding;
+ };
+ }
+ /**
+ * Creates a function that wraps `func` to enable currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {number} arity The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
- function parse20(capture) {
- var newList = [];
- var items = capture.split(',');
- items.forEach(function (item) {
- // ID has # at the front
- var id = linkEntity('n' + item.slice(1));
- newList.push(id);
- });
- return newList.join(', ');
- }
- }
+ function createCurry(func, bitmask, arity) {
+ var Ctor = createCtor(func);
- var serviceKeepRight = {
- title: 'keepRight',
- init: function init() {
- _mainFileFetcher.get('keepRight').then(function (d) {
- return _krData = d;
- });
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length,
+ placeholder = getHolder(wrapper);
- if (!_cache$2) {
- this.reset();
- }
+ while (index--) {
+ args[index] = arguments[index];
+ }
- this.event = utilRebind(this, dispatch$7, 'on');
- },
- reset: function reset() {
- if (_cache$2) {
- Object.values(_cache$2.inflightTile).forEach(abortRequest$6);
- }
+ var holders = length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder ? [] : replaceHolders(args, placeholder);
+ length -= holders.length;
- _cache$2 = {
- data: {},
- loadedTile: {},
- inflightTile: {},
- inflightPost: {},
- closed: {},
- rtree: new RBush()
- };
- },
- // KeepRight API: http://osm.mueschelsoft.de/keepright/interfacing.php
- loadIssues: function loadIssues(projection) {
- var _this = this;
+ if (length < arity) {
+ return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, undefined$1, args, holders, undefined$1, undefined$1, arity - length);
+ }
- var options = {
- format: 'geojson',
- ch: _krRuleset
- }; // determine the needed tiles to cover the view
+ var fn = this && this !== root && this instanceof wrapper ? Ctor : func;
+ return apply(fn, this, args);
+ }
- var tiles = tiler$6.zoomExtent([_tileZoom$3, _tileZoom$3]).getTiles(projection); // abort inflight requests that are no longer needed
+ return wrapper;
+ }
+ /**
+ * Creates a `_.find` or `_.findLast` function.
+ *
+ * @private
+ * @param {Function} findIndexFunc The function to find the collection index.
+ * @returns {Function} Returns the new find function.
+ */
- abortUnwantedRequests$3(_cache$2, tiles); // issue new requests..
- tiles.forEach(function (tile) {
- if (_cache$2.loadedTile[tile.id] || _cache$2.inflightTile[tile.id]) return;
+ function createFind(findIndexFunc) {
+ return function (collection, predicate, fromIndex) {
+ var iterable = Object(collection);
- var _tile$extent$rectangl = tile.extent.rectangle(),
- _tile$extent$rectangl2 = _slicedToArray(_tile$extent$rectangl, 4),
- left = _tile$extent$rectangl2[0],
- top = _tile$extent$rectangl2[1],
- right = _tile$extent$rectangl2[2],
- bottom = _tile$extent$rectangl2[3];
+ if (!isArrayLike(collection)) {
+ var iteratee = getIteratee(predicate, 3);
+ collection = keys(collection);
- var params = Object.assign({}, options, {
- left: left,
- bottom: bottom,
- right: right,
- top: top
- });
- var url = "".concat(_krUrlRoot, "/export.php?") + utilQsString(params);
- var controller = new AbortController();
- _cache$2.inflightTile[tile.id] = controller;
- d3_json(url, {
- signal: controller.signal
- }).then(function (data) {
- delete _cache$2.inflightTile[tile.id];
- _cache$2.loadedTile[tile.id] = true;
+ predicate = function predicate(key) {
+ return iteratee(iterable[key], key, iterable);
+ };
+ }
- if (!data || !data.features || !data.features.length) {
- throw new Error('No Data');
- }
+ var index = findIndexFunc(collection, predicate, fromIndex);
+ return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined$1;
+ };
+ }
+ /**
+ * Creates a `_.flow` or `_.flowRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new flow function.
+ */
- data.features.forEach(function (feature) {
- var _feature$properties = feature.properties,
- itemType = _feature$properties.error_type,
- id = _feature$properties.error_id,
- _feature$properties$c = _feature$properties.comment,
- comment = _feature$properties$c === void 0 ? null : _feature$properties$c,
- objectId = _feature$properties.object_id,
- objectType = _feature$properties.object_type,
- schema = _feature$properties.schema,
- title = _feature$properties.title;
- var loc = feature.geometry.coordinates,
- _feature$properties$d = feature.properties.description,
- description = _feature$properties$d === void 0 ? '' : _feature$properties$d; // if there is a parent, save its error type e.g.:
- // Error 191 = "highway-highway"
- // Error 190 = "intersections without junctions" (parent)
- var issueTemplate = _krData.errorTypes[itemType];
- var parentIssueType = (Math.floor(itemType / 10) * 10).toString(); // try to handle error type directly, fallback to parent error type.
+ function createFlow(fromRight) {
+ return flatRest(function (funcs) {
+ var length = funcs.length,
+ index = length,
+ prereq = LodashWrapper.prototype.thru;
- var whichType = issueTemplate ? itemType : parentIssueType;
- var whichTemplate = _krData.errorTypes[whichType]; // Rewrite a few of the errors at this point..
- // This is done to make them easier to linkify and translate.
+ if (fromRight) {
+ funcs.reverse();
+ }
- switch (whichType) {
- case '170':
- description = "This feature has a FIXME tag: ".concat(description);
- break;
+ while (index--) {
+ var func = funcs[index];
- case '292':
- case '293':
- description = description.replace('A turn-', 'This turn-');
- break;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- case '294':
- case '295':
- case '296':
- case '297':
- case '298':
- description = "This turn-restriction~".concat(description);
- break;
+ if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+ var wrapper = new LodashWrapper([], true);
+ }
+ }
- case '300':
- description = 'This highway is missing a maxspeed tag';
- break;
+ index = wrapper ? index : length;
- case '411':
- case '412':
- case '413':
- description = "This feature~".concat(description);
- break;
- } // move markers slightly so it doesn't obscure the geometry,
- // then move markers away from other coincident markers
+ while (++index < length) {
+ func = funcs[index];
+ var funcName = getFuncName(func),
+ data = funcName == 'wrapper' ? getData(func) : undefined$1;
+
+ if (data && isLaziable(data[0]) && data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) && !data[4].length && data[9] == 1) {
+ wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+ } else {
+ wrapper = func.length == 1 && isLaziable(func) ? wrapper[funcName]() : wrapper.thru(func);
+ }
+ }
+ return function () {
+ var args = arguments,
+ value = args[0];
- var coincident = false;
+ if (wrapper && args.length == 1 && isArray(value)) {
+ return wrapper.plant(value).value();
+ }
- do {
- // first time, move marker up. after that, move marker right.
- var delta = coincident ? [0.00001, 0] : [0, 0.00001];
- loc = geoVecAdd(loc, delta);
- var bbox = geoExtent(loc).bbox();
- coincident = _cache$2.rtree.search(bbox).length;
- } while (coincident);
+ var index = 0,
+ result = length ? funcs[index].apply(this, args) : value;
- var d = new QAItem(loc, _this, itemType, id, {
- comment: comment,
- description: description,
- whichType: whichType,
- parentIssueType: parentIssueType,
- severity: whichTemplate.severity || 'error',
- objectId: objectId,
- objectType: objectType,
- schema: schema,
- title: title
- });
- d.replacements = tokenReplacements(d);
- _cache$2.data[id] = d;
+ while (++index < length) {
+ result = funcs[index].call(this, result);
+ }
- _cache$2.rtree.insert(encodeIssueRtree$2(d));
+ return result;
+ };
});
- dispatch$7.call('loaded');
- })["catch"](function () {
- delete _cache$2.inflightTile[tile.id];
- _cache$2.loadedTile[tile.id] = true;
- });
- });
- },
- postUpdate: function postUpdate(d, callback) {
- var _this2 = this;
+ }
+ /**
+ * Creates a function that wraps `func` to invoke it with optional `this`
+ * binding of `thisArg`, partial application, and currying.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to
+ * the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [partialsRight] The arguments to append to those provided
+ * to the new function.
+ * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
- if (_cache$2.inflightPost[d.id]) {
- return callback({
- message: 'Error update already inflight',
- status: -2
- }, d);
- }
- var params = {
- schema: d.schema,
- id: d.id
- };
+ function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+ var isAry = bitmask & WRAP_ARY_FLAG,
+ isBind = bitmask & WRAP_BIND_FLAG,
+ isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
+ isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
+ isFlip = bitmask & WRAP_FLIP_FLAG,
+ Ctor = isBindKey ? undefined$1 : createCtor(func);
- if (d.newStatus) {
- params.st = d.newStatus;
- }
+ function wrapper() {
+ var length = arguments.length,
+ args = Array(length),
+ index = length;
- if (d.newComment !== undefined) {
- params.co = d.newComment;
- } // NOTE: This throws a CORS err, but it seems successful.
- // We don't care too much about the response, so this is fine.
+ while (index--) {
+ args[index] = arguments[index];
+ }
+ if (isCurried) {
+ var placeholder = getHolder(wrapper),
+ holdersCount = countHolders(args, placeholder);
+ }
- var url = "".concat(_krUrlRoot, "/comment.php?") + utilQsString(params);
- var controller = new AbortController();
- _cache$2.inflightPost[d.id] = controller; // Since this is expected to throw an error just continue as if it worked
- // (worst case scenario the request truly fails and issue will show up if iD restarts)
+ if (partials) {
+ args = composeArgs(args, partials, holders, isCurried);
+ }
- d3_json(url, {
- signal: controller.signal
- })["finally"](function () {
- delete _cache$2.inflightPost[d.id];
+ if (partialsRight) {
+ args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+ }
- if (d.newStatus === 'ignore') {
- // ignore permanently (false positive)
- _this2.removeItem(d);
- } else if (d.newStatus === 'ignore_t') {
- // ignore temporarily (error fixed)
- _this2.removeItem(d);
+ length -= holdersCount;
- _cache$2.closed["".concat(d.schema, ":").concat(d.id)] = true;
- } else {
- d = _this2.replaceItem(d.update({
- comment: d.newComment,
- newComment: undefined,
- newState: undefined
- }));
- }
+ if (isCurried && length < arity) {
+ var newHolders = replaceHolders(args, placeholder);
+ return createRecurry(func, bitmask, createHybrid, wrapper.placeholder, thisArg, args, newHolders, argPos, ary, arity - length);
+ }
- if (callback) callback(null, d);
- });
- },
- // Get all cached QAItems covering the viewport
- getItems: function getItems(projection) {
- var viewport = projection.clipExtent();
- var min = [viewport[0][0], viewport[1][1]];
- var max = [viewport[1][0], viewport[0][1]];
- var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
- return _cache$2.rtree.search(bbox).map(function (d) {
- return d.data;
- });
- },
- // Get a QAItem from cache
- // NOTE: Don't change method name until UI v3 is merged
- getError: function getError(id) {
- return _cache$2.data[id];
- },
- // Replace a single QAItem in the cache
- replaceItem: function replaceItem(item) {
- if (!(item instanceof QAItem) || !item.id) return;
- _cache$2.data[item.id] = item;
- updateRtree$3(encodeIssueRtree$2(item), true); // true = replace
+ var thisBinding = isBind ? thisArg : this,
+ fn = isBindKey ? thisBinding[func] : func;
+ length = args.length;
- return item;
- },
- // Remove a single QAItem from the cache
- removeItem: function removeItem(item) {
- if (!(item instanceof QAItem) || !item.id) return;
- delete _cache$2.data[item.id];
- updateRtree$3(encodeIssueRtree$2(item), false); // false = remove
- },
- issueURL: function issueURL(item) {
- return "".concat(_krUrlRoot, "/report_map.php?schema=").concat(item.schema, "&error=").concat(item.id);
- },
- // Get an array of issues closed during this session.
- // Used to populate `closed:keepright` changeset tag
- getClosedIDs: function getClosedIDs() {
- return Object.keys(_cache$2.closed).sort();
- }
- };
+ if (argPos) {
+ args = reorder(args, argPos);
+ } else if (isFlip && length > 1) {
+ args.reverse();
+ }
- var tiler$5 = utilTiler();
- var dispatch$6 = dispatch$8('loaded');
- var _tileZoom$2 = 14;
- var _impOsmUrls = {
- ow: 'https://grab.community.improve-osm.org/directionOfFlowService',
- mr: 'https://grab.community.improve-osm.org/missingGeoService',
- tr: 'https://grab.community.improve-osm.org/turnRestrictionService'
- };
- var _impOsmData = {
- icons: {}
- }; // This gets reassigned if reset
+ if (isAry && ary < length) {
+ args.length = ary;
+ }
- var _cache$1;
+ if (this && this !== root && this instanceof wrapper) {
+ fn = Ctor || createCtor(fn);
+ }
- function abortRequest$5(i) {
- Object.values(i).forEach(function (controller) {
- if (controller) {
- controller.abort();
- }
- });
- }
+ return fn.apply(thisBinding, args);
+ }
- function abortUnwantedRequests$2(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ return wrapper;
+ }
+ /**
+ * Creates a function like `_.invertBy`.
+ *
+ * @private
+ * @param {Function} setter The function to set accumulator values.
+ * @param {Function} toIteratee The function to resolve iteratees.
+ * @returns {Function} Returns the new inverter function.
+ */
- if (!wanted) {
- abortRequest$5(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
- function encodeIssueRtree$1(d) {
- return {
- minX: d.loc[0],
- minY: d.loc[1],
- maxX: d.loc[0],
- maxY: d.loc[1],
- data: d
- };
- } // Replace or remove QAItem from rtree
+ function createInverter(setter, toIteratee) {
+ return function (object, iteratee) {
+ return baseInverter(object, setter, toIteratee(iteratee), {});
+ };
+ }
+ /**
+ * Creates a function that performs a mathematical operation on two values.
+ *
+ * @private
+ * @param {Function} operator The function to perform the operation.
+ * @param {number} [defaultValue] The value used for `undefined` arguments.
+ * @returns {Function} Returns the new mathematical operation function.
+ */
- function updateRtree$2(item, replace) {
- _cache$1.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
+ function createMathOperation(operator, defaultValue) {
+ return function (value, other) {
+ var result;
- if (replace) {
- _cache$1.rtree.insert(item);
- }
- }
+ if (value === undefined$1 && other === undefined$1) {
+ return defaultValue;
+ }
- function linkErrorObject(d) {
- return "".concat(d, "");
- }
+ if (value !== undefined$1) {
+ result = value;
+ }
- function linkEntity(d) {
- return "".concat(d, "");
- }
+ if (other !== undefined$1) {
+ if (result === undefined$1) {
+ return other;
+ }
- function pointAverage(points) {
- if (points.length) {
- var sum = points.reduce(function (acc, point) {
- return geoVecAdd(acc, [point.lon, point.lat]);
- }, [0, 0]);
- return geoVecScale(sum, 1 / points.length);
- } else {
- return [0, 0];
- }
- }
+ if (typeof value == 'string' || typeof other == 'string') {
+ value = baseToString(value);
+ other = baseToString(other);
+ } else {
+ value = baseToNumber(value);
+ other = baseToNumber(other);
+ }
- function relativeBearing(p1, p2) {
- var angle = Math.atan2(p2.lon - p1.lon, p2.lat - p1.lat);
+ result = operator(value, other);
+ }
- if (angle < 0) {
- angle += 2 * Math.PI;
- } // Return degrees
+ return result;
+ };
+ }
+ /**
+ * Creates a function like `_.over`.
+ *
+ * @private
+ * @param {Function} arrayFunc The function to iterate over iteratees.
+ * @returns {Function} Returns the new over function.
+ */
- return angle * 180 / Math.PI;
- } // Assuming range [0,360)
+ function createOver(arrayFunc) {
+ return flatRest(function (iteratees) {
+ iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
+ return baseRest(function (args) {
+ var thisArg = this;
+ return arrayFunc(iteratees, function (iteratee) {
+ return apply(iteratee, thisArg, args);
+ });
+ });
+ });
+ }
+ /**
+ * Creates the padding for `string` based on `length`. The `chars` string
+ * is truncated if the number of characters exceeds `length`.
+ *
+ * @private
+ * @param {number} length The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padding for `string`.
+ */
- function cardinalDirection(bearing) {
- var dir = 45 * Math.round(bearing / 45);
- var compass = {
- 0: 'north',
- 45: 'northeast',
- 90: 'east',
- 135: 'southeast',
- 180: 'south',
- 225: 'southwest',
- 270: 'west',
- 315: 'northwest',
- 360: 'north'
- };
- return _t("QA.improveOSM.directions.".concat(compass[dir]));
- } // Errors shouldn't obscure each other
+ function createPadding(length, chars) {
+ chars = chars === undefined$1 ? ' ' : baseToString(chars);
+ var charsLength = chars.length;
+ if (charsLength < 2) {
+ return charsLength ? baseRepeat(chars, length) : chars;
+ }
- function preventCoincident$1(loc, bumpUp) {
- var coincident = false;
+ var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+ return hasUnicode(chars) ? castSlice(stringToArray(result), 0, length).join('') : result.slice(0, length);
+ }
+ /**
+ * Creates a function that wraps `func` to invoke it with the `this` binding
+ * of `thisArg` and `partials` prepended to the arguments it receives.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {Array} partials The arguments to prepend to those provided to
+ * the new function.
+ * @returns {Function} Returns the new wrapped function.
+ */
- do {
- // first time, move marker up. after that, move marker right.
- var delta = coincident ? [0.00001, 0] : bumpUp ? [0, 0.00001] : [0, 0];
- loc = geoVecAdd(loc, delta);
- var bbox = geoExtent(loc).bbox();
- coincident = _cache$1.rtree.search(bbox).length;
- } while (coincident);
- return loc;
- }
+ function createPartial(func, bitmask, thisArg, partials) {
+ var isBind = bitmask & WRAP_BIND_FLAG,
+ Ctor = createCtor(func);
- var serviceImproveOSM = {
- title: 'improveOSM',
- init: function init() {
- _mainFileFetcher.get('qa_data').then(function (d) {
- return _impOsmData = d.improveOSM;
- });
+ function wrapper() {
+ var argsIndex = -1,
+ argsLength = arguments.length,
+ leftIndex = -1,
+ leftLength = partials.length,
+ args = Array(leftLength + argsLength),
+ fn = this && this !== root && this instanceof wrapper ? Ctor : func;
- if (!_cache$1) {
- this.reset();
- }
+ while (++leftIndex < leftLength) {
+ args[leftIndex] = partials[leftIndex];
+ }
- this.event = utilRebind(this, dispatch$6, 'on');
- },
- reset: function reset() {
- if (_cache$1) {
- Object.values(_cache$1.inflightTile).forEach(abortRequest$5);
- }
+ while (argsLength--) {
+ args[leftIndex++] = arguments[++argsIndex];
+ }
- _cache$1 = {
- data: {},
- loadedTile: {},
- inflightTile: {},
- inflightPost: {},
- closed: {},
- rtree: new RBush()
- };
- },
- loadIssues: function loadIssues(projection) {
- var _this = this;
+ return apply(fn, isBind ? thisArg : this, args);
+ }
- var options = {
- client: 'iD',
- status: 'OPEN',
- zoom: '19' // Use a high zoom so that clusters aren't returned
+ return wrapper;
+ }
+ /**
+ * Creates a `_.range` or `_.rangeRight` function.
+ *
+ * @private
+ * @param {boolean} [fromRight] Specify iterating from right to left.
+ * @returns {Function} Returns the new range function.
+ */
- }; // determine the needed tiles to cover the view
- var tiles = tiler$5.zoomExtent([_tileZoom$2, _tileZoom$2]).getTiles(projection); // abort inflight requests that are no longer needed
+ function createRange(fromRight) {
+ return function (start, end, step) {
+ if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+ end = step = undefined$1;
+ } // Ensure the sign of `-0` is preserved.
- abortUnwantedRequests$2(_cache$1, tiles); // issue new requests..
- tiles.forEach(function (tile) {
- if (_cache$1.loadedTile[tile.id] || _cache$1.inflightTile[tile.id]) return;
+ start = toFinite(start);
- var _tile$extent$rectangl = tile.extent.rectangle(),
- _tile$extent$rectangl2 = _slicedToArray(_tile$extent$rectangl, 4),
- east = _tile$extent$rectangl2[0],
- north = _tile$extent$rectangl2[1],
- west = _tile$extent$rectangl2[2],
- south = _tile$extent$rectangl2[3];
+ if (end === undefined$1) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
- var params = Object.assign({}, options, {
- east: east,
- south: south,
- west: west,
- north: north
- }); // 3 separate requests to store for each tile
+ step = step === undefined$1 ? start < end ? 1 : -1 : toFinite(step);
+ return baseRange(start, end, step, fromRight);
+ };
+ }
+ /**
+ * Creates a function that performs a relational operation on two values.
+ *
+ * @private
+ * @param {Function} operator The function to perform the operation.
+ * @returns {Function} Returns the new relational operation function.
+ */
- var requests = {};
- Object.keys(_impOsmUrls).forEach(function (k) {
- // We exclude WATER from missing geometry as it doesn't seem useful
- // We use most confident one-way and turn restrictions only, still have false positives
- var kParams = Object.assign({}, params, k === 'mr' ? {
- type: 'PARKING,ROAD,BOTH,PATH'
- } : {
- confidenceLevel: 'C1'
- });
- var url = "".concat(_impOsmUrls[k], "/search?") + utilQsString(kParams);
- var controller = new AbortController();
- requests[k] = controller;
- d3_json(url, {
- signal: controller.signal
- }).then(function (data) {
- delete _cache$1.inflightTile[tile.id][k];
- if (!Object.keys(_cache$1.inflightTile[tile.id]).length) {
- delete _cache$1.inflightTile[tile.id];
- _cache$1.loadedTile[tile.id] = true;
- } // Road segments at high zoom == oneways
+ function createRelationalOperation(operator) {
+ return function (value, other) {
+ if (!(typeof value == 'string' && typeof other == 'string')) {
+ value = toNumber(value);
+ other = toNumber(other);
+ }
+ return operator(value, other);
+ };
+ }
+ /**
+ * Creates a function that wraps `func` to continue currying.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @param {Function} wrapFunc The function to create the `func` wrapper.
+ * @param {*} placeholder The placeholder value.
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to prepend to those provided to
+ * the new function.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
- if (data.roadSegments) {
- data.roadSegments.forEach(function (feature) {
- // Position error at the approximate middle of the segment
- var points = feature.points,
- wayId = feature.wayId,
- fromNodeId = feature.fromNodeId,
- toNodeId = feature.toNodeId;
- var itemId = "".concat(wayId).concat(fromNodeId).concat(toNodeId);
- var mid = points.length / 2;
- var loc; // Even number of points, find midpoint of the middle two
- // Odd number of points, use position of very middle point
- if (mid % 1 === 0) {
- loc = pointAverage([points[mid - 1], points[mid]]);
- } else {
- mid = points[Math.floor(mid)];
- loc = [mid.lon, mid.lat];
- } // One-ways can land on same segment in opposite direction
+ function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+ var isCurry = bitmask & WRAP_CURRY_FLAG,
+ newHolders = isCurry ? holders : undefined$1,
+ newHoldersRight = isCurry ? undefined$1 : holders,
+ newPartials = isCurry ? partials : undefined$1,
+ newPartialsRight = isCurry ? undefined$1 : partials;
+ bitmask |= isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG;
+ bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
+ if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
+ bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
+ }
- loc = preventCoincident$1(loc, false);
- var d = new QAItem(loc, _this, k, itemId, {
- issueKey: k,
- // used as a category
- identifier: {
- // used to post changes
- wayId: wayId,
- fromNodeId: fromNodeId,
- toNodeId: toNodeId
- },
- objectId: wayId,
- objectType: 'way'
- }); // Variables used in the description
+ var newData = [func, bitmask, thisArg, newPartials, newHolders, newPartialsRight, newHoldersRight, argPos, ary, arity];
+ var result = wrapFunc.apply(undefined$1, newData);
- d.replacements = {
- percentage: feature.percentOfTrips,
- num_trips: feature.numberOfTrips,
- highway: linkErrorObject(_t('QA.keepRight.error_parts.highway')),
- from_node: linkEntity('n' + feature.fromNodeId),
- to_node: linkEntity('n' + feature.toNodeId)
- };
- _cache$1.data[d.id] = d;
+ if (isLaziable(func)) {
+ setData(result, newData);
+ }
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
- });
- } // Tiles at high zoom == missing roads
+ result.placeholder = placeholder;
+ return setWrapToString(result, func, bitmask);
+ }
+ /**
+ * Creates a function like `_.round`.
+ *
+ * @private
+ * @param {string} methodName The name of the `Math` method to use when rounding.
+ * @returns {Function} Returns the new round function.
+ */
- if (data.tiles) {
- data.tiles.forEach(function (feature) {
- var type = feature.type,
- x = feature.x,
- y = feature.y,
- numberOfTrips = feature.numberOfTrips;
- var geoType = type.toLowerCase();
- var itemId = "".concat(geoType).concat(x).concat(y).concat(numberOfTrips); // Average of recorded points should land on the missing geometry
- // Missing geometry could happen to land on another error
+ function createRound(methodName) {
+ var func = Math[methodName];
+ return function (number, precision) {
+ number = toNumber(number);
+ precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
- var loc = pointAverage(feature.points);
- loc = preventCoincident$1(loc, false);
- var d = new QAItem(loc, _this, "".concat(k, "-").concat(geoType), itemId, {
- issueKey: k,
- identifier: {
- x: x,
- y: y
- }
- });
- d.replacements = {
- num_trips: numberOfTrips,
- geometry_type: _t("QA.improveOSM.geometry_types.".concat(geoType))
- }; // -1 trips indicates data came from a 3rd party
+ if (precision && nativeIsFinite(number)) {
+ // Shift with exponential notation to avoid floating-point issues.
+ // See [MDN](https://mdn.io/round#Examples) for more details.
+ var pair = (toString(number) + 'e').split('e'),
+ value = func(pair[0] + 'e' + (+pair[1] + precision));
+ pair = (toString(value) + 'e').split('e');
+ return +(pair[0] + 'e' + (+pair[1] - precision));
+ }
- if (numberOfTrips === -1) {
- d.desc = _t('QA.improveOSM.error_types.mr.description_alt', d.replacements);
- }
+ return func(number);
+ };
+ }
+ /**
+ * Creates a set object of `values`.
+ *
+ * @private
+ * @param {Array} values The values to add to the set.
+ * @returns {Object} Returns the new set.
+ */
- _cache$1.data[d.id] = d;
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
- });
- } // Entities at high zoom == turn restrictions
+ var createSet = !(Set && 1 / setToArray(new Set([, -0]))[1] == INFINITY) ? noop : function (values) {
+ return new Set(values);
+ };
+ /**
+ * Creates a `_.toPairs` or `_.toPairsIn` function.
+ *
+ * @private
+ * @param {Function} keysFunc The function to get the keys of a given object.
+ * @returns {Function} Returns the new pairs function.
+ */
+ function createToPairs(keysFunc) {
+ return function (object) {
+ var tag = getTag(object);
- if (data.entities) {
- data.entities.forEach(function (feature) {
- var point = feature.point,
- id = feature.id,
- segments = feature.segments,
- numberOfPasses = feature.numberOfPasses,
- turnType = feature.turnType;
- var itemId = "".concat(id.replace(/[,:+#]/g, '_')); // Turn restrictions could be missing at same junction
- // We also want to bump the error up so node is accessible
+ if (tag == mapTag) {
+ return mapToArray(object);
+ }
- var loc = preventCoincident$1([point.lon, point.lat], true); // Elements are presented in a strange way
+ if (tag == setTag) {
+ return setToPairs(object);
+ }
- var ids = id.split(',');
- var from_way = ids[0];
- var via_node = ids[3];
- var to_way = ids[2].split(':')[1];
- var d = new QAItem(loc, _this, k, itemId, {
- issueKey: k,
- identifier: id,
- objectId: via_node,
- objectType: 'node'
- }); // Travel direction along from_way clarifies the turn restriction
+ return baseToPairs(object, keysFunc(object));
+ };
+ }
+ /**
+ * Creates a function that either curries or invokes `func` with optional
+ * `this` binding and partially applied arguments.
+ *
+ * @private
+ * @param {Function|string} func The function or method name to wrap.
+ * @param {number} bitmask The bitmask flags.
+ * 1 - `_.bind`
+ * 2 - `_.bindKey`
+ * 4 - `_.curry` or `_.curryRight` of a bound function
+ * 8 - `_.curry`
+ * 16 - `_.curryRight`
+ * 32 - `_.partial`
+ * 64 - `_.partialRight`
+ * 128 - `_.rearg`
+ * 256 - `_.ary`
+ * 512 - `_.flip`
+ * @param {*} [thisArg] The `this` binding of `func`.
+ * @param {Array} [partials] The arguments to be partially applied.
+ * @param {Array} [holders] The `partials` placeholder indexes.
+ * @param {Array} [argPos] The argument positions of the new function.
+ * @param {number} [ary] The arity cap of `func`.
+ * @param {number} [arity] The arity of `func`.
+ * @returns {Function} Returns the new wrapped function.
+ */
- var _segments$0$points = _slicedToArray(segments[0].points, 2),
- p1 = _segments$0$points[0],
- p2 = _segments$0$points[1];
- var dir_of_travel = cardinalDirection(relativeBearing(p1, p2)); // Variables used in the description
+ function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+ var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
- d.replacements = {
- num_passed: numberOfPasses,
- num_trips: segments[0].numberOfTrips,
- turn_restriction: turnType.toLowerCase(),
- from_way: linkEntity('w' + from_way),
- to_way: linkEntity('w' + to_way),
- travel_direction: dir_of_travel,
- junction: linkErrorObject(_t('QA.keepRight.error_parts.this_node'))
- };
- _cache$1.data[d.id] = d;
+ if (!isBindKey && typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- _cache$1.rtree.insert(encodeIssueRtree$1(d));
+ var length = partials ? partials.length : 0;
- dispatch$6.call('loaded');
- });
- }
- })["catch"](function () {
- delete _cache$1.inflightTile[tile.id][k];
+ if (!length) {
+ bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
+ partials = holders = undefined$1;
+ }
- if (!Object.keys(_cache$1.inflightTile[tile.id]).length) {
- delete _cache$1.inflightTile[tile.id];
- _cache$1.loadedTile[tile.id] = true;
- }
- });
- });
- _cache$1.inflightTile[tile.id] = requests;
- });
- },
- getComments: function getComments(item) {
- var _this2 = this;
+ ary = ary === undefined$1 ? ary : nativeMax(toInteger(ary), 0);
+ arity = arity === undefined$1 ? arity : toInteger(arity);
+ length -= holders ? holders.length : 0;
- // If comments already retrieved no need to do so again
- if (item.comments) {
- return Promise.resolve(item);
- }
+ if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
+ var partialsRight = partials,
+ holdersRight = holders;
+ partials = holders = undefined$1;
+ }
- var key = item.issueKey;
- var qParams = {};
+ var data = isBindKey ? undefined$1 : getData(func);
+ var newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
- if (key === 'ow') {
- qParams = item.identifier;
- } else if (key === 'mr') {
- qParams.tileX = item.identifier.x;
- qParams.tileY = item.identifier.y;
- } else if (key === 'tr') {
- qParams.targetId = item.identifier;
- }
+ if (data) {
+ mergeData(newData, data);
+ }
- var url = "".concat(_impOsmUrls[key], "/retrieveComments?") + utilQsString(qParams);
+ func = newData[0];
+ bitmask = newData[1];
+ thisArg = newData[2];
+ partials = newData[3];
+ holders = newData[4];
+ arity = newData[9] = newData[9] === undefined$1 ? isBindKey ? 0 : func.length : nativeMax(newData[9] - length, 0);
- var cacheComments = function cacheComments(data) {
- // Assign directly for immediate use afterwards
- // comments are served newest to oldest
- item.comments = data.comments ? data.comments.reverse() : [];
+ if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
+ bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
+ }
- _this2.replaceItem(item);
- };
+ if (!bitmask || bitmask == WRAP_BIND_FLAG) {
+ var result = createBind(func, bitmask, thisArg);
+ } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
+ result = createCurry(func, bitmask, arity);
+ } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
+ result = createPartial(func, bitmask, thisArg, partials);
+ } else {
+ result = createHybrid.apply(undefined$1, newData);
+ }
- return d3_json(url).then(cacheComments).then(function () {
- return item;
- });
- },
- postUpdate: function postUpdate(d, callback) {
- if (!serviceOsm.authenticated()) {
- // Username required in payload
- return callback({
- message: 'Not Authenticated',
- status: -3
- }, d);
- }
+ var setter = data ? baseSetData : setData;
+ return setWrapToString(setter(result, newData), func, bitmask);
+ }
+ /**
+ * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
+ * of source objects to the destination object for all destination properties
+ * that resolve to `undefined`.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to assign.
+ * @param {Object} object The parent object of `objValue`.
+ * @returns {*} Returns the value to assign.
+ */
- if (_cache$1.inflightPost[d.id]) {
- return callback({
- message: 'Error update already inflight',
- status: -2
- }, d);
- } // Payload can only be sent once username is established
+ function customDefaultsAssignIn(objValue, srcValue, key, object) {
+ if (objValue === undefined$1 || eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key)) {
+ return srcValue;
+ }
- serviceOsm.userDetails(sendPayload.bind(this));
+ return objValue;
+ }
+ /**
+ * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
+ * objects into destination objects that are passed thru.
+ *
+ * @private
+ * @param {*} objValue The destination value.
+ * @param {*} srcValue The source value.
+ * @param {string} key The key of the property to merge.
+ * @param {Object} object The parent object of `objValue`.
+ * @param {Object} source The parent object of `srcValue`.
+ * @param {Object} [stack] Tracks traversed source values and their merged
+ * counterparts.
+ * @returns {*} Returns the value to assign.
+ */
- function sendPayload(err, user) {
- var _this3 = this;
- if (err) {
- return callback(err, d);
+ function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
+ if (isObject(objValue) && isObject(srcValue)) {
+ // Recursively merge objects and arrays (susceptible to call stack limits).
+ stack.set(srcValue, objValue);
+ baseMerge(objValue, srcValue, undefined$1, customDefaultsMerge, stack);
+ stack['delete'](srcValue);
+ }
+
+ return objValue;
}
+ /**
+ * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
+ * objects.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @param {string} key The key of the property to inspect.
+ * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
+ */
- var key = d.issueKey;
- var url = "".concat(_impOsmUrls[key], "/comment");
- var payload = {
- username: user.display_name,
- targetIds: [d.identifier]
- };
- if (d.newStatus) {
- payload.status = d.newStatus;
- payload.text = 'status changed';
- } // Comment take place of default text
+ function customOmitClone(value) {
+ return isPlainObject(value) ? undefined$1 : value;
+ }
+ /**
+ * A specialized version of `baseIsEqualDeep` for arrays with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Array} array The array to compare.
+ * @param {Array} other The other array to compare.
+ * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Object} stack Tracks traversed `array` and `other` objects.
+ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+ */
- if (d.newComment) {
- payload.text = d.newComment;
- }
+ function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
+ var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+ arrLength = array.length,
+ othLength = other.length;
- var controller = new AbortController();
- _cache$1.inflightPost[d.id] = controller;
- var options = {
- method: 'POST',
- signal: controller.signal,
- body: JSON.stringify(payload)
- };
- d3_json(url, options).then(function () {
- delete _cache$1.inflightPost[d.id]; // Just a comment, update error in cache
+ if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+ return false;
+ } // Check that cyclic values are equal.
- if (!d.newStatus) {
- var now = new Date();
- var comments = d.comments ? d.comments : [];
- comments.push({
- username: payload.username,
- text: payload.text,
- timestamp: now.getTime() / 1000
- });
- _this3.replaceItem(d.update({
- comments: comments,
- newComment: undefined
- }));
- } else {
- _this3.removeItem(d);
+ var arrStacked = stack.get(array);
+ var othStacked = stack.get(other);
- if (d.newStatus === 'SOLVED') {
- // Keep track of the number of issues closed per type to tag the changeset
- if (!(d.issueKey in _cache$1.closed)) {
- _cache$1.closed[d.issueKey] = 0;
+ if (arrStacked && othStacked) {
+ return arrStacked == other && othStacked == array;
+ }
+
+ var index = -1,
+ result = true,
+ seen = bitmask & COMPARE_UNORDERED_FLAG ? new SetCache() : undefined$1;
+ stack.set(array, other);
+ stack.set(other, array); // Ignore non-index properties.
+
+ while (++index < arrLength) {
+ var arrValue = array[index],
+ othValue = other[index];
+
+ if (customizer) {
+ var compared = isPartial ? customizer(othValue, arrValue, index, other, array, stack) : customizer(arrValue, othValue, index, array, other, stack);
+ }
+
+ if (compared !== undefined$1) {
+ if (compared) {
+ continue;
}
- _cache$1.closed[d.issueKey] += 1;
+ result = false;
+ break;
+ } // Recursively compare arrays (susceptible to call stack limits).
+
+
+ if (seen) {
+ if (!arraySome(other, function (othValue, othIndex) {
+ if (!cacheHas(seen, othIndex) && (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
+ return seen.push(othIndex);
+ }
+ })) {
+ result = false;
+ break;
+ }
+ } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
+ result = false;
+ break;
}
}
- if (callback) callback(null, d);
- })["catch"](function (err) {
- delete _cache$1.inflightPost[d.id];
- if (callback) callback(err.message);
- });
- }
- },
- // Get all cached QAItems covering the viewport
- getItems: function getItems(projection) {
- var viewport = projection.clipExtent();
- var min = [viewport[0][0], viewport[1][1]];
- var max = [viewport[1][0], viewport[0][1]];
- var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
- return _cache$1.rtree.search(bbox).map(function (d) {
- return d.data;
- });
- },
- // Get a QAItem from cache
- // NOTE: Don't change method name until UI v3 is merged
- getError: function getError(id) {
- return _cache$1.data[id];
- },
- // get the name of the icon to display for this item
- getIcon: function getIcon(itemType) {
- return _impOsmData.icons[itemType];
- },
- // Replace a single QAItem in the cache
- replaceItem: function replaceItem(issue) {
- if (!(issue instanceof QAItem) || !issue.id) return;
- _cache$1.data[issue.id] = issue;
- updateRtree$2(encodeIssueRtree$1(issue), true); // true = replace
+ stack['delete'](array);
+ stack['delete'](other);
+ return result;
+ }
+ /**
+ * A specialized version of `baseIsEqualDeep` for comparing objects of
+ * the same `toStringTag`.
+ *
+ * **Note:** This function only supports comparing values with tags of
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {string} tag The `toStringTag` of the objects to compare.
+ * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
- return issue;
- },
- // Remove a single QAItem from the cache
- removeItem: function removeItem(issue) {
- if (!(issue instanceof QAItem) || !issue.id) return;
- delete _cache$1.data[issue.id];
- updateRtree$2(encodeIssueRtree$1(issue), false); // false = remove
- },
- // Used to populate `closed:improveosm:*` changeset tags
- getClosedCounts: function getClosedCounts() {
- return _cache$1.closed;
- }
- };
- var defaults$5 = createCommonjsModule(function (module) {
- function getDefaults() {
- return {
- baseUrl: null,
- breaks: false,
- gfm: true,
- headerIds: true,
- headerPrefix: '',
- highlight: null,
- langPrefix: 'language-',
- mangle: true,
- pedantic: false,
- renderer: null,
- sanitize: false,
- sanitizer: null,
- silent: false,
- smartLists: false,
- smartypants: false,
- tokenizer: null,
- walkTokens: null,
- xhtml: false
- };
- }
+ function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
+ switch (tag) {
+ case dataViewTag:
+ if (object.byteLength != other.byteLength || object.byteOffset != other.byteOffset) {
+ return false;
+ }
- function changeDefaults(newDefaults) {
- module.exports.defaults = newDefaults;
- }
+ object = object.buffer;
+ other = other.buffer;
- module.exports = {
- defaults: getDefaults(),
- getDefaults: getDefaults,
- changeDefaults: changeDefaults
- };
- });
+ case arrayBufferTag:
+ if (object.byteLength != other.byteLength || !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+ return false;
+ }
- /**
- * Helpers
- */
- var escapeTest = /[&<>"']/;
- var escapeReplace = /[&<>"']/g;
- var escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/;
- var escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g;
- var escapeReplacements = {
- '&': '&',
- '<': '<',
- '>': '>',
- '"': '"',
- "'": '''
- };
+ return true;
- var getEscapeReplacement = function getEscapeReplacement(ch) {
- return escapeReplacements[ch];
- };
+ case boolTag:
+ case dateTag:
+ case numberTag:
+ // Coerce booleans to `1` or `0` and dates to milliseconds.
+ // Invalid dates are coerced to `NaN`.
+ return eq(+object, +other);
- function escape$3(html, encode) {
- if (encode) {
- if (escapeTest.test(html)) {
- return html.replace(escapeReplace, getEscapeReplacement);
- }
- } else {
- if (escapeTestNoEncode.test(html)) {
- return html.replace(escapeReplaceNoEncode, getEscapeReplacement);
- }
- }
+ case errorTag:
+ return object.name == other.name && object.message == other.message;
- return html;
- }
+ case regexpTag:
+ case stringTag:
+ // Coerce regexes to strings and treat strings, primitives and objects,
+ // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
+ // for more details.
+ return object == other + '';
- var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
+ case mapTag:
+ var convert = mapToArray;
- function unescape$2(html) {
- // explicitly match decimal, hex, and named HTML entities
- return html.replace(unescapeTest, function (_, n) {
- n = n.toLowerCase();
- if (n === 'colon') return ':';
+ case setTag:
+ var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
+ convert || (convert = setToArray);
- if (n.charAt(0) === '#') {
- return n.charAt(1) === 'x' ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
- }
+ if (object.size != other.size && !isPartial) {
+ return false;
+ } // Assume cyclic values are equal.
- return '';
- });
- }
- var caret = /(^|[^\[])\^/g;
+ var stacked = stack.get(object);
- function edit$1(regex, opt) {
- regex = regex.source || regex;
- opt = opt || '';
- var obj = {
- replace: function replace(name, val) {
- val = val.source || val;
- val = val.replace(caret, '$1');
- regex = regex.replace(name, val);
- return obj;
- },
- getRegex: function getRegex() {
- return new RegExp(regex, opt);
- }
- };
- return obj;
- }
+ if (stacked) {
+ return stacked == other;
+ }
- var nonWordAndColonTest = /[^\w:]/g;
- var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;
+ bitmask |= COMPARE_UNORDERED_FLAG; // Recursively compare objects (susceptible to call stack limits).
- function cleanUrl$1(sanitize, base, href) {
- if (sanitize) {
- var prot;
+ stack.set(object, other);
+ var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
+ stack['delete'](object);
+ return result;
- try {
- prot = decodeURIComponent(unescape$2(href)).replace(nonWordAndColonTest, '').toLowerCase();
- } catch (e) {
- return null;
- }
+ case symbolTag:
+ if (symbolValueOf) {
+ return symbolValueOf.call(object) == symbolValueOf.call(other);
+ }
- if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) {
- return null;
- }
- }
+ }
- if (base && !originIndependentUrl.test(href)) {
- href = resolveUrl$1(base, href);
- }
+ return false;
+ }
+ /**
+ * A specialized version of `baseIsEqualDeep` for objects with support for
+ * partial deep comparisons.
+ *
+ * @private
+ * @param {Object} object The object to compare.
+ * @param {Object} other The other object to compare.
+ * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+ * @param {Function} customizer The function to customize comparisons.
+ * @param {Function} equalFunc The function to determine equivalents of values.
+ * @param {Object} stack Tracks traversed `object` and `other` objects.
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+ */
- try {
- href = encodeURI(href).replace(/%25/g, '%');
- } catch (e) {
- return null;
- }
- return href;
- }
+ function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
+ var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+ objProps = getAllKeys(object),
+ objLength = objProps.length,
+ othProps = getAllKeys(other),
+ othLength = othProps.length;
- var baseUrls = {};
- var justDomain = /^[^:]+:\/*[^/]*$/;
- var protocol = /^([^:]+:)[\s\S]*$/;
- var domain = /^([^:]+:\/*[^/]*)[\s\S]*$/;
+ if (objLength != othLength && !isPartial) {
+ return false;
+ }
- function resolveUrl$1(base, href) {
- if (!baseUrls[' ' + base]) {
- // we can ignore everything in base after the last slash of its path component,
- // but we might need to add _that_
- // https://tools.ietf.org/html/rfc3986#section-3
- if (justDomain.test(base)) {
- baseUrls[' ' + base] = base + '/';
- } else {
- baseUrls[' ' + base] = rtrim$1(base, '/', true);
- }
- }
+ var index = objLength;
- base = baseUrls[' ' + base];
- var relativeBase = base.indexOf(':') === -1;
+ while (index--) {
+ var key = objProps[index];
- if (href.substring(0, 2) === '//') {
- if (relativeBase) {
- return href;
- }
+ if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+ return false;
+ }
+ } // Check that cyclic values are equal.
- return base.replace(protocol, '$1') + href;
- } else if (href.charAt(0) === '/') {
- if (relativeBase) {
- return href;
- }
- return base.replace(domain, '$1') + href;
- } else {
- return base + href;
- }
- }
+ var objStacked = stack.get(object);
+ var othStacked = stack.get(other);
- var noopTest$1 = {
- exec: function noopTest() {}
- };
+ if (objStacked && othStacked) {
+ return objStacked == other && othStacked == object;
+ }
- function merge$2(obj) {
- var i = 1,
- target,
- key;
+ var result = true;
+ stack.set(object, other);
+ stack.set(other, object);
+ var skipCtor = isPartial;
- for (; i < arguments.length; i++) {
- target = arguments[i];
+ while (++index < objLength) {
+ key = objProps[index];
+ var objValue = object[key],
+ othValue = other[key];
- for (key in target) {
- if (Object.prototype.hasOwnProperty.call(target, key)) {
- obj[key] = target[key];
- }
- }
- }
+ if (customizer) {
+ var compared = isPartial ? customizer(othValue, objValue, key, other, object, stack) : customizer(objValue, othValue, key, object, other, stack);
+ } // Recursively compare objects (susceptible to call stack limits).
- return obj;
- }
- function splitCells$1(tableRow, count) {
- // ensure that every cell-delimiting pipe has a space
- // before it to distinguish it from an escaped pipe
- var row = tableRow.replace(/\|/g, function (match, offset, str) {
- var escaped = false,
- curr = offset;
+ if (!(compared === undefined$1 ? objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack) : compared)) {
+ result = false;
+ break;
+ }
- while (--curr >= 0 && str[curr] === '\\') {
- escaped = !escaped;
- }
+ skipCtor || (skipCtor = key == 'constructor');
+ }
- if (escaped) {
- // odd number of slashes means | is escaped
- // so we leave it alone
- return '|';
- } else {
- // add space before unescaped |
- return ' |';
- }
- }),
- cells = row.split(/ \|/);
- var i = 0;
+ if (result && !skipCtor) {
+ var objCtor = object.constructor,
+ othCtor = other.constructor; // Non `Object` object instances with different constructors are not equal.
- if (cells.length > count) {
- cells.splice(count);
- } else {
- while (cells.length < count) {
- cells.push('');
- }
- }
+ if (objCtor != othCtor && 'constructor' in object && 'constructor' in other && !(typeof objCtor == 'function' && objCtor instanceof objCtor && typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+ result = false;
+ }
+ }
- for (; i < cells.length; i++) {
- // leading or trailing whitespace is ignored per the gfm spec
- cells[i] = cells[i].trim().replace(/\\\|/g, '|');
- }
+ stack['delete'](object);
+ stack['delete'](other);
+ return result;
+ }
+ /**
+ * A specialized version of `baseRest` which flattens the rest array.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @returns {Function} Returns the new function.
+ */
- return cells;
- } // Remove trailing 'c's. Equivalent to str.replace(/c*$/, '').
- // /c*$/ is vulnerable to REDOS.
- // invert: Remove suffix of non-c chars instead. Default falsey.
+ function flatRest(func) {
+ return setToString(overRest(func, undefined$1, flatten), func + '');
+ }
+ /**
+ * Creates an array of own enumerable property names and symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
- function rtrim$1(str, c, invert) {
- var l = str.length;
- if (l === 0) {
- return '';
- } // Length of suffix matching the invert condition.
+ function getAllKeys(object) {
+ return baseGetAllKeys(object, keys, getSymbols);
+ }
+ /**
+ * Creates an array of own and inherited enumerable property names and
+ * symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names and symbols.
+ */
- var suffLen = 0; // Step left until we fail to match the invert condition.
+ function getAllKeysIn(object) {
+ return baseGetAllKeys(object, keysIn, getSymbolsIn);
+ }
+ /**
+ * Gets metadata for `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {*} Returns the metadata for `func`.
+ */
- while (suffLen < l) {
- var currChar = str.charAt(l - suffLen - 1);
- if (currChar === c && !invert) {
- suffLen++;
- } else if (currChar !== c && invert) {
- suffLen++;
- } else {
- break;
- }
- }
+ var getData = !metaMap ? noop : function (func) {
+ return metaMap.get(func);
+ };
+ /**
+ * Gets the name of `func`.
+ *
+ * @private
+ * @param {Function} func The function to query.
+ * @returns {string} Returns the function name.
+ */
- return str.substr(0, l - suffLen);
- }
+ function getFuncName(func) {
+ var result = func.name + '',
+ array = realNames[result],
+ length = hasOwnProperty.call(realNames, result) ? array.length : 0;
- function findClosingBracket$1(str, b) {
- if (str.indexOf(b[1]) === -1) {
- return -1;
- }
+ while (length--) {
+ var data = array[length],
+ otherFunc = data.func;
- var l = str.length;
- var level = 0,
- i = 0;
+ if (otherFunc == null || otherFunc == func) {
+ return data.name;
+ }
+ }
- for (; i < l; i++) {
- if (str[i] === '\\') {
- i++;
- } else if (str[i] === b[0]) {
- level++;
- } else if (str[i] === b[1]) {
- level--;
+ return result;
+ }
+ /**
+ * Gets the argument placeholder value for `func`.
+ *
+ * @private
+ * @param {Function} func The function to inspect.
+ * @returns {*} Returns the placeholder value.
+ */
- if (level < 0) {
- return i;
+
+ function getHolder(func) {
+ var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+ return object.placeholder;
}
- }
- }
+ /**
+ * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+ * this function returns the custom method, otherwise it returns `baseIteratee`.
+ * If arguments are provided, the chosen function is invoked with them and
+ * its result is returned.
+ *
+ * @private
+ * @param {*} [value] The value to convert to an iteratee.
+ * @param {number} [arity] The arity of the created iteratee.
+ * @returns {Function} Returns the chosen function or its result.
+ */
- return -1;
- }
- function checkSanitizeDeprecation$1(opt) {
- if (opt && opt.sanitize && !opt.silent) {
- console.warn('marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options');
- }
- } // copied from https://stackoverflow.com/a/5450113/806777
+ function getIteratee() {
+ var result = lodash.iteratee || iteratee;
+ result = result === iteratee ? baseIteratee : result;
+ return arguments.length ? result(arguments[0], arguments[1]) : result;
+ }
+ /**
+ * Gets the data for `map`.
+ *
+ * @private
+ * @param {Object} map The map to query.
+ * @param {string} key The reference key.
+ * @returns {*} Returns the map data.
+ */
- function repeatString$1(pattern, count) {
- if (count < 1) {
- return '';
- }
+ function getMapData(map, key) {
+ var data = map.__data__;
+ return isKeyable(key) ? data[typeof key == 'string' ? 'string' : 'hash'] : data.map;
+ }
+ /**
+ * Gets the property names, values, and compare flags of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the match data of `object`.
+ */
- var result = '';
- while (count > 1) {
- if (count & 1) {
- result += pattern;
- }
+ function getMatchData(object) {
+ var result = keys(object),
+ length = result.length;
- count >>= 1;
- pattern += pattern;
- }
+ while (length--) {
+ var key = result[length],
+ value = object[key];
+ result[length] = [key, value, isStrictComparable(value)];
+ }
- return result + pattern;
- }
+ return result;
+ }
+ /**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
- var helpers = {
- escape: escape$3,
- unescape: unescape$2,
- edit: edit$1,
- cleanUrl: cleanUrl$1,
- resolveUrl: resolveUrl$1,
- noopTest: noopTest$1,
- merge: merge$2,
- splitCells: splitCells$1,
- rtrim: rtrim$1,
- findClosingBracket: findClosingBracket$1,
- checkSanitizeDeprecation: checkSanitizeDeprecation$1,
- repeatString: repeatString$1
- };
- var defaults$4 = defaults$5.defaults;
- var rtrim = helpers.rtrim,
- splitCells = helpers.splitCells,
- _escape = helpers.escape,
- findClosingBracket = helpers.findClosingBracket;
+ function getNative(object, key) {
+ var value = getValue(object, key);
+ return baseIsNative(value) ? value : undefined$1;
+ }
+ /**
+ * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the raw `toStringTag`.
+ */
- function outputLink(cap, link, raw) {
- var href = link.href;
- var title = link.title ? _escape(link.title) : null;
- var text = cap[1].replace(/\\([\[\]])/g, '$1');
- if (cap[0].charAt(0) !== '!') {
- return {
- type: 'link',
- raw: raw,
- href: href,
- title: title,
- text: text
- };
- } else {
- return {
- type: 'image',
- raw: raw,
- href: href,
- title: title,
- text: _escape(text)
- };
- }
- }
+ function getRawTag(value) {
+ var isOwn = hasOwnProperty.call(value, symToStringTag),
+ tag = value[symToStringTag];
- function indentCodeCompensation(raw, text) {
- var matchIndentToCode = raw.match(/^(\s+)(?:```)/);
+ try {
+ value[symToStringTag] = undefined$1;
+ var unmasked = true;
+ } catch (e) {}
- if (matchIndentToCode === null) {
- return text;
- }
+ var result = nativeObjectToString.call(value);
- var indentToCode = matchIndentToCode[1];
- return text.split('\n').map(function (node) {
- var matchIndentInNode = node.match(/^\s+/);
+ if (unmasked) {
+ if (isOwn) {
+ value[symToStringTag] = tag;
+ } else {
+ delete value[symToStringTag];
+ }
+ }
- if (matchIndentInNode === null) {
- return node;
- }
+ return result;
+ }
+ /**
+ * Creates an array of the own enumerable symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of symbols.
+ */
- var _matchIndentInNode = _slicedToArray(matchIndentInNode, 1),
- indentInNode = _matchIndentInNode[0];
- if (indentInNode.length >= indentToCode.length) {
- return node.slice(indentToCode.length);
- }
+ var getSymbols = !nativeGetSymbols ? stubArray : function (object) {
+ if (object == null) {
+ return [];
+ }
- return node;
- }).join('\n');
- }
- /**
- * Tokenizer
- */
+ object = Object(object);
+ return arrayFilter(nativeGetSymbols(object), function (symbol) {
+ return propertyIsEnumerable.call(object, symbol);
+ });
+ };
+ /**
+ * Creates an array of the own and inherited enumerable symbols of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of symbols.
+ */
+ var getSymbolsIn = !nativeGetSymbols ? stubArray : function (object) {
+ var result = [];
- var Tokenizer_1 = /*#__PURE__*/function () {
- function Tokenizer(options) {
- _classCallCheck$1(this, Tokenizer);
+ while (object) {
+ arrayPush(result, getSymbols(object));
+ object = getPrototype(object);
+ }
- this.options = options || defaults$4;
- }
+ return result;
+ };
+ /**
+ * Gets the `toStringTag` of `value`.
+ *
+ * @private
+ * @param {*} value The value to query.
+ * @returns {string} Returns the `toStringTag`.
+ */
- _createClass$1(Tokenizer, [{
- key: "space",
- value: function space(src) {
- var cap = this.rules.block.newline.exec(src);
+ var getTag = baseGetTag; // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
- if (cap) {
- if (cap[0].length > 1) {
- return {
- type: 'space',
- raw: cap[0]
- };
- }
+ if (DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag || Map && getTag(new Map()) != mapTag || Promise && getTag(Promise.resolve()) != promiseTag || Set && getTag(new Set()) != setTag || WeakMap && getTag(new WeakMap()) != weakMapTag) {
+ getTag = function getTag(value) {
+ var result = baseGetTag(value),
+ Ctor = result == objectTag ? value.constructor : undefined$1,
+ ctorString = Ctor ? toSource(Ctor) : '';
- return {
- raw: '\n'
- };
- }
- }
- }, {
- key: "code",
- value: function code(src) {
- var cap = this.rules.block.code.exec(src);
+ if (ctorString) {
+ switch (ctorString) {
+ case dataViewCtorString:
+ return dataViewTag;
- if (cap) {
- var text = cap[0].replace(/^ {1,4}/gm, '');
- return {
- type: 'code',
- raw: cap[0],
- codeBlockStyle: 'indented',
- text: !this.options.pedantic ? rtrim(text, '\n') : text
- };
- }
- }
- }, {
- key: "fences",
- value: function fences(src) {
- var cap = this.rules.block.fences.exec(src);
+ case mapCtorString:
+ return mapTag;
- if (cap) {
- var raw = cap[0];
- var text = indentCodeCompensation(raw, cap[3] || '');
- return {
- type: 'code',
- raw: raw,
- lang: cap[2] ? cap[2].trim() : cap[2],
- text: text
+ case promiseCtorString:
+ return promiseTag;
+
+ case setCtorString:
+ return setTag;
+
+ case weakMapCtorString:
+ return weakMapTag;
+ }
+ }
+
+ return result;
};
}
- }
- }, {
- key: "heading",
- value: function heading(src) {
- var cap = this.rules.block.heading.exec(src);
+ /**
+ * Gets the view, applying any `transforms` to the `start` and `end` positions.
+ *
+ * @private
+ * @param {number} start The start of the view.
+ * @param {number} end The end of the view.
+ * @param {Array} transforms The transformations to apply to the view.
+ * @returns {Object} Returns an object containing the `start` and `end`
+ * positions of the view.
+ */
- if (cap) {
- var text = cap[2].trim(); // remove trailing #s
- if (/#$/.test(text)) {
- var trimmed = rtrim(text, '#');
+ function getView(start, end, transforms) {
+ var index = -1,
+ length = transforms.length;
- if (this.options.pedantic) {
- text = trimmed.trim();
- } else if (!trimmed || / $/.test(trimmed)) {
- // CommonMark requires space before trailing #s
- text = trimmed.trim();
+ while (++index < length) {
+ var data = transforms[index],
+ size = data.size;
+
+ switch (data.type) {
+ case 'drop':
+ start += size;
+ break;
+
+ case 'dropRight':
+ end -= size;
+ break;
+
+ case 'take':
+ end = nativeMin(end, start + size);
+ break;
+
+ case 'takeRight':
+ start = nativeMax(start, end - size);
+ break;
}
}
return {
- type: 'heading',
- raw: cap[0],
- depth: cap[1].length,
- text: text
+ 'start': start,
+ 'end': end
};
}
- }
- }, {
- key: "nptable",
- value: function nptable(src) {
- var cap = this.rules.block.nptable.exec(src);
+ /**
+ * Extracts wrapper details from the `source` body comment.
+ *
+ * @private
+ * @param {string} source The source to inspect.
+ * @returns {Array} Returns the wrapper details.
+ */
- if (cap) {
- var item = {
- type: 'table',
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [],
- raw: cap[0]
- };
- if (item.header.length === item.align.length) {
- var l = item.align.length;
- var i;
+ function getWrapDetails(source) {
+ var match = source.match(reWrapDetails);
+ return match ? match[1].split(reSplitDetails) : [];
+ }
+ /**
+ * Checks if `path` exists on `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @param {Function} hasFunc The function to check properties.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ */
- for (i = 0; i < l; i++) {
- if (/^ *-+: *$/.test(item.align[i])) {
- item.align[i] = 'right';
- } else if (/^ *:-+: *$/.test(item.align[i])) {
- item.align[i] = 'center';
- } else if (/^ *:-+ *$/.test(item.align[i])) {
- item.align[i] = 'left';
- } else {
- item.align[i] = null;
- }
- }
- l = item.cells.length;
+ function hasPath(object, path, hasFunc) {
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length,
+ result = false;
- for (i = 0; i < l; i++) {
- item.cells[i] = splitCells(item.cells[i], item.header.length);
+ while (++index < length) {
+ var key = toKey(path[index]);
+
+ if (!(result = object != null && hasFunc(object, key))) {
+ break;
}
- return item;
+ object = object[key];
+ }
+
+ if (result || ++index != length) {
+ return result;
}
+
+ length = object == null ? 0 : object.length;
+ return !!length && isLength(length) && isIndex(key, length) && (isArray(object) || isArguments(object));
}
- }
- }, {
- key: "hr",
- value: function hr(src) {
- var cap = this.rules.block.hr.exec(src);
+ /**
+ * Initializes an array clone.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the initialized clone.
+ */
- if (cap) {
- return {
- type: 'hr',
- raw: cap[0]
- };
+
+ function initCloneArray(array) {
+ var length = array.length,
+ result = new array.constructor(length); // Add properties assigned by `RegExp#exec`.
+
+ if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+ result.index = array.index;
+ result.input = array.input;
+ }
+
+ return result;
}
- }
- }, {
- key: "blockquote",
- value: function blockquote(src) {
- var cap = this.rules.block.blockquote.exec(src);
+ /**
+ * Initializes an object clone.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @returns {Object} Returns the initialized clone.
+ */
- if (cap) {
- var text = cap[0].replace(/^ *> ?/gm, '');
- return {
- type: 'blockquote',
- raw: cap[0],
- text: text
- };
+
+ function initCloneObject(object) {
+ return typeof object.constructor == 'function' && !isPrototype(object) ? baseCreate(getPrototype(object)) : {};
}
- }
- }, {
- key: "list",
- value: function list(src) {
- var cap = this.rules.block.list.exec(src);
+ /**
+ * Initializes an object clone based on its `toStringTag`.
+ *
+ * **Note:** This function only supports cloning values with tags of
+ * `Boolean`, `Date`, `Error`, `Map`, `Number`, `RegExp`, `Set`, or `String`.
+ *
+ * @private
+ * @param {Object} object The object to clone.
+ * @param {string} tag The `toStringTag` of the object to clone.
+ * @param {boolean} [isDeep] Specify a deep clone.
+ * @returns {Object} Returns the initialized clone.
+ */
- if (cap) {
- var raw = cap[0];
- var bull = cap[2];
- var isordered = bull.length > 1;
- var list = {
- type: 'list',
- raw: raw,
- ordered: isordered,
- start: isordered ? +bull.slice(0, -1) : '',
- loose: false,
- items: []
- }; // Get each top-level item.
- var itemMatch = cap[0].match(this.rules.block.item);
- var next = false,
- item,
- space,
- bcurr,
- bnext,
- addBack,
- loose,
- istask,
- ischecked,
- endMatch;
- var l = itemMatch.length;
- bcurr = this.rules.block.listItemStart.exec(itemMatch[0]);
+ function initCloneByTag(object, tag, isDeep) {
+ var Ctor = object.constructor;
- for (var i = 0; i < l; i++) {
- item = itemMatch[i];
- raw = item;
+ switch (tag) {
+ case arrayBufferTag:
+ return cloneArrayBuffer(object);
- if (!this.options.pedantic) {
- // Determine if current item contains the end of the list
- endMatch = item.match(new RegExp('\\n\\s*\\n {0,' + (bcurr[0].length - 1) + '}\\S'));
+ case boolTag:
+ case dateTag:
+ return new Ctor(+object);
- if (endMatch) {
- addBack = item.length - endMatch.index + itemMatch.slice(i + 1).join('\n').length;
- list.raw = list.raw.substring(0, list.raw.length - addBack);
- item = item.substring(0, endMatch.index);
- raw = item;
- l = i + 1;
- }
- } // Determine whether the next list item belongs here.
- // Backpedal if it does not belong in this list.
+ case dataViewTag:
+ return cloneDataView(object, isDeep);
+ case float32Tag:
+ case float64Tag:
+ case int8Tag:
+ case int16Tag:
+ case int32Tag:
+ case uint8Tag:
+ case uint8ClampedTag:
+ case uint16Tag:
+ case uint32Tag:
+ return cloneTypedArray(object, isDeep);
- if (i !== l - 1) {
- bnext = this.rules.block.listItemStart.exec(itemMatch[i + 1]);
+ case mapTag:
+ return new Ctor();
- if (!this.options.pedantic ? bnext[1].length >= bcurr[0].length || bnext[1].length > 3 : bnext[1].length > bcurr[1].length) {
- // nested list or continuation
- itemMatch.splice(i, 2, itemMatch[i] + (!this.options.pedantic && bnext[1].length < bcurr[0].length && !itemMatch[i].match(/\n$/) ? '' : '\n') + itemMatch[i + 1]);
- i--;
- l--;
- continue;
- } else if ( // different bullet style
- !this.options.pedantic || this.options.smartLists ? bnext[2][bnext[2].length - 1] !== bull[bull.length - 1] : isordered === (bnext[2].length === 1)) {
- addBack = itemMatch.slice(i + 1).join('\n').length;
- list.raw = list.raw.substring(0, list.raw.length - addBack);
- i = l - 1;
- }
+ case numberTag:
+ case stringTag:
+ return new Ctor(object);
- bcurr = bnext;
- } // Remove the list item's bullet
- // so it is seen as the next token.
+ case regexpTag:
+ return cloneRegExp(object);
+ case setTag:
+ return new Ctor();
- space = item.length;
- item = item.replace(/^ *([*+-]|\d+[.)]) ?/, ''); // Outdent whatever the
- // list item contains. Hacky.
+ case symbolTag:
+ return cloneSymbol(object);
+ }
+ }
+ /**
+ * Inserts wrapper `details` in a comment at the top of the `source` body.
+ *
+ * @private
+ * @param {string} source The source to modify.
+ * @returns {Array} details The details to insert.
+ * @returns {string} Returns the modified source.
+ */
- if (~item.indexOf('\n ')) {
- space -= item.length;
- item = !this.options.pedantic ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : item.replace(/^ {1,4}/gm, '');
- } // trim item newlines at end
+ function insertWrapDetails(source, details) {
+ var length = details.length;
- item = rtrim(item, '\n');
+ if (!length) {
+ return source;
+ }
- if (i !== l - 1) {
- raw = raw + '\n';
- } // Determine whether item is loose or not.
- // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
- // for discount behavior.
+ var lastIndex = length - 1;
+ details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
+ details = details.join(length > 2 ? ', ' : ' ');
+ return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
+ }
+ /**
+ * Checks if `value` is a flattenable `arguments` object or array.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+ */
- loose = next || /\n\n(?!\s*$)/.test(raw);
+ function isFlattenable(value) {
+ return isArray(value) || isArguments(value) || !!(spreadableSymbol && value && value[spreadableSymbol]);
+ }
+ /**
+ * Checks if `value` is a valid array-like index.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+ */
- if (i !== l - 1) {
- next = raw.slice(-2) === '\n\n';
- if (!loose) loose = next;
- }
- if (loose) {
- list.loose = true;
- } // Check for task list items
+ function isIndex(value, length) {
+ var type = _typeof(value);
+ length = length == null ? MAX_SAFE_INTEGER : length;
+ return !!length && (type == 'number' || type != 'symbol' && reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
+ }
+ /**
+ * Checks if the given arguments are from an iteratee call.
+ *
+ * @private
+ * @param {*} value The potential iteratee value argument.
+ * @param {*} index The potential iteratee index or key argument.
+ * @param {*} object The potential iteratee object argument.
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+ * else `false`.
+ */
- if (this.options.gfm) {
- istask = /^\[[ xX]\] /.test(item);
- ischecked = undefined;
- if (istask) {
- ischecked = item[1] !== ' ';
- item = item.replace(/^\[[ xX]\] +/, '');
- }
- }
+ function isIterateeCall(value, index, object) {
+ if (!isObject(object)) {
+ return false;
+ }
- list.items.push({
- type: 'list_item',
- raw: raw,
- task: istask,
- checked: ischecked,
- loose: loose,
- text: item
- });
+ var type = _typeof(index);
+
+ if (type == 'number' ? isArrayLike(object) && isIndex(index, object.length) : type == 'string' && index in object) {
+ return eq(object[index], value);
}
- return list;
+ return false;
}
- }
- }, {
- key: "html",
- value: function html(src) {
- var cap = this.rules.block.html.exec(src);
+ /**
+ * Checks if `value` is a property name and not a property path.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+ */
- if (cap) {
- return {
- type: this.options.sanitize ? 'paragraph' : 'html',
- raw: cap[0],
- pre: !this.options.sanitizer && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
- text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0]
- };
- }
- }
- }, {
- key: "def",
- value: function def(src) {
- var cap = this.rules.block.def.exec(src);
- if (cap) {
- if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1);
- var tag = cap[1].toLowerCase().replace(/\s+/g, ' ');
- return {
- type: 'def',
- tag: tag,
- raw: cap[0],
- href: cap[2],
- title: cap[3]
- };
+ function isKey(value, object) {
+ if (isArray(value)) {
+ return false;
+ }
+
+ var type = _typeof(value);
+
+ if (type == 'number' || type == 'symbol' || type == 'boolean' || value == null || isSymbol(value)) {
+ return true;
+ }
+
+ return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || object != null && value in Object(object);
}
- }
- }, {
- key: "table",
- value: function table(src) {
- var cap = this.rules.block.table.exec(src);
+ /**
+ * Checks if `value` is suitable for use as unique object key.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+ */
- if (cap) {
- var item = {
- type: 'table',
- header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')),
- align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
- cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : []
- };
- if (item.header.length === item.align.length) {
- item.raw = cap[0];
- var l = item.align.length;
- var i;
+ function isKeyable(value) {
+ var type = _typeof(value);
- for (i = 0; i < l; i++) {
- if (/^ *-+: *$/.test(item.align[i])) {
- item.align[i] = 'right';
- } else if (/^ *:-+: *$/.test(item.align[i])) {
- item.align[i] = 'center';
- } else if (/^ *:-+ *$/.test(item.align[i])) {
- item.align[i] = 'left';
- } else {
- item.align[i] = null;
- }
- }
+ return type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean' ? value !== '__proto__' : value === null;
+ }
+ /**
+ * Checks if `func` has a lazy counterpart.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+ * else `false`.
+ */
- l = item.cells.length;
- for (i = 0; i < l; i++) {
- item.cells[i] = splitCells(item.cells[i].replace(/^ *\| *| *\| *$/g, ''), item.header.length);
- }
+ function isLaziable(func) {
+ var funcName = getFuncName(func),
+ other = lodash[funcName];
- return item;
+ if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+ return false;
+ }
+
+ if (func === other) {
+ return true;
}
+
+ var data = getData(other);
+ return !!data && func === data[0];
}
- }
- }, {
- key: "lheading",
- value: function lheading(src) {
- var cap = this.rules.block.lheading.exec(src);
+ /**
+ * Checks if `func` has its source masked.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+ */
- if (cap) {
- return {
- type: 'heading',
- raw: cap[0],
- depth: cap[2].charAt(0) === '=' ? 1 : 2,
- text: cap[1]
- };
+
+ function isMasked(func) {
+ return !!maskSrcKey && maskSrcKey in func;
}
- }
- }, {
- key: "paragraph",
- value: function paragraph(src) {
- var cap = this.rules.block.paragraph.exec(src);
+ /**
+ * Checks if `func` is capable of being masked.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
+ */
- if (cap) {
- return {
- type: 'paragraph',
- raw: cap[0],
- text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]
- };
+
+ var isMaskable = coreJsData ? isFunction : stubFalse;
+ /**
+ * Checks if `value` is likely a prototype object.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+ */
+
+ function isPrototype(value) {
+ var Ctor = value && value.constructor,
+ proto = typeof Ctor == 'function' && Ctor.prototype || objectProto;
+ return value === proto;
}
- }
- }, {
- key: "text",
- value: function text(src) {
- var cap = this.rules.block.text.exec(src);
+ /**
+ * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` if suitable for strict
+ * equality comparisons, else `false`.
+ */
- if (cap) {
- return {
- type: 'text',
- raw: cap[0],
- text: cap[0]
- };
+
+ function isStrictComparable(value) {
+ return value === value && !isObject(value);
}
- }
- }, {
- key: "escape",
- value: function escape(src) {
- var cap = this.rules.inline.escape.exec(src);
+ /**
+ * A specialized version of `matchesProperty` for source values suitable
+ * for strict equality comparisons, i.e. `===`.
+ *
+ * @private
+ * @param {string} key The key of the property to get.
+ * @param {*} srcValue The value to match.
+ * @returns {Function} Returns the new spec function.
+ */
- if (cap) {
- return {
- type: 'escape',
- raw: cap[0],
- text: _escape(cap[1])
+
+ function matchesStrictComparable(key, srcValue) {
+ return function (object) {
+ if (object == null) {
+ return false;
+ }
+
+ return object[key] === srcValue && (srcValue !== undefined$1 || key in Object(object));
};
}
- }
- }, {
- key: "tag",
- value: function tag(src, inLink, inRawBlock) {
- var cap = this.rules.inline.tag.exec(src);
+ /**
+ * A specialized version of `_.memoize` which clears the memoized function's
+ * cache when it exceeds `MAX_MEMOIZE_SIZE`.
+ *
+ * @private
+ * @param {Function} func The function to have its output memoized.
+ * @returns {Function} Returns the new memoized function.
+ */
- if (cap) {
- if (!inLink && /^/i.test(cap[0])) {
- inLink = false;
- }
- if (!inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
- inRawBlock = true;
- } else if (inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
- inRawBlock = false;
- }
+ function memoizeCapped(func) {
+ var result = memoize(func, function (key) {
+ if (cache.size === MAX_MEMOIZE_SIZE) {
+ cache.clear();
+ }
- return {
- type: this.options.sanitize ? 'text' : 'html',
- raw: cap[0],
- inLink: inLink,
- inRawBlock: inRawBlock,
- text: this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0]
- };
+ return key;
+ });
+ var cache = result.cache;
+ return result;
}
- }
- }, {
- key: "link",
- value: function link(src) {
- var cap = this.rules.inline.link.exec(src);
+ /**
+ * Merges the function metadata of `source` into `data`.
+ *
+ * Merging metadata reduces the number of wrappers used to invoke a function.
+ * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+ * may be applied regardless of execution order. Methods like `_.ary` and
+ * `_.rearg` modify function arguments, making the order in which they are
+ * executed important, preventing the merging of metadata. However, we make
+ * an exception for a safe combined case where curried functions have `_.ary`
+ * and or `_.rearg` applied.
+ *
+ * @private
+ * @param {Array} data The destination metadata.
+ * @param {Array} source The source metadata.
+ * @returns {Array} Returns `data`.
+ */
- if (cap) {
- var trimmedUrl = cap[2].trim();
- if (!this.options.pedantic && /^$/.test(trimmedUrl)) {
- return;
- } // ending angle bracket cannot be escaped
+ function mergeData(data, source) {
+ var bitmask = data[1],
+ srcBitmask = source[1],
+ newBitmask = bitmask | srcBitmask,
+ isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
+ var isCombo = srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_CURRY_FLAG || srcBitmask == WRAP_ARY_FLAG && bitmask == WRAP_REARG_FLAG && data[7].length <= source[8] || srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG) && source[7].length <= source[8] && bitmask == WRAP_CURRY_FLAG; // Exit early if metadata can't be merged.
+ if (!(isCommon || isCombo)) {
+ return data;
+ } // Use source `thisArg` if available.
- var rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\');
- if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
- return;
- }
- } else {
- // find closing parenthesis
- var lastParenIndex = findClosingBracket(cap[2], '()');
+ if (srcBitmask & WRAP_BIND_FLAG) {
+ data[2] = source[2]; // Set when currying a bound function.
- if (lastParenIndex > -1) {
- var start = cap[0].indexOf('!') === 0 ? 5 : 4;
- var linkLen = start + cap[1].length + lastParenIndex;
- cap[2] = cap[2].substring(0, lastParenIndex);
- cap[0] = cap[0].substring(0, linkLen).trim();
- cap[3] = '';
+ newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
+ } // Compose partial arguments.
+
+
+ var value = source[3];
+
+ if (value) {
+ var partials = data[3];
+ data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+ data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+ } // Compose partial right arguments.
+
+
+ value = source[5];
+
+ if (value) {
+ partials = data[5];
+ data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+ data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+ } // Use source `argPos` if available.
+
+
+ value = source[7];
+
+ if (value) {
+ data[7] = value;
+ } // Use source `ary` if it's smaller.
+
+
+ if (srcBitmask & WRAP_ARY_FLAG) {
+ data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+ } // Use source `arity` if one is not provided.
+
+
+ if (data[9] == null) {
+ data[9] = source[9];
+ } // Use source `func` and merge bitmasks.
+
+
+ data[0] = source[0];
+ data[1] = newBitmask;
+ return data;
+ }
+ /**
+ * This function is like
+ * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * except that it includes inherited enumerable properties.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ */
+
+
+ function nativeKeysIn(object) {
+ var result = [];
+
+ if (object != null) {
+ for (var key in Object(object)) {
+ result.push(key);
}
}
- var href = cap[2];
- var title = '';
+ return result;
+ }
+ /**
+ * Converts `value` to a string using `Object.prototype.toString`.
+ *
+ * @private
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ */
- if (this.options.pedantic) {
- // split pedantic href and title
- var link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
- if (link) {
- href = link[1];
- title = link[3];
+ function objectToString(value) {
+ return nativeObjectToString.call(value);
+ }
+ /**
+ * A specialized version of `baseRest` which transforms the rest array.
+ *
+ * @private
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @param {Function} transform The rest array transform.
+ * @returns {Function} Returns the new function.
+ */
+
+
+ function overRest(func, start, transform) {
+ start = nativeMax(start === undefined$1 ? func.length - 1 : start, 0);
+ return function () {
+ var args = arguments,
+ index = -1,
+ length = nativeMax(args.length - start, 0),
+ array = Array(length);
+
+ while (++index < length) {
+ array[index] = args[start + index];
}
- } else {
- title = cap[3] ? cap[3].slice(1, -1) : '';
- }
- href = href.trim();
+ index = -1;
+ var otherArgs = Array(start + 1);
- if (/^$/.test(trimmedUrl)) {
- // pedantic allows starting angle bracket without ending angle bracket
- href = href.slice(1);
- } else {
- href = href.slice(1, -1);
+ while (++index < start) {
+ otherArgs[index] = args[index];
}
+
+ otherArgs[start] = transform(array);
+ return apply(func, this, otherArgs);
+ };
+ }
+ /**
+ * Gets the parent value at `path` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {Array} path The path to get the parent value of.
+ * @returns {*} Returns the parent value.
+ */
+
+
+ function parent(object, path) {
+ return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
+ }
+ /**
+ * Reorder `array` according to the specified indexes where the element at
+ * the first index is assigned as the first element, the element at
+ * the second index is assigned as the second element, and so on.
+ *
+ * @private
+ * @param {Array} array The array to reorder.
+ * @param {Array} indexes The arranged array indexes.
+ * @returns {Array} Returns `array`.
+ */
+
+
+ function reorder(array, indexes) {
+ var arrLength = array.length,
+ length = nativeMin(indexes.length, arrLength),
+ oldArray = copyArray(array);
+
+ while (length--) {
+ var index = indexes[length];
+ array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined$1;
}
- return outputLink(cap, {
- href: href ? href.replace(this.rules.inline._escapes, '$1') : href,
- title: title ? title.replace(this.rules.inline._escapes, '$1') : title
- }, cap[0]);
+ return array;
}
- }
- }, {
- key: "reflink",
- value: function reflink(src, links) {
- var cap;
+ /**
+ * Gets the value at `key`, unless `key` is "__proto__" or "constructor".
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the property to get.
+ * @returns {*} Returns the property value.
+ */
- if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
- var link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
- link = links[link.toLowerCase()];
- if (!link || !link.href) {
- var text = cap[0].charAt(0);
- return {
- type: 'text',
- raw: text,
- text: text
- };
+ function safeGet(object, key) {
+ if (key === 'constructor' && typeof object[key] === 'function') {
+ return;
}
- return outputLink(cap, link, cap[0]);
+ if (key == '__proto__') {
+ return;
+ }
+
+ return object[key];
}
- }
- }, {
- key: "emStrong",
- value: function emStrong(src, maskedSrc) {
- var prevChar = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
- var match = this.rules.inline.emStrong.lDelim.exec(src);
- if (!match) return; // _ can't be between two alphanumerics. \p{L}\p{N} includes non-english alphabet/numbers as well
+ /**
+ * Sets metadata for `func`.
+ *
+ * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+ * period of time, it will trip its breaker and transition to an identity
+ * function to avoid garbage collection pauses in V8. See
+ * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+ * for more details.
+ *
+ * @private
+ * @param {Function} func The function to associate metadata with.
+ * @param {*} data The metadata.
+ * @returns {Function} Returns `func`.
+ */
- if (match[3] && prevChar.match(/(?:[0-9A-Za-z\xAA\xB2\xB3\xB5\xB9\xBA\xBC-\xBE\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0560-\u0588\u05D0-\u05EA\u05EF-\u05F2\u0620-\u064A\u0660-\u0669\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07C0-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08C7\u0904-\u0939\u093D\u0950\u0958-\u0961\u0966-\u096F\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09E6-\u09F1\u09F4-\u09F9\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A66-\u0A6F\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AE6-\u0AEF\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B66-\u0B6F\u0B71-\u0B77\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0BE6-\u0BF2\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C66-\u0C6F\u0C78-\u0C7E\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CE6-\u0CEF\u0CF1\u0CF2\u0D04-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D58-\u0D61\u0D66-\u0D78\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DE6-\u0DEF\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E86-\u0E8A\u0E8C-\u0EA3\u0EA5\u0EA7-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F20-\u0F33\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F-\u1049\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u1090-\u1099\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1369-\u137C\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1820-\u1878\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A16\u1A20-\u1A54\u1A80-\u1A89\u1A90-\u1A99\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B50-\u1B59\u1B83-\u1BA0\u1BAE-\u1BE5\u1C00-\u1C23\u1C40-\u1C49\u1C4D-\u1C7D\u1C80-\u1C88\u1C90-\u1CBA\u1CBD-\u1CBF\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u1CFA\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2070\u2071\u2074-\u2079\u207F-\u2089\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2150-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2CFD\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312F\u3131-\u318E\u3192-\u3195\u31A0-\u31BF\u31F0-\u31FF\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\u3400-\u4DBF\u4E00-\u9FFC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7BF\uA7C2-\uA7CA\uA7F5-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA830-\uA835\uA840-\uA873\uA882-\uA8B3\uA8D0-\uA8D9\uA8F2-\uA8F7\uA8FB\uA8FD\uA8FE\uA900-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF-\uA9D9\uA9E0-\uA9E4\uA9E6-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA50-\uAA59\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB69\uAB70-\uABE2\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDE80-\uDE9C\uDEA0-\uDED0\uDEE1-\uDEFB\uDF00-\uDF23\uDF2D-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDCB0-\uDCD3\uDCD8-\uDCFB\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC58-\uDC76\uDC79-\uDC9E\uDCA7-\uDCAF\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDD1B\uDD20-\uDD39\uDD80-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE35\uDE40-\uDE48\uDE60-\uDE7E\uDE80-\uDE9F\uDEC0-\uDEC7\uDEC9-\uDEE4\uDEEB-\uDEEF\uDF00-\uDF35\uDF40-\uDF55\uDF58-\uDF72\uDF78-\uDF91\uDFA9-\uDFAF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDD23\uDD30-\uDD39\uDE60-\uDE7E\uDE80-\uDEA9\uDEB0\uDEB1\uDF00-\uDF27\uDF30-\uDF45\uDF51-\uDF54\uDFB0-\uDFCB\uDFE0-\uDFF6]|\uD804[\uDC03-\uDC37\uDC52-\uDC6F\uDC83-\uDCAF\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD03-\uDD26\uDD36-\uDD3F\uDD44\uDD47\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDD0-\uDDDA\uDDDC\uDDE1-\uDDF4\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDEF0-\uDEF9\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC00-\uDC34\uDC47-\uDC4A\uDC50-\uDC59\uDC5F-\uDC61\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE50-\uDE59\uDE80-\uDEAA\uDEB8\uDEC0-\uDEC9\uDF00-\uDF1A\uDF30-\uDF3B]|\uD806[\uDC00-\uDC2B\uDCA0-\uDCF2\uDCFF-\uDD06\uDD09\uDD0C-\uDD13\uDD15\uDD16\uDD18-\uDD2F\uDD3F\uDD41\uDD50-\uDD59\uDDA0-\uDDA7\uDDAA-\uDDD0\uDDE1\uDDE3\uDE00\uDE0B-\uDE32\uDE3A\uDE50\uDE5C-\uDE89\uDE9D\uDEC0-\uDEF8]|\uD807[\uDC00-\uDC08\uDC0A-\uDC2E\uDC40\uDC50-\uDC6C\uDC72-\uDC8F\uDD00-\uDD06\uDD08\uDD09\uDD0B-\uDD30\uDD46\uDD50-\uDD59\uDD60-\uDD65\uDD67\uDD68\uDD6A-\uDD89\uDD98\uDDA0-\uDDA9\uDEE0-\uDEF2\uDFB0\uDFC0-\uDFD4]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD81C-\uD820\uD822\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879\uD880-\uD883][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDE40-\uDE96\uDF00-\uDF4A\uDF50\uDF93-\uDF9F\uDFE0\uDFE1\uDFE3]|\uD821[\uDC00-\uDFF7]|\uD823[\uDC00-\uDCD5\uDD00-\uDD08]|\uD82C[\uDC00-\uDD1E\uDD50-\uDD52\uDD64-\uDD67\uDD70-\uDEFB]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD834[\uDEE0-\uDEF3\uDF60-\uDF78]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD838[\uDD00-\uDD2C\uDD37-\uDD3D\uDD40-\uDD49\uDD4E\uDEC0-\uDEEB\uDEF0-\uDEF9]|\uD83A[\uDC00-\uDCC4\uDCC7-\uDCCF\uDD00-\uDD43\uDD4B\uDD50-\uDD59]|\uD83B[\uDC71-\uDCAB\uDCAD-\uDCAF\uDCB1-\uDCB4\uDD01-\uDD2D\uDD2F-\uDD3D\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD83C[\uDD00-\uDD0C]|\uD83E[\uDFF0-\uDFF9]|\uD869[\uDC00-\uDEDD\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0]|\uD87E[\uDC00-\uDE1D]|\uD884[\uDC00-\uDF4A])/)) return;
- var nextChar = match[1] || match[2] || '';
- if (!nextChar || nextChar && (prevChar === '' || this.rules.inline.punctuation.exec(prevChar))) {
- var lLength = match[0].length - 1;
- var rDelim,
- rLength,
- delimTotal = lLength,
- midDelimTotal = 0;
- var endReg = match[0][0] === '*' ? this.rules.inline.emStrong.rDelimAst : this.rules.inline.emStrong.rDelimUnd;
- endReg.lastIndex = 0; // Clip maskedSrc to same section of string as src (move to lexer?)
+ var setData = shortOut(baseSetData);
+ /**
+ * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
+ *
+ * @private
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @returns {number|Object} Returns the timer id or timeout object.
+ */
- maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
+ var setTimeout = ctxSetTimeout || function (func, wait) {
+ return root.setTimeout(func, wait);
+ };
+ /**
+ * Sets the `toString` method of `func` to return `string`.
+ *
+ * @private
+ * @param {Function} func The function to modify.
+ * @param {Function} string The `toString` result.
+ * @returns {Function} Returns `func`.
+ */
- while ((match = endReg.exec(maskedSrc)) != null) {
- rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
- if (!rDelim) continue; // skip single * in __abc*abc__
- rLength = rDelim.length;
+ var setToString = shortOut(baseSetToString);
+ /**
+ * Sets the `toString` method of `wrapper` to mimic the source of `reference`
+ * with wrapper details in a comment at the top of the source body.
+ *
+ * @private
+ * @param {Function} wrapper The function to modify.
+ * @param {Function} reference The reference function.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @returns {Function} Returns `wrapper`.
+ */
- if (match[3] || match[4]) {
- // found another Left Delim
- delimTotal += rLength;
- continue;
- } else if (match[5] || match[6]) {
- // either Left or Right Delim
- if (lLength % 3 && !((lLength + rLength) % 3)) {
- midDelimTotal += rLength;
- continue; // CommonMark Emphasis Rules 9-10
+ function setWrapToString(wrapper, reference, bitmask) {
+ var source = reference + '';
+ return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
+ }
+ /**
+ * Creates a function that'll short out and invoke `identity` instead
+ * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
+ * milliseconds.
+ *
+ * @private
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new shortable function.
+ */
+
+
+ function shortOut(func) {
+ var count = 0,
+ lastCalled = 0;
+ return function () {
+ var stamp = nativeNow(),
+ remaining = HOT_SPAN - (stamp - lastCalled);
+ lastCalled = stamp;
+
+ if (remaining > 0) {
+ if (++count >= HOT_COUNT) {
+ return arguments[0];
}
+ } else {
+ count = 0;
}
- delimTotal -= rLength;
- if (delimTotal > 0) continue; // Haven't found enough closing delimiters
- // Remove extra characters. *a*** -> *a*
-
- rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal); // Create `em` if smallest delimiter has odd char count. *a***
+ return func.apply(undefined$1, arguments);
+ };
+ }
+ /**
+ * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
+ *
+ * @private
+ * @param {Array} array The array to shuffle.
+ * @param {number} [size=array.length] The size of `array`.
+ * @returns {Array} Returns `array`.
+ */
- if (Math.min(lLength, rLength) % 2) {
- return {
- type: 'em',
- raw: src.slice(0, lLength + match.index + rLength + 1),
- text: src.slice(1, lLength + match.index + rLength)
- };
- } // Create 'strong' if smallest delimiter has even char count. **a***
+ function shuffleSelf(array, size) {
+ var index = -1,
+ length = array.length,
+ lastIndex = length - 1;
+ size = size === undefined$1 ? length : size;
- return {
- type: 'strong',
- raw: src.slice(0, lLength + match.index + rLength + 1),
- text: src.slice(2, lLength + match.index + rLength - 1)
- };
+ while (++index < size) {
+ var rand = baseRandom(index, lastIndex),
+ value = array[rand];
+ array[rand] = array[index];
+ array[index] = value;
}
+
+ array.length = size;
+ return array;
}
- }
- }, {
- key: "codespan",
- value: function codespan(src) {
- var cap = this.rules.inline.code.exec(src);
+ /**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
- if (cap) {
- var text = cap[2].replace(/\n/g, ' ');
- var hasNonSpaceChars = /[^ ]/.test(text);
- var hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
- if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
- text = text.substring(1, text.length - 1);
+ var stringToPath = memoizeCapped(function (string) {
+ var result = [];
+
+ if (string.charCodeAt(0) === 46
+ /* . */
+ ) {
+ result.push('');
}
- text = _escape(text, true);
- return {
- type: 'codespan',
- raw: cap[0],
- text: text
- };
+ string.replace(rePropName, function (match, number, quote, subString) {
+ result.push(quote ? subString.replace(reEscapeChar, '$1') : number || match);
+ });
+ return result;
+ });
+ /**
+ * Converts `value` to a string key if it's not a string or symbol.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {string|symbol} Returns the key.
+ */
+
+ function toKey(value) {
+ if (typeof value == 'string' || isSymbol(value)) {
+ return value;
+ }
+
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
}
- }
- }, {
- key: "br",
- value: function br(src) {
- var cap = this.rules.inline.br.exec(src);
+ /**
+ * Converts `func` to its source code.
+ *
+ * @private
+ * @param {Function} func The function to convert.
+ * @returns {string} Returns the source code.
+ */
- if (cap) {
- return {
- type: 'br',
- raw: cap[0]
- };
+
+ function toSource(func) {
+ if (func != null) {
+ try {
+ return funcToString.call(func);
+ } catch (e) {}
+
+ try {
+ return func + '';
+ } catch (e) {}
+ }
+
+ return '';
}
- }
- }, {
- key: "del",
- value: function del(src) {
- var cap = this.rules.inline.del.exec(src);
+ /**
+ * Updates wrapper `details` based on `bitmask` flags.
+ *
+ * @private
+ * @returns {Array} details The details to modify.
+ * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+ * @returns {Array} Returns `details`.
+ */
- if (cap) {
- return {
- type: 'del',
- raw: cap[0],
- text: cap[2]
- };
+
+ function updateWrapDetails(details, bitmask) {
+ arrayEach(wrapFlags, function (pair) {
+ var value = '_.' + pair[0];
+
+ if (bitmask & pair[1] && !arrayIncludes(details, value)) {
+ details.push(value);
+ }
+ });
+ return details.sort();
}
- }
- }, {
- key: "autolink",
- value: function autolink(src, mangle) {
- var cap = this.rules.inline.autolink.exec(src);
+ /**
+ * Creates a clone of `wrapper`.
+ *
+ * @private
+ * @param {Object} wrapper The wrapper to clone.
+ * @returns {Object} Returns the cloned wrapper.
+ */
- if (cap) {
- var text, href;
- if (cap[2] === '@') {
- text = _escape(this.options.mangle ? mangle(cap[1]) : cap[1]);
- href = 'mailto:' + text;
- } else {
- text = _escape(cap[1]);
- href = text;
+ function wrapperClone(wrapper) {
+ if (wrapper instanceof LazyWrapper) {
+ return wrapper.clone();
}
- return {
- type: 'link',
- raw: cap[0],
- text: text,
- href: href,
- tokens: [{
- type: 'text',
- raw: text,
- text: text
- }]
- };
+ var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+ result.__actions__ = copyArray(wrapper.__actions__);
+ result.__index__ = wrapper.__index__;
+ result.__values__ = wrapper.__values__;
+ return result;
}
- }
- }, {
- key: "url",
- value: function url(src, mangle) {
- var cap;
+ /*------------------------------------------------------------------------*/
- if (cap = this.rules.inline.url.exec(src)) {
- var text, href;
+ /**
+ * Creates an array of elements split into groups the length of `size`.
+ * If `array` can't be split evenly, the final chunk will be the remaining
+ * elements.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to process.
+ * @param {number} [size=1] The length of each chunk
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the new array of chunks.
+ * @example
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 2);
+ * // => [['a', 'b'], ['c', 'd']]
+ *
+ * _.chunk(['a', 'b', 'c', 'd'], 3);
+ * // => [['a', 'b', 'c'], ['d']]
+ */
- if (cap[2] === '@') {
- text = _escape(this.options.mangle ? mangle(cap[0]) : cap[0]);
- href = 'mailto:' + text;
+
+ function chunk(array, size, guard) {
+ if (guard ? isIterateeCall(array, size, guard) : size === undefined$1) {
+ size = 1;
} else {
- // do extended autolink path validation
- var prevCapZero;
+ size = nativeMax(toInteger(size), 0);
+ }
- do {
- prevCapZero = cap[0];
- cap[0] = this.rules.inline._backpedal.exec(cap[0])[0];
- } while (prevCapZero !== cap[0]);
+ var length = array == null ? 0 : array.length;
- text = _escape(cap[0]);
+ if (!length || size < 1) {
+ return [];
+ }
- if (cap[1] === 'www.') {
- href = 'http://' + text;
- } else {
- href = text;
+ var index = 0,
+ resIndex = 0,
+ result = Array(nativeCeil(length / size));
+
+ while (index < length) {
+ result[resIndex++] = baseSlice(array, index, index += size);
+ }
+
+ return result;
+ }
+ /**
+ * Creates an array with all falsey values removed. The values `false`, `null`,
+ * `0`, `""`, `undefined`, and `NaN` are falsey.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to compact.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.compact([0, 1, false, 2, '', 3]);
+ * // => [1, 2, 3]
+ */
+
+
+ function compact(array) {
+ var index = -1,
+ length = array == null ? 0 : array.length,
+ resIndex = 0,
+ result = [];
+
+ while (++index < length) {
+ var value = array[index];
+
+ if (value) {
+ result[resIndex++] = value;
}
}
- return {
- type: 'link',
- raw: cap[0],
- text: text,
- href: href,
- tokens: [{
- type: 'text',
- raw: text,
- text: text
- }]
- };
+ return result;
}
- }
- }, {
- key: "inlineText",
- value: function inlineText(src, inRawBlock, smartypants) {
- var cap = this.rules.inline.text.exec(src);
+ /**
+ * Creates a new array concatenating `array` with any additional arrays
+ * and/or values.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to concatenate.
+ * @param {...*} [values] The values to concatenate.
+ * @returns {Array} Returns the new concatenated array.
+ * @example
+ *
+ * var array = [1];
+ * var other = _.concat(array, 2, [3], [[4]]);
+ *
+ * console.log(other);
+ * // => [1, 2, 3, [4]]
+ *
+ * console.log(array);
+ * // => [1]
+ */
- if (cap) {
- var text;
- if (inRawBlock) {
- text = this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : _escape(cap[0]) : cap[0];
- } else {
- text = _escape(this.options.smartypants ? smartypants(cap[0]) : cap[0]);
+ function concat() {
+ var length = arguments.length;
+
+ if (!length) {
+ return [];
}
- return {
- type: 'text',
- raw: cap[0],
- text: text
- };
+ var args = Array(length - 1),
+ array = arguments[0],
+ index = length;
+
+ while (index--) {
+ args[index - 1] = arguments[index];
+ }
+
+ return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
}
- }
- }]);
+ /**
+ * Creates an array of `array` values not included in the other given arrays
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. The order and references of result values are
+ * determined by the first array.
+ *
+ * **Note:** Unlike `_.pullAll`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.without, _.xor
+ * @example
+ *
+ * _.difference([2, 1], [2, 3]);
+ * // => [1]
+ */
- return Tokenizer;
- }();
- var noopTest = helpers.noopTest,
- edit = helpers.edit,
- merge$1 = helpers.merge;
- /**
- * Block-Level Grammar
- */
+ var difference = baseRest(function (array, values) {
+ return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true)) : [];
+ });
+ /**
+ * This method is like `_.difference` except that it accepts `iteratee` which
+ * is invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. The order and references of result values are
+ * determined by the first array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
- var block$1 = {
- newline: /^(?: *(?:\n|$))+/,
- code: /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,
- fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
- hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
- heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
- blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
- list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
- html: '^ {0,3}(?:' // optional indentation
- + '<(script|pre|style)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)' // (1)
- + '|comment[^\\n]*(\\n+|$)' // (2)
- + '|<\\?[\\s\\S]*?(?:\\?>\\n*|$)' // (3)
- + '|\\n*|$)' // (4)
- + '|\\n*|$)' // (5)
- + '|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (6)
- + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) open tag
- + '|(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)' // (7) closing tag
- + ')',
- def: /^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,
- nptable: noopTest,
- table: noopTest,
- lheading: /^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,
- // regex template, placeholders will be replaced according to different paragraph
- // interruption rules of commonmark and the original markdown spec:
- _paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html| +\n)[^\n]+)*)/,
- text: /^[^\n]+/
- };
- block$1._label = /(?!\s*\])(?:\\[\[\]]|[^\[\]])+/;
- block$1._title = /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/;
- block$1.def = edit(block$1.def).replace('label', block$1._label).replace('title', block$1._title).getRegex();
- block$1.bullet = /(?:[*+-]|\d{1,9}[.)])/;
- block$1.item = /^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/;
- block$1.item = edit(block$1.item, 'gm').replace(/bull/g, block$1.bullet).getRegex();
- block$1.listItemStart = edit(/^( *)(bull) */).replace('bull', block$1.bullet).getRegex();
- block$1.list = edit(block$1.list).replace(/bull/g, block$1.bullet).replace('hr', '\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))').replace('def', '\\n+(?=' + block$1.def.source + ')').getRegex();
- block$1._tag = 'address|article|aside|base|basefont|blockquote|body|caption' + '|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption' + '|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe' + '|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option' + '|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr' + '|track|ul';
- block$1._comment = /|$)/;
- block$1.html = edit(block$1.html, 'i').replace('comment', block$1._comment).replace('tag', block$1._tag).replace('attribute', / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
- block$1.paragraph = edit(block$1._paragraph).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('|lheading', '') // setex headings don't interrupt commonmark paragraphs
- .replace('blockquote', ' {0,3}>').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
- .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // pars can be interrupted by type (6) html blocks
- .getRegex();
- block$1.blockquote = edit(block$1.blockquote).replace('paragraph', block$1.paragraph).getRegex();
- /**
- * Normal Block Grammar
- */
+ var differenceBy = baseRest(function (array, values) {
+ var iteratee = last(values);
- block$1.normal = merge$1({}, block$1);
- /**
- * GFM Block Grammar
- */
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined$1;
+ }
- block$1.gfm = merge$1({}, block$1.normal, {
- nptable: '^ *([^|\\n ].*\\|.*)\\n' // Header
- + ' {0,3}([-:]+ *\\|[-| :]*)' // Align
- + '(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)',
- // Cells
- table: '^ *\\|(.+)\\n' // Header
- + ' {0,3}\\|?( *[-:]+[-| :]*)' // Align
- + '(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)' // Cells
+ return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2)) : [];
+ });
+ /**
+ * This method is like `_.difference` except that it accepts `comparator`
+ * which is invoked to compare elements of `array` to `values`. The order and
+ * references of result values are determined by the first array. The comparator
+ * is invoked with two arguments: (arrVal, othVal).
+ *
+ * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...Array} [values] The values to exclude.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ *
+ * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+ * // => [{ 'x': 2, 'y': 1 }]
+ */
- });
- block$1.gfm.nptable = edit(block$1.gfm.nptable).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
- .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks
- .getRegex();
- block$1.gfm.table = edit(block$1.gfm.table).replace('hr', block$1.hr).replace('heading', ' {0,3}#{1,6} ').replace('blockquote', ' {0,3}>').replace('code', ' {4}[^\\n]').replace('fences', ' {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n').replace('list', ' {0,3}(?:[*+-]|1[.)]) ') // only lists starting from 1 can interrupt
- .replace('html', '?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)').replace('tag', block$1._tag) // tables can be interrupted by type (6) html blocks
- .getRegex();
- /**
- * Pedantic grammar (original John Gruber's loose markdown specification)
- */
+ var differenceWith = baseRest(function (array, values) {
+ var comparator = last(values);
- block$1.pedantic = merge$1({}, block$1.normal, {
- html: edit('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)' // closed tag
- + '|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))').replace('comment', block$1._comment).replace(/tag/g, '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b').getRegex(),
- def: /^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
- heading: /^(#{1,6})(.*)(?:\n+|$)/,
- fences: noopTest,
- // fences not supported
- paragraph: edit(block$1.normal._paragraph).replace('hr', block$1.hr).replace('heading', ' *#{1,6} *[^\n]').replace('lheading', block$1.lheading).replace('blockquote', ' {0,3}>').replace('|fences', '').replace('|list', '').replace('|html', '').getRegex()
- });
- /**
- * Inline-Level Grammar
- */
+ if (isArrayLikeObject(comparator)) {
+ comparator = undefined$1;
+ }
- var inline$1 = {
- escape: /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,
- autolink: /^<(scheme:[^\s\x00-\x1f<>]*|email)>/,
- url: noopTest,
- tag: '^comment' + '|^[a-zA-Z][\\w:-]*\\s*>' // self-closing tag
- + '|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>' // open tag
- + '|^<\\?[\\s\\S]*?\\?>' // processing instruction, e.g.
- + '|^' // declaration, e.g.
- + '|^',
- // CDATA section
- link: /^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,
- reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,
- nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,
- reflinkSearch: 'reflink|nolink(?!\\()',
- emStrong: {
- lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
- // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
- // () Skip other delimiter (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
- rDelimAst: /\_\_[^_*]*?\*[^_*]*?\_\_|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
- rDelimUnd: /\*\*[^_*]*?\_[^_*]*?\*\*|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
+ return isArrayLikeObject(array) ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined$1, comparator) : [];
+ });
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.drop([1, 2, 3]);
+ * // => [2, 3]
+ *
+ * _.drop([1, 2, 3], 2);
+ * // => [3]
+ *
+ * _.drop([1, 2, 3], 5);
+ * // => []
+ *
+ * _.drop([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
- },
- code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
- br: /^( {2,}|\\)\n(?!\s*$)/,
- del: noopTest,
- text: /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~';
- inline$1.punctuation = edit(inline$1.punctuation).replace(/punctuation/g, inline$1._punctuation).getRegex(); // sequences em should skip over [title](link), `code`,
+ if (!length) {
+ return [];
+ }
- inline$1.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g;
- inline$1.escapedEmSt = /\\\*|\\_/g;
- inline$1._comment = edit(block$1._comment).replace('(?:-->|$)', '-->').getRegex();
- inline$1.emStrong.lDelim = edit(inline$1.emStrong.lDelim).replace(/punct/g, inline$1._punctuation).getRegex();
- inline$1.emStrong.rDelimAst = edit(inline$1.emStrong.rDelimAst, 'g').replace(/punct/g, inline$1._punctuation).getRegex();
- inline$1.emStrong.rDelimUnd = edit(inline$1.emStrong.rDelimUnd, 'g').replace(/punct/g, inline$1._punctuation).getRegex();
- inline$1._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g;
- inline$1._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/;
- inline$1._email = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/;
- inline$1.autolink = edit(inline$1.autolink).replace('scheme', inline$1._scheme).replace('email', inline$1._email).getRegex();
- inline$1._attribute = /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/;
- inline$1.tag = edit(inline$1.tag).replace('comment', inline$1._comment).replace('attribute', inline$1._attribute).getRegex();
- inline$1._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
- inline$1._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/;
- inline$1._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/;
- inline$1.link = edit(inline$1.link).replace('label', inline$1._label).replace('href', inline$1._href).replace('title', inline$1._title).getRegex();
- inline$1.reflink = edit(inline$1.reflink).replace('label', inline$1._label).getRegex();
- inline$1.reflinkSearch = edit(inline$1.reflinkSearch, 'g').replace('reflink', inline$1.reflink).replace('nolink', inline$1.nolink).getRegex();
- /**
- * Normal Inline Grammar
- */
+ n = guard || n === undefined$1 ? 1 : toInteger(n);
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+ /**
+ * Creates a slice of `array` with `n` elements dropped from the end.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to drop.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.dropRight([1, 2, 3]);
+ * // => [1, 2]
+ *
+ * _.dropRight([1, 2, 3], 2);
+ * // => [1]
+ *
+ * _.dropRight([1, 2, 3], 5);
+ * // => []
+ *
+ * _.dropRight([1, 2, 3], 0);
+ * // => [1, 2, 3]
+ */
- inline$1.normal = merge$1({}, inline$1);
- /**
- * Pedantic Inline Grammar
- */
- inline$1.pedantic = merge$1({}, inline$1.normal, {
- strong: {
- start: /^__|\*\*/,
- middle: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
- endAst: /\*\*(?!\*)/g,
- endUnd: /__(?!_)/g
- },
- em: {
- start: /^_|\*/,
- middle: /^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,
- endAst: /\*(?!\*)/g,
- endUnd: /_(?!_)/g
- },
- link: edit(/^!?\[(label)\]\((.*?)\)/).replace('label', inline$1._label).getRegex(),
- reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace('label', inline$1._label).getRegex()
- });
- /**
- * GFM Inline Grammar
- */
+ function dropRight(array, n, guard) {
+ var length = array == null ? 0 : array.length;
- inline$1.gfm = merge$1({}, inline$1.normal, {
- escape: edit(inline$1.escape).replace('])', '~|])').getRegex(),
- _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,
- url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,
- _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,
- del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
- text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\ objects for ['barney']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropRightWhile(users, ['active', false]);
+ * // => objects for ['barney']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropRightWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
- var defaults$3 = defaults$5.defaults;
- var block = rules.block,
- inline = rules.inline;
- var repeatString = helpers.repeatString;
- /**
- * smartypants text replacement
- */
- function smartypants(text) {
- return text // em-dashes
- .replace(/---/g, "\u2014") // en-dashes
- .replace(/--/g, "\u2013") // opening singles
- .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018") // closing singles & apostrophes
- .replace(/'/g, "\u2019") // opening doubles
- .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201C") // closing doubles
- .replace(/"/g, "\u201D") // ellipses
- .replace(/\.{3}/g, "\u2026");
- }
- /**
- * mangle email addresses
- */
+ function dropRightWhile(array, predicate) {
+ return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true, true) : [];
+ }
+ /**
+ * Creates a slice of `array` excluding elements dropped from the beginning.
+ * Elements are dropped until `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.dropWhile(users, function(o) { return !o.active; });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.dropWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.dropWhile(users, ['active', false]);
+ * // => objects for ['pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.dropWhile(users, 'active');
+ * // => objects for ['barney', 'fred', 'pebbles']
+ */
- function mangle(text) {
- var out = '',
- i,
- ch;
- var l = text.length;
+ function dropWhile(array, predicate) {
+ return array && array.length ? baseWhile(array, getIteratee(predicate, 3), true) : [];
+ }
+ /**
+ * Fills elements of `array` with `value` from `start` up to, but not
+ * including, `end`.
+ *
+ * **Note:** This method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Array
+ * @param {Array} array The array to fill.
+ * @param {*} value The value to fill `array` with.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.fill(array, 'a');
+ * console.log(array);
+ * // => ['a', 'a', 'a']
+ *
+ * _.fill(Array(3), 2);
+ * // => [2, 2, 2]
+ *
+ * _.fill([4, 6, 8, 10], '*', 1, 3);
+ * // => [4, '*', '*', 10]
+ */
- for (i = 0; i < l; i++) {
- ch = text.charCodeAt(i);
- if (Math.random() > 0.5) {
- ch = 'x' + ch.toString(16);
- }
+ function fill(array, value, start, end) {
+ var length = array == null ? 0 : array.length;
- out += '' + ch + ';';
- }
+ if (!length) {
+ return [];
+ }
- return out;
- }
- /**
- * Block Lexer
- */
+ if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+ start = 0;
+ end = length;
+ }
+ return baseFill(array, value, start, end);
+ }
+ /**
+ * This method is like `_.find` except that it returns the index of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.findIndex(users, function(o) { return o.user == 'barney'; });
+ * // => 0
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findIndex(users, { 'user': 'fred', 'active': false });
+ * // => 1
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findIndex(users, ['active', false]);
+ * // => 0
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findIndex(users, 'active');
+ * // => 2
+ */
- var Lexer_1 = /*#__PURE__*/function () {
- function Lexer(options) {
- _classCallCheck$1(this, Lexer);
- this.tokens = [];
- this.tokens.links = Object.create(null);
- this.options = options || defaults$3;
- this.options.tokenizer = this.options.tokenizer || new Tokenizer_1();
- this.tokenizer = this.options.tokenizer;
- this.tokenizer.options = this.options;
- var rules = {
- block: block.normal,
- inline: inline.normal
- };
+ function findIndex(array, predicate, fromIndex) {
+ var length = array == null ? 0 : array.length;
- if (this.options.pedantic) {
- rules.block = block.pedantic;
- rules.inline = inline.pedantic;
- } else if (this.options.gfm) {
- rules.block = block.gfm;
+ if (!length) {
+ return -1;
+ }
- if (this.options.breaks) {
- rules.inline = inline.breaks;
- } else {
- rules.inline = inline.gfm;
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
+
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
+
+ return baseFindIndex(array, getIteratee(predicate, 3), index);
}
- }
+ /**
+ * This method is like `_.findIndex` except that it iterates over elements
+ * of `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the found element, else `-1`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+ * // => 2
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+ * // => 0
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastIndex(users, ['active', false]);
+ * // => 2
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastIndex(users, 'active');
+ * // => 0
+ */
- this.tokenizer.rules = rules;
- }
- /**
- * Expose Rules
- */
+ function findLastIndex(array, predicate, fromIndex) {
+ var length = array == null ? 0 : array.length;
- _createClass$1(Lexer, [{
- key: "lex",
- value:
- /**
- * Preprocessing
- */
- function lex(src) {
- src = src.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' ');
- this.blockTokens(src, this.tokens, true);
- this.inline(this.tokens);
- return this.tokens;
- }
- /**
- * Lexing
- */
+ if (!length) {
+ return -1;
+ }
- }, {
- key: "blockTokens",
- value: function blockTokens(src) {
- var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
- var top = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
+ var index = length - 1;
- if (this.options.pedantic) {
- src = src.replace(/^ +$/gm, '');
- }
+ if (fromIndex !== undefined$1) {
+ index = toInteger(fromIndex);
+ index = fromIndex < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
+ }
- var token, i, l, lastToken;
+ return baseFindIndex(array, getIteratee(predicate, 3), index, true);
+ }
+ /**
+ * Flattens `array` a single level deep.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flatten([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, [3, [4]], 5]
+ */
- while (src) {
- // newline
- if (token = this.tokenizer.space(src)) {
- src = src.substring(token.raw.length);
- if (token.type) {
- tokens.push(token);
- }
+ function flatten(array) {
+ var length = array == null ? 0 : array.length;
+ return length ? baseFlatten(array, 1) : [];
+ }
+ /**
+ * Recursively flattens `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * _.flattenDeep([1, [2, [3, [4]], 5]]);
+ * // => [1, 2, 3, 4, 5]
+ */
- continue;
- } // code
+ function flattenDeep(array) {
+ var length = array == null ? 0 : array.length;
+ return length ? baseFlatten(array, INFINITY) : [];
+ }
+ /**
+ * Recursively flatten `array` up to `depth` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.4.0
+ * @category Array
+ * @param {Array} array The array to flatten.
+ * @param {number} [depth=1] The maximum recursion depth.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * var array = [1, [2, [3, [4]], 5]];
+ *
+ * _.flattenDepth(array, 1);
+ * // => [1, 2, [3, [4]], 5]
+ *
+ * _.flattenDepth(array, 2);
+ * // => [1, 2, 3, [4], 5]
+ */
- if (token = this.tokenizer.code(src)) {
- src = src.substring(token.raw.length);
- lastToken = tokens[tokens.length - 1]; // An indented code block cannot interrupt a paragraph.
- if (lastToken && lastToken.type === 'paragraph') {
- lastToken.raw += '\n' + token.raw;
- lastToken.text += '\n' + token.text;
- } else {
- tokens.push(token);
- }
+ function flattenDepth(array, depth) {
+ var length = array == null ? 0 : array.length;
- continue;
- } // fences
+ if (!length) {
+ return [];
+ }
+ depth = depth === undefined$1 ? 1 : toInteger(depth);
+ return baseFlatten(array, depth);
+ }
+ /**
+ * The inverse of `_.toPairs`; this method returns an object composed
+ * from key-value `pairs`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} pairs The key-value pairs.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.fromPairs([['a', 1], ['b', 2]]);
+ * // => { 'a': 1, 'b': 2 }
+ */
- if (token = this.tokenizer.fences(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // heading
+ function fromPairs(pairs) {
+ var index = -1,
+ length = pairs == null ? 0 : pairs.length,
+ result = {};
- if (token = this.tokenizer.heading(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // table no leading pipe (gfm)
+ while (++index < length) {
+ var pair = pairs[index];
+ result[pair[0]] = pair[1];
+ }
+ return result;
+ }
+ /**
+ * Gets the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @alias first
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the first element of `array`.
+ * @example
+ *
+ * _.head([1, 2, 3]);
+ * // => 1
+ *
+ * _.head([]);
+ * // => undefined
+ */
- if (token = this.tokenizer.nptable(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // hr
+ function head(array) {
+ return array && array.length ? array[0] : undefined$1;
+ }
+ /**
+ * Gets the index at which the first occurrence of `value` is found in `array`
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. If `fromIndex` is negative, it's used as the
+ * offset from the end of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.indexOf([1, 2, 1, 2], 2);
+ * // => 1
+ *
+ * // Search from the `fromIndex`.
+ * _.indexOf([1, 2, 1, 2], 2, 2);
+ * // => 3
+ */
- if (token = this.tokenizer.hr(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // blockquote
+ function indexOf(array, value, fromIndex) {
+ var length = array == null ? 0 : array.length;
- if (token = this.tokenizer.blockquote(src)) {
- src = src.substring(token.raw.length);
- token.tokens = this.blockTokens(token.text, [], top);
- tokens.push(token);
- continue;
- } // list
+ if (!length) {
+ return -1;
+ }
+ var index = fromIndex == null ? 0 : toInteger(fromIndex);
- if (token = this.tokenizer.list(src)) {
- src = src.substring(token.raw.length);
- l = token.items.length;
+ if (index < 0) {
+ index = nativeMax(length + index, 0);
+ }
- for (i = 0; i < l; i++) {
- token.items[i].tokens = this.blockTokens(token.items[i].text, [], false);
- }
+ return baseIndexOf(array, value, index);
+ }
+ /**
+ * Gets all but the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.initial([1, 2, 3]);
+ * // => [1, 2]
+ */
- tokens.push(token);
- continue;
- } // html
+ function initial(array) {
+ var length = array == null ? 0 : array.length;
+ return length ? baseSlice(array, 0, -1) : [];
+ }
+ /**
+ * Creates an array of unique values that are included in all given arrays
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons. The order and references of result values are
+ * determined by the first array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersection([2, 1], [2, 3]);
+ * // => [2]
+ */
- if (token = this.tokenizer.html(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // def
+ var intersection = baseRest(function (arrays) {
+ var mapped = arrayMap(arrays, castArrayLikeObject);
+ return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped) : [];
+ });
+ /**
+ * This method is like `_.intersection` except that it accepts `iteratee`
+ * which is invoked for each element of each `arrays` to generate the criterion
+ * by which they're compared. The order and references of result values are
+ * determined by the first array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [2.1]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }]
+ */
- if (top && (token = this.tokenizer.def(src))) {
- src = src.substring(token.raw.length);
+ var intersectionBy = baseRest(function (arrays) {
+ var iteratee = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
- if (!this.tokens.links[token.tag]) {
- this.tokens.links[token.tag] = {
- href: token.href,
- title: token.title
- };
- }
+ if (iteratee === last(mapped)) {
+ iteratee = undefined$1;
+ } else {
+ mapped.pop();
+ }
- continue;
- } // table (gfm)
+ return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, getIteratee(iteratee, 2)) : [];
+ });
+ /**
+ * This method is like `_.intersection` except that it accepts `comparator`
+ * which is invoked to compare elements of `arrays`. The order and references
+ * of result values are determined by the first array. The comparator is
+ * invoked with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of intersecting values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.intersectionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }]
+ */
+ var intersectionWith = baseRest(function (arrays) {
+ var comparator = last(arrays),
+ mapped = arrayMap(arrays, castArrayLikeObject);
+ comparator = typeof comparator == 'function' ? comparator : undefined$1;
- if (token = this.tokenizer.table(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // lheading
+ if (comparator) {
+ mapped.pop();
+ }
+ return mapped.length && mapped[0] === arrays[0] ? baseIntersection(mapped, undefined$1, comparator) : [];
+ });
+ /**
+ * Converts all elements in `array` into a string separated by `separator`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to convert.
+ * @param {string} [separator=','] The element separator.
+ * @returns {string} Returns the joined string.
+ * @example
+ *
+ * _.join(['a', 'b', 'c'], '~');
+ * // => 'a~b~c'
+ */
- if (token = this.tokenizer.lheading(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // top-level paragraph
+ function join(array, separator) {
+ return array == null ? '' : nativeJoin.call(array, separator);
+ }
+ /**
+ * Gets the last element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {*} Returns the last element of `array`.
+ * @example
+ *
+ * _.last([1, 2, 3]);
+ * // => 3
+ */
- if (top && (token = this.tokenizer.paragraph(src))) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // text
-
+ function last(array) {
+ var length = array == null ? 0 : array.length;
+ return length ? array[length - 1] : undefined$1;
+ }
+ /**
+ * This method is like `_.indexOf` except that it iterates over elements of
+ * `array` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=array.length-1] The index to search from.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.lastIndexOf([1, 2, 1, 2], 2);
+ * // => 3
+ *
+ * // Search from the `fromIndex`.
+ * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+ * // => 1
+ */
- if (token = this.tokenizer.text(src)) {
- src = src.substring(token.raw.length);
- lastToken = tokens[tokens.length - 1];
- if (lastToken && lastToken.type === 'text') {
- lastToken.raw += '\n' + token.raw;
- lastToken.text += '\n' + token.text;
- } else {
- tokens.push(token);
- }
+ function lastIndexOf(array, value, fromIndex) {
+ var length = array == null ? 0 : array.length;
- continue;
+ if (!length) {
+ return -1;
}
- if (src) {
- var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+ var index = length;
- if (this.options.silent) {
- console.error(errMsg);
- break;
- } else {
- throw new Error(errMsg);
- }
+ if (fromIndex !== undefined$1) {
+ index = toInteger(fromIndex);
+ index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
}
+
+ return value === value ? strictLastIndexOf(array, value, index) : baseFindIndex(array, baseIsNaN, index, true);
}
+ /**
+ * Gets the element at index `n` of `array`. If `n` is negative, the nth
+ * element from the end is returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.11.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=0] The index of the element to return.
+ * @returns {*} Returns the nth element of `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'd'];
+ *
+ * _.nth(array, 1);
+ * // => 'b'
+ *
+ * _.nth(array, -2);
+ * // => 'c';
+ */
- return tokens;
- }
- }, {
- key: "inline",
- value: function inline(tokens) {
- var i, j, k, l2, row, token;
- var l = tokens.length;
- for (i = 0; i < l; i++) {
- token = tokens[i];
+ function nth(array, n) {
+ return array && array.length ? baseNth(array, toInteger(n)) : undefined$1;
+ }
+ /**
+ * Removes all given values from `array` using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+ * to remove elements from an array by predicate.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...*} [values] The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+ *
+ * _.pull(array, 'a', 'c');
+ * console.log(array);
+ * // => ['b', 'b']
+ */
- switch (token.type) {
- case 'paragraph':
- case 'text':
- case 'heading':
- {
- token.tokens = [];
- this.inlineTokens(token.text, token.tokens);
- break;
- }
- case 'table':
- {
- token.tokens = {
- header: [],
- cells: []
- }; // header
+ var pull = baseRest(pullAll);
+ /**
+ * This method is like `_.pull` except that it accepts an array of values to remove.
+ *
+ * **Note:** Unlike `_.difference`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+ *
+ * _.pullAll(array, ['a', 'c']);
+ * console.log(array);
+ * // => ['b', 'b']
+ */
- l2 = token.header.length;
+ function pullAll(array, values) {
+ return array && array.length && values && values.length ? basePullAll(array, values) : array;
+ }
+ /**
+ * This method is like `_.pullAll` except that it accepts `iteratee` which is
+ * invoked for each element of `array` and `values` to generate the criterion
+ * by which they're compared. The iteratee is invoked with one argument: (value).
+ *
+ * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+ *
+ * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+ * console.log(array);
+ * // => [{ 'x': 2 }]
+ */
- for (j = 0; j < l2; j++) {
- token.tokens.header[j] = [];
- this.inlineTokens(token.header[j], token.tokens.header[j]);
- } // cells
+ function pullAllBy(array, values, iteratee) {
+ return array && array.length && values && values.length ? basePullAll(array, values, getIteratee(iteratee, 2)) : array;
+ }
+ /**
+ * This method is like `_.pullAll` except that it accepts `comparator` which
+ * is invoked to compare elements of `array` to `values`. The comparator is
+ * invoked with two arguments: (arrVal, othVal).
+ *
+ * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Array} values The values to remove.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+ *
+ * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+ * console.log(array);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+ */
- l2 = token.cells.length;
- for (j = 0; j < l2; j++) {
- row = token.cells[j];
- token.tokens.cells[j] = [];
+ function pullAllWith(array, values, comparator) {
+ return array && array.length && values && values.length ? basePullAll(array, values, undefined$1, comparator) : array;
+ }
+ /**
+ * Removes elements from `array` corresponding to `indexes` and returns an
+ * array of removed elements.
+ *
+ * **Note:** Unlike `_.at`, this method mutates `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = ['a', 'b', 'c', 'd'];
+ * var pulled = _.pullAt(array, [1, 3]);
+ *
+ * console.log(array);
+ * // => ['a', 'c']
+ *
+ * console.log(pulled);
+ * // => ['b', 'd']
+ */
- for (k = 0; k < row.length; k++) {
- token.tokens.cells[j][k] = [];
- this.inlineTokens(row[k], token.tokens.cells[j][k]);
- }
- }
- break;
- }
+ var pullAt = flatRest(function (array, indexes) {
+ var length = array == null ? 0 : array.length,
+ result = baseAt(array, indexes);
+ basePullAt(array, arrayMap(indexes, function (index) {
+ return isIndex(index, length) ? +index : index;
+ }).sort(compareAscending));
+ return result;
+ });
+ /**
+ * Removes all elements from `array` that `predicate` returns truthy for
+ * and returns an array of the removed elements. The predicate is invoked
+ * with three arguments: (value, index, array).
+ *
+ * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+ * to pull elements from an array by value.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new array of removed elements.
+ * @example
+ *
+ * var array = [1, 2, 3, 4];
+ * var evens = _.remove(array, function(n) {
+ * return n % 2 == 0;
+ * });
+ *
+ * console.log(array);
+ * // => [1, 3]
+ *
+ * console.log(evens);
+ * // => [2, 4]
+ */
- case 'blockquote':
- {
- this.inline(token.tokens);
- break;
- }
+ function remove(array, predicate) {
+ var result = [];
- case 'list':
- {
- l2 = token.items.length;
+ if (!(array && array.length)) {
+ return result;
+ }
- for (j = 0; j < l2; j++) {
- this.inline(token.items[j].tokens);
- }
+ var index = -1,
+ indexes = [],
+ length = array.length;
+ predicate = getIteratee(predicate, 3);
- break;
- }
+ while (++index < length) {
+ var value = array[index];
+
+ if (predicate(value, index, array)) {
+ result.push(value);
+ indexes.push(index);
+ }
}
+
+ basePullAt(array, indexes);
+ return result;
}
+ /**
+ * Reverses `array` so that the first element becomes the last, the second
+ * element becomes the second to last, and so on.
+ *
+ * **Note:** This method mutates `array` and is based on
+ * [`Array#reverse`](https://mdn.io/Array/reverse).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to modify.
+ * @returns {Array} Returns `array`.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _.reverse(array);
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
- return tokens;
- }
- /**
- * Lexing/Compiling
- */
- }, {
- key: "inlineTokens",
- value: function inlineTokens(src) {
- var tokens = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
- var inLink = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
- var inRawBlock = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
- var token, lastToken; // String with links masked to avoid interference with em and strong
+ function reverse(array) {
+ return array == null ? array : nativeReverse.call(array);
+ }
+ /**
+ * Creates a slice of `array` from `start` up to, but not including, `end`.
+ *
+ * **Note:** This method is used instead of
+ * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+ * returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to slice.
+ * @param {number} [start=0] The start position.
+ * @param {number} [end=array.length] The end position.
+ * @returns {Array} Returns the slice of `array`.
+ */
- var maskedSrc = src;
- var match;
- var keepPrevChar, prevChar; // Mask out reflinks
- if (this.tokens.links) {
- var links = Object.keys(this.tokens.links);
+ function slice(array, start, end) {
+ var length = array == null ? 0 : array.length;
- if (links.length > 0) {
- while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
- if (links.includes(match[0].slice(match[0].lastIndexOf('[') + 1, -1))) {
- maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
- }
- }
+ if (!length) {
+ return [];
}
- } // Mask out other blocks
+ if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+ start = 0;
+ end = length;
+ } else {
+ start = start == null ? 0 : toInteger(start);
+ end = end === undefined$1 ? length : toInteger(end);
+ }
- while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
- maskedSrc = maskedSrc.slice(0, match.index) + '[' + repeatString('a', match[0].length - 2) + ']' + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
- } // Mask out escaped em & strong delimiters
+ return baseSlice(array, start, end);
+ }
+ /**
+ * Uses a binary search to determine the lowest index at which `value`
+ * should be inserted into `array` in order to maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedIndex([30, 50], 40);
+ * // => 1
+ */
- while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) {
- maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
+ function sortedIndex(array, value) {
+ return baseSortedIndex(array, value);
}
+ /**
+ * This method is like `_.sortedIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * var objects = [{ 'x': 4 }, { 'x': 5 }];
+ *
+ * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+ * // => 0
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
+ * // => 0
+ */
- while (src) {
- if (!keepPrevChar) {
- prevChar = '';
- }
- keepPrevChar = false; // escape
+ function sortedIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
+ }
+ /**
+ * This method is like `_.indexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
+ * // => 1
+ */
- if (token = this.tokenizer.escape(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // tag
+ function sortedIndexOf(array, value) {
+ var length = array == null ? 0 : array.length;
- if (token = this.tokenizer.tag(src, inLink, inRawBlock)) {
- src = src.substring(token.raw.length);
- inLink = token.inLink;
- inRawBlock = token.inRawBlock;
- var _lastToken = tokens[tokens.length - 1];
+ if (length) {
+ var index = baseSortedIndex(array, value);
- if (_lastToken && token.type === 'text' && _lastToken.type === 'text') {
- _lastToken.raw += token.raw;
- _lastToken.text += token.text;
- } else {
- tokens.push(token);
+ if (index < length && eq(array[index], value)) {
+ return index;
}
+ }
- continue;
- } // link
+ return -1;
+ }
+ /**
+ * This method is like `_.sortedIndex` except that it returns the highest
+ * index at which `value` should be inserted into `array` in order to
+ * maintain its sort order.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
+ * // => 4
+ */
- if (token = this.tokenizer.link(src)) {
- src = src.substring(token.raw.length);
+ function sortedLastIndex(array, value) {
+ return baseSortedIndex(array, value, true);
+ }
+ /**
+ * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+ * which is invoked for `value` and each element of `array` to compute their
+ * sort ranking. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The sorted array to inspect.
+ * @param {*} value The value to evaluate.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {number} Returns the index at which `value` should be inserted
+ * into `array`.
+ * @example
+ *
+ * var objects = [{ 'x': 4 }, { 'x': 5 }];
+ *
+ * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+ * // => 1
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
+ * // => 1
+ */
- if (token.type === 'link') {
- token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
- }
- tokens.push(token);
- continue;
- } // reflink, nolink
+ function sortedLastIndexBy(array, value, iteratee) {
+ return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
+ }
+ /**
+ * This method is like `_.lastIndexOf` except that it performs a binary
+ * search on a sorted `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {*} value The value to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ * @example
+ *
+ * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
+ * // => 3
+ */
- if (token = this.tokenizer.reflink(src, this.tokens.links)) {
- src = src.substring(token.raw.length);
- var _lastToken2 = tokens[tokens.length - 1];
+ function sortedLastIndexOf(array, value) {
+ var length = array == null ? 0 : array.length;
- if (token.type === 'link') {
- token.tokens = this.inlineTokens(token.text, [], true, inRawBlock);
- tokens.push(token);
- } else if (_lastToken2 && token.type === 'text' && _lastToken2.type === 'text') {
- _lastToken2.raw += token.raw;
- _lastToken2.text += token.text;
- } else {
- tokens.push(token);
+ if (length) {
+ var index = baseSortedIndex(array, value, true) - 1;
+
+ if (eq(array[index], value)) {
+ return index;
}
+ }
- continue;
- } // em & strong
+ return -1;
+ }
+ /**
+ * This method is like `_.uniq` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniq([1, 1, 2]);
+ * // => [1, 2]
+ */
- if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
- src = src.substring(token.raw.length);
- token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
- tokens.push(token);
- continue;
- } // code
+ function sortedUniq(array) {
+ return array && array.length ? baseSortedUniq(array) : [];
+ }
+ /**
+ * This method is like `_.uniqBy` except that it's designed and optimized
+ * for sorted arrays.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+ * // => [1.1, 2.3]
+ */
- if (token = this.tokenizer.codespan(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // br
+ function sortedUniqBy(array, iteratee) {
+ return array && array.length ? baseSortedUniq(array, getIteratee(iteratee, 2)) : [];
+ }
+ /**
+ * Gets all but the first element of `array`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.tail([1, 2, 3]);
+ * // => [2, 3]
+ */
- if (token = this.tokenizer.br(src)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // del (gfm)
+ function tail(array) {
+ var length = array == null ? 0 : array.length;
+ return length ? baseSlice(array, 1, length) : [];
+ }
+ /**
+ * Creates a slice of `array` with `n` elements taken from the beginning.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.take([1, 2, 3]);
+ * // => [1]
+ *
+ * _.take([1, 2, 3], 2);
+ * // => [1, 2]
+ *
+ * _.take([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.take([1, 2, 3], 0);
+ * // => []
+ */
- if (token = this.tokenizer.del(src)) {
- src = src.substring(token.raw.length);
- token.tokens = this.inlineTokens(token.text, [], inLink, inRawBlock);
- tokens.push(token);
- continue;
- } // autolink
+ function take(array, n, guard) {
+ if (!(array && array.length)) {
+ return [];
+ }
+ n = guard || n === undefined$1 ? 1 : toInteger(n);
+ return baseSlice(array, 0, n < 0 ? 0 : n);
+ }
+ /**
+ * Creates a slice of `array` with `n` elements taken from the end.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {number} [n=1] The number of elements to take.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * _.takeRight([1, 2, 3]);
+ * // => [3]
+ *
+ * _.takeRight([1, 2, 3], 2);
+ * // => [2, 3]
+ *
+ * _.takeRight([1, 2, 3], 5);
+ * // => [1, 2, 3]
+ *
+ * _.takeRight([1, 2, 3], 0);
+ * // => []
+ */
- if (token = this.tokenizer.autolink(src, mangle)) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // url (gfm)
+ function takeRight(array, n, guard) {
+ var length = array == null ? 0 : array.length;
- if (!inLink && (token = this.tokenizer.url(src, mangle))) {
- src = src.substring(token.raw.length);
- tokens.push(token);
- continue;
- } // text
+ if (!length) {
+ return [];
+ }
+ n = guard || n === undefined$1 ? 1 : toInteger(n);
+ n = length - n;
+ return baseSlice(array, n < 0 ? 0 : n, length);
+ }
+ /**
+ * Creates a slice of `array` with elements taken from the end. Elements are
+ * taken until `predicate` returns falsey. The predicate is invoked with
+ * three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': false }
+ * ];
+ *
+ * _.takeRightWhile(users, function(o) { return !o.active; });
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+ * // => objects for ['pebbles']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeRightWhile(users, ['active', false]);
+ * // => objects for ['fred', 'pebbles']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeRightWhile(users, 'active');
+ * // => []
+ */
- if (token = this.tokenizer.inlineText(src, inRawBlock, smartypants)) {
- src = src.substring(token.raw.length);
- if (token.raw.slice(-1) !== '_') {
- // Track prevChar before string of ____ started
- prevChar = token.raw.slice(-1);
- }
+ function takeRightWhile(array, predicate) {
+ return array && array.length ? baseWhile(array, getIteratee(predicate, 3), false, true) : [];
+ }
+ /**
+ * Creates a slice of `array` with elements taken from the beginning. Elements
+ * are taken until `predicate` returns falsey. The predicate is invoked with
+ * three arguments: (value, index, array).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Array
+ * @param {Array} array The array to query.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the slice of `array`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': false },
+ * { 'user': 'fred', 'active': false },
+ * { 'user': 'pebbles', 'active': true }
+ * ];
+ *
+ * _.takeWhile(users, function(o) { return !o.active; });
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.takeWhile(users, { 'user': 'barney', 'active': false });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.takeWhile(users, ['active', false]);
+ * // => objects for ['barney', 'fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.takeWhile(users, 'active');
+ * // => []
+ */
- keepPrevChar = true;
- lastToken = tokens[tokens.length - 1];
- if (lastToken && lastToken.type === 'text') {
- lastToken.raw += token.raw;
- lastToken.text += token.text;
- } else {
- tokens.push(token);
- }
+ function takeWhile(array, predicate) {
+ return array && array.length ? baseWhile(array, getIteratee(predicate, 3)) : [];
+ }
+ /**
+ * Creates an array of unique values, in order, from all given arrays using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.union([2], [1, 2]);
+ * // => [2, 1]
+ */
- continue;
- }
- if (src) {
- var errMsg = 'Infinite loop on byte: ' + src.charCodeAt(0);
+ var union = baseRest(function (arrays) {
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+ });
+ /**
+ * This method is like `_.union` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by
+ * which uniqueness is computed. Result values are chosen from the first
+ * array in which the value occurs. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * _.unionBy([2.1], [1.2, 2.3], Math.floor);
+ * // => [2.1, 1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
- if (this.options.silent) {
- console.error(errMsg);
- break;
- } else {
- throw new Error(errMsg);
- }
- }
- }
+ var unionBy = baseRest(function (arrays) {
+ var iteratee = last(arrays);
- return tokens;
- }
- }], [{
- key: "rules",
- get: function get() {
- return {
- block: block,
- inline: inline
- };
- }
- /**
- * Static Lex Method
- */
+ if (isArrayLikeObject(iteratee)) {
+ iteratee = undefined$1;
+ }
- }, {
- key: "lex",
- value: function lex(src, options) {
- var lexer = new Lexer(options);
- return lexer.lex(src);
- }
- /**
- * Static Lex Inline Method
- */
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
+ });
+ /**
+ * This method is like `_.union` except that it accepts `comparator` which
+ * is invoked to compare elements of `arrays`. Result values are chosen from
+ * the first array in which the value occurs. The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new array of combined values.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+ * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.unionWith(objects, others, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
- }, {
- key: "lexInline",
- value: function lexInline(src, options) {
- var lexer = new Lexer(options);
- return lexer.inlineTokens(src);
- }
- }]);
+ var unionWith = baseRest(function (arrays) {
+ var comparator = last(arrays);
+ comparator = typeof comparator == 'function' ? comparator : undefined$1;
+ return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined$1, comparator);
+ });
+ /**
+ * Creates a duplicate-free version of an array, using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons, in which only the first occurrence of each element
+ * is kept. The order of result values is determined by the order they occur
+ * in the array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniq([2, 1, 2]);
+ * // => [2, 1]
+ */
- return Lexer;
- }();
+ function uniq(array) {
+ return array && array.length ? baseUniq(array) : [];
+ }
+ /**
+ * This method is like `_.uniq` except that it accepts `iteratee` which is
+ * invoked for each element in `array` to generate the criterion by which
+ * uniqueness is computed. The order of result values is determined by the
+ * order they occur in the array. The iteratee is invoked with one argument:
+ * (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+ * // => [2.1, 1.2]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 1 }, { 'x': 2 }]
+ */
- var defaults$2 = defaults$5.defaults;
- var cleanUrl = helpers.cleanUrl,
- escape$2 = helpers.escape;
- /**
- * Renderer
- */
- var Renderer_1 = /*#__PURE__*/function () {
- function Renderer(options) {
- _classCallCheck$1(this, Renderer);
+ function uniqBy(array, iteratee) {
+ return array && array.length ? baseUniq(array, getIteratee(iteratee, 2)) : [];
+ }
+ /**
+ * This method is like `_.uniq` except that it accepts `comparator` which
+ * is invoked to compare elements of `array`. The order of result values is
+ * determined by the order they occur in the array.The comparator is invoked
+ * with two arguments: (arrVal, othVal).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {Function} [comparator] The comparator invoked per element.
+ * @returns {Array} Returns the new duplicate free array.
+ * @example
+ *
+ * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
+ *
+ * _.uniqWith(objects, _.isEqual);
+ * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+ */
- this.options = options || defaults$2;
- }
- _createClass$1(Renderer, [{
- key: "code",
- value: function code(_code, infostring, escaped) {
- var lang = (infostring || '').match(/\S*/)[0];
+ function uniqWith(array, comparator) {
+ comparator = typeof comparator == 'function' ? comparator : undefined$1;
+ return array && array.length ? baseUniq(array, undefined$1, comparator) : [];
+ }
+ /**
+ * This method is like `_.zip` except that it accepts an array of grouped
+ * elements and creates an array regrouping the elements to their pre-zip
+ * configuration.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.2.0
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
+ * // => [['a', 1, true], ['b', 2, false]]
+ *
+ * _.unzip(zipped);
+ * // => [['a', 'b'], [1, 2], [true, false]]
+ */
- if (this.options.highlight) {
- var out = this.options.highlight(_code, lang);
- if (out != null && out !== _code) {
- escaped = true;
- _code = out;
+ function unzip(array) {
+ if (!(array && array.length)) {
+ return [];
}
+
+ var length = 0;
+ array = arrayFilter(array, function (group) {
+ if (isArrayLikeObject(group)) {
+ length = nativeMax(group.length, length);
+ return true;
+ }
+ });
+ return baseTimes(length, function (index) {
+ return arrayMap(array, baseProperty(index));
+ });
}
+ /**
+ * This method is like `_.unzip` except that it accepts `iteratee` to specify
+ * how regrouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Array
+ * @param {Array} array The array of grouped elements to process.
+ * @param {Function} [iteratee=_.identity] The function to combine
+ * regrouped values.
+ * @returns {Array} Returns the new array of regrouped elements.
+ * @example
+ *
+ * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+ * // => [[1, 10, 100], [2, 20, 200]]
+ *
+ * _.unzipWith(zipped, _.add);
+ * // => [3, 30, 300]
+ */
- _code = _code.replace(/\n$/, '') + '\n';
- if (!lang) {
- return '' + (escaped ? _code : escape$2(_code, true)) + '
\n';
- }
+ function unzipWith(array, iteratee) {
+ if (!(array && array.length)) {
+ return [];
+ }
- return '' + (escaped ? _code : escape$2(_code, true)) + '
\n';
- }
- }, {
- key: "blockquote",
- value: function blockquote(quote) {
- return '\n' + quote + '
\n';
- }
- }, {
- key: "html",
- value: function html(_html) {
- return _html;
- }
- }, {
- key: "heading",
- value: function heading(text, level, raw, slugger) {
- if (this.options.headerIds) {
- return '\n';
- } // ignore IDs
-
-
- return '' + text + '\n';
- }
- }, {
- key: "hr",
- value: function hr() {
- return this.options.xhtml ? '
\n' : '
\n';
- }
- }, {
- key: "list",
- value: function list(body, ordered, start) {
- var type = ordered ? 'ol' : 'ul',
- startatt = ordered && start !== 1 ? ' start="' + start + '"' : '';
- return '<' + type + startatt + '>\n' + body + '' + type + '>\n';
- }
- }, {
- key: "listitem",
- value: function listitem(text) {
- return '' + text + '\n';
- }
- }, {
- key: "checkbox",
- value: function checkbox(checked) {
- return ' ';
- }
- }, {
- key: "paragraph",
- value: function paragraph(text) {
- return '' + text + '
\n';
- }
- }, {
- key: "table",
- value: function table(header, body) {
- if (body) body = '' + body + '';
- return '\n' + '\n' + header + '\n' + body + '
\n';
- }
- }, {
- key: "tablerow",
- value: function tablerow(content) {
- return '\n' + content + '
\n';
- }
- }, {
- key: "tablecell",
- value: function tablecell(content, flags) {
- var type = flags.header ? 'th' : 'td';
- var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' : '<' + type + '>';
- return tag + content + '' + type + '>\n';
- } // span level renderer
+ var result = unzip(array);
- }, {
- key: "strong",
- value: function strong(text) {
- return '' + text + '';
- }
- }, {
- key: "em",
- value: function em(text) {
- return '' + text + '';
- }
- }, {
- key: "codespan",
- value: function codespan(text) {
- return '' + text + '
';
- }
- }, {
- key: "br",
- value: function br() {
- return this.options.xhtml ? '
' : '
';
- }
- }, {
- key: "del",
- value: function del(text) {
- return '' + text + '';
- }
- }, {
- key: "link",
- value: function link(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ if (iteratee == null) {
+ return result;
+ }
- if (href === null) {
- return text;
+ return arrayMap(result, function (group) {
+ return apply(iteratee, undefined$1, group);
+ });
}
+ /**
+ * Creates an array excluding all given values using
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * for equality comparisons.
+ *
+ * **Note:** Unlike `_.pull`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {Array} array The array to inspect.
+ * @param {...*} [values] The values to exclude.
+ * @returns {Array} Returns the new array of filtered values.
+ * @see _.difference, _.xor
+ * @example
+ *
+ * _.without([2, 1, 2, 3], 1, 2);
+ * // => [3]
+ */
- var out = ' [1, 3]
+ */
- out += '>' + text + '';
- return out;
- }
- }, {
- key: "image",
- value: function image(href, title, text) {
- href = cleanUrl(this.options.sanitize, this.options.baseUrl, href);
+ var xor = baseRest(function (arrays) {
+ return baseXor(arrayFilter(arrays, isArrayLikeObject));
+ });
+ /**
+ * This method is like `_.xor` except that it accepts `iteratee` which is
+ * invoked for each element of each `arrays` to generate the criterion by
+ * which by which they're compared. The order of result values is determined
+ * by the order they occur in the arrays. The iteratee is invoked with one
+ * argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to inspect.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Array} Returns the new array of filtered values.
+ * @example
+ *
+ * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+ * // => [1.2, 3.4]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+ * // => [{ 'x': 2 }]
+ */
- if (href === null) {
- return text;
- }
+ var xorBy = baseRest(function (arrays) {
+ var iteratee = last(arrays);
- var out = ' [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+ */
- out += this.options.xhtml ? '/>' : '>';
- return out;
- }
- }, {
- key: "text",
- value: function text(_text) {
- return _text;
- }
- }]);
+ var xorWith = baseRest(function (arrays) {
+ var comparator = last(arrays);
+ comparator = typeof comparator == 'function' ? comparator : undefined$1;
+ return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined$1, comparator);
+ });
+ /**
+ * Creates an array of grouped elements, the first of which contains the
+ * first elements of the given arrays, the second of which contains the
+ * second elements of the given arrays, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zip(['a', 'b'], [1, 2], [true, false]);
+ * // => [['a', 1, true], ['b', 2, false]]
+ */
- return Renderer;
- }();
+ var zip = baseRest(unzip);
+ /**
+ * This method is like `_.fromPairs` except that it accepts two arrays,
+ * one of property identifiers and one of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.4.0
+ * @category Array
+ * @param {Array} [props=[]] The property identifiers.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObject(['a', 'b'], [1, 2]);
+ * // => { 'a': 1, 'b': 2 }
+ */
- /**
- * TextRenderer
- * returns only the textual part of the token
- */
- var TextRenderer_1 = /*#__PURE__*/function () {
- function TextRenderer() {
- _classCallCheck$1(this, TextRenderer);
- }
+ function zipObject(props, values) {
+ return baseZipObject(props || [], values || [], assignValue);
+ }
+ /**
+ * This method is like `_.zipObject` except that it supports property paths.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.1.0
+ * @category Array
+ * @param {Array} [props=[]] The property identifiers.
+ * @param {Array} [values=[]] The property values.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+ * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+ */
- _createClass$1(TextRenderer, [{
- key: "strong",
- value: // no need for block level renderers
- function strong(text) {
- return text;
- }
- }, {
- key: "em",
- value: function em(text) {
- return text;
- }
- }, {
- key: "codespan",
- value: function codespan(text) {
- return text;
- }
- }, {
- key: "del",
- value: function del(text) {
- return text;
- }
- }, {
- key: "html",
- value: function html(text) {
- return text;
- }
- }, {
- key: "text",
- value: function text(_text) {
- return _text;
- }
- }, {
- key: "link",
- value: function link(href, title, text) {
- return '' + text;
- }
- }, {
- key: "image",
- value: function image(href, title, text) {
- return '' + text;
- }
- }, {
- key: "br",
- value: function br() {
- return '';
- }
- }]);
- return TextRenderer;
- }();
+ function zipObjectDeep(props, values) {
+ return baseZipObject(props || [], values || [], baseSet);
+ }
+ /**
+ * This method is like `_.zip` except that it accepts `iteratee` to specify
+ * how grouped values should be combined. The iteratee is invoked with the
+ * elements of each group: (...group).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Array
+ * @param {...Array} [arrays] The arrays to process.
+ * @param {Function} [iteratee=_.identity] The function to combine
+ * grouped values.
+ * @returns {Array} Returns the new array of grouped elements.
+ * @example
+ *
+ * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+ * return a + b + c;
+ * });
+ * // => [111, 222]
+ */
- /**
- * Slugger generates header id
- */
- var Slugger_1 = /*#__PURE__*/function () {
- function Slugger() {
- _classCallCheck$1(this, Slugger);
- this.seen = {};
- }
+ var zipWith = baseRest(function (arrays) {
+ var length = arrays.length,
+ iteratee = length > 1 ? arrays[length - 1] : undefined$1;
+ iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined$1;
+ return unzipWith(arrays, iteratee);
+ });
+ /*------------------------------------------------------------------------*/
- _createClass$1(Slugger, [{
- key: "serialize",
- value: function serialize(value) {
- return value.toLowerCase().trim() // remove html tags
- .replace(/<[!\/a-z].*?>/ig, '') // remove unwanted chars
- .replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g, '').replace(/\s/g, '-');
- }
- /**
- * Finds the next safe (unique) slug to use
- */
+ /**
+ * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+ * chain sequences enabled. The result of such sequences must be unwrapped
+ * with `_#value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.3.0
+ * @category Seq
+ * @param {*} value The value to wrap.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'pebbles', 'age': 1 }
+ * ];
+ *
+ * var youngest = _
+ * .chain(users)
+ * .sortBy('age')
+ * .map(function(o) {
+ * return o.user + ' is ' + o.age;
+ * })
+ * .head()
+ * .value();
+ * // => 'pebbles is 1'
+ */
- }, {
- key: "getNextSafeSlug",
- value: function getNextSafeSlug(originalSlug, isDryRun) {
- var slug = originalSlug;
- var occurenceAccumulator = 0;
+ function chain(value) {
+ var result = lodash(value);
+ result.__chain__ = true;
+ return result;
+ }
+ /**
+ * This method invokes `interceptor` and returns `value`. The interceptor
+ * is invoked with one argument; (value). The purpose of this method is to
+ * "tap into" a method chain sequence in order to modify intermediate results.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns `value`.
+ * @example
+ *
+ * _([1, 2, 3])
+ * .tap(function(array) {
+ * // Mutate input array.
+ * array.pop();
+ * })
+ * .reverse()
+ * .value();
+ * // => [2, 1]
+ */
- if (this.seen.hasOwnProperty(slug)) {
- occurenceAccumulator = this.seen[originalSlug];
- do {
- occurenceAccumulator++;
- slug = originalSlug + '-' + occurenceAccumulator;
- } while (this.seen.hasOwnProperty(slug));
+ function tap(value, interceptor) {
+ interceptor(value);
+ return value;
}
+ /**
+ * This method is like `_.tap` except that it returns the result of `interceptor`.
+ * The purpose of this method is to "pass thru" values replacing intermediate
+ * results in a method chain sequence.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Seq
+ * @param {*} value The value to provide to `interceptor`.
+ * @param {Function} interceptor The function to invoke.
+ * @returns {*} Returns the result of `interceptor`.
+ * @example
+ *
+ * _(' abc ')
+ * .chain()
+ * .trim()
+ * .thru(function(value) {
+ * return [value];
+ * })
+ * .value();
+ * // => ['abc']
+ */
- if (!isDryRun) {
- this.seen[originalSlug] = occurenceAccumulator;
- this.seen[slug] = 0;
+
+ function thru(value, interceptor) {
+ return interceptor(value);
}
+ /**
+ * This method is the wrapper version of `_.at`.
+ *
+ * @name at
+ * @memberOf _
+ * @since 1.0.0
+ * @category Seq
+ * @param {...(string|string[])} [paths] The property paths to pick.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _(object).at(['a[0].b.c', 'a[1]']).value();
+ * // => [3, 4]
+ */
- return slug;
- }
- /**
- * Convert string to unique id
- * @param {object} options
- * @param {boolean} options.dryrun Generates the next unique slug without updating the internal accumulator.
- */
- }, {
- key: "slug",
- value: function slug(value) {
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- var slug = this.serialize(value);
- return this.getNextSafeSlug(slug, options.dryrun);
- }
- }]);
+ var wrapperAt = flatRest(function (paths) {
+ var length = paths.length,
+ start = length ? paths[0] : 0,
+ value = this.__wrapped__,
+ interceptor = function interceptor(object) {
+ return baseAt(object, paths);
+ };
- return Slugger;
- }();
+ if (length > 1 || this.__actions__.length || !(value instanceof LazyWrapper) || !isIndex(start)) {
+ return this.thru(interceptor);
+ }
- var defaults$1 = defaults$5.defaults;
- var unescape$1 = helpers.unescape;
- /**
- * Parsing & Compiling
- */
+ value = value.slice(start, +start + (length ? 1 : 0));
- var Parser_1 = /*#__PURE__*/function () {
- function Parser(options) {
- _classCallCheck$1(this, Parser);
+ value.__actions__.push({
+ 'func': thru,
+ 'args': [interceptor],
+ 'thisArg': undefined$1
+ });
- this.options = options || defaults$1;
- this.options.renderer = this.options.renderer || new Renderer_1();
- this.renderer = this.options.renderer;
- this.renderer.options = this.options;
- this.textRenderer = new TextRenderer_1();
- this.slugger = new Slugger_1();
- }
- /**
- * Static Parse Method
- */
+ return new LodashWrapper(value, this.__chain__).thru(function (array) {
+ if (length && !array.length) {
+ array.push(undefined$1);
+ }
+ return array;
+ });
+ });
+ /**
+ * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+ *
+ * @name chain
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 40 }
+ * ];
+ *
+ * // A sequence without explicit chaining.
+ * _(users).head();
+ * // => { 'user': 'barney', 'age': 36 }
+ *
+ * // A sequence with explicit chaining.
+ * _(users)
+ * .chain()
+ * .head()
+ * .pick('user')
+ * .value();
+ * // => { 'user': 'barney' }
+ */
- _createClass$1(Parser, [{
- key: "parse",
- value:
- /**
- * Parse Loop
- */
- function parse(tokens) {
- var top = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
- var out = '',
- i,
- j,
- k,
- l2,
- l3,
- row,
- cell,
- header,
- body,
- token,
- ordered,
- start,
- loose,
- itemBody,
- item,
- checked,
- task,
- checkbox;
- var l = tokens.length;
+ function wrapperChain() {
+ return chain(this);
+ }
+ /**
+ * Executes the chain sequence and returns the wrapped result.
+ *
+ * @name commit
+ * @memberOf _
+ * @since 3.2.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2];
+ * var wrapped = _(array).push(3);
+ *
+ * console.log(array);
+ * // => [1, 2]
+ *
+ * wrapped = wrapped.commit();
+ * console.log(array);
+ * // => [1, 2, 3]
+ *
+ * wrapped.last();
+ * // => 3
+ *
+ * console.log(array);
+ * // => [1, 2, 3]
+ */
- for (i = 0; i < l; i++) {
- token = tokens[i];
- switch (token.type) {
- case 'space':
- {
- continue;
- }
+ function wrapperCommit() {
+ return new LodashWrapper(this.value(), this.__chain__);
+ }
+ /**
+ * Gets the next value on a wrapped object following the
+ * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+ *
+ * @name next
+ * @memberOf _
+ * @since 4.0.0
+ * @category Seq
+ * @returns {Object} Returns the next iterator value.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 1 }
+ *
+ * wrapped.next();
+ * // => { 'done': false, 'value': 2 }
+ *
+ * wrapped.next();
+ * // => { 'done': true, 'value': undefined }
+ */
- case 'hr':
- {
- out += this.renderer.hr();
- continue;
- }
- case 'heading':
- {
- out += this.renderer.heading(this.parseInline(token.tokens), token.depth, unescape$1(this.parseInline(token.tokens, this.textRenderer)), this.slugger);
- continue;
- }
+ function wrapperNext() {
+ if (this.__values__ === undefined$1) {
+ this.__values__ = toArray(this.value());
+ }
- case 'code':
- {
- out += this.renderer.code(token.text, token.lang, token.escaped);
- continue;
- }
+ var done = this.__index__ >= this.__values__.length,
+ value = done ? undefined$1 : this.__values__[this.__index__++];
+ return {
+ 'done': done,
+ 'value': value
+ };
+ }
+ /**
+ * Enables the wrapper to be iterable.
+ *
+ * @name Symbol.iterator
+ * @memberOf _
+ * @since 4.0.0
+ * @category Seq
+ * @returns {Object} Returns the wrapper object.
+ * @example
+ *
+ * var wrapped = _([1, 2]);
+ *
+ * wrapped[Symbol.iterator]() === wrapped;
+ * // => true
+ *
+ * Array.from(wrapped);
+ * // => [1, 2]
+ */
- case 'table':
- {
- header = ''; // header
- cell = '';
- l2 = token.header.length;
+ function wrapperToIterator() {
+ return this;
+ }
+ /**
+ * Creates a clone of the chain sequence planting `value` as the wrapped value.
+ *
+ * @name plant
+ * @memberOf _
+ * @since 3.2.0
+ * @category Seq
+ * @param {*} value The value to plant.
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var wrapped = _([1, 2]).map(square);
+ * var other = wrapped.plant([3, 4]);
+ *
+ * other.value();
+ * // => [9, 16]
+ *
+ * wrapped.value();
+ * // => [1, 4]
+ */
- for (j = 0; j < l2; j++) {
- cell += this.renderer.tablecell(this.parseInline(token.tokens.header[j]), {
- header: true,
- align: token.align[j]
- });
- }
- header += this.renderer.tablerow(cell);
- body = '';
- l2 = token.cells.length;
+ function wrapperPlant(value) {
+ var result,
+ parent = this;
- for (j = 0; j < l2; j++) {
- row = token.tokens.cells[j];
- cell = '';
- l3 = row.length;
+ while (parent instanceof baseLodash) {
+ var clone = wrapperClone(parent);
+ clone.__index__ = 0;
+ clone.__values__ = undefined$1;
- for (k = 0; k < l3; k++) {
- cell += this.renderer.tablecell(this.parseInline(row[k]), {
- header: false,
- align: token.align[k]
- });
- }
+ if (result) {
+ previous.__wrapped__ = clone;
+ } else {
+ result = clone;
+ }
- body += this.renderer.tablerow(cell);
- }
+ var previous = clone;
+ parent = parent.__wrapped__;
+ }
- out += this.renderer.table(header, body);
- continue;
- }
+ previous.__wrapped__ = value;
+ return result;
+ }
+ /**
+ * This method is the wrapper version of `_.reverse`.
+ *
+ * **Note:** This method mutates the wrapped array.
+ *
+ * @name reverse
+ * @memberOf _
+ * @since 0.1.0
+ * @category Seq
+ * @returns {Object} Returns the new `lodash` wrapper instance.
+ * @example
+ *
+ * var array = [1, 2, 3];
+ *
+ * _(array).reverse().value()
+ * // => [3, 2, 1]
+ *
+ * console.log(array);
+ * // => [3, 2, 1]
+ */
- case 'blockquote':
- {
- body = this.parse(token.tokens);
- out += this.renderer.blockquote(body);
- continue;
- }
- case 'list':
- {
- ordered = token.ordered;
- start = token.start;
- loose = token.loose;
- l2 = token.items.length;
- body = '';
+ function wrapperReverse() {
+ var value = this.__wrapped__;
- for (j = 0; j < l2; j++) {
- item = token.items[j];
- checked = item.checked;
- task = item.task;
- itemBody = '';
+ if (value instanceof LazyWrapper) {
+ var wrapped = value;
- if (item.task) {
- checkbox = this.renderer.checkbox(checked);
+ if (this.__actions__.length) {
+ wrapped = new LazyWrapper(this);
+ }
- if (loose) {
- if (item.tokens.length > 0 && item.tokens[0].type === 'text') {
- item.tokens[0].text = checkbox + ' ' + item.tokens[0].text;
+ wrapped = wrapped.reverse();
- if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === 'text') {
- item.tokens[0].tokens[0].text = checkbox + ' ' + item.tokens[0].tokens[0].text;
- }
- } else {
- item.tokens.unshift({
- type: 'text',
- text: checkbox
- });
- }
- } else {
- itemBody += checkbox;
- }
- }
+ wrapped.__actions__.push({
+ 'func': thru,
+ 'args': [reverse],
+ 'thisArg': undefined$1
+ });
- itemBody += this.parse(item.tokens, loose);
- body += this.renderer.listitem(itemBody, task, checked);
- }
+ return new LodashWrapper(wrapped, this.__chain__);
+ }
- out += this.renderer.list(body, ordered, start);
- continue;
- }
+ return this.thru(reverse);
+ }
+ /**
+ * Executes the chain sequence to resolve the unwrapped value.
+ *
+ * @name value
+ * @memberOf _
+ * @since 0.1.0
+ * @alias toJSON, valueOf
+ * @category Seq
+ * @returns {*} Returns the resolved unwrapped value.
+ * @example
+ *
+ * _([1, 2, 3]).value();
+ * // => [1, 2, 3]
+ */
- case 'html':
- {
- // TODO parse inline content if parameter markdown=1
- out += this.renderer.html(token.text);
- continue;
- }
- case 'paragraph':
- {
- out += this.renderer.paragraph(this.parseInline(token.tokens));
- continue;
- }
+ function wrapperValue() {
+ return baseWrapperValue(this.__wrapped__, this.__actions__);
+ }
+ /*------------------------------------------------------------------------*/
- case 'text':
- {
- body = token.tokens ? this.parseInline(token.tokens) : token.text;
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The corresponding value of
+ * each key is the number of times the key was returned by `iteratee`. The
+ * iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.countBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': 1, '6': 2 }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.countBy(['one', 'two', 'three'], 'length');
+ * // => { '3': 2, '5': 1 }
+ */
- while (i + 1 < l && tokens[i + 1].type === 'text') {
- token = tokens[++i];
- body += '\n' + (token.tokens ? this.parseInline(token.tokens) : token.text);
- }
- out += top ? this.renderer.paragraph(body) : body;
- continue;
- }
+ var countBy = createAggregator(function (result, value, key) {
+ if (hasOwnProperty.call(result, key)) {
+ ++result[key];
+ } else {
+ baseAssignValue(result, key, 1);
+ }
+ });
+ /**
+ * Checks if `predicate` returns truthy for **all** elements of `collection`.
+ * Iteration is stopped once `predicate` returns falsey. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * **Note:** This method returns `true` for
+ * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
+ * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
+ * elements of empty collections.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
+ * else `false`.
+ * @example
+ *
+ * _.every([true, 1, null, 'yes'], Boolean);
+ * // => false
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.every(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.every(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.every(users, 'active');
+ * // => false
+ */
- default:
- {
- var errMsg = 'Token with "' + token.type + '" type was not found.';
+ function every(collection, predicate, guard) {
+ var func = isArray(collection) ? arrayEvery : baseEvery;
- if (this.options.silent) {
- console.error(errMsg);
- return;
- } else {
- throw new Error(errMsg);
- }
- }
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined$1;
}
- }
- return out;
- }
- /**
- * Parse Inline Tokens
- */
+ return func(collection, getIteratee(predicate, 3));
+ }
+ /**
+ * Iterates over elements of `collection`, returning an array of all elements
+ * `predicate` returns truthy for. The predicate is invoked with three
+ * arguments: (value, index|key, collection).
+ *
+ * **Note:** Unlike `_.remove`, this method returns a new array.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @see _.reject
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false }
+ * ];
+ *
+ * _.filter(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.filter(users, { 'age': 36, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.filter(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.filter(users, 'active');
+ * // => objects for ['barney']
+ *
+ * // Combining several predicates using `_.overEvery` or `_.overSome`.
+ * _.filter(users, _.overSome([{ 'age': 36 }, ['age', 40]]));
+ * // => objects for ['fred', 'barney']
+ */
- }, {
- key: "parseInline",
- value: function parseInline(tokens, renderer) {
- renderer = renderer || this.renderer;
- var out = '',
- i,
- token;
- var l = tokens.length;
- for (i = 0; i < l; i++) {
- token = tokens[i];
+ function filter(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ return func(collection, getIteratee(predicate, 3));
+ }
+ /**
+ * Iterates over elements of `collection`, returning the first element
+ * `predicate` returns truthy for. The predicate is invoked with three
+ * arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': true },
+ * { 'user': 'fred', 'age': 40, 'active': false },
+ * { 'user': 'pebbles', 'age': 1, 'active': true }
+ * ];
+ *
+ * _.find(users, function(o) { return o.age < 40; });
+ * // => object for 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.find(users, { 'age': 1, 'active': true });
+ * // => object for 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.find(users, ['active', false]);
+ * // => object for 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.find(users, 'active');
+ * // => object for 'barney'
+ */
- switch (token.type) {
- case 'escape':
- {
- out += renderer.text(token.text);
- break;
- }
- case 'html':
- {
- out += renderer.html(token.text);
- break;
- }
+ var find = createFind(findIndex);
+ /**
+ * This method is like `_.find` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param {number} [fromIndex=collection.length-1] The index to search from.
+ * @returns {*} Returns the matched element, else `undefined`.
+ * @example
+ *
+ * _.findLast([1, 2, 3, 4], function(n) {
+ * return n % 2 == 1;
+ * });
+ * // => 3
+ */
- case 'link':
- {
- out += renderer.link(token.href, token.title, this.parseInline(token.tokens, renderer));
- break;
- }
+ var findLast = createFind(findLastIndex);
+ /**
+ * Creates a flattened array of values by running each element in `collection`
+ * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+ * with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [n, n];
+ * }
+ *
+ * _.flatMap([1, 2], duplicate);
+ * // => [1, 1, 2, 2]
+ */
- case 'image':
- {
- out += renderer.image(token.href, token.title, token.text);
- break;
- }
+ function flatMap(collection, iteratee) {
+ return baseFlatten(map(collection, iteratee), 1);
+ }
+ /**
+ * This method is like `_.flatMap` except that it recursively flattens the
+ * mapped results.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [[[n, n]]];
+ * }
+ *
+ * _.flatMapDeep([1, 2], duplicate);
+ * // => [1, 1, 2, 2]
+ */
- case 'strong':
- {
- out += renderer.strong(this.parseInline(token.tokens, renderer));
- break;
- }
- case 'em':
- {
- out += renderer.em(this.parseInline(token.tokens, renderer));
- break;
- }
+ function flatMapDeep(collection, iteratee) {
+ return baseFlatten(map(collection, iteratee), INFINITY);
+ }
+ /**
+ * This method is like `_.flatMap` except that it recursively flattens the
+ * mapped results up to `depth` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.7.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {number} [depth=1] The maximum recursion depth.
+ * @returns {Array} Returns the new flattened array.
+ * @example
+ *
+ * function duplicate(n) {
+ * return [[[n, n]]];
+ * }
+ *
+ * _.flatMapDepth([1, 2], duplicate, 2);
+ * // => [[1, 1], [2, 2]]
+ */
- case 'codespan':
- {
- out += renderer.codespan(token.text);
- break;
- }
- case 'br':
- {
- out += renderer.br();
- break;
- }
+ function flatMapDepth(collection, iteratee, depth) {
+ depth = depth === undefined$1 ? 1 : toInteger(depth);
+ return baseFlatten(map(collection, iteratee), depth);
+ }
+ /**
+ * Iterates over elements of `collection` and invokes `iteratee` for each element.
+ * The iteratee is invoked with three arguments: (value, index|key, collection).
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * **Note:** As with other "Collections" methods, objects with a "length"
+ * property are iterated like arrays. To avoid this behavior use `_.forIn`
+ * or `_.forOwn` for object iteration.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @alias each
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @see _.forEachRight
+ * @example
+ *
+ * _.forEach([1, 2], function(value) {
+ * console.log(value);
+ * });
+ * // => Logs `1` then `2`.
+ *
+ * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+ */
- case 'del':
- {
- out += renderer.del(this.parseInline(token.tokens, renderer));
- break;
- }
- case 'text':
- {
- out += renderer.text(token.text);
- break;
- }
+ function forEach(collection, iteratee) {
+ var func = isArray(collection) ? arrayEach : baseEach;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+ /**
+ * This method is like `_.forEach` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @alias eachRight
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array|Object} Returns `collection`.
+ * @see _.forEach
+ * @example
+ *
+ * _.forEachRight([1, 2], function(value) {
+ * console.log(value);
+ * });
+ * // => Logs `2` then `1`.
+ */
- default:
- {
- var errMsg = 'Token with "' + token.type + '" type was not found.';
- if (this.options.silent) {
- console.error(errMsg);
- return;
- } else {
- throw new Error(errMsg);
- }
- }
- }
+ function forEachRight(collection, iteratee) {
+ var func = isArray(collection) ? arrayEachRight : baseEachRight;
+ return func(collection, getIteratee(iteratee, 3));
}
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The order of grouped values
+ * is determined by the order they occur in `collection`. The corresponding
+ * value of each key is an array of elements responsible for generating the
+ * key. The iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+ * // => { '4': [4.2], '6': [6.1, 6.3] }
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.groupBy(['one', 'two', 'three'], 'length');
+ * // => { '3': ['one', 'two'], '5': ['three'] }
+ */
- return out;
- }
- }], [{
- key: "parse",
- value: function parse(tokens, options) {
- var parser = new Parser(options);
- return parser.parse(tokens);
- }
- /**
- * Static Parse Inline Method
- */
- }, {
- key: "parseInline",
- value: function parseInline(tokens, options) {
- var parser = new Parser(options);
- return parser.parseInline(tokens);
- }
- }]);
+ var groupBy = createAggregator(function (result, value, key) {
+ if (hasOwnProperty.call(result, key)) {
+ result[key].push(value);
+ } else {
+ baseAssignValue(result, key, [value]);
+ }
+ });
+ /**
+ * Checks if `value` is in `collection`. If `collection` is a string, it's
+ * checked for a substring of `value`, otherwise
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * is used for equality comparisons. If `fromIndex` is negative, it's used as
+ * the offset from the end of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @param {*} value The value to search for.
+ * @param {number} [fromIndex=0] The index to search from.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+ * @returns {boolean} Returns `true` if `value` is found, else `false`.
+ * @example
+ *
+ * _.includes([1, 2, 3], 1);
+ * // => true
+ *
+ * _.includes([1, 2, 3], 1, 2);
+ * // => false
+ *
+ * _.includes({ 'a': 1, 'b': 2 }, 1);
+ * // => true
+ *
+ * _.includes('abcd', 'bc');
+ * // => true
+ */
- return Parser;
- }();
+ function includes(collection, value, fromIndex, guard) {
+ collection = isArrayLike(collection) ? collection : values(collection);
+ fromIndex = fromIndex && !guard ? toInteger(fromIndex) : 0;
+ var length = collection.length;
- var merge = helpers.merge,
- checkSanitizeDeprecation = helpers.checkSanitizeDeprecation,
- escape$1 = helpers.escape;
- var getDefaults = defaults$5.getDefaults,
- changeDefaults = defaults$5.changeDefaults,
- defaults = defaults$5.defaults;
- /**
- * Marked
- */
+ if (fromIndex < 0) {
+ fromIndex = nativeMax(length + fromIndex, 0);
+ }
- function marked(src, opt, callback) {
- // throw error in case of non string input
- if (typeof src === 'undefined' || src === null) {
- throw new Error('marked(): input parameter is undefined or null');
- }
+ return isString(collection) ? fromIndex <= length && collection.indexOf(value, fromIndex) > -1 : !!length && baseIndexOf(collection, value, fromIndex) > -1;
+ }
+ /**
+ * Invokes the method at `path` of each element in `collection`, returning
+ * an array of the results of each invoked method. Any additional arguments
+ * are provided to each invoked method. If `path` is a function, it's invoked
+ * for, and `this` bound to, each element in `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Array|Function|string} path The path of the method to invoke or
+ * the function invoked per iteration.
+ * @param {...*} [args] The arguments to invoke each method with.
+ * @returns {Array} Returns the array of results.
+ * @example
+ *
+ * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+ * // => [[1, 5, 7], [1, 2, 3]]
+ *
+ * _.invokeMap([123, 456], String.prototype.split, '');
+ * // => [['1', '2', '3'], ['4', '5', '6']]
+ */
- if (typeof src !== 'string') {
- throw new Error('marked(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
- }
- if (typeof opt === 'function') {
- callback = opt;
- opt = null;
- }
+ var invokeMap = baseRest(function (collection, path, args) {
+ var index = -1,
+ isFunc = typeof path == 'function',
+ result = isArrayLike(collection) ? Array(collection.length) : [];
+ baseEach(collection, function (value) {
+ result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
+ });
+ return result;
+ });
+ /**
+ * Creates an object composed of keys generated from the results of running
+ * each element of `collection` thru `iteratee`. The corresponding value of
+ * each key is the last element responsible for generating the key. The
+ * iteratee is invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+ * @returns {Object} Returns the composed aggregate object.
+ * @example
+ *
+ * var array = [
+ * { 'dir': 'left', 'code': 97 },
+ * { 'dir': 'right', 'code': 100 }
+ * ];
+ *
+ * _.keyBy(array, function(o) {
+ * return String.fromCharCode(o.code);
+ * });
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+ *
+ * _.keyBy(array, 'dir');
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+ */
- opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
+ var keyBy = createAggregator(function (result, value, key) {
+ baseAssignValue(result, key, value);
+ });
+ /**
+ * Creates an array of values by running each element in `collection` thru
+ * `iteratee`. The iteratee is invoked with three arguments:
+ * (value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+ *
+ * The guarded methods are:
+ * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+ * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+ * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+ * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new mapped array.
+ * @example
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * _.map([4, 8], square);
+ * // => [16, 64]
+ *
+ * _.map({ 'a': 4, 'b': 8 }, square);
+ * // => [16, 64] (iteration order is not guaranteed)
+ *
+ * var users = [
+ * { 'user': 'barney' },
+ * { 'user': 'fred' }
+ * ];
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.map(users, 'user');
+ * // => ['barney', 'fred']
+ */
- if (callback) {
- var highlight = opt.highlight;
- var tokens;
+ function map(collection, iteratee) {
+ var func = isArray(collection) ? arrayMap : baseMap;
+ return func(collection, getIteratee(iteratee, 3));
+ }
+ /**
+ * This method is like `_.sortBy` except that it allows specifying the sort
+ * orders of the iteratees to sort by. If `orders` is unspecified, all values
+ * are sorted in ascending order. Otherwise, specify an order of "desc" for
+ * descending or "asc" for ascending sort order of corresponding values.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+ * The iteratees to sort by.
+ * @param {string[]} [orders] The sort orders of `iteratees`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 34 },
+ * { 'user': 'fred', 'age': 40 },
+ * { 'user': 'barney', 'age': 36 }
+ * ];
+ *
+ * // Sort by `user` in ascending order and by `age` in descending order.
+ * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+ */
- try {
- tokens = Lexer_1.lex(src, opt);
- } catch (e) {
- return callback(e);
- }
- var done = function done(err) {
- var out;
+ function orderBy(collection, iteratees, orders, guard) {
+ if (collection == null) {
+ return [];
+ }
- if (!err) {
- try {
- if (opt.walkTokens) {
- marked.walkTokens(tokens, opt.walkTokens);
- }
+ if (!isArray(iteratees)) {
+ iteratees = iteratees == null ? [] : [iteratees];
+ }
- out = Parser_1.parse(tokens, opt);
- } catch (e) {
- err = e;
+ orders = guard ? undefined$1 : orders;
+
+ if (!isArray(orders)) {
+ orders = orders == null ? [] : [orders];
}
- }
- opt.highlight = highlight;
- return err ? callback(err) : callback(null, out);
- };
+ return baseOrderBy(collection, iteratees, orders);
+ }
+ /**
+ * Creates an array of elements split into two groups, the first of which
+ * contains elements `predicate` returns truthy for, the second of which
+ * contains elements `predicate` returns falsey for. The predicate is
+ * invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the array of grouped elements.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true },
+ * { 'user': 'pebbles', 'age': 1, 'active': false }
+ * ];
+ *
+ * _.partition(users, function(o) { return o.active; });
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.partition(users, { 'age': 1, 'active': false });
+ * // => objects for [['pebbles'], ['barney', 'fred']]
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.partition(users, ['active', false]);
+ * // => objects for [['barney', 'pebbles'], ['fred']]
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.partition(users, 'active');
+ * // => objects for [['fred'], ['barney', 'pebbles']]
+ */
- if (!highlight || highlight.length < 3) {
- return done();
- }
- delete opt.highlight;
- if (!tokens.length) return done();
- var pending = 0;
- marked.walkTokens(tokens, function (token) {
- if (token.type === 'code') {
- pending++;
- setTimeout(function () {
- highlight(token.text, token.lang, function (err, code) {
- if (err) {
- return done(err);
- }
+ var partition = createAggregator(function (result, value, key) {
+ result[key ? 0 : 1].push(value);
+ }, function () {
+ return [[], []];
+ });
+ /**
+ * Reduces `collection` to a value which is the accumulated result of running
+ * each element in `collection` thru `iteratee`, where each successive
+ * invocation is supplied the return value of the previous. If `accumulator`
+ * is not given, the first element of `collection` is used as the initial
+ * value. The iteratee is invoked with four arguments:
+ * (accumulator, value, index|key, collection).
+ *
+ * Many lodash methods are guarded to work as iteratees for methods like
+ * `_.reduce`, `_.reduceRight`, and `_.transform`.
+ *
+ * The guarded methods are:
+ * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+ * and `sortBy`
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @see _.reduceRight
+ * @example
+ *
+ * _.reduce([1, 2], function(sum, n) {
+ * return sum + n;
+ * }, 0);
+ * // => 3
+ *
+ * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * return result;
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+ */
- if (code != null && code !== token.text) {
- token.text = code;
- token.escaped = true;
- }
+ function reduce(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduce : baseReduce,
+ initAccum = arguments.length < 3;
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+ }
+ /**
+ * This method is like `_.reduce` except that it iterates over elements of
+ * `collection` from right to left.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The initial value.
+ * @returns {*} Returns the accumulated value.
+ * @see _.reduce
+ * @example
+ *
+ * var array = [[0, 1], [2, 3], [4, 5]];
+ *
+ * _.reduceRight(array, function(flattened, other) {
+ * return flattened.concat(other);
+ * }, []);
+ * // => [4, 5, 2, 3, 0, 1]
+ */
- pending--;
- if (pending === 0) {
- done();
- }
- });
- }, 0);
+ function reduceRight(collection, iteratee, accumulator) {
+ var func = isArray(collection) ? arrayReduceRight : baseReduce,
+ initAccum = arguments.length < 3;
+ return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
}
- });
+ /**
+ * The opposite of `_.filter`; this method returns the elements of `collection`
+ * that `predicate` does **not** return truthy for.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {Array} Returns the new filtered array.
+ * @see _.filter
+ * @example
+ *
+ * var users = [
+ * { 'user': 'barney', 'age': 36, 'active': false },
+ * { 'user': 'fred', 'age': 40, 'active': true }
+ * ];
+ *
+ * _.reject(users, function(o) { return !o.active; });
+ * // => objects for ['fred']
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.reject(users, { 'age': 40, 'active': true });
+ * // => objects for ['barney']
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.reject(users, ['active', false]);
+ * // => objects for ['fred']
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.reject(users, 'active');
+ * // => objects for ['barney']
+ */
- if (pending === 0) {
- done();
- }
- return;
- }
+ function reject(collection, predicate) {
+ var func = isArray(collection) ? arrayFilter : baseFilter;
+ return func(collection, negate(getIteratee(predicate, 3)));
+ }
+ /**
+ * Gets a random element from `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @returns {*} Returns the random element.
+ * @example
+ *
+ * _.sample([1, 2, 3, 4]);
+ * // => 2
+ */
- try {
- var _tokens = Lexer_1.lex(src, opt);
- if (opt.walkTokens) {
- marked.walkTokens(_tokens, opt.walkTokens);
- }
+ function sample(collection) {
+ var func = isArray(collection) ? arraySample : baseSample;
+ return func(collection);
+ }
+ /**
+ * Gets `n` random elements at unique keys from `collection` up to the
+ * size of `collection`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to sample.
+ * @param {number} [n=1] The number of elements to sample.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Array} Returns the random elements.
+ * @example
+ *
+ * _.sampleSize([1, 2, 3], 2);
+ * // => [3, 1]
+ *
+ * _.sampleSize([1, 2, 3], 4);
+ * // => [2, 3, 1]
+ */
- return Parser_1.parse(_tokens, opt);
- } catch (e) {
- e.message += '\nPlease report this to https://github.com/markedjs/marked.';
- if (opt.silent) {
- return 'An error occurred:
' + escape$1(e.message + '', true) + '
';
- }
+ function sampleSize(collection, n, guard) {
+ if (guard ? isIterateeCall(collection, n, guard) : n === undefined$1) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
- throw e;
- }
- }
- /**
- * Options
- */
+ var func = isArray(collection) ? arraySampleSize : baseSampleSize;
+ return func(collection, n);
+ }
+ /**
+ * Creates an array of shuffled values, using a version of the
+ * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to shuffle.
+ * @returns {Array} Returns the new shuffled array.
+ * @example
+ *
+ * _.shuffle([1, 2, 3, 4]);
+ * // => [4, 1, 3, 2]
+ */
- marked.options = marked.setOptions = function (opt) {
- merge(marked.defaults, opt);
- changeDefaults(marked.defaults);
- return marked;
- };
+ function shuffle(collection) {
+ var func = isArray(collection) ? arrayShuffle : baseShuffle;
+ return func(collection);
+ }
+ /**
+ * Gets the size of `collection` by returning its length for array-like
+ * values or the number of own enumerable string keyed properties for objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object|string} collection The collection to inspect.
+ * @returns {number} Returns the collection size.
+ * @example
+ *
+ * _.size([1, 2, 3]);
+ * // => 3
+ *
+ * _.size({ 'a': 1, 'b': 2 });
+ * // => 2
+ *
+ * _.size('pebbles');
+ * // => 7
+ */
- marked.getDefaults = getDefaults;
- marked.defaults = defaults;
- /**
- * Use Extension
- */
- marked.use = function (extension) {
- var opts = merge({}, extension);
+ function size(collection) {
+ if (collection == null) {
+ return 0;
+ }
- if (extension.renderer) {
- (function () {
- var renderer = marked.defaults.renderer || new Renderer_1();
+ if (isArrayLike(collection)) {
+ return isString(collection) ? stringSize(collection) : collection.length;
+ }
- var _loop = function _loop(prop) {
- var prevRenderer = renderer[prop];
+ var tag = getTag(collection);
- renderer[prop] = function () {
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
- args[_key] = arguments[_key];
- }
+ if (tag == mapTag || tag == setTag) {
+ return collection.size;
+ }
- var ret = extension.renderer[prop].apply(renderer, args);
+ return baseKeys(collection).length;
+ }
+ /**
+ * Checks if `predicate` returns truthy for **any** element of `collection`.
+ * Iteration is stopped once `predicate` returns truthy. The predicate is
+ * invoked with three arguments: (value, index|key, collection).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
+ * else `false`.
+ * @example
+ *
+ * _.some([null, 0, 'yes', false], Boolean);
+ * // => true
+ *
+ * var users = [
+ * { 'user': 'barney', 'active': true },
+ * { 'user': 'fred', 'active': false }
+ * ];
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.some(users, { 'user': 'barney', 'active': false });
+ * // => false
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.some(users, ['active', false]);
+ * // => true
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.some(users, 'active');
+ * // => true
+ */
- if (ret === false) {
- ret = prevRenderer.apply(renderer, args);
- }
- return ret;
- };
- };
+ function some(collection, predicate, guard) {
+ var func = isArray(collection) ? arraySome : baseSome;
- for (var prop in extension.renderer) {
- _loop(prop);
+ if (guard && isIterateeCall(collection, predicate, guard)) {
+ predicate = undefined$1;
+ }
+
+ return func(collection, getIteratee(predicate, 3));
}
+ /**
+ * Creates an array of elements, sorted in ascending order by the results of
+ * running each element in a collection thru each iteratee. This method
+ * performs a stable sort, that is, it preserves the original sort order of
+ * equal elements. The iteratees are invoked with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Collection
+ * @param {Array|Object} collection The collection to iterate over.
+ * @param {...(Function|Function[])} [iteratees=[_.identity]]
+ * The iteratees to sort by.
+ * @returns {Array} Returns the new sorted array.
+ * @example
+ *
+ * var users = [
+ * { 'user': 'fred', 'age': 48 },
+ * { 'user': 'barney', 'age': 36 },
+ * { 'user': 'fred', 'age': 30 },
+ * { 'user': 'barney', 'age': 34 }
+ * ];
+ *
+ * _.sortBy(users, [function(o) { return o.user; }]);
+ * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]]
+ *
+ * _.sortBy(users, ['user', 'age']);
+ * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]]
+ */
- opts.renderer = renderer;
- })();
- }
- if (extension.tokenizer) {
- (function () {
- var tokenizer = marked.defaults.tokenizer || new Tokenizer_1();
+ var sortBy = baseRest(function (collection, iteratees) {
+ if (collection == null) {
+ return [];
+ }
- var _loop2 = function _loop2(prop) {
- var prevTokenizer = tokenizer[prop];
+ var length = iteratees.length;
- tokenizer[prop] = function () {
- for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
- args[_key2] = arguments[_key2];
- }
+ if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+ iteratees = [];
+ } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+ iteratees = [iteratees[0]];
+ }
- var ret = extension.tokenizer[prop].apply(tokenizer, args);
+ return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+ });
+ /*------------------------------------------------------------------------*/
- if (ret === false) {
- ret = prevTokenizer.apply(tokenizer, args);
- }
+ /**
+ * Gets the timestamp of the number of milliseconds that have elapsed since
+ * the Unix epoch (1 January 1970 00:00:00 UTC).
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Date
+ * @returns {number} Returns the timestamp.
+ * @example
+ *
+ * _.defer(function(stamp) {
+ * console.log(_.now() - stamp);
+ * }, _.now());
+ * // => Logs the number of milliseconds it took for the deferred invocation.
+ */
- return ret;
- };
+ var now = ctxNow || function () {
+ return root.Date.now();
};
+ /*------------------------------------------------------------------------*/
- for (var prop in extension.tokenizer) {
- _loop2(prop);
- }
-
- opts.tokenizer = tokenizer;
- })();
- }
+ /**
+ * The opposite of `_.before`; this method creates a function that invokes
+ * `func` once it's called `n` or more times.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {number} n The number of calls before `func` is invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var saves = ['profile', 'settings'];
+ *
+ * var done = _.after(saves.length, function() {
+ * console.log('done saving!');
+ * });
+ *
+ * _.forEach(saves, function(type) {
+ * asyncSave({ 'type': type, 'complete': done });
+ * });
+ * // => Logs 'done saving!' after the two async saves have completed.
+ */
- if (extension.walkTokens) {
- var walkTokens = marked.defaults.walkTokens;
- opts.walkTokens = function (token) {
- extension.walkTokens(token);
+ function after(n, func) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- if (walkTokens) {
- walkTokens(token);
- }
- };
- }
+ n = toInteger(n);
+ return function () {
+ if (--n < 1) {
+ return func.apply(this, arguments);
+ }
+ };
+ }
+ /**
+ * Creates a function that invokes `func`, with up to `n` arguments,
+ * ignoring any additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @param {number} [n=func.length] The arity cap.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new capped function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+ * // => [6, 8, 10]
+ */
- marked.setOptions(opts);
- };
- /**
- * Run callback for every token
- */
+ function ary(func, n, guard) {
+ n = guard ? undefined$1 : n;
+ n = func && n == null ? func.length : n;
+ return createWrap(func, WRAP_ARY_FLAG, undefined$1, undefined$1, undefined$1, undefined$1, n);
+ }
+ /**
+ * Creates a function that invokes `func`, with the `this` binding and arguments
+ * of the created function, while it's called less than `n` times. Subsequent
+ * calls to the created function return the result of the last `func` invocation.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {number} n The number of calls at which `func` is no longer invoked.
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * jQuery(element).on('click', _.before(5, addContactToList));
+ * // => Allows adding up to 4 contacts to the list.
+ */
- marked.walkTokens = function (tokens, callback) {
- var _iterator = _createForOfIteratorHelper(tokens),
- _step;
- try {
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
- var token = _step.value;
- callback(token);
+ function before(n, func) {
+ var result;
- switch (token.type) {
- case 'table':
- {
- var _iterator2 = _createForOfIteratorHelper(token.tokens.header),
- _step2;
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- try {
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
- var cell = _step2.value;
- marked.walkTokens(cell, callback);
- }
- } catch (err) {
- _iterator2.e(err);
- } finally {
- _iterator2.f();
- }
+ n = toInteger(n);
+ return function () {
+ if (--n > 0) {
+ result = func.apply(this, arguments);
+ }
- var _iterator3 = _createForOfIteratorHelper(token.tokens.cells),
- _step3;
+ if (n <= 1) {
+ func = undefined$1;
+ }
- try {
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
- var row = _step3.value;
+ return result;
+ };
+ }
+ /**
+ * Creates a function that invokes `func` with the `this` binding of `thisArg`
+ * and `partials` prepended to the arguments it receives.
+ *
+ * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
+ * property of bound functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to bind.
+ * @param {*} thisArg The `this` binding of `func`.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * function greet(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * }
+ *
+ * var object = { 'user': 'fred' };
+ *
+ * var bound = _.bind(greet, object, 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bind(greet, object, _, '!');
+ * bound('hi');
+ * // => 'hi fred!'
+ */
- var _iterator4 = _createForOfIteratorHelper(row),
- _step4;
- try {
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
- var _cell = _step4.value;
- marked.walkTokens(_cell, callback);
- }
- } catch (err) {
- _iterator4.e(err);
- } finally {
- _iterator4.f();
- }
- }
- } catch (err) {
- _iterator3.e(err);
- } finally {
- _iterator3.f();
- }
+ var bind = baseRest(function (func, thisArg, partials) {
+ var bitmask = WRAP_BIND_FLAG;
- break;
- }
+ if (partials.length) {
+ var holders = replaceHolders(partials, getHolder(bind));
+ bitmask |= WRAP_PARTIAL_FLAG;
+ }
- case 'list':
- {
- marked.walkTokens(token.items, callback);
- break;
- }
+ return createWrap(func, bitmask, thisArg, partials, holders);
+ });
+ /**
+ * Creates a function that invokes the method at `object[key]` with `partials`
+ * prepended to the arguments it receives.
+ *
+ * This method differs from `_.bind` by allowing bound functions to reference
+ * methods that may be redefined or don't yet exist. See
+ * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+ * for more details.
+ *
+ * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.10.0
+ * @category Function
+ * @param {Object} object The object to invoke the method on.
+ * @param {string} key The key of the method.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new bound function.
+ * @example
+ *
+ * var object = {
+ * 'user': 'fred',
+ * 'greet': function(greeting, punctuation) {
+ * return greeting + ' ' + this.user + punctuation;
+ * }
+ * };
+ *
+ * var bound = _.bindKey(object, 'greet', 'hi');
+ * bound('!');
+ * // => 'hi fred!'
+ *
+ * object.greet = function(greeting, punctuation) {
+ * return greeting + 'ya ' + this.user + punctuation;
+ * };
+ *
+ * bound('!');
+ * // => 'hiya fred!'
+ *
+ * // Bound with placeholders.
+ * var bound = _.bindKey(object, 'greet', _, '!');
+ * bound('hi');
+ * // => 'hiya fred!'
+ */
- default:
- {
- if (token.tokens) {
- marked.walkTokens(token.tokens, callback);
- }
- }
+ var bindKey = baseRest(function (object, key, partials) {
+ var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
+
+ if (partials.length) {
+ var holders = replaceHolders(partials, getHolder(bindKey));
+ bitmask |= WRAP_PARTIAL_FLAG;
+ }
+
+ return createWrap(key, bitmask, object, partials, holders);
+ });
+ /**
+ * Creates a function that accepts arguments of `func` and either invokes
+ * `func` returning its result, if at least `arity` number of arguments have
+ * been provided, or returns a function that accepts the remaining `func`
+ * arguments, and so on. The arity of `func` may be specified if `func.length`
+ * is not sufficient.
+ *
+ * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+ * may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curry(abc);
+ *
+ * curried(1)(2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2)(3);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(1)(_, 3)(2);
+ * // => [1, 2, 3]
+ */
+
+ function curry(func, arity, guard) {
+ arity = guard ? undefined$1 : arity;
+ var result = createWrap(func, WRAP_CURRY_FLAG, undefined$1, undefined$1, undefined$1, undefined$1, undefined$1, arity);
+ result.placeholder = curry.placeholder;
+ return result;
}
- }
- } catch (err) {
- _iterator.e(err);
- } finally {
- _iterator.f();
- }
- };
- /**
- * Parse Inline
- */
+ /**
+ * This method is like `_.curry` except that arguments are applied to `func`
+ * in the manner of `_.partialRight` instead of `_.partial`.
+ *
+ * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for provided arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of curried functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to curry.
+ * @param {number} [arity=func.length] The arity of `func`.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the new curried function.
+ * @example
+ *
+ * var abc = function(a, b, c) {
+ * return [a, b, c];
+ * };
+ *
+ * var curried = _.curryRight(abc);
+ *
+ * curried(3)(2)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(2, 3)(1);
+ * // => [1, 2, 3]
+ *
+ * curried(1, 2, 3);
+ * // => [1, 2, 3]
+ *
+ * // Curried with placeholders.
+ * curried(3)(1, _)(2);
+ * // => [1, 2, 3]
+ */
- marked.parseInline = function (src, opt) {
- // throw error in case of non string input
- if (typeof src === 'undefined' || src === null) {
- throw new Error('marked.parseInline(): input parameter is undefined or null');
- }
+ function curryRight(func, arity, guard) {
+ arity = guard ? undefined$1 : arity;
+ var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined$1, undefined$1, undefined$1, undefined$1, undefined$1, arity);
+ result.placeholder = curryRight.placeholder;
+ return result;
+ }
+ /**
+ * Creates a debounced function that delays invoking `func` until after `wait`
+ * milliseconds have elapsed since the last time the debounced function was
+ * invoked. The debounced function comes with a `cancel` method to cancel
+ * delayed `func` invocations and a `flush` method to immediately invoke them.
+ * Provide `options` to indicate whether `func` should be invoked on the
+ * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+ * with the last arguments provided to the debounced function. Subsequent
+ * calls to the debounced function return the result of the last `func`
+ * invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the debounced function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.debounce` and `_.throttle`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to debounce.
+ * @param {number} [wait=0] The number of milliseconds to delay.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=false]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {number} [options.maxWait]
+ * The maximum time `func` is allowed to be delayed before it's invoked.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new debounced function.
+ * @example
+ *
+ * // Avoid costly calculations while the window size is in flux.
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+ *
+ * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+ * jQuery(element).on('click', _.debounce(sendMail, 300, {
+ * 'leading': true,
+ * 'trailing': false
+ * }));
+ *
+ * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+ * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+ * var source = new EventSource('/stream');
+ * jQuery(source).on('message', debounced);
+ *
+ * // Cancel the trailing debounced invocation.
+ * jQuery(window).on('popstate', debounced.cancel);
+ */
- if (typeof src !== 'string') {
- throw new Error('marked.parseInline(): input parameter is of type ' + Object.prototype.toString.call(src) + ', string expected');
- }
- opt = merge({}, marked.defaults, opt || {});
- checkSanitizeDeprecation(opt);
+ function debounce(func, wait, options) {
+ var lastArgs,
+ lastThis,
+ maxWait,
+ result,
+ timerId,
+ lastCallTime,
+ lastInvokeTime = 0,
+ leading = false,
+ maxing = false,
+ trailing = true;
- try {
- var tokens = Lexer_1.lexInline(src, opt);
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- if (opt.walkTokens) {
- marked.walkTokens(tokens, opt.walkTokens);
- }
+ wait = toNumber(wait) || 0;
- return Parser_1.parseInline(tokens, opt);
- } catch (e) {
- e.message += '\nPlease report this to https://github.com/markedjs/marked.';
+ if (isObject(options)) {
+ leading = !!options.leading;
+ maxing = 'maxWait' in options;
+ maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
- if (opt.silent) {
- return 'An error occurred:
' + escape$1(e.message + '', true) + '
';
- }
+ function invokeFunc(time) {
+ var args = lastArgs,
+ thisArg = lastThis;
+ lastArgs = lastThis = undefined$1;
+ lastInvokeTime = time;
+ result = func.apply(thisArg, args);
+ return result;
+ }
- throw e;
- }
- };
- /**
- * Expose
- */
+ function leadingEdge(time) {
+ // Reset any `maxWait` timer.
+ lastInvokeTime = time; // Start the timer for the trailing edge.
+ timerId = setTimeout(timerExpired, wait); // Invoke the leading edge.
- marked.Parser = Parser_1;
- marked.parser = Parser_1.parse;
- marked.Renderer = Renderer_1;
- marked.TextRenderer = TextRenderer_1;
- marked.Lexer = Lexer_1;
- marked.lexer = Lexer_1.lex;
- marked.Tokenizer = Tokenizer_1;
- marked.Slugger = Slugger_1;
- marked.parse = marked;
- var marked_1 = marked;
+ return leading ? invokeFunc(time) : result;
+ }
- var tiler$4 = utilTiler();
- var dispatch$5 = dispatch$8('loaded');
- var _tileZoom$1 = 14;
- var _osmoseUrlRoot = 'https://osmose.openstreetmap.fr/api/0.3';
- var _osmoseData = {
- icons: {},
- items: []
- }; // This gets reassigned if reset
+ function remainingWait(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime,
+ timeWaiting = wait - timeSinceLastCall;
+ return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting;
+ }
- var _cache;
+ function shouldInvoke(time) {
+ var timeSinceLastCall = time - lastCallTime,
+ timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we're at the
+ // trailing edge, the system time has gone backwards and we're treating
+ // it as the trailing edge, or we've hit the `maxWait` limit.
- function abortRequest$4(controller) {
- if (controller) {
- controller.abort();
- }
- }
+ return lastCallTime === undefined$1 || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
+ }
- function abortUnwantedRequests$1(cache, tiles) {
- Object.keys(cache.inflightTile).forEach(function (k) {
- var wanted = tiles.find(function (tile) {
- return k === tile.id;
- });
+ function timerExpired() {
+ var time = now();
- if (!wanted) {
- abortRequest$4(cache.inflightTile[k]);
- delete cache.inflightTile[k];
- }
- });
- }
+ if (shouldInvoke(time)) {
+ return trailingEdge(time);
+ } // Restart the timer.
- function encodeIssueRtree(d) {
- return {
- minX: d.loc[0],
- minY: d.loc[1],
- maxX: d.loc[0],
- maxY: d.loc[1],
- data: d
- };
- } // Replace or remove QAItem from rtree
+ timerId = setTimeout(timerExpired, remainingWait(time));
+ }
- function updateRtree$1(item, replace) {
- _cache.rtree.remove(item, function (a, b) {
- return a.data.id === b.data.id;
- });
+ function trailingEdge(time) {
+ timerId = undefined$1; // Only invoke if we have `lastArgs` which means `func` has been
+ // debounced at least once.
- if (replace) {
- _cache.rtree.insert(item);
- }
- } // Issues shouldn't obscure each other
+ if (trailing && lastArgs) {
+ return invokeFunc(time);
+ }
+ lastArgs = lastThis = undefined$1;
+ return result;
+ }
- function preventCoincident(loc) {
- var coincident = false;
+ function cancel() {
+ if (timerId !== undefined$1) {
+ clearTimeout(timerId);
+ }
- do {
- // first time, move marker up. after that, move marker right.
- var delta = coincident ? [0.00001, 0] : [0, 0.00001];
- loc = geoVecAdd(loc, delta);
- var bbox = geoExtent(loc).bbox();
- coincident = _cache.rtree.search(bbox).length;
- } while (coincident);
+ lastInvokeTime = 0;
+ lastArgs = lastCallTime = lastThis = timerId = undefined$1;
+ }
- return loc;
- }
+ function flush() {
+ return timerId === undefined$1 ? result : trailingEdge(now());
+ }
- var serviceOsmose = {
- title: 'osmose',
- init: function init() {
- _mainFileFetcher.get('qa_data').then(function (d) {
- _osmoseData = d.osmose;
- _osmoseData.items = Object.keys(d.osmose.icons).map(function (s) {
- return s.split('-')[0];
- }).reduce(function (unique, item) {
- return unique.indexOf(item) !== -1 ? unique : [].concat(_toConsumableArray(unique), [item]);
- }, []);
- });
+ function debounced() {
+ var time = now(),
+ isInvoking = shouldInvoke(time);
+ lastArgs = arguments;
+ lastThis = this;
+ lastCallTime = time;
- if (!_cache) {
- this.reset();
- }
+ if (isInvoking) {
+ if (timerId === undefined$1) {
+ return leadingEdge(lastCallTime);
+ }
- this.event = utilRebind(this, dispatch$5, 'on');
- },
- reset: function reset() {
- var _strings = {};
- var _colors = {};
+ if (maxing) {
+ // Handle invocations in a tight loop.
+ clearTimeout(timerId);
+ timerId = setTimeout(timerExpired, wait);
+ return invokeFunc(lastCallTime);
+ }
+ }
- if (_cache) {
- Object.values(_cache.inflightTile).forEach(abortRequest$4); // Strings and colors are static and should not be re-populated
+ if (timerId === undefined$1) {
+ timerId = setTimeout(timerExpired, wait);
+ }
- _strings = _cache.strings;
- _colors = _cache.colors;
- }
+ return result;
+ }
- _cache = {
- data: {},
- loadedTile: {},
- inflightTile: {},
- inflightPost: {},
- closed: {},
- rtree: new RBush(),
- strings: _strings,
- colors: _colors
- };
- },
- loadIssues: function loadIssues(projection) {
- var _this = this;
+ debounced.cancel = cancel;
+ debounced.flush = flush;
+ return debounced;
+ }
+ /**
+ * Defers invoking the `func` until the current call stack has cleared. Any
+ * additional arguments are provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to defer.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.defer(function(text) {
+ * console.log(text);
+ * }, 'deferred');
+ * // => Logs 'deferred' after one millisecond.
+ */
- var params = {
- // Tiles return a maximum # of issues
- // So we want to filter our request for only types iD supports
- item: _osmoseData.items
- }; // determine the needed tiles to cover the view
- var tiles = tiler$4.zoomExtent([_tileZoom$1, _tileZoom$1]).getTiles(projection); // abort inflight requests that are no longer needed
+ var defer = baseRest(function (func, args) {
+ return baseDelay(func, 1, args);
+ });
+ /**
+ * Invokes `func` after `wait` milliseconds. Any additional arguments are
+ * provided to `func` when it's invoked.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to delay.
+ * @param {number} wait The number of milliseconds to delay invocation.
+ * @param {...*} [args] The arguments to invoke `func` with.
+ * @returns {number} Returns the timer id.
+ * @example
+ *
+ * _.delay(function(text) {
+ * console.log(text);
+ * }, 1000, 'later');
+ * // => Logs 'later' after one second.
+ */
- abortUnwantedRequests$1(_cache, tiles); // issue new requests..
+ var delay = baseRest(function (func, wait, args) {
+ return baseDelay(func, toNumber(wait) || 0, args);
+ });
+ /**
+ * Creates a function that invokes `func` with arguments reversed.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to flip arguments for.
+ * @returns {Function} Returns the new flipped function.
+ * @example
+ *
+ * var flipped = _.flip(function() {
+ * return _.toArray(arguments);
+ * });
+ *
+ * flipped('a', 'b', 'c', 'd');
+ * // => ['d', 'c', 'b', 'a']
+ */
- tiles.forEach(function (tile) {
- if (_cache.loadedTile[tile.id] || _cache.inflightTile[tile.id]) return;
+ function flip(func) {
+ return createWrap(func, WRAP_FLIP_FLAG);
+ }
+ /**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided, it determines the cache key for storing the result based on the
+ * arguments provided to the memoized function. By default, the first argument
+ * provided to the memoized function is used as the map cache key. The `func`
+ * is invoked with the `this` binding of the memoized function.
+ *
+ * **Note:** The cache is exposed as the `cache` property on the memoized
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
+ * constructor with one whose instances implement the
+ * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+ * method interface of `clear`, `delete`, `get`, `has`, and `set`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] The function to resolve the cache key.
+ * @returns {Function} Returns the new memoized function.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ * var other = { 'c': 3, 'd': 4 };
+ *
+ * var values = _.memoize(_.values);
+ * values(object);
+ * // => [1, 2]
+ *
+ * values(other);
+ * // => [3, 4]
+ *
+ * object.a = 2;
+ * values(object);
+ * // => [1, 2]
+ *
+ * // Modify the result cache.
+ * values.cache.set(object, ['a', 'b']);
+ * values(object);
+ * // => ['a', 'b']
+ *
+ * // Replace `_.memoize.Cache`.
+ * _.memoize.Cache = WeakMap;
+ */
- var _tile$xyz = _slicedToArray(tile.xyz, 3),
- x = _tile$xyz[0],
- y = _tile$xyz[1],
- z = _tile$xyz[2];
- var url = "".concat(_osmoseUrlRoot, "/issues/").concat(z, "/").concat(x, "/").concat(y, ".json?") + utilQsString(params);
- var controller = new AbortController();
- _cache.inflightTile[tile.id] = controller;
- d3_json(url, {
- signal: controller.signal
- }).then(function (data) {
- delete _cache.inflightTile[tile.id];
- _cache.loadedTile[tile.id] = true;
+ function memoize(func, resolver) {
+ if (typeof func != 'function' || resolver != null && typeof resolver != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- if (data.features) {
- data.features.forEach(function (issue) {
- var _issue$properties = issue.properties,
- item = _issue$properties.item,
- cl = _issue$properties["class"],
- id = _issue$properties.uuid;
- /* Osmose issues are uniquely identified by a unique
- `item` and `class` combination (both integer values) */
+ var memoized = function memoized() {
+ var args = arguments,
+ key = resolver ? resolver.apply(this, args) : args[0],
+ cache = memoized.cache;
- var itemType = "".concat(item, "-").concat(cl); // Filter out unsupported issue types (some are too specific or advanced)
+ if (cache.has(key)) {
+ return cache.get(key);
+ }
- if (itemType in _osmoseData.icons) {
- var loc = issue.geometry.coordinates; // lon, lat
+ var result = func.apply(this, args);
+ memoized.cache = cache.set(key, result) || cache;
+ return result;
+ };
- loc = preventCoincident(loc);
- var d = new QAItem(loc, _this, itemType, id, {
- item: item
- }); // Setting elems here prevents UI detail requests
+ memoized.cache = new (memoize.Cache || MapCache)();
+ return memoized;
+ } // Expose `MapCache`.
- if (item === 8300 || item === 8360) {
- d.elems = [];
- }
- _cache.data[d.id] = d;
+ memoize.Cache = MapCache;
+ /**
+ * Creates a function that negates the result of the predicate `func`. The
+ * `func` predicate is invoked with the `this` binding and arguments of the
+ * created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} predicate The predicate to negate.
+ * @returns {Function} Returns the new negated function.
+ * @example
+ *
+ * function isEven(n) {
+ * return n % 2 == 0;
+ * }
+ *
+ * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+ * // => [1, 3, 5]
+ */
- _cache.rtree.insert(encodeIssueRtree(d));
- }
- });
+ function negate(predicate) {
+ if (typeof predicate != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
}
- dispatch$5.call('loaded');
- })["catch"](function () {
- delete _cache.inflightTile[tile.id];
- _cache.loadedTile[tile.id] = true;
- });
- });
- },
- loadIssueDetail: function loadIssueDetail(issue) {
- var _this2 = this;
+ return function () {
+ var args = arguments;
- // Issue details only need to be fetched once
- if (issue.elems !== undefined) {
- return Promise.resolve(issue);
- }
+ switch (args.length) {
+ case 0:
+ return !predicate.call(this);
- var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "?langs=").concat(_mainLocalizer.localeCode());
+ case 1:
+ return !predicate.call(this, args[0]);
- var cacheDetails = function cacheDetails(data) {
- // Associated elements used for highlighting
- // Assign directly for immediate use in the callback
- issue.elems = data.elems.map(function (e) {
- return e.type.substring(0, 1) + e.id;
- }); // Some issues have instance specific detail in a subtitle
+ case 2:
+ return !predicate.call(this, args[0], args[1]);
- issue.detail = data.subtitle ? marked_1(data.subtitle.auto) : '';
+ case 3:
+ return !predicate.call(this, args[0], args[1], args[2]);
+ }
- _this2.replaceItem(issue);
- };
+ return !predicate.apply(this, args);
+ };
+ }
+ /**
+ * Creates a function that is restricted to invoking `func` once. Repeat calls
+ * to the function return the value of the first invocation. The `func` is
+ * invoked with the `this` binding and arguments of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to restrict.
+ * @returns {Function} Returns the new restricted function.
+ * @example
+ *
+ * var initialize = _.once(createApplication);
+ * initialize();
+ * initialize();
+ * // => `createApplication` is invoked once
+ */
- return d3_json(url).then(cacheDetails).then(function () {
- return issue;
- });
- },
- loadStrings: function loadStrings() {
- var locale = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _mainLocalizer.localeCode();
- var items = Object.keys(_osmoseData.icons);
- if (locale in _cache.strings && Object.keys(_cache.strings[locale]).length === items.length) {
- return Promise.resolve(_cache.strings[locale]);
- } // May be partially populated already if some requests were successful
+ function once(func) {
+ return before(2, func);
+ }
+ /**
+ * Creates a function that invokes `func` with its arguments transformed.
+ *
+ * @static
+ * @since 4.0.0
+ * @memberOf _
+ * @category Function
+ * @param {Function} func The function to wrap.
+ * @param {...(Function|Function[])} [transforms=[_.identity]]
+ * The argument transforms.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * function doubled(n) {
+ * return n * 2;
+ * }
+ *
+ * function square(n) {
+ * return n * n;
+ * }
+ *
+ * var func = _.overArgs(function(x, y) {
+ * return [x, y];
+ * }, [square, doubled]);
+ *
+ * func(9, 3);
+ * // => [81, 6]
+ *
+ * func(10, 5);
+ * // => [100, 10]
+ */
- if (!(locale in _cache.strings)) {
- _cache.strings[locale] = {};
- } // Only need to cache strings for supported issue types
- // Using multiple individual item + class requests to reduce fetched data size
+ var overArgs = castRest(function (func, transforms) {
+ transforms = transforms.length == 1 && isArray(transforms[0]) ? arrayMap(transforms[0], baseUnary(getIteratee())) : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
+ var funcsLength = transforms.length;
+ return baseRest(function (args) {
+ var index = -1,
+ length = nativeMin(args.length, funcsLength);
+ while (++index < length) {
+ args[index] = transforms[index].call(this, args[index]);
+ }
- var allRequests = items.map(function (itemType) {
- // No need to request data we already have
- if (itemType in _cache.strings[locale]) return null;
+ return apply(func, this, args);
+ });
+ });
+ /**
+ * Creates a function that invokes `func` with `partials` prepended to the
+ * arguments it receives. This method is like `_.bind` except it does **not**
+ * alter the `this` binding.
+ *
+ * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.2.0
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * function greet(greeting, name) {
+ * return greeting + ' ' + name;
+ * }
+ *
+ * var sayHelloTo = _.partial(greet, 'hello');
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ *
+ * // Partially applied with placeholders.
+ * var greetFred = _.partial(greet, _, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ */
- var cacheData = function cacheData(data) {
- // Bunch of nested single value arrays of objects
- var _data$categories = _slicedToArray(data.categories, 1),
- _data$categories$ = _data$categories[0],
- cat = _data$categories$ === void 0 ? {
- items: []
- } : _data$categories$;
+ var partial = baseRest(function (func, partials) {
+ var holders = replaceHolders(partials, getHolder(partial));
+ return createWrap(func, WRAP_PARTIAL_FLAG, undefined$1, partials, holders);
+ });
+ /**
+ * This method is like `_.partial` except that partially applied arguments
+ * are appended to the arguments it receives.
+ *
+ * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+ * builds, may be used as a placeholder for partially applied arguments.
+ *
+ * **Note:** This method doesn't set the "length" property of partially
+ * applied functions.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Function
+ * @param {Function} func The function to partially apply arguments to.
+ * @param {...*} [partials] The arguments to be partially applied.
+ * @returns {Function} Returns the new partially applied function.
+ * @example
+ *
+ * function greet(greeting, name) {
+ * return greeting + ' ' + name;
+ * }
+ *
+ * var greetFred = _.partialRight(greet, 'fred');
+ * greetFred('hi');
+ * // => 'hi fred'
+ *
+ * // Partially applied with placeholders.
+ * var sayHelloTo = _.partialRight(greet, 'hello', _);
+ * sayHelloTo('fred');
+ * // => 'hello fred'
+ */
- var _cat$items = _slicedToArray(cat.items, 1),
- _cat$items$ = _cat$items[0],
- item = _cat$items$ === void 0 ? {
- "class": []
- } : _cat$items$;
+ var partialRight = baseRest(function (func, partials) {
+ var holders = replaceHolders(partials, getHolder(partialRight));
+ return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined$1, partials, holders);
+ });
+ /**
+ * Creates a function that invokes `func` with arguments arranged according
+ * to the specified `indexes` where the argument value at the first index is
+ * provided as the first argument, the argument value at the second index is
+ * provided as the second argument, and so on.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Function
+ * @param {Function} func The function to rearrange arguments for.
+ * @param {...(number|number[])} indexes The arranged argument indexes.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var rearged = _.rearg(function(a, b, c) {
+ * return [a, b, c];
+ * }, [2, 0, 1]);
+ *
+ * rearged('b', 'c', 'a')
+ * // => ['a', 'b', 'c']
+ */
- var _item$class = _slicedToArray(item["class"], 1),
- _item$class$ = _item$class[0],
- cl = _item$class$ === void 0 ? null : _item$class$; // If null default value is reached, data wasn't as expected (or was empty)
+ var rearg = flatRest(function (func, indexes) {
+ return createWrap(func, WRAP_REARG_FLAG, undefined$1, undefined$1, undefined$1, indexes);
+ });
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * created function and arguments from `start` and beyond provided as
+ * an array.
+ *
+ * **Note:** This method is based on the
+ * [rest parameter](https://mdn.io/rest_parameters).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to apply a rest parameter to.
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.rest(function(what, names) {
+ * return what + ' ' + _.initial(names).join(', ') +
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+ * });
+ *
+ * say('hello', 'fred', 'barney', 'pebbles');
+ * // => 'hello fred, barney, & pebbles'
+ */
+ function rest(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- if (!cl) {
- /* eslint-disable no-console */
- console.log("Osmose strings request (".concat(itemType, ") had unexpected data"));
- /* eslint-enable no-console */
+ start = start === undefined$1 ? start : toInteger(start);
+ return baseRest(func, start);
+ }
+ /**
+ * Creates a function that invokes `func` with the `this` binding of the
+ * create function and an array of arguments much like
+ * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
+ *
+ * **Note:** This method is based on the
+ * [spread operator](https://mdn.io/spread_operator).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.2.0
+ * @category Function
+ * @param {Function} func The function to spread arguments over.
+ * @param {number} [start=0] The start position of the spread.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var say = _.spread(function(who, what) {
+ * return who + ' says ' + what;
+ * });
+ *
+ * say(['fred', 'hello']);
+ * // => 'fred says hello'
+ *
+ * var numbers = Promise.all([
+ * Promise.resolve(40),
+ * Promise.resolve(36)
+ * ]);
+ *
+ * numbers.then(_.spread(function(x, y) {
+ * return x + y;
+ * }));
+ * // => a Promise of 76
+ */
- return;
- } // Cache served item colors to automatically style issue markers later
+ function spread(func, start) {
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
- var itemInt = item.item,
- color = item.color;
+ start = start == null ? 0 : nativeMax(toInteger(start), 0);
+ return baseRest(function (args) {
+ var array = args[start],
+ otherArgs = castSlice(args, 0, start);
- if (/^#[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}/.test(color)) {
- _cache.colors[itemInt] = color;
- } // Value of root key will be null if no string exists
- // If string exists, value is an object with key 'auto' for string
+ if (array) {
+ arrayPush(otherArgs, array);
+ }
+ return apply(func, this, otherArgs);
+ });
+ }
+ /**
+ * Creates a throttled function that only invokes `func` at most once per
+ * every `wait` milliseconds. The throttled function comes with a `cancel`
+ * method to cancel delayed `func` invocations and a `flush` method to
+ * immediately invoke them. Provide `options` to indicate whether `func`
+ * should be invoked on the leading and/or trailing edge of the `wait`
+ * timeout. The `func` is invoked with the last arguments provided to the
+ * throttled function. Subsequent calls to the throttled function return the
+ * result of the last `func` invocation.
+ *
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is
+ * invoked on the trailing edge of the timeout only if the throttled function
+ * is invoked more than once during the `wait` timeout.
+ *
+ * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+ * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+ *
+ * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+ * for details over the differences between `_.throttle` and `_.debounce`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to throttle.
+ * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+ * @param {Object} [options={}] The options object.
+ * @param {boolean} [options.leading=true]
+ * Specify invoking on the leading edge of the timeout.
+ * @param {boolean} [options.trailing=true]
+ * Specify invoking on the trailing edge of the timeout.
+ * @returns {Function} Returns the new throttled function.
+ * @example
+ *
+ * // Avoid excessively updating the position while scrolling.
+ * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+ *
+ * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+ * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+ * jQuery(element).on('click', throttled);
+ *
+ * // Cancel the trailing throttled invocation.
+ * jQuery(window).on('popstate', throttled.cancel);
+ */
- var title = cl.title,
- detail = cl.detail,
- fix = cl.fix,
- trap = cl.trap; // Osmose titles shouldn't contain markdown
- var issueStrings = {};
- if (title) issueStrings.title = title.auto;
- if (detail) issueStrings.detail = marked_1(detail.auto);
- if (trap) issueStrings.trap = marked_1(trap.auto);
- if (fix) issueStrings.fix = marked_1(fix.auto);
- _cache.strings[locale][itemType] = issueStrings;
- };
+ function throttle(func, wait, options) {
+ var leading = true,
+ trailing = true;
- var _itemType$split = itemType.split('-'),
- _itemType$split2 = _slicedToArray(_itemType$split, 2),
- item = _itemType$split2[0],
- cl = _itemType$split2[1]; // Osmose API falls back to English strings where untranslated or if locale doesn't exist
+ if (typeof func != 'function') {
+ throw new TypeError(FUNC_ERROR_TEXT);
+ }
+ if (isObject(options)) {
+ leading = 'leading' in options ? !!options.leading : leading;
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
+ }
- var url = "".concat(_osmoseUrlRoot, "/items/").concat(item, "/class/").concat(cl, "?langs=").concat(locale);
- return d3_json(url).then(cacheData);
- }).filter(Boolean);
- return Promise.all(allRequests).then(function () {
- return _cache.strings[locale];
- });
- },
- getStrings: function getStrings(itemType) {
- var locale = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _mainLocalizer.localeCode();
- // No need to fallback to English, Osmose API handles this for us
- return locale in _cache.strings ? _cache.strings[locale][itemType] : {};
- },
- getColor: function getColor(itemType) {
- return itemType in _cache.colors ? _cache.colors[itemType] : '#FFFFFF';
- },
- postUpdate: function postUpdate(issue, callback) {
- var _this3 = this;
+ return debounce(func, wait, {
+ 'leading': leading,
+ 'maxWait': wait,
+ 'trailing': trailing
+ });
+ }
+ /**
+ * Creates a function that accepts up to one argument, ignoring any
+ * additional arguments.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Function
+ * @param {Function} func The function to cap arguments for.
+ * @returns {Function} Returns the new capped function.
+ * @example
+ *
+ * _.map(['6', '8', '10'], _.unary(parseInt));
+ * // => [6, 8, 10]
+ */
- if (_cache.inflightPost[issue.id]) {
- return callback({
- message: 'Issue update already inflight',
- status: -2
- }, issue);
- } // UI sets the status to either 'done' or 'false'
+
+ function unary(func) {
+ return ary(func, 1);
+ }
+ /**
+ * Creates a function that provides `value` to `wrapper` as its first
+ * argument. Any additional arguments provided to the function are appended
+ * to those provided to the `wrapper`. The wrapper is invoked with the `this`
+ * binding of the created function.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {*} value The value to wrap.
+ * @param {Function} [wrapper=identity] The wrapper function.
+ * @returns {Function} Returns the new function.
+ * @example
+ *
+ * var p = _.wrap(_.escape, function(func, text) {
+ * return '' + func(text) + '
';
+ * });
+ *
+ * p('fred, barney, & pebbles');
+ * // => 'fred, barney, & pebbles
'
+ */
- var url = "".concat(_osmoseUrlRoot, "/issue/").concat(issue.id, "/").concat(issue.newStatus);
- var controller = new AbortController();
+ function wrap(value, wrapper) {
+ return partial(castFunction(wrapper), value);
+ }
+ /*------------------------------------------------------------------------*/
- var after = function after() {
- delete _cache.inflightPost[issue.id];
+ /**
+ * Casts `value` as an array if it's not one.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.4.0
+ * @category Lang
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast array.
+ * @example
+ *
+ * _.castArray(1);
+ * // => [1]
+ *
+ * _.castArray({ 'a': 1 });
+ * // => [{ 'a': 1 }]
+ *
+ * _.castArray('abc');
+ * // => ['abc']
+ *
+ * _.castArray(null);
+ * // => [null]
+ *
+ * _.castArray(undefined);
+ * // => [undefined]
+ *
+ * _.castArray();
+ * // => []
+ *
+ * var array = [1, 2, 3];
+ * console.log(_.castArray(array) === array);
+ * // => true
+ */
- _this3.removeItem(issue);
- if (issue.newStatus === 'done') {
- // Keep track of the number of issues closed per `item` to tag the changeset
- if (!(issue.item in _cache.closed)) {
- _cache.closed[issue.item] = 0;
+ function castArray() {
+ if (!arguments.length) {
+ return [];
}
- _cache.closed[issue.item] += 1;
+ var value = arguments[0];
+ return isArray(value) ? value : [value];
}
+ /**
+ * Creates a shallow clone of `value`.
+ *
+ * **Note:** This method is loosely based on the
+ * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+ * and supports cloning arrays, array buffers, booleans, date objects, maps,
+ * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+ * arrays. The own enumerable properties of `arguments` objects are cloned
+ * as plain objects. An empty object is returned for uncloneable values such
+ * as error objects, functions, DOM nodes, and WeakMaps.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @returns {*} Returns the cloned value.
+ * @see _.cloneDeep
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var shallow = _.clone(objects);
+ * console.log(shallow[0] === objects[0]);
+ * // => true
+ */
- if (callback) callback(null, issue);
- };
-
- _cache.inflightPost[issue.id] = controller;
- fetch(url, {
- signal: controller.signal
- }).then(after)["catch"](function (err) {
- delete _cache.inflightPost[issue.id];
- if (callback) callback(err.message);
- });
- },
- // Get all cached QAItems covering the viewport
- getItems: function getItems(projection) {
- var viewport = projection.clipExtent();
- var min = [viewport[0][0], viewport[1][1]];
- var max = [viewport[1][0], viewport[0][1]];
- var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
- return _cache.rtree.search(bbox).map(function (d) {
- return d.data;
- });
- },
- // Get a QAItem from cache
- // NOTE: Don't change method name until UI v3 is merged
- getError: function getError(id) {
- return _cache.data[id];
- },
- // get the name of the icon to display for this item
- getIcon: function getIcon(itemType) {
- return _osmoseData.icons[itemType];
- },
- // Replace a single QAItem in the cache
- replaceItem: function replaceItem(item) {
- if (!(item instanceof QAItem) || !item.id) return;
- _cache.data[item.id] = item;
- updateRtree$1(encodeIssueRtree(item), true); // true = replace
- return item;
- },
- // Remove a single QAItem from the cache
- removeItem: function removeItem(item) {
- if (!(item instanceof QAItem) || !item.id) return;
- delete _cache.data[item.id];
- updateRtree$1(encodeIssueRtree(item), false); // false = remove
- },
- // Used to populate `closed:osmose:*` changeset tags
- getClosedCounts: function getClosedCounts() {
- return _cache.closed;
- },
- itemURL: function itemURL(item) {
- return "https://osmose.openstreetmap.fr/en/error/".concat(item.id);
- }
- };
+ function clone(value) {
+ return baseClone(value, CLONE_SYMBOLS_FLAG);
+ }
+ /**
+ * This method is like `_.clone` except that it accepts `customizer` which
+ * is invoked to produce the cloned value. If `customizer` returns `undefined`,
+ * cloning is handled by the method instead. The `customizer` is invoked with
+ * up to four arguments; (value [, index|key, object, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the cloned value.
+ * @see _.cloneDeepWith
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(false);
+ * }
+ * }
+ *
+ * var el = _.cloneWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 0
+ */
- /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */
- var read$6 = function read(buffer, offset, isLE, mLen, nBytes) {
- var e, m;
- var eLen = nBytes * 8 - mLen - 1;
- var eMax = (1 << eLen) - 1;
- var eBias = eMax >> 1;
- var nBits = -7;
- var i = isLE ? nBytes - 1 : 0;
- var d = isLE ? -1 : 1;
- var s = buffer[offset + i];
- i += d;
- e = s & (1 << -nBits) - 1;
- s >>= -nBits;
- nBits += eLen;
- for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+ function cloneWith(value, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ return baseClone(value, CLONE_SYMBOLS_FLAG, customizer);
+ }
+ /**
+ * This method is like `_.clone` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @returns {*} Returns the deep cloned value.
+ * @see _.clone
+ * @example
+ *
+ * var objects = [{ 'a': 1 }, { 'b': 2 }];
+ *
+ * var deep = _.cloneDeep(objects);
+ * console.log(deep[0] === objects[0]);
+ * // => false
+ */
- m = e & (1 << -nBits) - 1;
- e >>= -nBits;
- nBits += mLen;
- for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
+ function cloneDeep(value) {
+ return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
+ }
+ /**
+ * This method is like `_.cloneWith` except that it recursively clones `value`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to recursively clone.
+ * @param {Function} [customizer] The function to customize cloning.
+ * @returns {*} Returns the deep cloned value.
+ * @see _.cloneWith
+ * @example
+ *
+ * function customizer(value) {
+ * if (_.isElement(value)) {
+ * return value.cloneNode(true);
+ * }
+ * }
+ *
+ * var el = _.cloneDeepWith(document.body, customizer);
+ *
+ * console.log(el === document.body);
+ * // => false
+ * console.log(el.nodeName);
+ * // => 'BODY'
+ * console.log(el.childNodes.length);
+ * // => 20
+ */
- if (e === 0) {
- e = 1 - eBias;
- } else if (e === eMax) {
- return m ? NaN : (s ? -1 : 1) * Infinity;
- } else {
- m = m + Math.pow(2, mLen);
- e = e - eBias;
- }
- return (s ? -1 : 1) * m * Math.pow(2, e - mLen);
- };
+ function cloneDeepWith(value, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer);
+ }
+ /**
+ * Checks if `object` conforms to `source` by invoking the predicate
+ * properties of `source` with the corresponding property values of `object`.
+ *
+ * **Note:** This method is equivalent to `_.conforms` when `source` is
+ * partially applied.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.14.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property predicates to conform to.
+ * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ *
+ * _.conformsTo(object, { 'b': function(n) { return n > 1; } });
+ * // => true
+ *
+ * _.conformsTo(object, { 'b': function(n) { return n > 2; } });
+ * // => false
+ */
- var write$6 = function write(buffer, value, offset, isLE, mLen, nBytes) {
- var e, m, c;
- var eLen = nBytes * 8 - mLen - 1;
- var eMax = (1 << eLen) - 1;
- var eBias = eMax >> 1;
- var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0;
- var i = isLE ? 0 : nBytes - 1;
- var d = isLE ? 1 : -1;
- var s = value < 0 || value === 0 && 1 / value < 0 ? 1 : 0;
- value = Math.abs(value);
- if (isNaN(value) || value === Infinity) {
- m = isNaN(value) ? 1 : 0;
- e = eMax;
- } else {
- e = Math.floor(Math.log(value) / Math.LN2);
+ function conformsTo(object, source) {
+ return source == null || baseConformsTo(object, source, keys(source));
+ }
+ /**
+ * Performs a
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * comparison between two values to determine if they are equivalent.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.eq(object, object);
+ * // => true
+ *
+ * _.eq(object, other);
+ * // => false
+ *
+ * _.eq('a', 'a');
+ * // => true
+ *
+ * _.eq('a', Object('a'));
+ * // => false
+ *
+ * _.eq(NaN, NaN);
+ * // => true
+ */
- if (value * (c = Math.pow(2, -e)) < 1) {
- e--;
- c *= 2;
- }
- if (e + eBias >= 1) {
- value += rt / c;
- } else {
- value += rt * Math.pow(2, 1 - eBias);
- }
+ function eq(value, other) {
+ return value === other || value !== value && other !== other;
+ }
+ /**
+ * Checks if `value` is greater than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than `other`,
+ * else `false`.
+ * @see _.lt
+ * @example
+ *
+ * _.gt(3, 1);
+ * // => true
+ *
+ * _.gt(3, 3);
+ * // => false
+ *
+ * _.gt(1, 3);
+ * // => false
+ */
- if (value * c >= 2) {
- e++;
- c /= 2;
- }
- if (e + eBias >= eMax) {
- m = 0;
- e = eMax;
- } else if (e + eBias >= 1) {
- m = (value * c - 1) * Math.pow(2, mLen);
- e = e + eBias;
- } else {
- m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
- e = 0;
- }
- }
+ var gt = createRelationalOperation(baseGt);
+ /**
+ * Checks if `value` is greater than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is greater than or equal to
+ * `other`, else `false`.
+ * @see _.lte
+ * @example
+ *
+ * _.gte(3, 1);
+ * // => true
+ *
+ * _.gte(3, 3);
+ * // => true
+ *
+ * _.gte(1, 3);
+ * // => false
+ */
- for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+ var gte = createRelationalOperation(function (value, other) {
+ return value >= other;
+ });
+ /**
+ * Checks if `value` is likely an `arguments` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+ * else `false`.
+ * @example
+ *
+ * _.isArguments(function() { return arguments; }());
+ * // => true
+ *
+ * _.isArguments([1, 2, 3]);
+ * // => false
+ */
- e = e << mLen | m;
- eLen += mLen;
+ var isArguments = baseIsArguments(function () {
+ return arguments;
+ }()) ? baseIsArguments : function (value) {
+ return isObjectLike(value) && hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
+ };
+ /**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
- for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+ var isArray = Array.isArray;
+ /**
+ * Checks if `value` is classified as an `ArrayBuffer` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+ * @example
+ *
+ * _.isArrayBuffer(new ArrayBuffer(2));
+ * // => true
+ *
+ * _.isArrayBuffer(new Array(2));
+ * // => false
+ */
- buffer[offset + i - d] |= s * 128;
- };
+ var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer;
+ /**
+ * Checks if `value` is array-like. A value is considered array-like if it's
+ * not a function and has a `value.length` that's an integer greater than or
+ * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+ * @example
+ *
+ * _.isArrayLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLike(document.body.children);
+ * // => true
+ *
+ * _.isArrayLike('abc');
+ * // => true
+ *
+ * _.isArrayLike(_.noop);
+ * // => false
+ */
- var ieee754 = {
- read: read$6,
- write: write$6
- };
+ function isArrayLike(value) {
+ return value != null && isLength(value.length) && !isFunction(value);
+ }
+ /**
+ * This method is like `_.isArrayLike` except that it also checks if `value`
+ * is an object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array-like object,
+ * else `false`.
+ * @example
+ *
+ * _.isArrayLikeObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isArrayLikeObject(document.body.children);
+ * // => true
+ *
+ * _.isArrayLikeObject('abc');
+ * // => false
+ *
+ * _.isArrayLikeObject(_.noop);
+ * // => false
+ */
- var pbf = Pbf;
- function Pbf(buf) {
- this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
- this.pos = 0;
- this.type = 0;
- this.length = this.buf.length;
- }
+ function isArrayLikeObject(value) {
+ return isObjectLike(value) && isArrayLike(value);
+ }
+ /**
+ * Checks if `value` is classified as a boolean primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
+ * @example
+ *
+ * _.isBoolean(false);
+ * // => true
+ *
+ * _.isBoolean(null);
+ * // => false
+ */
- Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
- Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+ function isBoolean(value) {
+ return value === true || value === false || isObjectLike(value) && baseGetTag(value) == boolTag;
+ }
+ /**
+ * Checks if `value` is a buffer.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+ * @example
+ *
+ * _.isBuffer(new Buffer(2));
+ * // => true
+ *
+ * _.isBuffer(new Uint8Array(2));
+ * // => false
+ */
- Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
- Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+ var isBuffer = nativeIsBuffer || stubFalse;
+ /**
+ * Checks if `value` is classified as a `Date` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+ * @example
+ *
+ * _.isDate(new Date);
+ * // => true
+ *
+ * _.isDate('Mon April 23 2012');
+ * // => false
+ */
- var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
- SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32; // Threshold chosen based on both benchmarking and knowledge about browser string
- // data structures (which currently switch structure types at 12 bytes or more)
+ var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
+ /**
+ * Checks if `value` is likely a DOM element.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
+ * @example
+ *
+ * _.isElement(document.body);
+ * // => true
+ *
+ * _.isElement('');
+ * // => false
+ */
- var TEXT_DECODER_MIN_LENGTH = 12;
- var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8');
- Pbf.prototype = {
- destroy: function destroy() {
- this.buf = null;
- },
- // === READING =================================================================
- readFields: function readFields(readField, result, end) {
- end = end || this.length;
+ function isElement(value) {
+ return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
+ }
+ /**
+ * Checks if `value` is an empty object, collection, map, or set.
+ *
+ * Objects are considered empty if they have no own enumerable string keyed
+ * properties.
+ *
+ * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+ * jQuery-like collections are considered empty if they have a `length` of `0`.
+ * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+ * @example
+ *
+ * _.isEmpty(null);
+ * // => true
+ *
+ * _.isEmpty(true);
+ * // => true
+ *
+ * _.isEmpty(1);
+ * // => true
+ *
+ * _.isEmpty([1, 2, 3]);
+ * // => false
+ *
+ * _.isEmpty({ 'a': 1 });
+ * // => false
+ */
- while (this.pos < end) {
- var val = this.readVarint(),
- tag = val >> 3,
- startPos = this.pos;
- this.type = val & 0x7;
- readField(tag, result, this);
- if (this.pos === startPos) this.skip(val);
- }
- return result;
- },
- readMessage: function readMessage(readField, result) {
- return this.readFields(readField, result, this.readVarint() + this.pos);
- },
- readFixed32: function readFixed32() {
- var val = readUInt32(this.buf, this.pos);
- this.pos += 4;
- return val;
- },
- readSFixed32: function readSFixed32() {
- var val = readInt32(this.buf, this.pos);
- this.pos += 4;
- return val;
- },
- // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
- readFixed64: function readFixed64() {
- var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
- this.pos += 8;
- return val;
- },
- readSFixed64: function readSFixed64() {
- var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
- this.pos += 8;
- return val;
- },
- readFloat: function readFloat() {
- var val = ieee754.read(this.buf, this.pos, true, 23, 4);
- this.pos += 4;
- return val;
- },
- readDouble: function readDouble() {
- var val = ieee754.read(this.buf, this.pos, true, 52, 8);
- this.pos += 8;
- return val;
- },
- readVarint: function readVarint(isSigned) {
- var buf = this.buf,
- val,
- b;
- b = buf[this.pos++];
- val = b & 0x7f;
- if (b < 0x80) return val;
- b = buf[this.pos++];
- val |= (b & 0x7f) << 7;
- if (b < 0x80) return val;
- b = buf[this.pos++];
- val |= (b & 0x7f) << 14;
- if (b < 0x80) return val;
- b = buf[this.pos++];
- val |= (b & 0x7f) << 21;
- if (b < 0x80) return val;
- b = buf[this.pos];
- val |= (b & 0x0f) << 28;
- return readVarintRemainder(val, isSigned, this);
- },
- readVarint64: function readVarint64() {
- // for compatibility with v2.0.1
- return this.readVarint(true);
- },
- readSVarint: function readSVarint() {
- var num = this.readVarint();
- return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
- },
- readBoolean: function readBoolean() {
- return Boolean(this.readVarint());
- },
- readString: function readString() {
- var end = this.readVarint() + this.pos;
- var pos = this.pos;
- this.pos = end;
+ function isEmpty(value) {
+ if (value == null) {
+ return true;
+ }
- if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
- // longer strings are fast with the built-in browser TextDecoder API
- return readUtf8TextDecoder(this.buf, pos, end);
- } // short strings are fast with our custom implementation
+ if (isArrayLike(value) && (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' || isBuffer(value) || isTypedArray(value) || isArguments(value))) {
+ return !value.length;
+ }
+ var tag = getTag(value);
- return readUtf8(this.buf, pos, end);
- },
- readBytes: function readBytes() {
- var end = this.readVarint() + this.pos,
- buffer = this.buf.subarray(this.pos, end);
- this.pos = end;
- return buffer;
- },
- // verbose for performance reasons; doesn't affect gzipped size
- readPackedVarint: function readPackedVarint(arr, isSigned) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
- var end = readPackedEnd(this);
- arr = arr || [];
+ if (tag == mapTag || tag == setTag) {
+ return !value.size;
+ }
- while (this.pos < end) {
- arr.push(this.readVarint(isSigned));
- }
+ if (isPrototype(value)) {
+ return !baseKeys(value).length;
+ }
- return arr;
- },
- readPackedSVarint: function readPackedSVarint(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
- var end = readPackedEnd(this);
- arr = arr || [];
+ for (var key in value) {
+ if (hasOwnProperty.call(value, key)) {
+ return false;
+ }
+ }
- while (this.pos < end) {
- arr.push(this.readSVarint());
- }
+ return true;
+ }
+ /**
+ * Performs a deep comparison between two values to determine if they are
+ * equivalent.
+ *
+ * **Note:** This method supports comparing arrays, array buffers, booleans,
+ * date objects, error objects, maps, numbers, `Object` objects, regexes,
+ * sets, strings, symbols, and typed arrays. `Object` objects are compared
+ * by their own, not inherited, enumerable properties. Functions and DOM
+ * nodes are compared by strict equality, i.e. `===`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.isEqual(object, other);
+ * // => true
+ *
+ * object === other;
+ * // => false
+ */
- return arr;
- },
- readPackedBoolean: function readPackedBoolean(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
- var end = readPackedEnd(this);
- arr = arr || [];
- while (this.pos < end) {
- arr.push(this.readBoolean());
- }
+ function isEqual(value, other) {
+ return baseIsEqual(value, other);
+ }
+ /**
+ * This method is like `_.isEqual` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+ * are handled by the method instead. The `customizer` is invoked with up to
+ * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, othValue) {
+ * if (isGreeting(objValue) && isGreeting(othValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var array = ['hello', 'goodbye'];
+ * var other = ['hi', 'goodbye'];
+ *
+ * _.isEqualWith(array, other, customizer);
+ * // => true
+ */
- return arr;
- },
- readPackedFloat: function readPackedFloat(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
- var end = readPackedEnd(this);
- arr = arr || [];
- while (this.pos < end) {
- arr.push(this.readFloat());
- }
+ function isEqualWith(value, other, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ var result = customizer ? customizer(value, other) : undefined$1;
+ return result === undefined$1 ? baseIsEqual(value, other, undefined$1, customizer) : !!result;
+ }
+ /**
+ * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+ * `SyntaxError`, `TypeError`, or `URIError` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
+ * @example
+ *
+ * _.isError(new Error);
+ * // => true
+ *
+ * _.isError(Error);
+ * // => false
+ */
- return arr;
- },
- readPackedDouble: function readPackedDouble(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
- var end = readPackedEnd(this);
- arr = arr || [];
- while (this.pos < end) {
- arr.push(this.readDouble());
- }
+ function isError(value) {
+ if (!isObjectLike(value)) {
+ return false;
+ }
- return arr;
- },
- readPackedFixed32: function readPackedFixed32(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
- var end = readPackedEnd(this);
- arr = arr || [];
+ var tag = baseGetTag(value);
+ return tag == errorTag || tag == domExcTag || typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value);
+ }
+ /**
+ * Checks if `value` is a finite primitive number.
+ *
+ * **Note:** This method is based on
+ * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
+ * @example
+ *
+ * _.isFinite(3);
+ * // => true
+ *
+ * _.isFinite(Number.MIN_VALUE);
+ * // => true
+ *
+ * _.isFinite(Infinity);
+ * // => false
+ *
+ * _.isFinite('3');
+ * // => false
+ */
- while (this.pos < end) {
- arr.push(this.readFixed32());
- }
- return arr;
- },
- readPackedSFixed32: function readPackedSFixed32(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
- var end = readPackedEnd(this);
- arr = arr || [];
+ function isFinite(value) {
+ return typeof value == 'number' && nativeIsFinite(value);
+ }
+ /**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
- while (this.pos < end) {
- arr.push(this.readSFixed32());
- }
- return arr;
- },
- readPackedFixed64: function readPackedFixed64(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
- var end = readPackedEnd(this);
- arr = arr || [];
+ function isFunction(value) {
+ if (!isObject(value)) {
+ return false;
+ } // The use of `Object#toString` avoids issues with the `typeof` operator
+ // in Safari 9 which returns 'object' for typed arrays and other constructors.
- while (this.pos < end) {
- arr.push(this.readFixed64());
- }
- return arr;
- },
- readPackedSFixed64: function readPackedSFixed64(arr) {
- if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
- var end = readPackedEnd(this);
- arr = arr || [];
+ var tag = baseGetTag(value);
+ return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
+ }
+ /**
+ * Checks if `value` is an integer.
+ *
+ * **Note:** This method is based on
+ * [`Number.isInteger`](https://mdn.io/Number/isInteger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+ * @example
+ *
+ * _.isInteger(3);
+ * // => true
+ *
+ * _.isInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isInteger(Infinity);
+ * // => false
+ *
+ * _.isInteger('3');
+ * // => false
+ */
- while (this.pos < end) {
- arr.push(this.readSFixed64());
- }
- return arr;
- },
- skip: function skip(val) {
- var type = val & 0x7;
- if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {} else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;else if (type === Pbf.Fixed32) this.pos += 4;else if (type === Pbf.Fixed64) this.pos += 8;else throw new Error('Unimplemented type: ' + type);
- },
- // === WRITING =================================================================
- writeTag: function writeTag(tag, type) {
- this.writeVarint(tag << 3 | type);
- },
- realloc: function realloc(min) {
- var length = this.length || 16;
+ function isInteger(value) {
+ return typeof value == 'number' && value == toInteger(value);
+ }
+ /**
+ * Checks if `value` is a valid array-like length.
+ *
+ * **Note:** This method is loosely based on
+ * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+ * @example
+ *
+ * _.isLength(3);
+ * // => true
+ *
+ * _.isLength(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isLength(Infinity);
+ * // => false
+ *
+ * _.isLength('3');
+ * // => false
+ */
- while (length < this.pos + min) {
- length *= 2;
- }
- if (length !== this.length) {
- var buf = new Uint8Array(length);
- buf.set(this.buf);
- this.buf = buf;
- this.length = length;
- }
- },
- finish: function finish() {
- this.length = this.pos;
- this.pos = 0;
- return this.buf.subarray(0, this.length);
- },
- writeFixed32: function writeFixed32(val) {
- this.realloc(4);
- writeInt32(this.buf, val, this.pos);
- this.pos += 4;
- },
- writeSFixed32: function writeSFixed32(val) {
- this.realloc(4);
- writeInt32(this.buf, val, this.pos);
- this.pos += 4;
- },
- writeFixed64: function writeFixed64(val) {
- this.realloc(8);
- writeInt32(this.buf, val & -1, this.pos);
- writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
- this.pos += 8;
- },
- writeSFixed64: function writeSFixed64(val) {
- this.realloc(8);
- writeInt32(this.buf, val & -1, this.pos);
- writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
- this.pos += 8;
- },
- writeVarint: function writeVarint(val) {
- val = +val || 0;
+ function isLength(value) {
+ return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+ }
+ /**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
- if (val > 0xfffffff || val < 0) {
- writeBigVarint(val, this);
- return;
- }
- this.realloc(4);
- this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0);
- if (val <= 0x7f) return;
- this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
- if (val <= 0x7f) return;
- this.buf[this.pos++] = (val >>>= 7) & 0x7f | (val > 0x7f ? 0x80 : 0);
- if (val <= 0x7f) return;
- this.buf[this.pos++] = val >>> 7 & 0x7f;
- },
- writeSVarint: function writeSVarint(val) {
- this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
- },
- writeBoolean: function writeBoolean(val) {
- this.writeVarint(Boolean(val));
- },
- writeString: function writeString(str) {
- str = String(str);
- this.realloc(str.length * 4);
- this.pos++; // reserve 1 byte for short string length
+ function isObject(value) {
+ var type = _typeof(value);
- var startPos = this.pos; // write the string directly to the buffer and see how much was written
+ return value != null && (type == 'object' || type == 'function');
+ }
+ /**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
- this.pos = writeUtf8(this.buf, str, this.pos);
- var len = this.pos - startPos;
- if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position
- this.pos = startPos - 1;
- this.writeVarint(len);
- this.pos += len;
- },
- writeFloat: function writeFloat(val) {
- this.realloc(4);
- ieee754.write(this.buf, val, this.pos, true, 23, 4);
- this.pos += 4;
- },
- writeDouble: function writeDouble(val) {
- this.realloc(8);
- ieee754.write(this.buf, val, this.pos, true, 52, 8);
- this.pos += 8;
- },
- writeBytes: function writeBytes(buffer) {
- var len = buffer.length;
- this.writeVarint(len);
- this.realloc(len);
+ function isObjectLike(value) {
+ return value != null && _typeof(value) == 'object';
+ }
+ /**
+ * Checks if `value` is classified as a `Map` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+ * @example
+ *
+ * _.isMap(new Map);
+ * // => true
+ *
+ * _.isMap(new WeakMap);
+ * // => false
+ */
- for (var i = 0; i < len; i++) {
- this.buf[this.pos++] = buffer[i];
- }
- },
- writeRawMessage: function writeRawMessage(fn, obj) {
- this.pos++; // reserve 1 byte for short message length
- // write the message directly to the buffer and see how much was written
- var startPos = this.pos;
- fn(obj, this);
- var len = this.pos - startPos;
- if (len >= 0x80) makeRoomForExtraLength(startPos, len, this); // finally, write the message length in the reserved place and restore the position
+ var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap;
+ /**
+ * Performs a partial deep comparison between `object` and `source` to
+ * determine if `object` contains equivalent property values.
+ *
+ * **Note:** This method is equivalent to `_.matches` when `source` is
+ * partially applied.
+ *
+ * Partial comparisons will match empty array and empty object `source`
+ * values against any array or object value, respectively. See `_.isEqual`
+ * for a list of supported value comparisons.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ *
+ * _.isMatch(object, { 'b': 2 });
+ * // => true
+ *
+ * _.isMatch(object, { 'b': 1 });
+ * // => false
+ */
- this.pos = startPos - 1;
- this.writeVarint(len);
- this.pos += len;
- },
- writeMessage: function writeMessage(tag, fn, obj) {
- this.writeTag(tag, Pbf.Bytes);
- this.writeRawMessage(fn, obj);
- },
- writePackedVarint: function writePackedVarint(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedVarint, arr);
- },
- writePackedSVarint: function writePackedSVarint(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedSVarint, arr);
- },
- writePackedBoolean: function writePackedBoolean(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedBoolean, arr);
- },
- writePackedFloat: function writePackedFloat(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedFloat, arr);
- },
- writePackedDouble: function writePackedDouble(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedDouble, arr);
- },
- writePackedFixed32: function writePackedFixed32(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedFixed, arr);
- },
- writePackedSFixed32: function writePackedSFixed32(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedSFixed, arr);
- },
- writePackedFixed64: function writePackedFixed64(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedFixed2, arr);
- },
- writePackedSFixed64: function writePackedSFixed64(tag, arr) {
- if (arr.length) this.writeMessage(tag, _writePackedSFixed2, arr);
- },
- writeBytesField: function writeBytesField(tag, buffer) {
- this.writeTag(tag, Pbf.Bytes);
- this.writeBytes(buffer);
- },
- writeFixed32Field: function writeFixed32Field(tag, val) {
- this.writeTag(tag, Pbf.Fixed32);
- this.writeFixed32(val);
- },
- writeSFixed32Field: function writeSFixed32Field(tag, val) {
- this.writeTag(tag, Pbf.Fixed32);
- this.writeSFixed32(val);
- },
- writeFixed64Field: function writeFixed64Field(tag, val) {
- this.writeTag(tag, Pbf.Fixed64);
- this.writeFixed64(val);
- },
- writeSFixed64Field: function writeSFixed64Field(tag, val) {
- this.writeTag(tag, Pbf.Fixed64);
- this.writeSFixed64(val);
- },
- writeVarintField: function writeVarintField(tag, val) {
- this.writeTag(tag, Pbf.Varint);
- this.writeVarint(val);
- },
- writeSVarintField: function writeSVarintField(tag, val) {
- this.writeTag(tag, Pbf.Varint);
- this.writeSVarint(val);
- },
- writeStringField: function writeStringField(tag, str) {
- this.writeTag(tag, Pbf.Bytes);
- this.writeString(str);
- },
- writeFloatField: function writeFloatField(tag, val) {
- this.writeTag(tag, Pbf.Fixed32);
- this.writeFloat(val);
- },
- writeDoubleField: function writeDoubleField(tag, val) {
- this.writeTag(tag, Pbf.Fixed64);
- this.writeDouble(val);
- },
- writeBooleanField: function writeBooleanField(tag, val) {
- this.writeVarintField(tag, Boolean(val));
- }
- };
+ function isMatch(object, source) {
+ return object === source || baseIsMatch(object, source, getMatchData(source));
+ }
+ /**
+ * This method is like `_.isMatch` except that it accepts `customizer` which
+ * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+ * are handled by the method instead. The `customizer` is invoked with five
+ * arguments: (objValue, srcValue, index|key, object, source).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {Object} object The object to inspect.
+ * @param {Object} source The object of property values to match.
+ * @param {Function} [customizer] The function to customize comparisons.
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+ * @example
+ *
+ * function isGreeting(value) {
+ * return /^h(?:i|ello)$/.test(value);
+ * }
+ *
+ * function customizer(objValue, srcValue) {
+ * if (isGreeting(objValue) && isGreeting(srcValue)) {
+ * return true;
+ * }
+ * }
+ *
+ * var object = { 'greeting': 'hello' };
+ * var source = { 'greeting': 'hi' };
+ *
+ * _.isMatchWith(object, source, customizer);
+ * // => true
+ */
- function readVarintRemainder(l, s, p) {
- var buf = p.buf,
- h,
- b;
- b = buf[p.pos++];
- h = (b & 0x70) >> 4;
- if (b < 0x80) return toNum(l, h, s);
- b = buf[p.pos++];
- h |= (b & 0x7f) << 3;
- if (b < 0x80) return toNum(l, h, s);
- b = buf[p.pos++];
- h |= (b & 0x7f) << 10;
- if (b < 0x80) return toNum(l, h, s);
- b = buf[p.pos++];
- h |= (b & 0x7f) << 17;
- if (b < 0x80) return toNum(l, h, s);
- b = buf[p.pos++];
- h |= (b & 0x7f) << 24;
- if (b < 0x80) return toNum(l, h, s);
- b = buf[p.pos++];
- h |= (b & 0x01) << 31;
- if (b < 0x80) return toNum(l, h, s);
- throw new Error('Expected varint not more than 10 bytes');
- }
- function readPackedEnd(pbf) {
- return pbf.type === Pbf.Bytes ? pbf.readVarint() + pbf.pos : pbf.pos + 1;
- }
+ function isMatchWith(object, source, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ return baseIsMatch(object, source, getMatchData(source), customizer);
+ }
+ /**
+ * Checks if `value` is `NaN`.
+ *
+ * **Note:** This method is based on
+ * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+ * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+ * `undefined` and other non-number values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+ * @example
+ *
+ * _.isNaN(NaN);
+ * // => true
+ *
+ * _.isNaN(new Number(NaN));
+ * // => true
+ *
+ * isNaN(undefined);
+ * // => true
+ *
+ * _.isNaN(undefined);
+ * // => false
+ */
- function toNum(low, high, isSigned) {
- if (isSigned) {
- return high * 0x100000000 + (low >>> 0);
- }
- return (high >>> 0) * 0x100000000 + (low >>> 0);
- }
+ function isNaN(value) {
+ // An `NaN` primitive is the only value that is not equal to itself.
+ // Perform the `toStringTag` check first to avoid errors with some
+ // ActiveX objects in IE.
+ return isNumber(value) && value != +value;
+ }
+ /**
+ * Checks if `value` is a pristine native function.
+ *
+ * **Note:** This method can't reliably detect native functions in the presence
+ * of the core-js package because core-js circumvents this kind of detection.
+ * Despite multiple requests, the core-js maintainer has made it clear: any
+ * attempt to fix the detection will be obstructed. As a result, we're left
+ * with little choice but to throw an error. Unfortunately, this also affects
+ * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
+ * which rely on core-js.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ * else `false`.
+ * @example
+ *
+ * _.isNative(Array.prototype.push);
+ * // => true
+ *
+ * _.isNative(_);
+ * // => false
+ */
- function writeBigVarint(val, pbf) {
- var low, high;
- if (val >= 0) {
- low = val % 0x100000000 | 0;
- high = val / 0x100000000 | 0;
- } else {
- low = ~(-val % 0x100000000);
- high = ~(-val / 0x100000000);
+ function isNative(value) {
+ if (isMaskable(value)) {
+ throw new Error(CORE_ERROR_TEXT);
+ }
- if (low ^ 0xffffffff) {
- low = low + 1 | 0;
- } else {
- low = 0;
- high = high + 1 | 0;
- }
- }
+ return baseIsNative(value);
+ }
+ /**
+ * Checks if `value` is `null`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+ * @example
+ *
+ * _.isNull(null);
+ * // => true
+ *
+ * _.isNull(void 0);
+ * // => false
+ */
- if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
- throw new Error('Given varint doesn\'t fit into 10 bytes');
- }
- pbf.realloc(10);
- writeBigVarintLow(low, high, pbf);
- writeBigVarintHigh(high, pbf);
- }
+ function isNull(value) {
+ return value === null;
+ }
+ /**
+ * Checks if `value` is `null` or `undefined`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+ * @example
+ *
+ * _.isNil(null);
+ * // => true
+ *
+ * _.isNil(void 0);
+ * // => true
+ *
+ * _.isNil(NaN);
+ * // => false
+ */
- function writeBigVarintLow(low, high, pbf) {
- pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
- low >>>= 7;
- pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
- low >>>= 7;
- pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
- low >>>= 7;
- pbf.buf[pbf.pos++] = low & 0x7f | 0x80;
- low >>>= 7;
- pbf.buf[pbf.pos] = low & 0x7f;
- }
- function writeBigVarintHigh(high, pbf) {
- var lsb = (high & 0x07) << 4;
- pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0);
- if (!high) return;
- pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
- if (!high) return;
- pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
- if (!high) return;
- pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
- if (!high) return;
- pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0);
- if (!high) return;
- pbf.buf[pbf.pos++] = high & 0x7f;
- }
+ function isNil(value) {
+ return value == null;
+ }
+ /**
+ * Checks if `value` is classified as a `Number` primitive or object.
+ *
+ * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+ * classified as numbers, use the `_.isFinite` method.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a number, else `false`.
+ * @example
+ *
+ * _.isNumber(3);
+ * // => true
+ *
+ * _.isNumber(Number.MIN_VALUE);
+ * // => true
+ *
+ * _.isNumber(Infinity);
+ * // => true
+ *
+ * _.isNumber('3');
+ * // => false
+ */
- function makeRoomForExtraLength(startPos, len, pbf) {
- var extraLen = len <= 0x3fff ? 1 : len <= 0x1fffff ? 2 : len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7)); // if 1 byte isn't enough for encoding message length, shift the data to the right
- pbf.realloc(extraLen);
+ function isNumber(value) {
+ return typeof value == 'number' || isObjectLike(value) && baseGetTag(value) == numberTag;
+ }
+ /**
+ * Checks if `value` is a plain object, that is, an object created by the
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.8.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * _.isPlainObject(new Foo);
+ * // => false
+ *
+ * _.isPlainObject([1, 2, 3]);
+ * // => false
+ *
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
+ * // => true
+ *
+ * _.isPlainObject(Object.create(null));
+ * // => true
+ */
- for (var i = pbf.pos - 1; i >= startPos; i--) {
- pbf.buf[i + extraLen] = pbf.buf[i];
- }
- }
- function _writePackedVarint(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeVarint(arr[i]);
- }
- }
+ function isPlainObject(value) {
+ if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
+ return false;
+ }
- function _writePackedSVarint(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeSVarint(arr[i]);
- }
- }
+ var proto = getPrototype(value);
- function _writePackedFloat(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFloat(arr[i]);
- }
- }
+ if (proto === null) {
+ return true;
+ }
- function _writePackedDouble(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeDouble(arr[i]);
- }
- }
+ var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+ return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString;
+ }
+ /**
+ * Checks if `value` is classified as a `RegExp` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+ * @example
+ *
+ * _.isRegExp(/abc/);
+ * // => true
+ *
+ * _.isRegExp('/abc/');
+ * // => false
+ */
- function _writePackedBoolean(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeBoolean(arr[i]);
- }
- }
- function _writePackedFixed(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFixed32(arr[i]);
- }
- }
+ var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp;
+ /**
+ * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+ * double precision number which isn't the result of a rounded unsafe integer.
+ *
+ * **Note:** This method is based on
+ * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
+ * @example
+ *
+ * _.isSafeInteger(3);
+ * // => true
+ *
+ * _.isSafeInteger(Number.MIN_VALUE);
+ * // => false
+ *
+ * _.isSafeInteger(Infinity);
+ * // => false
+ *
+ * _.isSafeInteger('3');
+ * // => false
+ */
- function _writePackedSFixed(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeSFixed32(arr[i]);
- }
- }
+ function isSafeInteger(value) {
+ return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+ }
+ /**
+ * Checks if `value` is classified as a `Set` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+ * @example
+ *
+ * _.isSet(new Set);
+ * // => true
+ *
+ * _.isSet(new WeakSet);
+ * // => false
+ */
- function _writePackedFixed2(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeFixed64(arr[i]);
- }
- }
- function _writePackedSFixed2(arr, pbf) {
- for (var i = 0; i < arr.length; i++) {
- pbf.writeSFixed64(arr[i]);
- }
- } // Buffer code below from https://github.com/feross/buffer, MIT-licensed
+ var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet;
+ /**
+ * Checks if `value` is classified as a `String` primitive or object.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a string, else `false`.
+ * @example
+ *
+ * _.isString('abc');
+ * // => true
+ *
+ * _.isString(1);
+ * // => false
+ */
+ function isString(value) {
+ return typeof value == 'string' || !isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag;
+ }
+ /**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
- function readUInt32(buf, pos) {
- return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + buf[pos + 3] * 0x1000000;
- }
- function writeInt32(buf, val, pos) {
- buf[pos] = val;
- buf[pos + 1] = val >>> 8;
- buf[pos + 2] = val >>> 16;
- buf[pos + 3] = val >>> 24;
- }
+ function isSymbol(value) {
+ return _typeof(value) == 'symbol' || isObjectLike(value) && baseGetTag(value) == symbolTag;
+ }
+ /**
+ * Checks if `value` is classified as a typed array.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+ * @example
+ *
+ * _.isTypedArray(new Uint8Array);
+ * // => true
+ *
+ * _.isTypedArray([]);
+ * // => false
+ */
- function readInt32(buf, pos) {
- return (buf[pos] | buf[pos + 1] << 8 | buf[pos + 2] << 16) + (buf[pos + 3] << 24);
- }
- function readUtf8(buf, pos, end) {
- var str = '';
- var i = pos;
+ var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
+ /**
+ * Checks if `value` is `undefined`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+ * @example
+ *
+ * _.isUndefined(void 0);
+ * // => true
+ *
+ * _.isUndefined(null);
+ * // => false
+ */
- while (i < end) {
- var b0 = buf[i];
- var c = null; // codepoint
+ function isUndefined(value) {
+ return value === undefined$1;
+ }
+ /**
+ * Checks if `value` is classified as a `WeakMap` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a weak map, else `false`.
+ * @example
+ *
+ * _.isWeakMap(new WeakMap);
+ * // => true
+ *
+ * _.isWeakMap(new Map);
+ * // => false
+ */
- var bytesPerSequence = b0 > 0xEF ? 4 : b0 > 0xDF ? 3 : b0 > 0xBF ? 2 : 1;
- if (i + bytesPerSequence > end) break;
- var b1, b2, b3;
- if (bytesPerSequence === 1) {
- if (b0 < 0x80) {
- c = b0;
+ function isWeakMap(value) {
+ return isObjectLike(value) && getTag(value) == weakMapTag;
}
- } else if (bytesPerSequence === 2) {
- b1 = buf[i + 1];
+ /**
+ * Checks if `value` is classified as a `WeakSet` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.3.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a weak set, else `false`.
+ * @example
+ *
+ * _.isWeakSet(new WeakSet);
+ * // => true
+ *
+ * _.isWeakSet(new Set);
+ * // => false
+ */
- if ((b1 & 0xC0) === 0x80) {
- c = (b0 & 0x1F) << 0x6 | b1 & 0x3F;
- if (c <= 0x7F) {
- c = null;
- }
+ function isWeakSet(value) {
+ return isObjectLike(value) && baseGetTag(value) == weakSetTag;
}
- } else if (bytesPerSequence === 3) {
- b1 = buf[i + 1];
- b2 = buf[i + 2];
+ /**
+ * Checks if `value` is less than `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than `other`,
+ * else `false`.
+ * @see _.gt
+ * @example
+ *
+ * _.lt(1, 3);
+ * // => true
+ *
+ * _.lt(3, 3);
+ * // => false
+ *
+ * _.lt(3, 1);
+ * // => false
+ */
- if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
- c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | b2 & 0x3F;
- if (c <= 0x7FF || c >= 0xD800 && c <= 0xDFFF) {
- c = null;
+ var lt = createRelationalOperation(baseLt);
+ /**
+ * Checks if `value` is less than or equal to `other`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.9.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if `value` is less than or equal to
+ * `other`, else `false`.
+ * @see _.gte
+ * @example
+ *
+ * _.lte(1, 3);
+ * // => true
+ *
+ * _.lte(3, 3);
+ * // => true
+ *
+ * _.lte(3, 1);
+ * // => false
+ */
+
+ var lte = createRelationalOperation(function (value, other) {
+ return value <= other;
+ });
+ /**
+ * Converts `value` to an array.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Array} Returns the converted array.
+ * @example
+ *
+ * _.toArray({ 'a': 1, 'b': 2 });
+ * // => [1, 2]
+ *
+ * _.toArray('abc');
+ * // => ['a', 'b', 'c']
+ *
+ * _.toArray(1);
+ * // => []
+ *
+ * _.toArray(null);
+ * // => []
+ */
+
+ function toArray(value) {
+ if (!value) {
+ return [];
}
+
+ if (isArrayLike(value)) {
+ return isString(value) ? stringToArray(value) : copyArray(value);
+ }
+
+ if (symIterator && value[symIterator]) {
+ return iteratorToArray(value[symIterator]());
+ }
+
+ var tag = getTag(value),
+ func = tag == mapTag ? mapToArray : tag == setTag ? setToArray : values;
+ return func(value);
}
- } else if (bytesPerSequence === 4) {
- b1 = buf[i + 1];
- b2 = buf[i + 2];
- b3 = buf[i + 3];
+ /**
+ * Converts `value` to a finite number.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.12.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted number.
+ * @example
+ *
+ * _.toFinite(3.2);
+ * // => 3.2
+ *
+ * _.toFinite(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toFinite(Infinity);
+ * // => 1.7976931348623157e+308
+ *
+ * _.toFinite('3.2');
+ * // => 3.2
+ */
- if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
- c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | b3 & 0x3F;
- if (c <= 0xFFFF || c >= 0x110000) {
- c = null;
+ function toFinite(value) {
+ if (!value) {
+ return value === 0 ? value : 0;
+ }
+
+ value = toNumber(value);
+
+ if (value === INFINITY || value === -INFINITY) {
+ var sign = value < 0 ? -1 : 1;
+ return sign * MAX_INTEGER;
}
+
+ return value === value ? value : 0;
}
- }
+ /**
+ * Converts `value` to an integer.
+ *
+ * **Note:** This method is loosely based on
+ * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toInteger(3.2);
+ * // => 3
+ *
+ * _.toInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toInteger(Infinity);
+ * // => 1.7976931348623157e+308
+ *
+ * _.toInteger('3.2');
+ * // => 3
+ */
- if (c === null) {
- c = 0xFFFD;
- bytesPerSequence = 1;
- } else if (c > 0xFFFF) {
- c -= 0x10000;
- str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
- c = 0xDC00 | c & 0x3FF;
- }
- str += String.fromCharCode(c);
- i += bytesPerSequence;
- }
+ function toInteger(value) {
+ var result = toFinite(value),
+ remainder = result % 1;
+ return result === result ? remainder ? result - remainder : result : 0;
+ }
+ /**
+ * Converts `value` to an integer suitable for use as the length of an
+ * array-like object.
+ *
+ * **Note:** This method is based on
+ * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toLength(3.2);
+ * // => 3
+ *
+ * _.toLength(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toLength(Infinity);
+ * // => 4294967295
+ *
+ * _.toLength('3.2');
+ * // => 3
+ */
- return str;
- }
- function readUtf8TextDecoder(buf, pos, end) {
- return utf8TextDecoder.decode(buf.subarray(pos, end));
- }
+ function toLength(value) {
+ return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+ }
+ /**
+ * Converts `value` to a number.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {number} Returns the number.
+ * @example
+ *
+ * _.toNumber(3.2);
+ * // => 3.2
+ *
+ * _.toNumber(Number.MIN_VALUE);
+ * // => 5e-324
+ *
+ * _.toNumber(Infinity);
+ * // => Infinity
+ *
+ * _.toNumber('3.2');
+ * // => 3.2
+ */
- function writeUtf8(buf, str, pos) {
- for (var i = 0, c, lead; i < str.length; i++) {
- c = str.charCodeAt(i); // code point
- if (c > 0xD7FF && c < 0xE000) {
- if (lead) {
- if (c < 0xDC00) {
- buf[pos++] = 0xEF;
- buf[pos++] = 0xBF;
- buf[pos++] = 0xBD;
- lead = c;
- continue;
- } else {
- c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
- lead = null;
+ function toNumber(value) {
+ if (typeof value == 'number') {
+ return value;
}
- } else {
- if (c > 0xDBFF || i + 1 === str.length) {
- buf[pos++] = 0xEF;
- buf[pos++] = 0xBF;
- buf[pos++] = 0xBD;
- } else {
- lead = c;
+
+ if (isSymbol(value)) {
+ return NAN;
}
- continue;
- }
- } else if (lead) {
- buf[pos++] = 0xEF;
- buf[pos++] = 0xBF;
- buf[pos++] = 0xBD;
- lead = null;
- }
+ if (isObject(value)) {
+ var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+ value = isObject(other) ? other + '' : other;
+ }
- if (c < 0x80) {
- buf[pos++] = c;
- } else {
- if (c < 0x800) {
- buf[pos++] = c >> 0x6 | 0xC0;
- } else {
- if (c < 0x10000) {
- buf[pos++] = c >> 0xC | 0xE0;
- } else {
- buf[pos++] = c >> 0x12 | 0xF0;
- buf[pos++] = c >> 0xC & 0x3F | 0x80;
+ if (typeof value != 'string') {
+ return value === 0 ? value : +value;
}
- buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+ value = baseTrim(value);
+ var isBinary = reIsBinary.test(value);
+ return isBinary || reIsOctal.test(value) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : reIsBadHex.test(value) ? NAN : +value;
}
+ /**
+ * Converts `value` to a plain object flattening inherited enumerable string
+ * keyed properties of `value` to own properties of the plain object.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {Object} Returns the converted plain object.
+ * @example
+ *
+ * function Foo() {
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.assign({ 'a': 1 }, new Foo);
+ * // => { 'a': 1, 'b': 2 }
+ *
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
+ */
- buf[pos++] = c & 0x3F | 0x80;
- }
- }
- return pos;
- }
+ function toPlainObject(value) {
+ return copyObject(value, keysIn(value));
+ }
+ /**
+ * Converts `value` to a safe integer. A safe integer can be compared and
+ * represented correctly.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.toSafeInteger(3.2);
+ * // => 3
+ *
+ * _.toSafeInteger(Number.MIN_VALUE);
+ * // => 0
+ *
+ * _.toSafeInteger(Infinity);
+ * // => 9007199254740991
+ *
+ * _.toSafeInteger('3.2');
+ * // => 3
+ */
- var pointGeometry = Point;
- /**
- * A standalone point geometry with useful accessor, comparison, and
- * modification methods.
- *
- * @class Point
- * @param {Number} x the x-coordinate. this could be longitude or screen
- * pixels, or any other sort of unit.
- * @param {Number} y the y-coordinate. this could be latitude or screen
- * pixels, or any other sort of unit.
- * @example
- * var point = new Point(-77, 38);
- */
- function Point(x, y) {
- this.x = x;
- this.y = y;
- }
+ function toSafeInteger(value) {
+ return value ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER) : value === 0 ? value : 0;
+ }
+ /**
+ * Converts `value` to a string. An empty string is returned for `null`
+ * and `undefined` values. The sign of `-0` is preserved.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.toString(null);
+ * // => ''
+ *
+ * _.toString(-0);
+ * // => '-0'
+ *
+ * _.toString([1, 2, 3]);
+ * // => '1,2,3'
+ */
- Point.prototype = {
- /**
- * Clone this point, returning a new point that can be modified
- * without affecting the old one.
- * @return {Point} the clone
- */
- clone: function clone() {
- return new Point(this.x, this.y);
- },
- /**
- * Add this point's x & y coordinates to another point,
- * yielding a new point.
- * @param {Point} p the other point
- * @return {Point} output point
- */
- add: function add(p) {
- return this.clone()._add(p);
- },
+ function toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+ /*------------------------------------------------------------------------*/
- /**
- * Subtract this point's x & y coordinates to from point,
- * yielding a new point.
- * @param {Point} p the other point
- * @return {Point} output point
- */
- sub: function sub(p) {
- return this.clone()._sub(p);
- },
+ /**
+ * Assigns own enumerable string keyed properties of source objects to the
+ * destination object. Source objects are applied from left to right.
+ * Subsequent sources overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object` and is loosely based on
+ * [`Object.assign`](https://mdn.io/Object/assign).
+ *
+ * @static
+ * @memberOf _
+ * @since 0.10.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.assignIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * function Bar() {
+ * this.c = 3;
+ * }
+ *
+ * Foo.prototype.b = 2;
+ * Bar.prototype.d = 4;
+ *
+ * _.assign({ 'a': 0 }, new Foo, new Bar);
+ * // => { 'a': 1, 'c': 3 }
+ */
- /**
- * Multiply this point's x & y coordinates by point,
- * yielding a new point.
- * @param {Point} p the other point
- * @return {Point} output point
- */
- multByPoint: function multByPoint(p) {
- return this.clone()._multByPoint(p);
- },
- /**
- * Divide this point's x & y coordinates by point,
- * yielding a new point.
- * @param {Point} p the other point
- * @return {Point} output point
- */
- divByPoint: function divByPoint(p) {
- return this.clone()._divByPoint(p);
- },
+ var assign = createAssigner(function (object, source) {
+ if (isPrototype(source) || isArrayLike(source)) {
+ copyObject(source, keys(source), object);
+ return;
+ }
- /**
- * Multiply this point's x & y coordinates by a factor,
- * yielding a new point.
- * @param {Point} k factor
- * @return {Point} output point
- */
- mult: function mult(k) {
- return this.clone()._mult(k);
- },
+ for (var key in source) {
+ if (hasOwnProperty.call(source, key)) {
+ assignValue(object, key, source[key]);
+ }
+ }
+ });
+ /**
+ * This method is like `_.assign` except that it iterates over own and
+ * inherited source properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias extend
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.assign
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * }
+ *
+ * function Bar() {
+ * this.c = 3;
+ * }
+ *
+ * Foo.prototype.b = 2;
+ * Bar.prototype.d = 4;
+ *
+ * _.assignIn({ 'a': 0 }, new Foo, new Bar);
+ * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
+ */
- /**
- * Divide this point's x & y coordinates by a factor,
- * yielding a new point.
- * @param {Point} k factor
- * @return {Point} output point
- */
- div: function div(k) {
- return this.clone()._div(k);
- },
+ var assignIn = createAssigner(function (object, source) {
+ copyObject(source, keysIn(source), object);
+ });
+ /**
+ * This method is like `_.assignIn` except that it accepts `customizer`
+ * which is invoked to produce the assigned values. If `customizer` returns
+ * `undefined`, assignment is handled by the method instead. The `customizer`
+ * is invoked with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias extendWith
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @see _.assignWith
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignInWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
- /**
- * Rotate this point around the 0, 0 origin by an angle a,
- * given in radians
- * @param {Number} a angle to rotate around, in radians
- * @return {Point} output point
- */
- rotate: function rotate(a) {
- return this.clone()._rotate(a);
- },
+ var assignInWith = createAssigner(function (object, source, srcIndex, customizer) {
+ copyObject(source, keysIn(source), object, customizer);
+ });
+ /**
+ * This method is like `_.assign` except that it accepts `customizer`
+ * which is invoked to produce the assigned values. If `customizer` returns
+ * `undefined`, assignment is handled by the method instead. The `customizer`
+ * is invoked with five arguments: (objValue, srcValue, key, object, source).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @see _.assignInWith
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * return _.isUndefined(objValue) ? srcValue : objValue;
+ * }
+ *
+ * var defaults = _.partialRight(_.assignWith, customizer);
+ *
+ * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
- /**
- * Rotate this point around p point by an angle a,
- * given in radians
- * @param {Number} a angle to rotate around, in radians
- * @param {Point} p Point to rotate around
- * @return {Point} output point
- */
- rotateAround: function rotateAround(a, p) {
- return this.clone()._rotateAround(a, p);
- },
+ var assignWith = createAssigner(function (object, source, srcIndex, customizer) {
+ copyObject(source, keys(source), object, customizer);
+ });
+ /**
+ * Creates an array of values corresponding to `paths` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {...(string|string[])} [paths] The property paths to pick.
+ * @returns {Array} Returns the picked values.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+ *
+ * _.at(object, ['a[0].b.c', 'a[1]']);
+ * // => [3, 4]
+ */
- /**
- * Multiply this point by a 4x1 transformation matrix
- * @param {Array} m transformation matrix
- * @return {Point} output point
- */
- matMult: function matMult(m) {
- return this.clone()._matMult(m);
- },
+ var at = flatRest(baseAt);
+ /**
+ * Creates an object that inherits from the `prototype` object. If a
+ * `properties` object is given, its own enumerable string keyed properties
+ * are assigned to the created object.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.3.0
+ * @category Object
+ * @param {Object} prototype The object to inherit from.
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * function Shape() {
+ * this.x = 0;
+ * this.y = 0;
+ * }
+ *
+ * function Circle() {
+ * Shape.call(this);
+ * }
+ *
+ * Circle.prototype = _.create(Shape.prototype, {
+ * 'constructor': Circle
+ * });
+ *
+ * var circle = new Circle;
+ * circle instanceof Circle;
+ * // => true
+ *
+ * circle instanceof Shape;
+ * // => true
+ */
- /**
- * Calculate this point but as a unit vector from 0, 0, meaning
- * that the distance from the resulting point to the 0, 0
- * coordinate will be equal to 1 and the angle from the resulting
- * point to the 0, 0 coordinate will be the same as before.
- * @return {Point} unit vector point
- */
- unit: function unit() {
- return this.clone()._unit();
- },
+ function create(prototype, properties) {
+ var result = baseCreate(prototype);
+ return properties == null ? result : baseAssign(result, properties);
+ }
+ /**
+ * Assigns own and inherited enumerable string keyed properties of source
+ * objects to the destination object for all destination properties that
+ * resolve to `undefined`. Source objects are applied from left to right.
+ * Once a property is set, additional values of the same property are ignored.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.defaultsDeep
+ * @example
+ *
+ * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+ * // => { 'a': 1, 'b': 2 }
+ */
- /**
- * Compute a perpendicular point, where the new y coordinate
- * is the old x coordinate and the new x coordinate is the old y
- * coordinate multiplied by -1
- * @return {Point} perpendicular point
- */
- perp: function perp() {
- return this.clone()._perp();
- },
- /**
- * Return a version of this point with the x & y coordinates
- * rounded to integers.
- * @return {Point} rounded point
- */
- round: function round() {
- return this.clone()._round();
- },
+ var defaults = baseRest(function (object, sources) {
+ object = Object(object);
+ var index = -1;
+ var length = sources.length;
+ var guard = length > 2 ? sources[2] : undefined$1;
- /**
- * Return the magitude of this point: this is the Euclidean
- * distance from the 0, 0 coordinate to this point's x and y
- * coordinates.
- * @return {Number} magnitude
- */
- mag: function mag() {
- return Math.sqrt(this.x * this.x + this.y * this.y);
- },
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+ length = 1;
+ }
- /**
- * Judge whether this point is equal to another point, returning
- * true or false.
- * @param {Point} other the other point
- * @return {boolean} whether the points are equal
- */
- equals: function equals(other) {
- return this.x === other.x && this.y === other.y;
- },
+ while (++index < length) {
+ var source = sources[index];
+ var props = keysIn(source);
+ var propsIndex = -1;
+ var propsLength = props.length;
- /**
- * Calculate the distance from this point to another point
- * @param {Point} p the other point
- * @return {Number} distance
- */
- dist: function dist(p) {
- return Math.sqrt(this.distSqr(p));
- },
+ while (++propsIndex < propsLength) {
+ var key = props[propsIndex];
+ var value = object[key];
- /**
- * Calculate the distance from this point to another point,
- * without the square root step. Useful if you're comparing
- * relative distances.
- * @param {Point} p the other point
- * @return {Number} distance
- */
- distSqr: function distSqr(p) {
- var dx = p.x - this.x,
- dy = p.y - this.y;
- return dx * dx + dy * dy;
- },
+ if (value === undefined$1 || eq(value, objectProto[key]) && !hasOwnProperty.call(object, key)) {
+ object[key] = source[key];
+ }
+ }
+ }
- /**
- * Get the angle from the 0, 0 coordinate to this point, in radians
- * coordinates.
- * @return {Number} angle
- */
- angle: function angle() {
- return Math.atan2(this.y, this.x);
- },
+ return object;
+ });
+ /**
+ * This method is like `_.defaults` except that it recursively assigns
+ * default properties.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.10.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @see _.defaults
+ * @example
+ *
+ * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
+ * // => { 'a': { 'b': 2, 'c': 3 } }
+ */
- /**
- * Get the angle from this point to another point, in radians
- * @param {Point} b the other point
- * @return {Number} angle
- */
- angleTo: function angleTo(b) {
- return Math.atan2(this.y - b.y, this.x - b.x);
- },
+ var defaultsDeep = baseRest(function (args) {
+ args.push(undefined$1, customDefaultsMerge);
+ return apply(mergeWith, undefined$1, args);
+ });
+ /**
+ * This method is like `_.find` except that it returns the key of the first
+ * element `predicate` returns truthy for instead of the element itself.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element,
+ * else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findKey(users, function(o) { return o.age < 40; });
+ * // => 'barney' (iteration order is not guaranteed)
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findKey(users, { 'age': 1, 'active': true });
+ * // => 'pebbles'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findKey(users, 'active');
+ * // => 'barney'
+ */
- /**
- * Get the angle between this point and another point, in radians
- * @param {Point} b the other point
- * @return {Number} angle
- */
- angleWith: function angleWith(b) {
- return this.angleWithSep(b.x, b.y);
- },
+ function findKey(object, predicate) {
+ return baseFindKey(object, getIteratee(predicate, 3), baseForOwn);
+ }
+ /**
+ * This method is like `_.findKey` except that it iterates over elements of
+ * a collection in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @param {Function} [predicate=_.identity] The function invoked per iteration.
+ * @returns {string|undefined} Returns the key of the matched element,
+ * else `undefined`.
+ * @example
+ *
+ * var users = {
+ * 'barney': { 'age': 36, 'active': true },
+ * 'fred': { 'age': 40, 'active': false },
+ * 'pebbles': { 'age': 1, 'active': true }
+ * };
+ *
+ * _.findLastKey(users, function(o) { return o.age < 40; });
+ * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+ *
+ * // The `_.matches` iteratee shorthand.
+ * _.findLastKey(users, { 'age': 36, 'active': true });
+ * // => 'barney'
+ *
+ * // The `_.matchesProperty` iteratee shorthand.
+ * _.findLastKey(users, ['active', false]);
+ * // => 'fred'
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.findLastKey(users, 'active');
+ * // => 'pebbles'
+ */
- /*
- * Find the angle of the two vectors, solving the formula for
- * the cross product a x b = |a||b|sin(θ) for θ.
- * @param {Number} x the x-coordinate
- * @param {Number} y the y-coordinate
- * @return {Number} the angle in radians
- */
- angleWithSep: function angleWithSep(x, y) {
- return Math.atan2(this.x * y - this.y * x, this.x * x + this.y * y);
- },
- _matMult: function _matMult(m) {
- var x = m[0] * this.x + m[1] * this.y,
- y = m[2] * this.x + m[3] * this.y;
- this.x = x;
- this.y = y;
- return this;
- },
- _add: function _add(p) {
- this.x += p.x;
- this.y += p.y;
- return this;
- },
- _sub: function _sub(p) {
- this.x -= p.x;
- this.y -= p.y;
- return this;
- },
- _mult: function _mult(k) {
- this.x *= k;
- this.y *= k;
- return this;
- },
- _div: function _div(k) {
- this.x /= k;
- this.y /= k;
- return this;
- },
- _multByPoint: function _multByPoint(p) {
- this.x *= p.x;
- this.y *= p.y;
- return this;
- },
- _divByPoint: function _divByPoint(p) {
- this.x /= p.x;
- this.y /= p.y;
- return this;
- },
- _unit: function _unit() {
- this._div(this.mag());
- return this;
- },
- _perp: function _perp() {
- var y = this.y;
- this.y = this.x;
- this.x = -y;
- return this;
- },
- _rotate: function _rotate(angle) {
- var cos = Math.cos(angle),
- sin = Math.sin(angle),
- x = cos * this.x - sin * this.y,
- y = sin * this.x + cos * this.y;
- this.x = x;
- this.y = y;
- return this;
- },
- _rotateAround: function _rotateAround(angle, p) {
- var cos = Math.cos(angle),
- sin = Math.sin(angle),
- x = p.x + cos * (this.x - p.x) - sin * (this.y - p.y),
- y = p.y + sin * (this.x - p.x) + cos * (this.y - p.y);
- this.x = x;
- this.y = y;
- return this;
- },
- _round: function _round() {
- this.x = Math.round(this.x);
- this.y = Math.round(this.y);
- return this;
- }
- };
- /**
- * Construct a point from an array if necessary, otherwise if the input
- * is already a Point, or an unknown type, return it unchanged
- * @param {Array|Point|*} a any kind of input value
- * @return {Point} constructed point, or passed-through value.
- * @example
- * // this
- * var point = Point.convert([0, 1]);
- * // is equivalent to
- * var point = new Point(0, 1);
- */
+ function findLastKey(object, predicate) {
+ return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight);
+ }
+ /**
+ * Iterates over own and inherited enumerable string keyed properties of an
+ * object and invokes `iteratee` for each property. The iteratee is invoked
+ * with three arguments: (value, key, object). Iteratee functions may exit
+ * iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forInRight
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forIn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+ */
- Point.convert = function (a) {
- if (a instanceof Point) {
- return a;
- }
- if (Array.isArray(a)) {
- return new Point(a[0], a[1]);
- }
+ function forIn(object, iteratee) {
+ return object == null ? object : baseFor(object, getIteratee(iteratee, 3), keysIn);
+ }
+ /**
+ * This method is like `_.forIn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forInRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+ */
- return a;
- };
- var vectortilefeature = VectorTileFeature$1;
+ function forInRight(object, iteratee) {
+ return object == null ? object : baseForRight(object, getIteratee(iteratee, 3), keysIn);
+ }
+ /**
+ * Iterates over own enumerable string keyed properties of an object and
+ * invokes `iteratee` for each property. The iteratee is invoked with three
+ * arguments: (value, key, object). Iteratee functions may exit iteration
+ * early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forOwnRight
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwn(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+ */
- function VectorTileFeature$1(pbf, end, extent, keys, values) {
- // Public
- this.properties = {};
- this.extent = extent;
- this.type = 0; // Private
- this._pbf = pbf;
- this._geometry = -1;
- this._keys = keys;
- this._values = values;
- pbf.readFields(readFeature, this, end);
- }
+ function forOwn(object, iteratee) {
+ return object && baseForOwn(object, getIteratee(iteratee, 3));
+ }
+ /**
+ * This method is like `_.forOwn` except that it iterates over properties of
+ * `object` in the opposite order.
+ *
+ * @static
+ * @memberOf _
+ * @since 2.0.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns `object`.
+ * @see _.forOwn
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.forOwnRight(new Foo, function(value, key) {
+ * console.log(key);
+ * });
+ * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+ */
- function readFeature(tag, feature, pbf) {
- if (tag == 1) feature.id = pbf.readVarint();else if (tag == 2) readTag(pbf, feature);else if (tag == 3) feature.type = pbf.readVarint();else if (tag == 4) feature._geometry = pbf.pos;
- }
- function readTag(pbf, feature) {
- var end = pbf.readVarint() + pbf.pos;
+ function forOwnRight(object, iteratee) {
+ return object && baseForOwnRight(object, getIteratee(iteratee, 3));
+ }
+ /**
+ * Creates an array of function property names from own enumerable properties
+ * of `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the function names.
+ * @see _.functionsIn
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functions(new Foo);
+ * // => ['a', 'b']
+ */
- while (pbf.pos < end) {
- var key = feature._keys[pbf.readVarint()],
- value = feature._values[pbf.readVarint()];
- feature.properties[key] = value;
- }
- }
+ function functions(object) {
+ return object == null ? [] : baseFunctions(object, keys(object));
+ }
+ /**
+ * Creates an array of function property names from own and inherited
+ * enumerable properties of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to inspect.
+ * @returns {Array} Returns the function names.
+ * @see _.functions
+ * @example
+ *
+ * function Foo() {
+ * this.a = _.constant('a');
+ * this.b = _.constant('b');
+ * }
+ *
+ * Foo.prototype.c = _.constant('c');
+ *
+ * _.functionsIn(new Foo);
+ * // => ['a', 'b', 'c']
+ */
- VectorTileFeature$1.types = ['Unknown', 'Point', 'LineString', 'Polygon'];
- VectorTileFeature$1.prototype.loadGeometry = function () {
- var pbf = this._pbf;
- pbf.pos = this._geometry;
- var end = pbf.readVarint() + pbf.pos,
- cmd = 1,
- length = 0,
- x = 0,
- y = 0,
- lines = [],
- line;
+ function functionsIn(object) {
+ return object == null ? [] : baseFunctions(object, keysIn(object));
+ }
+ /**
+ * Gets the value at `path` of `object`. If the resolved value is
+ * `undefined`, the `defaultValue` is returned in its place.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to get.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.get(object, 'a[0].b.c');
+ * // => 3
+ *
+ * _.get(object, ['a', '0', 'b', 'c']);
+ * // => 3
+ *
+ * _.get(object, 'a.b.c', 'default');
+ * // => 'default'
+ */
- while (pbf.pos < end) {
- if (length <= 0) {
- var cmdLen = pbf.readVarint();
- cmd = cmdLen & 0x7;
- length = cmdLen >> 3;
- }
- length--;
+ function get(object, path, defaultValue) {
+ var result = object == null ? undefined$1 : baseGet(object, path);
+ return result === undefined$1 ? defaultValue : result;
+ }
+ /**
+ * Checks if `path` is a direct property of `object`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = { 'a': { 'b': 2 } };
+ * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+ *
+ * _.has(object, 'a');
+ * // => true
+ *
+ * _.has(object, 'a.b');
+ * // => true
+ *
+ * _.has(object, ['a', 'b']);
+ * // => true
+ *
+ * _.has(other, 'a');
+ * // => false
+ */
- if (cmd === 1 || cmd === 2) {
- x += pbf.readSVarint();
- y += pbf.readSVarint();
- if (cmd === 1) {
- // moveTo
- if (line) lines.push(line);
- line = [];
+ function has(object, path) {
+ return object != null && hasPath(object, path, baseHas);
}
+ /**
+ * Checks if `path` is a direct or inherited property of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path to check.
+ * @returns {boolean} Returns `true` if `path` exists, else `false`.
+ * @example
+ *
+ * var object = _.create({ 'a': _.create({ 'b': 2 }) });
+ *
+ * _.hasIn(object, 'a');
+ * // => true
+ *
+ * _.hasIn(object, 'a.b');
+ * // => true
+ *
+ * _.hasIn(object, ['a', 'b']);
+ * // => true
+ *
+ * _.hasIn(object, 'b');
+ * // => false
+ */
- line.push(new pointGeometry(x, y));
- } else if (cmd === 7) {
- // Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90
- if (line) {
- line.push(line[0].clone()); // closePolygon
- }
- } else {
- throw new Error('unknown command ' + cmd);
- }
- }
- if (line) lines.push(line);
- return lines;
- };
+ function hasIn(object, path) {
+ return object != null && hasPath(object, path, baseHasIn);
+ }
+ /**
+ * Creates an object composed of the inverted keys and values of `object`.
+ * If `object` contains duplicate values, subsequent values overwrite
+ * property assignments of previous values.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.7.0
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invert(object);
+ * // => { '1': 'c', '2': 'b' }
+ */
- VectorTileFeature$1.prototype.bbox = function () {
- var pbf = this._pbf;
- pbf.pos = this._geometry;
- var end = pbf.readVarint() + pbf.pos,
- cmd = 1,
- length = 0,
- x = 0,
- y = 0,
- x1 = Infinity,
- x2 = -Infinity,
- y1 = Infinity,
- y2 = -Infinity;
- while (pbf.pos < end) {
- if (length <= 0) {
- var cmdLen = pbf.readVarint();
- cmd = cmdLen & 0x7;
- length = cmdLen >> 3;
- }
+ var invert = createInverter(function (result, value, key) {
+ if (value != null && typeof value.toString != 'function') {
+ value = nativeObjectToString.call(value);
+ }
- length--;
+ result[value] = key;
+ }, constant(identity));
+ /**
+ * This method is like `_.invert` except that the inverted object is generated
+ * from the results of running each element of `object` thru `iteratee`. The
+ * corresponding inverted value of each inverted key is an array of keys
+ * responsible for generating the inverted value. The iteratee is invoked
+ * with one argument: (value).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.1.0
+ * @category Object
+ * @param {Object} object The object to invert.
+ * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+ * @returns {Object} Returns the new inverted object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
+ *
+ * _.invertBy(object);
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ *
+ * _.invertBy(object, function(value) {
+ * return 'group' + value;
+ * });
+ * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+ */
- if (cmd === 1 || cmd === 2) {
- x += pbf.readSVarint();
- y += pbf.readSVarint();
- if (x < x1) x1 = x;
- if (x > x2) x2 = x;
- if (y < y1) y1 = y;
- if (y > y2) y2 = y;
- } else if (cmd !== 7) {
- throw new Error('unknown command ' + cmd);
- }
- }
+ var invertBy = createInverter(function (result, value, key) {
+ if (value != null && typeof value.toString != 'function') {
+ value = nativeObjectToString.call(value);
+ }
- return [x1, y1, x2, y2];
- };
+ if (hasOwnProperty.call(result, value)) {
+ result[value].push(key);
+ } else {
+ result[value] = [key];
+ }
+ }, getIteratee);
+ /**
+ * Invokes the method at `path` of `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the method to invoke.
+ * @param {...*} [args] The arguments to invoke the method with.
+ * @returns {*} Returns the result of the invoked method.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+ *
+ * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+ * // => [2, 3]
+ */
- VectorTileFeature$1.prototype.toGeoJSON = function (x, y, z) {
- var size = this.extent * Math.pow(2, z),
- x0 = this.extent * x,
- y0 = this.extent * y,
- coords = this.loadGeometry(),
- type = VectorTileFeature$1.types[this.type],
- i,
- j;
+ var invoke = baseRest(baseInvoke);
+ /**
+ * Creates an array of the own enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects. See the
+ * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+ * for more details.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keys(new Foo);
+ * // => ['a', 'b'] (iteration order is not guaranteed)
+ *
+ * _.keys('hi');
+ * // => ['0', '1']
+ */
- function project(line) {
- for (var j = 0; j < line.length; j++) {
- var p = line[j],
- y2 = 180 - (p.y + y0) * 360 / size;
- line[j] = [(p.x + x0) * 360 / size - 180, 360 / Math.PI * Math.atan(Math.exp(y2 * Math.PI / 180)) - 90];
- }
- }
+ function keys(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
+ }
+ /**
+ * Creates an array of the own and inherited enumerable property names of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property names.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.keysIn(new Foo);
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+ */
- switch (this.type) {
- case 1:
- var points = [];
- for (i = 0; i < coords.length; i++) {
- points[i] = coords[i][0];
+ function keysIn(object) {
+ return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
}
+ /**
+ * The opposite of `_.mapValues`; this method creates an object with the
+ * same values as `object` and keys generated by running each own enumerable
+ * string keyed property of `object` thru `iteratee`. The iteratee is invoked
+ * with three arguments: (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.8.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @see _.mapValues
+ * @example
+ *
+ * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+ * return key + value;
+ * });
+ * // => { 'a1': 1, 'b2': 2 }
+ */
- coords = points;
- project(coords);
- break;
- case 2:
- for (i = 0; i < coords.length; i++) {
- project(coords[i]);
+ function mapKeys(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+ baseForOwn(object, function (value, key, object) {
+ baseAssignValue(result, iteratee(value, key, object), value);
+ });
+ return result;
}
+ /**
+ * Creates an object with the same keys as `object` and values generated
+ * by running each own enumerable string keyed property of `object` thru
+ * `iteratee`. The iteratee is invoked with three arguments:
+ * (value, key, object).
+ *
+ * @static
+ * @memberOf _
+ * @since 2.4.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @returns {Object} Returns the new mapped object.
+ * @see _.mapKeys
+ * @example
+ *
+ * var users = {
+ * 'fred': { 'user': 'fred', 'age': 40 },
+ * 'pebbles': { 'user': 'pebbles', 'age': 1 }
+ * };
+ *
+ * _.mapValues(users, function(o) { return o.age; });
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ *
+ * // The `_.property` iteratee shorthand.
+ * _.mapValues(users, 'age');
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+ */
- break;
-
- case 3:
- coords = classifyRings(coords);
- for (i = 0; i < coords.length; i++) {
- for (j = 0; j < coords[i].length; j++) {
- project(coords[i][j]);
- }
+ function mapValues(object, iteratee) {
+ var result = {};
+ iteratee = getIteratee(iteratee, 3);
+ baseForOwn(object, function (value, key, object) {
+ baseAssignValue(result, key, iteratee(value, key, object));
+ });
+ return result;
}
+ /**
+ * This method is like `_.assign` except that it recursively merges own and
+ * inherited enumerable string keyed properties of source objects into the
+ * destination object. Source properties that resolve to `undefined` are
+ * skipped if a destination value exists. Array and plain object properties
+ * are merged recursively. Other objects and value types are overridden by
+ * assignment. Source objects are applied from left to right. Subsequent
+ * sources overwrite property assignments of previous sources.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.5.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} [sources] The source objects.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {
+ * 'a': [{ 'b': 2 }, { 'd': 4 }]
+ * };
+ *
+ * var other = {
+ * 'a': [{ 'c': 3 }, { 'e': 5 }]
+ * };
+ *
+ * _.merge(object, other);
+ * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
+ */
- break;
- }
- if (coords.length === 1) {
- coords = coords[0];
- } else {
- type = 'Multi' + type;
- }
+ var merge = createAssigner(function (object, source, srcIndex) {
+ baseMerge(object, source, srcIndex);
+ });
+ /**
+ * This method is like `_.merge` except that it accepts `customizer` which
+ * is invoked to produce the merged values of the destination and source
+ * properties. If `customizer` returns `undefined`, merging is handled by the
+ * method instead. The `customizer` is invoked with six arguments:
+ * (objValue, srcValue, key, object, source, stack).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The destination object.
+ * @param {...Object} sources The source objects.
+ * @param {Function} customizer The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * function customizer(objValue, srcValue) {
+ * if (_.isArray(objValue)) {
+ * return objValue.concat(srcValue);
+ * }
+ * }
+ *
+ * var object = { 'a': [1], 'b': [2] };
+ * var other = { 'a': [3], 'b': [4] };
+ *
+ * _.mergeWith(object, other, customizer);
+ * // => { 'a': [1, 3], 'b': [2, 4] }
+ */
- var result = {
- type: "Feature",
- geometry: {
- type: type,
- coordinates: coords
- },
- properties: this.properties
- };
+ var mergeWith = createAssigner(function (object, source, srcIndex, customizer) {
+ baseMerge(object, source, srcIndex, customizer);
+ });
+ /**
+ * The opposite of `_.pick`; this method creates an object composed of the
+ * own and inherited enumerable property paths of `object` that are not omitted.
+ *
+ * **Note:** This method is considerably slower than `_.pick`.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [paths] The property paths to omit.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omit(object, ['a', 'c']);
+ * // => { 'b': '2' }
+ */
- if ('id' in this) {
- result.id = this.id;
- }
+ var omit = flatRest(function (object, paths) {
+ var result = {};
- return result;
- }; // classifies an array of rings into polygons with outer rings and holes
+ if (object == null) {
+ return result;
+ }
+ var isDeep = false;
+ paths = arrayMap(paths, function (path) {
+ path = castPath(path, object);
+ isDeep || (isDeep = path.length > 1);
+ return path;
+ });
+ copyObject(object, getAllKeysIn(object), result);
- function classifyRings(rings) {
- var len = rings.length;
- if (len <= 1) return [rings];
- var polygons = [],
- polygon,
- ccw;
+ if (isDeep) {
+ result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone);
+ }
- for (var i = 0; i < len; i++) {
- var area = signedArea(rings[i]);
- if (area === 0) continue;
- if (ccw === undefined) ccw = area < 0;
+ var length = paths.length;
- if (ccw === area < 0) {
- if (polygon) polygons.push(polygon);
- polygon = [rings[i]];
- } else {
- polygon.push(rings[i]);
- }
- }
+ while (length--) {
+ baseUnset(result, paths[length]);
+ }
- if (polygon) polygons.push(polygon);
- return polygons;
- }
+ return result;
+ });
+ /**
+ * The opposite of `_.pickBy`; this method creates an object composed of
+ * the own and inherited enumerable string keyed properties of `object` that
+ * `predicate` doesn't return truthy for. The predicate is invoked with two
+ * arguments: (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.omitBy(object, _.isNumber);
+ * // => { 'b': '2' }
+ */
- function signedArea(ring) {
- var sum = 0;
+ function omitBy(object, predicate) {
+ return pickBy(object, negate(getIteratee(predicate)));
+ }
+ /**
+ * Creates an object composed of the picked `object` properties.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {...(string|string[])} [paths] The property paths to pick.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pick(object, ['a', 'c']);
+ * // => { 'a': 1, 'c': 3 }
+ */
- for (var i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
- p1 = ring[i];
- p2 = ring[j];
- sum += (p2.x - p1.x) * (p1.y + p2.y);
- }
- return sum;
- }
+ var pick = flatRest(function (object, paths) {
+ return object == null ? {} : basePick(object, paths);
+ });
+ /**
+ * Creates an object composed of the `object` properties `predicate` returns
+ * truthy for. The predicate is invoked with two arguments: (value, key).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The source object.
+ * @param {Function} [predicate=_.identity] The function invoked per property.
+ * @returns {Object} Returns the new object.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': '2', 'c': 3 };
+ *
+ * _.pickBy(object, _.isNumber);
+ * // => { 'a': 1, 'c': 3 }
+ */
- var vectortilelayer = VectorTileLayer$1;
+ function pickBy(object, predicate) {
+ if (object == null) {
+ return {};
+ }
- function VectorTileLayer$1(pbf, end) {
- // Public
- this.version = 1;
- this.name = null;
- this.extent = 4096;
- this.length = 0; // Private
+ var props = arrayMap(getAllKeysIn(object), function (prop) {
+ return [prop];
+ });
+ predicate = getIteratee(predicate);
+ return basePickBy(object, props, function (value, path) {
+ return predicate(value, path[0]);
+ });
+ }
+ /**
+ * This method is like `_.get` except that if the resolved value is a
+ * function it's invoked with the `this` binding of its parent object and
+ * its result is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to resolve.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+ *
+ * _.result(object, 'a[0].b.c1');
+ * // => 3
+ *
+ * _.result(object, 'a[0].b.c2');
+ * // => 4
+ *
+ * _.result(object, 'a[0].b.c3', 'default');
+ * // => 'default'
+ *
+ * _.result(object, 'a[0].b.c3', _.constant('default'));
+ * // => 'default'
+ */
- this._pbf = pbf;
- this._keys = [];
- this._values = [];
- this._features = [];
- pbf.readFields(readLayer, this, end);
- this.length = this._features.length;
- }
- function readLayer(tag, layer, pbf) {
- if (tag === 15) layer.version = pbf.readVarint();else if (tag === 1) layer.name = pbf.readString();else if (tag === 5) layer.extent = pbf.readVarint();else if (tag === 2) layer._features.push(pbf.pos);else if (tag === 3) layer._keys.push(pbf.readString());else if (tag === 4) layer._values.push(readValueMessage(pbf));
- }
+ function result(object, path, defaultValue) {
+ path = castPath(path, object);
+ var index = -1,
+ length = path.length; // Ensure the loop is entered when path is empty.
- function readValueMessage(pbf) {
- var value = null,
- end = pbf.readVarint() + pbf.pos;
+ if (!length) {
+ length = 1;
+ object = undefined$1;
+ }
- while (pbf.pos < end) {
- var tag = pbf.readVarint() >> 3;
- value = tag === 1 ? pbf.readString() : tag === 2 ? pbf.readFloat() : tag === 3 ? pbf.readDouble() : tag === 4 ? pbf.readVarint64() : tag === 5 ? pbf.readVarint() : tag === 6 ? pbf.readSVarint() : tag === 7 ? pbf.readBoolean() : null;
- }
+ while (++index < length) {
+ var value = object == null ? undefined$1 : object[toKey(path[index])];
- return value;
- } // return feature `i` from this layer as a `VectorTileFeature`
+ if (value === undefined$1) {
+ index = length;
+ value = defaultValue;
+ }
+ object = isFunction(value) ? value.call(object) : value;
+ }
- VectorTileLayer$1.prototype.feature = function (i) {
- if (i < 0 || i >= this._features.length) throw new Error('feature index out of bounds');
- this._pbf.pos = this._features[i];
+ return object;
+ }
+ /**
+ * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+ * it's created. Arrays are created for missing index properties while objects
+ * are created for all other missing properties. Use `_.setWith` to customize
+ * `path` creation.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.7.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.set(object, 'a[0].b.c', 4);
+ * console.log(object.a[0].b.c);
+ * // => 4
+ *
+ * _.set(object, ['x', '0', 'y', 'z'], 5);
+ * console.log(object.x[0].y.z);
+ * // => 5
+ */
- var end = this._pbf.readVarint() + this._pbf.pos;
- return new vectortilefeature(this._pbf, end, this.extent, this._keys, this._values);
- };
+ function set(object, path, value) {
+ return object == null ? object : baseSet(object, path, value);
+ }
+ /**
+ * This method is like `_.set` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {*} value The value to set.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.setWith(object, '[0][1]', 'a', Object);
+ * // => { '0': { '1': 'a' } }
+ */
- var vectortile = VectorTile$1;
- function VectorTile$1(pbf, end) {
- this.layers = pbf.readFields(readTile, {}, end);
- }
+ function setWith(object, path, value, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ return object == null ? object : baseSet(object, path, value, customizer);
+ }
+ /**
+ * Creates an array of own enumerable string keyed-value pairs for `object`
+ * which can be consumed by `_.fromPairs`. If `object` is a map or set, its
+ * entries are returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias entries
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairs(new Foo);
+ * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+ */
- function readTile(tag, layers, pbf) {
- if (tag === 3) {
- var layer = new vectortilelayer(pbf, pbf.readVarint() + pbf.pos);
- if (layer.length) layers[layer.name] = layer;
- }
- }
- var VectorTile = vectortile;
- var VectorTileFeature = vectortilefeature;
- var VectorTileLayer = vectortilelayer;
- var vectorTile = {
- VectorTile: VectorTile,
- VectorTileFeature: VectorTileFeature,
- VectorTileLayer: VectorTileLayer
- };
+ var toPairs = createToPairs(keys);
+ /**
+ * Creates an array of own and inherited enumerable string keyed-value pairs
+ * for `object` which can be consumed by `_.fromPairs`. If `object` is a map
+ * or set, its entries are returned.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @alias entriesIn
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the key-value pairs.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.toPairsIn(new Foo);
+ * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)
+ */
- var accessToken = 'MLY|4100327730013843|5bb78b81720791946a9a7b956c57b7cf';
- var apiUrl = 'https://graph.mapillary.com/';
- var baseTileUrl = 'https://tiles.mapillary.com/maps/vtp';
- var mapFeatureTileUrl = "".concat(baseTileUrl, "/mly_map_feature_point/2/{z}/{x}/{y}?access_token=").concat(accessToken);
- var tileUrl = "".concat(baseTileUrl, "/mly1_public/2/{z}/{x}/{y}?access_token=").concat(accessToken);
- var trafficSignTileUrl = "".concat(baseTileUrl, "/mly_map_feature_traffic_sign/2/{z}/{x}/{y}?access_token=").concat(accessToken);
- var viewercss = 'mapillary-js/mapillary.css';
- var viewerjs = 'mapillary-js/mapillary.js';
- var minZoom$1 = 14;
- var dispatch$4 = dispatch$8('change', 'loadedImages', 'loadedSigns', 'loadedMapFeatures', 'bearingChanged', 'imageChanged');
+ var toPairsIn = createToPairs(keysIn);
+ /**
+ * An alternative to `_.reduce`; this method transforms `object` to a new
+ * `accumulator` object which is the result of running each of its own
+ * enumerable string keyed properties thru `iteratee`, with each invocation
+ * potentially mutating the `accumulator` object. If `accumulator` is not
+ * provided, a new object with the same `[[Prototype]]` will be used. The
+ * iteratee is invoked with four arguments: (accumulator, value, key, object).
+ * Iteratee functions may exit iteration early by explicitly returning `false`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.3.0
+ * @category Object
+ * @param {Object} object The object to iterate over.
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+ * @param {*} [accumulator] The custom accumulator value.
+ * @returns {*} Returns the accumulated value.
+ * @example
+ *
+ * _.transform([2, 3, 4], function(result, n) {
+ * result.push(n *= n);
+ * return n % 2 == 0;
+ * }, []);
+ * // => [4, 9]
+ *
+ * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+ * (result[value] || (result[value] = [])).push(key);
+ * }, {});
+ * // => { '1': ['a', 'c'], '2': ['b'] }
+ */
- var _loadViewerPromise$2;
+ function transform(object, iteratee, accumulator) {
+ var isArr = isArray(object),
+ isArrLike = isArr || isBuffer(object) || isTypedArray(object);
+ iteratee = getIteratee(iteratee, 4);
- var _mlyActiveImage;
+ if (accumulator == null) {
+ var Ctor = object && object.constructor;
- var _mlyCache;
+ if (isArrLike) {
+ accumulator = isArr ? new Ctor() : [];
+ } else if (isObject(object)) {
+ accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+ } else {
+ accumulator = {};
+ }
+ }
- var _mlyFallback = false;
+ (isArrLike ? arrayEach : baseForOwn)(object, function (value, index, object) {
+ return iteratee(accumulator, value, index, object);
+ });
+ return accumulator;
+ }
+ /**
+ * Removes the property at `path` of `object`.
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to unset.
+ * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+ * _.unset(object, 'a[0].b.c');
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ *
+ * _.unset(object, ['a', '0', 'b', 'c']);
+ * // => true
+ *
+ * console.log(object);
+ * // => { 'a': [{ 'b': {} }] };
+ */
- var _mlyHighlightedDetection;
- var _mlyShowFeatureDetections = false;
- var _mlyShowSignDetections = false;
+ function unset(object, path) {
+ return object == null ? true : baseUnset(object, path);
+ }
+ /**
+ * This method is like `_.set` except that accepts `updater` to produce the
+ * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+ * is invoked with one argument: (value).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+ *
+ * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+ * console.log(object.a[0].b.c);
+ * // => 9
+ *
+ * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+ * console.log(object.x[0].y.z);
+ * // => 0
+ */
- var _mlyViewer;
- var _mlyViewerFilter = ['all']; // Load all data for the specified type from Mapillary vector tiles
+ function update(object, path, updater) {
+ return object == null ? object : baseUpdate(object, path, castFunction(updater));
+ }
+ /**
+ * This method is like `_.update` except that it accepts `customizer` which is
+ * invoked to produce the objects of `path`. If `customizer` returns `undefined`
+ * path creation is handled by the method instead. The `customizer` is invoked
+ * with three arguments: (nsValue, key, nsObject).
+ *
+ * **Note:** This method mutates `object`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.6.0
+ * @category Object
+ * @param {Object} object The object to modify.
+ * @param {Array|string} path The path of the property to set.
+ * @param {Function} updater The function to produce the updated value.
+ * @param {Function} [customizer] The function to customize assigned values.
+ * @returns {Object} Returns `object`.
+ * @example
+ *
+ * var object = {};
+ *
+ * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+ * // => { '0': { '1': 'a' } }
+ */
- function loadTiles$2(which, url, maxZoom, projection) {
- var tiler = utilTiler().zoomExtent([minZoom$1, maxZoom]).skipNullIsland(true);
- var tiles = tiler.getTiles(projection);
- tiles.forEach(function (tile) {
- loadTile$1(which, url, tile);
- });
- } // Load all data for the specified type from one vector tile
+ function updateWith(object, path, updater, customizer) {
+ customizer = typeof customizer == 'function' ? customizer : undefined$1;
+ return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
+ }
+ /**
+ * Creates an array of the own enumerable string keyed property values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.values(new Foo);
+ * // => [1, 2] (iteration order is not guaranteed)
+ *
+ * _.values('hi');
+ * // => ['h', 'i']
+ */
- function loadTile$1(which, url, tile) {
- var cache = _mlyCache.requests;
- var tileId = "".concat(tile.id, "-").concat(which);
- if (cache.loaded[tileId] || cache.inflight[tileId]) return;
- var controller = new AbortController();
- cache.inflight[tileId] = controller;
- var requestUrl = url.replace('{x}', tile.xyz[0]).replace('{y}', tile.xyz[1]).replace('{z}', tile.xyz[2]);
- fetch(requestUrl, {
- signal: controller.signal
- }).then(function (response) {
- if (!response.ok) {
- throw new Error(response.status + ' ' + response.statusText);
- }
- cache.loaded[tileId] = true;
- delete cache.inflight[tileId];
- return response.arrayBuffer();
- }).then(function (data) {
- if (!data) {
- throw new Error('No Data');
- }
+ function values(object) {
+ return object == null ? [] : baseValues(object, keys(object));
+ }
+ /**
+ * Creates an array of the own and inherited enumerable string keyed property
+ * values of `object`.
+ *
+ * **Note:** Non-object values are coerced to objects.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category Object
+ * @param {Object} object The object to query.
+ * @returns {Array} Returns the array of property values.
+ * @example
+ *
+ * function Foo() {
+ * this.a = 1;
+ * this.b = 2;
+ * }
+ *
+ * Foo.prototype.c = 3;
+ *
+ * _.valuesIn(new Foo);
+ * // => [1, 2, 3] (iteration order is not guaranteed)
+ */
- loadTileDataToCache(data, tile, which);
- if (which === 'images') {
- dispatch$4.call('loadedImages');
- } else if (which === 'signs') {
- dispatch$4.call('loadedSigns');
- } else if (which === 'points') {
- dispatch$4.call('loadedMapFeatures');
- }
- })["catch"](function () {
- cache.loaded[tileId] = true;
- delete cache.inflight[tileId];
- });
- } // Load the data from the vector tile into cache
+ function valuesIn(object) {
+ return object == null ? [] : baseValues(object, keysIn(object));
+ }
+ /*------------------------------------------------------------------------*/
+ /**
+ * Clamps `number` within the inclusive `lower` and `upper` bounds.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Number
+ * @param {number} number The number to clamp.
+ * @param {number} [lower] The lower bound.
+ * @param {number} upper The upper bound.
+ * @returns {number} Returns the clamped number.
+ * @example
+ *
+ * _.clamp(-10, -5, 5);
+ * // => -5
+ *
+ * _.clamp(10, -5, 5);
+ * // => 5
+ */
- function loadTileDataToCache(data, tile, which) {
- var vectorTile = new VectorTile(new pbf(data));
- var features, cache, layer, i, feature, loc, d;
- if (vectorTile.layers.hasOwnProperty('image')) {
- features = [];
- cache = _mlyCache.images;
- layer = vectorTile.layers.image;
+ function clamp(number, lower, upper) {
+ if (upper === undefined$1) {
+ upper = lower;
+ lower = undefined$1;
+ }
- for (i = 0; i < layer.length; i++) {
- feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
- loc = feature.geometry.coordinates;
- d = {
- loc: loc,
- captured_at: feature.properties.captured_at,
- ca: feature.properties.compass_angle,
- id: feature.properties.id,
- is_pano: feature.properties.is_pano,
- sequence_id: feature.properties.sequence_id
- };
- cache.forImageId[d.id] = d;
- features.push({
- minX: loc[0],
- minY: loc[1],
- maxX: loc[0],
- maxY: loc[1],
- data: d
- });
- }
+ if (upper !== undefined$1) {
+ upper = toNumber(upper);
+ upper = upper === upper ? upper : 0;
+ }
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
+ if (lower !== undefined$1) {
+ lower = toNumber(lower);
+ lower = lower === lower ? lower : 0;
+ }
- if (vectorTile.layers.hasOwnProperty('sequence')) {
- features = [];
- cache = _mlyCache.sequences;
- layer = vectorTile.layers.sequence;
+ return baseClamp(toNumber(number), lower, upper);
+ }
+ /**
+ * Checks if `n` is between `start` and up to, but not including, `end`. If
+ * `end` is not specified, it's set to `start` with `start` then set to `0`.
+ * If `start` is greater than `end` the params are swapped to support
+ * negative ranges.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.3.0
+ * @category Number
+ * @param {number} number The number to check.
+ * @param {number} [start=0] The start of the range.
+ * @param {number} end The end of the range.
+ * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+ * @see _.range, _.rangeRight
+ * @example
+ *
+ * _.inRange(3, 2, 4);
+ * // => true
+ *
+ * _.inRange(4, 8);
+ * // => true
+ *
+ * _.inRange(4, 2);
+ * // => false
+ *
+ * _.inRange(2, 2);
+ * // => false
+ *
+ * _.inRange(1.2, 2);
+ * // => true
+ *
+ * _.inRange(5.2, 4);
+ * // => false
+ *
+ * _.inRange(-3, -2, -6);
+ * // => true
+ */
- for (i = 0; i < layer.length; i++) {
- feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
- if (cache.lineString[feature.properties.id]) {
- cache.lineString[feature.properties.id].push(feature);
- } else {
- cache.lineString[feature.properties.id] = [feature];
- }
- }
- }
+ function inRange(number, start, end) {
+ start = toFinite(start);
- if (vectorTile.layers.hasOwnProperty('point')) {
- features = [];
- cache = _mlyCache[which];
- layer = vectorTile.layers.point;
+ if (end === undefined$1) {
+ end = start;
+ start = 0;
+ } else {
+ end = toFinite(end);
+ }
- for (i = 0; i < layer.length; i++) {
- feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
- loc = feature.geometry.coordinates;
- d = {
- loc: loc,
- id: feature.properties.id,
- first_seen_at: feature.properties.first_seen_at,
- last_seen_at: feature.properties.last_seen_at,
- value: feature.properties.value
- };
- features.push({
- minX: loc[0],
- minY: loc[1],
- maxX: loc[0],
- maxY: loc[1],
- data: d
- });
- }
+ number = toNumber(number);
+ return baseInRange(number, start, end);
+ }
+ /**
+ * Produces a random number between the inclusive `lower` and `upper` bounds.
+ * If only one argument is provided a number between `0` and the given number
+ * is returned. If `floating` is `true`, or either `lower` or `upper` are
+ * floats, a floating-point number is returned instead of an integer.
+ *
+ * **Note:** JavaScript follows the IEEE-754 standard for resolving
+ * floating-point values which can produce unexpected results.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.7.0
+ * @category Number
+ * @param {number} [lower=0] The lower bound.
+ * @param {number} [upper=1] The upper bound.
+ * @param {boolean} [floating] Specify returning a floating-point number.
+ * @returns {number} Returns the random number.
+ * @example
+ *
+ * _.random(0, 5);
+ * // => an integer between 0 and 5
+ *
+ * _.random(5);
+ * // => also an integer between 0 and 5
+ *
+ * _.random(5, true);
+ * // => a floating-point number between 0 and 5
+ *
+ * _.random(1.2, 5.2);
+ * // => a floating-point number between 1.2 and 5.2
+ */
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
- if (vectorTile.layers.hasOwnProperty('traffic_sign')) {
- features = [];
- cache = _mlyCache[which];
- layer = vectorTile.layers.traffic_sign;
+ function random(lower, upper, floating) {
+ if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+ upper = floating = undefined$1;
+ }
- for (i = 0; i < layer.length; i++) {
- feature = layer.feature(i).toGeoJSON(tile.xyz[0], tile.xyz[1], tile.xyz[2]);
- loc = feature.geometry.coordinates;
- d = {
- loc: loc,
- id: feature.properties.id,
- first_seen_at: feature.properties.first_seen_at,
- last_seen_at: feature.properties.last_seen_at,
- value: feature.properties.value
- };
- features.push({
- minX: loc[0],
- minY: loc[1],
- maxX: loc[0],
- maxY: loc[1],
- data: d
- });
- }
+ if (floating === undefined$1) {
+ if (typeof upper == 'boolean') {
+ floating = upper;
+ upper = undefined$1;
+ } else if (typeof lower == 'boolean') {
+ floating = lower;
+ lower = undefined$1;
+ }
+ }
- if (cache.rtree) {
- cache.rtree.load(features);
- }
- }
- } // Get data from the API
+ if (lower === undefined$1 && upper === undefined$1) {
+ lower = 0;
+ upper = 1;
+ } else {
+ lower = toFinite(lower);
+ if (upper === undefined$1) {
+ upper = lower;
+ lower = 0;
+ } else {
+ upper = toFinite(upper);
+ }
+ }
- function loadData(url) {
- return fetch(url).then(function (response) {
- if (!response.ok) {
- throw new Error(response.status + ' ' + response.statusText);
- }
+ if (lower > upper) {
+ var temp = lower;
+ lower = upper;
+ upper = temp;
+ }
- return response.json();
- }).then(function (result) {
- if (!result) {
- return [];
- }
+ if (floating || lower % 1 || upper % 1) {
+ var rand = nativeRandom();
+ return nativeMin(lower + rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1))), upper);
+ }
- return result.data || [];
- });
- } // Partition viewport into higher zoom tiles
+ return baseRandom(lower, upper);
+ }
+ /*------------------------------------------------------------------------*/
+ /**
+ * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the camel cased string.
+ * @example
+ *
+ * _.camelCase('Foo Bar');
+ * // => 'fooBar'
+ *
+ * _.camelCase('--foo-bar--');
+ * // => 'fooBar'
+ *
+ * _.camelCase('__FOO_BAR__');
+ * // => 'fooBar'
+ */
- function partitionViewport$2(projection) {
- var z = geoScaleToZoom(projection.scale());
- var z2 = Math.ceil(z * 2) / 2 + 2.5; // round to next 0.5 and add 2.5
- var tiler = utilTiler().zoomExtent([z2, z2]);
- return tiler.getTiles(projection).map(function (tile) {
- return tile.extent;
- });
- } // Return no more than `limit` results per partition.
+ var camelCase = createCompounder(function (result, word, index) {
+ word = word.toLowerCase();
+ return result + (index ? capitalize(word) : word);
+ });
+ /**
+ * Converts the first character of `string` to upper case and the remaining
+ * to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to capitalize.
+ * @returns {string} Returns the capitalized string.
+ * @example
+ *
+ * _.capitalize('FRED');
+ * // => 'Fred'
+ */
+ function capitalize(string) {
+ return upperFirst(toString(string).toLowerCase());
+ }
+ /**
+ * Deburrs `string` by converting
+ * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+ * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)
+ * letters to basic Latin letters and removing
+ * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to deburr.
+ * @returns {string} Returns the deburred string.
+ * @example
+ *
+ * _.deburr('déjà vu');
+ * // => 'deja vu'
+ */
- function searchLimited$2(limit, projection, rtree) {
- limit = limit || 5;
- return partitionViewport$2(projection).reduce(function (result, extent) {
- var found = rtree.search(extent.bbox()).slice(0, limit).map(function (d) {
- return d.data;
- });
- return found.length ? result.concat(found) : result;
- }, []);
- }
- var serviceMapillary = {
- // Initialize Mapillary
- init: function init() {
- if (!_mlyCache) {
- this.reset();
- }
+ function deburr(string) {
+ string = toString(string);
+ return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
+ }
+ /**
+ * Checks if `string` ends with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=string.length] The position to search up to.
+ * @returns {boolean} Returns `true` if `string` ends with `target`,
+ * else `false`.
+ * @example
+ *
+ * _.endsWith('abc', 'c');
+ * // => true
+ *
+ * _.endsWith('abc', 'b');
+ * // => false
+ *
+ * _.endsWith('abc', 'b', 2);
+ * // => true
+ */
- this.event = utilRebind(this, dispatch$4, 'on');
- },
- // Reset cache and state
- reset: function reset() {
- if (_mlyCache) {
- Object.values(_mlyCache.requests.inflight).forEach(function (request) {
- request.abort();
- });
- }
- _mlyCache = {
- images: {
- rtree: new RBush(),
- forImageId: {}
- },
- image_detections: {
- forImageId: {}
- },
- signs: {
- rtree: new RBush()
- },
- points: {
- rtree: new RBush()
- },
- sequences: {
- rtree: new RBush(),
- lineString: {}
- },
- requests: {
- loaded: {},
- inflight: {}
+ function endsWith(string, target, position) {
+ string = toString(string);
+ target = baseToString(target);
+ var length = string.length;
+ position = position === undefined$1 ? length : baseClamp(toInteger(position), 0, length);
+ var end = position;
+ position -= target.length;
+ return position >= 0 && string.slice(position, end) == target;
}
- };
- _mlyActiveImage = null;
- },
- // Get visible images
- images: function images(projection) {
- var limit = 5;
- return searchLimited$2(limit, projection, _mlyCache.images.rtree);
- },
- // Get visible traffic signs
- signs: function signs(projection) {
- var limit = 5;
- return searchLimited$2(limit, projection, _mlyCache.signs.rtree);
- },
- // Get visible map (point) features
- mapFeatures: function mapFeatures(projection) {
- var limit = 5;
- return searchLimited$2(limit, projection, _mlyCache.points.rtree);
- },
- // Get cached image by id
- cachedImage: function cachedImage(imageId) {
- return _mlyCache.images.forImageId[imageId];
- },
- // Get visible sequences
- sequences: function sequences(projection) {
- var viewport = projection.clipExtent();
- var min = [viewport[0][0], viewport[1][1]];
- var max = [viewport[1][0], viewport[0][1]];
- var bbox = geoExtent(projection.invert(min), projection.invert(max)).bbox();
- var sequenceIds = {};
- var lineStrings = [];
+ /**
+ * Converts the characters "&", "<", ">", '"', and "'" in `string` to their
+ * corresponding HTML entities.
+ *
+ * **Note:** No other characters are escaped. To escape additional
+ * characters use a third-party library like [_he_](https://mths.be/he).
+ *
+ * Though the ">" character is escaped for symmetry, characters like
+ * ">" and "/" don't need escaping in HTML and have no special meaning
+ * unless they're part of a tag or unquoted attribute value. See
+ * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+ * (under "semi-related fun fact") for more details.
+ *
+ * When working with HTML you should always
+ * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+ * XSS vectors.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escape('fred, barney, & pebbles');
+ * // => 'fred, barney, & pebbles'
+ */
- _mlyCache.images.rtree.search(bbox).forEach(function (d) {
- if (d.data.sequence_id) {
- sequenceIds[d.data.sequence_id] = true;
+
+ function escape(string) {
+ string = toString(string);
+ return string && reHasUnescapedHtml.test(string) ? string.replace(reUnescapedHtml, escapeHtmlChar) : string;
}
- });
+ /**
+ * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+ * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to escape.
+ * @returns {string} Returns the escaped string.
+ * @example
+ *
+ * _.escapeRegExp('[lodash](https://lodash.com/)');
+ * // => '\[lodash\]\(https://lodash\.com/\)'
+ */
- Object.keys(sequenceIds).forEach(function (sequenceId) {
- if (_mlyCache.sequences.lineString[sequenceId]) {
- lineStrings = lineStrings.concat(_mlyCache.sequences.lineString[sequenceId]);
+
+ function escapeRegExp(string) {
+ string = toString(string);
+ return string && reHasRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string;
}
- });
- return lineStrings;
- },
- // Load images in the visible area
- loadImages: function loadImages(projection) {
- loadTiles$2('images', tileUrl, 14, projection);
- },
- // Load traffic signs in the visible area
- loadSigns: function loadSigns(projection) {
- loadTiles$2('signs', trafficSignTileUrl, 14, projection);
- },
- // Load map (point) features in the visible area
- loadMapFeatures: function loadMapFeatures(projection) {
- loadTiles$2('points', mapFeatureTileUrl, 14, projection);
- },
- // Return a promise that resolves when the image viewer (Mapillary JS) library has finished loading
- ensureViewerLoaded: function ensureViewerLoaded(context) {
- if (_loadViewerPromise$2) return _loadViewerPromise$2; // add mly-wrapper
+ /**
+ * Converts `string` to
+ * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the kebab cased string.
+ * @example
+ *
+ * _.kebabCase('Foo Bar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('fooBar');
+ * // => 'foo-bar'
+ *
+ * _.kebabCase('__FOO_BAR__');
+ * // => 'foo-bar'
+ */
- var wrap = context.container().select('.photoviewer').selectAll('.mly-wrapper').data([0]);
- wrap.enter().append('div').attr('id', 'ideditor-mly').attr('class', 'photo-wrapper mly-wrapper').classed('hide', true);
- var that = this;
- _loadViewerPromise$2 = new Promise(function (resolve, reject) {
- var loadedCount = 0;
- function loaded() {
- loadedCount += 1; // wait until both files are loaded
+ var kebabCase = createCompounder(function (result, word, index) {
+ return result + (index ? '-' : '') + word.toLowerCase();
+ });
+ /**
+ * Converts `string`, as space separated words, to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the lower cased string.
+ * @example
+ *
+ * _.lowerCase('--Foo-Bar--');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('fooBar');
+ * // => 'foo bar'
+ *
+ * _.lowerCase('__FOO_BAR__');
+ * // => 'foo bar'
+ */
- if (loadedCount === 2) resolve();
- }
+ var lowerCase = createCompounder(function (result, word, index) {
+ return result + (index ? ' ' : '') + word.toLowerCase();
+ });
+ /**
+ * Converts the first character of `string` to lower case.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the converted string.
+ * @example
+ *
+ * _.lowerFirst('Fred');
+ * // => 'fred'
+ *
+ * _.lowerFirst('FRED');
+ * // => 'fRED'
+ */
- var head = select('head'); // load mapillary-viewercss
+ var lowerFirst = createCaseFirst('toLowerCase');
+ /**
+ * Pads `string` on the left and right sides if it's shorter than `length`.
+ * Padding characters are truncated if they can't be evenly divided by `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.pad('abc', 8);
+ * // => ' abc '
+ *
+ * _.pad('abc', 8, '_-');
+ * // => '_-abc_-_'
+ *
+ * _.pad('abc', 3);
+ * // => 'abc'
+ */
- head.selectAll('#ideditor-mapillary-viewercss').data([0]).enter().append('link').attr('id', 'ideditor-mapillary-viewercss').attr('rel', 'stylesheet').attr('crossorigin', 'anonymous').attr('href', context.asset(viewercss)).on('load.serviceMapillary', loaded).on('error.serviceMapillary', function () {
- reject();
- }); // load mapillary-viewerjs
+ function pad(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+ var strLength = length ? stringSize(string) : 0;
- head.selectAll('#ideditor-mapillary-viewerjs').data([0]).enter().append('script').attr('id', 'ideditor-mapillary-viewerjs').attr('crossorigin', 'anonymous').attr('src', context.asset(viewerjs)).on('load.serviceMapillary', loaded).on('error.serviceMapillary', function () {
- reject();
- });
- })["catch"](function () {
- _loadViewerPromise$2 = null;
- }).then(function () {
- that.initViewer(context);
- });
- return _loadViewerPromise$2;
- },
- // Load traffic sign image sprites
- loadSignResources: function loadSignResources(context) {
- context.ui().svgDefs.addSprites(['mapillary-sprite'], false
- /* don't override colors */
- );
- return this;
- },
- // Load map (point) feature image sprites
- loadObjectResources: function loadObjectResources(context) {
- context.ui().svgDefs.addSprites(['mapillary-object-sprite'], false
- /* don't override colors */
- );
- return this;
- },
- // Remove previous detections in image viewer
- resetTags: function resetTags() {
- if (_mlyViewer && !_mlyFallback) {
- _mlyViewer.getComponent('tag').removeAll();
- }
- },
- // Show map feature detections in image viewer
- showFeatureDetections: function showFeatureDetections(value) {
- _mlyShowFeatureDetections = value;
+ if (!length || strLength >= length) {
+ return string;
+ }
- if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
- this.resetTags();
- }
- },
- // Show traffic sign detections in image viewer
- showSignDetections: function showSignDetections(value) {
- _mlyShowSignDetections = value;
+ var mid = (length - strLength) / 2;
+ return createPadding(nativeFloor(mid), chars) + string + createPadding(nativeCeil(mid), chars);
+ }
+ /**
+ * Pads `string` on the right side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padEnd('abc', 6);
+ * // => 'abc '
+ *
+ * _.padEnd('abc', 6, '_-');
+ * // => 'abc_-_'
+ *
+ * _.padEnd('abc', 3);
+ * // => 'abc'
+ */
- if (!_mlyShowFeatureDetections && !_mlyShowSignDetections) {
- this.resetTags();
- }
- },
- // Apply filter to image viewer
- filterViewer: function filterViewer(context) {
- var showsPano = context.photos().showsPanoramic();
- var showsFlat = context.photos().showsFlat();
- var fromDate = context.photos().fromDate();
- var toDate = context.photos().toDate();
- var filter = ['all'];
- if (!showsPano) filter.push(['!=', 'cameraType', 'spherical']);
- if (!showsFlat && showsPano) filter.push(['==', 'pano', true]);
- if (fromDate) {
- filter.push(['>=', 'capturedAt', new Date(fromDate).getTime()]);
- }
+ function padEnd(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+ var strLength = length ? stringSize(string) : 0;
+ return length && strLength < length ? string + createPadding(length - strLength, chars) : string;
+ }
+ /**
+ * Pads `string` on the left side if it's shorter than `length`. Padding
+ * characters are truncated if they exceed `length`.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to pad.
+ * @param {number} [length=0] The padding length.
+ * @param {string} [chars=' '] The string used as padding.
+ * @returns {string} Returns the padded string.
+ * @example
+ *
+ * _.padStart('abc', 6);
+ * // => ' abc'
+ *
+ * _.padStart('abc', 6, '_-');
+ * // => '_-_abc'
+ *
+ * _.padStart('abc', 3);
+ * // => 'abc'
+ */
- if (toDate) {
- filter.push(['>=', 'capturedAt', new Date(toDate).getTime()]);
- }
- if (_mlyViewer) {
- _mlyViewer.setFilter(filter);
- }
+ function padStart(string, length, chars) {
+ string = toString(string);
+ length = toInteger(length);
+ var strLength = length ? stringSize(string) : 0;
+ return length && strLength < length ? createPadding(length - strLength, chars) + string : string;
+ }
+ /**
+ * Converts `string` to an integer of the specified radix. If `radix` is
+ * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
+ * hexadecimal, in which case a `radix` of `16` is used.
+ *
+ * **Note:** This method aligns with the
+ * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+ *
+ * @static
+ * @memberOf _
+ * @since 1.1.0
+ * @category String
+ * @param {string} string The string to convert.
+ * @param {number} [radix=10] The radix to interpret `value` by.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {number} Returns the converted integer.
+ * @example
+ *
+ * _.parseInt('08');
+ * // => 8
+ *
+ * _.map(['6', '08', '10'], _.parseInt);
+ * // => [6, 8, 10]
+ */
- _mlyViewerFilter = filter;
- return filter;
- },
- // Make the image viewer visible
- showViewer: function showViewer(context) {
- var wrap = context.container().select('.photoviewer').classed('hide', false);
- var isHidden = wrap.selectAll('.photo-wrapper.mly-wrapper.hide').size();
- if (isHidden && _mlyViewer) {
- wrap.selectAll('.photo-wrapper:not(.mly-wrapper)').classed('hide', true);
- wrap.selectAll('.photo-wrapper.mly-wrapper').classed('hide', false);
+ function parseInt(string, radix, guard) {
+ if (guard || radix == null) {
+ radix = 0;
+ } else if (radix) {
+ radix = +radix;
+ }
- _mlyViewer.resize();
- }
+ return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0);
+ }
+ /**
+ * Repeats the given string `n` times.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to repeat.
+ * @param {number} [n=1] The number of times to repeat the string.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {string} Returns the repeated string.
+ * @example
+ *
+ * _.repeat('*', 3);
+ * // => '***'
+ *
+ * _.repeat('abc', 2);
+ * // => 'abcabc'
+ *
+ * _.repeat('abc', 0);
+ * // => ''
+ */
- return this;
- },
- // Hide the image viewer and resets map markers
- hideViewer: function hideViewer(context) {
- _mlyActiveImage = null;
- if (!_mlyFallback && _mlyViewer) {
- _mlyViewer.getComponent('sequence').stop();
- }
+ function repeat(string, n, guard) {
+ if (guard ? isIterateeCall(string, n, guard) : n === undefined$1) {
+ n = 1;
+ } else {
+ n = toInteger(n);
+ }
- var viewer = context.container().select('.photoviewer');
- if (!viewer.empty()) viewer.datum(null);
- viewer.classed('hide', true).selectAll('.photo-wrapper').classed('hide', true);
- this.updateUrlImage(null);
- dispatch$4.call('imageChanged');
- dispatch$4.call('loadedMapFeatures');
- dispatch$4.call('loadedSigns');
- return this.setStyles(context, null);
- },
- // Update the URL with current image id
- updateUrlImage: function updateUrlImage(imageId) {
- if (!window.mocha) {
- var hash = utilStringQs(window.location.hash);
+ return baseRepeat(toString(string), n);
+ }
+ /**
+ * Replaces matches for `pattern` in `string` with `replacement`.
+ *
+ * **Note:** This method is based on
+ * [`String#replace`](https://mdn.io/String/replace).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to modify.
+ * @param {RegExp|string} pattern The pattern to replace.
+ * @param {Function|string} replacement The match replacement.
+ * @returns {string} Returns the modified string.
+ * @example
+ *
+ * _.replace('Hi Fred', 'Fred', 'Barney');
+ * // => 'Hi Barney'
+ */
- if (imageId) {
- hash.photo = 'mapillary/' + imageId;
- } else {
- delete hash.photo;
+
+ function replace() {
+ var args = arguments,
+ string = toString(args[0]);
+ return args.length < 3 ? string : string.replace(args[1], args[2]);
}
+ /**
+ * Converts `string` to
+ * [snake case](https://en.wikipedia.org/wiki/Snake_case).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the snake cased string.
+ * @example
+ *
+ * _.snakeCase('Foo Bar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('fooBar');
+ * // => 'foo_bar'
+ *
+ * _.snakeCase('--FOO-BAR--');
+ * // => 'foo_bar'
+ */
- window.location.replace('#' + utilQsString(hash, true));
- }
- },
- // Highlight the detection in the viewer that is related to the clicked map feature
- highlightDetection: function highlightDetection(detection) {
- if (detection) {
- _mlyHighlightedDetection = detection.id;
- }
- return this;
- },
- // Initialize image viewer (Mapillar JS)
- initViewer: function initViewer(context) {
- var that = this;
- if (!window.mapillary) return;
- var opts = {
- accessToken: accessToken,
- component: {
- cover: false,
- keyboard: false,
- tag: true
- },
- container: 'ideditor-mly'
- }; // Disable components requiring WebGL support
+ var snakeCase = createCompounder(function (result, word, index) {
+ return result + (index ? '_' : '') + word.toLowerCase();
+ });
+ /**
+ * Splits `string` by `separator`.
+ *
+ * **Note:** This method is based on
+ * [`String#split`](https://mdn.io/String/split).
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category String
+ * @param {string} [string=''] The string to split.
+ * @param {RegExp|string} separator The separator pattern to split by.
+ * @param {number} [limit] The length to truncate results to.
+ * @returns {Array} Returns the string segments.
+ * @example
+ *
+ * _.split('a-b-c', '-', 2);
+ * // => ['a', 'b']
+ */
- if (!mapillary.isSupported() && mapillary.isFallbackSupported()) {
- _mlyFallback = true;
- opts.component = {
- cover: false,
- direction: false,
- imagePlane: false,
- keyboard: false,
- mouse: false,
- sequence: false,
- tag: false,
- image: true,
- // fallback
- navigation: true // fallback
+ function split(string, separator, limit) {
+ if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+ separator = limit = undefined$1;
+ }
- };
- }
+ limit = limit === undefined$1 ? MAX_ARRAY_LENGTH : limit >>> 0;
- _mlyViewer = new mapillary.Viewer(opts);
+ if (!limit) {
+ return [];
+ }
- _mlyViewer.on('image', imageChanged);
+ string = toString(string);
- _mlyViewer.on('bearing', bearingChanged);
+ if (string && (typeof separator == 'string' || separator != null && !isRegExp(separator))) {
+ separator = baseToString(separator);
- if (_mlyViewerFilter) {
- _mlyViewer.setFilter(_mlyViewerFilter);
- } // Register viewer resize handler
+ if (!separator && hasUnicode(string)) {
+ return castSlice(stringToArray(string), 0, limit);
+ }
+ }
+ return string.split(separator, limit);
+ }
+ /**
+ * Converts `string` to
+ * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+ *
+ * @static
+ * @memberOf _
+ * @since 3.1.0
+ * @category String
+ * @param {string} [string=''] The string to convert.
+ * @returns {string} Returns the start cased string.
+ * @example
+ *
+ * _.startCase('--foo-bar--');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('fooBar');
+ * // => 'Foo Bar'
+ *
+ * _.startCase('__FOO_BAR__');
+ * // => 'FOO BAR'
+ */
- context.ui().photoviewer.on('resize.mapillary', function () {
- if (_mlyViewer) _mlyViewer.resize();
- }); // imageChanged: called after the viewer has changed images and is ready.
- function imageChanged(node) {
- that.resetTags();
- var image = node.image;
- that.setActiveImage(image);
- that.setStyles(context, null);
- var loc = [image.originalLngLat.lng, image.originalLngLat.lat];
- context.map().centerEase(loc);
- that.updateUrlImage(image.id);
+ var startCase = createCompounder(function (result, word, index) {
+ return result + (index ? ' ' : '') + upperFirst(word);
+ });
+ /**
+ * Checks if `string` starts with the given target string.
+ *
+ * @static
+ * @memberOf _
+ * @since 3.0.0
+ * @category String
+ * @param {string} [string=''] The string to inspect.
+ * @param {string} [target] The string to search for.
+ * @param {number} [position=0] The position to search from.
+ * @returns {boolean} Returns `true` if `string` starts with `target`,
+ * else `false`.
+ * @example
+ *
+ * _.startsWith('abc', 'a');
+ * // => true
+ *
+ * _.startsWith('abc', 'b');
+ * // => false
+ *
+ * _.startsWith('abc', 'b', 1);
+ * // => true
+ */
- if (_mlyShowFeatureDetections || _mlyShowSignDetections) {
- that.updateDetections(image.id, "".concat(apiUrl, "/").concat(image.id, "/detections?access_token=").concat(accessToken, "&fields=id,image,geometry,value"));
+ function startsWith(string, target, position) {
+ string = toString(string);
+ position = position == null ? 0 : baseClamp(toInteger(position), 0, string.length);
+ target = baseToString(target);
+ return string.slice(position, position + target.length) == target;
}
+ /**
+ * Creates a compiled template function that can interpolate data properties
+ * in "interpolate" delimiters, HTML-escape interpolated data properties in
+ * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+ * properties may be accessed as free variables in the template. If a setting
+ * object is given, it takes precedence over `_.templateSettings` values.
+ *
+ * **Note:** In the development build `_.template` utilizes
+ * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+ * for easier debugging.
+ *
+ * For more information on precompiling templates see
+ * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+ *
+ * For more information on Chrome extension sandboxes see
+ * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category String
+ * @param {string} [string=''] The template string.
+ * @param {Object} [options={}] The options object.
+ * @param {RegExp} [options.escape=_.templateSettings.escape]
+ * The HTML "escape" delimiter.
+ * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
+ * The "evaluate" delimiter.
+ * @param {Object} [options.imports=_.templateSettings.imports]
+ * An object to import into the template as free variables.
+ * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
+ * The "interpolate" delimiter.
+ * @param {string} [options.sourceURL='lodash.templateSources[n]']
+ * The sourceURL of the compiled template.
+ * @param {string} [options.variable='obj']
+ * The data object variable name.
+ * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+ * @returns {Function} Returns the compiled template function.
+ * @example
+ *
+ * // Use the "interpolate" delimiter to create a compiled template.
+ * var compiled = _.template('hello <%= user %>!');
+ * compiled({ 'user': 'fred' });
+ * // => 'hello fred!'
+ *
+ * // Use the HTML "escape" delimiter to escape data property values.
+ * var compiled = _.template('<%- value %>');
+ * compiled({ 'value': '